GCC Code Coverage Report
Directory: . Exec Total Coverage
File: src/theory/quantifiers/sygus/transition_inference.cpp Lines: 280 298 94.0 %
Date: 2021-05-22 Branches: 521 1118 46.6 %

Line Exec Source
1
/******************************************************************************
2
 * Top contributors (to current version):
3
 *   Andrew Reynolds, Andres Noetzli, 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 utility for inferring whether a synthesis conjecture
14
 * encodes a transition system.
15
 */
16
#include "theory/quantifiers/sygus/transition_inference.h"
17
18
#include "expr/node_algorithm.h"
19
#include "expr/skolem_manager.h"
20
#include "theory/arith/arith_msum.h"
21
#include "theory/quantifiers/term_util.h"
22
#include "theory/rewriter.h"
23
24
using namespace cvc5::kind;
25
26
namespace cvc5 {
27
namespace theory {
28
namespace quantifiers {
29
30
428
bool DetTrace::DetTraceTrie::add(Node loc, const std::vector<Node>& val)
31
{
32
428
  DetTraceTrie* curr = this;
33
1270
  for (const Node& v : val)
34
  {
35
842
    curr = &(curr->d_children[v]);
36
  }
37
428
  if (curr->d_children.empty())
38
  {
39
428
    curr->d_children[loc].clear();
40
428
    return true;
41
  }
42
  return false;
43
}
44
45
14
Node DetTrace::DetTraceTrie::constructFormula(const std::vector<Node>& vars,
46
                                              unsigned index)
47
{
48
14
  NodeManager* nm = NodeManager::currentNM();
49
14
  if (index == vars.size())
50
  {
51
    return nm->mkConst(true);
52
  }
53
28
  std::vector<Node> disj;
54
48
  for (std::pair<const Node, DetTraceTrie>& p : d_children)
55
  {
56
68
    Node eq = vars[index].eqNode(p.first);
57
34
    if (index < vars.size() - 1)
58
    {
59
20
      Node conc = p.second.constructFormula(vars, index + 1);
60
10
      disj.push_back(nm->mkNode(AND, eq, conc));
61
    }
62
    else
63
    {
64
24
      disj.push_back(eq);
65
    }
66
  }
67
14
  Assert(!disj.empty());
68
14
  return disj.size() == 1 ? disj[0] : nm->mkNode(OR, disj);
69
}
70
71
428
bool DetTrace::increment(Node loc, std::vector<Node>& vals)
72
{
73
428
  if (d_trie.add(loc, vals))
74
  {
75
1270
    for (unsigned i = 0, vsize = vals.size(); i < vsize; i++)
76
    {
77
842
      d_curr[i] = vals[i];
78
    }
79
428
    return true;
80
  }
81
  return false;
82
}
83
84
4
Node DetTrace::constructFormula(const std::vector<Node>& vars)
85
{
86
4
  return d_trie.constructFormula(vars);
87
}
88
89
432
void DetTrace::print(const char* c) const
90
{
91
1280
  for (const Node& n : d_curr)
92
  {
93
848
    Trace(c) << n << " ";
94
  }
95
432
}
96
97
37
Node TransitionInference::getFunction() const { return d_func; }
98
99
37
void TransitionInference::getVariables(std::vector<Node>& vars) const
100
{
101
37
  vars.insert(vars.end(), d_vars.begin(), d_vars.end());
102
37
}
103
104
37
Node TransitionInference::getPreCondition() const { return d_pre.d_this; }
105
461
Node TransitionInference::getPostCondition() const { return d_post.d_this; }
106
432
Node TransitionInference::getTransitionRelation() const
107
{
108
432
  return d_trans.d_this;
109
}
110
111
92
void TransitionInference::getConstantSubstitution(
112
    const std::vector<Node>& vars,
113
    const std::vector<Node>& disjuncts,
114
    std::vector<Node>& const_var,
115
    std::vector<Node>& const_subs,
116
    bool reqPol)
117
{
118
454
  for (const Node& d : disjuncts)
119
  {
120
724
    Node sn;
121
362
    if (!const_var.empty())
122
    {
123
181
      sn = d.substitute(const_var.begin(),
124
                        const_var.end(),
125
                        const_subs.begin(),
126
                        const_subs.end());
127
181
      sn = Rewriter::rewrite(sn);
128
    }
129
    else
130
    {
131
181
      sn = d;
132
    }
133
362
    bool slit_pol = sn.getKind() != NOT;
134
724
    Node slit = sn.getKind() == NOT ? sn[0] : sn;
135
362
    if (slit.getKind() == EQUAL && slit_pol == reqPol)
136
    {
137
      // check if it is a variable equality
138
260
      TNode v;
139
260
      Node s;
140
208
      for (unsigned r = 0; r < 2; r++)
141
      {
142
186
        if (std::find(vars.begin(), vars.end(), slit[r]) != vars.end())
143
        {
144
108
          if (!expr::hasSubterm(slit[1 - r], slit[r]))
145
          {
146
108
            v = slit[r];
147
108
            s = slit[1 - r];
148
108
            break;
149
          }
150
        }
151
      }
152
130
      if (v.isNull())
153
      {
154
        // solve for var
155
44
        std::map<Node, Node> msum;
156
22
        if (ArithMSum::getMonomialSumLit(slit, msum))
157
        {
158
88
          for (std::pair<const Node, Node>& m : msum)
159
          {
160
66
            if (std::find(vars.begin(), vars.end(), m.first) != vars.end())
161
            {
162
44
              Node veq_c;
163
44
              Node val;
164
22
              int ires = ArithMSum::isolate(m.first, msum, veq_c, val, EQUAL);
165
44
              if (ires != 0 && veq_c.isNull()
166
44
                  && !expr::hasSubterm(val, m.first))
167
              {
168
22
                v = m.first;
169
22
                s = val;
170
              }
171
            }
172
          }
173
        }
174
      }
175
130
      if (!v.isNull())
176
      {
177
260
        TNode ts = s;
178
560
        for (unsigned k = 0, csize = const_subs.size(); k < csize; k++)
179
        {
180
430
          const_subs[k] = Rewriter::rewrite(const_subs[k].substitute(v, ts));
181
        }
182
260
        Trace("cegqi-inv-debug2")
183
130
            << "...substitution : " << v << " -> " << s << std::endl;
184
130
        const_var.push_back(v);
185
130
        const_subs.push_back(s);
186
      }
187
    }
188
  }
189
92
}
190
191
42
void TransitionInference::process(Node n, Node f)
192
{
193
  // set the function
194
42
  d_func = f;
195
42
  process(n);
196
42
}
197
198
42
void TransitionInference::process(Node n)
199
{
200
42
  NodeManager* nm = NodeManager::currentNM();
201
42
  SkolemManager* sm = nm->getSkolemManager();
202
42
  d_complete = true;
203
42
  d_trivial = true;
204
84
  std::vector<Node> n_check;
205
42
  if (n.getKind() == AND)
206
  {
207
120
    for (const Node& nc : n)
208
    {
209
89
      n_check.push_back(nc);
210
    }
211
  }
212
  else
213
  {
214
11
    n_check.push_back(n);
215
  }
216
142
  for (const Node& nn : n_check)
217
  {
218
192
    std::map<bool, std::map<Node, bool> > visited;
219
192
    std::map<bool, Node> terms;
220
192
    std::vector<Node> disjuncts;
221
200
    Trace("cegqi-inv") << "TransitionInference : Process disjunct : " << nn
222
100
                       << std::endl;
223
105
    if (!processDisjunct(nn, terms, disjuncts, visited, true))
224
    {
225
5
      d_complete = false;
226
5
      continue;
227
    }
228
95
    if (terms.empty())
229
    {
230
3
      continue;
231
    }
232
184
    Node curr;
233
    // The component that this disjunct contributes to, where
234
    // 1 : pre-condition, -1 : post-condition, 0 : transition relation
235
    int comp_num;
236
92
    std::map<bool, Node>::iterator itt = terms.find(false);
237
92
    if (itt != terms.end())
238
    {
239
62
      curr = itt->second;
240
62
      if (terms.find(true) != terms.end())
241
      {
242
25
        comp_num = 0;
243
      }
244
      else
245
      {
246
37
        comp_num = -1;
247
      }
248
    }
249
    else
250
    {
251
30
      curr = terms[true];
252
30
      comp_num = 1;
253
    }
254
92
    Trace("cegqi-inv-debug2") << "  normalize based on " << curr << std::endl;
255
184
    std::vector<Node> vars;
256
184
    std::vector<Node> svars;
257
92
    getNormalizedSubstitution(curr, d_vars, vars, svars, disjuncts);
258
450
    for (unsigned j = 0, dsize = disjuncts.size(); j < dsize; j++)
259
    {
260
358
      Trace("cegqi-inv-debug2") << "  apply " << disjuncts[j] << std::endl;
261
358
      disjuncts[j] = Rewriter::rewrite(disjuncts[j].substitute(
262
          vars.begin(), vars.end(), svars.begin(), svars.end()));
263
358
      Trace("cegqi-inv-debug2") << "  ..." << disjuncts[j] << std::endl;
264
    }
265
184
    std::vector<Node> const_var;
266
184
    std::vector<Node> const_subs;
267
92
    if (comp_num == 0)
268
    {
269
      // transition
270
25
      Assert(terms.find(true) != terms.end());
271
50
      Node next = terms[true];
272
25
      next = Rewriter::rewrite(next.substitute(
273
          vars.begin(), vars.end(), svars.begin(), svars.end()));
274
50
      Trace("cegqi-inv-debug")
275
25
          << "transition next predicate : " << next << std::endl;
276
      // make the primed variables if we have not already
277
25
      if (d_prime_vars.empty())
278
      {
279
139
        for (unsigned j = 0, nchild = next.getNumChildren(); j < nchild; j++)
280
        {
281
          Node v = sm->mkDummySkolem(
282
228
              "ir", next[j].getType(), "template inference rev argument");
283
114
          d_prime_vars.push_back(v);
284
        }
285
      }
286
      // normalize the other direction
287
25
      Trace("cegqi-inv-debug2") << "  normalize based on " << next << std::endl;
288
50
      std::vector<Node> rvars;
289
50
      std::vector<Node> rsvars;
290
25
      getNormalizedSubstitution(next, d_prime_vars, rvars, rsvars, disjuncts);
291
25
      Assert(rvars.size() == rsvars.size());
292
139
      for (unsigned j = 0, dsize = disjuncts.size(); j < dsize; j++)
293
      {
294
114
        Trace("cegqi-inv-debug2") << "  apply " << disjuncts[j] << std::endl;
295
114
        disjuncts[j] = Rewriter::rewrite(disjuncts[j].substitute(
296
            rvars.begin(), rvars.end(), rsvars.begin(), rsvars.end()));
297
114
        Trace("cegqi-inv-debug2") << "  ..." << disjuncts[j] << std::endl;
298
      }
299
25
      getConstantSubstitution(
300
          d_prime_vars, disjuncts, const_var, const_subs, false);
301
    }
302
    else
303
    {
304
67
      getConstantSubstitution(d_vars, disjuncts, const_var, const_subs, false);
305
    }
306
184
    Node res;
307
92
    if (disjuncts.empty())
308
    {
309
3
      res = nm->mkConst(false);
310
    }
311
89
    else if (disjuncts.size() == 1)
312
    {
313
22
      res = disjuncts[0];
314
    }
315
    else
316
    {
317
67
      res = nm->mkNode(OR, disjuncts);
318
    }
319
92
    if (expr::hasBoundVar(res))
320
    {
321
      Trace("cegqi-inv-debug2") << "...failed, free variable." << std::endl;
322
      d_complete = false;
323
      continue;
324
    }
325
184
    Trace("cegqi-inv") << "*** inferred "
326
246
                       << (comp_num == 1 ? "pre"
327
154
                                         : (comp_num == -1 ? "post" : "trans"))
328
92
                       << "-condition : " << res << std::endl;
329
92
    Component& c =
330
        (comp_num == 1 ? d_pre : (comp_num == -1 ? d_post : d_trans));
331
92
    c.d_conjuncts.push_back(res);
332
92
    if (!const_var.empty())
333
    {
334
50
      bool has_const_eq = const_var.size() == d_vars.size();
335
100
      Trace("cegqi-inv") << "    with constant substitution, complete = "
336
50
                         << has_const_eq << " : " << std::endl;
337
180
      for (unsigned i = 0, csize = const_var.size(); i < csize; i++)
338
      {
339
260
        Trace("cegqi-inv") << "      " << const_var[i] << " -> "
340
130
                           << const_subs[i] << std::endl;
341
130
        if (has_const_eq)
342
        {
343
51
          c.d_const_eq[res][const_var[i]] = const_subs[i];
344
        }
345
      }
346
100
      Trace("cegqi-inv") << "...size = " << const_var.size()
347
50
                         << ", #vars = " << d_vars.size() << std::endl;
348
    }
349
  }
350
351
  // finalize the components
352
168
  for (int i = -1; i <= 1; i++)
353
  {
354
126
    Component& c = (i == 1 ? d_pre : (i == -1 ? d_post : d_trans));
355
252
    Node ret;
356
126
    if (c.d_conjuncts.empty())
357
    {
358
37
      ret = nm->mkConst(true);
359
    }
360
89
    else if (c.d_conjuncts.size() == 1)
361
    {
362
87
      ret = c.d_conjuncts[0];
363
    }
364
    else
365
    {
366
2
      ret = nm->mkNode(AND, c.d_conjuncts);
367
    }
368
126
    if (i == 0 || i == 1)
369
    {
370
      // pre-condition and transition are negated
371
84
      ret = TermUtil::simpleNegate(ret);
372
    }
373
126
    c.d_this = ret;
374
  }
375
42
}
376
117
void TransitionInference::getNormalizedSubstitution(
377
    Node curr,
378
    const std::vector<Node>& pvars,
379
    std::vector<Node>& vars,
380
    std::vector<Node>& subs,
381
    std::vector<Node>& disjuncts)
382
{
383
659
  for (unsigned j = 0, nchild = curr.getNumChildren(); j < nchild; j++)
384
  {
385
542
    if (curr[j].getKind() == BOUND_VARIABLE)
386
    {
387
      // if the argument is a bound variable, add to the renaming
388
501
      vars.push_back(curr[j]);
389
501
      subs.push_back(pvars[j]);
390
    }
391
    else
392
    {
393
      // otherwise, treat as a constraint on the variable
394
      // For example, this transforms e.g. a precondition clause
395
      // I( 0, 1 ) to x1 != 0 OR x2 != 1 OR I( x1, x2 ).
396
82
      Node eq = curr[j].eqNode(pvars[j]);
397
41
      disjuncts.push_back(eq.negate());
398
    }
399
  }
400
117
}
401
402
2229
bool TransitionInference::processDisjunct(
403
    Node n,
404
    std::map<bool, Node>& terms,
405
    std::vector<Node>& disjuncts,
406
    std::map<bool, std::map<Node, bool> >& visited,
407
    bool topLevel)
408
{
409
2229
  if (visited[topLevel].find(n) != visited[topLevel].end())
410
  {
411
559
    return true;
412
  }
413
1670
  visited[topLevel][n] = true;
414
1670
  bool childTopLevel = n.getKind() == OR && topLevel;
415
  // if another part mentions UF or a free variable, then fail
416
1670
  bool lit_pol = n.getKind() != NOT;
417
3340
  Node lit = n.getKind() == NOT ? n[0] : n;
418
  // is it an application of the function-to-synthesize? Yes if we haven't
419
  // encountered a function or if it matches the existing d_func.
420
3340
  if (lit.getKind() == APPLY_UF
421
3340
      && (d_func.isNull() || lit.getOperator() == d_func))
422
  {
423
244
    Node op = lit.getOperator();
424
    // initialize the variables
425
122
    if (d_trivial)
426
    {
427
41
      d_trivial = false;
428
41
      d_func = op;
429
41
      Trace("cegqi-inv-debug") << "Use " << op << " with args ";
430
41
      NodeManager* nm = NodeManager::currentNM();
431
41
      SkolemManager* sm = nm->getSkolemManager();
432
240
      for (const Node& l : lit)
433
      {
434
        Node v =
435
398
            sm->mkDummySkolem("i", l.getType(), "template inference argument");
436
199
        d_vars.push_back(v);
437
199
        Trace("cegqi-inv-debug") << v << " ";
438
      }
439
41
      Trace("cegqi-inv-debug") << std::endl;
440
    }
441
122
    Assert(!d_func.isNull());
442
122
    if (topLevel)
443
    {
444
117
      if (terms.find(lit_pol) == terms.end())
445
      {
446
117
        terms[lit_pol] = lit;
447
117
        return true;
448
      }
449
      else
450
      {
451
        Trace("cegqi-inv-debug")
452
            << "...failed, repeated inv-app : " << lit << std::endl;
453
        return false;
454
      }
455
    }
456
10
    Trace("cegqi-inv-debug")
457
5
        << "...failed, non-entailed inv-app : " << lit << std::endl;
458
5
    return false;
459
  }
460
1548
  else if (topLevel && !childTopLevel)
461
  {
462
329
    disjuncts.push_back(n);
463
  }
464
3668
  for (const Node& nc : n)
465
  {
466
2129
    if (!processDisjunct(nc, terms, disjuncts, visited, childTopLevel))
467
    {
468
9
      return false;
469
    }
470
  }
471
1539
  return true;
472
}
473
474
28
TraceIncStatus TransitionInference::initializeTrace(DetTrace& dt,
475
                                                    Node loc,
476
                                                    bool fwd)
477
{
478
28
  Component& c = fwd ? d_pre : d_post;
479
28
  Assert(c.has(loc));
480
28
  std::map<Node, std::map<Node, Node> >::iterator it = c.d_const_eq.find(loc);
481
28
  if (it != c.d_const_eq.end())
482
  {
483
16
    std::vector<Node> next;
484
22
    for (const Node& v : d_vars)
485
    {
486
14
      Assert(it->second.find(v) != it->second.end());
487
14
      next.push_back(it->second[v]);
488
14
      dt.d_curr.push_back(it->second[v]);
489
    }
490
8
    Trace("cegqi-inv-debug2") << "dtrace : initial increment" << std::endl;
491
8
    bool ret = dt.increment(loc, next);
492
8
    AlwaysAssert(ret);
493
8
    return TRACE_INC_SUCCESS;
494
  }
495
20
  return TRACE_INC_INVALID;
496
}
497
498
424
TraceIncStatus TransitionInference::incrementTrace(DetTrace& dt,
499
                                                   Node loc,
500
                                                   bool fwd)
501
{
502
424
  Assert(d_trans.has(loc));
503
  // check if it satisfies the pre/post condition
504
848
  Node cc = fwd ? getPostCondition() : getPreCondition();
505
424
  Assert(!cc.isNull());
506
848
  Node ccr = Rewriter::rewrite(cc.substitute(
507
848
      d_vars.begin(), d_vars.end(), dt.d_curr.begin(), dt.d_curr.end()));
508
424
  if (ccr.isConst())
509
  {
510
424
    if (ccr.getConst<bool>() == (fwd ? false : true))
511
    {
512
      Trace("cegqi-inv-debug2") << "dtrace : counterexample" << std::endl;
513
      return TRACE_INC_CEX;
514
    }
515
  }
516
517
  // terminates?
518
848
  Node c = getTransitionRelation();
519
424
  Assert(!c.isNull());
520
521
424
  Assert(d_vars.size() == dt.d_curr.size());
522
848
  Node cr = Rewriter::rewrite(c.substitute(
523
848
      d_vars.begin(), d_vars.end(), dt.d_curr.begin(), dt.d_curr.end()));
524
424
  if (cr.isConst())
525
  {
526
4
    if (!cr.getConst<bool>())
527
    {
528
4
      Trace("cegqi-inv-debug2") << "dtrace : terminated" << std::endl;
529
4
      return TRACE_INC_TERMINATE;
530
    }
531
    return TRACE_INC_INVALID;
532
  }
533
420
  if (!fwd)
534
  {
535
    // only implemented in forward direction
536
    Assert(false);
537
    return TRACE_INC_INVALID;
538
  }
539
420
  Component& cm = d_trans;
540
420
  std::map<Node, std::map<Node, Node> >::iterator it = cm.d_const_eq.find(loc);
541
420
  if (it == cm.d_const_eq.end())
542
  {
543
    return TRACE_INC_INVALID;
544
  }
545
840
  std::vector<Node> next;
546
1248
  for (const Node& pv : d_prime_vars)
547
  {
548
828
    Assert(it->second.find(pv) != it->second.end());
549
1656
    Node pvs = it->second[pv];
550
828
    Assert(d_vars.size() == dt.d_curr.size());
551
1656
    Node pvsr = Rewriter::rewrite(pvs.substitute(
552
1656
        d_vars.begin(), d_vars.end(), dt.d_curr.begin(), dt.d_curr.end()));
553
828
    next.push_back(pvsr);
554
  }
555
420
  if (dt.increment(loc, next))
556
  {
557
420
    Trace("cegqi-inv-debug2") << "dtrace : success increment" << std::endl;
558
420
    return TRACE_INC_SUCCESS;
559
  }
560
  // looped
561
  Trace("cegqi-inv-debug2") << "dtrace : looped" << std::endl;
562
  return TRACE_INC_TERMINATE;
563
}
564
565
29
TraceIncStatus TransitionInference::initializeTrace(DetTrace& dt, bool fwd)
566
{
567
29
  Trace("cegqi-inv-debug2") << "Initialize trace" << std::endl;
568
29
  Component& c = fwd ? d_pre : d_post;
569
29
  if (c.d_conjuncts.size() == 1)
570
  {
571
28
    return initializeTrace(dt, c.d_conjuncts[0], fwd);
572
  }
573
1
  return TRACE_INC_INVALID;
574
}
575
576
424
TraceIncStatus TransitionInference::incrementTrace(DetTrace& dt, bool fwd)
577
{
578
424
  if (d_trans.d_conjuncts.size() == 1)
579
  {
580
424
    return incrementTrace(dt, d_trans.d_conjuncts[0], fwd);
581
  }
582
  return TRACE_INC_INVALID;
583
}
584
585
4
Node TransitionInference::constructFormulaTrace(DetTrace& dt) const
586
{
587
4
  return dt.constructFormula(d_vars);
588
}
589
590
}  // namespace quantifiers
591
}  // namespace theory
592
28194
}  // namespace cvc5