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

Line Exec Source
1
/******************************************************************************
2
 * Top contributors (to current version):
3
 *   Andrew Reynolds, Mathias Preiner, Aina Niemetz
4
 *
5
 * This file is part of the cvc5 project.
6
 *
7
 * Copyright (c) 2009-2021 by the authors listed in the file AUTHORS
8
 * in the top-level source directory and their institutional affiliations.
9
 * All rights reserved.  See the file COPYING in the top-level source
10
 * directory for licensing information.
11
 * ****************************************************************************
12
 *
13
 * Implementation of sygus_unif_io.
14
 */
15
16
#include "theory/quantifiers/sygus/sygus_unif_io.h"
17
18
#include "options/quantifiers_options.h"
19
#include "theory/datatypes/sygus_datatype_utils.h"
20
#include "theory/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
10575
NodeRole UnifContextIo::getCurrentRole() { return d_curr_role; }
43
44
620
bool UnifContextIo::updateContext(SygusUnifIo* sui,
45
                                  std::vector<Node>& vals,
46
                                  bool pol)
47
{
48
620
  Assert(d_vals.size() == vals.size());
49
620
  bool changed = false;
50
1240
  Node poln = pol ? d_true : d_false;
51
13593
  for (size_t i = 0, vsize = vals.size(); i < vsize; i++)
52
  {
53
25946
    Node v = vals[i];
54
12973
    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
12973
    if (v != poln)
61
    {
62
6464
      if (d_vals[i] == d_true)
63
      {
64
5224
        d_vals[i] = d_false;
65
5224
        changed = true;
66
      }
67
    }
68
  }
69
620
  if (changed)
70
  {
71
618
    d_visit_role.clear();
72
  }
73
1240
  return changed;
74
}
75
76
1097
bool UnifContextIo::updateStringPosition(SygusUnifIo* sui,
77
                                         std::vector<size_t>& pos,
78
                                         NodeRole nrole)
79
{
80
1097
  Assert(pos.size() == d_str_pos.size());
81
1097
  bool changed = false;
82
13102
  for (unsigned i = 0; i < pos.size(); i++)
83
  {
84
12005
    if (pos[i] > 0)
85
    {
86
7787
      d_str_pos[i] += pos[i];
87
7787
      changed = true;
88
    }
89
  }
90
1097
  if (changed)
91
  {
92
1067
    d_visit_role.clear();
93
  }
94
1097
  d_curr_role = nrole;
95
1097
  return changed;
96
}
97
98
532
void UnifContextIo::initialize(SygusUnifIo* sui)
99
{
100
  // clear previous data
101
532
  d_vals.clear();
102
532
  d_str_pos.clear();
103
532
  d_curr_role = role_equal;
104
532
  d_visit_role.clear();
105
106
  // initialize with #examples
107
532
  unsigned sz = sui->d_examples.size();
108
9014
  for (unsigned i = 0; i < sz; i++)
109
  {
110
8482
    d_vals.push_back(d_true);
111
  }
112
113
532
  if (!sui->d_examples_out.empty())
114
  {
115
    // output type of the examples
116
1064
    TypeNode exotn = sui->d_examples_out[0].getType();
117
118
532
    if (exotn.isStringLike())
119
    {
120
3924
      for (unsigned i = 0; i < sz; i++)
121
      {
122
3690
        d_str_pos.push_back(0);
123
      }
124
    }
125
  }
126
532
  d_visit_role.clear();
127
532
}
128
129
2873
void UnifContextIo::getCurrentStrings(SygusUnifIo* sui,
130
                                      const std::vector<Node>& vals,
131
                                      std::vector<Node>& ex_vals)
132
{
133
2873
  bool isPrefix = d_curr_role == role_string_prefix;
134
5746
  Node dummy;
135
39082
  for (unsigned i = 0; i < vals.size(); i++)
136
  {
137
36209
    if (d_vals[i] == sui->d_true)
138
    {
139
26391
      Assert(vals[i].isConst());
140
26391
      unsigned pos_value = d_str_pos[i];
141
26391
      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
8595
        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
2873
}
163
164
7676
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
31548
  for (unsigned j = 0; j < vals.size(); j++)
172
  {
173
30178
    size_t ival = 0;
174
30178
    if (d_vals[j] == sui->d_true)
175
    {
176
      // example is active in this context
177
16678
      if (!vals[j].isConst())
178
      {
179
        // the value is unknown, thus we cannot use it to increment the strings
180
        // position
181
6311
        return false;
182
      }
183
16673
      ival = strings::Word::getLength(vals[j]);
184
16673
      size_t exjLen = strings::Word::getLength(ex_vals[j]);
185
16673
      if (ival <= exjLen)
186
      {
187
52252
        if (!(isPrefix ? strings::Word::strncmp(ex_vals[j], vals[j], ival)
188
35948
                       : 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
10372
      Trace("sygus-sui-dt-debug") << ival;
200
10372
      tot += ival;
201
    }
202
    else
203
    {
204
      // inactive in this context
205
13500
      Trace("sygus-sui-dt-debug") << "-";
206
    }
207
23872
    inc.push_back(ival);
208
  }
209
1370
  return true;
210
}
211
8561
bool UnifContextIo::isStringSolved(SygusUnifIo* sui,
212
                                   const std::vector<Node>& ex_vals,
213
                                   const std::vector<Node>& vals)
214
{
215
25204
  for (unsigned j = 0; j < vals.size(); j++)
216
  {
217
25132
    if (d_vals[j] == sui->d_true)
218
    {
219
      // example is active in this context
220
8668
      if (!vals[j].isConst())
221
      {
222
        // value is unknown, thus it does not solve
223
7
        return false;
224
      }
225
8661
      if (ex_vals[j] != vals[j])
226
      {
227
8482
        return false;
228
      }
229
    }
230
  }
231
72
  return true;
232
}
233
234
// status : 0 : exact, -1 : vals is subset, 1 : vals is superset
235
16608
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
16608
  if (index == vals.size())
246
  {
247
864
    if (status == 0)
248
    {
249
      // set the term if checkExistsOnly = false
250
633
      if (d_term.isNull() && !checkExistsOnly)
251
      {
252
438
        d_term = t;
253
      }
254
    }
255
231
    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
41
      Assert(!checkExistsOnly && checkSubsume);
275
    }
276
864
    return d_term;
277
  }
278
15744
  NodeManager* nm = NodeManager::currentNM();
279
  // the current value
280
15744
  Assert(pol || (vals[index].isConst() && vals[index].getType().isBoolean()));
281
31488
  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
15744
  if (!checkExistsOnly && checkSubsume)
285
  {
286
1311
    Assert(cv.isConst() && cv.getType().isBoolean());
287
2512
    std::vector<bool> check_subsumed_by;
288
1311
    if (status == 0)
289
    {
290
1025
      if (!cv.getConst<bool>())
291
      {
292
510
        check_subsumed_by.push_back(spol);
293
      }
294
    }
295
286
    else if (status == -1)
296
    {
297
99
      check_subsumed_by.push_back(spol);
298
99
      if (!cv.getConst<bool>())
299
      {
300
54
        check_subsumed_by.push_back(!spol);
301
      }
302
    }
303
    // check for subsumed nodes
304
1853
    for (unsigned i = 0, size = check_subsumed_by.size(); i < size; i++)
305
    {
306
652
      bool csbi = check_subsumed_by[i];
307
1194
      Node csval = nm->mkConst(csbi);
308
      // check if subsumed
309
652
      std::map<Node, SubsumeTrie>::iterator itc = d_children.find(csval);
310
652
      if (itc != d_children.end())
311
      {
312
140
        Node ret = itc->second.addTermInternal(t,
313
                                               vals,
314
                                               pol,
315
                                               subsumed,
316
                                               spol,
317
                                               index + 1,
318
                                               -1,
319
                                               checkExistsOnly,
320
170
                                               checkSubsume);
321
        // ret subsumes t
322
140
        if (!ret.isNull())
323
        {
324
110
          return ret;
325
        }
326
      }
327
    }
328
  }
329
31268
  Node ret;
330
31268
  std::vector<bool> check_subsume;
331
15634
  if (status == 0)
332
  {
333
11006
    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
11006
      Assert(spol);
352
11006
      ret = d_children[cv].addTermInternal(t,
353
                                           vals,
354
                                           pol,
355
                                           subsumed,
356
                                           spol,
357
                                           index + 1,
358
                                           0,
359
                                           checkExistsOnly,
360
                                           checkSubsume);
361
11006
      if (ret != t)
362
      {
363
        // we were subsumed by ret, return
364
3941
        return ret;
365
      }
366
    }
367
7065
    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
4628
  else if (status == 1)
378
  {
379
4598
    Assert(checkSubsume);
380
4598
    Assert(cv.isConst() && cv.getType().isBoolean());
381
4598
    check_subsume.push_back(!spol);
382
4598
    if (cv.getConst<bool>())
383
    {
384
2843
      check_subsume.push_back(spol);
385
    }
386
  }
387
11693
  if (checkSubsume)
388
  {
389
    // check for subsumed terms
390
13266
    for (unsigned i = 0, size = check_subsume.size(); i < size; i++)
391
    {
392
15720
      Node csval = nm->mkConst<bool>(check_subsume[i]);
393
7860
      std::map<Node, SubsumeTrie>::iterator itc = d_children.find(csval);
394
7860
      if (itc != d_children.end())
395
      {
396
3748
        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
3748
        if (itc->second.isEmpty())
407
        {
408
          Assert(!checkExistsOnly);
409
          d_children.erase(csval);
410
        }
411
      }
412
    }
413
  }
414
11693
  return ret;
415
}
416
417
139
Node SubsumeTrie::addTerm(Node t,
418
                          const std::vector<Node>& vals,
419
                          bool pol,
420
                          std::vector<Node>& subsumed)
421
{
422
139
  return addTermInternal(t, vals, pol, subsumed, true, 0, 0, false, true);
423
}
424
425
535
Node SubsumeTrie::addCond(Node c, const std::vector<Node>& vals, bool pol)
426
{
427
1070
  std::vector<Node> subsumed;
428
1070
  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
1040
void SubsumeTrie::getSubsumedBy(const std::vector<Node>& vals,
439
                                bool pol,
440
                                std::vector<Node>& subsumed_by)
441
{
442
  // flip polarities
443
2080
  addTermInternal(
444
3120
      Node::null(), vals, !pol, subsumed_by, false, 0, 1, true, true);
445
1040
}
446
447
62749
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
62749
  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
3258
    int rstatus = status == -2 ? -1 : status;
458
3258
    Assert(!d_term.isNull());
459
3258
    Assert(std::find(v[rstatus].begin(), v[rstatus].end(), d_term)
460
           == v[rstatus].end());
461
3258
    v[rstatus].push_back(d_term);
462
  }
463
  else
464
  {
465
59491
    Assert(vals[index].isConst() && vals[index].getType().isBoolean());
466
59491
    bool curr_val_true = vals[index].getConst<bool>() == pol;
467
121016
    for (std::map<Node, SubsumeTrie>::iterator it = d_children.begin();
468
121016
         it != d_children.end();
469
         ++it)
470
    {
471
61525
      int new_status = status;
472
61525
      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
61525
      if (curr_val_true)
476
      {
477
37788
        if (it->first.isNull())
478
        {
479
          // The value of this child is unknown on this point, hence we
480
          // do not recurse
481
28
          success = false;
482
        }
483
37760
        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
27590
          Assert(it->first.getType().isBoolean());
488
27590
          Assert(it->first.isConst());
489
27590
          new_status = (it->first.getConst<bool>() ? 1 : -1);
490
27590
          if (status != -2 && new_status != status)
491
          {
492
867
            new_status = 0;
493
          }
494
        }
495
      }
496
61525
      if (success)
497
      {
498
61497
        it->second.getLeavesInternal(vals, pol, v, index + 1, new_status);
499
      }
500
    }
501
  }
502
62749
}
503
504
1252
void SubsumeTrie::getLeaves(const std::vector<Node>& vals,
505
                            bool pol,
506
                            std::map<int, std::vector<Node> >& v)
507
{
508
1252
  getLeavesInternal(vals, pol, v, 0, -2);
509
1252
}
510
511
47
SygusUnifIo::SygusUnifIo(Env& env, SynthConjecture* p)
512
    : SygusUnif(env),
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.at(f).staticLearnRedundantOps(strategy_lemmas);
553
47
}
554
555
583
void SygusUnifIo::notifyEnumeration(Node e, Node v, std::vector<Node>& lemmas)
556
{
557
1166
  Trace("sygus-sui-enum") << "Notify enumeration for " << e << " : " << v
558
583
                          << std::endl;
559
1166
  Node c = d_candidate;
560
583
  Assert(!d_examples.empty());
561
583
  Assert(d_examples.size() == d_examples_out.size());
562
563
583
  EnumInfo& ei = d_strategy.at(c).getEnumInfo(e);
564
  // The explanation for why the current value should be excluded in future
565
  // iterations.
566
1166
  Node exp_exc;
567
568
1166
  std::vector<Node> base_results;
569
1166
  TypeNode xtn = e.getType();
570
1166
  Node bv = d_tds->sygusToBuiltin(v, xtn);
571
583
  bv = extendedRewrite(bv);
572
583
  Trace("sygus-sui-enum") << "PBE Compute Examples for " << bv << std::endl;
573
  // compte the results (should be cached)
574
583
  ExampleEvalCache* eec = d_parent->getExampleEvalCache(e);
575
583
  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
583
  eec->evaluateVec(bv, base_results);
581
  // get the results for each slave enumerator
582
1166
  std::map<Node, std::vector<Node>> srmap;
583
1735
  for (const Node& xs : ei.d_enum_slave)
584
  {
585
1152
    Assert(srmap.find(xs) == srmap.end());
586
1152
    EnumInfo& eiv = d_strategy.at(c).getEnumInfo(xs);
587
2304
    Node templ = eiv.d_template;
588
1152
    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
887
      srmap[xs] = base_results;
604
    }
605
  }
606
607
  // is it excluded for domain-specific reason?
608
1166
  std::vector<Node> exp_exc_vec;
609
583
  Assert(d_tds->isEnumerator(e));
610
583
  bool isPassive = d_tds->isPassiveEnumerator(e);
611
583
  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
533
    Assert(!ei.d_enum_slave.empty());
627
    // explanation for why this value should be excluded
628
1553
    for (unsigned s = 0; s < ei.d_enum_slave.size(); s++)
629
    {
630
2040
      Node xs = ei.d_enum_slave[s];
631
1020
      EnumInfo& eiv = d_strategy.at(c).getEnumInfo(xs);
632
1020
      EnumCache& ecv = d_ecache[xs];
633
1020
      Trace("sygus-sui-enum") << "Process " << xs << " from " << s << std::endl;
634
      // bool prevIsCover = false;
635
1020
      if (eiv.getRole() == enum_io)
636
      {
637
485
        Trace("sygus-sui-enum") << "   IO-Eval of ";
638
        // prevIsCover = eiv.isFeasible();
639
      }
640
      else
641
      {
642
535
        Trace("sygus-sui-enum") << "Evaluation of ";
643
      }
644
1020
      Trace("sygus-sui-enum") << xs << " : ";
645
      // evaluate all input/output examples
646
2040
      std::vector<Node> results;
647
2040
      std::map<Node, bool> cond_vals;
648
1020
      std::map<Node, std::vector<Node>>::iterator itsr = srmap.find(xs);
649
1020
      Trace("sygus-sui-debug") << " {" << itsr->second << "} ";
650
1020
      Assert(itsr != srmap.end());
651
16972
      for (unsigned j = 0, size = itsr->second.size(); j < size; j++)
652
      {
653
31904
        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
31904
        Node resb;
657
15952
        if (eiv.getRole() == enum_io)
658
        {
659
11860
          Node out = d_examples_out[j];
660
5930
          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
5930
          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
10022
          if (res.isConst())
676
          {
677
9994
            resb = res;
678
          }
679
        }
680
15952
        cond_vals[resb] = true;
681
15952
        results.push_back(resb);
682
15952
        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
1020
      bool keep = false;
699
1020
      if (eiv.getRole() == enum_io)
700
      {
701
        // latter is the degenerate case of no examples
702
485
        if (cond_vals.find(d_true) != cond_vals.end() || cond_vals.empty())
703
        {
704
          // check subsumbed/subsuming
705
278
          std::vector<Node> subsume;
706
139
          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
244
            Node val = ecv.d_term_trie.addTerm(v, results, true, subsume);
725
122
            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
60
              Assert(subsume.empty());
743
60
              Trace("sygus-sui-enum") << "  ...fail : subsumed" << std::endl;
744
            }
745
          }
746
        }
747
        else
748
        {
749
692
          Trace("sygus-sui-enum")
750
346
              << "  ...fail : it does not satisfy examples." << std::endl;
751
        }
752
      }
753
      else
754
      {
755
        // must be unique up to examples
756
1070
        Node val = ecv.d_term_trie.addCond(v, results, true);
757
535
        if (val == v)
758
        {
759
718
          Trace("sygus-sui-enum") << "  ...success!   add to PBE pool : "
760
359
                                  << d_tds->sygusToBuiltin(v) << std::endl;
761
359
          keep = true;
762
        }
763
        else
764
        {
765
352
          Trace("sygus-sui-enum")
766
176
              << "  ...fail : term is not unique" << std::endl;
767
        }
768
      }
769
1020
      if (keep)
770
      {
771
        // notify to retry the build of solution
772
438
        d_check_sol = true;
773
438
        d_cond_count++;
774
438
        ecv.addEnumValue(v, results);
775
      }
776
    }
777
  }
778
779
583
  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
583
}
794
795
525
bool SygusUnifIo::constructSolution(std::vector<Node>& sols,
796
                                    std::vector<Node>& lemmas)
797
{
798
1050
  Node sol = constructSolutionNode(lemmas);
799
525
  if (!sol.isNull())
800
  {
801
51
    sols.push_back(sol);
802
51
    return true;
803
  }
804
474
  return false;
805
}
806
807
525
Node SygusUnifIo::constructSolutionNode(std::vector<Node>& lemmas)
808
{
809
1050
  Node c = d_candidate;
810
525
  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
520
  if (d_check_sol)
817
  {
818
610
    Trace("sygus-pbe") << "Construct solution, #iterations = " << d_cond_count
819
305
                       << std::endl;
820
305
    d_check_sol = false;
821
564
    Node newSolution;
822
305
    d_solConsUsingInfoGain = false;
823
    // try multiple times if we have done multiple conditions, due to
824
    // non-determinism
825
564
    for (unsigned i = 0; i <= d_cond_count; i++)
826
    {
827
532
      Trace("sygus-pbe-dt") << "ConstructPBE for candidate: " << c << std::endl;
828
      // initialize a call to construct solution
829
532
      initializeConstructSol();
830
532
      initializeConstructSolFor(c);
831
      // call the virtual construct solution method
832
791
      Node e = d_strategy.at(c).getRootEnumerator();
833
791
      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
1064
      if (!vcc.isNull()
837
1120
          && (d_solution.isNull()
838
101
              || (!d_solution.isNull()
839
734
                  && datatypes::utils::getSygusTermSize(vcc)
840
101
                         < d_sol_term_size)))
841
      {
842
56
        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
56
        d_solution = vcc;
850
56
        newSolution = vcc;
851
56
        d_sol_term_size = datatypes::utils::getSygusTermSize(vcc);
852
112
        Trace("sygus-pbe-sol")
853
56
            << "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
56
        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
476
      else if (!d_sol_cons_nondet)
866
      {
867
273
        break;
868
      }
869
    }
870
305
    if (!newSolution.isNull())
871
    {
872
46
      return newSolution;
873
    }
874
259
    Trace("sygus-pbe") << "...failed to solve." << std::endl;
875
  }
876
474
  return Node::null();
877
}
878
879
// ------------------------------------ solution construction from enumeration
880
881
583
bool SygusUnifIo::useStrContainsEnumeratorExclude(Node e)
882
{
883
1166
  TypeNode xbt = d_tds->sygusToBuiltinType(e.getType());
884
583
  if (xbt.isStringLike())
885
  {
886
93
    std::map<Node, bool>::iterator itx = d_use_str_contains_eexc.find(e);
887
93
    if (itx != d_use_str_contains_eexc.end())
888
    {
889
76
      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.at(c).getEnumInfo(e);
896
67
    for (const Node& sn : ei.d_enum_slave)
897
    {
898
50
      EnumInfo& eis = d_strategy.at(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
490
  return false;
920
}
921
922
583
bool SygusUnifIo::getExplanationForEnumeratorExclude(
923
    Node e,
924
    Node v,
925
    std::vector<Node>& results,
926
    std::vector<Node>& exp)
927
{
928
583
  NodeManager* nm = NodeManager::currentNM();
929
583
  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
93
    bool isConditional = d_use_str_contains_eexc_conditional[e];
938
93
    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
93
    Assert(d_examples_out.size() == results.size());
944
186
    Trace("sygus-sui-cterm-debug")
945
93
        << "Check enumerator exclusion for " << e << " -> "
946
93
        << d_tds->sygusToBuiltin(v) << " based on str.contains." << std::endl;
947
126
    std::vector<unsigned> cmp_indices;
948
3524
    for (size_t 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
3441
      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 = 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
83
    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
523
  return false;
992
}
993
994
438
void SygusUnifIo::EnumCache::addEnumValue(Node v, std::vector<Node>& results)
995
{
996
876
  Trace("sygus-sui-debug") << "Add enum value " << this << " " << v << " : "
997
438
                           << results << std::endl;
998
  // should not have been enumerated before
999
438
  Assert(d_enum_val_to_index.find(v) == d_enum_val_to_index.end());
1000
438
  d_enum_val_to_index[v] = d_enum_vals.size();
1001
438
  d_enum_vals.push_back(v);
1002
438
  d_enum_vals_res.push_back(results);
1003
438
}
1004
1005
532
void SygusUnifIo::initializeConstructSol()
1006
{
1007
532
  d_context.initialize(this);
1008
532
  d_sol_cons_nondet = false;
1009
532
}
1010
1011
532
void SygusUnifIo::initializeConstructSolFor(Node f)
1012
{
1013
532
  Assert(d_candidate == f);
1014
532
}
1015
1016
3593
Node SygusUnifIo::constructSol(
1017
    Node f, Node e, NodeRole nrole, int ind, std::vector<Node>& lemmas)
1018
{
1019
3593
  Assert(d_candidate == f);
1020
3593
  UnifContextIo& x = d_context;
1021
7186
  TypeNode etn = e.getType();
1022
3593
  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
3593
  EnumTypeInfo& tinfo = d_strategy.at(f).getEnumTypeInfo(etn);
1043
1044
  // get the enumerator information
1045
3593
  EnumInfo& einfo = d_strategy.at(f).getEnumInfo(e);
1046
1047
3593
  EnumCache& ecache = d_ecache[e];
1048
1049
3593
  bool retValMod = x.isReturnValueModified();
1050
1051
3593
  Node ret_dt;
1052
7186
  Node cached_ret_dt;
1053
3593
  if (nrole == role_equal)
1054
  {
1055
2192
    if (!retValMod)
1056
    {
1057
1076
      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
2080
        std::vector<Node> subsumed_by;
1070
1040
        ecache.d_term_trie.getSubsumedBy(x.d_vals, true, subsumed_by);
1071
1040
        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
886
          indent("sygus-sui-dt-debug", ind);
1081
1772
          Trace("sygus-sui-dt-debug")
1082
886
              << "  ...not currently conditionally solved." << std::endl;
1083
        }
1084
      }
1085
    }
1086
2192
    if (ret_dt.isNull())
1087
    {
1088
2002
      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
1472
            tinfo.d_enum.find(enum_concat_term);
1094
1472
        if (itnt != tinfo.d_enum.end())
1095
        {
1096
2944
          Node et = itnt->second;
1097
1098
1472
          EnumCache& ecachet = d_ecache[et];
1099
          // get the current examples
1100
2944
          std::vector<Node> ex_vals;
1101
1472
          x.getCurrentStrings(this, d_examples_out, ex_vals);
1102
1472
          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
2944
          std::vector<Node> str_solved;
1106
10033
          for (unsigned i = 0, size = ecachet.d_enum_vals.size(); i < size; i++)
1107
          {
1108
8561
            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
1472
          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
1400
            indent("sygus-sui-dt-debug", ind);
1123
2800
            Trace("sygus-sui-dt-debug")
1124
1400
                << "  ...not currently string solved." << std::endl;
1125
          }
1126
        }
1127
      }
1128
    }
1129
    // maybe we can find one in the cache
1130
2192
    if (ret_dt.isNull() && !retValMod)
1131
    {
1132
886
      bool firstTime = true;
1133
1772
      std::unordered_set<Node> intersection;
1134
886
      std::map<TypeNode, std::unordered_set<Node>>::iterator pit;
1135
2835
      for (size_t i = 0, nvals = x.d_vals.size(); i < nvals; i++)
1136
      {
1137
2701
        if (x.d_vals[i].getConst<bool>())
1138
        {
1139
1518
          pit = d_psolutions[i].find(etn);
1140
1518
          if (pit == d_psolutions[i].end())
1141
          {
1142
            // no cached solution
1143
752
            intersection.clear();
1144
752
            break;
1145
          }
1146
766
          if (firstTime)
1147
          {
1148
139
            intersection = pit->second;
1149
139
            firstTime = false;
1150
          }
1151
          else
1152
          {
1153
1254
            std::vector<Node> rm;
1154
2057
            for (const Node& a : intersection)
1155
            {
1156
1430
              if (pit->second.find(a) == pit->second.end())
1157
              {
1158
55
                rm.push_back(a);
1159
              }
1160
            }
1161
682
            for (const Node& a : rm)
1162
            {
1163
55
              intersection.erase(a);
1164
            }
1165
627
            if (intersection.empty())
1166
            {
1167
              break;
1168
            }
1169
          }
1170
        }
1171
      }
1172
886
      if (!intersection.empty())
1173
      {
1174
134
        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
198
          std::vector<Node> intervec;
1180
99
          intervec.insert(
1181
198
              intervec.begin(), intersection.begin(), intersection.end());
1182
99
          cached_ret_dt = getMinimalTerm(intervec);
1183
        }
1184
        else
1185
        {
1186
35
          ret_dt = *intersection.begin();
1187
        }
1188
134
        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
1401
  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
1401
    if (!retValMod || x.getCurrentRole() == nrole)
1208
    {
1209
2802
      std::map<Node, std::vector<size_t>> incr;
1210
1401
      bool isPrefix = nrole == role_string_prefix;
1211
2802
      std::map<Node, size_t> total_inc;
1212
2802
      std::vector<Node> inc_strs;
1213
      // make the value of the examples
1214
2802
      std::vector<Node> ex_vals;
1215
1401
      x.getCurrentStrings(this, d_examples_out, ex_vals);
1216
1401
      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
1401
      Assert(ecache.d_enum_vals.size() == ecache.d_enum_vals_res.size());
1230
1231
9077
      for (unsigned i = 0, size = ecache.d_enum_vals.size(); i < size; i++)
1232
      {
1233
15352
        Node val_t = ecache.d_enum_vals[i];
1234
7676
        Assert(incr.find(val_t) == incr.end());
1235
7676
        indent("sygus-sui-dt-debug", ind);
1236
7676
        Trace("sygus-sui-dt-debug") << "increment string values : ";
1237
7676
        TermDbSygus::toStreamSygus("sygus-sui-dt-debug", val_t);
1238
7676
        Trace("sygus-sui-dt-debug") << " : ";
1239
7676
        Assert(ecache.d_enum_vals_res[i].size() == d_examples_out.size());
1240
7676
        size_t tot = 0;
1241
7676
        bool exsuccess = x.getStringIncrement(this,
1242
                                              isPrefix,
1243
                                              ex_vals,
1244
7676
                                              ecache.d_enum_vals_res[i],
1245
7676
                                              incr[val_t],
1246
7676
                                              tot);
1247
7676
        if (!exsuccess)
1248
        {
1249
6306
          incr.erase(val_t);
1250
6306
          Trace("sygus-sui-dt-debug") << "...fail" << std::endl;
1251
        }
1252
        else
1253
        {
1254
1370
          total_inc[val_t] = tot;
1255
1370
          inc_strs.push_back(val_t);
1256
2740
          Trace("sygus-sui-dt-debug")
1257
1370
              << "...success, total increment = " << tot << std::endl;
1258
        }
1259
      }
1260
1261
1401
      if (!incr.empty())
1262
      {
1263
        // solution construction for strings concatenation is non-deterministic
1264
        // with respect to failure/success.
1265
1097
        d_sol_cons_nondet = true;
1266
1097
        ret_dt = constructBestStringToConcat(inc_strs, total_inc, incr);
1267
1097
        Assert(!ret_dt.isNull());
1268
1097
        indent("sygus-sui-dt", ind);
1269
2194
        Trace("sygus-sui-dt")
1270
1097
            << "PBE: CONCAT strategy : choose " << (isPrefix ? "pre" : "suf")
1271
1097
            << "fix value " << d_tds->sygusToBuiltin(ret_dt) << std::endl;
1272
        // update the context
1273
1097
        bool ret = x.updateStringPosition(this, incr[ret_dt], nrole);
1274
1097
        AlwaysAssert(ret == (total_inc[ret_dt] > 0));
1275
      }
1276
      else
1277
      {
1278
304
        indent("sygus-sui-dt", ind);
1279
608
        Trace("sygus-sui-dt") << "PBE: failed CONCAT strategy, no values are "
1280
304
                              << (isPrefix ? "pre" : "suf")
1281
304
                              << "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
3593
  if (!ret_dt.isNull() || einfo.isTemplated())
1293
  {
1294
1430
    Assert(ret_dt.isNull() || ret_dt.getType() == e.getType());
1295
1430
    indent("sygus-sui-dt", ind);
1296
2860
    Trace("sygus-sui-dt") << "ConstructPBE: returned (pre-strategy) " << ret_dt
1297
1430
                          << std::endl;
1298
1430
    return ret_dt;
1299
  }
1300
  // we will try a single strategy
1301
2163
  EnumTypeInfoStrat* etis = nullptr;
1302
2163
  std::map<NodeRole, StrategyNode>::iterator itsn = tinfo.d_snodes.find(nrole);
1303
2163
  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
2163
  StrategyNode& snode = itsn->second;
1312
2163
  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
143
    indent("sygus-sui-dt", ind);
1317
286
    Trace("sygus-sui-dt") << "ConstructPBE: returned (already visited) "
1318
143
                          << ret_dt << std::endl;
1319
143
    return ret_dt;
1320
  }
1321
2020
  x.d_visit_role[e][nrole] = true;
1322
  // try a random strategy
1323
2020
  if (snode.d_strats.size() > 1)
1324
  {
1325
1443
    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
2020
  if (d_solution.isNull())
1333
  {
1334
2667
    for (unsigned i = 0; i < snode.d_strats.size(); i++)
1335
    {
1336
2318
      if (snode.d_strats[i]->d_this == strat_ITE)
1337
      {
1338
        // flip the two
1339
1126
        EnumTypeInfoStrat* etis_i = snode.d_strats[i];
1340
1126
        snode.d_strats[i] = snode.d_strats[0];
1341
1126
        snode.d_strats[0] = etis_i;
1342
1126
        break;
1343
      }
1344
    }
1345
  }
1346
1347
  // iterate over the strategies
1348
2020
  unsigned sindex = 0;
1349
2020
  bool did_recurse = false;
1350
8174
  while (ret_dt.isNull() && !did_recurse && sindex < snode.d_strats.size())
1351
  {
1352
3077
    if (snode.d_strats[sindex]->isValid(x))
1353
    {
1354
2599
      etis = snode.d_strats[sindex];
1355
2599
      Assert(etis != nullptr);
1356
2599
      StrategyType strat = etis->d_this;
1357
2599
      indent("sygus-sui-dt", ind + 1);
1358
5198
      Trace("sygus-sui-dt")
1359
2599
          << "...try STRATEGY " << strat << "..." << std::endl;
1360
1361
5198
      std::vector<Node> dt_children_cons;
1362
2599
      bool success = true;
1363
1364
      // for ITE
1365
5198
      Node split_cond_enum;
1366
2599
      unsigned split_cond_res_index = 0;
1367
2599
      CVC5_UNUSED bool set_split_cond_res_index = false;
1368
1369
4890
      for (unsigned sc = 0, size = etis->d_cenum.size(); sc < size; sc++)
1370
      {
1371
4313
        indent("sygus-sui-dt", ind + 1);
1372
8626
        Trace("sygus-sui-dt")
1373
4313
            << "construct PBE child #" << sc << "..." << std::endl;
1374
6604
        Node rec_c;
1375
1376
4313
        std::pair<Node, NodeRole>& cenum = etis->d_cenum[sc];
1377
1378
        // update the context
1379
6604
        std::vector<Node> prev;
1380
4313
        if (strat == strat_ITE && sc > 0)
1381
        {
1382
620
          EnumCache& ecache_cond = d_ecache[split_cond_enum];
1383
620
          Assert(set_split_cond_res_index);
1384
620
          Assert(split_cond_res_index < ecache_cond.d_enum_vals_res.size());
1385
620
          prev = x.d_vals;
1386
1240
          x.updateContext(this,
1387
620
                          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
4313
        if (strat == strat_ITE && sc == 0)
1396
        {
1397
2504
          Node ce = cenum.first;
1398
1399
1252
          EnumCache& ecache_child = d_ecache[ce];
1400
1401
          // get the conditionals in the current context : they must be
1402
          // distinguishable
1403
2504
          std::map<int, std::vector<Node> > possible_cond;
1404
2504
          std::map<Node, int> solved_cond;  // stores branch
1405
1252
          ecache_child.d_term_trie.getLeaves(x.d_vals, true, possible_cond);
1406
1407
          std::map<int, std::vector<Node>>::iterator itpc =
1408
1252
              possible_cond.find(0);
1409
1252
          if (itpc != possible_cond.end())
1410
          {
1411
432
            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
432
            if (rec_c.isNull())
1425
            {
1426
432
              rec_c = constructBestConditional(ce, itpc->second);
1427
432
              Assert(!rec_c.isNull());
1428
432
              indent("sygus-sui-dt", ind);
1429
864
              Trace("sygus-sui-dt")
1430
432
                  << "PBE: ITE strategy : choose best conditional "
1431
432
                  << 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
1640
            TypeNode branchType1 = etis->d_cenum[1].first.getType();
1440
1640
            TypeNode branchType2 = etis->d_cenum[2].first.getType();
1441
820
            bool childTypesEqual = branchType1 == etn && branchType2 == etn;
1442
820
            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
820
            if (rec_c.isNull())
1456
            {
1457
818
              indent("sygus-sui-dt", ind);
1458
1636
              Trace("sygus-sui-dt")
1459
                  << "return PBE: failed ITE strategy, "
1460
818
                     "cannot find a distinguishable condition, childTypesEqual="
1461
818
                  << childTypesEqual << std::endl;
1462
            }
1463
          }
1464
1252
          if (!rec_c.isNull())
1465
          {
1466
434
            Assert(ecache_child.d_enum_val_to_index.find(rec_c)
1467
                   != ecache_child.d_enum_val_to_index.end());
1468
434
            split_cond_res_index = ecache_child.d_enum_val_to_index[rec_c];
1469
434
            set_split_cond_res_index = true;
1470
434
            split_cond_enum = ce;
1471
434
            Assert(split_cond_res_index < ecache_child.d_enum_vals_res.size());
1472
1252
          }
1473
        }
1474
        else
1475
        {
1476
3061
          did_recurse = true;
1477
3061
          rec_c = constructSol(f, cenum.first, cenum.second, ind + 2, lemmas);
1478
        }
1479
        // undo update the context
1480
4313
        if (strat == strat_ITE && sc > 0)
1481
        {
1482
620
          x.d_vals = prev;
1483
        }
1484
4313
        if (!rec_c.isNull())
1485
        {
1486
2291
          dt_children_cons.push_back(rec_c);
1487
        }
1488
        else
1489
        {
1490
2022
          success = false;
1491
2022
          break;
1492
        }
1493
      }
1494
2599
      if (success)
1495
      {
1496
577
        Assert(dt_children_cons.size() == etis->d_sol_templ_args.size());
1497
        // ret_dt = NodeManager::currentNM()->mkNode( APPLY_CONSTRUCTOR,
1498
        // dt_children );
1499
577
        ret_dt = etis->d_sol_templ;
1500
577
        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
577
        indent("sygus-sui-dt-debug", ind);
1505
1154
        Trace("sygus-sui-dt-debug")
1506
577
            << "PBE: success : constructed for strategy " << strat << std::endl;
1507
      }
1508
      else
1509
      {
1510
2022
        indent("sygus-sui-dt-debug", ind);
1511
4044
        Trace("sygus-sui-dt-debug")
1512
2022
            << "PBE: failed for strategy " << strat << std::endl;
1513
      }
1514
    }
1515
    // increment
1516
3077
    sindex++;
1517
  }
1518
1519
  // if there was a cached solution, process it now
1520
2020
  if (!cached_ret_dt.isNull() && cached_ret_dt != ret_dt)
1521
  {
1522
71
    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
38
    else if (d_enableMinimality)
1528
    {
1529
38
      Assert(ret_dt.getType() == cached_ret_dt.getType());
1530
      // take the cached one if it is smaller
1531
76
      std::vector<Node> retDts;
1532
38
      retDts.push_back(cached_ret_dt);
1533
38
      retDts.push_back(ret_dt);
1534
38
      ret_dt = getMinimalTerm(retDts);
1535
    }
1536
  }
1537
2020
  Assert(ret_dt.isNull() || ret_dt.getType() == e.getType());
1538
2020
  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
2020
  if (nrole == role_equal)
1547
  {
1548
1854
    if (!retValMod && !ret_dt.isNull())
1549
    {
1550
1252
      for (size_t i = 0, nvals = x.d_vals.size(); i < nvals; i++)
1551
      {
1552
1112
        if (x.d_vals[i].getConst<bool>())
1553
        {
1554
941
          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
941
          d_psolutions[i][etn].insert(ret_dt);
1562
        }
1563
      }
1564
    }
1565
  }
1566
1567
2020
  return ret_dt;
1568
}
1569
1570
434
Node SygusUnifIo::constructBestConditional(Node ce,
1571
                                           const std::vector<Node>& conds)
1572
{
1573
434
  if (!d_solConsUsingInfoGain)
1574
  {
1575
358
    return SygusUnif::constructBestConditional(ce, conds);
1576
  }
1577
76
  UnifContextIo& x = d_context;
1578
  // use information gain heuristic
1579
76
  Trace("sygus-sui-dt-igain") << "Best information gain in context ";
1580
76
  print_val("sygus-sui-dt-igain", x.d_vals);
1581
76
  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
152
  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
152
  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
152
  std::map<unsigned, std::map<Node, unsigned>> evalCount;
1590
76
  unsigned nconds = conds.size();
1591
76
  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
152
  std::vector<unsigned> eindex;
1595
247
  for (unsigned j = 0; j < nconds; j++)
1596
  {
1597
171
    eindex.push_back(ecache.d_enum_val_to_index[conds[j]]);
1598
  }
1599
76
  unsigned activePoints = 0;
1600
1252
  for (unsigned i = 0, npoints = x.d_vals.size(); i < npoints; i++)
1601
  {
1602
1176
    if (x.d_vals[i].getConst<bool>())
1603
    {
1604
813
      activePoints++;
1605
1626
      Node eo = d_examples_out[i];
1606
2996
      for (unsigned j = 0; j < nconds; j++)
1607
      {
1608
4366
        Node resn = ecache.d_enum_vals_res[eindex[j]][i];
1609
2183
        Assert(resn.isConst());
1610
2183
        eval[j][resn][eo]++;
1611
2183
        evalCount[j][resn]++;
1612
      }
1613
    }
1614
  }
1615
76
  AlwaysAssert(activePoints > 0);
1616
  // find the condition that leads to the lowest entropy
1617
  // initially set minEntropy to > 1.0.
1618
76
  double minEntropy = 2.0;
1619
76
  unsigned bestIndex = 0;
1620
76
  int numEqual = 1;
1621
247
  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
171
    double entropySum = 0.0;
1631
171
    Trace("sygus-sui-dt-igain") << j << " : ";
1632
513
    for (std::pair<const Node, std::map<Node, unsigned>>& ej : eval[j])
1633
    {
1634
342
      unsigned ecount = evalCount[j][ej.first];
1635
342
      if (ecount > 0)
1636
      {
1637
342
        double probBranch = double(ecount) / double(activePoints);
1638
342
        Trace("sygus-sui-dt-igain") << ej.first << " -> ( ";
1639
2337
        for (std::pair<const Node, unsigned>& eej : ej.second)
1640
        {
1641
1995
          if (eej.second > 0)
1642
          {
1643
1995
            double probVal = double(eej.second) / double(ecount);
1644
3990
            Trace("sygus-sui-dt-igain")
1645
1995
                << eej.first << ":" << eej.second << " ";
1646
1995
            double factor = -probVal * log2(probVal);
1647
1995
            entropySum += probBranch * factor;
1648
          }
1649
        }
1650
342
        Trace("sygus-sui-dt-igain") << ") ";
1651
      }
1652
    }
1653
171
    Trace("sygus-sui-dt-igain") << "..." << entropySum << std::endl;
1654
    // either less, or equal and coin flip passes
1655
171
    bool doSet = false;
1656
171
    if (entropySum == minEntropy)
1657
    {
1658
22
      numEqual++;
1659
22
      if (Random::getRandom().pickWithProb(double(1) / double(numEqual)))
1660
      {
1661
10
        doSet = true;
1662
      }
1663
    }
1664
149
    else if (entropySum < minEntropy)
1665
    {
1666
35
      doSet = true;
1667
35
      numEqual = 1;
1668
    }
1669
171
    if (doSet)
1670
    {
1671
45
      minEntropy = entropySum;
1672
45
      bestIndex = j;
1673
    }
1674
  }
1675
1676
76
  Assert(!conds.empty());
1677
76
  return conds[bestIndex];
1678
}
1679
1680
}  // namespace quantifiers
1681
}  // namespace theory
1682
22746
}  // namespace cvc5