GCC Code Coverage Report
Directory: . Exec Total Coverage
File: src/theory/quantifiers/sygus/sygus_unif_io.cpp Lines: 745 823 90.5 %
Date: 2021-08-16 Branches: 1336 3290 40.6 %

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