GCC Code Coverage Report
Directory: . Exec Total Coverage
File: src/smt/term_formula_removal.cpp Lines: 209 225 92.9 %
Date: 2021-09-18 Branches: 422 902 46.8 %

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
10007
RemoveTermFormulas::RemoveTermFormulas(context::UserContext* u,
32
10007
                                       ProofNodeManager* pnm)
33
    : d_tfCache(u),
34
      d_skolem_cache(u),
35
      d_pnm(pnm),
36
      d_tpg(nullptr),
37
10007
      d_lp(nullptr)
38
{
39
  // enable proofs if necessary
40
10007
  if (d_pnm != nullptr)
41
  {
42
7620
    d_tpg.reset(
43
        new TConvProofGenerator(d_pnm,
44
                                nullptr,
45
                                TConvPolicy::FIXPOINT,
46
                                TConvCachePolicy::NEVER,
47
                                "RemoveTermFormulas::TConvProofGenerator",
48
3810
                                &d_rtfc));
49
7620
    d_lp.reset(new LazyCDProof(
50
3810
        d_pnm, nullptr, nullptr, "RemoveTermFormulas::LazyCDProof"));
51
  }
52
10007
}
53
54
10004
RemoveTermFormulas::~RemoveTermFormulas() {}
55
56
630459
TrustNode RemoveTermFormulas::run(TNode assertion,
57
                                  std::vector<TrustNode>& newAsserts,
58
                                  std::vector<Node>& newSkolems,
59
                                  bool fixedPoint)
60
{
61
1260918
  Node itesRemoved = runInternal(assertion, newAsserts, newSkolems);
62
630459
  Assert(newAsserts.size() == newSkolems.size());
63
630459
  if (itesRemoved == assertion)
64
  {
65
566747
    return TrustNode::null();
66
  }
67
  // if running to fixed point, we run each new assertion through the
68
  // run lemma method
69
63712
  if (fixedPoint)
70
  {
71
1
    size_t i = 0;
72
649
    while (i < newAsserts.size())
73
    {
74
648
      TrustNode trn = newAsserts[i];
75
      // do not run to fixed point on subcall, since we are processing all
76
      // lemmas in this loop
77
324
      newAsserts[i] = runLemma(trn, newAsserts, newSkolems, false);
78
324
      i++;
79
    }
80
  }
81
  // The rewriting of assertion can be justified by the term conversion proof
82
  // generator d_tpg.
83
63712
  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
324
TrustNode RemoveTermFormulas::runLemma(TrustNode lem,
94
                                       std::vector<TrustNode>& newAsserts,
95
                                       std::vector<Node>& newSkolems,
96
                                       bool fixedPoint)
97
{
98
648
  TrustNode trn = run(lem.getProven(), newAsserts, newSkolems, fixedPoint);
99
324
  if (trn.isNull())
100
  {
101
    // no change
102
68
    return lem;
103
  }
104
256
  Assert(trn.getKind() == TrustNodeKind::REWRITE);
105
512
  Node newAssertion = trn.getNode();
106
256
  if (!isProofEnabled())
107
  {
108
    // proofs not enabled, just take result
109
256
    return TrustNode::mkTrustLemma(newAssertion, nullptr);
110
  }
111
  Trace("rtf-proof-debug")
112
      << "RemoveTermFormulas::run: setup proof for processed new lemma"
113
      << std::endl;
114
  Node assertionPre = lem.getProven();
115
  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
  if (trn.getGenerator() != d_lp.get())
120
  {
121
    d_lp->addLazyStep(naEq, trn.getGenerator());
122
  }
123
  // ---------------- from input  ------------------------------- from trn
124
  // assertionPre                 assertionPre = newAssertion
125
  // ------------------------------------------------------- EQ_RESOLVE
126
  // newAssertion
127
  d_lp->addStep(newAssertion, PfRule::EQ_RESOLVE, {assertionPre, naEq}, {});
128
  return TrustNode::mkTrustLemma(newAssertion, d_lp.get());
129
}
130
131
630459
Node RemoveTermFormulas::runInternal(TNode assertion,
132
                                     std::vector<TrustNode>& output,
133
                                     std::vector<Node>& newSkolems)
134
{
135
630459
  NodeManager* nm = NodeManager::currentNM();
136
1260918
  TCtxStack ctx(&d_rtfc);
137
1260918
  std::vector<bool> processedChildren;
138
630459
  ctx.pushInitial(assertion);
139
630459
  processedChildren.push_back(false);
140
1260918
  std::pair<Node, uint32_t> initial = ctx.getCurrent();
141
1260918
  std::pair<Node, uint32_t> curr;
142
1260918
  Node node;
143
  uint32_t nodeVal;
144
630459
  TermFormulaCache::const_iterator itc;
145
8351493
  while (!ctx.empty())
146
  {
147
3860517
    curr = ctx.getCurrent();
148
3860517
    itc = d_tfCache.find(curr);
149
3860517
    node = curr.first;
150
3860517
    nodeVal = curr.second;
151
3860517
    Trace("rtf-debug") << "Visit " << node << ", " << nodeVal << std::endl;
152
5366049
    if (itc != d_tfCache.end())
153
    {
154
1505532
      Trace("rtf-debug") << "...already computed" << std::endl;
155
1505532
      ctx.pop();
156
1505532
      processedChildren.pop_back();
157
      // already computed
158
4340456
      continue;
159
    }
160
    // if we have yet to process children
161
2354985
    if (!processedChildren.back())
162
    {
163
      // check if we should replace the current node
164
2658784
      TrustNode newLem;
165
2658784
      Node currt = runCurrent(curr, newLem);
166
      // if we replaced by a skolem, we do not recurse
167
1329392
      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
31230
        if (!newLem.isNull())
172
        {
173
26647
          output.push_back(newLem);
174
26647
          newSkolems.push_back(currt);
175
        }
176
31230
        Trace("rtf-debug") << "...replace by skolem" << std::endl;
177
31230
        d_tfCache.insert(curr, currt);
178
31230
        ctx.pop();
179
31230
        processedChildren.pop_back();
180
      }
181
1298162
      else if (node.isClosure())
182
      {
183
        // currently, we never do any term formula removal in quantifier bodies
184
26954
        d_tfCache.insert(curr, node);
185
      }
186
      else
187
      {
188
1271208
        size_t nchild = node.getNumChildren();
189
1271208
        if (nchild > 0)
190
        {
191
1025593
          Trace("rtf-debug") << "...recurse to children" << std::endl;
192
          // recurse if we have children
193
1025593
          processedChildren[processedChildren.size() - 1] = true;
194
3203104
          for (size_t i = 0; i < nchild; i++)
195
          {
196
2177511
            ctx.pushChild(node, nodeVal, i);
197
2177511
            processedChildren.push_back(false);
198
          }
199
        }
200
        else
201
        {
202
245615
          Trace("rtf-debug") << "...base case" << std::endl;
203
245615
          d_tfCache.insert(curr, node);
204
245615
          ctx.pop();
205
245615
          processedChildren.pop_back();
206
        }
207
      }
208
1329392
      continue;
209
    }
210
1025593
    Trace("rtf-debug") << "...reconstruct" << std::endl;
211
    // otherwise, we are now finished processing children, pop the current node
212
    // and compute the result
213
1025593
    ctx.pop();
214
1025593
    processedChildren.pop_back();
215
    // if we have not already computed the result
216
2051186
    std::vector<Node> newChildren;
217
1025593
    bool childChanged = false;
218
1025593
    if (node.getMetaKind() == kind::metakind::PARAMETERIZED)
219
    {
220
209534
      newChildren.push_back(node.getOperator());
221
    }
222
    // reconstruct from the children
223
2051186
    std::pair<Node, uint32_t> currChild;
224
3203104
    for (size_t i = 0, nchild = node.getNumChildren(); i < nchild; i++)
225
    {
226
      // recompute the value of the child
227
2177511
      uint32_t val = d_rtfc.computeValue(node, nodeVal, i);
228
2177511
      currChild = std::pair<Node, uint32_t>(node[i], val);
229
2177511
      itc = d_tfCache.find(currChild);
230
2177511
      Assert(itc != d_tfCache.end());
231
4355022
      Node newChild = (*itc).second;
232
2177511
      Assert(!newChild.isNull());
233
2177511
      childChanged |= (newChild != node[i]);
234
2177511
      newChildren.push_back(newChild);
235
    }
236
    // If changes, we reconstruct the node
237
2051186
    Node ret = node;
238
1025593
    if (childChanged)
239
    {
240
107004
      ret = nm->mkNode(node.getKind(), newChildren);
241
    }
242
    // cache
243
1025593
    d_tfCache.insert(curr, ret);
244
  }
245
630459
  itc = d_tfCache.find(initial);
246
630459
  Assert(itc != d_tfCache.end());
247
1260918
  return (*itc).second;
248
}
249
250
1329392
Node RemoveTermFormulas::runCurrent(std::pair<Node, uint32_t>& curr,
251
                                    TrustNode& newLem)
252
{
253
2658784
  TNode node = curr.first;
254
1329392
  uint32_t cval = curr.second;
255
  bool inQuant, inTerm;
256
1329392
  RtfTermContext::getFlags(curr.second, inQuant, inTerm);
257
2658784
  Debug("ite") << "removeITEs(" << node << ")"
258
1329392
               << " " << inQuant << " " << inTerm << std::endl;
259
1329392
  Assert(!inQuant);
260
261
1329392
  NodeManager *nodeManager = NodeManager::currentNM();
262
263
2658784
  TypeNode nodeType = node.getType();
264
2658784
  Node skolem;
265
2658784
  Node newAssertion;
266
  // the exists form of the assertion
267
1329392
  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
1329392
  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
29636
    skolem = getSkolemForNode(node);
277
29636
    if (skolem.isNull())
278
    {
279
50150
      Trace("rtf-proof-debug")
280
25075
          << "RemoveTermFormulas::run: make ITE skolem" << std::endl;
281
      // Make the skolem to represent the ITE
282
25075
      SkolemManager* sm = nodeManager->getSkolemManager();
283
25075
      skolem = sm->mkPurifySkolem(
284
          node,
285
          "termITE",
286
          "a variable introduced due to term-level ITE removal");
287
25075
      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
75225
      newAssertion = nodeManager->mkNode(
297
100300
          kind::ITE, node[0], skolem.eqNode(node[1]), skolem.eqNode(node[2]));
298
299
      // we justify it internally
300
25075
      if (isProofEnabled())
301
      {
302
26288
        Trace("rtf-proof-debug")
303
13144
            << "RemoveTermFormulas::run: justify " << newAssertion
304
13144
            << " 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
26288
        Node axiom = getAxiomFor(node);
315
13144
        d_lp->addStep(axiom, PfRule::REMOVE_TERM_FORMULA_AXIOM, {}, {node});
316
26288
        Node eq = node.eqNode(skolem);
317
13144
        d_lp->addStep(eq, PfRule::MACRO_SR_PRED_INTRO, {}, {eq});
318
52576
        d_lp->addStep(newAssertion,
319
                      PfRule::MACRO_SR_PRED_TRANSFORM,
320
                      {axiom, eq},
321
39432
                      {newAssertion});
322
13144
        newAssertionPg = d_lp.get();
323
      }
324
    }
325
  }
326
1299756
  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
1299731
  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
548
    if (!expr::hasFreeVar(node))
374
    {
375
      // NOTE: we can replace by t if body is of the form (and (= z t) ...)
376
548
      skolem = getSkolemForNode(node);
377
548
      if (skolem.isNull())
378
      {
379
1052
        Trace("rtf-proof-debug")
380
526
            << "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
526
        SkolemManager* sm = nodeManager->getSkolemManager();
384
526
        skolem = sm->mkPurifySkolem(
385
            node,
386
            "witnessK",
387
            "a skolem introduced due to term-level witness removal");
388
526
        d_skolem_cache.insert(node, skolem);
389
390
526
        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
526
        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
526
        if (isProofEnabled())
401
        {
402
          Node existsAssertion =
403
96
              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
48
          ProofGenerator* expg = sm->getProofGenerator(existsAssertion);
409
48
          d_lp->addLazyStep(existsAssertion,
410
                            expg,
411
                            PfRule::WITNESS_AXIOM,
412
                            true,
413
                            "RemoveTermFormulas::run:skolem_pf");
414
48
          d_lp->addStep(newAssertion, PfRule::SKOLEMIZE, {existsAssertion}, {});
415
48
          newAssertionPg = d_lp.get();
416
        }
417
      }
418
    }
419
  }
420
3895817
  else if (node.getKind() != kind::BOOLEAN_TERM_VARIABLE && nodeType.isBoolean()
421
1939231
           && inTerm)
422
  {
423
    // if a non-variable Boolean term within another term, replace it
424
1021
    skolem = getSkolemForNode(node);
425
1021
    if (skolem.isNull())
426
    {
427
2042
      Trace("rtf-proof-debug")
428
1021
          << "RemoveTermFormulas::run: make BOOLEAN_TERM_VARIABLE skolem"
429
1021
          << 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
1021
      SkolemManager* sm = nodeManager->getSkolemManager();
438
1021
      skolem = sm->mkPurifySkolem(
439
          node,
440
          "btvK",
441
          "a Boolean term variable introduced during term formula removal",
442
          NodeManager::SKOLEM_BOOL_TERM_VAR);
443
1021
      d_skolem_cache.insert(node, skolem);
444
445
      // The new assertion
446
1021
      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
1329392
  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
31230
    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
31710
      d_tpg->addRewriteStep(node,
467
                            skolem,
468
                            PfRule::MACRO_SR_PRED_INTRO,
469
                            {},
470
                            {node.eqNode(skolem)},
471
                            true,
472
15855
                            cval);
473
    }
474
475
    // if the skolem was introduced in this call
476
31230
    if (!newAssertion.isNull())
477
    {
478
53294
      Trace("rtf-proof-debug")
479
26647
          << "RemoveTermFormulas::run: setup proof for new assertion "
480
26647
          << newAssertion << std::endl;
481
      // if proofs are enabled
482
26647
      if (isProofEnabled())
483
      {
484
        // justify the axiom that defines the skolem, if not already done so
485
13542
        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
700
          d_lp->addStep(
492
350
              newAssertion, PfRule::MACRO_SR_PRED_INTRO, {}, {newAssertion});
493
        }
494
      }
495
53294
      Trace("rtf-debug") << "*** term formula removal introduced " << skolem
496
26647
                         << " for " << node << std::endl;
497
498
26647
      newLem = TrustNode::mkTrustLemma(newAssertion, d_lp.get());
499
500
26647
      Trace("rtf-proof-debug") << "Checking closed..." << std::endl;
501
26647
      newLem.debugCheckClosed("rtf-proof-debug",
502
                              "RemoveTermFormulas::run:new_assert");
503
    }
504
505
    // The representation is now the skolem
506
31230
    return skolem;
507
  }
508
509
  // return null, indicating we will traverse children within runInternal
510
1298162
  return Node::null();
511
}
512
513
31230
Node RemoveTermFormulas::getSkolemForNode(Node k) const
514
{
515
  context::CDInsertHashMap<Node, Node>::const_iterator itk =
516
31230
      d_skolem_cache.find(k);
517
31230
  if (itk != d_skolem_cache.end())
518
  {
519
4583
    return itk->second;
520
  }
521
26647
  return Node::null();
522
}
523
524
16376
Node RemoveTermFormulas::getAxiomFor(Node n)
525
{
526
16376
  NodeManager* nm = NodeManager::currentNM();
527
16376
  Kind k = n.getKind();
528
16376
  if (k == kind::ITE)
529
  {
530
16376
    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
83734
bool RemoveTermFormulas::isProofEnabled() const { return d_pnm != nullptr; }
541
542
29574
}  // namespace cvc5