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