GCC Code Coverage Report
Directory: . Exec Total Coverage
File: src/theory/quantifiers/single_inv_partition.cpp Lines: 270 304 88.8 %
Date: 2021-09-29 Branches: 509 1032 49.3 %

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
 * Utility for processing single invocation synthesis conjectures.
14
 */
15
#include "theory/quantifiers/single_inv_partition.h"
16
17
#include <sstream>
18
19
#include "expr/node_algorithm.h"
20
#include "expr/skolem_manager.h"
21
#include "theory/quantifiers/term_util.h"
22
#include "theory/rewriter.h"
23
24
using namespace cvc5;
25
using namespace cvc5::kind;
26
using namespace std;
27
28
namespace cvc5 {
29
namespace theory {
30
namespace quantifiers {
31
32
bool SingleInvocationPartition::init(Node n)
33
{
34
  // first, get types of arguments for functions
35
  std::vector<TypeNode> typs;
36
  std::map<Node, bool> visited;
37
  std::vector<Node> funcs;
38
  if (inferArgTypes(n, typs, visited))
39
  {
40
    return init(funcs, typs, n, false);
41
  }
42
  else
43
  {
44
    Trace("si-prt") << "Could not infer argument types." << std::endl;
45
    return false;
46
  }
47
}
48
49
63
Node SingleInvocationPartition::getFirstOrderVariableForFunction(Node f) const
50
{
51
63
  std::map<Node, Node>::const_iterator it = d_func_fo_var.find(f);
52
63
  if (it != d_func_fo_var.end())
53
  {
54
63
    return it->second;
55
  }
56
  return Node::null();
57
}
58
59
Node SingleInvocationPartition::getFunctionForFirstOrderVariable(Node v) const
60
{
61
  std::map<Node, Node>::const_iterator it = d_fo_var_to_func.find(v);
62
  if (it != d_fo_var_to_func.end())
63
  {
64
    return it->second;
65
  }
66
  return Node::null();
67
}
68
69
63
Node SingleInvocationPartition::getFunctionInvocationFor(Node f) const
70
{
71
63
  std::map<Node, Node>::const_iterator it = d_func_inv.find(f);
72
63
  if (it != d_func_inv.end())
73
  {
74
63
    return it->second;
75
  }
76
  return Node::null();
77
}
78
79
93
void SingleInvocationPartition::getFunctionVariables(
80
    std::vector<Node>& fvars) const
81
{
82
93
  fvars.insert(fvars.end(), d_func_vars.begin(), d_func_vars.end());
83
93
}
84
85
311
void SingleInvocationPartition::getFunctions(std::vector<Node>& fs) const
86
{
87
311
  fs.insert(fs.end(), d_all_funcs.begin(), d_all_funcs.end());
88
311
}
89
90
95
void SingleInvocationPartition::getSingleInvocationVariables(
91
    std::vector<Node>& sivars) const
92
{
93
95
  sivars.insert(sivars.end(), d_si_vars.begin(), d_si_vars.end());
94
95
}
95
96
2
void SingleInvocationPartition::getAllVariables(std::vector<Node>& vars) const
97
{
98
2
  vars.insert(vars.end(), d_all_vars.begin(), d_all_vars.end());
99
2
}
100
101
// gets the argument type list for the first APPLY_UF we see
102
bool SingleInvocationPartition::inferArgTypes(Node n,
103
                                              std::vector<TypeNode>& typs,
104
                                              std::map<Node, bool>& visited)
105
{
106
  if (visited.find(n) == visited.end())
107
  {
108
    visited[n] = true;
109
    if (n.getKind() != FORALL)
110
    {
111
      if (n.getKind() == APPLY_UF)
112
      {
113
        for (unsigned i = 0; i < n.getNumChildren(); i++)
114
        {
115
          typs.push_back(n[i].getType());
116
        }
117
        return true;
118
      }
119
      else
120
      {
121
        for (unsigned i = 0; i < n.getNumChildren(); i++)
122
        {
123
          if (inferArgTypes(n[i], typs, visited))
124
          {
125
            return true;
126
          }
127
        }
128
      }
129
    }
130
  }
131
  return false;
132
}
133
134
332
bool SingleInvocationPartition::init(std::vector<Node>& funcs, Node n)
135
{
136
664
  Trace("si-prt") << "Initialize with " << funcs.size() << " input functions ("
137
332
                  << funcs << ")..." << std::endl;
138
664
  std::vector<TypeNode> typs;
139
332
  if (!funcs.empty())
140
  {
141
648
    TypeNode tn0 = funcs[0].getType();
142
332
    if (tn0.isFunction())
143
    {
144
1002
      for (unsigned i = 0, nargs = tn0.getNumChildren() - 1; i < nargs; i++)
145
      {
146
727
        typs.push_back(tn0[i]);
147
      }
148
    }
149
640
    for (unsigned i = 1, size = funcs.size(); i < size; i++)
150
    {
151
632
      TypeNode tni = funcs[i].getType();
152
324
      if (tni.getNumChildren() != tn0.getNumChildren())
153
      {
154
        // can't anti-skolemize functions of different sort
155
16
        Trace("si-prt") << "...type mismatch" << std::endl;
156
16
        return false;
157
      }
158
308
      else if (tni.isFunction())
159
      {
160
1027
        for (unsigned j = 0, nargs = tni.getNumChildren() - 1; j < nargs; j++)
161
        {
162
764
          if (tni[j] != typs[j])
163
          {
164
            Trace("si-prt") << "...argument type mismatch" << std::endl;
165
            return false;
166
          }
167
        }
168
      }
169
    }
170
  }
171
316
  Trace("si-prt") << "#types = " << typs.size() << std::endl;
172
316
  return init(funcs, typs, n, true);
173
}
174
175
316
bool SingleInvocationPartition::init(std::vector<Node>& funcs,
176
                                     std::vector<TypeNode>& typs,
177
                                     Node n,
178
                                     bool has_funcs)
179
{
180
316
  Assert(d_arg_types.empty());
181
316
  Assert(d_input_funcs.empty());
182
316
  Assert(d_si_vars.empty());
183
316
  NodeManager* nm = NodeManager::currentNM();
184
316
  SkolemManager* sm = nm->getSkolemManager();
185
316
  d_has_input_funcs = has_funcs;
186
316
  d_arg_types.insert(d_arg_types.end(), typs.begin(), typs.end());
187
316
  d_input_funcs.insert(d_input_funcs.end(), funcs.begin(), funcs.end());
188
316
  Trace("si-prt") << "Initialize..." << std::endl;
189
1036
  for (unsigned j = 0; j < d_arg_types.size(); j++)
190
  {
191
1440
    std::stringstream ss;
192
720
    ss << "s_" << j;
193
1440
    Node si_v = nm->mkBoundVar(ss.str(), d_arg_types[j]);
194
720
    d_si_vars.push_back(si_v);
195
  }
196
316
  Assert(d_si_vars.size() == d_arg_types.size());
197
935
  for (const Node& inf : d_input_funcs)
198
  {
199
1238
    Node sk = sm->mkDummySkolem("_sik", inf.getType());
200
619
    d_input_func_sks.push_back(sk);
201
  }
202
316
  Trace("si-prt") << "SingleInvocationPartition::process " << n << std::endl;
203
316
  Trace("si-prt") << "Get conjuncts..." << std::endl;
204
632
  std::vector<Node> conj;
205
316
  if (collectConjuncts(n, true, conj))
206
  {
207
314
    Trace("si-prt") << "...success." << std::endl;
208
1259
    for (unsigned i = 0; i < conj.size(); i++)
209
    {
210
1890
      std::vector<Node> si_terms;
211
1890
      std::vector<Node> si_subs;
212
945
      Trace("si-prt") << "Process conjunct : " << conj[i] << std::endl;
213
      // do DER on conjunct
214
      // Must avoid eliminating the first-order input functions in the
215
      // getQuantSimplify step below. We use a substitution to avoid this.
216
      // This makes it so that e.g. the synthesis conjecture:
217
      //   exists f. f!=0 ^ P
218
      // is not rewritten to exists f. (f=0 => false) ^ P and subsquently
219
      // rewritten to exists f. false ^ P by the elimination f -> 0.
220
945
      Node cr = conj[i].substitute(d_input_funcs.begin(),
221
                                   d_input_funcs.end(),
222
                                   d_input_func_sks.begin(),
223
1890
                                   d_input_func_sks.end());
224
945
      cr = TermUtil::getQuantSimplify(cr);
225
945
      cr = cr.substitute(d_input_func_sks.begin(),
226
                         d_input_func_sks.end(),
227
                         d_input_funcs.begin(),
228
                         d_input_funcs.end());
229
945
      if (cr != conj[i])
230
      {
231
420
        Trace("si-prt-debug") << "...rewritten to " << cr << std::endl;
232
      }
233
1890
      std::map<Node, bool> visited;
234
      // functions to arguments
235
1890
      std::vector<Node> args;
236
1890
      Subs sb;
237
945
      bool singleInvocation = true;
238
945
      bool ngroundSingleInvocation = false;
239
945
      if (processConjunct(cr, visited, args, sb))
240
      {
241
1991
        for (size_t j = 0, vsize = sb.d_vars.size(); j < vsize; j++)
242
        {
243
2232
          Node s = sb.d_subs[j];
244
1116
          si_terms.push_back(s);
245
2232
          Node op = s.hasOperator() ? s.getOperator() : s;
246
1116
          Assert(d_func_fo_var.find(op) != d_func_fo_var.end());
247
1116
          si_subs.push_back(d_func_fo_var[op]);
248
        }
249
1750
        std::map<Node, Node> subs_map;
250
1750
        std::map<Node, Node> subs_map_rev;
251
        // normalize the invocations
252
875
        if (!sb.empty())
253
        {
254
847
          cr = sb.apply(cr);
255
        }
256
1750
        std::vector<Node> children;
257
875
        children.push_back(cr);
258
875
        sb.clear();
259
1750
        Trace("si-prt") << "...single invocation, with arguments: "
260
875
                        << std::endl;
261
2748
        for (unsigned j = 0; j < args.size(); j++)
262
        {
263
1873
          Trace("si-prt") << args[j] << " ";
264
1873
          if (args[j].getKind() == BOUND_VARIABLE && !sb.contains(args[j]))
265
          {
266
774
            sb.add(args[j], d_si_vars[j]);
267
          }
268
          else
269
          {
270
1099
            children.push_back(d_si_vars[j].eqNode(args[j]).negate());
271
          }
272
        }
273
875
        Trace("si-prt") << std::endl;
274
875
        cr = nm->mkOr(children);
275
875
        cr = sb.apply(cr);
276
1750
        Trace("si-prt-debug") << "...normalized invocations to " << cr
277
875
                              << std::endl;
278
        // now must check if it has other bound variables
279
1750
        std::unordered_set<Node> fvs;
280
875
        expr::getFreeVariables(cr, fvs);
281
        // bound variables must be contained in the single invocation variables
282
3896
        for (const Node& bv : fvs)
283
        {
284
9063
          if (std::find(d_si_vars.begin(), d_si_vars.end(), bv)
285
9063
              == d_si_vars.end())
286
          {
287
            // getFreeVariables also collects functions in the rare case that
288
            // we are synthesizing a function with 0 arguments, take this into
289
            // account here.
290
3444
            if (std::find(d_input_funcs.begin(), d_input_funcs.end(), bv)
291
3444
                == d_input_funcs.end())
292
            {
293
64
              Trace("si-prt")
294
32
                  << "...not ground single invocation." << std::endl;
295
32
              ngroundSingleInvocation = true;
296
32
              singleInvocation = false;
297
            }
298
          }
299
        }
300
875
        if (singleInvocation)
301
        {
302
859
          Trace("si-prt") << "...ground single invocation" << std::endl;
303
        }
304
      }
305
      else
306
      {
307
70
        Trace("si-prt") << "...not single invocation." << std::endl;
308
70
        singleInvocation = false;
309
        // rename bound variables with maximal overlap with si_vars
310
140
        std::unordered_set<Node> fvs;
311
70
        expr::getFreeVariables(cr, fvs);
312
140
        std::vector<Node> termsNs;
313
140
        std::vector<Node> subsNs;
314
491
        for (const Node& v : fvs)
315
        {
316
842
          TypeNode tn = v.getType();
317
842
          Trace("si-prt-debug")
318
421
              << "Fit bound var: " << v << " with si." << std::endl;
319
3101
          for (unsigned k = 0; k < d_si_vars.size(); k++)
320
          {
321
2920
            if (tn == d_arg_types[k])
322
            {
323
5622
              if (std::find(subsNs.begin(), subsNs.end(), d_si_vars[k])
324
5622
                  == subsNs.end())
325
              {
326
240
                termsNs.push_back(v);
327
240
                subsNs.push_back(d_si_vars[k]);
328
480
                Trace("si-prt-debug") << "  ...use " << d_si_vars[k]
329
240
                                      << std::endl;
330
240
                break;
331
              }
332
            }
333
          }
334
        }
335
70
        Assert(termsNs.size() == subsNs.size());
336
70
        cr = cr.substitute(
337
            termsNs.begin(), termsNs.end(), subsNs.begin(), subsNs.end());
338
      }
339
945
      cr = Rewriter::rewrite(cr);
340
1890
      Trace("si-prt") << ".....got si=" << singleInvocation
341
945
                      << ", result : " << cr << std::endl;
342
945
      d_conjuncts[2].push_back(cr);
343
1890
      std::unordered_set<Node> fvs;
344
945
      expr::getFreeVariables(cr, fvs);
345
945
      d_all_vars.insert(fvs.begin(), fvs.end());
346
945
      if (singleInvocation)
347
      {
348
        // replace with single invocation formulation
349
859
        Assert(si_terms.size() == si_subs.size());
350
859
        cr = cr.substitute(
351
            si_terms.begin(), si_terms.end(), si_subs.begin(), si_subs.end());
352
859
        cr = Rewriter::rewrite(cr);
353
859
        Trace("si-prt") << ".....si version=" << cr << std::endl;
354
859
        d_conjuncts[0].push_back(cr);
355
      }
356
      else
357
      {
358
86
        d_conjuncts[1].push_back(cr);
359
86
        if (ngroundSingleInvocation)
360
        {
361
16
          d_conjuncts[3].push_back(cr);
362
        }
363
      }
364
    }
365
  }
366
  else
367
  {
368
2
    Trace("si-prt") << "...failed." << std::endl;
369
2
    return false;
370
  }
371
314
  return true;
372
}
373
374
1159
bool SingleInvocationPartition::collectConjuncts(Node n,
375
                                                 bool pol,
376
                                                 std::vector<Node>& conj)
377
{
378
1159
  if ((!pol && n.getKind() == OR) || (pol && n.getKind() == AND))
379
  {
380
921
    for (unsigned i = 0; i < n.getNumChildren(); i++)
381
    {
382
777
      if (!collectConjuncts(n[i], pol, conj))
383
      {
384
2
        return false;
385
      }
386
    }
387
  }
388
1013
  else if (n.getKind() == NOT)
389
  {
390
66
    return collectConjuncts(n[0], !pol, conj);
391
  }
392
947
  else if (n.getKind() == FORALL)
393
  {
394
2
    return false;
395
  }
396
  else
397
  {
398
945
    if (!pol)
399
    {
400
65
      n = TermUtil::simpleNegate(n);
401
    }
402
945
    Trace("si-prt") << "Conjunct : " << n << std::endl;
403
945
    conj.push_back(n);
404
  }
405
1089
  return true;
406
}
407
408
18882
bool SingleInvocationPartition::processConjunct(Node n,
409
                                                std::map<Node, bool>& visited,
410
                                                std::vector<Node>& args,
411
                                                Subs& sb)
412
{
413
18882
  std::map<Node, bool>::iterator it = visited.find(n);
414
18882
  if (it != visited.end())
415
  {
416
7688
    return true;
417
  }
418
  else
419
  {
420
11194
    bool ret = true;
421
29131
    for (unsigned i = 0; i < n.getNumChildren(); i++)
422
    {
423
17937
      if (!processConjunct(n[i], visited, args, sb))
424
      {
425
160
        ret = false;
426
      }
427
    }
428
11194
    if (ret)
429
    {
430
22088
      Node f;
431
11044
      bool success = false;
432
11044
      if (d_has_input_funcs)
433
      {
434
11044
        f = n.hasOperator() ? n.getOperator() : n;
435
33132
        if (std::find(d_input_funcs.begin(), d_input_funcs.end(), f)
436
33132
            != d_input_funcs.end())
437
        {
438
          // If n is an application of a function-to-synthesize f, or is
439
          // itself a function-to-synthesize, then n must be fully applied.
440
          // This catches cases where n is a function-to-synthesize that occurs
441
          // in a higher-order context.
442
          // If the type of n is functional, then it is not fully applied.
443
1265
          if (n.getType().isFunction())
444
          {
445
2
            ret = false;
446
2
            success = false;
447
          }
448
          else
449
          {
450
1263
            success = true;
451
          }
452
        }
453
      }
454
      else
455
      {
456
        if (n.getKind() == kind::APPLY_UF)
457
        {
458
          f = n.getOperator();
459
          success = true;
460
        }
461
      }
462
11044
      if (success)
463
      {
464
1263
        Trace("si-prt-debug") << "Process " << n << std::endl;
465
1263
        if (!sb.contains(n))
466
        {
467
          // check if it matches the type requirement
468
1263
          if (isAntiSkolemizableType(f))
469
          {
470
1255
            if (args.empty())
471
            {
472
              // record arguments
473
3054
              for (unsigned i = 0; i < n.getNumChildren(); i++)
474
              {
475
2119
                args.push_back(n[i]);
476
              }
477
            }
478
            else
479
            {
480
              // arguments must be the same as those already recorded
481
1051
              for (unsigned i = 0; i < n.getNumChildren(); i++)
482
              {
483
801
                if (args[i] != n[i])
484
                {
485
140
                  Trace("si-prt-debug") << "...bad invocation : " << n
486
70
                                        << " at arg " << i << "." << std::endl;
487
70
                  ret = false;
488
70
                  break;
489
                }
490
              }
491
            }
492
1255
            if (ret)
493
            {
494
1185
              sb.add(n, d_func_inv[f]);
495
            }
496
          }
497
          else
498
          {
499
16
            Trace("si-prt-debug") << "... " << f << " is a bad operator."
500
8
                                  << std::endl;
501
8
            ret = false;
502
          }
503
        }
504
      }
505
    }
506
11194
    visited[n] = ret;
507
11194
    return ret;
508
  }
509
}
510
511
1263
bool SingleInvocationPartition::isAntiSkolemizableType(Node f)
512
{
513
1263
  std::map<Node, bool>::iterator it = d_funcs.find(f);
514
1263
  if (it != d_funcs.end())
515
  {
516
715
    return it->second;
517
  }
518
  else
519
  {
520
1096
    TypeNode tn = f.getType();
521
548
    bool ret = false;
522
1583
    if (((tn.isFunction() && tn.getNumChildren() == d_arg_types.size() + 1)
523
609
         || (d_arg_types.empty() && tn.getNumChildren() == 0)))
524
    {
525
540
      ret = true;
526
1080
      std::vector<Node> children;
527
540
      children.push_back(f);
528
      // TODO: permutations of arguments
529
1913
      for (unsigned i = 0; i < d_arg_types.size(); i++)
530
      {
531
1373
        children.push_back(d_si_vars[i]);
532
1373
        if (tn[i] != d_arg_types[i])
533
        {
534
          ret = false;
535
          break;
536
        }
537
      }
538
540
      if (ret)
539
      {
540
1080
        Node t;
541
540
        if (children.size() > 1)
542
        {
543
487
          t = NodeManager::currentNM()->mkNode(kind::APPLY_UF, children);
544
        }
545
        else
546
        {
547
53
          t = children[0];
548
        }
549
540
        d_func_inv[f] = t;
550
1080
        std::stringstream ss;
551
540
        ss << "F_" << f;
552
1080
        TypeNode rt;
553
540
        if (d_arg_types.empty())
554
        {
555
53
          rt = tn;
556
        }
557
        else
558
        {
559
487
          rt = tn.getRangeType();
560
        }
561
1080
        Node v = NodeManager::currentNM()->mkBoundVar(ss.str(), rt);
562
540
        d_func_fo_var[f] = v;
563
540
        d_fo_var_to_func[v] = f;
564
540
        d_func_vars.push_back(v);
565
540
        d_all_funcs.push_back(f);
566
      }
567
    }
568
548
    d_funcs[f] = ret;
569
548
    return ret;
570
  }
571
}
572
573
95
Node SingleInvocationPartition::getConjunct(int index)
574
{
575
95
  return d_conjuncts[index].empty() ? NodeManager::currentNM()->mkConst(true)
576
95
                                    : (d_conjuncts[index].size() == 1
577
62
                                           ? d_conjuncts[index][0]
578
                                           : NodeManager::currentNM()->mkNode(
579
252
                                                 AND, d_conjuncts[index]));
580
}
581
582
314
void SingleInvocationPartition::debugPrint(const char* c)
583
{
584
314
  Trace(c) << "Single invocation variables : ";
585
1031
  for (unsigned i = 0; i < d_si_vars.size(); i++)
586
  {
587
717
    Trace(c) << d_si_vars[i] << " ";
588
  }
589
314
  Trace(c) << std::endl;
590
314
  Trace(c) << "Functions : " << std::endl;
591
862
  for (std::map<Node, bool>::iterator it = d_funcs.begin(); it != d_funcs.end();
592
       ++it)
593
  {
594
548
    Trace(c) << "  " << it->first << " : ";
595
548
    if (it->second)
596
    {
597
1080
      Trace(c) << d_func_inv[it->first] << " " << d_func_fo_var[it->first]
598
540
               << std::endl;
599
    }
600
    else
601
    {
602
8
      Trace(c) << "not incorporated." << std::endl;
603
    }
604
  }
605
1570
  for (unsigned i = 0; i < 4; i++)
606
  {
607
2198
    Trace(c) << (i == 0 ? "Single invocation"
608
1570
                        : (i == 1 ? "Non-single invocation"
609
628
                                  : (i == 2 ? "All"
610
                                            : "Non-ground single invocation")));
611
1256
    Trace(c) << " conjuncts: " << std::endl;
612
3162
    for (unsigned j = 0; j < d_conjuncts[i].size(); j++)
613
    {
614
1906
      Trace(c) << "  " << (j + 1) << " : " << d_conjuncts[i][j] << std::endl;
615
    }
616
  }
617
314
  Trace(c) << std::endl;
618
314
}
619
620
}  // namespace quantifiers
621
}  // namespace theory
622
22746
}  // namespace cvc5