GCC Code Coverage Report
Directory: . Exec Total Coverage
File: src/theory/quantifiers/sygus/sygus_reconstruct.cpp Lines: 183 204 89.7 %
Date: 2021-05-22 Branches: 394 794 49.6 %

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