GCC Code Coverage Report
Directory: . Exec Total Coverage
File: src/smt/term_formula_removal.cpp Lines: 217 231 93.9 %
Date: 2021-11-07 Branches: 444 934 47.5 %

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