GCC Code Coverage Report
Directory: . Exec Total Coverage
File: src/theory/theory_preprocessor.cpp Lines: 201 216 93.1 %
Date: 2021-09-18 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
10007
TheoryPreprocessor::TheoryPreprocessor(TheoryEngine& engine,
31
                                       context::UserContext* userContext,
32
10007
                                       ProofNodeManager* pnm)
33
    : d_engine(engine),
34
10007
      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
3810
                      &d_iqtc)
45
                : nullptr),
46
      d_tpgRtf(pnm ? new TConvProofGenerator(pnm,
47
                                             userContext,
48
                                             TConvPolicy::FIXPOINT,
49
                                             TConvCachePolicy::NEVER,
50
                                             "TheoryPreprocessor::rtf",
51
3810
                                             &d_iqtc)
52
                   : nullptr),
53
      d_tpgRew(pnm ? new TConvProofGenerator(pnm,
54
                                             userContext,
55
                                             TConvPolicy::ONCE,
56
                                             TConvCachePolicy::NEVER,
57
3810
                                             "TheoryPreprocessor::pprew")
58
                   : nullptr),
59
      d_tspg(nullptr),
60
      d_lp(pnm ? new LazyCDProof(pnm,
61
                                 nullptr,
62
                                 userContext,
63
3810
                                 "TheoryPreprocessor::LazyCDProof")
64
35254
               : nullptr)
65
{
66
10007
  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
7620
    std::vector<ProofGenerator*> ts;
74
3810
    ts.push_back(d_tpgRew.get());
75
3810
    ts.push_back(d_tpgRtf.get());
76
7620
    d_tspg.reset(new TConvSeqProofGenerator(
77
3810
        pnm, ts, userContext, "TheoryPreprocessor::sequence"));
78
  }
79
10007
}
80
81
10004
TheoryPreprocessor::~TheoryPreprocessor() {}
82
83
309996
TrustNode TheoryPreprocessor::preprocess(TNode node,
84
                                         std::vector<TrustNode>& newLemmas,
85
                                         std::vector<Node>& newSkolems)
86
{
87
309996
  return preprocessInternal(node, newLemmas, newSkolems, true);
88
}
89
90
783961
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
783961
  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
1567922
  Node irNode = rewriteWithProof(node, d_tpgRew.get(), true);
106
107
  // run theory preprocessing
108
1567922
  TrustNode tpp = theoryPreprocess(irNode, newLemmas, newSkolems);
109
1567904
  Node ppNode = tpp.getNode();
110
111
783952
  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
783952
  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
755830
    size_t i = 0;
133
812074
    while (i < newLemmas.size())
134
    {
135
56244
      TrustNode cur = newLemmas[i];
136
28122
      newLemmas[i] = preprocessLemmaInternal(cur, newLemmas, newSkolems, false);
137
28122
      Trace("tpp") << "Final lemma : " << newLemmas[i].getProven() << std::endl;
138
28122
      i++;
139
    }
140
  }
141
142
783952
  if (node == ppNode)
143
  {
144
1227798
    Trace("tpp-debug") << "...TheoryPreprocessor::preprocess returned no change"
145
613899
                       << std::endl;
146
    // no change
147
613899
    return TrustNode::null();
148
  }
149
150
  // Now, sequence the conversion steps if proofs are enabled.
151
340106
  TrustNode tret;
152
170053
  if (isProofEnabled())
153
  {
154
149932
    std::vector<Node> cterms;
155
74966
    cterms.push_back(node);
156
74966
    cterms.push_back(irNode);
157
74966
    cterms.push_back(ppNode);
158
    // We have that:
159
    // node -> irNode via rewriting
160
    // irNode -> ppNode via theory-preprocessing + rewriting + tf removal
161
74966
    tret = d_tspg->mkTrustRewriteSequence(cterms);
162
74966
    tret.debugCheckClosed("tpp-debug", "TheoryPreprocessor::lemma_ret");
163
  }
164
  else
165
  {
166
95087
    tret = TrustNode::mkTrustRewrite(node, ppNode, nullptr);
167
  }
168
169
340106
  Trace("tpp-debug") << "...TheoryPreprocessor::preprocess returned "
170
170053
                     << tret.getNode() << std::endl;
171
340106
  Trace("tpp") << "TheoryPreprocessor::preprocess: return " << tret.getNode()
172
170053
               << ", procLemmas=" << procLemmas
173
170053
               << ", # lemmas = " << newLemmas.size() << std::endl;
174
170053
  return tret;
175
}
176
177
445843
TrustNode TheoryPreprocessor::preprocessLemma(TrustNode node,
178
                                              std::vector<TrustNode>& newLemmas,
179
                                              std::vector<Node>& newSkolems)
180
{
181
445843
  return preprocessLemmaInternal(node, newLemmas, newSkolems, true);
182
}
183
184
473965
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
947930
  Node lemma = node.getProven();
192
  TrustNode tplemma =
193
947930
      preprocessInternal(lemma, newLemmas, newSkolems, procLemmas);
194
473963
  if (tplemma.isNull())
195
  {
196
    // no change needed
197
343136
    return node;
198
  }
199
130827
  Assert(tplemma.getKind() == TrustNodeKind::REWRITE);
200
  // what it was preprocessed to
201
261654
  Node lemmap = tplemma.getNode();
202
130827
  Assert(lemmap != node.getProven());
203
  // process the preprocessing
204
130827
  if (isProofEnabled())
205
  {
206
55876
    Assert(d_lp != nullptr);
207
    // add the original proof to the lazy proof
208
111752
    d_lp->addLazyStep(
209
111752
        node.getProven(), node.getGenerator(), PfRule::THEORY_PREPROCESS_LEMMA);
210
    // only need to do anything if lemmap changed in a non-trivial way
211
55876
    if (!CDProof::isSame(lemmap, lemma))
212
    {
213
50891
      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
101782
      std::vector<Node> pfChildren;
223
50891
      pfChildren.push_back(lemma);
224
50891
      pfChildren.push_back(tplemma.getProven());
225
50891
      d_lp->addStep(lemmap, PfRule::EQ_RESOLVE, pfChildren, {});
226
    }
227
  }
228
130827
  return TrustNode::mkTrustLemma(lemmap, d_lp.get());
229
}
230
231
2
RemoveTermFormulas& TheoryPreprocessor::getRemoveTermFormulas()
232
{
233
2
  return d_tfr;
234
}
235
236
11937329
struct preprocess_stack_element
237
{
238
  TNode node;
239
  bool children_added;
240
2796549
  preprocess_stack_element(TNode n) : node(n), children_added(false) {}
241
};
242
243
783961
TrustNode TheoryPreprocessor::theoryPreprocess(
244
    TNode assertion,
245
    std::vector<TrustNode>& newLemmas,
246
    std::vector<Node>& newSkolems)
247
{
248
1567922
  Trace("theory::preprocess")
249
783961
      << "TheoryPreprocessor::theoryPreprocess(" << assertion << ")" << endl;
250
  // spendResource();
251
252
  // Do a topological sort of the subexpressions and substitute them
253
1567922
  vector<preprocess_stack_element> toVisit;
254
783961
  toVisit.push_back(assertion);
255
256
11219667
  while (!toVisit.empty())
257
  {
258
    // The current node we are processing
259
5217862
    preprocess_stack_element& stackHead = toVisit.back();
260
8888856
    TNode current = stackHead.node;
261
262
10435724
    Trace("theory::preprocess-debug")
263
5217862
        << "TheoryPreprocessor::theoryPreprocess processing " << current
264
5217862
        << endl;
265
266
    // If node already in the cache we're done, pop from the stack
267
6134597
    if (d_rtfCache.find(current) != d_rtfCache.end())
268
    {
269
916735
      toVisit.pop_back();
270
916735
      continue;
271
    }
272
273
4301127
    TheoryId tid = Theory::theoryOf(current);
274
275
4301127
    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
4301127
    if (tid != THEORY_BOOL)
287
    {
288
1260284
      std::vector<SkolemLemma> lems;
289
1260284
      Node ppRewritten = ppTheoryRewrite(current, lems);
290
631932
      for (const SkolemLemma& lem : lems)
291
      {
292
1799
        newLemmas.push_back(lem.d_lemma);
293
1799
        newSkolems.push_back(lem.d_skolem);
294
      }
295
630133
      Assert(Rewriter::rewrite(ppRewritten) == ppRewritten);
296
630133
      if (isProofEnabled() && ppRewritten != current)
297
      {
298
        TrustNode trn =
299
7472
            TrustNode::mkTrustRewrite(current, ppRewritten, d_tpg.get());
300
3736
        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
1260266
      TrustNode ttfr = d_tfr.run(ppRewritten, newLemmas, newSkolems, false);
307
1260266
      Node rtfNode = ppRewritten;
308
630133
      if (!ttfr.isNull())
309
      {
310
63455
        rtfNode = ttfr.getNode();
311
63455
        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
1260266
      Node retNode = rewriteWithProof(rtfNode, d_tpgRtf.get(), true);
321
630133
      d_rtfCache[current] = retNode;
322
630133
      continue;
323
    }
324
325
    // Not yet substituted, so process
326
3670985
    if (stackHead.children_added)
327
    {
328
      // Children have been processed, so substitute
329
3582370
      NodeBuilder builder(current.getKind());
330
1791185
      if (current.getMetaKind() == kind::metakind::PARAMETERIZED)
331
      {
332
        builder << current.getOperator();
333
      }
334
7643804
      for (unsigned i = 0; i < current.getNumChildren(); ++i)
335
      {
336
5852619
        Assert(d_rtfCache.find(current[i]) != d_rtfCache.end());
337
5852619
        builder << d_rtfCache[current[i]].get();
338
      }
339
      // Mark the substitution and continue
340
3582370
      Node result = builder;
341
      // always rewrite here, since current may not be in rewritten form after
342
      // reconstruction
343
1791185
      result = rewriteWithProof(result, d_tpgRtf.get(), false);
344
3582370
      Trace("theory::preprocess-debug")
345
1791185
          << "TheoryPreprocessor::theoryPreprocess setting " << current
346
1791185
          << " -> " << result << endl;
347
1791185
      d_rtfCache[current] = result;
348
1791185
      toVisit.pop_back();
349
    }
350
    else
351
    {
352
      // Mark that we have added the children if any
353
1879800
      if (current.getNumChildren() > 0)
354
      {
355
1791194
        stackHead.children_added = true;
356
        // We need to add the children
357
7643830
        for (TNode::iterator child_it = current.begin();
358
7643830
             child_it != current.end();
359
             ++child_it)
360
        {
361
11705272
          TNode childNode = *child_it;
362
5852636
          if (d_rtfCache.find(childNode) == d_rtfCache.end())
363
          {
364
2012588
            toVisit.push_back(childNode);
365
          }
366
        }
367
      }
368
      else
369
      {
370
        // No children, so we're done
371
177212
        Trace("theory::preprocess-debug")
372
88606
            << "SubstitutionMap::internalSubstitute setting " << current
373
88606
            << " -> " << current << endl;
374
88606
        d_rtfCache[current] = current;
375
88606
        toVisit.pop_back();
376
      }
377
    }
378
  }
379
783952
  Assert(d_rtfCache.find(assertion) != d_rtfCache.end());
380
  // Return the substituted version
381
1567904
  Node res = d_rtfCache[assertion];
382
1567904
  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
2768237
Node TheoryPreprocessor::ppTheoryRewrite(TNode term,
387
                                         std::vector<SkolemLemma>& lems)
388
{
389
2768237
  NodeMap::iterator find = d_ppCache.find(term);
390
2768237
  if (find != d_ppCache.end())
391
  {
392
611952
    return (*find).second;
393
  }
394
2156285
  if (term.getNumChildren() == 0)
395
  {
396
1101464
    return preprocessWithProof(term, lems);
397
  }
398
  // should be in rewritten form here
399
1054823
  Assert(term == Rewriter::rewrite(term));
400
1054823
  Trace("theory-pp") << "ppTheoryRewrite { " << term << endl;
401
  // do not rewrite inside quantifiers
402
2109646
  Node newTerm = term;
403
1054823
  if (!term.isClosure())
404
  {
405
2054678
    NodeBuilder newNode(term.getKind());
406
1027339
    if (term.getMetaKind() == kind::metakind::PARAMETERIZED)
407
    {
408
203683
      newNode << term.getOperator();
409
    }
410
3158913
    for (const Node& nt : term)
411
    {
412
2131574
      newNode << ppTheoryRewrite(nt, lems);
413
    }
414
1027327
    newTerm = Node(newNode);
415
1027327
    newTerm = rewriteWithProof(newTerm, d_tpg.get(), false);
416
  }
417
1054811
  newTerm = preprocessWithProof(newTerm, lems);
418
1054804
  d_ppCache[term] = newTerm;
419
1054804
  Trace("theory-pp") << "ppTheoryRewrite returning " << newTerm << "}" << endl;
420
1054804
  return newTerm;
421
}
422
423
4239127
Node TheoryPreprocessor::rewriteWithProof(Node term,
424
                                          TConvProofGenerator* pg,
425
                                          bool isPre)
426
{
427
4239127
  Node termr = Rewriter::rewrite(term);
428
  // store rewrite step if tracking proofs and it rewrites
429
4239127
  if (isProofEnabled())
430
  {
431
    // may rewrite the same term more than once, thus check hasRewriteStep
432
2112977
    if (termr != term)
433
    {
434
149908
      Trace("tpp-debug") << "TheoryPreprocessor: addRewriteStep (rewriting) "
435
74954
                         << term << " -> " << termr << std::endl;
436
      // always use term context hash 0 (default)
437
74954
      pg->addRewriteStep(term, termr, PfRule::REWRITE, {}, {term}, isPre);
438
    }
439
  }
440
4239127
  return termr;
441
}
442
443
2156273
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
2156273
  Assert(term == Rewriter::rewrite(term));
451
4312546
  Trace("tpp-debug2") << "preprocessWithProof " << term
452
2156273
                      << ", #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
2156273
  if (term.getKind() == kind::EQUAL)
467
  {
468
311073
    return term;
469
  }
470
  // call ppRewrite for the given theory
471
3690400
  TrustNode trn = d_engine.theoryOf(term)->ppRewrite(term, lems);
472
3690382
  Trace("tpp-debug2") << "preprocessWithProof returned " << trn
473
1845191
                      << ", #lems = " << lems.size() << std::endl;
474
1845191
  if (trn.isNull())
475
  {
476
    // no change, return
477
1838670
    return term;
478
  }
479
13042
  Node termr = trn.getNode();
480
6521
  Assert(term != termr);
481
6521
  if (isProofEnabled())
482
  {
483
3495
    registerTrustedRewrite(trn, d_tpg.get(), false);
484
  }
485
  // Rewrite again here, which notice is a *pre* rewrite.
486
6521
  termr = rewriteWithProof(termr, d_tpg.get(), true);
487
6521
  return ppTheoryRewrite(termr, lems);
488
}
489
490
5257354
bool TheoryPreprocessor::isProofEnabled() const { return d_tpg != nullptr; }
491
492
70686
void TheoryPreprocessor::registerTrustedRewrite(TrustNode trn,
493
                                                TConvProofGenerator* pg,
494
                                                bool isPre)
495
{
496
70686
  if (!isProofEnabled() || trn.isNull())
497
  {
498
28557
    return;
499
  }
500
42129
  Assert(trn.getKind() == TrustNodeKind::REWRITE);
501
84258
  Node eq = trn.getProven();
502
84258
  Node term = eq[0];
503
84258
  Node termr = eq[1];
504
42129
  if (trn.getGenerator() != nullptr)
505
  {
506
77282
    Trace("tpp-debug") << "TheoryPreprocessor: addRewriteStep (generator) "
507
38641
                       << term << " -> " << termr << std::endl;
508
38641
    trn.debugCheckClosed("tpp-debug",
509
                         "TheoryPreprocessor::preprocessWithProof");
510
    // always use term context hash 0 (default)
511
38641
    pg->addRewriteStep(
512
        term, termr, trn.getGenerator(), isPre, PfRule::ASSUME, true);
513
  }
514
  else
515
  {
516
6976
    Trace("tpp-debug") << "TheoryPreprocessor: addRewriteStep (trusted) "
517
3488
                       << term << " -> " << termr << std::endl;
518
    // small step trust
519
6976
    pg->addRewriteStep(term,
520
                       termr,
521
                       PfRule::THEORY_PREPROCESS,
522
                       {},
523
                       {term.eqNode(termr)},
524
3488
                       isPre);
525
  }
526
}
527
528
}  // namespace theory
529
29574
}  // namespace cvc5