GCC Code Coverage Report
Directory: . Exec Total Coverage
File: src/smt/term_formula_removal.cpp Lines: 218 225 96.9 %
Date: 2021-08-14 Branches: 443 902 49.1 %

Line Exec Source
1
/******************************************************************************
2
 * Top contributors (to current version):
3
 *   Andrew Reynolds, Dejan Jovanovic, Mudathir Mohamed
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
 * Removal of term formulas.
14
 */
15
#include "smt/term_formula_removal.h"
16
17
#include <vector>
18
19
#include "expr/attribute.h"
20
#include "expr/node_algorithm.h"
21
#include "expr/skolem_manager.h"
22
#include "expr/term_context_stack.h"
23
#include "options/smt_options.h"
24
#include "proof/conv_proof_generator.h"
25
#include "proof/lazy_proof.h"
26
27
using namespace std;
28
29
namespace cvc5 {
30
31
9920
RemoveTermFormulas::RemoveTermFormulas(context::UserContext* u,
32
9920
                                       ProofNodeManager* pnm)
33
    : d_tfCache(u),
34
      d_skolem_cache(u),
35
      d_pnm(pnm),
36
      d_tpg(nullptr),
37
9920
      d_lp(nullptr)
38
{
39
  // enable proofs if necessary
40
9920
  if (d_pnm != nullptr)
41
  {
42
7568
    d_tpg.reset(
43
        new TConvProofGenerator(d_pnm,
44
                                nullptr,
45
                                TConvPolicy::FIXPOINT,
46
                                TConvCachePolicy::NEVER,
47
                                "RemoveTermFormulas::TConvProofGenerator",
48
3784
                                &d_rtfc));
49
7568
    d_lp.reset(new LazyCDProof(
50
3784
        d_pnm, nullptr, nullptr, "RemoveTermFormulas::LazyCDProof"));
51
  }
52
9920
}
53
54
9920
RemoveTermFormulas::~RemoveTermFormulas() {}
55
56
625659
TrustNode RemoveTermFormulas::run(TNode assertion,
57
                                  std::vector<TrustNode>& newAsserts,
58
                                  std::vector<Node>& newSkolems,
59
                                  bool fixedPoint)
60
{
61
1251318
  Node itesRemoved = runInternal(assertion, newAsserts, newSkolems);
62
625659
  Assert(newAsserts.size() == newSkolems.size());
63
625659
  if (itesRemoved == assertion)
64
  {
65
559586
    return TrustNode::null();
66
  }
67
  // if running to fixed point, we run each new assertion through the
68
  // run lemma method
69
66073
  if (fixedPoint)
70
  {
71
2
    size_t i = 0;
72
1298
    while (i < newAsserts.size())
73
    {
74
1296
      TrustNode trn = newAsserts[i];
75
      // do not run to fixed point on subcall, since we are processing all
76
      // lemmas in this loop
77
648
      newAsserts[i] = runLemma(trn, newAsserts, newSkolems, false);
78
648
      i++;
79
    }
80
  }
81
  // The rewriting of assertion can be justified by the term conversion proof
82
  // generator d_tpg.
83
66073
  return TrustNode::mkTrustRewrite(assertion, itesRemoved, d_tpg.get());
84
}
85
86
TrustNode RemoveTermFormulas::run(TNode assertion)
87
{
88
  std::vector<TrustNode> newAsserts;
89
  std::vector<Node> newSkolems;
90
  return run(assertion, newAsserts, newSkolems, false);
91
}
92
93
648
TrustNode RemoveTermFormulas::runLemma(TrustNode lem,
94
                                       std::vector<TrustNode>& newAsserts,
95
                                       std::vector<Node>& newSkolems,
96
                                       bool fixedPoint)
97
{
98
1296
  TrustNode trn = run(lem.getProven(), newAsserts, newSkolems, fixedPoint);
99
648
  if (trn.isNull())
100
  {
101
    // no change
102
136
    return lem;
103
  }
104
512
  Assert(trn.getKind() == TrustNodeKind::REWRITE);
105
1024
  Node newAssertion = trn.getNode();
106
512
  if (!isProofEnabled())
107
  {
108
    // proofs not enabled, just take result
109
256
    return TrustNode::mkTrustLemma(newAssertion, nullptr);
110
  }
111
512
  Trace("rtf-proof-debug")
112
256
      << "RemoveTermFormulas::run: setup proof for processed new lemma"
113
256
      << std::endl;
114
512
  Node assertionPre = lem.getProven();
115
512
  Node naEq = trn.getProven();
116
  // this method is applying this method to TrustNode whose generator is
117
  // already d_lp (from the run method above), in which case this link is
118
  // not necessary.
119
256
  if (trn.getGenerator() != d_lp.get())
120
  {
121
256
    d_lp->addLazyStep(naEq, trn.getGenerator());
122
  }
123
  // ---------------- from input  ------------------------------- from trn
124
  // assertionPre                 assertionPre = newAssertion
125
  // ------------------------------------------------------- EQ_RESOLVE
126
  // newAssertion
127
256
  d_lp->addStep(newAssertion, PfRule::EQ_RESOLVE, {assertionPre, naEq}, {});
128
256
  return TrustNode::mkTrustLemma(newAssertion, d_lp.get());
129
}
130
131
625659
Node RemoveTermFormulas::runInternal(TNode assertion,
132
                                     std::vector<TrustNode>& output,
133
                                     std::vector<Node>& newSkolems)
134
{
135
625659
  NodeManager* nm = NodeManager::currentNM();
136
1251318
  TCtxStack ctx(&d_rtfc);
137
1251318
  std::vector<bool> processedChildren;
138
625659
  ctx.pushInitial(assertion);
139
625659
  processedChildren.push_back(false);
140
1251318
  std::pair<Node, uint32_t> initial = ctx.getCurrent();
141
1251318
  std::pair<Node, uint32_t> curr;
142
1251318
  Node node;
143
  uint32_t nodeVal;
144
625659
  TermFormulaCache::const_iterator itc;
145
8523857
  while (!ctx.empty())
146
  {
147
3949099
    curr = ctx.getCurrent();
148
3949099
    itc = d_tfCache.find(curr);
149
3949099
    node = curr.first;
150
3949099
    nodeVal = curr.second;
151
3949099
    Trace("rtf-debug") << "Visit " << node << ", " << nodeVal << std::endl;
152
5546698
    if (itc != d_tfCache.end())
153
    {
154
1597599
      Trace("rtf-debug") << "...already computed" << std::endl;
155
1597599
      ctx.pop();
156
1597599
      processedChildren.pop_back();
157
      // already computed
158
4524316
      continue;
159
    }
160
    // if we have yet to process children
161
2351500
    if (!processedChildren.back())
162
    {
163
      // check if we should replace the current node
164
2658236
      TrustNode newLem;
165
2658236
      Node currt = runCurrent(curr, newLem);
166
      // if we replaced by a skolem, we do not recurse
167
1329118
      if (!currt.isNull())
168
      {
169
        // if this is the first time we've seen this term, we have a new lemma
170
        // which we add to our vectors
171
32780
        if (!newLem.isNull())
172
        {
173
27955
          output.push_back(newLem);
174
27955
          newSkolems.push_back(currt);
175
        }
176
32780
        Trace("rtf-debug") << "...replace by skolem" << std::endl;
177
32780
        d_tfCache.insert(curr, currt);
178
32780
        ctx.pop();
179
32780
        processedChildren.pop_back();
180
      }
181
1296338
      else if (node.isClosure())
182
      {
183
        // currently, we never do any term formula removal in quantifier bodies
184
26895
        d_tfCache.insert(curr, node);
185
      }
186
      else
187
      {
188
1269443
        size_t nchild = node.getNumChildren();
189
1269443
        if (nchild > 0)
190
        {
191
1022382
          Trace("rtf-debug") << "...recurse to children" << std::endl;
192
          // recurse if we have children
193
1022382
          processedChildren[processedChildren.size() - 1] = true;
194
3296545
          for (size_t i = 0; i < nchild; i++)
195
          {
196
2274163
            ctx.pushChild(node, nodeVal, i);
197
2274163
            processedChildren.push_back(false);
198
          }
199
        }
200
        else
201
        {
202
247061
          Trace("rtf-debug") << "...base case" << std::endl;
203
247061
          d_tfCache.insert(curr, node);
204
247061
          ctx.pop();
205
247061
          processedChildren.pop_back();
206
        }
207
      }
208
1329118
      continue;
209
    }
210
1022382
    Trace("rtf-debug") << "...reconstruct" << std::endl;
211
    // otherwise, we are now finished processing children, pop the current node
212
    // and compute the result
213
1022382
    ctx.pop();
214
1022382
    processedChildren.pop_back();
215
    // if we have not already computed the result
216
2044764
    std::vector<Node> newChildren;
217
1022382
    bool childChanged = false;
218
1022382
    if (node.getMetaKind() == kind::metakind::PARAMETERIZED)
219
    {
220
207934
      newChildren.push_back(node.getOperator());
221
    }
222
    // reconstruct from the children
223
2044764
    std::pair<Node, uint32_t> currChild;
224
3296545
    for (size_t i = 0, nchild = node.getNumChildren(); i < nchild; i++)
225
    {
226
      // recompute the value of the child
227
2274163
      uint32_t val = d_rtfc.computeValue(node, nodeVal, i);
228
2274163
      currChild = std::pair<Node, uint32_t>(node[i], val);
229
2274163
      itc = d_tfCache.find(currChild);
230
2274163
      Assert(itc != d_tfCache.end());
231
4548326
      Node newChild = (*itc).second;
232
2274163
      Assert(!newChild.isNull());
233
2274163
      childChanged |= (newChild != node[i]);
234
2274163
      newChildren.push_back(newChild);
235
    }
236
    // If changes, we reconstruct the node
237
2044764
    Node ret = node;
238
1022382
    if (childChanged)
239
    {
240
111023
      ret = nm->mkNode(node.getKind(), newChildren);
241
    }
242
    // cache
243
1022382
    d_tfCache.insert(curr, ret);
244
  }
245
625659
  itc = d_tfCache.find(initial);
246
625659
  Assert(itc != d_tfCache.end());
247
1251318
  return (*itc).second;
248
}
249
250
1329118
Node RemoveTermFormulas::runCurrent(std::pair<Node, uint32_t>& curr,
251
                                    TrustNode& newLem)
252
{
253
2658236
  TNode node = curr.first;
254
1329118
  uint32_t cval = curr.second;
255
  bool inQuant, inTerm;
256
1329118
  RtfTermContext::getFlags(curr.second, inQuant, inTerm);
257
2658236
  Debug("ite") << "removeITEs(" << node << ")"
258
1329118
               << " " << inQuant << " " << inTerm << std::endl;
259
1329118
  Assert(!inQuant);
260
261
1329118
  NodeManager *nodeManager = NodeManager::currentNM();
262
263
2658236
  TypeNode nodeType = node.getType();
264
2658236
  Node skolem;
265
2658236
  Node newAssertion;
266
  // the exists form of the assertion
267
1329118
  ProofGenerator* newAssertionPg = nullptr;
268
  // Handle non-Boolean ITEs here. Boolean ones (within terms) are handled
269
  // in the "non-variable Boolean term within term" case below.
270
1329118
  if (node.getKind() == kind::ITE && !nodeType.isBoolean())
271
  {
272
    // Here, we eliminate the ITE if we are not Boolean and if we are
273
    // not in a quantified formula. This policy should be in sync with
274
    // the policy for when to apply theory preprocessing to terms, see PR
275
    // #5497.
276
31172
    skolem = getSkolemForNode(node);
277
31172
    if (skolem.isNull())
278
    {
279
52738
      Trace("rtf-proof-debug")
280
26369
          << "RemoveTermFormulas::run: make ITE skolem" << std::endl;
281
      // Make the skolem to represent the ITE
282
26369
      SkolemManager* sm = nodeManager->getSkolemManager();
283
26369
      skolem = sm->mkPurifySkolem(
284
          node,
285
          "termITE",
286
          "a variable introduced due to term-level ITE removal");
287
26369
      d_skolem_cache.insert(node, skolem);
288
289
      // Notice that in very rare cases, two different terms may have the
290
      // same purification skolem (see SkolemManager::mkPurifySkolem) For such
291
      // cases, for simplicity, we repeat the work of constructing the
292
      // assertion and proofs below. This is so that the proof for the new form
293
      // of the lemma is used.
294
295
      // The new assertion
296
79107
      newAssertion = nodeManager->mkNode(
297
105476
          kind::ITE, node[0], skolem.eqNode(node[1]), skolem.eqNode(node[2]));
298
299
      // we justify it internally
300
26369
      if (isProofEnabled())
301
      {
302
26638
        Trace("rtf-proof-debug")
303
13319
            << "RemoveTermFormulas::run: justify " << newAssertion
304
13319
            << " with ITE axiom" << std::endl;
305
        // ---------------------- REMOVE_TERM_FORMULA_AXIOM
306
        // (ite node[0]
307
        //      (= node node[1])            ------------- MACRO_SR_PRED_INTRO
308
        //      (= node node[2]))           node = skolem
309
        // ------------------------------------------ MACRO_SR_PRED_TRANSFORM
310
        // (ite node[0] (= skolem node[1]) (= skolem node[2]))
311
        //
312
        // Note that the MACRO_SR_PRED_INTRO step holds due to conversion
313
        // of skolem into its witness form, which is node.
314
26638
        Node axiom = getAxiomFor(node);
315
13319
        d_lp->addStep(axiom, PfRule::REMOVE_TERM_FORMULA_AXIOM, {}, {node});
316
26638
        Node eq = node.eqNode(skolem);
317
13319
        d_lp->addStep(eq, PfRule::MACRO_SR_PRED_INTRO, {}, {eq});
318
53276
        d_lp->addStep(newAssertion,
319
                      PfRule::MACRO_SR_PRED_TRANSFORM,
320
                      {axiom, eq},
321
39957
                      {newAssertion});
322
13319
        newAssertionPg = d_lp.get();
323
      }
324
    }
325
  }
326
1297946
  else if (node.getKind() == kind::LAMBDA)
327
  {
328
    // if a lambda, do lambda-lifting
329
25
    if (!expr::hasFreeVar(node))
330
    {
331
25
      skolem = getSkolemForNode(node);
332
25
      if (skolem.isNull())
333
      {
334
50
        Trace("rtf-proof-debug")
335
25
            << "RemoveTermFormulas::run: make LAMBDA skolem" << std::endl;
336
        // Make the skolem to represent the lambda
337
25
        SkolemManager* sm = nodeManager->getSkolemManager();
338
25
        skolem = sm->mkPurifySkolem(
339
            node,
340
            "lambdaF",
341
            "a function introduced due to term-level lambda removal");
342
25
        d_skolem_cache.insert(node, skolem);
343
344
        // The new assertion
345
50
        std::vector<Node> children;
346
        // bound variable list
347
25
        children.push_back(node[0]);
348
        // body
349
50
        std::vector<Node> skolem_app_c;
350
25
        skolem_app_c.push_back(skolem);
351
25
        skolem_app_c.insert(skolem_app_c.end(), node[0].begin(), node[0].end());
352
50
        Node skolem_app = nodeManager->mkNode(kind::APPLY_UF, skolem_app_c);
353
25
        children.push_back(skolem_app.eqNode(node[1]));
354
        // axiom defining skolem
355
25
        newAssertion = nodeManager->mkNode(kind::FORALL, children);
356
357
        // Lambda lifting is trivial to justify, hence we don't set a proof
358
        // generator here. In particular, replacing the skolem introduced
359
        // here with its original lambda ensures the new assertion rewrites
360
        // to true.
361
        // For example, if (lambda y. t[y]) has skolem k, then this lemma is:
362
        //   forall x. k(x)=t[x]
363
        // whose witness form rewrites
364
        //   forall x. (lambda y. t[y])(x)=t[x] --> forall x. t[x]=t[x] --> true
365
      }
366
    }
367
  }
368
1297921
  else if (node.getKind() == kind::WITNESS)
369
  {
370
    // If a witness choice
371
    //   For details on this operator, see
372
    //   http://planetmath.org/hilbertsvarepsilonoperator.
373
560
    if (!expr::hasFreeVar(node))
374
    {
375
      // NOTE: we can replace by t if body is of the form (and (= z t) ...)
376
560
      skolem = getSkolemForNode(node);
377
560
      if (skolem.isNull())
378
      {
379
1076
        Trace("rtf-proof-debug")
380
538
            << "RemoveTermFormulas::run: make WITNESS skolem" << std::endl;
381
        // Make the skolem to witness the choice, which notice is handled
382
        // as a special case within SkolemManager::mkPurifySkolem.
383
538
        SkolemManager* sm = nodeManager->getSkolemManager();
384
538
        skolem = sm->mkPurifySkolem(
385
            node,
386
            "witnessK",
387
            "a skolem introduced due to term-level witness removal");
388
538
        d_skolem_cache.insert(node, skolem);
389
390
538
        Assert(node[0].getNumChildren() == 1);
391
392
        // The new assertion is the assumption that the body
393
        // of the witness operator holds for the Skolem
394
538
        newAssertion = node[1].substitute(node[0][0], skolem);
395
396
        // Get the proof generator, if one exists, which was responsible for
397
        // constructing this witness term. This may not exist, in which case
398
        // the witness term was trivial to justify. This is the case e.g. for
399
        // purification witness terms.
400
538
        if (isProofEnabled())
401
        {
402
          Node existsAssertion =
403
120
              nodeManager->mkNode(kind::EXISTS, node[0], node[1]);
404
          // -------------------- from skolem manager
405
          // (exists x. node[1])
406
          // -------------------- SKOLEMIZE
407
          // node[1] * { x -> skolem }
408
60
          ProofGenerator* expg = sm->getProofGenerator(existsAssertion);
409
60
          d_lp->addLazyStep(existsAssertion,
410
                            expg,
411
                            PfRule::WITNESS_AXIOM,
412
                            true,
413
                            "RemoveTermFormulas::run:skolem_pf");
414
60
          d_lp->addStep(newAssertion, PfRule::SKOLEMIZE, {existsAssertion}, {});
415
60
          newAssertionPg = d_lp.get();
416
        }
417
      }
418
    }
419
  }
420
3890343
  else if (node.getKind() != kind::BOOLEAN_TERM_VARIABLE && nodeType.isBoolean()
421
1942928
           && inTerm)
422
  {
423
    // if a non-variable Boolean term within another term, replace it
424
1023
    skolem = getSkolemForNode(node);
425
1023
    if (skolem.isNull())
426
    {
427
2046
      Trace("rtf-proof-debug")
428
1023
          << "RemoveTermFormulas::run: make BOOLEAN_TERM_VARIABLE skolem"
429
1023
          << std::endl;
430
      // Make the skolem to represent the Boolean term
431
      // Skolems introduced for Boolean formulas appearing in terms have a
432
      // special kind (BOOLEAN_TERM_VARIABLE) that ensures they are handled
433
      // properly in theory combination. We must use this kind here instead of a
434
      // generic skolem. Notice that the name/comment are currently ignored
435
      // within SkolemManager::mkPurifySkolem, since BOOLEAN_TERM_VARIABLE
436
      // variables cannot be given names.
437
1023
      SkolemManager* sm = nodeManager->getSkolemManager();
438
1023
      skolem = sm->mkPurifySkolem(
439
          node,
440
          "btvK",
441
          "a Boolean term variable introduced during term formula removal",
442
          NodeManager::SKOLEM_BOOL_TERM_VAR);
443
1023
      d_skolem_cache.insert(node, skolem);
444
445
      // The new assertion
446
1023
      newAssertion = skolem.eqNode(node);
447
448
      // Boolean term removal is trivial to justify, hence we don't set a proof
449
      // generator here. It is trivial to justify since it is an instance of
450
      // purification, which is justified by conversion to witness forms.
451
    }
452
  }
453
454
  // if the term should be replaced by a skolem
455
1329118
  if( !skolem.isNull() ){
456
    // this must be done regardless of whether the assertion was new below,
457
    // since a formula-term may rewrite to the same skolem in multiple contexts.
458
32780
    if (isProofEnabled())
459
    {
460
      // justify the introduction of the skolem
461
      // ------------------- MACRO_SR_PRED_INTRO
462
      // t = witness x. x=t
463
      // The above step is trivial, since the skolems introduced above are
464
      // all purification skolems. We record this equality in the term
465
      // conversion proof generator.
466
32246
      d_tpg->addRewriteStep(node,
467
                            skolem,
468
                            PfRule::MACRO_SR_PRED_INTRO,
469
                            {},
470
                            {node.eqNode(skolem)},
471
                            true,
472
16123
                            cval);
473
    }
474
475
    // if the skolem was introduced in this call
476
32780
    if (!newAssertion.isNull())
477
    {
478
55910
      Trace("rtf-proof-debug")
479
27955
          << "RemoveTermFormulas::run: setup proof for new assertion "
480
27955
          << newAssertion << std::endl;
481
      // if proofs are enabled
482
27955
      if (isProofEnabled())
483
      {
484
        // justify the axiom that defines the skolem, if not already done so
485
13728
        if (newAssertionPg == nullptr)
486
        {
487
          // Should have trivial justification if not yet provided. This is the
488
          // case of lambda lifting and Boolean term removal.
489
          // ---------------- MACRO_SR_PRED_INTRO
490
          // newAssertion
491
698
          d_lp->addStep(
492
349
              newAssertion, PfRule::MACRO_SR_PRED_INTRO, {}, {newAssertion});
493
        }
494
      }
495
55910
      Trace("rtf-debug") << "*** term formula removal introduced " << skolem
496
27955
                         << " for " << node << std::endl;
497
498
27955
      newLem = TrustNode::mkTrustLemma(newAssertion, d_lp.get());
499
500
27955
      Trace("rtf-proof-debug") << "Checking closed..." << std::endl;
501
27955
      newLem.debugCheckClosed("rtf-proof-debug",
502
                              "RemoveTermFormulas::run:new_assert");
503
    }
504
505
    // The representation is now the skolem
506
32780
    return skolem;
507
  }
508
509
  // return null, indicating we will traverse children within runInternal
510
1296338
  return Node::null();
511
}
512
513
32780
Node RemoveTermFormulas::getSkolemForNode(Node k) const
514
{
515
  context::CDInsertHashMap<Node, Node>::const_iterator itk =
516
32780
      d_skolem_cache.find(k);
517
32780
  if (itk != d_skolem_cache.end())
518
  {
519
4825
    return itk->second;
520
  }
521
27955
  return Node::null();
522
}
523
524
27599
Node RemoveTermFormulas::getAxiomFor(Node n)
525
{
526
27599
  NodeManager* nm = NodeManager::currentNM();
527
27599
  Kind k = n.getKind();
528
27599
  if (k == kind::ITE)
529
  {
530
27599
    return nm->mkNode(kind::ITE, n[0], n.eqNode(n[1]), n.eqNode(n[2]));
531
  }
532
  return Node::null();
533
}
534
535
ProofGenerator* RemoveTermFormulas::getTConvProofGenerator()
536
{
537
  return d_tpg.get();
538
}
539
540
88154
bool RemoveTermFormulas::isProofEnabled() const { return d_pnm != nullptr; }
541
542
29340
}  // namespace cvc5