GCC Code Coverage Report
Directory: . Exec Total Coverage
File: src/theory/quantifiers/sygus/sygus_reconstruct.cpp Lines: 184 205 89.8 %
Date: 2021-09-15 Branches: 394 792 49.7 %

Line Exec Source
1
/******************************************************************************
2
 * Top contributors (to current version):
3
 *   Abdalrhman Mohamed, Andrew Reynolds, 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 for reconstruct.
14
 */
15
16
#include "theory/quantifiers/sygus/sygus_reconstruct.h"
17
18
#include "expr/node_algorithm.h"
19
#include "smt/command.h"
20
#include "theory/datatypes/sygus_datatype_utils.h"
21
#include "theory/rewriter.h"
22
23
using namespace cvc5::kind;
24
25
namespace cvc5 {
26
namespace theory {
27
namespace quantifiers {
28
29
1233
SygusReconstruct::SygusReconstruct(Env& env,
30
                                   TermDbSygus* tds,
31
1233
                                   SygusStatistics& s)
32
1233
    : d_env(env), d_tds(tds), d_stats(s)
33
{
34
1233
}
35
36
24
Node SygusReconstruct::reconstructSolution(Node sol,
37
                                           TypeNode stn,
38
                                           int8_t& reconstructed,
39
                                           uint64_t enumLimit)
40
{
41
48
  Trace("sygus-rcons") << "SygusReconstruct::reconstructSolution: " << sol
42
24
                       << std::endl;
43
24
  Trace("sygus-rcons") << "- target sygus type is " << stn << std::endl;
44
24
  Trace("sygus-rcons") << "- enumeration limit is " << enumLimit << std::endl;
45
46
  // this method may get called multiple times with the same object. We need to
47
  // reset the state to avoid conflicts
48
24
  clear();
49
50
24
  initialize(stn);
51
52
  /** a set of builtin terms to reconstruct satisfied for each sygus datatype */
53
48
  TypeBuiltinSetMap termsToRecons;
54
55
  // add the main obligation to the set of obligations
56
  // paramaters stn and sol constitute the main obligation to satisfy
57
24
  d_obs.push_back(std::make_unique<RConsObligation>(stn, sol));
58
24
  termsToRecons[stn].emplace(sol);
59
24
  d_stnInfo[stn].setBuiltinToOb(sol, d_obs[0].get());
60
24
  RConsObligation* ob0 = d_obs[0].get();
61
48
  Node k0 = ob0->getSkolem();
62
63
  // We need to add the main obligation to the crd in case it cannot be broken
64
  // down by matching. By doing so, we can solve the obligation using
65
  // enumeration and crd (if it is in the grammar)
66
24
  d_stnInfo[stn].addTerm(sol);
67
68
  // the set of unique (up to rewriting) patterns/shapes in the grammar used by
69
  // matching
70
48
  std::unordered_map<TypeNode, std::vector<Node>> pool;
71
72
24
  uint64_t count = 0;
73
74
  // algorithm
75
20298
  while (d_sol[k0].isNull() && count < enumLimit)
76
  {
77
    // enumeration phase
78
    // a temporary set of new terms to reconstruct cached for processing in the
79
    // match phase
80
20274
    TypeBuiltinSetMap termsToReconsPrime;
81
21645
    for (const std::pair<const TypeNode, BuiltinSet>& pair : termsToRecons)
82
    {
83
      // enumerate a new term
84
11508
      Trace("sygus-rcons") << "enum: " << stn << ": ";
85
16124
      Node sz = d_stnInfo[pair.first].nextEnum();
86
11508
      if (sz.isNull())
87
      {
88
6221
        continue;
89
      }
90
9903
      Node builtin = Rewriter::rewrite(datatypes::utils::sygusToBuiltin(sz));
91
      // if enumerated term does not contain free variables, then its
92
      // corresponding obligation can be solved immediately
93
5287
      if (sz.isConst())
94
      {
95
1342
        Node rep = d_stnInfo[pair.first].addTerm(builtin);
96
671
        RConsObligation* ob = d_stnInfo[pair.first].builtinToOb(rep);
97
        // check if the enumerated term solves an obligation
98
671
        if (ob == nullptr)
99
        {
100
          // if not, create an "artifical" obligation whose solution would be
101
          // the enumerated term
102
1284
          d_obs.push_back(
103
1284
              std::make_unique<RConsObligation>(pair.first, builtin));
104
642
          d_stnInfo[pair.first].setBuiltinToOb(builtin, d_obs.back().get());
105
642
          ob = d_obs.back().get();
106
        }
107
        // mark the obligation as solved
108
671
        markSolved(ob, sz);
109
        // Since we added the term to the candidate rewrite database, there is
110
        // no point in adding it to the pool too
111
671
        continue;
112
      }
113
      // if there are no matches (all calls to notify return true)...
114
4616
      if (d_poolTrie.getMatches(builtin, this))
115
      {
116
        // then, this is a new term and we should add it to pool
117
452
        d_poolTrie.addTerm(builtin);
118
452
        pool[pair.first].push_back(sz);
119
1339
        for (const Node& t : pair.second)
120
        {
121
887
          RConsObligation* ob = d_stnInfo[pair.first].builtinToOb(t);
122
887
          if (d_sol[ob->getSkolem()].isNull())
123
          {
124
886
            Trace("sygus-rcons") << "ob: " << *ob << std::endl;
125
            // try to match builtin term `t` with the enumerated term sz
126
1772
            TypeBuiltinSetMap temp = matchNewObs(t, sz);
127
            // cache the new obligations for processing in the match phase
128
905
            for (const std::pair<const TypeNode, BuiltinSet>& tempPair : temp)
129
            {
130
19
              termsToReconsPrime[tempPair.first].insert(
131
                  tempPair.second.cbegin(), tempPair.second.cend());
132
            }
133
          }
134
        }
135
      }
136
    }
137
    // match phase
138
10173
    while (!termsToReconsPrime.empty())
139
    {
140
      // a temporary set of new terms to reconstruct cached for later processing
141
36
      TypeBuiltinSetMap obsDPrime;
142
22
      for (const std::pair<const TypeNode, BuiltinSet>& pair :
143
18
           termsToReconsPrime)
144
      {
145
54
        for (const Node& t : pair.second)
146
        {
147
32
          termsToRecons[pair.first].emplace(t);
148
32
          RConsObligation* ob = d_stnInfo[pair.first].builtinToOb(t);
149
32
          if (d_sol[ob->getSkolem()].isNull())
150
          {
151
32
            Trace("sygus-rcons") << "ob: " << *ob << std::endl;
152
294
            for (const Node& sz : pool[pair.first])
153
            {
154
              // try to match each newly generated and cached term with patterns
155
              // in pool
156
524
              TypeBuiltinSetMap temp = matchNewObs(t, sz);
157
              // cache the new terms for later processing
158
267
              for (const std::pair<const TypeNode, BuiltinSet>& tempPair : temp)
159
              {
160
5
                obsDPrime[tempPair.first].insert(tempPair.second.cbegin(),
161
                                                 tempPair.second.cend());
162
              }
163
            }
164
          }
165
        }
166
      }
167
18
      termsToReconsPrime = std::move(obsDPrime);
168
    }
169
    // remove reconstructed terms from termsToRecons
170
10137
    removeReconstructedTerms(termsToRecons);
171
10137
    ++count;
172
  }
173
174
24
  if (Trace("sygus-rcons").isConnected())
175
  {
176
    RConsObligation::printCandSols(ob0, d_obs);
177
    printPool(pool);
178
  }
179
180
  // if the main obligation is solved, return the solution
181
24
  if (!d_sol[k0].isNull())
182
  {
183
24
    reconstructed = 1;
184
    // The algorithm mostly works with rewritten terms and may not notice that
185
    // the original terms contain variables eliminated by the rewriter. For
186
    // example, rewrite((ite true 0 z)) = 0. In such cases, we replace those
187
    // variables with ground values.
188
24
    return d_sol[k0].isConst() ? Node(d_sol[k0]) : mkGround(d_sol[k0]);
189
  }
190
191
  // we ran out of elements, return null
192
  reconstructed = -1;
193
  Warning() << CommandFailure(
194
      "Cannot get synth function: reconstruction to syntax failed.");
195
  return Node::null();
196
}
197
198
1148
TypeBuiltinSetMap SygusReconstruct::matchNewObs(Node t, Node sz)
199
{
200
1148
  TypeBuiltinSetMap termsToReconsPrime;
201
202
  // substitutions generated by match. Note that we might have already seen (and
203
  // even solved) obligations corresponsing to those substitutions
204
2296
  NodePairMap matches;
205
  // the builtin terms corresponding to sygus variables in the grammar are bound
206
  // variables. However, we want the `match` method to treat them as ground
207
  // terms. So, we add redundant substitutions
208
1148
  matches.insert(d_sygusVars.cbegin(), d_sygusVars.cend());
209
210
  // try to match the builtin term with the pattern sz
211
3444
  if (expr::match(
212
2296
          Rewriter::rewrite(datatypes::utils::sygusToBuiltin(sz)), t, matches))
213
  {
214
    // the bound variables z generated by the enumerators are reused across
215
    // enumerated terms, so we need to replace them with our own skolems
216
82
    NodePairMap subs;
217
41
    Trace("sygus-rcons") << "-- ct: " << sz << std::endl;
218
    // remove redundant substitutions
219
146
    for (const std::pair<const Node, Node>& pair : d_sygusVars)
220
    {
221
105
      matches.erase(pair.first);
222
    }
223
    // for each match
224
114
    for (const std::pair<const Node, Node>& match : matches)
225
    {
226
146
      TypeNode stn = datatypes::utils::builtinVarToSygus(match.first).getType();
227
      RConsObligation* newOb;
228
      // did we come across an equivalent obligation before?
229
146
      Node rep = d_stnInfo[stn].addTerm(match.second);
230
73
      RConsObligation* repOb = d_stnInfo[stn].builtinToOb(rep);
231
73
      if (repOb != nullptr)
232
      {
233
        // if so, use the original obligation
234
41
        newOb = repOb;
235
        // while `match.second` is equivalent to `rep`, it may be easier to
236
        // reconstruct than `rep`. For example:
237
        //
238
        // Grammar: S -> p | q | (not S) | (and S S) | (or S S)
239
        // rep = (= p q)
240
        // match.second = (or (and p q) (and (not p) (not q)))
241
        //
242
        // In this case, `match.second` is easy to reconstruct by matching
243
        // because it only uses operators that are already in the grammar.
244
        // `rep`, on the other hand, cannot be reconstructed by matching and
245
        // can only be solved by enumeration (currently).
246
        //
247
        // At this point, we do not know which one is easier to reconstruct by
248
        // matching, so we add `match.second` to the set of equivalent builtin
249
        // terms in `repOb` and match against both terms.
250
123
        if (repOb->getBuiltins().find(match.second)
251
123
            == repOb->getBuiltins().cend())
252
        {
253
1
          repOb->addBuiltin(match.second);
254
1
          d_stnInfo[stn].setBuiltinToOb(match.second, repOb);
255
1
          termsToReconsPrime[stn].emplace(match.second);
256
        }
257
      }
258
      else
259
      {
260
        // otherwise, create a new obligation of the corresponding sygus type
261
32
        d_obs.push_back(std::make_unique<RConsObligation>(stn, match.second));
262
32
        d_stnInfo[stn].setBuiltinToOb(match.second, d_obs.back().get());
263
32
        newOb = d_obs.back().get();
264
        // if the match is a constant and the grammar allows random constants
265
32
        if (match.second.isConst() && stn.getDType().getSygusAllowConst())
266
        {
267
          // then immediately solve the obligation
268
1
          markSolved(newOb, d_tds->getProxyVariable(stn, match.second));
269
        }
270
        else
271
        {
272
          // otherwise, add this match to the list of obligations
273
31
          termsToReconsPrime[stn].emplace(match.second);
274
        }
275
      }
276
73
      subs.emplace(datatypes::utils::builtinVarToSygus(match.first),
277
146
                   newOb->getSkolem());
278
    }
279
    // replace original free vars in sz with new ones
280
41
    if (!subs.empty())
281
    {
282
38
      sz = sz.substitute(subs.cbegin(), subs.cend());
283
    }
284
    // sz is solved if it has no sub-obligations or if all of them are solved
285
41
    bool isSolved = true;
286
114
    for (const std::pair<const Node, Node>& match : matches)
287
    {
288
146
      TypeNode stn = datatypes::utils::builtinVarToSygus(match.first).getType();
289
73
      RConsObligation* ob = d_stnInfo[stn].builtinToOb(match.second);
290
73
      if (d_sol[ob->getSkolem()].isNull())
291
      {
292
33
        isSolved = false;
293
33
        d_subObs[sz].push_back(ob);
294
      }
295
    }
296
297
41
    RConsObligation* ob = d_stnInfo[sz.getType()].builtinToOb(t);
298
299
41
    if (isSolved)
300
    {
301
      // As it traverses sz, substitute populates its input cache with TNodes
302
      // that are not preserved by this module and maybe destroyed after the
303
      // method call. To avoid referencing those unsafe TNodes throughout this
304
      // module, we pass a iterators of d_sol instead.
305
40
      Node s = sz.substitute(d_sol.cbegin(), d_sol.cend());
306
20
      markSolved(ob, s);
307
    }
308
    else
309
    {
310
      // add sz as a possible solution to ob
311
21
      ob->addCandidateSolution(sz);
312
21
      d_parentOb[sz] = ob;
313
21
      d_subObs[sz].back()->addCandidateSolutionToWatchSet(sz);
314
    }
315
  }
316
317
2296
  return termsToReconsPrime;
318
}
319
320
692
void SygusReconstruct::markSolved(RConsObligation* ob, Node s)
321
{
322
  // return if ob is already solved
323
692
  if (!d_sol[ob->getSkolem()].isNull())
324
  {
325
18
    return;
326
  }
327
328
674
  Trace("sygus-rcons") << "sol: " << s << std::endl;
329
1348
  Trace("sygus-rcons") << "builtin sol: " << datatypes::utils::sygusToBuiltin(s)
330
674
                       << std::endl;
331
332
  // First, mark ob as solved
333
674
  ob->addCandidateSolution(s);
334
674
  d_sol[ob->getSkolem()] = s;
335
674
  d_parentOb[s] = ob;
336
337
1348
  std::vector<RConsObligation*> stack;
338
674
  stack.push_back(ob);
339
340
2054
  while (!stack.empty())
341
  {
342
690
    RConsObligation* curr = stack.back();
343
690
    stack.pop_back();
344
345
    // for each partial solution/parent of the now solved obligation `curr`
346
709
    for (const Node& parent : curr->getWatchSet())
347
    {
348
      // remove `curr` and (possibly) other solved obligations from its list
349
      // of children
350
69
      while (!d_subObs[parent].empty()
351
88
             && !d_sol[d_subObs[parent].back()->getSkolem()].isNull())
352
      {
353
25
        d_subObs[parent].pop_back();
354
      }
355
356
      // if the partial solution does not have any more children...
357
19
      if (d_subObs[parent].empty())
358
      {
359
        // then it is completely solved and can be used as a solution of its
360
        // corresponding obligation
361
        // pass iterators of d_sol to avoid populating it with unsafe TNodes
362
32
        Node parentSol = parent.substitute(d_sol.cbegin(), d_sol.cend());
363
16
        RConsObligation* parentOb = d_parentOb[parent];
364
        // proceed only if parent obligation is not already solved
365
16
        if (d_sol[parentOb->getSkolem()].isNull())
366
        {
367
16
          parentOb->addCandidateSolution(parentSol);
368
16
          d_sol[parentOb->getSkolem()] = parentSol;
369
16
          d_parentOb[parentSol] = parentOb;
370
          // repeat the same process for the parent obligation
371
16
          stack.push_back(parentOb);
372
        }
373
      }
374
      else
375
      {
376
        // if it does have remaining children, add it to the watch list of one
377
        // of them (picked arbitrarily)
378
3
        d_subObs[parent].back()->addCandidateSolutionToWatchSet(parent);
379
      }
380
    }
381
  }
382
}
383
384
24
void SygusReconstruct::initialize(TypeNode stn)
385
{
386
48
  std::vector<Node> builtinVars;
387
388
  // Cache the sygus variables introduced by the problem (which we treat as
389
  // ground terms when calling the `match` method) as opposed to the sygus
390
  // variables introduced by the enumerators (which we treat as bound
391
  // variables).
392
71
  for (Node sv : stn.getDType().getSygusVarList())
393
  {
394
47
    builtinVars.push_back(datatypes::utils::sygusToBuiltin(sv));
395
47
    d_sygusVars.emplace(datatypes::utils::sygusToBuiltin(sv),
396
94
                        datatypes::utils::sygusToBuiltin(sv));
397
  }
398
399
48
  SygusTypeInfo stnInfo;
400
24
  stnInfo.initialize(d_tds, stn);
401
402
  // find the non-terminals of the grammar
403
48
  std::vector<TypeNode> sfTypes;
404
24
  stnInfo.getSubfieldTypes(sfTypes);
405
406
  // initialize the enumerators and candidate rewrite databases. Notice here
407
  // that we treat the sygus variables introduced by the problem as bound
408
  // variables (needed for making sure that obligations are equal). This is fine
409
  // as we will never add variables that were introduced by the enumerators to
410
  // the database.
411
77
  for (TypeNode tn : sfTypes)
412
  {
413
53
    d_stnInfo[tn].initialize(d_env, d_tds, d_stats, tn, builtinVars);
414
  }
415
24
}
416
417
10137
void SygusReconstruct::removeReconstructedTerms(
418
    TypeBuiltinSetMap& termsToRecons)
419
{
420
21652
  for (std::pair<const TypeNode, BuiltinSet>& pair : termsToRecons)
421
  {
422
11515
    BuiltinSet::iterator it = pair.second.begin();
423
39241
    while (it != pair.second.end())
424
    {
425
13863
      if (d_sol[d_stnInfo[pair.first].builtinToOb(*it)->getSkolem()].isNull())
426
      {
427
13815
        ++it;
428
      }
429
      else
430
      {
431
48
        it = pair.second.erase(it);
432
      }
433
    }
434
  }
435
10137
}
436
437
6
Node SygusReconstruct::mkGround(Node n) const
438
{
439
  // get the set of bound variables in n
440
12
  std::unordered_set<TNode> vars;
441
6
  expr::getVariables(n, vars);
442
443
12
  std::unordered_map<TNode, TNode> subs;
444
445
  // generate a ground value for each one of those variables
446
12
  for (const TNode& var : vars)
447
  {
448
6
    subs.emplace(var, var.getType().mkGroundValue());
449
  }
450
451
  // substitute the variables with ground values
452
12
  return n.substitute(subs);
453
}
454
455
4297
bool SygusReconstruct::notify(Node s,
456
                              Node n,
457
                              std::vector<Node>& vars,
458
                              std::vector<Node>& subs)
459
{
460
14490
  for (size_t i = 0; i < vars.size(); ++i)
461
  {
462
    // We consider sygus variables as ground terms. So, if they are not equal to
463
    // their substitution, then s is not matchable with n and we try the next
464
    // term s. Example: If s = (+ z x) and n = (+ z y), then s is not matchable
465
    // with n and we return true
466
10326
    if (d_sygusVars.find(vars[i]) != d_sygusVars.cend() && vars[i] != subs[i])
467
    {
468
133
      return true;
469
    }
470
  }
471
  // Note: false here means that we finally found an s that is matchable with n,
472
  // so we should not add n to the pool
473
4164
  return false;
474
}
475
476
24
void SygusReconstruct::clear()
477
{
478
24
  d_obs.clear();
479
24
  d_stnInfo.clear();
480
24
  d_sol.clear();
481
24
  d_subObs.clear();
482
24
  d_parentOb.clear();
483
24
  d_sygusVars.clear();
484
24
  d_poolTrie.clear();
485
24
}
486
487
void SygusReconstruct::printPool(
488
    const std::unordered_map<TypeNode, std::vector<Node>>& pool) const
489
{
490
  Trace("sygus-rcons") << std::endl << "Pool:" << std::endl << '{';
491
492
  for (const std::pair<const TypeNode, std::vector<Node>>& pair : pool)
493
  {
494
    Trace("sygus-rcons") << std::endl
495
                         << "  " << pair.first << ':' << std::endl
496
                         << "  [" << std::endl;
497
498
    for (const Node& sygusTerm : pair.second)
499
    {
500
      Trace("sygus-rcons") << "    "
501
                           << Rewriter::rewrite(
502
                                  datatypes::utils::sygusToBuiltin(sygusTerm))
503
                                  .toString()
504
                           << std::endl;
505
    }
506
507
    Trace("sygus-rcons") << "  ]" << std::endl;
508
  }
509
510
  Trace("sygus-rcons") << '}' << std::endl;
511
}
512
513
}  // namespace quantifiers
514
}  // namespace theory
515
29577
}  // namespace cvc5