GCC Code Coverage Report
Directory: . Exec Total Coverage
File: src/theory/quantifiers/single_inv_partition.cpp Lines: 270 304 88.8 %
Date: 2021-09-07 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
65
Node SingleInvocationPartition::getFirstOrderVariableForFunction(Node f) const
50
{
51
65
  std::map<Node, Node>::const_iterator it = d_func_fo_var.find(f);
52
65
  if (it != d_func_fo_var.end())
53
  {
54
65
    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
65
Node SingleInvocationPartition::getFunctionInvocationFor(Node f) const
70
{
71
65
  std::map<Node, Node>::const_iterator it = d_func_inv.find(f);
72
65
  if (it != d_func_inv.end())
73
  {
74
65
    return it->second;
75
  }
76
  return Node::null();
77
}
78
79
94
void SingleInvocationPartition::getFunctionVariables(
80
    std::vector<Node>& fvars) const
81
{
82
94
  fvars.insert(fvars.end(), d_func_vars.begin(), d_func_vars.end());
83
94
}
84
85
314
void SingleInvocationPartition::getFunctions(std::vector<Node>& fs) const
86
{
87
314
  fs.insert(fs.end(), d_all_funcs.begin(), d_all_funcs.end());
88
314
}
89
90
97
void SingleInvocationPartition::getSingleInvocationVariables(
91
    std::vector<Node>& sivars) const
92
{
93
97
  sivars.insert(sivars.end(), d_si_vars.begin(), d_si_vars.end());
94
97
}
95
96
3
void SingleInvocationPartition::getAllVariables(std::vector<Node>& vars) const
97
{
98
3
  vars.insert(vars.end(), d_all_vars.begin(), d_all_vars.end());
99
3
}
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
337
bool SingleInvocationPartition::init(std::vector<Node>& funcs, Node n)
135
{
136
674
  Trace("si-prt") << "Initialize with " << funcs.size() << " input functions ("
137
337
                  << funcs << ")..." << std::endl;
138
674
  std::vector<TypeNode> typs;
139
337
  if (!funcs.empty())
140
  {
141
658
    TypeNode tn0 = funcs[0].getType();
142
337
    if (tn0.isFunction())
143
    {
144
998
      for (unsigned i = 0, nargs = tn0.getNumChildren() - 1; i < nargs; i++)
145
      {
146
724
        typs.push_back(tn0[i]);
147
      }
148
    }
149
610
    for (unsigned i = 1, size = funcs.size(); i < size; i++)
150
    {
151
562
      TypeNode tni = funcs[i].getType();
152
289
      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
273
      else if (tni.isFunction())
159
      {
160
793
        for (unsigned j = 0, nargs = tni.getNumChildren() - 1; j < nargs; j++)
161
        {
162
588
          if (tni[j] != typs[j])
163
          {
164
            Trace("si-prt") << "...argument type mismatch" << std::endl;
165
            return false;
166
          }
167
        }
168
      }
169
    }
170
  }
171
321
  Trace("si-prt") << "#types = " << typs.size() << std::endl;
172
321
  return init(funcs, typs, n, true);
173
}
174
175
321
bool SingleInvocationPartition::init(std::vector<Node>& funcs,
176
                                     std::vector<TypeNode>& typs,
177
                                     Node n,
178
                                     bool has_funcs)
179
{
180
321
  Assert(d_arg_types.empty());
181
321
  Assert(d_input_funcs.empty());
182
321
  Assert(d_si_vars.empty());
183
321
  NodeManager* nm = NodeManager::currentNM();
184
321
  SkolemManager* sm = nm->getSkolemManager();
185
321
  d_has_input_funcs = has_funcs;
186
321
  d_arg_types.insert(d_arg_types.end(), typs.begin(), typs.end());
187
321
  d_input_funcs.insert(d_input_funcs.end(), funcs.begin(), funcs.end());
188
321
  Trace("si-prt") << "Initialize..." << std::endl;
189
1038
  for (unsigned j = 0; j < d_arg_types.size(); j++)
190
  {
191
1434
    std::stringstream ss;
192
717
    ss << "s_" << j;
193
1434
    Node si_v = nm->mkBoundVar(ss.str(), d_arg_types[j]);
194
717
    d_si_vars.push_back(si_v);
195
  }
196
321
  Assert(d_si_vars.size() == d_arg_types.size());
197
910
  for (const Node& inf : d_input_funcs)
198
  {
199
1178
    Node sk = sm->mkDummySkolem("_sik", inf.getType());
200
589
    d_input_func_sks.push_back(sk);
201
  }
202
321
  Trace("si-prt") << "SingleInvocationPartition::process " << n << std::endl;
203
321
  Trace("si-prt") << "Get conjuncts..." << std::endl;
204
642
  std::vector<Node> conj;
205
321
  if (collectConjuncts(n, true, conj))
206
  {
207
318
    Trace("si-prt") << "...success." << std::endl;
208
1262
    for (unsigned i = 0; i < conj.size(); i++)
209
    {
210
1888
      std::vector<Node> si_terms;
211
1888
      std::vector<Node> si_subs;
212
944
      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
944
      Node cr = conj[i].substitute(d_input_funcs.begin(),
221
                                   d_input_funcs.end(),
222
                                   d_input_func_sks.begin(),
223
1888
                                   d_input_func_sks.end());
224
944
      cr = TermUtil::getQuantSimplify(cr);
225
944
      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
944
      if (cr != conj[i])
230
      {
231
425
        Trace("si-prt-debug") << "...rewritten to " << cr << std::endl;
232
      }
233
1888
      std::map<Node, bool> visited;
234
      // functions to arguments
235
1888
      std::vector<Node> args;
236
1888
      Subs sb;
237
944
      bool singleInvocation = true;
238
944
      bool ngroundSingleInvocation = false;
239
944
      if (processConjunct(cr, visited, args, sb))
240
      {
241
1948
        for (size_t j = 0, vsize = sb.d_vars.size(); j < vsize; j++)
242
        {
243
2152
          Node s = sb.d_subs[j];
244
1076
          si_terms.push_back(s);
245
2152
          Node op = s.hasOperator() ? s.getOperator() : s;
246
1076
          Assert(d_func_fo_var.find(op) != d_func_fo_var.end());
247
1076
          si_subs.push_back(d_func_fo_var[op]);
248
        }
249
1744
        std::map<Node, Node> subs_map;
250
1744
        std::map<Node, Node> subs_map_rev;
251
        // normalize the invocations
252
872
        if (!sb.empty())
253
        {
254
840
          cr = sb.apply(cr);
255
        }
256
1744
        std::vector<Node> children;
257
872
        children.push_back(cr);
258
872
        sb.clear();
259
1744
        Trace("si-prt") << "...single invocation, with arguments: "
260
872
                        << std::endl;
261
2739
        for (unsigned j = 0; j < args.size(); j++)
262
        {
263
1867
          Trace("si-prt") << args[j] << " ";
264
1867
          if (args[j].getKind() == BOUND_VARIABLE && !sb.contains(args[j]))
265
          {
266
786
            sb.add(args[j], d_si_vars[j]);
267
          }
268
          else
269
          {
270
1081
            children.push_back(d_si_vars[j].eqNode(args[j]).negate());
271
          }
272
        }
273
872
        Trace("si-prt") << std::endl;
274
872
        cr = nm->mkOr(children);
275
872
        cr = sb.apply(cr);
276
1744
        Trace("si-prt-debug") << "...normalized invocations to " << cr
277
872
                              << std::endl;
278
        // now must check if it has other bound variables
279
1744
        std::unordered_set<Node> fvs;
280
872
        expr::getFreeVariables(cr, fvs);
281
        // bound variables must be contained in the single invocation variables
282
3854
        for (const Node& bv : fvs)
283
        {
284
8946
          if (std::find(d_si_vars.begin(), d_si_vars.end(), bv)
285
8946
              == 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
3345
            if (std::find(d_input_funcs.begin(), d_input_funcs.end(), bv)
291
3345
                == d_input_funcs.end())
292
            {
293
78
              Trace("si-prt")
294
39
                  << "...not ground single invocation." << std::endl;
295
39
              ngroundSingleInvocation = true;
296
39
              singleInvocation = false;
297
            }
298
          }
299
        }
300
872
        if (singleInvocation)
301
        {
302
849
          Trace("si-prt") << "...ground single invocation" << std::endl;
303
        }
304
      }
305
      else
306
      {
307
72
        Trace("si-prt") << "...not single invocation." << std::endl;
308
72
        singleInvocation = false;
309
        // rename bound variables with maximal overlap with si_vars
310
144
        std::unordered_set<Node> fvs;
311
72
        expr::getFreeVariables(cr, fvs);
312
144
        std::vector<Node> termsNs;
313
144
        std::vector<Node> subsNs;
314
505
        for (const Node& v : fvs)
315
        {
316
866
          TypeNode tn = v.getType();
317
866
          Trace("si-prt-debug")
318
433
              << "Fit bound var: " << v << " with si." << std::endl;
319
3131
          for (unsigned k = 0; k < d_si_vars.size(); k++)
320
          {
321
2944
            if (tn == d_arg_types[k])
322
            {
323
5664
              if (std::find(subsNs.begin(), subsNs.end(), d_si_vars[k])
324
5664
                  == subsNs.end())
325
              {
326
246
                termsNs.push_back(v);
327
246
                subsNs.push_back(d_si_vars[k]);
328
492
                Trace("si-prt-debug") << "  ...use " << d_si_vars[k]
329
246
                                      << std::endl;
330
246
                break;
331
              }
332
            }
333
          }
334
        }
335
72
        Assert(termsNs.size() == subsNs.size());
336
72
        cr = cr.substitute(
337
            termsNs.begin(), termsNs.end(), subsNs.begin(), subsNs.end());
338
      }
339
944
      cr = Rewriter::rewrite(cr);
340
1888
      Trace("si-prt") << ".....got si=" << singleInvocation
341
944
                      << ", result : " << cr << std::endl;
342
944
      d_conjuncts[2].push_back(cr);
343
1888
      std::unordered_set<Node> fvs;
344
944
      expr::getFreeVariables(cr, fvs);
345
944
      d_all_vars.insert(fvs.begin(), fvs.end());
346
944
      if (singleInvocation)
347
      {
348
        // replace with single invocation formulation
349
849
        Assert(si_terms.size() == si_subs.size());
350
849
        cr = cr.substitute(
351
            si_terms.begin(), si_terms.end(), si_subs.begin(), si_subs.end());
352
849
        cr = Rewriter::rewrite(cr);
353
849
        Trace("si-prt") << ".....si version=" << cr << std::endl;
354
849
        d_conjuncts[0].push_back(cr);
355
      }
356
      else
357
      {
358
95
        d_conjuncts[1].push_back(cr);
359
95
        if (ngroundSingleInvocation)
360
        {
361
23
          d_conjuncts[3].push_back(cr);
362
        }
363
      }
364
    }
365
  }
366
  else
367
  {
368
3
    Trace("si-prt") << "...failed." << std::endl;
369
3
    return false;
370
  }
371
318
  return true;
372
}
373
374
1166
bool SingleInvocationPartition::collectConjuncts(Node n,
375
                                                 bool pol,
376
                                                 std::vector<Node>& conj)
377
{
378
1166
  if ((!pol && n.getKind() == OR) || (pol && n.getKind() == AND))
379
  {
380
925
    for (unsigned i = 0; i < n.getNumChildren(); i++)
381
    {
382
777
      if (!collectConjuncts(n[i], pol, conj))
383
      {
384
3
        return false;
385
      }
386
    }
387
  }
388
1015
  else if (n.getKind() == NOT)
389
  {
390
68
    return collectConjuncts(n[0], !pol, conj);
391
  }
392
947
  else if (n.getKind() == FORALL)
393
  {
394
3
    return false;
395
  }
396
  else
397
  {
398
944
    if (!pol)
399
    {
400
67
      n = TermUtil::simpleNegate(n);
401
    }
402
944
    Trace("si-prt") << "Conjunct : " << n << std::endl;
403
944
    conj.push_back(n);
404
  }
405
1092
  return true;
406
}
407
408
18603
bool SingleInvocationPartition::processConjunct(Node n,
409
                                                std::map<Node, bool>& visited,
410
                                                std::vector<Node>& args,
411
                                                Subs& sb)
412
{
413
18603
  std::map<Node, bool>::iterator it = visited.find(n);
414
18603
  if (it != visited.end())
415
  {
416
7458
    return true;
417
  }
418
  else
419
  {
420
11145
    bool ret = true;
421
28804
    for (unsigned i = 0; i < n.getNumChildren(); i++)
422
    {
423
17659
      if (!processConjunct(n[i], visited, args, sb))
424
      {
425
172
        ret = false;
426
      }
427
    }
428
11145
    if (ret)
429
    {
430
21966
      Node f;
431
10983
      bool success = false;
432
10983
      if (d_has_input_funcs)
433
      {
434
10983
        f = n.hasOperator() ? n.getOperator() : n;
435
32949
        if (std::find(d_input_funcs.begin(), d_input_funcs.end(), f)
436
32949
            != 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
1229
          if (n.getType().isFunction())
444
          {
445
2
            ret = false;
446
2
            success = false;
447
          }
448
          else
449
          {
450
1227
            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
10983
      if (success)
463
      {
464
1227
        Trace("si-prt-debug") << "Process " << n << std::endl;
465
1227
        if (!sb.contains(n))
466
        {
467
          // check if it matches the type requirement
468
1227
          if (isAntiSkolemizableType(f))
469
          {
470
1219
            if (args.empty())
471
            {
472
              // record arguments
473
3057
              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
891
              for (unsigned i = 0; i < n.getNumChildren(); i++)
482
              {
483
682
                if (args[i] != n[i])
484
                {
485
144
                  Trace("si-prt-debug") << "...bad invocation : " << n
486
72
                                        << " at arg " << i << "." << std::endl;
487
72
                  ret = false;
488
72
                  break;
489
                }
490
              }
491
            }
492
1219
            if (ret)
493
            {
494
1147
              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
11145
    visited[n] = ret;
507
11145
    return ret;
508
  }
509
}
510
511
1227
bool SingleInvocationPartition::isAntiSkolemizableType(Node f)
512
{
513
1227
  std::map<Node, bool>::iterator it = d_funcs.find(f);
514
1227
  if (it != d_funcs.end())
515
  {
516
712
    return it->second;
517
  }
518
  else
519
  {
520
1030
    TypeNode tn = f.getType();
521
515
    bool ret = false;
522
1476
    if (((tn.isFunction() && tn.getNumChildren() == d_arg_types.size() + 1)
523
584
         || (d_arg_types.empty() && tn.getNumChildren() == 0)))
524
    {
525
507
      ret = true;
526
1014
      std::vector<Node> children;
527
507
      children.push_back(f);
528
      // TODO: permutations of arguments
529
1757
      for (unsigned i = 0; i < d_arg_types.size(); i++)
530
      {
531
1250
        children.push_back(d_si_vars[i]);
532
1250
        if (tn[i] != d_arg_types[i])
533
        {
534
          ret = false;
535
          break;
536
        }
537
      }
538
507
      if (ret)
539
      {
540
1014
        Node t;
541
507
        if (children.size() > 1)
542
        {
543
446
          t = NodeManager::currentNM()->mkNode(kind::APPLY_UF, children);
544
        }
545
        else
546
        {
547
61
          t = children[0];
548
        }
549
507
        d_func_inv[f] = t;
550
1014
        std::stringstream ss;
551
507
        ss << "F_" << f;
552
1014
        TypeNode rt;
553
507
        if (d_arg_types.empty())
554
        {
555
61
          rt = tn;
556
        }
557
        else
558
        {
559
446
          rt = tn.getRangeType();
560
        }
561
1014
        Node v = NodeManager::currentNM()->mkBoundVar(ss.str(), rt);
562
507
        d_func_fo_var[f] = v;
563
507
        d_fo_var_to_func[v] = f;
564
507
        d_func_vars.push_back(v);
565
507
        d_all_funcs.push_back(f);
566
      }
567
    }
568
515
    d_funcs[f] = ret;
569
515
    return ret;
570
  }
571
}
572
573
97
Node SingleInvocationPartition::getConjunct(int index)
574
{
575
97
  return d_conjuncts[index].empty() ? NodeManager::currentNM()->mkConst(true)
576
97
                                    : (d_conjuncts[index].size() == 1
577
62
                                           ? d_conjuncts[index][0]
578
                                           : NodeManager::currentNM()->mkNode(
579
256
                                                 AND, d_conjuncts[index]));
580
}
581
582
318
void SingleInvocationPartition::debugPrint(const char* c)
583
{
584
318
  Trace(c) << "Single invocation variables : ";
585
1031
  for (unsigned i = 0; i < d_si_vars.size(); i++)
586
  {
587
713
    Trace(c) << d_si_vars[i] << " ";
588
  }
589
318
  Trace(c) << std::endl;
590
318
  Trace(c) << "Functions : " << std::endl;
591
833
  for (std::map<Node, bool>::iterator it = d_funcs.begin(); it != d_funcs.end();
592
       ++it)
593
  {
594
515
    Trace(c) << "  " << it->first << " : ";
595
515
    if (it->second)
596
    {
597
1014
      Trace(c) << d_func_inv[it->first] << " " << d_func_fo_var[it->first]
598
507
               << std::endl;
599
    }
600
    else
601
    {
602
8
      Trace(c) << "not incorporated." << std::endl;
603
    }
604
  }
605
1590
  for (unsigned i = 0; i < 4; i++)
606
  {
607
2226
    Trace(c) << (i == 0 ? "Single invocation"
608
1590
                        : (i == 1 ? "Non-single invocation"
609
636
                                  : (i == 2 ? "All"
610
                                            : "Non-ground single invocation")));
611
1272
    Trace(c) << " conjuncts: " << std::endl;
612
3183
    for (unsigned j = 0; j < d_conjuncts[i].size(); j++)
613
    {
614
1911
      Trace(c) << "  " << (j + 1) << " : " << d_conjuncts[i][j] << std::endl;
615
    }
616
  }
617
318
  Trace(c) << std::endl;
618
318
}
619
620
}  // namespace quantifiers
621
}  // namespace theory
622
29502
}  // namespace cvc5