/********************* */ /*! \file theory_proof.cpp ** \verbatim ** Top contributors (to current version): ** Guy Katz, Liana Hadarean, Yoni Zohar ** This file is part of the CVC4 project. ** Copyright (c) 2009-2019 by the authors listed in the file AUTHORS ** in the top-level source directory) and their institutional affiliations. ** All rights reserved. See the file COPYING in the top-level source ** directory for licensing information.\endverbatim ** ** \brief [[ Add one-line brief description here ]] ** ** [[ Add lengthier description here ]] ** \todo document this file **/ #include "proof/theory_proof.h" #include "base/check.h" #include "context/context.h" #include "expr/node_visitor.h" #include "options/bv_options.h" #include "options/proof_options.h" #include "proof/arith_proof.h" #include "proof/array_proof.h" #include "proof/clausal_bitvector_proof.h" #include "proof/clause_id.h" #include "proof/cnf_proof.h" #include "proof/proof_manager.h" #include "proof/proof_output_channel.h" #include "proof/proof_utils.h" #include "proof/resolution_bitvector_proof.h" #include "proof/sat_proof.h" #include "proof/simplify_boolean_node.h" #include "proof/uf_proof.h" #include "prop/sat_solver_types.h" #include "smt/smt_engine.h" #include "smt/smt_engine_scope.h" #include "theory/arrays/theory_arrays.h" #include "theory/bv/theory_bv.h" #include "theory/output_channel.h" #include "theory/term_registration_visitor.h" #include "theory/uf/theory_uf.h" #include "theory/valuation.h" #include "util/hash.h" #include "util/proof.h" namespace CVC4 { using proof::LfscResolutionBitVectorProof; using proof::ResolutionBitVectorProof; unsigned CVC4::ProofLetCount::counter = 0; static unsigned LET_COUNT = 1; TheoryProofEngine::TheoryProofEngine() : d_registrationCache() , d_theoryProofTable() { d_theoryProofTable[theory::THEORY_BOOL] = new LFSCBooleanProof(this); } TheoryProofEngine::~TheoryProofEngine() { TheoryProofTable::iterator it = d_theoryProofTable.begin(); TheoryProofTable::iterator end = d_theoryProofTable.end(); for (; it != end; ++it) { delete it->second; } } void TheoryProofEngine::registerTheory(theory::Theory* th) { if (th) { theory::TheoryId id = th->getId(); if(d_theoryProofTable.find(id) == d_theoryProofTable.end()) { Trace("pf::tp") << "TheoryProofEngine::registerTheory: " << id << std::endl; if (id == theory::THEORY_UF) { d_theoryProofTable[id] = new LFSCUFProof((theory::uf::TheoryUF*)th, this); return; } if (id == theory::THEORY_BV) { auto thBv = static_cast(th); if (options::bitblastMode() == options::BitblastMode::EAGER && options::bvSatSolver() == options::SatSolverMode::CRYPTOMINISAT) { proof::BitVectorProof* bvp = nullptr; switch (options::bvProofFormat()) { case options::BvProofFormat::DRAT: { bvp = new proof::LfscDratBitVectorProof(thBv, this); break; } case options::BvProofFormat::LRAT: { bvp = new proof::LfscLratBitVectorProof(thBv, this); break; } case options::BvProofFormat::ER: { bvp = new proof::LfscErBitVectorProof(thBv, this); break; } default: { Unreachable() << "Invalid BvProofFormat"; } }; d_theoryProofTable[id] = bvp; } else { proof::BitVectorProof* bvp = new proof::LfscResolutionBitVectorProof(thBv, this); d_theoryProofTable[id] = bvp; } return; } if (id == theory::THEORY_ARRAYS) { d_theoryProofTable[id] = new LFSCArrayProof((theory::arrays::TheoryArrays*)th, this); return; } if (id == theory::THEORY_ARITH) { d_theoryProofTable[id] = new LFSCArithProof((theory::arith::TheoryArith*)th, this); return; } // TODO other theories } } } void TheoryProofEngine::finishRegisterTheory(theory::Theory* th) { if (th) { theory::TheoryId id = th->getId(); if (id == theory::THEORY_BV) { theory::bv::TheoryBV* bv_th = static_cast(th); Assert(d_theoryProofTable.find(id) != d_theoryProofTable.end()); proof::BitVectorProof* bvp = static_cast(d_theoryProofTable[id]); bv_th->setProofLog(bvp); return; } } } TheoryProof* TheoryProofEngine::getTheoryProof(theory::TheoryId id) { // The UF theory handles queries for the Builtin theory. if (id == theory::THEORY_BUILTIN) { Debug("pf::tp") << "TheoryProofEngine::getTheoryProof: BUILTIN --> UF" << std::endl; id = theory::THEORY_UF; } if (d_theoryProofTable.find(id) == d_theoryProofTable.end()) { InternalError() << "Error! Proofs not yet supported for the following theory: " << id << std::endl; } return d_theoryProofTable[id]; } void TheoryProofEngine::markTermForFutureRegistration(Expr term, theory::TheoryId id) { d_exprToTheoryIds[term].insert(id); } void TheoryProofEngine::printConstantDisequalityProof(std::ostream& os, Expr c1, Expr c2, const ProofLetMap &globalLetMap) { Assert(c1.isConst()); Assert(c2.isConst()); Assert(theory::Theory::theoryOf(c1) == theory::Theory::theoryOf(c2)); getTheoryProof(theory::Theory::theoryOf(c1))->printConstantDisequalityProof(os, c1, c2, globalLetMap); } void TheoryProofEngine::printTheoryTerm(Expr term, std::ostream& os, const ProofLetMap& map, TypeNode expectedType) { this->printTheoryTermAsType(term, os, map, expectedType); } TypeNode TheoryProofEngine::equalityType(const Expr& left, const Expr& right) { // Ask the two theories what they think.. TypeNode leftType = getTheoryProof(theory::Theory::theoryOf(left))->equalityType(left, right); TypeNode rightType = getTheoryProof(theory::Theory::theoryOf(right))->equalityType(left, right); // Error if the disagree. Assert(leftType.isNull() || rightType.isNull() || leftType == rightType) << "TheoryProofEngine::equalityType(" << left << ", " << right << "):" << std::endl << "theories disagree about the type of an equality:" << std::endl << "\tleft: " << leftType << std::endl << "\tright:" << rightType; return leftType.isNull() ? rightType : leftType; } void TheoryProofEngine::registerTerm(Expr term) { Debug("pf::tp::register") << "TheoryProofEngine::registerTerm: registering term: " << term << std::endl; if (d_registrationCache.count(term)) { return; } Debug("pf::tp::register") << "TheoryProofEngine::registerTerm: registering NEW term: " << term << std::endl; theory::TheoryId theory_id = theory::Theory::theoryOf(term); Debug("pf::tp::register") << "Term's theory( " << term << " ) = " << theory_id << std::endl; // don't need to register boolean terms if (theory_id == theory::THEORY_BUILTIN || term.getKind() == kind::ITE) { for (unsigned i = 0; i < term.getNumChildren(); ++i) { registerTerm(term[i]); } d_registrationCache.insert(term); return; } if (!supportedTheory(theory_id)) return; // Register the term with its owner theory getTheoryProof(theory_id)->registerTerm(term); // A special case: the array theory needs to know of every skolem, even if // it belongs to another theory (e.g., a BV skolem) if (ProofManager::getSkolemizationManager()->isSkolem(term) && theory_id != theory::THEORY_ARRAYS) { Debug("pf::tp::register") << "TheoryProofEngine::registerTerm: registering a non-array skolem: " << term << std::endl; getTheoryProof(theory::THEORY_ARRAYS)->registerTerm(term); } d_registrationCache.insert(term); } theory::TheoryId TheoryProofEngine::getTheoryForLemma(const prop::SatClause* clause) { ProofManager* pm = ProofManager::currentPM(); std::set nodes; for(unsigned i = 0; i < clause->size(); ++i) { prop::SatLiteral lit = (*clause)[i]; Node node = pm->getCnfProof()->getAtom(lit.getSatVariable()); Expr atom = node.toExpr(); if (atom.isConst()) { Assert(atom == utils::mkTrue()); continue; } nodes.insert(lit.isNegated() ? node.notNode() : node); } // Ensure that the lemma is in the database. Assert(pm->getCnfProof()->haveProofRecipe(nodes)); return pm->getCnfProof()->getProofRecipe(nodes).getTheory(); } void LFSCTheoryProofEngine::bind(Expr term, ProofLetMap& map, Bindings& let_order) { ProofLetMap::iterator it = map.find(term); if (it != map.end()) { ProofLetCount& count = it->second; count.increment(); return; } for (unsigned i = 0; i < term.getNumChildren(); ++i) { bind(term[i], map, let_order); } unsigned new_id = ProofLetCount::newId(); map[term] = ProofLetCount(new_id); let_order.push_back(LetOrderElement(term, new_id)); } void LFSCTheoryProofEngine::printLetTerm(Expr term, std::ostream& os) { ProofLetMap map; Bindings let_order; bind(term, map, let_order); std::ostringstream paren; for (unsigned i = 0; i < let_order.size(); ++i) { Expr current_expr = let_order[i].expr; unsigned let_id = let_order[i].id; ProofLetMap::const_iterator it = map.find(current_expr); Assert(it != map.end()); unsigned let_count = it->second.count; Assert(let_count); // skip terms that only appear once if (let_count <= LET_COUNT) { continue; } os << "(@ let" <second.count; if (last_count <= LET_COUNT) { printTheoryTerm(last, os, map); } else { os << " let" << last_let_id; } os << paren.str(); } void LFSCTheoryProofEngine::printTheoryTermAsType(Expr term, std::ostream& os, const ProofLetMap& map, TypeNode expectedType) { theory::TheoryId theory_id = theory::Theory::theoryOf(term); // boolean terms and ITEs are special because they // are common to all theories if (theory_id == theory::THEORY_BUILTIN || term.getKind() == kind::ITE || term.getKind() == kind::EQUAL) { printCoreTerm(term, os, map, expectedType); return; } // dispatch to proper theory getTheoryProof(theory_id)->printOwnedTerm(term, os, map, expectedType); } void LFSCTheoryProofEngine::printSort(Type type, std::ostream& os) { if (type.isSort()) { getTheoryProof(theory::THEORY_UF)->printOwnedSort(type, os); return; } if (type.isBitVector()) { getTheoryProof(theory::THEORY_BV)->printOwnedSort(type, os); return; } if (type.isArray()) { getTheoryProof(theory::THEORY_ARRAYS)->printOwnedSort(type, os); return; } if (type.isInteger() || type.isReal()) { getTheoryProof(theory::THEORY_ARITH)->printOwnedSort(type, os); return; } if (type.isBoolean()) { getTheoryProof(theory::THEORY_BOOL)->printOwnedSort(type, os); return; } Unreachable(); } void LFSCTheoryProofEngine::performExtraRegistrations() { ExprToTheoryIds::const_iterator it; for (it = d_exprToTheoryIds.begin(); it != d_exprToTheoryIds.end(); ++it) { if (d_registrationCache.count(it->first)) { // Only register if the term appeared TheoryIdSet::const_iterator theoryIt; for (theoryIt = it->second.begin(); theoryIt != it->second.end(); ++theoryIt) { Debug("pf::tp") << "\tExtra registration of term " << it->first << " with theory: " << *theoryIt << std::endl; Assert(supportedTheory(*theoryIt)); getTheoryProof(*theoryIt)->registerTerm(it->first); } } } } void LFSCTheoryProofEngine::registerTermsFromAssertions() { ProofManager::assertions_iterator it = ProofManager::currentPM()->begin_assertions(); ProofManager::assertions_iterator end = ProofManager::currentPM()->end_assertions(); for(; it != end; ++it) { registerTerm(*it); } performExtraRegistrations(); } void LFSCTheoryProofEngine::printAssertions(std::ostream& os, std::ostream& paren) { Debug("pf::tp") << "LFSCTheoryProofEngine::printAssertions called" << std::endl << std::endl; ProofManager::assertions_iterator it = ProofManager::currentPM()->begin_assertions(); ProofManager::assertions_iterator end = ProofManager::currentPM()->end_assertions(); for (; it != end; ++it) { Debug("pf::tp") << "printAssertions: assertion is: " << *it << std::endl; os << "(% " << ProofManager::currentPM()->getInputFormulaName(*it) << " (th_holds "; // Assertions appear before the global let map, so we use a dummpMap to avoid letification here. ProofLetMap dummyMap; bool convertFromBool = (it->getType().isBoolean() && printsAsBool(*it)); if (convertFromBool) os << "(p_app "; printBoundTerm(*it, os, dummyMap); if (convertFromBool) os << ")"; os << ")\n"; paren << ")"; } Debug("pf::tp") << "LFSCTheoryProofEngine::printAssertions done" << std::endl << std::endl; } void LFSCTheoryProofEngine::printLemmaRewrites(NodePairSet& rewrites, std::ostream& os, std::ostream& paren) { Debug("pf::tp") << "LFSCTheoryProofEngine::printLemmaRewrites called" << std::endl << std::endl; NodePairSet::const_iterator it; for (it = rewrites.begin(); it != rewrites.end(); ++it) { Debug("pf::tp") << "printLemmaRewrites: " << it->first << " --> " << it->second << std::endl; Node n1 = it->first; Node n2 = it->second; Assert(n1.toExpr() == utils::mkFalse() || theory::Theory::theoryOf(n1) == theory::Theory::theoryOf(n2)); std::ostringstream rewriteRule; rewriteRule << ".lrr" << d_assertionToRewrite.size(); os << "(th_let_pf _ "; getTheoryProof(theory::Theory::theoryOf(n1))->printRewriteProof(os, n1, n2); os << "(\\ " << rewriteRule.str() << "\n"; d_assertionToRewrite[it->first] = rewriteRule.str(); Debug("pf::tp") << "d_assertionToRewrite[" << it->first << "] = " << rewriteRule.str() << std::endl; paren << "))"; } Debug("pf::tp") << "LFSCTheoryProofEngine::printLemmaRewrites done" << std::endl << std::endl; } void LFSCTheoryProofEngine::printSortDeclarations(std::ostream& os, std::ostream& paren) { Debug("pf::tp") << "LFSCTheoryProofEngine::printSortDeclarations called" << std::endl << std::endl; TheoryProofTable::const_iterator it = d_theoryProofTable.begin(); TheoryProofTable::const_iterator end = d_theoryProofTable.end(); for (; it != end; ++it) { it->second->printSortDeclarations(os, paren); } Debug("pf::tp") << "LFSCTheoryProofEngine::printSortDeclarations done" << std::endl << std::endl; } void LFSCTheoryProofEngine::printTermDeclarations(std::ostream& os, std::ostream& paren) { Debug("pf::tp") << "LFSCTheoryProofEngine::printTermDeclarations called" << std::endl << std::endl; TheoryProofTable::const_iterator it = d_theoryProofTable.begin(); TheoryProofTable::const_iterator end = d_theoryProofTable.end(); for (; it != end; ++it) { it->second->printTermDeclarations(os, paren); } Debug("pf::tp") << "LFSCTheoryProofEngine::printTermDeclarations done" << std::endl << std::endl; } void LFSCTheoryProofEngine::printDeferredDeclarations(std::ostream& os, std::ostream& paren) { Debug("pf::tp") << "LFSCTheoryProofEngine::printDeferredDeclarations called" << std::endl; TheoryProofTable::const_iterator it = d_theoryProofTable.begin(); TheoryProofTable::const_iterator end = d_theoryProofTable.end(); for (; it != end; ++it) { it->second->printDeferredDeclarations(os, paren); } } void LFSCTheoryProofEngine::printAliasingDeclarations(std::ostream& os, std::ostream& paren, const ProofLetMap &globalLetMap) { Debug("pf::tp") << "LFSCTheoryProofEngine::printAliasingDeclarations called" << std::endl; TheoryProofTable::const_iterator it = d_theoryProofTable.begin(); TheoryProofTable::const_iterator end = d_theoryProofTable.end(); for (; it != end; ++it) { it->second->printAliasingDeclarations(os, paren, globalLetMap); } } void LFSCTheoryProofEngine::dumpTheoryLemmas(const IdToSatClause& lemmas) { Debug("pf::dumpLemmas") << "Dumping ALL theory lemmas" << std::endl << std::endl; ProofManager* pm = ProofManager::currentPM(); for (IdToSatClause::const_iterator it = lemmas.begin(); it != lemmas.end(); ++it) { ClauseId id = it->first; Debug("pf::dumpLemmas") << "**** \tLemma ID = " << id << std::endl; const prop::SatClause* clause = it->second; std::set nodes; for(unsigned i = 0; i < clause->size(); ++i) { prop::SatLiteral lit = (*clause)[i]; Node node = pm->getCnfProof()->getAtom(lit.getSatVariable()); if (node.isConst()) { Assert(node.toExpr() == utils::mkTrue()); continue; } nodes.insert(lit.isNegated() ? node.notNode() : node); } LemmaProofRecipe recipe = pm->getCnfProof()->getProofRecipe(nodes); recipe.dump("pf::dumpLemmas"); } Debug("pf::dumpLemmas") << "Theory lemma printing DONE" << std::endl << std::endl; } // TODO: this function should be moved into the BV prover. void LFSCTheoryProofEngine::finalizeBvConflicts(const IdToSatClause& lemmas, std::ostream& os) { // BitVector theory is special case: must know all conflicts needed // ahead of time for resolution proof lemmas std::vector bv_lemmas; for (IdToSatClause::const_iterator it = lemmas.begin(); it != lemmas.end(); ++it) { const prop::SatClause* clause = it->second; std::vector conflict; std::set conflictNodes; for(unsigned i = 0; i < clause->size(); ++i) { prop::SatLiteral lit = (*clause)[i]; Node node = ProofManager::currentPM()->getCnfProof()->getAtom(lit.getSatVariable()); Expr atom = node.toExpr(); // The literals (true) and (not false) are omitted from conflicts if (atom.isConst()) { Assert(atom == utils::mkTrue() || (atom == utils::mkFalse() && lit.isNegated())); continue; } Expr expr_lit = lit.isNegated() ? atom.notExpr() : atom; conflict.push_back(expr_lit); conflictNodes.insert(lit.isNegated() ? node.notNode() : node); } LemmaProofRecipe recipe = ProofManager::currentPM()->getCnfProof()->getProofRecipe(conflictNodes); unsigned numberOfSteps = recipe.getNumSteps(); prop::SatClause currentClause = *clause; std::vector currentClauseExpr = conflict; for (unsigned i = 0; i < numberOfSteps; ++i) { const LemmaProofRecipe::ProofStep* currentStep = recipe.getStep(i); if (currentStep->getTheory() != theory::THEORY_BV) { continue; } // If any rewrites took place, we need to update the conflict clause accordingly std::set missingAssertions = recipe.getMissingAssertionsForStep(i); std::map explanationToMissingAssertion; std::set::iterator assertionIt; for (assertionIt = missingAssertions.begin(); assertionIt != missingAssertions.end(); ++assertionIt) { Node negated = (*assertionIt).negate(); explanationToMissingAssertion[recipe.getExplanation(negated)] = negated; } currentClause = *clause; currentClauseExpr = conflict; for (unsigned j = 0; j < i; ++j) { // Literals already used in previous steps need to be negated Node previousLiteralNode = recipe.getStep(j)->getLiteral(); // If this literal is the result of a rewrite, we need to translate it if (explanationToMissingAssertion.find(previousLiteralNode) != explanationToMissingAssertion.end()) { previousLiteralNode = explanationToMissingAssertion[previousLiteralNode]; } Node previousLiteralNodeNegated = previousLiteralNode.negate(); prop::SatLiteral previousLiteralNegated = ProofManager::currentPM()->getCnfProof()->getLiteral(previousLiteralNodeNegated); currentClause.push_back(previousLiteralNegated); currentClauseExpr.push_back(previousLiteralNodeNegated.toExpr()); } // If we're in the final step, the last literal is Null and should not be added. // Otherwise, the current literal does NOT need to be negated Node currentLiteralNode = currentStep->getLiteral(); if (currentLiteralNode != Node()) { prop::SatLiteral currentLiteral = ProofManager::currentPM()->getCnfProof()->getLiteral(currentLiteralNode); currentClause.push_back(currentLiteral); currentClauseExpr.push_back(currentLiteralNode.toExpr()); } bv_lemmas.push_back(utils::mkSortedExpr(kind::OR, currentClauseExpr)); } } proof::BitVectorProof* bv = ProofManager::getBitVectorProof(); bv->finalizeConflicts(bv_lemmas); } void LFSCTheoryProofEngine::printTheoryLemmas(const IdToSatClause& lemmas, std::ostream& os, std::ostream& paren, ProofLetMap& map) { os << " ;; Theory Lemmas \n"; Debug("pf::tp") << "LFSCTheoryProofEngine::printTheoryLemmas: starting" << std::endl; if (Debug.isOn("pf::dumpLemmas")) { dumpTheoryLemmas(lemmas); } // finalizeBvConflicts(lemmas, os, paren, map); ProofManager::getBitVectorProof()->printBBDeclarationAndCnf(os, paren, map); if (options::bitblastMode() == options::BitblastMode::EAGER) { Assert(lemmas.size() == 1); // nothing more to do (no combination with eager so far) return; } ProofManager* pm = ProofManager::currentPM(); Debug("pf::tp") << "LFSCTheoryProofEngine::printTheoryLemmas: printing lemmas..." << std::endl; for (IdToSatClause::const_iterator it = lemmas.begin(); it != lemmas.end(); ++it) { ClauseId id = it->first; const prop::SatClause* clause = it->second; Debug("pf::tp") << "LFSCTheoryProofEngine::printTheoryLemmas: printing lemma. ID = " << id << std::endl; std::vector clause_expr; std::set clause_expr_nodes; for(unsigned i = 0; i < clause->size(); ++i) { prop::SatLiteral lit = (*clause)[i]; Node node = pm->getCnfProof()->getAtom(lit.getSatVariable()); Expr atom = node.toExpr(); if (atom.isConst()) { Assert(atom == utils::mkTrue()); continue; } Expr expr_lit = lit.isNegated() ? atom.notExpr(): atom; clause_expr.push_back(expr_lit); clause_expr_nodes.insert(lit.isNegated() ? node.notNode() : node); } LemmaProofRecipe recipe = pm->getCnfProof()->getProofRecipe(clause_expr_nodes); if (recipe.simpleLemma()) { // In a simple lemma, there will be no propositional resolution in the end Debug("pf::tp") << "Simple lemma" << std::endl; // Printing the clause as it appears in resolution proof os << "(satlem _ _ "; std::ostringstream clause_paren; pm->getCnfProof()->printClause(*clause, os, clause_paren); // Find and handle missing assertions, due to rewrites std::set missingAssertions = recipe.getMissingAssertionsForStep(0); if (!missingAssertions.empty()) { Debug("pf::tp") << "Have missing assertions for this simple lemma!" << std::endl; } std::set::const_iterator missingAssertion; for (missingAssertion = missingAssertions.begin(); missingAssertion != missingAssertions.end(); ++missingAssertion) { Debug("pf::tp") << "Working on missing assertion: " << *missingAssertion << std::endl; Assert(recipe.wasRewritten(missingAssertion->negate())); Node explanation = recipe.getExplanation(missingAssertion->negate()).negate(); Debug("pf::tp") << "Found explanation: " << explanation << std::endl; // We have a missing assertion. // rewriteIt->first is the assertion after the rewrite (the explanation), // rewriteIt->second is the original assertion that needs to be fed into the theory. bool found = false; unsigned k; for (k = 0; k < clause_expr.size(); ++k) { if (clause_expr[k] == explanation.toExpr()) { found = true; break; } } AlwaysAssert(found); Debug("pf::tp") << "Replacing theory assertion " << clause_expr[k] << " with " << *missingAssertion << std::endl; clause_expr[k] = missingAssertion->toExpr(); std::ostringstream rewritten; if (missingAssertion->getKind() == kind::NOT && (*missingAssertion)[0].toExpr() == utils::mkFalse()) { rewritten << "(or_elim_2 _ _ "; rewritten << "(not_not_intro _ "; rewritten << pm->getLitName(explanation); rewritten << ") (iff_elim_2 _ _ "; rewritten << d_assertionToRewrite[missingAssertion->negate()]; rewritten << "))"; } else { rewritten << "(or_elim_1 _ _ "; rewritten << "(not_not_intro _ "; rewritten << pm->getLitName(explanation); rewritten << ") (iff_elim_1 _ _ "; rewritten << d_assertionToRewrite[missingAssertion->negate()]; rewritten << "))"; } Debug("pf::tp") << "Setting a rewrite filter for this proof: " << std::endl << pm->getLitName(*missingAssertion) << " --> " << rewritten.str() << ", explanation = " << explanation << std::endl << std::endl; pm->addRewriteFilter(pm->getLitName(*missingAssertion), rewritten.str()); } // Query the appropriate theory for a proof of this clause theory::TheoryId theory_id = getTheoryForLemma(clause); Debug("pf::tp") << "Get theory lemma from " << theory_id << "..." << std::endl; getTheoryProof(theory_id)->printTheoryLemmaProof(clause_expr, os, paren, map); // Turn rewrite filter OFF pm->clearRewriteFilters(); Debug("pf::tp") << "Get theory lemma from " << theory_id << "... DONE!" << std::endl; os << clause_paren.str(); os << "( \\ " << pm->getLemmaClauseName(id) <<"\n"; paren << "))"; } else { // This is a composite lemma unsigned numberOfSteps = recipe.getNumSteps(); prop::SatClause currentClause = *clause; std::vector currentClauseExpr = clause_expr; for (unsigned i = 0; i < numberOfSteps; ++i) { const LemmaProofRecipe::ProofStep* currentStep = recipe.getStep(i); currentClause = *clause; currentClauseExpr = clause_expr; for (unsigned j = 0; j < i; ++j) { // Literals already used in previous steps need to be negated Node previousLiteralNode = recipe.getStep(j)->getLiteral(); Node previousLiteralNodeNegated = previousLiteralNode.negate(); prop::SatLiteral previousLiteralNegated = ProofManager::currentPM()->getCnfProof()->getLiteral(previousLiteralNodeNegated); currentClause.push_back(previousLiteralNegated); currentClauseExpr.push_back(previousLiteralNodeNegated.toExpr()); } // If the current literal is NULL, can ignore (final step) // Otherwise, the current literal does NOT need to be negated Node currentLiteralNode = currentStep->getLiteral(); if (currentLiteralNode != Node()) { prop::SatLiteral currentLiteral = ProofManager::currentPM()->getCnfProof()->getLiteral(currentLiteralNode); currentClause.push_back(currentLiteral); currentClauseExpr.push_back(currentLiteralNode.toExpr()); } os << "(satlem _ _ "; std::ostringstream clause_paren; pm->getCnfProof()->printClause(currentClause, os, clause_paren); // query appropriate theory for proof of clause theory::TheoryId theory_id = currentStep->getTheory(); Debug("pf::tp") << "Get theory lemma from " << theory_id << "..." << std::endl; std::set missingAssertions = recipe.getMissingAssertionsForStep(i); if (!missingAssertions.empty()) { Debug("pf::tp") << "Have missing assertions for this step!" << std::endl; } // Turn rewrite filter ON std::set::const_iterator missingAssertion; for (missingAssertion = missingAssertions.begin(); missingAssertion != missingAssertions.end(); ++missingAssertion) { Debug("pf::tp") << "Working on missing assertion: " << *missingAssertion << std::endl; Assert(recipe.wasRewritten(missingAssertion->negate())); Node explanation = recipe.getExplanation(missingAssertion->negate()).negate(); Debug("pf::tp") << "Found explanation: " << explanation << std::endl; // We have a missing assertion. // rewriteIt->first is the assertion after the rewrite (the explanation), // rewriteIt->second is the original assertion that needs to be fed into the theory. bool found = false; unsigned k; for (k = 0; k < currentClauseExpr.size(); ++k) { if (currentClauseExpr[k] == explanation.toExpr()) { found = true; break; } } AlwaysAssert(found); Debug("pf::tp") << "Replacing theory assertion " << currentClauseExpr[k] << " with " << *missingAssertion << std::endl; currentClauseExpr[k] = missingAssertion->toExpr(); std::ostringstream rewritten; if (missingAssertion->getKind() == kind::NOT && (*missingAssertion)[0].toExpr() == utils::mkFalse()) { rewritten << "(or_elim_2 _ _ "; rewritten << "(not_not_intro _ "; rewritten << pm->getLitName(explanation); rewritten << ") (iff_elim_2 _ _ "; rewritten << d_assertionToRewrite[missingAssertion->negate()]; rewritten << "))"; } else { rewritten << "(or_elim_1 _ _ "; rewritten << "(not_not_intro _ "; rewritten << pm->getLitName(explanation); rewritten << ") (iff_elim_1 _ _ "; rewritten << d_assertionToRewrite[missingAssertion->negate()]; rewritten << "))"; } Debug("pf::tp") << "Setting a rewrite filter for this proof: " << std::endl << pm->getLitName(*missingAssertion) << " --> " << rewritten.str() << "explanation = " << explanation << std::endl << std::endl; pm->addRewriteFilter(pm->getLitName(*missingAssertion), rewritten.str()); } getTheoryProof(theory_id)->printTheoryLemmaProof(currentClauseExpr, os, paren, map); // Turn rewrite filter OFF pm->clearRewriteFilters(); Debug("pf::tp") << "Get theory lemma from " << theory_id << "... DONE!" << std::endl; os << clause_paren.str(); os << "( \\ " << pm->getLemmaClauseName(id) << "s" << i <<"\n"; paren << "))"; } Assert(numberOfSteps >= 2); os << "(satlem_simplify _ _ _ "; for (unsigned i = 0; i < numberOfSteps - 1; ++i) { // Resolve step i with step i + 1 if (recipe.getStep(i)->getLiteral().getKind() == kind::NOT) { os << "(Q _ _ "; } else { os << "(R _ _ "; } os << pm->getLemmaClauseName(id) << "s" << i; os << " "; } os << pm->getLemmaClauseName(id) << "s" << numberOfSteps - 1 << " "; prop::SatLiteral v; for (int i = numberOfSteps - 2; i >= 0; --i) { v = ProofManager::currentPM()->getCnfProof()->getLiteral(recipe.getStep(i)->getLiteral()); os << ProofManager::getVarName(v.getSatVariable(), "") << ") "; } os << "( \\ " << pm->getLemmaClauseName(id) << "\n"; paren << "))"; } } } void LFSCTheoryProofEngine::printBoundTermAsType(Expr term, std::ostream& os, const ProofLetMap& map, TypeNode expectedType) { Debug("pf::tp") << "LFSCTheoryProofEngine::printBoundTerm( " << term << " ) " << std::endl; // Since let-abbreviated terms are abbreviated with their default type, only // use the let map if there is no expectedType or the expectedType matches // the default. if (expectedType.isNull() || TypeNode::fromType(term.getType()) == expectedType) { ProofLetMap::const_iterator it = map.find(term); if (it != map.end()) { unsigned id = it->second.id; unsigned count = it->second.count; if (count > LET_COUNT) { os << "let" << id; Debug("pf::tp::letmap") << "Using let map for " << term << std::endl; return; } } } Debug("pf::tp::letmap") << "Skipping let map for " << term << std::endl; printTheoryTerm(term, os, map, expectedType); } void LFSCTheoryProofEngine::printBoundFormula(Expr term, std::ostream& os, const ProofLetMap& map) { Assert(term.getType().isBoolean() or term.getType().isPredicate()); bool wrapWithBoolToPred = term.getType().isBoolean() and printsAsBool(term); if (wrapWithBoolToPred) { os << "(p_app "; } printBoundTerm(term, os, map); if (wrapWithBoolToPred) { os << ")"; } } void LFSCTheoryProofEngine::printCoreTerm(Expr term, std::ostream& os, const ProofLetMap& map, TypeNode expectedType) { if (term.isVariable()) { os << ProofManager::sanitize(term); return; } Kind k = term.getKind(); switch(k) { case kind::ITE: { TypeNode armType = expectedType.isNull() ? TypeNode::fromType(term.getType()) : expectedType; bool useFormulaType = term.getType().isBoolean(); Assert(term[1].getType().isSubtypeOf(term.getType())); Assert(term[2].getType().isSubtypeOf(term.getType())); os << (useFormulaType ? "(ifte " : "(ite _ "); printBoundFormula(term[0], os, map); os << " "; if (useFormulaType) { printBoundFormula(term[1], os, map); } else { printBoundTerm(term[1], os, map, armType); } os << " "; if (useFormulaType) { printBoundFormula(term[2], os, map); } else { printBoundTerm(term[2], os, map, armType); } os << ")"; return; } case kind::EQUAL: { bool booleanCase = term[0].getType().isBoolean(); TypeNode armType = equalityType(term[0], term[1]); os << "("; if (booleanCase) { os << "iff "; } else { os << "= "; printSort(term[0].getType(), os); os << " "; } if (booleanCase && printsAsBool(term[0])) os << "(p_app "; printBoundTerm(term[0], os, map, armType); if (booleanCase && printsAsBool(term[0])) os << ")"; os << " "; if (booleanCase && printsAsBool(term[1])) os << "(p_app "; printBoundTerm(term[1], os, map, armType); if (booleanCase && printsAsBool(term[1])) os << ") "; os << ")"; return; } case kind::DISTINCT: { // Distinct nodes can have any number of chidlren. Assert(term.getNumChildren() >= 2); TypeNode armType = equalityType(term[0], term[1]); if (term.getNumChildren() == 2) { os << "(not (= "; printSort(term[0].getType(), os); os << " "; printBoundTerm(term[0], os, map, armType); os << " "; printBoundTerm(term[1], os, map, armType); os << "))"; } else { unsigned numOfPairs = term.getNumChildren() * (term.getNumChildren() - 1) / 2; for (unsigned i = 1; i < numOfPairs; ++i) { os << "(and "; } for (unsigned i = 0; i < term.getNumChildren(); ++i) { for (unsigned j = i + 1; j < term.getNumChildren(); ++j) { armType = equalityType(term[i], term[j]); if ((i != 0) || (j != 1)) { os << "(not (= "; printSort(term[0].getType(), os); os << " "; printBoundTerm(term[i], os, map, armType); os << " "; printBoundTerm(term[j], os, map, armType); os << ")))"; } else { os << "(not (= "; printSort(term[0].getType(), os); os << " "; printBoundTerm(term[0], os, map, armType); os << " "; printBoundTerm(term[1], os, map, armType); os << "))"; } } } } return; } default: Unhandled() << k; } } void TheoryProof::printTheoryLemmaProof(std::vector& lemma, std::ostream& os, std::ostream& paren, const ProofLetMap& map) { // Default method for replaying proofs: assert (negated) literals back to a fresh copy of the theory Assert(d_theory != NULL); context::UserContext fakeContext; ProofOutputChannel oc; theory::Valuation v(NULL); //make new copy of theory theory::Theory* th; Trace("pf::tp") << ";; Print theory lemma proof, theory id = " << d_theory->getId() << std::endl; if (d_theory->getId()==theory::THEORY_UF) { th = new theory::uf::TheoryUF(&fakeContext, &fakeContext, oc, v, ProofManager::currentPM()->getLogicInfo(), "replay::"); } else if (d_theory->getId()==theory::THEORY_ARRAYS) { th = new theory::arrays::TheoryArrays(&fakeContext, &fakeContext, oc, v, ProofManager::currentPM()->getLogicInfo(), "replay::"); } else if (d_theory->getId() == theory::THEORY_ARITH) { Trace("theory-proof-debug") << "Arith proofs currently not supported. Use 'trust'" << std::endl; os << " (clausify_false trust)"; return; } else { InternalError() << "can't generate theory-proof for " << ProofManager::currentPM()->getLogic(); } Debug("pf::tp") << "TheoryProof::printTheoryLemmaProof - calling th->ProduceProofs()" << std::endl; th->produceProofs(); Debug("pf::tp") << "TheoryProof::printTheoryLemmaProof - th->ProduceProofs() DONE" << std::endl; MyPreRegisterVisitor preRegVisitor(th); for (unsigned i=0; igetId() == theory::Theory::theoryOf(strippedLit)) { Node lit = Node::fromExpr( lemma[i] ).negate(); Trace("pf::tp") << "; preregistering and asserting " << lit << std::endl; NodeVisitor::run(preRegVisitor, lit); th->assertFact(lit, false); } } Debug("pf::tp") << "TheoryProof::printTheoryLemmaProof - calling th->check()" << std::endl; th->check(theory::Theory::EFFORT_FULL); Debug("pf::tp") << "TheoryProof::printTheoryLemmaProof - th->check() DONE" << std::endl; if(!oc.hasConflict()) { Trace("pf::tp") << "; conflict is null" << std::endl; Node lastLemma = oc.getLastLemma(); Assert(!lastLemma.isNull()); Trace("pf::tp") << "; ++ but got lemma: " << lastLemma << std::endl; if (lastLemma.getKind() == kind::OR) { Debug("pf::tp") << "OR lemma. Negating each child separately" << std::endl; for (unsigned i = 0; i < lastLemma.getNumChildren(); ++i) { if (lastLemma[i].getKind() == kind::NOT) { Trace("pf::tp") << "; asserting fact: " << lastLemma[i][0] << std::endl; th->assertFact(lastLemma[i][0], false); } else { Trace("pf::tp") << "; asserting fact: " << lastLemma[i].notNode() << std::endl; th->assertFact(lastLemma[i].notNode(), false); } } } else { Unreachable(); Assert(oc.getLastLemma().getKind() == kind::NOT); Debug("pf::tp") << "NOT lemma" << std::endl; Trace("pf::tp") << "; asserting fact: " << oc.getLastLemma()[0] << std::endl; th->assertFact(oc.getLastLemma()[0], false); } // Trace("pf::tp") << "; ++ but got lemma: " << oc.d_lemma << std::endl; // Trace("pf::tp") << "; asserting " << oc.d_lemma[1].negate() << std::endl; // th->assertFact(oc.d_lemma[1].negate(), false); // th->check(theory::Theory::EFFORT_FULL); } else { Debug("pf::tp") << "Calling oc.d_proof->toStream(os)" << std::endl; oc.getConflictProof().toStream(os, map); Debug("pf::tp") << "Calling oc.d_proof->toStream(os) -- DONE!" << std::endl; } Debug("pf::tp") << "About to delete the theory solver used for proving the lemma... " << std::endl; delete th; Debug("pf::tp") << "About to delete the theory solver used for proving the lemma: DONE! " << std::endl; } bool TheoryProofEngine::supportedTheory(theory::TheoryId id) { return (id == theory::THEORY_ARRAYS || id == theory::THEORY_ARITH || id == theory::THEORY_BV || id == theory::THEORY_UF || id == theory::THEORY_BOOL); } bool TheoryProofEngine::printsAsBool(const Node &n) { if (!n.getType().isBoolean()) { return false; } theory::TheoryId theory_id = theory::Theory::theoryOf(n); return getTheoryProof(theory_id)->printsAsBool(n); } BooleanProof::BooleanProof(TheoryProofEngine* proofEngine) : TheoryProof(NULL, proofEngine) {} void BooleanProof::registerTerm(Expr term) { Assert(term.getType().isBoolean()); if (term.isVariable() && d_declarations.find(term) == d_declarations.end()) { d_declarations.insert(term); return; } for (unsigned i = 0; i < term.getNumChildren(); ++i) { d_proofEngine->registerTerm(term[i]); } } theory::TheoryId BooleanProof::getTheoryId() { return theory::THEORY_BOOL; } void LFSCBooleanProof::printConstantDisequalityProof(std::ostream& os, Expr c1, Expr c2, const ProofLetMap &globalLetMap) { Node falseNode = NodeManager::currentNM()->mkConst(false); Node trueNode = NodeManager::currentNM()->mkConst(true); Assert(c1 == falseNode.toExpr() || c1 == trueNode.toExpr()); Assert(c2 == falseNode.toExpr() || c2 == trueNode.toExpr()); Assert(c1 != c2); if (c1 == trueNode.toExpr()) os << "t_t_neq_f"; else os << "(negsymm _ _ _ t_t_neq_f)"; } void LFSCBooleanProof::printOwnedTermAsType(Expr term, std::ostream& os, const ProofLetMap& map, TypeNode expectedType) { Assert(term.getType().isBoolean()); if (term.isVariable()) { os << ProofManager::sanitize(term); return; } Kind k = term.getKind(); switch(k) { case kind::OR: case kind::AND: if (options::lfscLetification() && term.getNumChildren() > 2) { // If letification is on, the entire term is probably a let expression. // However, we need to transform it from (and a b c) into (and a (and b c)) form first. Node currentExpression = term[term.getNumChildren() - 1]; for (int i = term.getNumChildren() - 2; i >= 0; --i) { NodeBuilder<> builder(k); builder << term[i]; builder << currentExpression.toExpr(); currentExpression = builder; } // The let map should already have the current expression. ProofLetMap::const_iterator it = map.find(currentExpression.toExpr()); if (it != map.end()) { unsigned id = it->second.id; unsigned count = it->second.count; if (count > LET_COUNT) { os << "let" << id; break; } } } // If letification is off or there were 2 children, same treatment as the other cases. CVC4_FALLTHROUGH; case kind::XOR: case kind::IMPLIES: case kind::NOT: // print the Boolean operators os << "(" << utils::toLFSCKind(k); if(term.getNumChildren() > 2) { // LFSC doesn't allow declarations with variable numbers of // arguments, so we have to flatten these N-ary versions. std::ostringstream paren; os << " "; for (unsigned i = 0; i < term.getNumChildren(); ++i) { if (printsAsBool(term[i])) os << "(p_app "; d_proofEngine->printBoundTerm(term[i], os, map); if (printsAsBool(term[i])) os << ")"; os << " "; if(i < term.getNumChildren() - 2) { os << "(" << utils::toLFSCKind(k) << " "; paren << ")"; } } os << paren.str() << ")"; } else { // this is for binary and unary operators for (unsigned i = 0; i < term.getNumChildren(); ++i) { os << " "; if (printsAsBool(term[i])) os << "(p_app "; d_proofEngine->printBoundTerm(term[i], os, map); if (printsAsBool(term[i])) os << ")"; } os << ")"; } return; case kind::CONST_BOOLEAN: os << (term.getConst() ? "true" : "false"); return; default: Unhandled() << k; } } void LFSCBooleanProof::printOwnedSort(Type type, std::ostream& os) { Assert(type.isBoolean()); os << "Bool"; } void LFSCBooleanProof::printSortDeclarations(std::ostream& os, std::ostream& paren) { // Nothing to do here at this point. } void LFSCBooleanProof::printTermDeclarations(std::ostream& os, std::ostream& paren) { for (ExprSet::const_iterator it = d_declarations.begin(); it != d_declarations.end(); ++it) { Expr term = *it; os << "(% " << ProofManager::sanitize(term) << " (term "; printSort(term.getType(), os); os <<")\n"; paren <<")"; } } void LFSCBooleanProof::printDeferredDeclarations(std::ostream& os, std::ostream& paren) { // Nothing to do here at this point. } void LFSCBooleanProof::printAliasingDeclarations(std::ostream& os, std::ostream& paren, const ProofLetMap &globalLetMap) { // Nothing to do here at this point. } void LFSCBooleanProof::printTheoryLemmaProof(std::vector& lemma, std::ostream& os, std::ostream& paren, const ProofLetMap& map) { Unreachable() << "No boolean lemmas yet!"; } bool LFSCBooleanProof::printsAsBool(const Node &n) { Kind k = n.getKind(); switch (k) { case kind::BOOLEAN_TERM_VARIABLE: case kind::VARIABLE: return true; default: return false; } } void TheoryProof::printConstantDisequalityProof(std::ostream& os, Expr c1, Expr c2, const ProofLetMap &globalLetMap) { // By default, we just print a trust statement. Specific theories can implement // better proofs. os << "(trust_f (not (= _ "; d_proofEngine->printBoundTerm(c1, os, globalLetMap); os << " "; d_proofEngine->printBoundTerm(c2, os, globalLetMap); os << ")))"; } void TheoryProof::printRewriteProof(std::ostream& os, const Node &n1, const Node &n2) { // This is the default for a rewrite proof: just a trust statement. ProofLetMap emptyMap; os << "(trust_f (iff "; d_proofEngine->printBoundTerm(n1.toExpr(), os, emptyMap); os << " "; d_proofEngine->printBoundTerm(n2.toExpr(), os, emptyMap); os << "))"; } void TheoryProof::printOwnedTerm(Expr term, std::ostream& os, const ProofLetMap& map, TypeNode expectedType) { this->printOwnedTermAsType(term, os, map, expectedType); } TypeNode TheoryProof::equalityType(const Expr& left, const Expr& right) { Assert(left.getType() == right.getType()) << "TheoryProof::equalityType(" << left << ", " << right << "):" << std::endl << "types disagree:" << std::endl << "\tleft: " << left.getType() << std::endl << "\tright:" << right.getType(); return TypeNode::fromType(left.getType()); } bool TheoryProof::match(TNode n1, TNode n2) { theory::TheoryId theoryId = this->getTheoryId(); ProofManager* pm = ProofManager::currentPM(); bool ufProof = (theoryId == theory::THEORY_UF); Debug(ufProof ? "pf::uf" : "mgd") << "match " << n1 << " " << n2 << std::endl; if (pm->hasOp(n1)) { n1 = pm->lookupOp(n1); } if (pm->hasOp(n2)) { n2 = pm->lookupOp(n2); } Debug(ufProof ? "pf::uf" : "mgd") << "+ match " << n1 << " " << n2 << std::endl; if (!ufProof) { Debug("pf::array") << "+ match: step 1" << std::endl; } if (n1 == n2) { return true; } if (n1.getType().isFunction() && n2.hasOperator()) { if (pm->hasOp(n2.getOperator())) { return n1 == pm->lookupOp(n2.getOperator()); } else { return n1 == n2.getOperator(); } } if (n2.getType().isFunction() && n1.hasOperator()) { if (pm->hasOp(n1.getOperator())) { return n2 == pm->lookupOp(n1.getOperator()); } else { return n2 == n1.getOperator(); } } if (n1.hasOperator() && n2.hasOperator() && n1.getOperator() != n2.getOperator()) { if (ufProof || !((n1.getKind() == kind::SELECT && n2.getKind() == kind::PARTIAL_SELECT_0) || (n1.getKind() == kind::SELECT && n2.getKind() == kind::PARTIAL_SELECT_1) || (n1.getKind() == kind::PARTIAL_SELECT_1 && n2.getKind() == kind::SELECT) || (n1.getKind() == kind::PARTIAL_SELECT_1 && n2.getKind() == kind::PARTIAL_SELECT_0) || (n1.getKind() == kind::PARTIAL_SELECT_0 && n2.getKind() == kind::SELECT) || (n1.getKind() == kind::PARTIAL_SELECT_0 && n2.getKind() == kind::PARTIAL_SELECT_1))) { return false; } } for (size_t i = 0; i < n1.getNumChildren() && i < n2.getNumChildren(); ++i) { if (n1[i] != n2[i]) { return false; } } return true; } int TheoryProof::assertAndPrint( const theory::eq::EqProof& pf, const ProofLetMap& map, std::shared_ptr subTrans, theory::eq::EqProof::PrettyPrinter* pPrettyPrinter) { theory::TheoryId theoryId = getTheoryId(); int neg = -1; Assert(theoryId == theory::THEORY_UF || theoryId == theory::THEORY_ARRAYS); bool ufProof = (theoryId == theory::THEORY_UF); std::string theoryName = theory::getStatsPrefix(theoryId); pf.debug_print(("pf::" + theoryName).c_str(), 0, pPrettyPrinter); Debug("pf::" + theoryName) << std::endl; Assert(pf.d_id == theory::eq::MERGED_THROUGH_TRANS); Assert(!pf.d_node.isNull()); Assert(pf.d_children.size() >= 2); subTrans->d_id = theory::eq::MERGED_THROUGH_TRANS; subTrans->d_node = pf.d_node; size_t i = 0; while (i < pf.d_children.size()) { // special treatment for uf and not for array if (ufProof || pf.d_children[i]->d_id != theory::eq::MERGED_THROUGH_CONGRUENCE) { pf.d_children[i]->d_node = simplifyBooleanNode(pf.d_children[i]->d_node); } // Look for the negative clause, with which we will form a contradiction. if (!pf.d_children[i]->d_node.isNull() && pf.d_children[i]->d_node.getKind() == kind::NOT) { Assert(neg < 0); (neg) = i; ++i; } // Handle congruence closures over equalities. else if (pf.d_children[i]->d_id == theory::eq::MERGED_THROUGH_CONGRUENCE && pf.d_children[i]->d_node.isNull()) { Debug("pf::" + theoryName) << "Handling congruence over equalities" << std::endl; // Gather the sequence of consecutive congruence closures. std::vector> congruenceClosures; unsigned count; Debug("pf::" + theoryName) << "Collecting congruence sequence" << std::endl; for (count = 0; i + count < pf.d_children.size() && pf.d_children[i + count]->d_id == theory::eq::MERGED_THROUGH_CONGRUENCE && pf.d_children[i + count]->d_node.isNull(); ++count) { Debug("pf::" + theoryName) << "Found a congruence: " << std::endl; pf.d_children[i + count]->debug_print( ("pf::" + theoryName).c_str(), 0, pPrettyPrinter); congruenceClosures.push_back(pf.d_children[i + count]); } Debug("pf::" + theoryName) << "Total number of congruences found: " << congruenceClosures.size() << std::endl; // Determine if the "target" of the congruence sequence appears right // before or right after the sequence. bool targetAppearsBefore = true; bool targetAppearsAfter = true; if ((i == 0) || (i == 1 && neg == 0)) { Debug("pf::" + theoryName) << "Target does not appear before" << std::endl; targetAppearsBefore = false; } if ((i + count >= pf.d_children.size()) || (!pf.d_children[i + count]->d_node.isNull() && pf.d_children[i + count]->d_node.getKind() == kind::NOT)) { Debug("pf::" + theoryName) << "Target does not appear after" << std::endl; targetAppearsAfter = false; } // Flow changes between uf and array if (ufProof) { // Assert that we have precisely at least one possible clause. Assert(targetAppearsBefore || targetAppearsAfter); // If both are valid, assume the one after the sequence is correct if (targetAppearsAfter && targetAppearsBefore) { targetAppearsBefore = false; } } else { // not a uf proof // Assert that we have precisely one target clause. Assert(targetAppearsBefore != targetAppearsAfter); } // Begin breaking up the congruences and ordering the equalities // correctly. std::vector> orderedEqualities; // Insert target clause first. if (targetAppearsBefore) { orderedEqualities.push_back(pf.d_children[i - 1]); // The target has already been added to subTrans; remove it. subTrans->d_children.pop_back(); } else { orderedEqualities.push_back(pf.d_children[i + count]); } // Start with the congruence closure closest to the target clause, and // work our way back/forward. if (targetAppearsBefore) { for (unsigned j = 0; j < count; ++j) { if (pf.d_children[i + j]->d_children[0]->d_id != theory::eq::MERGED_THROUGH_REFLEXIVITY) orderedEqualities.insert(orderedEqualities.begin(), pf.d_children[i + j]->d_children[0]); if (pf.d_children[i + j]->d_children[1]->d_id != theory::eq::MERGED_THROUGH_REFLEXIVITY) orderedEqualities.insert(orderedEqualities.end(), pf.d_children[i + j]->d_children[1]); } } else { for (unsigned j = 0; j < count; ++j) { if (pf.d_children[i + count - 1 - j]->d_children[0]->d_id != theory::eq::MERGED_THROUGH_REFLEXIVITY) orderedEqualities.insert( orderedEqualities.begin(), pf.d_children[i + count - 1 - j]->d_children[0]); if (pf.d_children[i + count - 1 - j]->d_children[1]->d_id != theory::eq::MERGED_THROUGH_REFLEXIVITY) orderedEqualities.insert( orderedEqualities.end(), pf.d_children[i + count - 1 - j]->d_children[1]); } } // Copy the result into the main transitivity proof. subTrans->d_children.insert(subTrans->d_children.end(), orderedEqualities.begin(), orderedEqualities.end()); // Increase i to skip over the children that have been processed. i += count; if (targetAppearsAfter) { ++i; } } // Else, just copy the child proof as is else { subTrans->d_children.push_back(pf.d_children[i]); ++i; } } bool disequalityFound = (neg >= 0); if (!disequalityFound) { Debug("pf::" + theoryName) << "A disequality was NOT found. UNSAT due to merged constants" << std::endl; Debug("pf::" + theoryName) << "Proof for: " << pf.d_node << std::endl; Assert(pf.d_node.getKind() == kind::EQUAL); Assert(pf.d_node.getNumChildren() == 2); Assert(pf.d_node[0].isConst() && pf.d_node[1].isConst()); } return neg; } std::pair TheoryProof::identicalEqualitiesPrinterHelper( bool evenLengthSequence, bool sequenceOver, const theory::eq::EqProof& pf, const ProofLetMap& map, const std::string subproofStr, std::stringstream* outStream, Node n, Node nodeAfterEqualitySequence) { theory::TheoryId theoryId = getTheoryId(); Assert(theoryId == theory::THEORY_UF || theoryId == theory::THEORY_ARRAYS); bool ufProof = (theoryId == theory::THEORY_UF); std::string theoryName = theory::getStatsPrefix(theoryId); if (evenLengthSequence) { // If the length is even, we need to apply transitivity for the "correct" // hand of the equality. Debug("pf::" + theoryName) << "Equality sequence of even length" << std::endl; Debug("pf::" + theoryName) << "n1 is: " << n << std::endl; Debug("pf::" + theoryName) << "pf-d_node is: " << pf.d_node << std::endl; Debug("pf::" + theoryName) << "Next node is: " << nodeAfterEqualitySequence << std::endl; (*outStream) << "(trans _ _ _ _ "; // If the sequence is at the very end of the transitivity proof, use // pf.d_node to guide us. if (!sequenceOver) { if (match(n[0], pf.d_node[0])) { n = n[0].eqNode(n[0]); (*outStream) << subproofStr << " (symm _ _ _ " << subproofStr << ")"; } else if (match(n[1], pf.d_node[1])) { n = n[1].eqNode(n[1]); (*outStream) << " (symm _ _ _ " << subproofStr << ")" << subproofStr; } else { Debug("pf::" + theoryName) << "Error: identical equalities over, but " "hands don't match what we're proving." << std::endl; Assert(false); } } else { // We have a "next node". Use it to guide us. if (!ufProof && nodeAfterEqualitySequence.getKind() == kind::NOT) { nodeAfterEqualitySequence = nodeAfterEqualitySequence[0]; } Assert(nodeAfterEqualitySequence.getKind() == kind::EQUAL); if ((n[0] == nodeAfterEqualitySequence[0]) || (n[0] == nodeAfterEqualitySequence[1])) { // Eliminate n[1] (*outStream) << subproofStr << " (symm _ _ _ " << subproofStr << ")"; n = n[0].eqNode(n[0]); } else if ((n[1] == nodeAfterEqualitySequence[0]) || (n[1] == nodeAfterEqualitySequence[1])) { // Eliminate n[0] (*outStream) << " (symm _ _ _ " << subproofStr << ")" << subproofStr; n = n[1].eqNode(n[1]); } else { Debug("pf::" + theoryName) << "Error: even length sequence, but I " "don't know which hand to keep!" << std::endl; Assert(false); } } (*outStream) << ")"; } else { Debug("pf::" + theoryName) << "Equality sequence length is odd!" << std::endl; (*outStream).str(subproofStr); } Debug("pf::" + theoryName) << "Have proven: " << n << std::endl; return std::make_pair(n, nodeAfterEqualitySequence); } } /* namespace CVC4 */