GCC Code Coverage Report
Directory: . Exec Total Coverage
File: src/theory/quantifiers/single_inv_partition.cpp Lines: 274 308 89.0 %
Date: 2021-08-17 Branches: 497 1032 48.2 %

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
96
void SingleInvocationPartition::getFunctionVariables(
80
    std::vector<Node>& fvars) const
81
{
82
96
  fvars.insert(fvars.end(), d_func_vars.begin(), d_func_vars.end());
83
96
}
84
85
315
void SingleInvocationPartition::getFunctions(std::vector<Node>& fs) const
86
{
87
315
  fs.insert(fs.end(), d_all_funcs.begin(), d_all_funcs.end());
88
315
}
89
90
99
void SingleInvocationPartition::getSingleInvocationVariables(
91
    std::vector<Node>& sivars) const
92
{
93
99
  sivars.insert(sivars.end(), d_si_vars.begin(), d_si_vars.end());
94
99
}
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
338
bool SingleInvocationPartition::init(std::vector<Node>& funcs, Node n)
135
{
136
676
  Trace("si-prt") << "Initialize with " << funcs.size() << " input functions ("
137
338
                  << funcs << ")..." << std::endl;
138
676
  std::vector<TypeNode> typs;
139
338
  if (!funcs.empty())
140
  {
141
660
    TypeNode tn0 = funcs[0].getType();
142
338
    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
612
    for (unsigned i = 1, size = funcs.size(); i < size; i++)
150
    {
151
564
      TypeNode tni = funcs[i].getType();
152
290
      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
274
      else if (tni.isFunction())
159
      {
160
795
        for (unsigned j = 0, nargs = tni.getNumChildren() - 1; j < nargs; j++)
161
        {
162
589
          if (tni[j] != typs[j])
163
          {
164
            Trace("si-prt") << "...argument type mismatch" << std::endl;
165
            return false;
166
          }
167
        }
168
      }
169
    }
170
  }
171
322
  Trace("si-prt") << "#types = " << typs.size() << std::endl;
172
322
  return init(funcs, typs, n, true);
173
}
174
175
322
bool SingleInvocationPartition::init(std::vector<Node>& funcs,
176
                                     std::vector<TypeNode>& typs,
177
                                     Node n,
178
                                     bool has_funcs)
179
{
180
322
  Assert(d_arg_types.empty());
181
322
  Assert(d_input_funcs.empty());
182
322
  Assert(d_si_vars.empty());
183
322
  NodeManager* nm = NodeManager::currentNM();
184
322
  SkolemManager* sm = nm->getSkolemManager();
185
322
  d_has_input_funcs = has_funcs;
186
322
  d_arg_types.insert(d_arg_types.end(), typs.begin(), typs.end());
187
322
  d_input_funcs.insert(d_input_funcs.end(), funcs.begin(), funcs.end());
188
322
  Trace("si-prt") << "Initialize..." << std::endl;
189
1039
  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
322
  Assert(d_si_vars.size() == d_arg_types.size());
197
913
  for (const Node& inf : d_input_funcs)
198
  {
199
1182
    Node sk = sm->mkDummySkolem("_sik", inf.getType());
200
591
    d_input_func_sks.push_back(sk);
201
  }
202
322
  Trace("si-prt") << "SingleInvocationPartition::process " << n << std::endl;
203
322
  Trace("si-prt") << "Get conjuncts..." << std::endl;
204
644
  std::vector<Node> conj;
205
322
  if (collectConjuncts(n, true, conj))
206
  {
207
319
    Trace("si-prt") << "...success." << std::endl;
208
1273
    for (unsigned i = 0; i < conj.size(); i++)
209
    {
210
1908
      std::vector<Node> si_terms;
211
1908
      std::vector<Node> si_subs;
212
954
      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
954
      Node cr = conj[i].substitute(d_input_funcs.begin(),
221
                                   d_input_funcs.end(),
222
                                   d_input_func_sks.begin(),
223
1908
                                   d_input_func_sks.end());
224
954
      cr = TermUtil::getQuantSimplify(cr);
225
954
      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
954
      if (cr != conj[i])
230
      {
231
430
        Trace("si-prt-debug") << "...rewritten to " << cr << std::endl;
232
      }
233
1908
      std::map<Node, bool> visited;
234
      // functions to arguments
235
1908
      std::vector<Node> args;
236
1908
      std::vector<Node> terms;
237
1908
      std::vector<Node> subs;
238
954
      bool singleInvocation = true;
239
954
      bool ngroundSingleInvocation = false;
240
954
      if (processConjunct(cr, visited, args, terms, subs))
241
      {
242
1966
        for (unsigned j = 0; j < terms.size(); j++)
243
        {
244
1083
          si_terms.push_back(subs[j]);
245
2166
          Node op = subs[j].hasOperator() ? subs[j].getOperator() : subs[j];
246
1083
          Assert(d_func_fo_var.find(op) != d_func_fo_var.end());
247
1083
          si_subs.push_back(d_func_fo_var[op]);
248
        }
249
1766
        std::map<Node, Node> subs_map;
250
1766
        std::map<Node, Node> subs_map_rev;
251
        // normalize the invocations
252
883
        if (!terms.empty())
253
        {
254
851
          Assert(terms.size() == subs.size());
255
851
          cr = cr.substitute(
256
              terms.begin(), terms.end(), subs.begin(), subs.end());
257
        }
258
1766
        std::vector<Node> children;
259
883
        children.push_back(cr);
260
883
        terms.clear();
261
883
        subs.clear();
262
1766
        Trace("si-prt") << "...single invocation, with arguments: "
263
883
                        << std::endl;
264
2759
        for (unsigned j = 0; j < args.size(); j++)
265
        {
266
1876
          Trace("si-prt") << args[j] << " ";
267
3752
          if (args[j].getKind() == BOUND_VARIABLE
268
1876
              && std::find(terms.begin(), terms.end(), args[j]) == terms.end())
269
          {
270
793
            terms.push_back(args[j]);
271
793
            subs.push_back(d_si_vars[j]);
272
          }
273
          else
274
          {
275
1083
            children.push_back(d_si_vars[j].eqNode(args[j]).negate());
276
          }
277
        }
278
883
        Trace("si-prt") << std::endl;
279
1766
        cr = children.size() == 1
280
1766
                 ? children[0]
281
                 : NodeManager::currentNM()->mkNode(OR, children);
282
883
        Assert(terms.size() == subs.size());
283
883
        cr =
284
1766
            cr.substitute(terms.begin(), terms.end(), subs.begin(), subs.end());
285
1766
        Trace("si-prt-debug") << "...normalized invocations to " << cr
286
883
                              << std::endl;
287
        // now must check if it has other bound variables
288
1766
        std::unordered_set<Node> fvs;
289
883
        expr::getFreeVariables(cr, fvs);
290
        // bound variables must be contained in the single invocation variables
291
3881
        for (const Node& bv : fvs)
292
        {
293
8994
          if (std::find(d_si_vars.begin(), d_si_vars.end(), bv)
294
8994
              == d_si_vars.end())
295
          {
296
            // getFreeVariables also collects functions in the rare case that
297
            // we are synthesizing a function with 0 arguments, take this into
298
            // account here.
299
3366
            if (std::find(d_input_funcs.begin(), d_input_funcs.end(), bv)
300
3366
                == d_input_funcs.end())
301
            {
302
78
              Trace("si-prt")
303
39
                  << "...not ground single invocation." << std::endl;
304
39
              ngroundSingleInvocation = true;
305
39
              singleInvocation = false;
306
            }
307
          }
308
        }
309
883
        if (singleInvocation)
310
        {
311
860
          Trace("si-prt") << "...ground single invocation" << std::endl;
312
        }
313
      }
314
      else
315
      {
316
71
        Trace("si-prt") << "...not single invocation." << std::endl;
317
71
        singleInvocation = false;
318
        // rename bound variables with maximal overlap with si_vars
319
142
        std::unordered_set<Node> fvs;
320
71
        expr::getFreeVariables(cr, fvs);
321
142
        std::vector<Node> termsNs;
322
142
        std::vector<Node> subsNs;
323
501
        for (const Node& v : fvs)
324
        {
325
860
          TypeNode tn = v.getType();
326
860
          Trace("si-prt-debug")
327
430
              << "Fit bound var: " << v << " with si." << std::endl;
328
3125
          for (unsigned k = 0; k < d_si_vars.size(); k++)
329
          {
330
2941
            if (tn == d_arg_types[k])
331
            {
332
5664
              if (std::find(subsNs.begin(), subsNs.end(), d_si_vars[k])
333
5664
                  == subsNs.end())
334
              {
335
246
                termsNs.push_back(v);
336
246
                subsNs.push_back(d_si_vars[k]);
337
492
                Trace("si-prt-debug") << "  ...use " << d_si_vars[k]
338
246
                                      << std::endl;
339
246
                break;
340
              }
341
            }
342
          }
343
        }
344
71
        Assert(termsNs.size() == subsNs.size());
345
71
        cr = cr.substitute(
346
            termsNs.begin(), termsNs.end(), subsNs.begin(), subsNs.end());
347
      }
348
954
      cr = Rewriter::rewrite(cr);
349
1908
      Trace("si-prt") << ".....got si=" << singleInvocation
350
954
                      << ", result : " << cr << std::endl;
351
954
      d_conjuncts[2].push_back(cr);
352
1908
      std::unordered_set<Node> fvs;
353
954
      expr::getFreeVariables(cr, fvs);
354
954
      d_all_vars.insert(fvs.begin(), fvs.end());
355
954
      if (singleInvocation)
356
      {
357
        // replace with single invocation formulation
358
860
        Assert(si_terms.size() == si_subs.size());
359
860
        cr = cr.substitute(
360
            si_terms.begin(), si_terms.end(), si_subs.begin(), si_subs.end());
361
860
        cr = Rewriter::rewrite(cr);
362
860
        Trace("si-prt") << ".....si version=" << cr << std::endl;
363
860
        d_conjuncts[0].push_back(cr);
364
      }
365
      else
366
      {
367
94
        d_conjuncts[1].push_back(cr);
368
94
        if (ngroundSingleInvocation)
369
        {
370
23
          d_conjuncts[3].push_back(cr);
371
        }
372
      }
373
    }
374
  }
375
  else
376
  {
377
3
    Trace("si-prt") << "...failed." << std::endl;
378
3
    return false;
379
  }
380
319
  return true;
381
}
382
383
1188
bool SingleInvocationPartition::collectConjuncts(Node n,
384
                                                 bool pol,
385
                                                 std::vector<Node>& conj)
386
{
387
1188
  if ((!pol && n.getKind() == OR) || (pol && n.getKind() == AND))
388
  {
389
942
    for (unsigned i = 0; i < n.getNumChildren(); i++)
390
    {
391
790
      if (!collectConjuncts(n[i], pol, conj))
392
      {
393
3
        return false;
394
      }
395
    }
396
  }
397
1033
  else if (n.getKind() == NOT)
398
  {
399
76
    return collectConjuncts(n[0], !pol, conj);
400
  }
401
957
  else if (n.getKind() == FORALL)
402
  {
403
3
    return false;
404
  }
405
  else
406
  {
407
954
    if (!pol)
408
    {
409
75
      n = TermUtil::simpleNegate(n);
410
    }
411
954
    Trace("si-prt") << "Conjunct : " << n << std::endl;
412
954
    conj.push_back(n);
413
  }
414
1106
  return true;
415
}
416
417
18621
bool SingleInvocationPartition::processConjunct(Node n,
418
                                                std::map<Node, bool>& visited,
419
                                                std::vector<Node>& args,
420
                                                std::vector<Node>& terms,
421
                                                std::vector<Node>& subs)
422
{
423
18621
  std::map<Node, bool>::iterator it = visited.find(n);
424
18621
  if (it != visited.end())
425
  {
426
7440
    return true;
427
  }
428
  else
429
  {
430
11181
    bool ret = true;
431
28848
    for (unsigned i = 0; i < n.getNumChildren(); i++)
432
    {
433
17667
      if (!processConjunct(n[i], visited, args, terms, subs))
434
      {
435
167
        ret = false;
436
      }
437
    }
438
11181
    if (ret)
439
    {
440
22046
      Node f;
441
11023
      bool success = false;
442
11023
      if (d_has_input_funcs)
443
      {
444
11023
        f = n.hasOperator() ? n.getOperator() : n;
445
33069
        if (std::find(d_input_funcs.begin(), d_input_funcs.end(), f)
446
33069
            != d_input_funcs.end())
447
        {
448
1234
          success = true;
449
        }
450
      }
451
      else
452
      {
453
        if (n.getKind() == kind::APPLY_UF)
454
        {
455
          f = n.getOperator();
456
          success = true;
457
        }
458
      }
459
11023
      if (success)
460
      {
461
1234
        if (std::find(terms.begin(), terms.end(), n) == terms.end())
462
        {
463
          // check if it matches the type requirement
464
1234
          if (isAntiSkolemizableType(f))
465
          {
466
1226
            if (args.empty())
467
            {
468
              // record arguments
469
3077
              for (unsigned i = 0; i < n.getNumChildren(); i++)
470
              {
471
2128
                args.push_back(n[i]);
472
              }
473
            }
474
            else
475
            {
476
              // arguments must be the same as those already recorded
477
871
              for (unsigned i = 0; i < n.getNumChildren(); i++)
478
              {
479
666
                if (args[i] != n[i])
480
                {
481
144
                  Trace("si-prt-debug") << "...bad invocation : " << n
482
72
                                        << " at arg " << i << "." << std::endl;
483
72
                  ret = false;
484
72
                  break;
485
                }
486
              }
487
            }
488
1226
            if (ret)
489
            {
490
1154
              terms.push_back(n);
491
1154
              subs.push_back(d_func_inv[f]);
492
            }
493
          }
494
          else
495
          {
496
16
            Trace("si-prt-debug") << "... " << f << " is a bad operator."
497
8
                                  << std::endl;
498
8
            ret = false;
499
          }
500
        }
501
      }
502
    }
503
    //}
504
11181
    visited[n] = ret;
505
11181
    return ret;
506
  }
507
}
508
509
1234
bool SingleInvocationPartition::isAntiSkolemizableType(Node f)
510
{
511
1234
  std::map<Node, bool>::iterator it = d_funcs.find(f);
512
1234
  if (it != d_funcs.end())
513
  {
514
715
    return it->second;
515
  }
516
  else
517
  {
518
1038
    TypeNode tn = f.getType();
519
519
    bool ret = false;
520
1487
    if (((tn.isFunction() && tn.getNumChildren() == d_arg_types.size() + 1)
521
589
         || (d_arg_types.empty() && tn.getNumChildren() == 0)))
522
    {
523
511
      ret = true;
524
1022
      std::vector<Node> children;
525
511
      children.push_back(f);
526
      // TODO: permutations of arguments
527
1764
      for (unsigned i = 0; i < d_arg_types.size(); i++)
528
      {
529
1253
        children.push_back(d_si_vars[i]);
530
1253
        if (tn[i] != d_arg_types[i])
531
        {
532
          ret = false;
533
          break;
534
        }
535
      }
536
511
      if (ret)
537
      {
538
1022
        Node t;
539
511
        if (children.size() > 1)
540
        {
541
449
          t = NodeManager::currentNM()->mkNode(kind::APPLY_UF, children);
542
        }
543
        else
544
        {
545
62
          t = children[0];
546
        }
547
511
        d_func_inv[f] = t;
548
1022
        std::stringstream ss;
549
511
        ss << "F_" << f;
550
1022
        TypeNode rt;
551
511
        if (d_arg_types.empty())
552
        {
553
62
          rt = tn;
554
        }
555
        else
556
        {
557
449
          rt = tn.getRangeType();
558
        }
559
1022
        Node v = NodeManager::currentNM()->mkBoundVar(ss.str(), rt);
560
511
        d_func_fo_var[f] = v;
561
511
        d_fo_var_to_func[v] = f;
562
511
        d_func_vars.push_back(v);
563
511
        d_all_funcs.push_back(f);
564
      }
565
    }
566
519
    d_funcs[f] = ret;
567
519
    return ret;
568
  }
569
}
570
571
99
Node SingleInvocationPartition::getConjunct(int index)
572
{
573
99
  return d_conjuncts[index].empty() ? NodeManager::currentNM()->mkConst(true)
574
99
                                    : (d_conjuncts[index].size() == 1
575
60
                                           ? d_conjuncts[index][0]
576
                                           : NodeManager::currentNM()->mkNode(
577
258
                                                 AND, d_conjuncts[index]));
578
}
579
580
319
void SingleInvocationPartition::debugPrint(const char* c)
581
{
582
319
  Trace(c) << "Single invocation variables : ";
583
1032
  for (unsigned i = 0; i < d_si_vars.size(); i++)
584
  {
585
713
    Trace(c) << d_si_vars[i] << " ";
586
  }
587
319
  Trace(c) << std::endl;
588
319
  Trace(c) << "Functions : " << std::endl;
589
838
  for (std::map<Node, bool>::iterator it = d_funcs.begin(); it != d_funcs.end();
590
       ++it)
591
  {
592
519
    Trace(c) << "  " << it->first << " : ";
593
519
    if (it->second)
594
    {
595
1022
      Trace(c) << d_func_inv[it->first] << " " << d_func_fo_var[it->first]
596
511
               << std::endl;
597
    }
598
    else
599
    {
600
8
      Trace(c) << "not incorporated." << std::endl;
601
    }
602
  }
603
1595
  for (unsigned i = 0; i < 4; i++)
604
  {
605
2233
    Trace(c) << (i == 0 ? "Single invocation"
606
1595
                        : (i == 1 ? "Non-single invocation"
607
638
                                  : (i == 2 ? "All"
608
                                            : "Non-ground single invocation")));
609
1276
    Trace(c) << " conjuncts: " << std::endl;
610
3207
    for (unsigned j = 0; j < d_conjuncts[i].size(); j++)
611
    {
612
1931
      Trace(c) << "  " << (j + 1) << " : " << d_conjuncts[i][j] << std::endl;
613
    }
614
  }
615
319
  Trace(c) << std::endl;
616
319
}
617
618
}  // namespace quantifiers
619
}  // namespace theory
620
29337
}  // namespace cvc5