GCC Code Coverage Report
Directory: . Exec Total Coverage
File: src/theory/quantifiers/sygus/sygus_unif_io.cpp Lines: 747 823 90.8 %
Date: 2021-09-10 Branches: 1343 3290 40.8 %

Line Exec Source
1
/******************************************************************************
2
 * Top contributors (to current version):
3
 *   Andrew Reynolds, Mathias Preiner, Aina Niemetz
4
 *
5
 * This file is part of the cvc5 project.
6
 *
7
 * Copyright (c) 2009-2021 by the authors listed in the file AUTHORS
8
 * in the top-level source directory and their institutional affiliations.
9
 * All rights reserved.  See the file COPYING in the top-level source
10
 * directory for licensing information.
11
 * ****************************************************************************
12
 *
13
 * Implementation of sygus_unif_io.
14
 */
15
16
#include "theory/quantifiers/sygus/sygus_unif_io.h"
17
18
#include "options/quantifiers_options.h"
19
#include "theory/datatypes/sygus_datatype_utils.h"
20
#include "theory/evaluator.h"
21
#include "theory/quantifiers/sygus/example_infer.h"
22
#include "theory/quantifiers/sygus/synth_conjecture.h"
23
#include "theory/quantifiers/sygus/term_database_sygus.h"
24
#include "theory/quantifiers/term_util.h"
25
#include "theory/rewriter.h"
26
#include "theory/strings/word.h"
27
#include "util/random.h"
28
29
#include <math.h>
30
31
using namespace cvc5::kind;
32
33
namespace cvc5 {
34
namespace theory {
35
namespace quantifiers {
36
37
47
UnifContextIo::UnifContextIo() : d_curr_role(role_invalid)
38
{
39
47
  d_true = NodeManager::currentNM()->mkConst(true);
40
47
  d_false = NodeManager::currentNM()->mkConst(false);
41
47
}
42
43
10575
NodeRole UnifContextIo::getCurrentRole() { return d_curr_role; }
44
45
620
bool UnifContextIo::updateContext(SygusUnifIo* sui,
46
                                  std::vector<Node>& vals,
47
                                  bool pol)
48
{
49
620
  Assert(d_vals.size() == vals.size());
50
620
  bool changed = false;
51
1240
  Node poln = pol ? d_true : d_false;
52
13593
  for (size_t i = 0, vsize = vals.size(); i < vsize; i++)
53
  {
54
25946
    Node v = vals[i];
55
12973
    if (v.isNull())
56
    {
57
      // nothing can be inferred if the evaluation is unknown, e.g. if using
58
      // partial functions.
59
      continue;
60
    }
61
12973
    if (v != poln)
62
    {
63
6464
      if (d_vals[i] == d_true)
64
      {
65
5224
        d_vals[i] = d_false;
66
5224
        changed = true;
67
      }
68
    }
69
  }
70
620
  if (changed)
71
  {
72
618
    d_visit_role.clear();
73
  }
74
1240
  return changed;
75
}
76
77
1097
bool UnifContextIo::updateStringPosition(SygusUnifIo* sui,
78
                                         std::vector<size_t>& pos,
79
                                         NodeRole nrole)
80
{
81
1097
  Assert(pos.size() == d_str_pos.size());
82
1097
  bool changed = false;
83
13102
  for (unsigned i = 0; i < pos.size(); i++)
84
  {
85
12005
    if (pos[i] > 0)
86
    {
87
7787
      d_str_pos[i] += pos[i];
88
7787
      changed = true;
89
    }
90
  }
91
1097
  if (changed)
92
  {
93
1067
    d_visit_role.clear();
94
  }
95
1097
  d_curr_role = nrole;
96
1097
  return changed;
97
}
98
99
532
void UnifContextIo::initialize(SygusUnifIo* sui)
100
{
101
  // clear previous data
102
532
  d_vals.clear();
103
532
  d_str_pos.clear();
104
532
  d_curr_role = role_equal;
105
532
  d_visit_role.clear();
106
107
  // initialize with #examples
108
532
  unsigned sz = sui->d_examples.size();
109
9014
  for (unsigned i = 0; i < sz; i++)
110
  {
111
8482
    d_vals.push_back(d_true);
112
  }
113
114
532
  if (!sui->d_examples_out.empty())
115
  {
116
    // output type of the examples
117
1064
    TypeNode exotn = sui->d_examples_out[0].getType();
118
119
532
    if (exotn.isStringLike())
120
    {
121
3924
      for (unsigned i = 0; i < sz; i++)
122
      {
123
3690
        d_str_pos.push_back(0);
124
      }
125
    }
126
  }
127
532
  d_visit_role.clear();
128
532
}
129
130
2873
void UnifContextIo::getCurrentStrings(SygusUnifIo* sui,
131
                                      const std::vector<Node>& vals,
132
                                      std::vector<Node>& ex_vals)
133
{
134
2873
  bool isPrefix = d_curr_role == role_string_prefix;
135
5746
  Node dummy;
136
39082
  for (unsigned i = 0; i < vals.size(); i++)
137
  {
138
36209
    if (d_vals[i] == sui->d_true)
139
    {
140
26391
      Assert(vals[i].isConst());
141
26391
      unsigned pos_value = d_str_pos[i];
142
26391
      if (pos_value > 0)
143
      {
144
17796
        Assert(d_curr_role != role_invalid);
145
35592
        Node s = vals[i];
146
17796
        size_t sSize = strings::Word::getLength(s);
147
17796
        Assert(pos_value <= sSize);
148
17796
        ex_vals.push_back(isPrefix
149
35592
                              ? strings::Word::suffix(s, sSize - pos_value)
150
                              : strings::Word::prefix(s, sSize - pos_value));
151
      }
152
      else
153
      {
154
8595
        ex_vals.push_back(vals[i]);
155
      }
156
    }
157
    else
158
    {
159
      // irrelevant, add dummy
160
9818
      ex_vals.push_back(dummy);
161
    }
162
  }
163
2873
}
164
165
7676
bool UnifContextIo::getStringIncrement(SygusUnifIo* sui,
166
                                       bool isPrefix,
167
                                       const std::vector<Node>& ex_vals,
168
                                       const std::vector<Node>& vals,
169
                                       std::vector<size_t>& inc,
170
                                       size_t& tot)
171
{
172
31548
  for (unsigned j = 0; j < vals.size(); j++)
173
  {
174
30178
    size_t ival = 0;
175
30178
    if (d_vals[j] == sui->d_true)
176
    {
177
      // example is active in this context
178
16678
      if (!vals[j].isConst())
179
      {
180
        // the value is unknown, thus we cannot use it to increment the strings
181
        // position
182
6311
        return false;
183
      }
184
16673
      ival = strings::Word::getLength(vals[j]);
185
16673
      size_t exjLen = strings::Word::getLength(ex_vals[j]);
186
16673
      if (ival <= exjLen)
187
      {
188
52252
        if (!(isPrefix ? strings::Word::strncmp(ex_vals[j], vals[j], ival)
189
35948
                       : strings::Word::rstrncmp(ex_vals[j], vals[j], ival)))
190
        {
191
5932
          Trace("sygus-sui-dt-debug") << "X";
192
5932
          return false;
193
        }
194
      }
195
      else
196
      {
197
369
        Trace("sygus-sui-dt-debug") << "X";
198
369
        return false;
199
      }
200
10372
      Trace("sygus-sui-dt-debug") << ival;
201
10372
      tot += ival;
202
    }
203
    else
204
    {
205
      // inactive in this context
206
13500
      Trace("sygus-sui-dt-debug") << "-";
207
    }
208
23872
    inc.push_back(ival);
209
  }
210
1370
  return true;
211
}
212
8561
bool UnifContextIo::isStringSolved(SygusUnifIo* sui,
213
                                   const std::vector<Node>& ex_vals,
214
                                   const std::vector<Node>& vals)
215
{
216
25204
  for (unsigned j = 0; j < vals.size(); j++)
217
  {
218
25132
    if (d_vals[j] == sui->d_true)
219
    {
220
      // example is active in this context
221
8668
      if (!vals[j].isConst())
222
      {
223
        // value is unknown, thus it does not solve
224
7
        return false;
225
      }
226
8661
      if (ex_vals[j] != vals[j])
227
      {
228
8482
        return false;
229
      }
230
    }
231
  }
232
72
  return true;
233
}
234
235
// status : 0 : exact, -1 : vals is subset, 1 : vals is superset
236
16608
Node SubsumeTrie::addTermInternal(Node t,
237
                                  const std::vector<Node>& vals,
238
                                  bool pol,
239
                                  std::vector<Node>& subsumed,
240
                                  bool spol,
241
                                  unsigned index,
242
                                  int status,
243
                                  bool checkExistsOnly,
244
                                  bool checkSubsume)
245
{
246
16608
  if (index == vals.size())
247
  {
248
864
    if (status == 0)
249
    {
250
      // set the term if checkExistsOnly = false
251
633
      if (d_term.isNull() && !checkExistsOnly)
252
      {
253
438
        d_term = t;
254
      }
255
    }
256
231
    else if (status == 1)
257
    {
258
190
      Assert(checkSubsume);
259
      // found a subsumed term
260
190
      if (!d_term.isNull())
261
      {
262
190
        subsumed.push_back(d_term);
263
        // If we are only interested in feasibility, we could set d_term to null
264
        // here. However, d_term still could be useful, since it may be
265
        // smaller than t and suffice as a solution under some condition.
266
        // As a simple example, consider predicate synthesis and a case where we
267
        // enumerate a C that is correct for all I/O points whose output is
268
        // true. Then, C subsumes true. However, true may be preferred, e.g.
269
        // to generate a solution ite( C, true, D ) instead of ite( C, C, D ),
270
        // since true is conditionally correct under C, and is smaller than C.
271
      }
272
    }
273
    else
274
    {
275
41
      Assert(!checkExistsOnly && checkSubsume);
276
    }
277
864
    return d_term;
278
  }
279
15744
  NodeManager* nm = NodeManager::currentNM();
280
  // the current value
281
15744
  Assert(pol || (vals[index].isConst() && vals[index].getType().isBoolean()));
282
31488
  Node cv = pol ? vals[index] : nm->mkConst(!vals[index].getConst<bool>());
283
  // if checkExistsOnly = false, check if the current value is subsumed if
284
  // checkSubsume = true, if so, don't add
285
15744
  if (!checkExistsOnly && checkSubsume)
286
  {
287
1311
    Assert(cv.isConst() && cv.getType().isBoolean());
288
2512
    std::vector<bool> check_subsumed_by;
289
1311
    if (status == 0)
290
    {
291
1025
      if (!cv.getConst<bool>())
292
      {
293
510
        check_subsumed_by.push_back(spol);
294
      }
295
    }
296
286
    else if (status == -1)
297
    {
298
99
      check_subsumed_by.push_back(spol);
299
99
      if (!cv.getConst<bool>())
300
      {
301
54
        check_subsumed_by.push_back(!spol);
302
      }
303
    }
304
    // check for subsumed nodes
305
1853
    for (unsigned i = 0, size = check_subsumed_by.size(); i < size; i++)
306
    {
307
652
      bool csbi = check_subsumed_by[i];
308
1194
      Node csval = nm->mkConst(csbi);
309
      // check if subsumed
310
652
      std::map<Node, SubsumeTrie>::iterator itc = d_children.find(csval);
311
652
      if (itc != d_children.end())
312
      {
313
140
        Node ret = itc->second.addTermInternal(t,
314
                                               vals,
315
                                               pol,
316
                                               subsumed,
317
                                               spol,
318
                                               index + 1,
319
                                               -1,
320
                                               checkExistsOnly,
321
170
                                               checkSubsume);
322
        // ret subsumes t
323
140
        if (!ret.isNull())
324
        {
325
110
          return ret;
326
        }
327
      }
328
    }
329
  }
330
31268
  Node ret;
331
31268
  std::vector<bool> check_subsume;
332
15634
  if (status == 0)
333
  {
334
11006
    if (checkExistsOnly)
335
    {
336
      std::map<Node, SubsumeTrie>::iterator itc = d_children.find(cv);
337
      if (itc != d_children.end())
338
      {
339
        ret = itc->second.addTermInternal(t,
340
                                          vals,
341
                                          pol,
342
                                          subsumed,
343
                                          spol,
344
                                          index + 1,
345
                                          0,
346
                                          checkExistsOnly,
347
                                          checkSubsume);
348
      }
349
    }
350
    else
351
    {
352
11006
      Assert(spol);
353
11006
      ret = d_children[cv].addTermInternal(t,
354
                                           vals,
355
                                           pol,
356
                                           subsumed,
357
                                           spol,
358
                                           index + 1,
359
                                           0,
360
                                           checkExistsOnly,
361
                                           checkSubsume);
362
11006
      if (ret != t)
363
      {
364
        // we were subsumed by ret, return
365
3941
        return ret;
366
      }
367
    }
368
7065
    if (checkSubsume)
369
    {
370
778
      Assert(cv.isConst() && cv.getType().isBoolean());
371
      // check for subsuming
372
778
      if (cv.getConst<bool>())
373
      {
374
419
        check_subsume.push_back(!spol);
375
      }
376
    }
377
  }
378
4628
  else if (status == 1)
379
  {
380
4598
    Assert(checkSubsume);
381
4598
    Assert(cv.isConst() && cv.getType().isBoolean());
382
4598
    check_subsume.push_back(!spol);
383
4598
    if (cv.getConst<bool>())
384
    {
385
2843
      check_subsume.push_back(spol);
386
    }
387
  }
388
11693
  if (checkSubsume)
389
  {
390
    // check for subsumed terms
391
13266
    for (unsigned i = 0, size = check_subsume.size(); i < size; i++)
392
    {
393
15720
      Node csval = nm->mkConst<bool>(check_subsume[i]);
394
7860
      std::map<Node, SubsumeTrie>::iterator itc = d_children.find(csval);
395
7860
      if (itc != d_children.end())
396
      {
397
3748
        itc->second.addTermInternal(t,
398
                                    vals,
399
                                    pol,
400
                                    subsumed,
401
                                    spol,
402
                                    index + 1,
403
                                    1,
404
                                    checkExistsOnly,
405
                                    checkSubsume);
406
        // clean up
407
3748
        if (itc->second.isEmpty())
408
        {
409
          Assert(!checkExistsOnly);
410
          d_children.erase(csval);
411
        }
412
      }
413
    }
414
  }
415
11693
  return ret;
416
}
417
418
139
Node SubsumeTrie::addTerm(Node t,
419
                          const std::vector<Node>& vals,
420
                          bool pol,
421
                          std::vector<Node>& subsumed)
422
{
423
139
  return addTermInternal(t, vals, pol, subsumed, true, 0, 0, false, true);
424
}
425
426
535
Node SubsumeTrie::addCond(Node c, const std::vector<Node>& vals, bool pol)
427
{
428
1070
  std::vector<Node> subsumed;
429
1070
  return addTermInternal(c, vals, pol, subsumed, true, 0, 0, false, false);
430
}
431
432
void SubsumeTrie::getSubsumed(const std::vector<Node>& vals,
433
                              bool pol,
434
                              std::vector<Node>& subsumed)
435
{
436
  addTermInternal(Node::null(), vals, pol, subsumed, true, 0, 1, true, true);
437
}
438
439
1040
void SubsumeTrie::getSubsumedBy(const std::vector<Node>& vals,
440
                                bool pol,
441
                                std::vector<Node>& subsumed_by)
442
{
443
  // flip polarities
444
2080
  addTermInternal(
445
3120
      Node::null(), vals, !pol, subsumed_by, false, 0, 1, true, true);
446
1040
}
447
448
62749
void SubsumeTrie::getLeavesInternal(const std::vector<Node>& vals,
449
                                    bool pol,
450
                                    std::map<int, std::vector<Node> >& v,
451
                                    unsigned index,
452
                                    int status)
453
{
454
62749
  if (index == vals.size())
455
  {
456
    // by convention, if we did not test any points, then we consider the
457
    // evaluation along the current path to be always false.
458
3258
    int rstatus = status == -2 ? -1 : status;
459
3258
    Assert(!d_term.isNull());
460
3258
    Assert(std::find(v[rstatus].begin(), v[rstatus].end(), d_term)
461
           == v[rstatus].end());
462
3258
    v[rstatus].push_back(d_term);
463
  }
464
  else
465
  {
466
59491
    Assert(vals[index].isConst() && vals[index].getType().isBoolean());
467
59491
    bool curr_val_true = vals[index].getConst<bool>() == pol;
468
121016
    for (std::map<Node, SubsumeTrie>::iterator it = d_children.begin();
469
121016
         it != d_children.end();
470
         ++it)
471
    {
472
61525
      int new_status = status;
473
61525
      bool success = true;
474
      // If the current value is true, then this is a relevant point.
475
      // We must consider the value of this child.
476
61525
      if (curr_val_true)
477
      {
478
37788
        if (it->first.isNull())
479
        {
480
          // The value of this child is unknown on this point, hence we
481
          // do not recurse
482
28
          success = false;
483
        }
484
37760
        else if (status != 0)
485
        {
486
          // if the status is not zero (indicating that we have a mix of T/F),
487
          // then we must compute the new status.
488
27590
          Assert(it->first.getType().isBoolean());
489
27590
          Assert(it->first.isConst());
490
27590
          new_status = (it->first.getConst<bool>() ? 1 : -1);
491
27590
          if (status != -2 && new_status != status)
492
          {
493
867
            new_status = 0;
494
          }
495
        }
496
      }
497
61525
      if (success)
498
      {
499
61497
        it->second.getLeavesInternal(vals, pol, v, index + 1, new_status);
500
      }
501
    }
502
  }
503
62749
}
504
505
1252
void SubsumeTrie::getLeaves(const std::vector<Node>& vals,
506
                            bool pol,
507
                            std::map<int, std::vector<Node> >& v)
508
{
509
1252
  getLeavesInternal(vals, pol, v, 0, -2);
510
1252
}
511
512
47
SygusUnifIo::SygusUnifIo(Env& env, SynthConjecture* p)
513
    : SygusUnif(env),
514
      d_parent(p),
515
      d_check_sol(false),
516
      d_cond_count(0),
517
      d_sol_term_size(0),
518
      d_sol_cons_nondet(false),
519
47
      d_solConsUsingInfoGain(false)
520
{
521
47
  d_true = NodeManager::currentNM()->mkConst(true);
522
47
  d_false = NodeManager::currentNM()->mkConst(false);
523
47
}
524
525
94
SygusUnifIo::~SygusUnifIo() {}
526
527
47
void SygusUnifIo::initializeCandidate(
528
    TermDbSygus* tds,
529
    Node f,
530
    std::vector<Node>& enums,
531
    std::map<Node, std::vector<Node>>& strategy_lemmas)
532
{
533
47
  d_candidate = f;
534
  // copy the examples from the parent
535
47
  ExampleInfer* ei = d_parent->getExampleInfer();
536
47
  d_examples.clear();
537
47
  d_examples_out.clear();
538
  // copy the examples
539
47
  if (ei->hasExamples(f))
540
  {
541
435
    for (unsigned i = 0, nex = ei->getNumExamples(f); i < nex; i++)
542
    {
543
776
      std::vector<Node> input;
544
388
      ei->getExample(f, i, input);
545
776
      Node output = ei->getExampleOut(f, i);
546
388
      d_examples.push_back(input);
547
388
      d_examples_out.push_back(output);
548
    }
549
  }
550
47
  d_ecache.clear();
551
47
  SygusUnif::initializeCandidate(tds, f, enums, strategy_lemmas);
552
  // learn redundant operators based on the strategy
553
47
  d_strategy.at(f).staticLearnRedundantOps(strategy_lemmas);
554
47
}
555
556
583
void SygusUnifIo::notifyEnumeration(Node e, Node v, std::vector<Node>& lemmas)
557
{
558
1166
  Trace("sygus-sui-enum") << "Notify enumeration for " << e << " : " << v
559
583
                          << std::endl;
560
1166
  Node c = d_candidate;
561
583
  Assert(!d_examples.empty());
562
583
  Assert(d_examples.size() == d_examples_out.size());
563
564
583
  EnumInfo& ei = d_strategy.at(c).getEnumInfo(e);
565
  // The explanation for why the current value should be excluded in future
566
  // iterations.
567
1166
  Node exp_exc;
568
569
1166
  std::vector<Node> base_results;
570
1166
  TypeNode xtn = e.getType();
571
1166
  Node bv = d_tds->sygusToBuiltin(v, xtn);
572
583
  bv = extendedRewrite(bv);
573
583
  Trace("sygus-sui-enum") << "PBE Compute Examples for " << bv << std::endl;
574
  // compte the results (should be cached)
575
583
  ExampleEvalCache* eec = d_parent->getExampleEvalCache(e);
576
583
  Assert(eec != nullptr);
577
  // Evaluate, which should be cached (assuming we have performed example-based
578
  // symmetry breaking on bv). Moreover don't cache the result in the case it
579
  // is not there already, since we won't need this evaluation anywhere outside
580
  // of this class.
581
583
  eec->evaluateVec(bv, base_results);
582
  // get the results for each slave enumerator
583
1166
  std::map<Node, std::vector<Node>> srmap;
584
1735
  for (const Node& xs : ei.d_enum_slave)
585
  {
586
1152
    Assert(srmap.find(xs) == srmap.end());
587
1152
    EnumInfo& eiv = d_strategy.at(c).getEnumInfo(xs);
588
2304
    Node templ = eiv.d_template;
589
1152
    if (!templ.isNull())
590
    {
591
      // Substitute and evaluate, notice that the template skeleton may
592
      // involve the sygus variables e.g. (>= x _) where x is a sygus
593
      // variable, hence we must compute the substituted template before
594
      // calling the evaluator.
595
530
      TNode targ = eiv.d_template_arg;
596
530
      TNode tbv = bv;
597
530
      Node stempl = templ.substitute(targ, tbv);
598
530
      std::vector<Node> sresults;
599
265
      eec->evaluateVec(stempl, sresults);
600
265
      srmap[xs] = sresults;
601
    }
602
    else
603
    {
604
887
      srmap[xs] = base_results;
605
    }
606
  }
607
608
  // is it excluded for domain-specific reason?
609
1166
  std::vector<Node> exp_exc_vec;
610
583
  Assert(d_tds->isEnumerator(e));
611
583
  bool isPassive = d_tds->isPassiveEnumerator(e);
612
583
  if (getExplanationForEnumeratorExclude(e, v, base_results, exp_exc_vec))
613
  {
614
50
    if (isPassive)
615
    {
616
      Assert(!exp_exc_vec.empty());
617
      exp_exc = exp_exc_vec.size() == 1
618
                    ? exp_exc_vec[0]
619
                    : NodeManager::currentNM()->mkNode(AND, exp_exc_vec);
620
    }
621
100
    Trace("sygus-sui-enum")
622
50
        << "  ...fail : term is excluded (domain-specific)" << std::endl;
623
  }
624
  else
625
  {
626
    // notify all slaves
627
533
    Assert(!ei.d_enum_slave.empty());
628
    // explanation for why this value should be excluded
629
1553
    for (unsigned s = 0; s < ei.d_enum_slave.size(); s++)
630
    {
631
2040
      Node xs = ei.d_enum_slave[s];
632
1020
      EnumInfo& eiv = d_strategy.at(c).getEnumInfo(xs);
633
1020
      EnumCache& ecv = d_ecache[xs];
634
1020
      Trace("sygus-sui-enum") << "Process " << xs << " from " << s << std::endl;
635
      // bool prevIsCover = false;
636
1020
      if (eiv.getRole() == enum_io)
637
      {
638
485
        Trace("sygus-sui-enum") << "   IO-Eval of ";
639
        // prevIsCover = eiv.isFeasible();
640
      }
641
      else
642
      {
643
535
        Trace("sygus-sui-enum") << "Evaluation of ";
644
      }
645
1020
      Trace("sygus-sui-enum") << xs << " : ";
646
      // evaluate all input/output examples
647
2040
      std::vector<Node> results;
648
2040
      std::map<Node, bool> cond_vals;
649
1020
      std::map<Node, std::vector<Node>>::iterator itsr = srmap.find(xs);
650
1020
      Trace("sygus-sui-debug") << " {" << itsr->second << "} ";
651
1020
      Assert(itsr != srmap.end());
652
16972
      for (unsigned j = 0, size = itsr->second.size(); j < size; j++)
653
      {
654
31904
        Node res = itsr->second[j];
655
        // The value of this term for this example, or the truth value of
656
        // the I/O pair if the role of this enumerator is enum_io.
657
31904
        Node resb;
658
15952
        if (eiv.getRole() == enum_io)
659
        {
660
11860
          Node out = d_examples_out[j];
661
5930
          Assert(out.isConst());
662
          // If the result is not constant, then we assume that it does
663
          // not satisfy the example. This is a safe underapproximation
664
          // of the good behavior of the current term, that is, we only
665
          // produce solutions whose values are fully evaluatable on all input
666
          // points. Notice that terms may be used as leaves of decision
667
          // trees that are fully evaluatable on points in that branch, but
668
          // are not evaluatable on others, e.g. (head x) in the solution:
669
          //   (ite ((_ is cons) x) (head x) 5)
670
5930
          resb = (res.isConst() && res == out) ? d_true : d_false;
671
        }
672
        else
673
        {
674
          // We only set resb if it is constant, otherwise it remains null.
675
          // This indicates its value cannot be determined.
676
10022
          if (res.isConst())
677
          {
678
9994
            resb = res;
679
          }
680
        }
681
15952
        cond_vals[resb] = true;
682
15952
        results.push_back(resb);
683
15952
        if (Trace.isOn("sygus-sui-enum"))
684
        {
685
          if (resb.isNull())
686
          {
687
            Trace("sygus-sui-enum") << "_";
688
          }
689
          else if (resb.getType().isBoolean())
690
          {
691
            Trace("sygus-sui-enum") << (resb == d_true ? "1" : "0");
692
          }
693
          else
694
          {
695
            Trace("sygus-sui-enum") << "?";
696
          }
697
        }
698
      }
699
1020
      bool keep = false;
700
1020
      if (eiv.getRole() == enum_io)
701
      {
702
        // latter is the degenerate case of no examples
703
485
        if (cond_vals.find(d_true) != cond_vals.end() || cond_vals.empty())
704
        {
705
          // check subsumbed/subsuming
706
278
          std::vector<Node> subsume;
707
139
          if (cond_vals.find(d_false) == cond_vals.end())
708
          {
709
17
            Assert(cond_vals.size() == 1);
710
            // it is the entire solution, we are done
711
34
            Trace("sygus-sui-enum")
712
17
                << "  ...success, full solution added to PBE pool : "
713
17
                << d_tds->sygusToBuiltin(v) << std::endl;
714
17
            if (!ecv.isSolved())
715
            {
716
17
              ecv.setSolved(v);
717
              // it subsumes everything
718
17
              ecv.d_term_trie.clear();
719
17
              ecv.d_term_trie.addTerm(v, results, true, subsume);
720
            }
721
17
            keep = true;
722
          }
723
          else
724
          {
725
244
            Node val = ecv.d_term_trie.addTerm(v, results, true, subsume);
726
122
            if (val == v)
727
            {
728
62
              Trace("sygus-sui-enum") << "  ...success";
729
62
              if (!subsume.empty())
730
              {
731
16
                ecv.d_enum_subsume.insert(
732
32
                    ecv.d_enum_subsume.end(), subsume.begin(), subsume.end());
733
32
                Trace("sygus-sui-enum")
734
16
                    << " and subsumed " << subsume.size() << " terms";
735
              }
736
124
              Trace("sygus-sui-enum")
737
124
                  << "!   add to PBE pool : " << d_tds->sygusToBuiltin(v)
738
62
                  << std::endl;
739
62
              keep = true;
740
            }
741
            else
742
            {
743
60
              Assert(subsume.empty());
744
60
              Trace("sygus-sui-enum") << "  ...fail : subsumed" << std::endl;
745
            }
746
          }
747
        }
748
        else
749
        {
750
692
          Trace("sygus-sui-enum")
751
346
              << "  ...fail : it does not satisfy examples." << std::endl;
752
        }
753
      }
754
      else
755
      {
756
        // must be unique up to examples
757
1070
        Node val = ecv.d_term_trie.addCond(v, results, true);
758
535
        if (val == v)
759
        {
760
718
          Trace("sygus-sui-enum") << "  ...success!   add to PBE pool : "
761
359
                                  << d_tds->sygusToBuiltin(v) << std::endl;
762
359
          keep = true;
763
        }
764
        else
765
        {
766
352
          Trace("sygus-sui-enum")
767
176
              << "  ...fail : term is not unique" << std::endl;
768
        }
769
      }
770
1020
      if (keep)
771
      {
772
        // notify to retry the build of solution
773
438
        d_check_sol = true;
774
438
        d_cond_count++;
775
438
        ecv.addEnumValue(v, results);
776
      }
777
    }
778
  }
779
780
583
  if (isPassive)
781
  {
782
    // exclude this value on subsequent iterations
783
7
    if (exp_exc.isNull())
784
    {
785
7
      Trace("sygus-sui-enum-lemma") << "Use basic exclusion." << std::endl;
786
      // if we did not already explain why this should be excluded, use default
787
7
      exp_exc = d_tds->getExplain()->getExplanationForEquality(e, v);
788
    }
789
7
    exp_exc = exp_exc.negate();
790
14
    Trace("sygus-sui-enum-lemma")
791
7
        << "SygusUnifIo : enumeration exclude lemma : " << exp_exc << std::endl;
792
7
    lemmas.push_back(exp_exc);
793
  }
794
583
}
795
796
525
bool SygusUnifIo::constructSolution(std::vector<Node>& sols,
797
                                    std::vector<Node>& lemmas)
798
{
799
1050
  Node sol = constructSolutionNode(lemmas);
800
525
  if (!sol.isNull())
801
  {
802
51
    sols.push_back(sol);
803
51
    return true;
804
  }
805
474
  return false;
806
}
807
808
525
Node SygusUnifIo::constructSolutionNode(std::vector<Node>& lemmas)
809
{
810
1050
  Node c = d_candidate;
811
525
  if (!d_solution.isNull() && !options::sygusStream())
812
  {
813
    // already has a solution
814
5
    return d_solution;
815
  }
816
  // only check if an enumerator updated
817
520
  if (d_check_sol)
818
  {
819
610
    Trace("sygus-pbe") << "Construct solution, #iterations = " << d_cond_count
820
305
                       << std::endl;
821
305
    d_check_sol = false;
822
564
    Node newSolution;
823
305
    d_solConsUsingInfoGain = false;
824
    // try multiple times if we have done multiple conditions, due to
825
    // non-determinism
826
564
    for (unsigned i = 0; i <= d_cond_count; i++)
827
    {
828
532
      Trace("sygus-pbe-dt") << "ConstructPBE for candidate: " << c << std::endl;
829
      // initialize a call to construct solution
830
532
      initializeConstructSol();
831
532
      initializeConstructSolFor(c);
832
      // call the virtual construct solution method
833
791
      Node e = d_strategy.at(c).getRootEnumerator();
834
791
      Node vcc = constructSol(c, e, role_equal, 1, lemmas);
835
      // if we constructed the solution, and we either did not previously have
836
      // a solution, or the new solution is better (smaller).
837
1064
      if (!vcc.isNull()
838
1120
          && (d_solution.isNull()
839
101
              || (!d_solution.isNull()
840
734
                  && datatypes::utils::getSygusTermSize(vcc)
841
101
                         < d_sol_term_size)))
842
      {
843
56
        if (Trace.isOn("sygus-pbe"))
844
        {
845
          Trace("sygus-pbe") << "**** SygusUnif SOLVED : " << c << " = ";
846
          TermDbSygus::toStreamSygus("sygus-pbe", vcc);
847
          Trace("sygus-pbe") << std::endl;
848
          Trace("sygus-pbe") << "...solved at iteration " << i << std::endl;
849
        }
850
56
        d_solution = vcc;
851
56
        newSolution = vcc;
852
56
        d_sol_term_size = datatypes::utils::getSygusTermSize(vcc);
853
112
        Trace("sygus-pbe-sol")
854
56
            << "PBE solution size: " << d_sol_term_size << std::endl;
855
        // We've determined its feasible, now, enable information gain and
856
        // retry. We do this since information gain comes with an overhead,
857
        // and we want testing feasibility to be fast.
858
56
        if (!d_solConsUsingInfoGain)
859
        {
860
          // we permanently enable information gain and minimality now
861
46
          d_solConsUsingInfoGain = true;
862
46
          d_enableMinimality = true;
863
46
          i = 0;
864
        }
865
      }
866
476
      else if (!d_sol_cons_nondet)
867
      {
868
273
        break;
869
      }
870
    }
871
305
    if (!newSolution.isNull())
872
    {
873
46
      return newSolution;
874
    }
875
259
    Trace("sygus-pbe") << "...failed to solve." << std::endl;
876
  }
877
474
  return Node::null();
878
}
879
880
// ------------------------------------ solution construction from enumeration
881
882
583
bool SygusUnifIo::useStrContainsEnumeratorExclude(Node e)
883
{
884
1166
  TypeNode xbt = d_tds->sygusToBuiltinType(e.getType());
885
583
  if (xbt.isStringLike())
886
  {
887
93
    std::map<Node, bool>::iterator itx = d_use_str_contains_eexc.find(e);
888
93
    if (itx != d_use_str_contains_eexc.end())
889
    {
890
76
      return itx->second;
891
    }
892
34
    Trace("sygus-sui-enum-debug")
893
17
        << "Is " << e << " is str.contains exclusion?" << std::endl;
894
17
    d_use_str_contains_eexc[e] = true;
895
34
    Node c = d_candidate;
896
17
    EnumInfo& ei = d_strategy.at(c).getEnumInfo(e);
897
67
    for (const Node& sn : ei.d_enum_slave)
898
    {
899
50
      EnumInfo& eis = d_strategy.at(c).getEnumInfo(sn);
900
50
      EnumRole er = eis.getRole();
901
50
      if (er != enum_io && er != enum_concat_term)
902
      {
903
        Trace("sygus-sui-enum-debug") << "  incompatible slave : " << sn
904
                                      << ", role = " << er << std::endl;
905
        d_use_str_contains_eexc[e] = false;
906
        return false;
907
      }
908
50
      d_use_str_contains_eexc_conditional[e] = false;
909
50
      if (eis.isConditional())
910
      {
911
4
        Trace("sygus-sui-enum-debug")
912
2
            << "  conditional slave : " << sn << std::endl;
913
2
        d_use_str_contains_eexc_conditional[e] = true;
914
      }
915
    }
916
34
    Trace("sygus-sui-enum-debug")
917
17
        << "...can use str.contains exclusion." << std::endl;
918
17
    return d_use_str_contains_eexc[e];
919
  }
920
490
  return false;
921
}
922
923
583
bool SygusUnifIo::getExplanationForEnumeratorExclude(
924
    Node e,
925
    Node v,
926
    std::vector<Node>& results,
927
    std::vector<Node>& exp)
928
{
929
583
  NodeManager* nm = NodeManager::currentNM();
930
583
  if (useStrContainsEnumeratorExclude(e))
931
  {
932
    // This check whether the example evaluates to something that is larger than
933
    // the output for some input/output pair. If so, then this term is never
934
    // useful. We generalize its explanation below.
935
936
    // if the enumerator is in a conditional context, then we are stricter
937
    // about when to exclude
938
93
    bool isConditional = d_use_str_contains_eexc_conditional[e];
939
93
    if (Trace.isOn("sygus-sui-cterm-debug"))
940
    {
941
      Trace("sygus-sui-enum") << std::endl;
942
    }
943
    // check if all examples had longer length that the output
944
93
    Assert(d_examples_out.size() == results.size());
945
186
    Trace("sygus-sui-cterm-debug")
946
93
        << "Check enumerator exclusion for " << e << " -> "
947
93
        << d_tds->sygusToBuiltin(v) << " based on str.contains." << std::endl;
948
126
    std::vector<unsigned> cmp_indices;
949
3524
    for (size_t i = 0, size = results.size(); i < size; i++)
950
    {
951
      // If the result is not constant, then it is worthless. It does not
952
      // impact whether the term is excluded.
953
3441
      if (results[i].isConst())
954
      {
955
3440
        Assert(d_examples_out[i].isConst());
956
6880
        Trace("sygus-sui-cterm-debug")
957
3440
            << "  " << results[i] << " <> " << d_examples_out[i];
958
6870
        Node cont = nm->mkNode(STRING_CONTAINS, d_examples_out[i], results[i]);
959
6870
        Node contr = rewrite(cont);
960
3440
        if (contr == d_false)
961
        {
962
2676
          cmp_indices.push_back(i);
963
2676
          Trace("sygus-sui-cterm-debug") << "...not contained." << std::endl;
964
        }
965
        else
966
        {
967
764
          Trace("sygus-sui-cterm-debug") << "...contained." << std::endl;
968
764
          if (isConditional)
969
          {
970
10
            return false;
971
          }
972
        }
973
      }
974
    }
975
83
    if (!cmp_indices.empty())
976
    {
977
      // we check invariance with respect to a negative contains test
978
100
      NegContainsSygusInvarianceTest ncset;
979
50
      if (isConditional)
980
      {
981
3
        ncset.setUniversal();
982
      }
983
50
      ncset.init(e, d_examples, d_examples_out, cmp_indices);
984
      // construct the generalized explanation
985
50
      d_tds->getExplain()->getExplanationFor(e, v, exp, ncset);
986
100
      Trace("sygus-sui-cterm")
987
100
          << "PBE-cterm : enumerator exclude " << d_tds->sygusToBuiltin(v)
988
50
          << " due to negative containment." << std::endl;
989
50
      return true;
990
    }
991
  }
992
523
  return false;
993
}
994
995
438
void SygusUnifIo::EnumCache::addEnumValue(Node v, std::vector<Node>& results)
996
{
997
876
  Trace("sygus-sui-debug") << "Add enum value " << this << " " << v << " : "
998
438
                           << results << std::endl;
999
  // should not have been enumerated before
1000
438
  Assert(d_enum_val_to_index.find(v) == d_enum_val_to_index.end());
1001
438
  d_enum_val_to_index[v] = d_enum_vals.size();
1002
438
  d_enum_vals.push_back(v);
1003
438
  d_enum_vals_res.push_back(results);
1004
438
}
1005
1006
532
void SygusUnifIo::initializeConstructSol()
1007
{
1008
532
  d_context.initialize(this);
1009
532
  d_sol_cons_nondet = false;
1010
532
}
1011
1012
532
void SygusUnifIo::initializeConstructSolFor(Node f)
1013
{
1014
532
  Assert(d_candidate == f);
1015
532
}
1016
1017
3593
Node SygusUnifIo::constructSol(
1018
    Node f, Node e, NodeRole nrole, int ind, std::vector<Node>& lemmas)
1019
{
1020
3593
  Assert(d_candidate == f);
1021
3593
  UnifContextIo& x = d_context;
1022
7186
  TypeNode etn = e.getType();
1023
3593
  if (Trace.isOn("sygus-sui-dt-debug"))
1024
  {
1025
    indent("sygus-sui-dt-debug", ind);
1026
    Trace("sygus-sui-dt-debug") << "ConstructPBE: (" << e << ", " << nrole
1027
                                << ") for type " << etn << " in context ";
1028
    print_val("sygus-sui-dt-debug", x.d_vals);
1029
    NodeRole ctx_role = x.getCurrentRole();
1030
    Trace("sygus-sui-dt-debug") << ", context role [" << ctx_role;
1031
    if (ctx_role == role_string_prefix || ctx_role == role_string_suffix)
1032
    {
1033
      Trace("sygus-sui-dt-debug") << ", string pos : ";
1034
      for (unsigned i = 0, size = x.d_str_pos.size(); i < size; i++)
1035
      {
1036
        Trace("sygus-sui-dt-debug") << " " << x.d_str_pos[i];
1037
      }
1038
    }
1039
    Trace("sygus-sui-dt-debug") << "]";
1040
    Trace("sygus-sui-dt-debug") << std::endl;
1041
  }
1042
  // enumerator type info
1043
3593
  EnumTypeInfo& tinfo = d_strategy.at(f).getEnumTypeInfo(etn);
1044
1045
  // get the enumerator information
1046
3593
  EnumInfo& einfo = d_strategy.at(f).getEnumInfo(e);
1047
1048
3593
  EnumCache& ecache = d_ecache[e];
1049
1050
3593
  bool retValMod = x.isReturnValueModified();
1051
1052
3593
  Node ret_dt;
1053
7186
  Node cached_ret_dt;
1054
3593
  if (nrole == role_equal)
1055
  {
1056
2192
    if (!retValMod)
1057
    {
1058
1076
      if (ecache.isSolved())
1059
      {
1060
        // this type has a complete solution
1061
36
        ret_dt = ecache.getSolved();
1062
36
        indent("sygus-sui-dt", ind);
1063
72
        Trace("sygus-sui-dt") << "return PBE: success : solved "
1064
36
                              << d_tds->sygusToBuiltin(ret_dt) << std::endl;
1065
36
        Assert(!ret_dt.isNull());
1066
      }
1067
      else
1068
      {
1069
        // could be conditionally solved
1070
2080
        std::vector<Node> subsumed_by;
1071
1040
        ecache.d_term_trie.getSubsumedBy(x.d_vals, true, subsumed_by);
1072
1040
        if (!subsumed_by.empty())
1073
        {
1074
154
          ret_dt = constructBestSolvedTerm(e, subsumed_by);
1075
154
          indent("sygus-sui-dt", ind);
1076
308
          Trace("sygus-sui-dt") << "return PBE: success : conditionally solved "
1077
154
                                << d_tds->sygusToBuiltin(ret_dt) << std::endl;
1078
        }
1079
        else
1080
        {
1081
886
          indent("sygus-sui-dt-debug", ind);
1082
1772
          Trace("sygus-sui-dt-debug")
1083
886
              << "  ...not currently conditionally solved." << std::endl;
1084
        }
1085
      }
1086
    }
1087
2192
    if (ret_dt.isNull())
1088
    {
1089
2002
      if (d_tds->sygusToBuiltinType(e.getType()).isStringLike())
1090
      {
1091
        // check if a current value that closes all examples
1092
        // get the term enumerator for this type
1093
        std::map<EnumRole, Node>::iterator itnt =
1094
1472
            tinfo.d_enum.find(enum_concat_term);
1095
1472
        if (itnt != tinfo.d_enum.end())
1096
        {
1097
2944
          Node et = itnt->second;
1098
1099
1472
          EnumCache& ecachet = d_ecache[et];
1100
          // get the current examples
1101
2944
          std::vector<Node> ex_vals;
1102
1472
          x.getCurrentStrings(this, d_examples_out, ex_vals);
1103
1472
          Assert(ecache.d_enum_vals.size() == ecache.d_enum_vals_res.size());
1104
1105
          // test each example in the term enumerator for the type
1106
2944
          std::vector<Node> str_solved;
1107
10033
          for (unsigned i = 0, size = ecachet.d_enum_vals.size(); i < size; i++)
1108
          {
1109
8561
            if (x.isStringSolved(this, ex_vals, ecachet.d_enum_vals_res[i]))
1110
            {
1111
72
              str_solved.push_back(ecachet.d_enum_vals[i]);
1112
            }
1113
          }
1114
1472
          if (!str_solved.empty())
1115
          {
1116
72
            ret_dt = constructBestSolvedTerm(e, str_solved);
1117
72
            indent("sygus-sui-dt", ind);
1118
144
            Trace("sygus-sui-dt") << "return PBE: success : string solved "
1119
72
                                  << d_tds->sygusToBuiltin(ret_dt) << std::endl;
1120
          }
1121
          else
1122
          {
1123
1400
            indent("sygus-sui-dt-debug", ind);
1124
2800
            Trace("sygus-sui-dt-debug")
1125
1400
                << "  ...not currently string solved." << std::endl;
1126
          }
1127
        }
1128
      }
1129
    }
1130
    // maybe we can find one in the cache
1131
2192
    if (ret_dt.isNull() && !retValMod)
1132
    {
1133
886
      bool firstTime = true;
1134
1772
      std::unordered_set<Node> intersection;
1135
886
      std::map<TypeNode, std::unordered_set<Node>>::iterator pit;
1136
2835
      for (size_t i = 0, nvals = x.d_vals.size(); i < nvals; i++)
1137
      {
1138
2701
        if (x.d_vals[i].getConst<bool>())
1139
        {
1140
1518
          pit = d_psolutions[i].find(etn);
1141
1518
          if (pit == d_psolutions[i].end())
1142
          {
1143
            // no cached solution
1144
752
            intersection.clear();
1145
752
            break;
1146
          }
1147
766
          if (firstTime)
1148
          {
1149
139
            intersection = pit->second;
1150
139
            firstTime = false;
1151
          }
1152
          else
1153
          {
1154
1254
            std::vector<Node> rm;
1155
2057
            for (const Node& a : intersection)
1156
            {
1157
1430
              if (pit->second.find(a) == pit->second.end())
1158
              {
1159
55
                rm.push_back(a);
1160
              }
1161
            }
1162
682
            for (const Node& a : rm)
1163
            {
1164
55
              intersection.erase(a);
1165
            }
1166
627
            if (intersection.empty())
1167
            {
1168
              break;
1169
            }
1170
          }
1171
        }
1172
      }
1173
886
      if (!intersection.empty())
1174
      {
1175
134
        if (d_enableMinimality)
1176
        {
1177
          // if we are enabling minimality, the minimal cached solution may
1178
          // still not be the best solution, thus we remember it and keep it if
1179
          // we don't construct a better one below
1180
198
          std::vector<Node> intervec;
1181
99
          intervec.insert(
1182
198
              intervec.begin(), intersection.begin(), intersection.end());
1183
99
          cached_ret_dt = getMinimalTerm(intervec);
1184
        }
1185
        else
1186
        {
1187
35
          ret_dt = *intersection.begin();
1188
        }
1189
134
        if (Trace.isOn("sygus-sui-dt"))
1190
        {
1191
          indent("sygus-sui-dt", ind);
1192
          Trace("sygus-sui-dt") << "ConstructPBE: found in cache: ";
1193
          Node csol = ret_dt;
1194
          if (d_enableMinimality)
1195
          {
1196
            csol = cached_ret_dt;
1197
            Trace("sygus-sui-dt") << "(minimal) ";
1198
          }
1199
          TermDbSygus::toStreamSygus("sygus-sui-dt", csol);
1200
          Trace("sygus-sui-dt") << std::endl;
1201
        }
1202
      }
1203
    }
1204
  }
1205
1401
  else if (nrole == role_string_prefix || nrole == role_string_suffix)
1206
  {
1207
    // check if each return value is a prefix/suffix of all open examples
1208
1401
    if (!retValMod || x.getCurrentRole() == nrole)
1209
    {
1210
2802
      std::map<Node, std::vector<size_t>> incr;
1211
1401
      bool isPrefix = nrole == role_string_prefix;
1212
2802
      std::map<Node, size_t> total_inc;
1213
2802
      std::vector<Node> inc_strs;
1214
      // make the value of the examples
1215
2802
      std::vector<Node> ex_vals;
1216
1401
      x.getCurrentStrings(this, d_examples_out, ex_vals);
1217
1401
      if (Trace.isOn("sygus-sui-dt-debug"))
1218
      {
1219
        indent("sygus-sui-dt-debug", ind);
1220
        Trace("sygus-sui-dt-debug") << "current strings : " << std::endl;
1221
        for (unsigned i = 0, size = ex_vals.size(); i < size; i++)
1222
        {
1223
          indent("sygus-sui-dt-debug", ind + 1);
1224
          Trace("sygus-sui-dt-debug") << ex_vals[i] << std::endl;
1225
        }
1226
      }
1227
1228
      // check if there is a value for which is a prefix/suffix of all active
1229
      // examples
1230
1401
      Assert(ecache.d_enum_vals.size() == ecache.d_enum_vals_res.size());
1231
1232
9077
      for (unsigned i = 0, size = ecache.d_enum_vals.size(); i < size; i++)
1233
      {
1234
15352
        Node val_t = ecache.d_enum_vals[i];
1235
7676
        Assert(incr.find(val_t) == incr.end());
1236
7676
        indent("sygus-sui-dt-debug", ind);
1237
7676
        Trace("sygus-sui-dt-debug") << "increment string values : ";
1238
7676
        TermDbSygus::toStreamSygus("sygus-sui-dt-debug", val_t);
1239
7676
        Trace("sygus-sui-dt-debug") << " : ";
1240
7676
        Assert(ecache.d_enum_vals_res[i].size() == d_examples_out.size());
1241
7676
        size_t tot = 0;
1242
7676
        bool exsuccess = x.getStringIncrement(this,
1243
                                              isPrefix,
1244
                                              ex_vals,
1245
7676
                                              ecache.d_enum_vals_res[i],
1246
7676
                                              incr[val_t],
1247
7676
                                              tot);
1248
7676
        if (!exsuccess)
1249
        {
1250
6306
          incr.erase(val_t);
1251
6306
          Trace("sygus-sui-dt-debug") << "...fail" << std::endl;
1252
        }
1253
        else
1254
        {
1255
1370
          total_inc[val_t] = tot;
1256
1370
          inc_strs.push_back(val_t);
1257
2740
          Trace("sygus-sui-dt-debug")
1258
1370
              << "...success, total increment = " << tot << std::endl;
1259
        }
1260
      }
1261
1262
1401
      if (!incr.empty())
1263
      {
1264
        // solution construction for strings concatenation is non-deterministic
1265
        // with respect to failure/success.
1266
1097
        d_sol_cons_nondet = true;
1267
1097
        ret_dt = constructBestStringToConcat(inc_strs, total_inc, incr);
1268
1097
        Assert(!ret_dt.isNull());
1269
1097
        indent("sygus-sui-dt", ind);
1270
2194
        Trace("sygus-sui-dt")
1271
1097
            << "PBE: CONCAT strategy : choose " << (isPrefix ? "pre" : "suf")
1272
1097
            << "fix value " << d_tds->sygusToBuiltin(ret_dt) << std::endl;
1273
        // update the context
1274
1097
        bool ret = x.updateStringPosition(this, incr[ret_dt], nrole);
1275
1097
        AlwaysAssert(ret == (total_inc[ret_dt] > 0));
1276
      }
1277
      else
1278
      {
1279
304
        indent("sygus-sui-dt", ind);
1280
608
        Trace("sygus-sui-dt") << "PBE: failed CONCAT strategy, no values are "
1281
304
                              << (isPrefix ? "pre" : "suf")
1282
304
                              << "fix of all examples." << std::endl;
1283
      }
1284
    }
1285
    else
1286
    {
1287
      indent("sygus-sui-dt", ind);
1288
      Trace("sygus-sui-dt")
1289
          << "PBE: failed CONCAT strategy, prefix/suffix mismatch."
1290
          << std::endl;
1291
    }
1292
  }
1293
3593
  if (!ret_dt.isNull() || einfo.isTemplated())
1294
  {
1295
1430
    Assert(ret_dt.isNull() || ret_dt.getType() == e.getType());
1296
1430
    indent("sygus-sui-dt", ind);
1297
2860
    Trace("sygus-sui-dt") << "ConstructPBE: returned (pre-strategy) " << ret_dt
1298
1430
                          << std::endl;
1299
1430
    return ret_dt;
1300
  }
1301
  // we will try a single strategy
1302
2163
  EnumTypeInfoStrat* etis = nullptr;
1303
2163
  std::map<NodeRole, StrategyNode>::iterator itsn = tinfo.d_snodes.find(nrole);
1304
2163
  if (itsn == tinfo.d_snodes.end())
1305
  {
1306
    indent("sygus-sui-dt", ind);
1307
    Trace("sygus-sui-dt") << "ConstructPBE: returned (no-strategy) " << ret_dt
1308
                          << std::endl;
1309
    return ret_dt;
1310
  }
1311
  // strategy info
1312
2163
  StrategyNode& snode = itsn->second;
1313
2163
  if (x.d_visit_role[e].find(nrole) != x.d_visit_role[e].end())
1314
  {
1315
    // already visited and context not changed (notice d_visit_role is cleared
1316
    // when the context changes).
1317
143
    indent("sygus-sui-dt", ind);
1318
286
    Trace("sygus-sui-dt") << "ConstructPBE: returned (already visited) "
1319
143
                          << ret_dt << std::endl;
1320
143
    return ret_dt;
1321
  }
1322
2020
  x.d_visit_role[e][nrole] = true;
1323
  // try a random strategy
1324
2020
  if (snode.d_strats.size() > 1)
1325
  {
1326
1443
    std::shuffle(
1327
        snode.d_strats.begin(), snode.d_strats.end(), Random::getRandom());
1328
  }
1329
  // ITE always first if we have not yet solved
1330
  // the reasoning is that splitting on conditions only subdivides the problem
1331
  // and cannot be the source of failure, whereas the wrong choice for a
1332
  // concatenation term may lead to failure
1333
2020
  if (d_solution.isNull())
1334
  {
1335
2667
    for (unsigned i = 0; i < snode.d_strats.size(); i++)
1336
    {
1337
2318
      if (snode.d_strats[i]->d_this == strat_ITE)
1338
      {
1339
        // flip the two
1340
1126
        EnumTypeInfoStrat* etis_i = snode.d_strats[i];
1341
1126
        snode.d_strats[i] = snode.d_strats[0];
1342
1126
        snode.d_strats[0] = etis_i;
1343
1126
        break;
1344
      }
1345
    }
1346
  }
1347
1348
  // iterate over the strategies
1349
2020
  unsigned sindex = 0;
1350
2020
  bool did_recurse = false;
1351
8174
  while (ret_dt.isNull() && !did_recurse && sindex < snode.d_strats.size())
1352
  {
1353
3077
    if (snode.d_strats[sindex]->isValid(x))
1354
    {
1355
2599
      etis = snode.d_strats[sindex];
1356
2599
      Assert(etis != nullptr);
1357
2599
      StrategyType strat = etis->d_this;
1358
2599
      indent("sygus-sui-dt", ind + 1);
1359
5198
      Trace("sygus-sui-dt")
1360
2599
          << "...try STRATEGY " << strat << "..." << std::endl;
1361
1362
5198
      std::vector<Node> dt_children_cons;
1363
2599
      bool success = true;
1364
1365
      // for ITE
1366
5198
      Node split_cond_enum;
1367
2599
      unsigned split_cond_res_index = 0;
1368
2599
      CVC5_UNUSED bool set_split_cond_res_index = false;
1369
1370
4890
      for (unsigned sc = 0, size = etis->d_cenum.size(); sc < size; sc++)
1371
      {
1372
4313
        indent("sygus-sui-dt", ind + 1);
1373
8626
        Trace("sygus-sui-dt")
1374
4313
            << "construct PBE child #" << sc << "..." << std::endl;
1375
6604
        Node rec_c;
1376
1377
4313
        std::pair<Node, NodeRole>& cenum = etis->d_cenum[sc];
1378
1379
        // update the context
1380
6604
        std::vector<Node> prev;
1381
4313
        if (strat == strat_ITE && sc > 0)
1382
        {
1383
620
          EnumCache& ecache_cond = d_ecache[split_cond_enum];
1384
620
          Assert(set_split_cond_res_index);
1385
620
          Assert(split_cond_res_index < ecache_cond.d_enum_vals_res.size());
1386
620
          prev = x.d_vals;
1387
1240
          x.updateContext(this,
1388
620
                          ecache_cond.d_enum_vals_res[split_cond_res_index],
1389
                          sc == 1);
1390
          // return value of above call may be false in corner cases where we
1391
          // must choose a non-separating condition to traverse to another
1392
          // strategy node
1393
        }
1394
1395
        // recurse
1396
4313
        if (strat == strat_ITE && sc == 0)
1397
        {
1398
2504
          Node ce = cenum.first;
1399
1400
1252
          EnumCache& ecache_child = d_ecache[ce];
1401
1402
          // get the conditionals in the current context : they must be
1403
          // distinguishable
1404
2504
          std::map<int, std::vector<Node> > possible_cond;
1405
2504
          std::map<Node, int> solved_cond;  // stores branch
1406
1252
          ecache_child.d_term_trie.getLeaves(x.d_vals, true, possible_cond);
1407
1408
          std::map<int, std::vector<Node>>::iterator itpc =
1409
1252
              possible_cond.find(0);
1410
1252
          if (itpc != possible_cond.end())
1411
          {
1412
432
            if (Trace.isOn("sygus-sui-dt-debug"))
1413
            {
1414
              indent("sygus-sui-dt-debug", ind + 1);
1415
              Trace("sygus-sui-dt-debug")
1416
                  << "PBE : We have " << itpc->second.size()
1417
                  << " distinguishable conditionals:" << std::endl;
1418
              for (Node& cond : itpc->second)
1419
              {
1420
                indent("sygus-sui-dt-debug", ind + 2);
1421
                Trace("sygus-sui-dt-debug")
1422
                    << d_tds->sygusToBuiltin(cond) << std::endl;
1423
              }
1424
            }
1425
432
            if (rec_c.isNull())
1426
            {
1427
432
              rec_c = constructBestConditional(ce, itpc->second);
1428
432
              Assert(!rec_c.isNull());
1429
432
              indent("sygus-sui-dt", ind);
1430
864
              Trace("sygus-sui-dt")
1431
432
                  << "PBE: ITE strategy : choose best conditional "
1432
432
                  << d_tds->sygusToBuiltin(rec_c) << std::endl;
1433
            }
1434
          }
1435
          else
1436
          {
1437
            // if the branch types are different, it could still make a
1438
            // difference to recurse, for instance see issue #4790. We do this
1439
            // if either branch is a different type from the current type.
1440
1640
            TypeNode branchType1 = etis->d_cenum[1].first.getType();
1441
1640
            TypeNode branchType2 = etis->d_cenum[2].first.getType();
1442
820
            bool childTypesEqual = branchType1 == etn && branchType2 == etn;
1443
820
            if (!childTypesEqual)
1444
            {
1445
2
              if (!ecache_child.d_enum_vals.empty())
1446
              {
1447
                // take arbitrary
1448
2
                rec_c = constructBestConditional(ce, ecache_child.d_enum_vals);
1449
2
                indent("sygus-sui-dt", ind);
1450
4
                Trace("sygus-sui-dt")
1451
                    << "PBE: ITE strategy : choose arbitrary conditional due "
1452
2
                       "to disequal child types "
1453
2
                    << d_tds->sygusToBuiltin(rec_c) << std::endl;
1454
              }
1455
            }
1456
820
            if (rec_c.isNull())
1457
            {
1458
818
              indent("sygus-sui-dt", ind);
1459
1636
              Trace("sygus-sui-dt")
1460
                  << "return PBE: failed ITE strategy, "
1461
818
                     "cannot find a distinguishable condition, childTypesEqual="
1462
818
                  << childTypesEqual << std::endl;
1463
            }
1464
          }
1465
1252
          if (!rec_c.isNull())
1466
          {
1467
434
            Assert(ecache_child.d_enum_val_to_index.find(rec_c)
1468
                   != ecache_child.d_enum_val_to_index.end());
1469
434
            split_cond_res_index = ecache_child.d_enum_val_to_index[rec_c];
1470
434
            set_split_cond_res_index = true;
1471
434
            split_cond_enum = ce;
1472
434
            Assert(split_cond_res_index < ecache_child.d_enum_vals_res.size());
1473
1252
          }
1474
        }
1475
        else
1476
        {
1477
3061
          did_recurse = true;
1478
3061
          rec_c = constructSol(f, cenum.first, cenum.second, ind + 2, lemmas);
1479
        }
1480
        // undo update the context
1481
4313
        if (strat == strat_ITE && sc > 0)
1482
        {
1483
620
          x.d_vals = prev;
1484
        }
1485
4313
        if (!rec_c.isNull())
1486
        {
1487
2291
          dt_children_cons.push_back(rec_c);
1488
        }
1489
        else
1490
        {
1491
2022
          success = false;
1492
2022
          break;
1493
        }
1494
      }
1495
2599
      if (success)
1496
      {
1497
577
        Assert(dt_children_cons.size() == etis->d_sol_templ_args.size());
1498
        // ret_dt = NodeManager::currentNM()->mkNode( APPLY_CONSTRUCTOR,
1499
        // dt_children );
1500
577
        ret_dt = etis->d_sol_templ;
1501
577
        ret_dt = ret_dt.substitute(etis->d_sol_templ_args.begin(),
1502
                                   etis->d_sol_templ_args.end(),
1503
                                   dt_children_cons.begin(),
1504
                                   dt_children_cons.end());
1505
577
        indent("sygus-sui-dt-debug", ind);
1506
1154
        Trace("sygus-sui-dt-debug")
1507
577
            << "PBE: success : constructed for strategy " << strat << std::endl;
1508
      }
1509
      else
1510
      {
1511
2022
        indent("sygus-sui-dt-debug", ind);
1512
4044
        Trace("sygus-sui-dt-debug")
1513
2022
            << "PBE: failed for strategy " << strat << std::endl;
1514
      }
1515
    }
1516
    // increment
1517
3077
    sindex++;
1518
  }
1519
1520
  // if there was a cached solution, process it now
1521
2020
  if (!cached_ret_dt.isNull() && cached_ret_dt != ret_dt)
1522
  {
1523
71
    if (ret_dt.isNull())
1524
    {
1525
      // take the cached one if it is the only one
1526
33
      ret_dt = cached_ret_dt;
1527
    }
1528
38
    else if (d_enableMinimality)
1529
    {
1530
38
      Assert(ret_dt.getType() == cached_ret_dt.getType());
1531
      // take the cached one if it is smaller
1532
76
      std::vector<Node> retDts;
1533
38
      retDts.push_back(cached_ret_dt);
1534
38
      retDts.push_back(ret_dt);
1535
38
      ret_dt = getMinimalTerm(retDts);
1536
    }
1537
  }
1538
2020
  Assert(ret_dt.isNull() || ret_dt.getType() == e.getType());
1539
2020
  if (Trace.isOn("sygus-sui-dt"))
1540
  {
1541
    indent("sygus-sui-dt", ind);
1542
    Trace("sygus-sui-dt") << "ConstructPBE: returned ";
1543
    TermDbSygus::toStreamSygus("sygus-sui-dt", ret_dt);
1544
    Trace("sygus-sui-dt") << std::endl;
1545
  }
1546
  // remember the solution
1547
2020
  if (nrole == role_equal)
1548
  {
1549
1854
    if (!retValMod && !ret_dt.isNull())
1550
    {
1551
1252
      for (size_t i = 0, nvals = x.d_vals.size(); i < nvals; i++)
1552
      {
1553
1112
        if (x.d_vals[i].getConst<bool>())
1554
        {
1555
941
          if (Trace.isOn("sygus-sui-cache"))
1556
          {
1557
            indent("sygus-sui-cache", ind);
1558
            Trace("sygus-sui-cache") << "Cache solution (#" << i << ") : ";
1559
            TermDbSygus::toStreamSygus("sygus-sui-cache", ret_dt);
1560
            Trace("sygus-sui-cache") << std::endl;
1561
          }
1562
941
          d_psolutions[i][etn].insert(ret_dt);
1563
        }
1564
      }
1565
    }
1566
  }
1567
1568
2020
  return ret_dt;
1569
}
1570
1571
434
Node SygusUnifIo::constructBestConditional(Node ce,
1572
                                           const std::vector<Node>& conds)
1573
{
1574
434
  if (!d_solConsUsingInfoGain)
1575
  {
1576
358
    return SygusUnif::constructBestConditional(ce, conds);
1577
  }
1578
76
  UnifContextIo& x = d_context;
1579
  // use information gain heuristic
1580
76
  Trace("sygus-sui-dt-igain") << "Best information gain in context ";
1581
76
  print_val("sygus-sui-dt-igain", x.d_vals);
1582
76
  Trace("sygus-sui-dt-igain") << std::endl;
1583
  // set of indices that are active in this branch, i.e. x.d_vals[i] is true
1584
152
  std::vector<unsigned> activeIndices;
1585
  // map (j,t,s) -> n, such that the j^th condition in the vector conds
1586
  // evaluates to t (typically true/false) on n active I/O pairs with output s.
1587
152
  std::map<unsigned, std::map<Node, std::map<Node, unsigned>>> eval;
1588
  // map (j,t) -> m, such that the j^th condition in the vector conds
1589
  // evaluates to t (typically true/false) for m active I/O pairs.
1590
152
  std::map<unsigned, std::map<Node, unsigned>> evalCount;
1591
76
  unsigned nconds = conds.size();
1592
76
  EnumCache& ecache = d_ecache[ce];
1593
  // Get the index of conds[j] in the enumerator cache, this is to look up
1594
  // its evaluation on each point.
1595
152
  std::vector<unsigned> eindex;
1596
247
  for (unsigned j = 0; j < nconds; j++)
1597
  {
1598
171
    eindex.push_back(ecache.d_enum_val_to_index[conds[j]]);
1599
  }
1600
76
  unsigned activePoints = 0;
1601
1252
  for (unsigned i = 0, npoints = x.d_vals.size(); i < npoints; i++)
1602
  {
1603
1176
    if (x.d_vals[i].getConst<bool>())
1604
    {
1605
813
      activePoints++;
1606
1626
      Node eo = d_examples_out[i];
1607
2996
      for (unsigned j = 0; j < nconds; j++)
1608
      {
1609
4366
        Node resn = ecache.d_enum_vals_res[eindex[j]][i];
1610
2183
        Assert(resn.isConst());
1611
2183
        eval[j][resn][eo]++;
1612
2183
        evalCount[j][resn]++;
1613
      }
1614
    }
1615
  }
1616
76
  AlwaysAssert(activePoints > 0);
1617
  // find the condition that leads to the lowest entropy
1618
  // initially set minEntropy to > 1.0.
1619
76
  double minEntropy = 2.0;
1620
76
  unsigned bestIndex = 0;
1621
76
  int numEqual = 1;
1622
247
  for (unsigned j = 0; j < nconds; j++)
1623
  {
1624
    // To compute the entropy for a condition C, for pair of terms (s, t), let
1625
    //   prob(t) be the probability C evaluates to t on an active point,
1626
    //   prob(s|t) be the probability that an active point on which C
1627
    //     evaluates to t has output s.
1628
    // Then, the entropy of C is:
1629
    //   sum{t}. prob(t)*( sum{s}. -prob(s|t)*log2(prob(s|t)) )
1630
    // where notice this is always between 0 and 1.
1631
171
    double entropySum = 0.0;
1632
171
    Trace("sygus-sui-dt-igain") << j << " : ";
1633
513
    for (std::pair<const Node, std::map<Node, unsigned>>& ej : eval[j])
1634
    {
1635
342
      unsigned ecount = evalCount[j][ej.first];
1636
342
      if (ecount > 0)
1637
      {
1638
342
        double probBranch = double(ecount) / double(activePoints);
1639
342
        Trace("sygus-sui-dt-igain") << ej.first << " -> ( ";
1640
2337
        for (std::pair<const Node, unsigned>& eej : ej.second)
1641
        {
1642
1995
          if (eej.second > 0)
1643
          {
1644
1995
            double probVal = double(eej.second) / double(ecount);
1645
3990
            Trace("sygus-sui-dt-igain")
1646
1995
                << eej.first << ":" << eej.second << " ";
1647
1995
            double factor = -probVal * log2(probVal);
1648
1995
            entropySum += probBranch * factor;
1649
          }
1650
        }
1651
342
        Trace("sygus-sui-dt-igain") << ") ";
1652
      }
1653
    }
1654
171
    Trace("sygus-sui-dt-igain") << "..." << entropySum << std::endl;
1655
    // either less, or equal and coin flip passes
1656
171
    bool doSet = false;
1657
171
    if (entropySum == minEntropy)
1658
    {
1659
22
      numEqual++;
1660
22
      if (Random::getRandom().pickWithProb(double(1) / double(numEqual)))
1661
      {
1662
10
        doSet = true;
1663
      }
1664
    }
1665
149
    else if (entropySum < minEntropy)
1666
    {
1667
35
      doSet = true;
1668
35
      numEqual = 1;
1669
    }
1670
171
    if (doSet)
1671
    {
1672
45
      minEntropy = entropySum;
1673
45
      bestIndex = j;
1674
    }
1675
  }
1676
1677
76
  Assert(!conds.empty());
1678
76
  return conds[bestIndex];
1679
}
1680
1681
}  // namespace quantifiers
1682
}  // namespace theory
1683
29502
}  // namespace cvc5