1 /******************************************************************************
2 * Top contributors (to current version):
3 * Andrew Reynolds, Tim King, Morgan Deters
5 * This file is part of the cvc5 project.
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 * ****************************************************************************
13 * Implementation of instantiate.
16 #include "theory/quantifiers/instantiate.h"
18 #include "expr/node_algorithm.h"
19 #include "options/base_options.h"
20 #include "options/outputc.h"
21 #include "options/printer_options.h"
22 #include "options/quantifiers_options.h"
23 #include "options/smt_options.h"
24 #include "proof/lazy_proof.h"
25 #include "proof/proof_node_manager.h"
26 #include "smt/logic_exception.h"
27 #include "smt/smt_engine.h"
28 #include "smt/smt_engine_scope.h"
29 #include "smt/smt_statistics_registry.h"
30 #include "theory/quantifiers/cegqi/inst_strategy_cegqi.h"
31 #include "theory/quantifiers/first_order_model.h"
32 #include "theory/quantifiers/quantifiers_attributes.h"
33 #include "theory/quantifiers/quantifiers_rewriter.h"
34 #include "theory/quantifiers/term_database.h"
35 #include "theory/quantifiers/term_enumeration.h"
36 #include "theory/quantifiers/term_registry.h"
37 #include "theory/quantifiers/term_util.h"
38 #include "theory/rewriter.h"
40 using namespace cvc5::kind
;
41 using namespace cvc5::context
;
45 namespace quantifiers
{
47 Instantiate::Instantiate(QuantifiersState
& qs
,
48 QuantifiersInferenceManager
& qim
,
49 QuantifiersRegistry
& qr
,
51 ProofNodeManager
* pnm
)
57 d_insts(qs
.getUserContext()),
58 d_c_inst_match_trie_dom(qs
.getUserContext()),
60 pnm
? new CDProof(pnm
, qs
.getUserContext(), "Instantiate::pfInst")
65 Instantiate::~Instantiate()
67 for (std::pair
<const Node
, CDInstMatchTrie
*>& t
: d_c_inst_match_trie
)
71 d_c_inst_match_trie
.clear();
74 bool Instantiate::reset(Theory::Effort e
)
76 Trace("inst-debug") << "Reset, effort " << e
<< std::endl
;
77 // clear explicitly recorded instantiations
78 d_recordedInst
.clear();
79 d_instDebugTemp
.clear();
83 void Instantiate::registerQuantifier(Node q
) {}
84 bool Instantiate::checkComplete(IncompleteId
& incId
)
86 if (!d_recordedInst
.empty())
88 Trace("quant-engine-debug")
89 << "Set incomplete due to recorded instantiations." << std::endl
;
90 incId
= IncompleteId::QUANTIFIERS_RECORDED_INST
;
96 void Instantiate::addRewriter(InstantiationRewriter
* ir
)
98 d_instRewrite
.push_back(ir
);
101 bool Instantiate::addInstantiation(Node q
,
102 std::vector
<Node
>& terms
,
108 // For resource-limiting (also does a time check).
109 d_qim
.safePoint(Resource::QuantifierStep
);
110 Assert(!d_qstate
.isInConflict());
111 Assert(terms
.size() == q
[0].getNumChildren());
112 Trace("inst-add-debug") << "For quantified formula " << q
113 << ", add instantiation: " << std::endl
;
114 for (unsigned i
= 0, size
= terms
.size(); i
< size
; i
++)
116 Trace("inst-add-debug") << " " << q
[0][i
];
117 Trace("inst-add-debug2") << " -> " << terms
[i
];
118 TypeNode tn
= q
[0][i
].getType();
119 if (terms
[i
].isNull())
121 terms
[i
] = d_treg
.getTermForType(tn
);
123 // Ensure the type is correct, this for instance ensures that real terms
124 // are cast to integers for { x -> t } where x has type Int and t has
126 terms
[i
] = ensureType(terms
[i
], tn
);
129 // pick the best possible representative for instantiation, based on past
130 // use and simplicity of term
131 terms
[i
] = d_treg
.getModel()->getInternalRepresentative(terms
[i
], q
, i
);
133 Trace("inst-add-debug") << " -> " << terms
[i
] << std::endl
;
134 if (terms
[i
].isNull())
136 Trace("inst-add-debug")
137 << " --> Failed to make term vector, due to term/type restrictions."
141 #ifdef CVC5_ASSERTIONS
142 bool bad_inst
= false;
143 if (TermUtil::containsUninterpretedConstant(terms
[i
]))
145 Trace("inst") << "***& inst contains uninterpreted constant : "
146 << terms
[i
] << std::endl
;
149 else if (!terms
[i
].getType().isSubtypeOf(q
[0][i
].getType()))
151 Trace("inst") << "***& inst bad type : " << terms
[i
] << " "
152 << terms
[i
].getType() << "/" << q
[0][i
].getType()
156 else if (options::cegqi())
158 Node icf
= TermUtil::getInstConstAttr(terms
[i
]);
163 Trace("inst") << "***& inst contains inst constant attr : "
164 << terms
[i
] << std::endl
;
167 else if (expr::hasSubterm(terms
[i
], d_qreg
.d_inst_constants
[q
]))
169 Trace("inst") << "***& inst contains inst constants : " << terms
[i
]
175 // this assertion is critical to soundness
178 Trace("inst") << "***& Bad Instantiate " << q
<< " with " << std::endl
;
179 for (unsigned j
= 0; j
< terms
.size(); j
++)
181 Trace("inst") << " " << terms
[j
] << std::endl
;
188 TermDb
* tdb
= d_treg
.getTermDatabase();
189 // Note we check for entailment before checking for term vector duplication.
190 // Although checking for term vector duplication is a faster check, it is
191 // included automatically with recordInstantiationInternal, hence we prefer
192 // two checks instead of three. In experiments, it is 1% slower or so to call
193 // existsInstantiation here.
194 // Alternatively, we could return an (index, trie node) in the call to
195 // existsInstantiation here, where this would return the node in the trie
196 // where we determined that there is definitely no duplication, and then
197 // continue from that point in recordInstantiation below. However, for
198 // simplicity, we do not pursue this option (as it would likely only
199 // lead to very small gains).
201 // check for positive entailment
202 if (options::instNoEntail())
204 // should check consistency of equality engine
205 // (if not aborting on utility's reset)
206 std::map
<TNode
, TNode
> subs
;
207 for (unsigned i
= 0, size
= terms
.size(); i
< size
; i
++)
209 subs
[q
[0][i
]] = terms
[i
];
211 if (tdb
->isEntailed(q
[1], subs
, false, true))
213 Trace("inst-add-debug") << " --> Currently entailed." << std::endl
;
214 ++(d_statistics
.d_inst_duplicate_ent
);
219 // check based on instantiation level
220 if (options::instMaxLevel() != -1)
222 for (Node
& t
: terms
)
224 if (!tdb
->isTermEligibleForInstantiation(t
, q
))
231 // record the instantiation
232 bool recorded
= recordInstantiationInternal(q
, terms
);
235 Trace("inst-add-debug") << " --> Already exists (no record)." << std::endl
;
236 ++(d_statistics
.d_inst_duplicate_eq
);
240 // Set up a proof if proofs are enabled. This proof stores a proof of
241 // the instantiation body with q as a free assumption.
242 std::shared_ptr
<LazyCDProof
> pfTmp
;
243 if (isProofEnabled())
245 pfTmp
.reset(new LazyCDProof(
246 d_pnm
, nullptr, nullptr, "Instantiate::LazyCDProof::tmp"));
249 // construct the instantiation
250 Trace("inst-add-debug") << "Constructing instantiation..." << std::endl
;
251 Assert(d_qreg
.d_vars
[q
].size() == terms
.size());
252 // get the instantiation
253 Node body
= getInstantiation(
254 q
, d_qreg
.d_vars
[q
], terms
, id
, pfArg
, doVts
, pfTmp
.get());
255 Node orig_body
= body
;
256 // now preprocess, storing the trust node for the rewrite
257 TrustNode tpBody
= QuantifiersRewriter::preprocess(body
, true);
258 if (!tpBody
.isNull())
260 Assert(tpBody
.getKind() == TrustNodeKind::REWRITE
);
261 body
= tpBody
.getNode();
262 // do a tranformation step
263 if (pfTmp
!= nullptr)
265 // ----------------- from preprocess
266 // orig_body orig_body = body
267 // ------------------------------ EQ_RESOLVE
269 Node proven
= tpBody
.getProven();
270 // add the transformation proof, or the trusted rule if none provided
271 pfTmp
->addLazyStep(proven
,
272 tpBody
.getGenerator(),
273 PfRule::QUANTIFIERS_PREPROCESS
,
275 "Instantiate::getInstantiation:qpreprocess");
276 pfTmp
->addStep(body
, PfRule::EQ_RESOLVE
, {orig_body
, proven
}, {});
279 Trace("inst-debug") << "...preprocess to " << body
<< std::endl
;
281 // construct the lemma
282 Trace("inst-assert") << "(assert " << body
<< ")" << std::endl
;
284 // construct the instantiation, and rewrite the lemma
285 Node lem
= NodeManager::currentNM()->mkNode(kind::IMPLIES
, q
, body
);
287 // If proofs are enabled, construct the proof, which is of the form:
288 // ... free assumption q ...
289 // ------------------------- from pfTmp
291 // ------------------------- SCOPE
293 // -------------------------- MACRO_SR_PRED_ELIM
295 bool hasProof
= false;
296 if (isProofEnabled())
298 // make the proof of body
299 std::shared_ptr
<ProofNode
> pfn
= pfTmp
->getProofFor(body
);
300 // make the scope proof to get (=> q body)
301 std::vector
<Node
> assumps
;
302 assumps
.push_back(q
);
303 std::shared_ptr
<ProofNode
> pfns
= d_pnm
->mkScope({pfn
}, assumps
);
304 Assert(assumps
.size() == 1 && assumps
[0] == q
);
305 // store in the main proof
306 d_pfInst
->addProof(pfns
);
308 lem
= Rewriter::rewrite(lem
);
311 d_pfInst
->addStep(lem
, PfRule::MACRO_SR_PRED_ELIM
, {prevLem
}, {});
317 lem
= Rewriter::rewrite(lem
);
320 // added lemma, which checks for lemma duplication
321 bool addedLem
= false;
324 // use proof generator
326 d_qim
.addPendingLemma(lem
, id
, LemmaProperty::NONE
, d_pfInst
.get());
330 addedLem
= d_qim
.addPendingLemma(lem
, id
);
335 Trace("inst-add-debug") << " --> Lemma already exists." << std::endl
;
336 ++(d_statistics
.d_inst_duplicate
);
340 // add to list of instantiations
341 InstLemmaList
* ill
= getOrMkInstLemmaList(q
);
342 ill
->d_list
.push_back(body
);
343 // add to temporary debug statistics (# inst on this round)
344 d_instDebugTemp
[q
]++;
345 if (Trace
.isOn("inst"))
347 Trace("inst") << "*** Instantiate " << q
<< " with " << std::endl
;
348 for (unsigned i
= 0, size
= terms
.size(); i
< size
; i
++)
350 if (Trace
.isOn("inst"))
352 Trace("inst") << " " << terms
[i
];
353 if (Trace
.isOn("inst-debug"))
355 Trace("inst-debug") << ", type=" << terms
[i
].getType()
356 << ", var_type=" << q
[0][i
].getType();
358 Trace("inst") << std::endl
;
362 if (options::instMaxLevel() != -1)
366 // virtual term substitution/instantiation level features are
368 std::stringstream ss
;
369 ss
<< "Cannot combine instantiation strategies that require virtual term "
370 "substitution with those that restrict instantiation levels";
371 throw LogicException(ss
.str());
375 uint64_t maxInstLevel
= 0;
376 for (const Node
& tc
: terms
)
378 if (tc
.hasAttribute(InstLevelAttribute())
379 && tc
.getAttribute(InstLevelAttribute()) > maxInstLevel
)
381 maxInstLevel
= tc
.getAttribute(InstLevelAttribute());
384 QuantAttributes::setInstantiationLevelAttr(
385 orig_body
, q
[1], maxInstLevel
+ 1);
388 d_treg
.processInstantiation(q
, terms
);
389 Trace("inst-add-debug") << " --> Success." << std::endl
;
390 ++(d_statistics
.d_instantiations
);
394 bool Instantiate::addInstantiationExpFail(Node q
,
395 std::vector
<Node
>& terms
,
396 std::vector
<bool>& failMask
,
403 if (addInstantiation(q
, terms
, id
, pfArg
, mkRep
, doVts
))
407 size_t tsize
= terms
.size();
408 failMask
.resize(tsize
, true);
411 // will never succeed with 1 variable
414 TermDb
* tdb
= d_treg
.getTermDatabase();
415 Trace("inst-exp-fail") << "Explain inst failure..." << terms
<< std::endl
;
416 // set up information for below
417 std::vector
<Node
>& vars
= d_qreg
.d_vars
[q
];
418 Assert(tsize
== vars
.size());
419 std::map
<TNode
, TNode
> subs
;
420 for (size_t i
= 0; i
< tsize
; i
++)
422 subs
[vars
[i
]] = terms
[i
];
424 // get the instantiation body
425 InferenceId idNone
= InferenceId::UNKNOWN
;
427 Node ibody
= getInstantiation(q
, vars
, terms
, idNone
, nulln
, doVts
);
428 ibody
= Rewriter::rewrite(ibody
);
429 for (size_t i
= 0; i
< tsize
; i
++)
431 // process consecutively in reverse order, which is important since we use
432 // the fail mask for incrementing in a lexicographic order
433 size_t ii
= (tsize
- 1) - i
;
434 // replace with the identity substitution
435 Node prev
= terms
[ii
];
436 terms
[ii
] = vars
[ii
];
437 subs
.erase(vars
[ii
]);
440 // will never succeed with empty substitution
443 Trace("inst-exp-fail") << "- revert " << ii
<< std::endl
;
444 // check whether we are still redundant
445 bool success
= false;
446 // check entailment, only if option is set
447 if (options::instNoEntail())
449 Trace("inst-exp-fail") << " check entailment" << std::endl
;
450 success
= tdb
->isEntailed(q
[1], subs
, false, true);
451 Trace("inst-exp-fail") << " entailed: " << success
<< std::endl
;
453 // check whether the instantiation rewrites to the same thing
456 Node ibodyc
= getInstantiation(q
, vars
, terms
, idNone
, nulln
, doVts
);
457 ibodyc
= Rewriter::rewrite(ibodyc
);
458 success
= (ibodyc
== ibody
);
459 Trace("inst-exp-fail") << " rewrite invariant: " << success
<< std::endl
;
463 // if we still fail, we are not critical
464 failMask
[ii
] = false;
468 subs
[vars
[ii
]] = prev
;
470 // not necessary to proceed if expFull is false
477 if (Trace
.isOn("inst-exp-fail"))
479 Trace("inst-exp-fail") << "Fail mask: ";
480 for (bool b
: failMask
)
482 Trace("inst-exp-fail") << (b
? 1 : 0);
484 Trace("inst-exp-fail") << std::endl
;
489 void Instantiate::recordInstantiation(Node q
,
490 std::vector
<Node
>& terms
,
493 Trace("inst-debug") << "Record instantiation for " << q
<< std::endl
;
494 // get the instantiation list, which ensures that q is marked as a quantified
495 // formula we instantiated, despite only recording an instantiation here
496 getOrMkInstLemmaList(q
);
497 Node inst
= getInstantiation(q
, terms
, doVts
);
498 d_recordedInst
[q
].push_back(inst
);
501 bool Instantiate::existsInstantiation(Node q
,
502 std::vector
<Node
>& terms
,
505 if (options::incrementalSolving())
507 std::map
<Node
, CDInstMatchTrie
*>::iterator it
= d_c_inst_match_trie
.find(q
);
508 if (it
!= d_c_inst_match_trie
.end())
510 return it
->second
->existsInstMatch(d_qstate
, q
, terms
, modEq
);
515 std::map
<Node
, InstMatchTrie
>::iterator it
= d_inst_match_trie
.find(q
);
516 if (it
!= d_inst_match_trie
.end())
518 return it
->second
.existsInstMatch(d_qstate
, q
, terms
, modEq
);
524 Node
Instantiate::getInstantiation(Node q
,
525 std::vector
<Node
>& vars
,
526 std::vector
<Node
>& terms
,
532 Assert(vars
.size() == terms
.size());
533 Assert(q
[0].getNumChildren() == vars
.size());
534 // Notice that this could be optimized, but no significant performance
535 // improvements were observed with alternative implementations (see #1386).
537 q
[1].substitute(vars
.begin(), vars
.end(), terms
.begin(), terms
.end());
539 // store the proof of the instantiated body, with (open) assumption q
542 // additional arguments: if the inference id is not unknown, include it,
543 // followed by the proof argument if non-null. The latter is used e.g.
544 // to track which trigger caused an instantiation.
545 std::vector
<Node
> pfTerms
= terms
;
546 if (id
!= InferenceId::UNKNOWN
)
548 pfTerms
.push_back(mkInferenceIdNode(id
));
551 pfTerms
.push_back(pfArg
);
554 pf
->addStep(body
, PfRule::INSTANTIATE
, {q
}, pfTerms
);
557 // run rewriters to rewrite the instantiation in sequence.
558 for (InstantiationRewriter
*& ir
: d_instRewrite
)
560 TrustNode trn
= ir
->rewriteInstantiation(q
, terms
, body
, doVts
);
563 Node newBody
= trn
.getNode();
564 // if using proofs, we store a preprocess + transformation step.
567 Node proven
= trn
.getProven();
568 pf
->addLazyStep(proven
,
570 PfRule::THEORY_PREPROCESS
,
572 "Instantiate::getInstantiation:rewrite_inst");
573 pf
->addStep(newBody
, PfRule::EQ_RESOLVE
, {body
, proven
}, {});
581 Node
Instantiate::getInstantiation(Node q
, std::vector
<Node
>& terms
, bool doVts
)
583 Assert(d_qreg
.d_vars
.find(q
) != d_qreg
.d_vars
.end());
584 return getInstantiation(
585 q
, d_qreg
.d_vars
[q
], terms
, InferenceId::UNKNOWN
, Node::null(), doVts
);
588 bool Instantiate::recordInstantiationInternal(Node q
, std::vector
<Node
>& terms
)
590 if (options::incrementalSolving())
592 Trace("inst-add-debug")
593 << "Adding into context-dependent inst trie" << std::endl
;
594 CDInstMatchTrie
* imt
;
595 std::map
<Node
, CDInstMatchTrie
*>::iterator it
= d_c_inst_match_trie
.find(q
);
596 if (it
!= d_c_inst_match_trie
.end())
602 imt
= new CDInstMatchTrie(d_qstate
.getUserContext());
603 d_c_inst_match_trie
[q
] = imt
;
605 d_c_inst_match_trie_dom
.insert(q
);
606 return imt
->addInstMatch(d_qstate
, q
, terms
);
608 Trace("inst-add-debug") << "Adding into inst trie" << std::endl
;
609 return d_inst_match_trie
[q
].addInstMatch(d_qstate
, q
, terms
);
612 bool Instantiate::removeInstantiationInternal(Node q
, std::vector
<Node
>& terms
)
614 if (options::incrementalSolving())
616 std::map
<Node
, CDInstMatchTrie
*>::iterator it
= d_c_inst_match_trie
.find(q
);
617 if (it
!= d_c_inst_match_trie
.end())
619 return it
->second
->removeInstMatch(q
, terms
);
623 return d_inst_match_trie
[q
].removeInstMatch(q
, terms
);
626 void Instantiate::getInstantiatedQuantifiedFormulas(std::vector
<Node
>& qs
) const
628 for (NodeInstListMap::const_iterator it
= d_insts
.begin();
632 qs
.push_back(it
->first
);
636 void Instantiate::getInstantiationTermVectors(
637 Node q
, std::vector
<std::vector
<Node
> >& tvecs
)
640 if (options::incrementalSolving())
642 std::map
<Node
, CDInstMatchTrie
*>::const_iterator it
=
643 d_c_inst_match_trie
.find(q
);
644 if (it
!= d_c_inst_match_trie
.end())
646 it
->second
->getInstantiations(q
, tvecs
);
651 std::map
<Node
, InstMatchTrie
>::const_iterator it
=
652 d_inst_match_trie
.find(q
);
653 if (it
!= d_inst_match_trie
.end())
655 it
->second
.getInstantiations(q
, tvecs
);
660 void Instantiate::getInstantiationTermVectors(
661 std::map
<Node
, std::vector
<std::vector
<Node
> > >& insts
)
663 if (options::incrementalSolving())
665 for (const auto& t
: d_c_inst_match_trie
)
667 getInstantiationTermVectors(t
.first
, insts
[t
.first
]);
672 for (const auto& t
: d_inst_match_trie
)
674 getInstantiationTermVectors(t
.first
, insts
[t
.first
]);
679 void Instantiate::getInstantiations(Node q
, std::vector
<Node
>& insts
)
681 Trace("inst-debug") << "get instantiations for " << q
<< std::endl
;
682 InstLemmaList
* ill
= getOrMkInstLemmaList(q
);
683 insts
.insert(insts
.end(), ill
->d_list
.begin(), ill
->d_list
.end());
684 // also include recorded instantations (for qe-partial)
685 std::map
<Node
, std::vector
<Node
> >::const_iterator it
=
686 d_recordedInst
.find(q
);
687 if (it
!= d_recordedInst
.end())
689 insts
.insert(insts
.end(), it
->second
.begin(), it
->second
.end());
693 bool Instantiate::isProofEnabled() const { return d_pfInst
!= nullptr; }
695 void Instantiate::notifyEndRound()
698 if (Trace
.isOn("inst-per-quant-round"))
700 for (std::pair
<const Node
, uint32_t>& i
: d_instDebugTemp
)
702 Trace("inst-per-quant-round")
703 << " * " << i
.second
<< " for " << i
.first
<< std::endl
;
706 if (Output
.isOn(options::OutputTag::INST
))
708 bool req
= !options::printInstFull();
709 for (std::pair
<const Node
, uint32_t>& i
: d_instDebugTemp
)
712 if (!d_qreg
.getNameForQuant(i
.first
, name
, req
))
716 Output(options::OutputTag::INST
) << "(num-instantiations " << name
<< " "
717 << i
.second
<< ")" << std::endl
;
722 void Instantiate::debugPrintModel()
724 if (Trace
.isOn("inst-per-quant"))
726 for (NodeInstListMap::iterator it
= d_insts
.begin(); it
!= d_insts
.end();
729 Trace("inst-per-quant") << " * " << (*it
).second
->d_list
.size() << " for "
730 << (*it
).first
<< std::endl
;
735 Node
Instantiate::ensureType(Node n
, TypeNode tn
)
737 Trace("inst-add-debug2") << "Ensure " << n
<< " : " << tn
<< std::endl
;
738 TypeNode ntn
= n
.getType();
739 Assert(ntn
.isComparableTo(tn
));
740 if (ntn
.isSubtypeOf(tn
))
746 return NodeManager::currentNM()->mkNode(TO_INTEGER
, n
);
751 InstLemmaList
* Instantiate::getOrMkInstLemmaList(TNode q
)
753 NodeInstListMap::iterator it
= d_insts
.find(q
);
754 if (it
!= d_insts
.end())
756 return it
->second
.get();
758 std::shared_ptr
<InstLemmaList
> ill
=
759 std::make_shared
<InstLemmaList
>(d_qstate
.getUserContext());
760 d_insts
.insert(q
, ill
);
764 Instantiate::Statistics::Statistics()
765 : d_instantiations(smtStatisticsRegistry().registerInt(
766 "Instantiate::Instantiations_Total")),
768 smtStatisticsRegistry().registerInt("Instantiate::Duplicate_Inst")),
769 d_inst_duplicate_eq(smtStatisticsRegistry().registerInt(
770 "Instantiate::Duplicate_Inst_Eq")),
771 d_inst_duplicate_ent(smtStatisticsRegistry().registerInt(
772 "Instantiate::Duplicate_Inst_Entailed"))
776 } // namespace quantifiers
777 } // namespace theory