GCC Code Coverage Report
Directory: . Exec Total Coverage
File: src/theory/theory_preprocessor.cpp Lines: 201 216 93.1 %
Date: 2021-05-22 Branches: 476 1054 45.2 %

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/lazy_proof.h"
19
#include "expr/skolem_manager.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
9495
TheoryPreprocessor::TheoryPreprocessor(TheoryEngine& engine,
31
                                       context::UserContext* userContext,
32
9495
                                       ProofNodeManager* pnm)
33
    : d_engine(engine),
34
9495
      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
3614
                      &d_iqtc)
45
                : nullptr),
46
      d_tpgRtf(pnm ? new TConvProofGenerator(pnm,
47
                                             userContext,
48
                                             TConvPolicy::FIXPOINT,
49
                                             TConvCachePolicy::NEVER,
50
                                             "TheoryPreprocessor::rtf",
51
3614
                                             &d_iqtc)
52
                   : nullptr),
53
      d_tpgRew(pnm ? new TConvProofGenerator(pnm,
54
                                             userContext,
55
                                             TConvPolicy::ONCE,
56
                                             TConvCachePolicy::NEVER,
57
3614
                                             "TheoryPreprocessor::pprew")
58
                   : nullptr),
59
      d_tspg(nullptr),
60
      d_lp(pnm ? new LazyCDProof(pnm,
61
                                 nullptr,
62
                                 userContext,
63
3614
                                 "TheoryPreprocessor::LazyCDProof")
64
33446
               : nullptr)
65
{
66
9495
  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
7228
    std::vector<ProofGenerator*> ts;
74
3614
    ts.push_back(d_tpgRew.get());
75
3614
    ts.push_back(d_tpgRtf.get());
76
7228
    d_tspg.reset(new TConvSeqProofGenerator(
77
3614
        pnm, ts, userContext, "TheoryPreprocessor::sequence"));
78
  }
79
9495
}
80
81
9495
TheoryPreprocessor::~TheoryPreprocessor() {}
82
83
265085
TrustNode TheoryPreprocessor::preprocess(TNode node,
84
                                         std::vector<TrustNode>& newLemmas,
85
                                         std::vector<Node>& newSkolems)
86
{
87
265085
  return preprocessInternal(node, newLemmas, newSkolems, true);
88
}
89
90
712428
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
712428
  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
1424856
  Node irNode = rewriteWithProof(node, d_tpgRew.get(), true);
106
107
  // run theory preprocessing
108
1424856
  TrustNode tpp = theoryPreprocess(irNode, newLemmas, newSkolems);
109
1424838
  Node ppNode = tpp.getNode();
110
111
712419
  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
712419
  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
682195
    size_t i = 0;
133
742643
    while (i < newLemmas.size())
134
    {
135
60448
      TrustNode cur = newLemmas[i];
136
30224
      newLemmas[i] = preprocessLemmaInternal(cur, newLemmas, newSkolems, false);
137
30224
      Trace("tpp") << "Final lemma : " << newLemmas[i].getProven() << std::endl;
138
30224
      i++;
139
    }
140
  }
141
142
712419
  if (node == ppNode)
143
  {
144
1085134
    Trace("tpp-debug") << "...TheoryPreprocessor::preprocess returned no change"
145
542567
                       << std::endl;
146
    // no change
147
542567
    return TrustNode::null();
148
  }
149
150
  // Now, sequence the conversion steps if proofs are enabled.
151
339704
  TrustNode tret;
152
169852
  if (isProofEnabled())
153
  {
154
151040
    std::vector<Node> cterms;
155
75520
    cterms.push_back(node);
156
75520
    cterms.push_back(irNode);
157
75520
    cterms.push_back(ppNode);
158
    // We have that:
159
    // node -> irNode via rewriting
160
    // irNode -> ppNode via theory-preprocessing + rewriting + tf removal
161
75520
    tret = d_tspg->mkTrustRewriteSequence(cterms);
162
75520
    tret.debugCheckClosed("tpp-debug", "TheoryPreprocessor::lemma_ret");
163
  }
164
  else
165
  {
166
94332
    tret = TrustNode::mkTrustRewrite(node, ppNode, nullptr);
167
  }
168
169
339704
  Trace("tpp-debug") << "...TheoryPreprocessor::preprocess returned "
170
169852
                     << tret.getNode() << std::endl;
171
339704
  Trace("tpp") << "TheoryPreprocessor::preprocess: return " << tret.getNode()
172
169852
               << ", procLemmas=" << procLemmas
173
169852
               << ", # lemmas = " << newLemmas.size() << std::endl;
174
169852
  return tret;
175
}
176
177
417119
TrustNode TheoryPreprocessor::preprocessLemma(TrustNode node,
178
                                              std::vector<TrustNode>& newLemmas,
179
                                              std::vector<Node>& newSkolems)
180
{
181
417119
  return preprocessLemmaInternal(node, newLemmas, newSkolems, true);
182
}
183
184
447343
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
894686
  Node lemma = node.getProven();
192
  TrustNode tplemma =
193
894686
      preprocessInternal(lemma, newLemmas, newSkolems, procLemmas);
194
447341
  if (tplemma.isNull())
195
  {
196
    // no change needed
197
310411
    return node;
198
  }
199
136930
  Assert(tplemma.getKind() == TrustNodeKind::REWRITE);
200
  // what it was preprocessed to
201
273860
  Node lemmap = tplemma.getNode();
202
136930
  Assert(lemmap != node.getProven());
203
  // process the preprocessing
204
136930
  if (isProofEnabled())
205
  {
206
58544
    Assert(d_lp != nullptr);
207
    // add the original proof to the lazy proof
208
117088
    d_lp->addLazyStep(
209
117088
        node.getProven(), node.getGenerator(), PfRule::THEORY_PREPROCESS_LEMMA);
210
    // only need to do anything if lemmap changed in a non-trivial way
211
58544
    if (!CDProof::isSame(lemmap, lemma))
212
    {
213
55131
      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
110262
      std::vector<Node> pfChildren;
223
55131
      pfChildren.push_back(lemma);
224
55131
      pfChildren.push_back(tplemma.getProven());
225
55131
      d_lp->addStep(lemmap, PfRule::EQ_RESOLVE, pfChildren, {});
226
    }
227
  }
228
136930
  return TrustNode::mkTrustLemma(lemmap, d_lp.get());
229
}
230
231
4
RemoveTermFormulas& TheoryPreprocessor::getRemoveTermFormulas()
232
{
233
4
  return d_tfr;
234
}
235
236
9871845
struct preprocess_stack_element
237
{
238
  TNode node;
239
  bool children_added;
240
2300707
  preprocess_stack_element(TNode n) : node(n), children_added(false) {}
241
};
242
243
712428
TrustNode TheoryPreprocessor::theoryPreprocess(
244
    TNode assertion,
245
    std::vector<TrustNode>& newLemmas,
246
    std::vector<Node>& newSkolems)
247
{
248
1424856
  Trace("theory::preprocess")
249
712428
      << "TheoryPreprocessor::theoryPreprocess(" << assertion << ")" << endl;
250
  // spendResource();
251
252
  // Do a topological sort of the subexpressions and substitute them
253
1424856
  vector<preprocess_stack_element> toVisit;
254
712428
  toVisit.push_back(assertion);
255
256
9230084
  while (!toVisit.empty())
257
  {
258
    // The current node we are processing
259
4258837
    preprocess_stack_element& stackHead = toVisit.back();
260
7172095
    TNode current = stackHead.node;
261
262
8517674
    Trace("theory::preprocess-debug")
263
4258837
        << "TheoryPreprocessor::theoryPreprocess processing " << current
264
4258837
        << endl;
265
266
    // If node already in the cache we're done, pop from the stack
267
5056659
    if (d_rtfCache.find(current) != d_rtfCache.end())
268
    {
269
797822
      toVisit.pop_back();
270
797822
      continue;
271
    }
272
273
3461015
    TheoryId tid = Theory::theoryOf(current);
274
275
3461015
    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
3461015
    if (tid != THEORY_BOOL)
287
    {
288
1095532
      std::vector<SkolemLemma> lems;
289
1095532
      Node ppRewritten = ppTheoryRewrite(current, lems);
290
549543
      for (const SkolemLemma& lem : lems)
291
      {
292
1786
        newLemmas.push_back(lem.d_lemma);
293
1786
        newSkolems.push_back(lem.d_skolem);
294
      }
295
547757
      Assert(Rewriter::rewrite(ppRewritten) == ppRewritten);
296
547757
      if (isProofEnabled() && ppRewritten != current)
297
      {
298
        TrustNode trn =
299
6724
            TrustNode::mkTrustRewrite(current, ppRewritten, d_tpg.get());
300
3362
        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
1095514
      TrustNode ttfr = d_tfr.run(ppRewritten, newLemmas, newSkolems, false);
307
1095514
      Node rtfNode = ppRewritten;
308
547757
      if (!ttfr.isNull())
309
      {
310
67103
        rtfNode = ttfr.getNode();
311
67103
        registerTrustedRewrite(ttfr, d_tpgRtf.get(), true);
312
      }
313
      // Finish the conversion by rewriting. This is registered as a
314
      // post-rewrite, since it is the last step applied for theory atoms.
315
1095514
      Node retNode = rewriteWithProof(rtfNode, d_tpgRtf.get(), false);
316
547757
      d_rtfCache[current] = retNode;
317
547757
      continue;
318
    }
319
320
    // Not yet substituted, so process
321
2913249
    if (stackHead.children_added)
322
    {
323
      // Children have been processed, so substitute
324
2820756
      NodeBuilder builder(current.getKind());
325
1410378
      if (current.getMetaKind() == kind::metakind::PARAMETERIZED)
326
      {
327
        builder << current.getOperator();
328
      }
329
6380664
      for (unsigned i = 0; i < current.getNumChildren(); ++i)
330
      {
331
4970286
        Assert(d_rtfCache.find(current[i]) != d_rtfCache.end());
332
4970286
        builder << d_rtfCache[current[i]].get();
333
      }
334
      // Mark the substitution and continue
335
2820756
      Node result = builder;
336
      // always rewrite here, since current may not be in rewritten form after
337
      // reconstruction
338
1410378
      result = rewriteWithProof(result, d_tpgRtf.get(), false);
339
2820756
      Trace("theory::preprocess-debug")
340
1410378
          << "TheoryPreprocessor::theoryPreprocess setting " << current
341
1410378
          << " -> " << result << endl;
342
1410378
      d_rtfCache[current] = result;
343
1410378
      toVisit.pop_back();
344
    }
345
    else
346
    {
347
      // Mark that we have added the children if any
348
1502871
      if (current.getNumChildren() > 0)
349
      {
350
1410387
        stackHead.children_added = true;
351
        // We need to add the children
352
6380690
        for (TNode::iterator child_it = current.begin();
353
6380690
             child_it != current.end();
354
             ++child_it)
355
        {
356
9940606
          TNode childNode = *child_it;
357
4970303
          if (d_rtfCache.find(childNode) == d_rtfCache.end())
358
          {
359
1588279
            toVisit.push_back(childNode);
360
          }
361
        }
362
      }
363
      else
364
      {
365
        // No children, so we're done
366
184968
        Trace("theory::preprocess-debug")
367
92484
            << "SubstitutionMap::internalSubstitute setting " << current
368
92484
            << " -> " << current << endl;
369
92484
        d_rtfCache[current] = current;
370
92484
        toVisit.pop_back();
371
      }
372
    }
373
  }
374
712419
  Assert(d_rtfCache.find(assertion) != d_rtfCache.end());
375
  // Return the substituted version
376
1424838
  Node res = d_rtfCache[assertion];
377
1424838
  return TrustNode::mkTrustRewrite(assertion, res, d_tpg.get());
378
}
379
380
// Recursively traverse a term and call the theory rewriter on its sub-terms
381
2542020
Node TheoryPreprocessor::ppTheoryRewrite(TNode term,
382
                                         std::vector<SkolemLemma>& lems)
383
{
384
2542020
  NodeMap::iterator find = d_ppCache.find(term);
385
2542020
  if (find != d_ppCache.end())
386
  {
387
565918
    return (*find).second;
388
  }
389
1976102
  if (term.getNumChildren() == 0)
390
  {
391
1020914
    return preprocessWithProof(term, lems);
392
  }
393
  // should be in rewritten form here
394
955190
  Assert(term == Rewriter::rewrite(term));
395
955190
  Trace("theory-pp") << "ppTheoryRewrite { " << term << endl;
396
  // do not rewrite inside quantifiers
397
1910380
  Node newTerm = term;
398
955190
  if (!term.isClosure())
399
  {
400
1855248
    NodeBuilder newNode(term.getKind());
401
927624
    if (term.getMetaKind() == kind::metakind::PARAMETERIZED)
402
    {
403
145757
      newNode << term.getOperator();
404
    }
405
2915830
    for (const Node& nt : term)
406
    {
407
1988206
      newNode << ppTheoryRewrite(nt, lems);
408
    }
409
927612
    newTerm = Node(newNode);
410
927612
    newTerm = rewriteWithProof(newTerm, d_tpg.get(), false);
411
  }
412
955178
  newTerm = preprocessWithProof(newTerm, lems);
413
955171
  d_ppCache[term] = newTerm;
414
955171
  Trace("theory-pp") << "ppTheoryRewrite returning " << newTerm << "}" << endl;
415
955171
  return newTerm;
416
}
417
418
3604223
Node TheoryPreprocessor::rewriteWithProof(Node term,
419
                                          TConvProofGenerator* pg,
420
                                          bool isPre)
421
{
422
3604223
  Node termr = Rewriter::rewrite(term);
423
  // store rewrite step if tracking proofs and it rewrites
424
3604223
  if (isProofEnabled())
425
  {
426
    // may rewrite the same term more than once, thus check hasRewriteStep
427
1611561
    if (termr != term)
428
    {
429
155350
      Trace("tpp-debug") << "TheoryPreprocessor: addRewriteStep (rewriting) "
430
77675
                         << term << " -> " << termr << std::endl;
431
      // always use term context hash 0 (default)
432
77675
      pg->addRewriteStep(term, termr, PfRule::REWRITE, {}, {term}, isPre);
433
    }
434
  }
435
3604223
  return termr;
436
}
437
438
1976090
Node TheoryPreprocessor::preprocessWithProof(Node term,
439
                                             std::vector<SkolemLemma>& lems)
440
{
441
  // Important that it is in rewritten form, to ensure that the rewrite steps
442
  // recorded in d_tpg are functional. In other words, there should not
443
  // be steps from the same term to multiple rewritten forms, which would be
444
  // the case if we registered a preprocessing step for a non-rewritten term.
445
1976090
  Assert(term == Rewriter::rewrite(term));
446
3952180
  Trace("tpp-debug2") << "preprocessWithProof " << term
447
1976090
                      << ", #lems = " << lems.size() << std::endl;
448
  // We never call ppRewrite on equalities here, since equalities have a
449
  // special status. In particular, notice that theory preprocessing can be
450
  // called on all formulas asserted to theory engine, including those generated
451
  // as new literals appearing in lemmas. Calling ppRewrite on equalities is
452
  // incompatible with theory combination where a split on equality requested
453
  // by a theory could be preprocessed to something else, thus making theory
454
  // combination either non-terminating or result in solution soundness.
455
  // Notice that an alternative solution is to ensure that certain lemmas
456
  // (e.g. splits from theory combination) can never have theory preprocessing
457
  // applied to them. However, it is more uniform to say that theory
458
  // preprocessing is applied to all formulas. This makes it so that e.g.
459
  // theory solvers do not need to specify whether they want their lemmas to
460
  // be theory-preprocessed or not.
461
1976090
  if (term.getKind() == kind::EQUAL)
462
  {
463
292908
    return term;
464
  }
465
  // call ppRewrite for the given theory
466
3366364
  TrustNode trn = d_engine.theoryOf(term)->ppRewrite(term, lems);
467
3366346
  Trace("tpp-debug2") << "preprocessWithProof returned " << trn
468
1683173
                      << ", #lems = " << lems.size() << std::endl;
469
1683173
  if (trn.isNull())
470
  {
471
    // no change, return
472
1677125
    return term;
473
  }
474
12096
  Node termr = trn.getNode();
475
6048
  Assert(term != termr);
476
6048
  if (isProofEnabled())
477
  {
478
3170
    registerTrustedRewrite(trn, d_tpg.get(), false);
479
  }
480
  // Rewrite again here, which notice is a *pre* rewrite.
481
6048
  termr = rewriteWithProof(termr, d_tpg.get(), true);
482
6048
  return ppTheoryRewrite(termr, lems);
483
}
484
485
4547940
bool TheoryPreprocessor::isProofEnabled() const { return d_tpg != nullptr; }
486
487
73635
void TheoryPreprocessor::registerTrustedRewrite(TrustNode trn,
488
                                                TConvProofGenerator* pg,
489
                                                bool isPre)
490
{
491
73635
  if (!isProofEnabled() || trn.isNull())
492
  {
493
31448
    return;
494
  }
495
42187
  Assert(trn.getKind() == TrustNodeKind::REWRITE);
496
84374
  Node eq = trn.getProven();
497
84374
  Node term = eq[0];
498
84374
  Node termr = eq[1];
499
42187
  if (trn.getGenerator() != nullptr)
500
  {
501
78038
    Trace("tpp-debug") << "TheoryPreprocessor: addRewriteStep (generator) "
502
39019
                       << term << " -> " << termr << std::endl;
503
39019
    trn.debugCheckClosed("tpp-debug",
504
                         "TheoryPreprocessor::preprocessWithProof");
505
    // always use term context hash 0 (default)
506
39019
    pg->addRewriteStep(
507
        term, termr, trn.getGenerator(), isPre, PfRule::ASSUME, true);
508
  }
509
  else
510
  {
511
6336
    Trace("tpp-debug") << "TheoryPreprocessor: addRewriteStep (trusted) "
512
3168
                       << term << " -> " << termr << std::endl;
513
    // small step trust
514
6336
    pg->addRewriteStep(term,
515
                       termr,
516
                       PfRule::THEORY_PREPROCESS,
517
                       {},
518
                       {term.eqNode(termr)},
519
3168
                       isPre);
520
  }
521
}
522
523
}  // namespace theory
524
28194
}  // namespace cvc5