GCC Code Coverage Report
Directory: . Exec Total Coverage
File: src/theory/theory_preprocessor.cpp Lines: 201 216 93.1 %
Date: 2021-09-29 Branches: 476 1048 45.4 %

Line Exec Source
1
/******************************************************************************
2
 * Top contributors (to current version):
3
 *   Andrew Reynolds, Dejan Jovanovic, Morgan Deters
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
 * The theory preprocessor.
14
 */
15
16
#include "theory/theory_preprocessor.h"
17
18
#include "expr/skolem_manager.h"
19
#include "proof/lazy_proof.h"
20
#include "smt/logic_exception.h"
21
#include "theory/logic_info.h"
22
#include "theory/rewriter.h"
23
#include "theory/theory_engine.h"
24
25
using namespace std;
26
27
namespace cvc5 {
28
namespace theory {
29
30
6328
TheoryPreprocessor::TheoryPreprocessor(TheoryEngine& engine,
31
                                       context::UserContext* userContext,
32
6328
                                       ProofNodeManager* pnm)
33
    : d_engine(engine),
34
6328
      d_logicInfo(engine.getLogicInfo()),
35
      d_ppCache(userContext),
36
      d_rtfCache(userContext),
37
      d_tfr(userContext, pnm),
38
      d_tpg(pnm ? new TConvProofGenerator(
39
                      pnm,
40
                      userContext,
41
                      TConvPolicy::FIXPOINT,
42
                      TConvCachePolicy::NEVER,
43
                      "TheoryPreprocessor::preprocess_rewrite",
44
149
                      &d_iqtc)
45
                : nullptr),
46
      d_tpgRtf(pnm ? new TConvProofGenerator(pnm,
47
                                             userContext,
48
                                             TConvPolicy::FIXPOINT,
49
                                             TConvCachePolicy::NEVER,
50
                                             "TheoryPreprocessor::rtf",
51
149
                                             &d_iqtc)
52
                   : nullptr),
53
      d_tpgRew(pnm ? new TConvProofGenerator(pnm,
54
                                             userContext,
55
                                             TConvPolicy::ONCE,
56
                                             TConvCachePolicy::NEVER,
57
149
                                             "TheoryPreprocessor::pprew")
58
                   : nullptr),
59
      d_tspg(nullptr),
60
      d_lp(pnm ? new LazyCDProof(pnm,
61
                                 nullptr,
62
                                 userContext,
63
149
                                 "TheoryPreprocessor::LazyCDProof")
64
13252
               : nullptr)
65
{
66
6328
  if (isProofEnabled())
67
  {
68
    // Make the main term conversion sequence generator, which tracks up to
69
    // three conversions made in succession:
70
    // (1) rewriting
71
    // (2) (theory preprocessing+rewriting until fixed point)+term formula
72
    // removal+rewriting.
73
298
    std::vector<ProofGenerator*> ts;
74
149
    ts.push_back(d_tpgRew.get());
75
149
    ts.push_back(d_tpgRtf.get());
76
298
    d_tspg.reset(new TConvSeqProofGenerator(
77
149
        pnm, ts, userContext, "TheoryPreprocessor::sequence"));
78
  }
79
6328
}
80
81
6325
TheoryPreprocessor::~TheoryPreprocessor() {}
82
83
190431
TrustNode TheoryPreprocessor::preprocess(TNode node,
84
                                         std::vector<TrustNode>& newLemmas,
85
                                         std::vector<Node>& newSkolems)
86
{
87
190431
  return preprocessInternal(node, newLemmas, newSkolems, true);
88
}
89
90
453781
TrustNode TheoryPreprocessor::preprocessInternal(
91
    TNode node,
92
    std::vector<TrustNode>& newLemmas,
93
    std::vector<Node>& newSkolems,
94
    bool procLemmas)
95
{
96
  // In this method, all rewriting steps of node are stored in d_tpg.
97
98
453781
  Trace("tpp") << "TheoryPreprocessor::preprocess: start " << node << std::endl;
99
100
  // We must rewrite before preprocessing, because some terms when rewritten
101
  // may introduce new terms that are not top-level and require preprocessing.
102
  // An example of this is (forall ((x Int)) (and (tail L) (P x))) which
103
  // rewrites to (and (tail L) (forall ((x Int)) (P x))). The subterm (tail L)
104
  // must be preprocessed as a child here.
105
907562
  Node irNode = rewriteWithProof(node, d_tpgRew.get(), true);
106
107
  // run theory preprocessing
108
907562
  TrustNode tpp = theoryPreprocess(irNode, newLemmas, newSkolems);
109
907544
  Node ppNode = tpp.getNode();
110
111
453772
  if (Trace.isOn("tpp-debug"))
112
  {
113
    if (node != irNode)
114
    {
115
      Trace("tpp-debug") << "after initial rewriting : " << irNode << std::endl;
116
    }
117
    if (irNode != ppNode)
118
    {
119
      Trace("tpp-debug")
120
          << "after preprocessing + rewriting and term formula removal : "
121
          << ppNode << std::endl;
122
    }
123
    Trace("tpp-debug") << "TheoryPreprocessor::preprocess: finish" << std::endl;
124
  }
125
126
453772
  if (procLemmas)
127
  {
128
    // Also must preprocess the new lemmas. This is especially important for
129
    // formulas containing witness terms whose bodies are not in preprocessed
130
    // form, as term formula removal introduces new lemmas for these that
131
    // require theory-preprocessing.
132
439289
    size_t i = 0;
133
468255
    while (i < newLemmas.size())
134
    {
135
28966
      TrustNode cur = newLemmas[i];
136
14483
      newLemmas[i] = preprocessLemmaInternal(cur, newLemmas, newSkolems, false);
137
14483
      Trace("tpp") << "Final lemma : " << newLemmas[i].getProven() << std::endl;
138
14483
      i++;
139
    }
140
  }
141
142
453772
  if (node == ppNode)
143
  {
144
710086
    Trace("tpp-debug") << "...TheoryPreprocessor::preprocess returned no change"
145
355043
                       << std::endl;
146
    // no change
147
355043
    return TrustNode::null();
148
  }
149
150
  // Now, sequence the conversion steps if proofs are enabled.
151
197458
  TrustNode tret;
152
98729
  if (isProofEnabled())
153
  {
154
8062
    std::vector<Node> cterms;
155
4031
    cterms.push_back(node);
156
4031
    cterms.push_back(irNode);
157
4031
    cterms.push_back(ppNode);
158
    // We have that:
159
    // node -> irNode via rewriting
160
    // irNode -> ppNode via theory-preprocessing + rewriting + tf removal
161
4031
    tret = d_tspg->mkTrustRewriteSequence(cterms);
162
4031
    tret.debugCheckClosed("tpp-debug", "TheoryPreprocessor::lemma_ret");
163
  }
164
  else
165
  {
166
94698
    tret = TrustNode::mkTrustRewrite(node, ppNode, nullptr);
167
  }
168
169
197458
  Trace("tpp-debug") << "...TheoryPreprocessor::preprocess returned "
170
98729
                     << tret.getNode() << std::endl;
171
197458
  Trace("tpp") << "TheoryPreprocessor::preprocess: return " << tret.getNode()
172
98729
               << ", procLemmas=" << procLemmas
173
98729
               << ", # lemmas = " << newLemmas.size() << std::endl;
174
98729
  return tret;
175
}
176
177
248867
TrustNode TheoryPreprocessor::preprocessLemma(TrustNode node,
178
                                              std::vector<TrustNode>& newLemmas,
179
                                              std::vector<Node>& newSkolems)
180
{
181
248867
  return preprocessLemmaInternal(node, newLemmas, newSkolems, true);
182
}
183
184
263350
TrustNode TheoryPreprocessor::preprocessLemmaInternal(
185
    TrustNode node,
186
    std::vector<TrustNode>& newLemmas,
187
    std::vector<Node>& newSkolems,
188
    bool procLemmas)
189
{
190
  // what was originally proven
191
526700
  Node lemma = node.getProven();
192
  TrustNode tplemma =
193
526700
      preprocessInternal(lemma, newLemmas, newSkolems, procLemmas);
194
263348
  if (tplemma.isNull())
195
  {
196
    // no change needed
197
184817
    return node;
198
  }
199
78531
  Assert(tplemma.getKind() == TrustNodeKind::REWRITE);
200
  // what it was preprocessed to
201
157062
  Node lemmap = tplemma.getNode();
202
78531
  Assert(lemmap != node.getProven());
203
  // process the preprocessing
204
78531
  if (isProofEnabled())
205
  {
206
3959
    Assert(d_lp != nullptr);
207
    // add the original proof to the lazy proof
208
7918
    d_lp->addLazyStep(
209
7918
        node.getProven(), node.getGenerator(), PfRule::THEORY_PREPROCESS_LEMMA);
210
    // only need to do anything if lemmap changed in a non-trivial way
211
3959
    if (!CDProof::isSame(lemmap, lemma))
212
    {
213
3915
      d_lp->addLazyStep(tplemma.getProven(),
214
                        tplemma.getGenerator(),
215
                        PfRule::THEORY_PREPROCESS,
216
                        true,
217
                        "TheoryEngine::lemma_pp");
218
      // ---------- from node -------------- from theory preprocess
219
      // lemma                lemma = lemmap
220
      // ------------------------------------------ EQ_RESOLVE
221
      // lemmap
222
7830
      std::vector<Node> pfChildren;
223
3915
      pfChildren.push_back(lemma);
224
3915
      pfChildren.push_back(tplemma.getProven());
225
3915
      d_lp->addStep(lemmap, PfRule::EQ_RESOLVE, pfChildren, {});
226
    }
227
  }
228
78531
  return TrustNode::mkTrustLemma(lemmap, d_lp.get());
229
}
230
231
2
RemoveTermFormulas& TheoryPreprocessor::getRemoveTermFormulas()
232
{
233
2
  return d_tfr;
234
}
235
236
6420316
struct preprocess_stack_element
237
{
238
  TNode node;
239
  bool children_added;
240
1460010
  preprocess_stack_element(TNode n) : node(n), children_added(false) {}
241
};
242
243
453781
TrustNode TheoryPreprocessor::theoryPreprocess(
244
    TNode assertion,
245
    std::vector<TrustNode>& newLemmas,
246
    std::vector<Node>& newSkolems)
247
{
248
907562
  Trace("theory::preprocess")
249
453781
      << "TheoryPreprocessor::theoryPreprocess(" << assertion << ")" << endl;
250
  // spendResource();
251
252
  // Do a topological sort of the subexpressions and substitute them
253
907562
  vector<preprocess_stack_element> toVisit;
254
453781
  toVisit.push_back(assertion);
255
256
5775637
  while (!toVisit.empty())
257
  {
258
    // The current node we are processing
259
2660937
    preprocess_stack_element& stackHead = toVisit.back();
260
4535145
    TNode current = stackHead.node;
261
262
5321874
    Trace("theory::preprocess-debug")
263
2660937
        << "TheoryPreprocessor::theoryPreprocess processing " << current
264
2660937
        << endl;
265
266
    // If node already in the cache we're done, pop from the stack
267
3148795
    if (d_rtfCache.find(current) != d_rtfCache.end())
268
    {
269
487858
      toVisit.pop_back();
270
487858
      continue;
271
    }
272
273
2173079
    TheoryId tid = Theory::theoryOf(current);
274
275
2173079
    if (!d_logicInfo.isTheoryEnabled(tid) && tid != THEORY_SAT_SOLVER)
276
    {
277
      stringstream ss;
278
      ss << "The logic was specified as " << d_logicInfo.getLogicString()
279
         << ", which doesn't include " << tid
280
         << ", but got a preprocessing-time fact for that theory." << endl
281
         << "The fact:" << endl
282
         << current;
283
      throw LogicException(ss.str());
284
    }
285
    // If this is an atom, we preprocess its terms with the theory ppRewriter
286
2173079
    if (tid != THEORY_BOOL)
287
    {
288
597760
      std::vector<SkolemLemma> lems;
289
597760
      Node ppRewritten = ppTheoryRewrite(current, lems);
290
299851
      for (const SkolemLemma& lem : lems)
291
      {
292
980
        newLemmas.push_back(lem.d_lemma);
293
980
        newSkolems.push_back(lem.d_skolem);
294
      }
295
298871
      Assert(Rewriter::rewrite(ppRewritten) == ppRewritten);
296
298871
      if (isProofEnabled() && ppRewritten != current)
297
      {
298
        TrustNode trn =
299
14
            TrustNode::mkTrustRewrite(current, ppRewritten, d_tpg.get());
300
7
        registerTrustedRewrite(trn, d_tpgRtf.get(), true);
301
      }
302
303
      // Term formula removal without fixed point. We do not need to do fixed
304
      // point since newLemmas are theory-preprocessed until fixed point in
305
      // preprocessInternal (at top-level, when procLemmas=true).
306
597742
      TrustNode ttfr = d_tfr.run(ppRewritten, newLemmas, newSkolems, false);
307
597742
      Node rtfNode = ppRewritten;
308
298871
      if (!ttfr.isNull())
309
      {
310
29302
        rtfNode = ttfr.getNode();
311
29302
        registerTrustedRewrite(ttfr, d_tpgRtf.get(), true);
312
      }
313
      // Finish the conversion by rewriting. Notice that we must consider this a
314
      // pre-rewrite since we do not recursively register the rewriting steps
315
      // of subterms of rtfNode. For example, if this step rewrites
316
      // (not A) ---> B, then if registered a pre-rewrite, it will apply when
317
      // reconstructing proofs via d_tpgRtf. However, if it is a post-rewrite
318
      // it will fail to apply if another call to this class registers A -> C,
319
      // in which case (not C) will be returned instead of B (see issue 6754).
320
597742
      Node retNode = rewriteWithProof(rtfNode, d_tpgRtf.get(), true);
321
298871
      d_rtfCache[current] = retNode;
322
298871
      continue;
323
    }
324
325
    // Not yet substituted, so process
326
1874199
    if (stackHead.children_added)
327
    {
328
      // Children have been processed, so substitute
329
1804122
      NodeBuilder builder(current.getKind());
330
902061
      if (current.getMetaKind() == kind::metakind::PARAMETERIZED)
331
      {
332
        builder << current.getOperator();
333
      }
334
4210613
      for (unsigned i = 0; i < current.getNumChildren(); ++i)
335
      {
336
3308552
        Assert(d_rtfCache.find(current[i]) != d_rtfCache.end());
337
3308552
        builder << d_rtfCache[current[i]].get();
338
      }
339
      // Mark the substitution and continue
340
1804122
      Node result = builder;
341
      // always rewrite here, since current may not be in rewritten form after
342
      // reconstruction
343
902061
      result = rewriteWithProof(result, d_tpgRtf.get(), false);
344
1804122
      Trace("theory::preprocess-debug")
345
902061
          << "TheoryPreprocessor::theoryPreprocess setting " << current
346
902061
          << " -> " << result << endl;
347
902061
      d_rtfCache[current] = result;
348
902061
      toVisit.pop_back();
349
    }
350
    else
351
    {
352
      // Mark that we have added the children if any
353
972138
      if (current.getNumChildren() > 0)
354
      {
355
902069
        stackHead.children_added = true;
356
        // We need to add the children
357
4210637
        for (TNode::iterator child_it = current.begin();
358
4210637
             child_it != current.end();
359
             ++child_it)
360
        {
361
6617136
          TNode childNode = *child_it;
362
3308568
          if (d_rtfCache.find(childNode) == d_rtfCache.end())
363
          {
364
1006229
            toVisit.push_back(childNode);
365
          }
366
        }
367
      }
368
      else
369
      {
370
        // No children, so we're done
371
140138
        Trace("theory::preprocess-debug")
372
70069
            << "SubstitutionMap::internalSubstitute setting " << current
373
70069
            << " -> " << current << endl;
374
70069
        d_rtfCache[current] = current;
375
70069
        toVisit.pop_back();
376
      }
377
    }
378
  }
379
453772
  Assert(d_rtfCache.find(assertion) != d_rtfCache.end());
380
  // Return the substituted version
381
907544
  Node res = d_rtfCache[assertion];
382
907544
  return TrustNode::mkTrustRewrite(assertion, res, d_tpg.get());
383
}
384
385
// Recursively traverse a term and call the theory rewriter on its sub-terms
386
1438192
Node TheoryPreprocessor::ppTheoryRewrite(TNode term,
387
                                         std::vector<SkolemLemma>& lems)
388
{
389
1438192
  NodeMap::iterator find = d_ppCache.find(term);
390
1438192
  if (find != d_ppCache.end())
391
  {
392
324906
    return (*find).second;
393
  }
394
1113286
  if (term.getNumChildren() == 0)
395
  {
396
589861
    return preprocessWithProof(term, lems);
397
  }
398
  // should be in rewritten form here
399
523427
  Assert(term == Rewriter::rewrite(term));
400
523427
  Trace("theory-pp") << "ppTheoryRewrite { " << term << endl;
401
  // do not rewrite inside quantifiers
402
1046854
  Node newTerm = term;
403
523427
  if (!term.isClosure())
404
  {
405
1023310
    NodeBuilder newNode(term.getKind());
406
511655
    if (term.getMetaKind() == kind::metakind::PARAMETERIZED)
407
    {
408
69114
      newNode << term.getOperator();
409
    }
410
1647938
    for (const Node& nt : term)
411
    {
412
1136283
      newNode << ppTheoryRewrite(nt, lems);
413
    }
414
511643
    newTerm = Node(newNode);
415
511643
    newTerm = rewriteWithProof(newTerm, d_tpg.get(), false);
416
  }
417
523415
  newTerm = preprocessWithProof(newTerm, lems);
418
523408
  d_ppCache[term] = newTerm;
419
523408
  Trace("theory-pp") << "ppTheoryRewrite returning " << newTerm << "}" << endl;
420
523408
  return newTerm;
421
}
422
423
2169385
Node TheoryPreprocessor::rewriteWithProof(Node term,
424
                                          TConvProofGenerator* pg,
425
                                          bool isPre)
426
{
427
2169385
  Node termr = Rewriter::rewrite(term);
428
  // store rewrite step if tracking proofs and it rewrites
429
2169385
  if (isProofEnabled())
430
  {
431
    // may rewrite the same term more than once, thus check hasRewriteStep
432
39128
    if (termr != term)
433
    {
434
7410
      Trace("tpp-debug") << "TheoryPreprocessor: addRewriteStep (rewriting) "
435
3705
                         << term << " -> " << termr << std::endl;
436
      // always use term context hash 0 (default)
437
3705
      pg->addRewriteStep(term, termr, PfRule::REWRITE, {}, {term}, isPre);
438
    }
439
  }
440
2169385
  return termr;
441
}
442
443
1113274
Node TheoryPreprocessor::preprocessWithProof(Node term,
444
                                             std::vector<SkolemLemma>& lems)
445
{
446
  // Important that it is in rewritten form, to ensure that the rewrite steps
447
  // recorded in d_tpg are functional. In other words, there should not
448
  // be steps from the same term to multiple rewritten forms, which would be
449
  // the case if we registered a preprocessing step for a non-rewritten term.
450
1113274
  Assert(term == Rewriter::rewrite(term));
451
2226548
  Trace("tpp-debug2") << "preprocessWithProof " << term
452
1113274
                      << ", #lems = " << lems.size() << std::endl;
453
  // We never call ppRewrite on equalities here, since equalities have a
454
  // special status. In particular, notice that theory preprocessing can be
455
  // called on all formulas asserted to theory engine, including those generated
456
  // as new literals appearing in lemmas. Calling ppRewrite on equalities is
457
  // incompatible with theory combination where a split on equality requested
458
  // by a theory could be preprocessed to something else, thus making theory
459
  // combination either non-terminating or result in solution soundness.
460
  // Notice that an alternative solution is to ensure that certain lemmas
461
  // (e.g. splits from theory combination) can never have theory preprocessing
462
  // applied to them. However, it is more uniform to say that theory
463
  // preprocessing is applied to all formulas. This makes it so that e.g.
464
  // theory solvers do not need to specify whether they want their lemmas to
465
  // be theory-preprocessed or not.
466
1113274
  if (term.getKind() == kind::EQUAL)
467
  {
468
161777
    return term;
469
  }
470
  // call ppRewrite for the given theory
471
1902994
  TrustNode trn = d_engine.theoryOf(term)->ppRewrite(term, lems);
472
1902976
  Trace("tpp-debug2") << "preprocessWithProof returned " << trn
473
951488
                      << ", #lems = " << lems.size() << std::endl;
474
951488
  if (trn.isNull())
475
  {
476
    // no change, return
477
948459
    return term;
478
  }
479
6058
  Node termr = trn.getNode();
480
3029
  Assert(term != termr);
481
3029
  if (isProofEnabled())
482
  {
483
15
    registerTrustedRewrite(trn, d_tpg.get(), false);
484
  }
485
  // Rewrite again here, which notice is a *pre* rewrite.
486
3029
  termr = rewriteWithProof(termr, d_tpg.get(), true);
487
3029
  return ppTheoryRewrite(termr, lems);
488
}
489
490
2684197
bool TheoryPreprocessor::isProofEnabled() const { return d_tpg != nullptr; }
491
492
29324
void TheoryPreprocessor::registerTrustedRewrite(TrustNode trn,
493
                                                TConvProofGenerator* pg,
494
                                                bool isPre)
495
{
496
29324
  if (!isProofEnabled() || trn.isNull())
497
  {
498
28575
    return;
499
  }
500
749
  Assert(trn.getKind() == TrustNodeKind::REWRITE);
501
1498
  Node eq = trn.getProven();
502
1498
  Node term = eq[0];
503
1498
  Node termr = eq[1];
504
749
  if (trn.getGenerator() != nullptr)
505
  {
506
1468
    Trace("tpp-debug") << "TheoryPreprocessor: addRewriteStep (generator) "
507
734
                       << term << " -> " << termr << std::endl;
508
734
    trn.debugCheckClosed("tpp-debug",
509
                         "TheoryPreprocessor::preprocessWithProof");
510
    // always use term context hash 0 (default)
511
734
    pg->addRewriteStep(
512
        term, termr, trn.getGenerator(), isPre, PfRule::ASSUME, true);
513
  }
514
  else
515
  {
516
30
    Trace("tpp-debug") << "TheoryPreprocessor: addRewriteStep (trusted) "
517
15
                       << term << " -> " << termr << std::endl;
518
    // small step trust
519
30
    pg->addRewriteStep(term,
520
                       termr,
521
                       PfRule::THEORY_PREPROCESS,
522
                       {},
523
                       {term.eqNode(termr)},
524
15
                       isPre);
525
  }
526
}
527
528
}  // namespace theory
529
22746
}  // namespace cvc5