1 /********************* */
2 /*! \file theory_preprocessor.cpp
4 ** Top contributors (to current version):
5 ** Andrew Reynolds, Dejan Jovanovic, Morgan Deters
6 ** This file is part of the CVC4 project.
7 ** Copyright (c) 2009-2020 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.\endverbatim
12 ** \brief The theory preprocessor
15 #include "theory/theory_preprocessor.h"
17 #include "expr/lazy_proof.h"
18 #include "expr/skolem_manager.h"
19 #include "theory/logic_info.h"
20 #include "theory/rewriter.h"
21 #include "theory/theory_engine.h"
28 TheoryPreprocessor::TheoryPreprocessor(TheoryEngine
& engine
,
29 context::UserContext
* userContext
,
30 ProofNodeManager
* pnm
)
32 d_logicInfo(engine
.getLogicInfo()),
33 d_ppCache(userContext
),
34 d_tfr(userContext
, pnm
),
35 d_tpg(pnm
? new TConvProofGenerator(
38 TConvPolicy::FIXPOINT
,
39 TConvCachePolicy::NEVER
,
40 "TheoryPreprocessor::preprocess_rewrite",
44 d_tpgRew(pnm
? new TConvProofGenerator(pnm
,
46 TConvPolicy::FIXPOINT
,
47 TConvCachePolicy::NEVER
,
48 "TheoryPreprocessor::rewrite")
51 d_lp(pnm
? new LazyCDProof(pnm
,
54 "TheoryPreprocessor::LazyCDProof")
59 // Make the main term conversion sequence generator, which tracks up to
60 // three conversions made in succession:
61 // (1) theory preprocessing+rewriting
62 // (2) term formula removal
64 // Steps (1) and (3) use a common term conversion generator.
65 std::vector
<ProofGenerator
*> ts
;
66 ts
.push_back(d_tpg
.get());
67 ts
.push_back(d_tfr
.getTConvProofGenerator());
68 ts
.push_back(d_tpg
.get());
69 d_tspg
.reset(new TConvSeqProofGenerator(
70 pnm
, ts
, userContext
, "TheoryPreprocessor::sequence"));
71 // Make the "no preprocess" term conversion sequence generator, which
72 // applies only steps (2) and (3), where notice (3) must use the
73 // "pure rewrite" term conversion (d_tpgRew).
74 std::vector
<ProofGenerator
*> tsNoPp
;
75 tsNoPp
.push_back(d_tfr
.getTConvProofGenerator());
76 tsNoPp
.push_back(d_tpgRew
.get());
77 d_tspgNoPp
.reset(new TConvSeqProofGenerator(
78 pnm
, tsNoPp
, userContext
, "TheoryPreprocessor::sequence_no_pp"));
82 TheoryPreprocessor::~TheoryPreprocessor() {}
84 TrustNode
TheoryPreprocessor::preprocess(TNode node
,
85 std::vector
<TrustNode
>& newLemmas
,
86 std::vector
<Node
>& newSkolems
,
87 bool doTheoryPreprocess
,
90 // In this method, all rewriting steps of node are stored in d_tpg.
92 Trace("tpp-debug") << "TheoryPreprocessor::preprocess: start " << node
93 << ", doTheoryPreprocess=" << doTheoryPreprocess
95 // Run theory preprocessing, maybe
97 if (doTheoryPreprocess
)
99 // run theory preprocessing
100 TrustNode tpp
= theoryPreprocess(node
);
101 ppNode
= tpp
.getNode();
104 // Remove the ITEs, fixed point
105 TrustNode ttfr
= d_tfr
.run(ppNode
, newLemmas
, newSkolems
, fixedPoint
);
106 Node rtfNode
= ttfr
.isNull() ? ppNode
: ttfr
.getNode();
108 if (Debug
.isOn("lemma-ites"))
110 Debug("lemma-ites") << "removed ITEs from lemma: " << rtfNode
<< endl
;
111 Debug("lemma-ites") << " + now have the following " << newLemmas
.size()
112 << " lemma(s):" << endl
;
113 for (size_t i
= 0, lsize
= newLemmas
.size(); i
<= lsize
; ++i
)
115 Debug("lemma-ites") << " + " << newLemmas
[i
].getNode() << endl
;
117 Debug("lemma-ites") << endl
;
119 // Rewrite the main node, we rewrite and store the proof step, if necessary,
120 // in d_tpg, which maintains the fact that d_tpg can prove the rewrite.
121 Node retNode
= rewriteWithProof(rtfNode
);
123 if (Trace
.isOn("tpp-debug"))
127 Trace("tpp-debug") << "after preprocessing : " << ppNode
<< std::endl
;
129 if (rtfNode
!= ppNode
)
131 Trace("tpp-debug") << "after rtf : " << rtfNode
<< std::endl
;
133 if (retNode
!= rtfNode
)
135 Trace("tpp-debug") << "after rewriting : " << retNode
<< std::endl
;
137 Trace("tpp-debug") << "TheoryPreprocessor::preprocess: finish" << std::endl
;
141 Trace("tpp-debug") << "...TheoryPreprocessor::preprocess returned no change"
144 Assert(newLemmas
.empty());
145 return TrustNode::null();
148 // Now, sequence the conversion steps if proofs are enabled.
150 if (isProofEnabled())
152 std::vector
<Node
> cterms
;
153 cterms
.push_back(node
);
154 if (doTheoryPreprocess
)
156 cterms
.push_back(ppNode
);
158 cterms
.push_back(rtfNode
);
159 cterms
.push_back(retNode
);
161 // node -> ppNode via preprocessing + rewriting (if doTheoryPreprocess)
162 // ppNode -> rtfNode via term formula removal
163 // rtfNode -> retNode via rewriting
164 if (!doTheoryPreprocess
)
166 // If preprocessing is not performed, we cannot use the main sequence
167 // generator, instead we use d_tspgNoPp.
168 // We register the top-level rewrite in the pure rewrite term converter.
169 d_tpgRew
->addRewriteStep(
170 rtfNode
, retNode
, PfRule::REWRITE
, {}, {rtfNode
});
171 // Now get the trust node from the sequence generator
172 tret
= d_tspgNoPp
->mkTrustRewriteSequence(cterms
);
176 // we wil use the sequence generator
177 tret
= d_tspg
->mkTrustRewriteSequence(cterms
);
179 tret
.debugCheckClosed("tpp-debug", "TheoryPreprocessor::lemma_ret");
183 tret
= TrustNode::mkTrustRewrite(node
, retNode
, nullptr);
186 // now, rewrite the lemmas
187 Trace("tpp-debug") << "TheoryPreprocessor::preprocess: process lemmas"
189 for (size_t i
= 0, lsize
= newLemmas
.size(); i
< lsize
; ++i
)
191 // get the trust node to process
192 TrustNode trn
= newLemmas
[i
];
193 trn
.debugCheckClosed(
194 "tpp-debug", "TheoryPreprocessor::lemma_new_initial", false);
195 Assert(trn
.getKind() == TrustNodeKind::LEMMA
);
196 Node assertion
= trn
.getNode();
197 // rewrite, which is independent of d_tpg, since additional lemmas
198 // are justified separately.
199 Node rewritten
= Rewriter::rewrite(assertion
);
200 if (assertion
!= rewritten
)
202 if (isProofEnabled())
204 Assert(d_lp
!= nullptr);
205 // store in the lazy proof
206 d_lp
->addLazyStep(assertion
,
208 PfRule::THEORY_PREPROCESS_LEMMA
,
210 "TheoryPreprocessor::rewrite_lemma_new");
211 d_lp
->addStep(rewritten
,
212 PfRule::MACRO_SR_PRED_TRANSFORM
,
216 newLemmas
[i
] = TrustNode::mkTrustLemma(rewritten
, d_lp
.get());
218 Assert(!isProofEnabled() || newLemmas
[i
].getGenerator() != nullptr);
219 newLemmas
[i
].debugCheckClosed("tpp-debug", "TheoryPreprocessor::lemma_new");
221 Trace("tpp-debug") << "...TheoryPreprocessor::preprocess returned "
222 << tret
.getNode() << std::endl
;
226 TrustNode
TheoryPreprocessor::preprocess(TNode node
, bool doTheoryPreprocess
)
228 // ignore lemmas, no fixed point
229 std::vector
<TrustNode
> newLemmas
;
230 std::vector
<Node
> newSkolems
;
231 return preprocess(node
, newLemmas
, newSkolems
, doTheoryPreprocess
, false);
234 TrustNode
TheoryPreprocessor::preprocessLemma(TrustNode node
,
235 std::vector
<TrustNode
>& newLemmas
,
236 std::vector
<Node
>& newSkolems
,
237 bool doTheoryPreprocess
,
240 // what was originally proven
241 Node lemma
= node
.getProven();
243 preprocess(lemma
, newLemmas
, newSkolems
, doTheoryPreprocess
, fixedPoint
);
244 if (tplemma
.isNull())
249 Assert(tplemma
.getKind() == TrustNodeKind::REWRITE
);
250 // what it was preprocessed to
251 Node lemmap
= tplemma
.getNode();
252 Assert(lemmap
!= node
.getProven());
253 // process the preprocessing
254 if (isProofEnabled())
256 Assert(d_lp
!= nullptr);
257 // add the original proof to the lazy proof
258 d_lp
->addLazyStep(node
.getProven(), node
.getGenerator());
259 // only need to do anything if lemmap changed in a non-trivial way
260 if (!CDProof::isSame(lemmap
, lemma
))
262 d_lp
->addLazyStep(tplemma
.getProven(),
263 tplemma
.getGenerator(),
264 PfRule::PREPROCESS_LEMMA
,
266 "TheoryEngine::lemma_pp");
267 // ---------- from node -------------- from theory preprocess
268 // lemma lemma = lemmap
269 // ------------------------------------------ EQ_RESOLVE
271 std::vector
<Node
> pfChildren
;
272 pfChildren
.push_back(lemma
);
273 pfChildren
.push_back(tplemma
.getProven());
274 d_lp
->addStep(lemmap
, PfRule::EQ_RESOLVE
, pfChildren
, {});
277 return TrustNode::mkTrustLemma(lemmap
, d_lp
.get());
280 TrustNode
TheoryPreprocessor::preprocessLemma(TrustNode node
,
281 bool doTheoryPreprocess
)
283 // ignore lemmas, no fixed point
284 std::vector
<TrustNode
> newLemmas
;
285 std::vector
<Node
> newSkolems
;
286 return preprocessLemma(
287 node
, newLemmas
, newSkolems
, doTheoryPreprocess
, false);
290 RemoveTermFormulas
& TheoryPreprocessor::getRemoveTermFormulas()
295 struct preprocess_stack_element
299 preprocess_stack_element(TNode n
) : node(n
), children_added(false) {}
302 TrustNode
TheoryPreprocessor::theoryPreprocess(TNode assertion
)
304 Trace("theory::preprocess")
305 << "TheoryPreprocessor::theoryPreprocess(" << assertion
<< ")" << endl
;
308 // Do a topological sort of the subexpressions and substitute them
309 vector
<preprocess_stack_element
> toVisit
;
310 toVisit
.push_back(assertion
);
312 while (!toVisit
.empty())
314 // The current node we are processing
315 preprocess_stack_element
& stackHead
= toVisit
.back();
316 TNode current
= stackHead
.node
;
318 Trace("theory::preprocess-debug")
319 << "TheoryPreprocessor::theoryPreprocess(" << assertion
320 << "): processing " << current
<< endl
;
322 // If node already in the cache we're done, pop from the stack
323 NodeMap::iterator find
= d_ppCache
.find(current
);
324 if (find
!= d_ppCache
.end())
330 if (!d_logicInfo
.isTheoryEnabled(Theory::theoryOf(current
))
331 && Theory::theoryOf(current
) != THEORY_SAT_SOLVER
)
334 ss
<< "The logic was specified as " << d_logicInfo
.getLogicString()
335 << ", which doesn't include " << Theory::theoryOf(current
)
336 << ", but got a preprocessing-time fact for that theory." << endl
337 << "The fact:" << endl
339 throw LogicException(ss
.str());
342 // If this is an atom, we preprocess its terms with the theory ppRewriter
343 if (Theory::theoryOf(current
) != THEORY_BOOL
)
345 Node ppRewritten
= ppTheoryRewrite(current
);
346 d_ppCache
[current
] = ppRewritten
;
347 Assert(Rewriter::rewrite(d_ppCache
[current
].get())
348 == d_ppCache
[current
].get());
352 // Not yet substituted, so process
353 if (stackHead
.children_added
)
355 // Children have been processed, so substitute
356 NodeBuilder
<> builder(current
.getKind());
357 if (current
.getMetaKind() == kind::metakind::PARAMETERIZED
)
359 builder
<< current
.getOperator();
361 for (unsigned i
= 0; i
< current
.getNumChildren(); ++i
)
363 Assert(d_ppCache
.find(current
[i
]) != d_ppCache
.end());
364 builder
<< d_ppCache
[current
[i
]].get();
366 // Mark the substitution and continue
367 Node result
= builder
;
368 if (result
!= current
)
370 result
= rewriteWithProof(result
);
372 Trace("theory::preprocess-debug")
373 << "TheoryPreprocessor::theoryPreprocess(" << assertion
374 << "): setting " << current
<< " -> " << result
<< endl
;
375 d_ppCache
[current
] = result
;
380 // Mark that we have added the children if any
381 if (current
.getNumChildren() > 0)
383 stackHead
.children_added
= true;
384 // We need to add the children
385 for (TNode::iterator child_it
= current
.begin();
386 child_it
!= current
.end();
389 TNode childNode
= *child_it
;
390 NodeMap::iterator childFind
= d_ppCache
.find(childNode
);
391 if (childFind
== d_ppCache
.end())
393 toVisit
.push_back(childNode
);
399 // No children, so we're done
400 Trace("theory::preprocess-debug")
401 << "SubstitutionMap::internalSubstitute(" << assertion
402 << "): setting " << current
<< " -> " << current
<< endl
;
403 d_ppCache
[current
] = current
;
408 // Return the substituted version
409 Node res
= d_ppCache
[assertion
];
410 return TrustNode::mkTrustRewrite(assertion
, res
, d_tpg
.get());
413 // Recursively traverse a term and call the theory rewriter on its sub-terms
414 Node
TheoryPreprocessor::ppTheoryRewrite(TNode term
)
416 NodeMap::iterator find
= d_ppCache
.find(term
);
417 if (find
!= d_ppCache
.end())
419 return (*find
).second
;
421 if (term
.getNumChildren() == 0)
423 return preprocessWithProof(term
);
425 Trace("theory-pp") << "ppTheoryRewrite { " << term
<< endl
;
426 // We must rewrite before preprocessing, because some terms when rewritten
427 // may introduce new terms that are not top-level and require preprocessing.
428 // An example of this is (forall ((x Int)) (and (tail L) (P x))) which
429 // rewrites to (and (tail L) (forall ((x Int)) (P x))). The subterm (tail L)
430 // must be preprocessed as a child here.
431 Node newTerm
= rewriteWithProof(term
);
432 // do not rewrite inside quantifiers
433 if (newTerm
.getNumChildren() > 0 && !newTerm
.isClosure())
435 NodeBuilder
<> newNode(newTerm
.getKind());
436 if (newTerm
.getMetaKind() == kind::metakind::PARAMETERIZED
)
438 newNode
<< newTerm
.getOperator();
440 for (const Node
& nt
: newTerm
)
442 newNode
<< ppTheoryRewrite(nt
);
444 newTerm
= Node(newNode
);
445 newTerm
= rewriteWithProof(newTerm
);
447 newTerm
= preprocessWithProof(newTerm
);
448 d_ppCache
[term
] = newTerm
;
449 Trace("theory-pp") << "ppTheoryRewrite returning " << newTerm
<< "}" << endl
;
453 Node
TheoryPreprocessor::rewriteWithProof(Node term
)
455 Node termr
= Rewriter::rewrite(term
);
456 // store rewrite step if tracking proofs and it rewrites
457 if (isProofEnabled())
459 // may rewrite the same term more than once, thus check hasRewriteStep
462 Trace("tpp-debug") << "TheoryPreprocessor: addRewriteStep (rewriting) "
463 << term
<< " -> " << termr
<< std::endl
;
464 // always use term context hash 0 (default)
465 d_tpg
->addRewriteStep(term
, termr
, PfRule::REWRITE
, {}, {term
});
471 Node
TheoryPreprocessor::preprocessWithProof(Node term
)
473 // Important that it is in rewritten form, to ensure that the rewrite steps
474 // recorded in d_tpg are functional. In other words, there should not
475 // be steps from the same term to multiple rewritten forms, which would be
476 // the case if we registered a preprocessing step for a non-rewritten term.
477 Assert(term
== Rewriter::rewrite(term
));
478 // We never call ppRewrite on equalities here, since equalities have a
479 // special status. In particular, notice that theory preprocessing can be
480 // called on all formulas asserted to theory engine, including those generated
481 // as new literals appearing in lemmas. Calling ppRewrite on equalities is
482 // incompatible with theory combination where a split on equality requested
483 // by a theory could be preprocessed to something else, thus making theory
484 // combination either non-terminating or result in solution soundness.
485 // Notice that an alternative solution is to ensure that certain lemmas
486 // (e.g. splits from theory combination) can never have theory preprocessing
487 // applied to them. However, it is more uniform to say that theory
488 // preprocessing is applied to all formulas. This makes it so that e.g.
489 // theory solvers do not need to specify whether they want their lemmas to
490 // be theory-preprocessed or not.
491 if (term
.getKind() == kind::EQUAL
)
495 // call ppRewrite for the given theory
496 TrustNode trn
= d_engine
.theoryOf(term
)->ppRewrite(term
);
499 // no change, return original term
502 Node termr
= trn
.getNode();
503 Assert(term
!= termr
);
504 if (isProofEnabled())
506 if (trn
.getGenerator() != nullptr)
508 Trace("tpp-debug") << "TheoryPreprocessor: addRewriteStep (generator) "
509 << term
<< " -> " << termr
<< std::endl
;
510 trn
.debugCheckClosed("tpp-debug",
511 "TheoryPreprocessor::preprocessWithProof");
512 // always use term context hash 0 (default)
513 d_tpg
->addRewriteStep(
514 term
, termr
, trn
.getGenerator(), PfRule::ASSUME
, true);
518 Trace("tpp-debug") << "TheoryPreprocessor: addRewriteStep (trusted) "
519 << term
<< " -> " << termr
<< std::endl
;
521 d_tpg
->addRewriteStep(
522 term
, termr
, PfRule::THEORY_PREPROCESS
, {}, {term
.eqNode(termr
)});
525 termr
= rewriteWithProof(termr
);
526 return ppTheoryRewrite(termr
);
529 bool TheoryPreprocessor::isProofEnabled() const { return d_tpg
!= nullptr; }
531 } // namespace theory