summaryrefslogtreecommitdiff
path: root/src/proof/proof_node_manager.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/proof/proof_node_manager.cpp')
-rw-r--r--src/proof/proof_node_manager.cpp407
1 files changed, 407 insertions, 0 deletions
diff --git a/src/proof/proof_node_manager.cpp b/src/proof/proof_node_manager.cpp
new file mode 100644
index 000000000..cf19eebdf
--- /dev/null
+++ b/src/proof/proof_node_manager.cpp
@@ -0,0 +1,407 @@
+/******************************************************************************
+ * Top contributors (to current version):
+ * Andrew Reynolds, Haniel Barbosa, Aina Niemetz
+ *
+ * This file is part of the cvc5 project.
+ *
+ * Copyright (c) 2009-2021 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.
+ * ****************************************************************************
+ *
+ * Implementation of proof node manager.
+ */
+
+#include "proof/proof_node_manager.h"
+
+#include <sstream>
+
+#include "options/proof_options.h"
+#include "proof/proof.h"
+#include "proof/proof_checker.h"
+#include "proof/proof_node.h"
+#include "proof/proof_node_algorithm.h"
+#include "theory/rewriter.h"
+
+using namespace cvc5::kind;
+
+namespace cvc5 {
+
+ProofNodeManager::ProofNodeManager(ProofChecker* pc) : d_checker(pc)
+{
+ d_true = NodeManager::currentNM()->mkConst(true);
+}
+
+std::shared_ptr<ProofNode> ProofNodeManager::mkNode(
+ PfRule id,
+ const std::vector<std::shared_ptr<ProofNode>>& children,
+ const std::vector<Node>& args,
+ Node expected)
+{
+ Trace("pnm") << "ProofNodeManager::mkNode " << id << " {" << expected.getId()
+ << "} " << expected << "\n";
+ Node res = checkInternal(id, children, args, expected);
+ if (res.isNull())
+ {
+ // if it was invalid, then we return the null node
+ return nullptr;
+ }
+ // otherwise construct the proof node and set its proven field
+ std::shared_ptr<ProofNode> pn =
+ std::make_shared<ProofNode>(id, children, args);
+ pn->d_proven = res;
+ return pn;
+}
+
+std::shared_ptr<ProofNode> ProofNodeManager::mkAssume(Node fact)
+{
+ Assert(!fact.isNull());
+ Assert(fact.getType().isBoolean());
+ return mkNode(PfRule::ASSUME, {}, {fact}, fact);
+}
+
+std::shared_ptr<ProofNode> ProofNodeManager::mkTrans(
+ const std::vector<std::shared_ptr<ProofNode>>& children, Node expected)
+{
+ Assert(!children.empty());
+ if (children.size() == 1)
+ {
+ Assert(expected.isNull() || children[0]->getResult() == expected);
+ return children[0];
+ }
+ return mkNode(PfRule::TRANS, children, {}, expected);
+}
+
+std::shared_ptr<ProofNode> ProofNodeManager::mkScope(
+ std::shared_ptr<ProofNode> pf,
+ std::vector<Node>& assumps,
+ bool ensureClosed,
+ bool doMinimize,
+ Node expected)
+{
+ if (!ensureClosed)
+ {
+ return mkNode(PfRule::SCOPE, {pf}, assumps, expected);
+ }
+ Trace("pnm-scope") << "ProofNodeManager::mkScope " << assumps << std::endl;
+ // we first ensure the assumptions are flattened
+ std::unordered_set<Node> ac{assumps.begin(), assumps.end()};
+ // map from the rewritten form of assumptions to the original. This is only
+ // computed in the rare case when we need rewriting to match the
+ // assumptions. An example of this is for Boolean constant equalities in
+ // scoped proofs from the proof equality engine.
+ std::unordered_map<Node, Node> acr;
+ // whether we have compute the map above
+ bool computedAcr = false;
+
+ // The free assumptions of the proof
+ std::map<Node, std::vector<std::shared_ptr<ProofNode>>> famap;
+ expr::getFreeAssumptionsMap(pf, famap);
+ std::unordered_set<Node> acu;
+ for (const std::pair<const Node, std::vector<std::shared_ptr<ProofNode>>>&
+ fa : famap)
+ {
+ Node a = fa.first;
+ if (ac.find(a) != ac.end())
+ {
+ // already covered by an assumption
+ acu.insert(a);
+ continue;
+ }
+ // trivial assumption
+ if (a == d_true)
+ {
+ Trace("pnm-scope") << "- justify trivial True assumption\n";
+ for (std::shared_ptr<ProofNode> pfs : fa.second)
+ {
+ Assert(pfs->getResult() == a);
+ updateNode(pfs.get(), PfRule::MACRO_SR_PRED_INTRO, {}, {a});
+ }
+ Trace("pnm-scope") << "...finished" << std::endl;
+ acu.insert(a);
+ continue;
+ }
+ Trace("pnm-scope") << "- try matching free assumption " << a << "\n";
+ // otherwise it may be due to symmetry?
+ Node aeqSym = CDProof::getSymmFact(a);
+ Trace("pnm-scope") << " - try sym " << aeqSym << "\n";
+ Node aMatch;
+ if (!aeqSym.isNull() && ac.count(aeqSym))
+ {
+ Trace("pnm-scope") << "- reorient assumption " << aeqSym << " via " << a
+ << " for " << fa.second.size() << " proof nodes"
+ << std::endl;
+ aMatch = aeqSym;
+ }
+ else
+ {
+ // Otherwise, may be derivable by rewriting. Note this is used in
+ // ensuring that proofs from the proof equality engine that involve
+ // equality with Boolean constants are closed.
+ if (!computedAcr)
+ {
+ computedAcr = true;
+ for (const Node& acc : ac)
+ {
+ Node accr = theory::Rewriter::rewrite(acc);
+ if (accr != acc)
+ {
+ acr[accr] = acc;
+ }
+ }
+ }
+ Node ar = theory::Rewriter::rewrite(a);
+ std::unordered_map<Node, Node>::iterator itr = acr.find(ar);
+ if (itr != acr.end())
+ {
+ aMatch = itr->second;
+ }
+ }
+
+ // if we found a match either by symmetry or rewriting, then we connect
+ // the assumption here.
+ if (!aMatch.isNull())
+ {
+ std::shared_ptr<ProofNode> pfaa = mkAssume(aMatch);
+ // must correct the orientation on this leaf
+ std::vector<std::shared_ptr<ProofNode>> children;
+ children.push_back(pfaa);
+ for (std::shared_ptr<ProofNode> pfs : fa.second)
+ {
+ Assert(pfs->getResult() == a);
+ // use SYMM if possible
+ if (aMatch == aeqSym)
+ {
+ updateNode(pfs.get(), PfRule::SYMM, children, {});
+ }
+ else
+ {
+ updateNode(pfs.get(), PfRule::MACRO_SR_PRED_TRANSFORM, children, {a});
+ }
+ }
+ Trace("pnm-scope") << "...finished" << std::endl;
+ acu.insert(aMatch);
+ continue;
+ }
+ // If we did not find a match, it is an error, since all free assumptions
+ // should be arguments to SCOPE.
+ std::stringstream ss;
+
+ bool dumpProofTraceOn = Trace.isOn("dump-proof-error");
+ if (dumpProofTraceOn)
+ {
+ ss << "The proof : " << *pf << std::endl;
+ }
+ ss << std::endl << "Free assumption: " << a << std::endl;
+ for (const Node& aprint : ac)
+ {
+ ss << "- assumption: " << aprint << std::endl;
+ }
+ if (!dumpProofTraceOn)
+ {
+ ss << "Use -t dump-proof-error for details on proof" << std::endl;
+ }
+ Unreachable() << "Generated a proof that is not closed by the scope: "
+ << ss.str() << std::endl;
+ }
+ if (acu.size() < ac.size())
+ {
+ // All assumptions should match a free assumption; if one does not, then
+ // the explanation could have been smaller.
+ for (const Node& a : ac)
+ {
+ if (acu.find(a) == acu.end())
+ {
+ Notice() << "ProofNodeManager::mkScope: assumption " << a
+ << " does not match a free assumption in proof" << std::endl;
+ }
+ }
+ }
+ if (doMinimize && acu.size() < ac.size())
+ {
+ assumps.clear();
+ assumps.insert(assumps.end(), acu.begin(), acu.end());
+ }
+ else if (ac.size() < assumps.size())
+ {
+ // remove duplicates to avoid redundant literals in clauses
+ assumps.clear();
+ assumps.insert(assumps.end(), ac.begin(), ac.end());
+ }
+ Node minExpected;
+ NodeManager* nm = NodeManager::currentNM();
+ Node exp;
+ if (assumps.empty())
+ {
+ // SCOPE with no arguments is a no-op, just return original
+ return pf;
+ }
+ Node conc = pf->getResult();
+ exp = assumps.size() == 1 ? assumps[0] : nm->mkNode(AND, assumps);
+ if (conc.isConst() && !conc.getConst<bool>())
+ {
+ minExpected = exp.notNode();
+ }
+ else
+ {
+ minExpected = nm->mkNode(IMPLIES, exp, conc);
+ }
+ return mkNode(PfRule::SCOPE, {pf}, assumps, minExpected);
+}
+
+bool ProofNodeManager::updateNode(
+ ProofNode* pn,
+ PfRule id,
+ const std::vector<std::shared_ptr<ProofNode>>& children,
+ const std::vector<Node>& args)
+{
+ return updateNodeInternal(pn, id, children, args, true);
+}
+
+bool ProofNodeManager::updateNode(ProofNode* pn, ProofNode* pnr)
+{
+ Assert(pn != nullptr);
+ Assert(pnr != nullptr);
+ if (pn->getResult() != pnr->getResult())
+ {
+ return false;
+ }
+ // can shortcut re-check of rule
+ return updateNodeInternal(
+ pn, pnr->getRule(), pnr->getChildren(), pnr->getArguments(), false);
+}
+
+Node ProofNodeManager::checkInternal(
+ PfRule id,
+ const std::vector<std::shared_ptr<ProofNode>>& children,
+ const std::vector<Node>& args,
+ Node expected)
+{
+ Node res;
+ if (d_checker)
+ {
+ // check with the checker, which takes expected as argument
+ res = d_checker->check(id, children, args, expected);
+ Assert(!res.isNull())
+ << "ProofNodeManager::checkInternal: failed to check proof";
+ }
+ else
+ {
+ // otherwise we trust the expected value, if it exists
+ Assert(!expected.isNull()) << "ProofNodeManager::checkInternal: no checker "
+ "or expected value provided";
+ res = expected;
+ }
+ return res;
+}
+
+ProofChecker* ProofNodeManager::getChecker() const { return d_checker; }
+
+std::shared_ptr<ProofNode> ProofNodeManager::clone(
+ std::shared_ptr<ProofNode> pn)
+{
+ const ProofNode* orig = pn.get();
+ std::unordered_map<const ProofNode*, std::shared_ptr<ProofNode>> visited;
+ std::unordered_map<const ProofNode*, std::shared_ptr<ProofNode>>::iterator it;
+ std::vector<const ProofNode*> visit;
+ std::shared_ptr<ProofNode> cloned;
+ visit.push_back(orig);
+ const ProofNode* cur;
+ while (!visit.empty())
+ {
+ cur = visit.back();
+ it = visited.find(cur);
+ if (it == visited.end())
+ {
+ visited[cur] = nullptr;
+ const std::vector<std::shared_ptr<ProofNode>>& children =
+ cur->getChildren();
+ for (const std::shared_ptr<ProofNode>& cp : children)
+ {
+ visit.push_back(cp.get());
+ }
+ continue;
+ }
+ visit.pop_back();
+ if (it->second.get() == nullptr)
+ {
+ std::vector<std::shared_ptr<ProofNode>> cchildren;
+ const std::vector<std::shared_ptr<ProofNode>>& children =
+ cur->getChildren();
+ for (const std::shared_ptr<ProofNode>& cp : children)
+ {
+ it = visited.find(cp.get());
+ Assert(it != visited.end());
+ Assert(it->second != nullptr);
+ cchildren.push_back(it->second);
+ }
+ cloned = std::make_shared<ProofNode>(
+ cur->getRule(), cchildren, cur->getArguments());
+ visited[cur] = cloned;
+ // we trust the above cloning does not change what is proven
+ cloned->d_proven = cur->d_proven;
+ }
+ }
+ Assert(visited.find(orig) != visited.end());
+ return visited[orig];
+}
+
+bool ProofNodeManager::updateNodeInternal(
+ ProofNode* pn,
+ PfRule id,
+ const std::vector<std::shared_ptr<ProofNode>>& children,
+ const std::vector<Node>& args,
+ bool needsCheck)
+{
+ Assert(pn != nullptr);
+ // ---------------- check for cyclic
+ if (options::proofEagerChecking())
+ {
+ std::unordered_set<const ProofNode*> visited;
+ for (const std::shared_ptr<ProofNode>& cpc : children)
+ {
+ if (expr::containsSubproof(cpc.get(), pn, visited))
+ {
+ std::stringstream ss;
+ ss << "ProofNodeManager::updateNode: attempting to make cyclic proof! "
+ << id << " " << pn->getResult() << ", children = " << std::endl;
+ for (const std::shared_ptr<ProofNode>& cp : children)
+ {
+ ss << " " << cp->getRule() << " " << cp->getResult() << std::endl;
+ }
+ ss << "Full children:" << std::endl;
+ for (const std::shared_ptr<ProofNode>& cp : children)
+ {
+ ss << " - ";
+ cp->printDebug(ss);
+ ss << std::endl;
+ }
+ Unreachable() << ss.str();
+ }
+ }
+ }
+ // ---------------- end check for cyclic
+
+ // should have already computed what is proven
+ Assert(!pn->d_proven.isNull())
+ << "ProofNodeManager::updateProofNode: invalid proof provided";
+ if (needsCheck)
+ {
+ // We expect to prove the same thing as before
+ Node res = checkInternal(id, children, args, pn->d_proven);
+ if (res.isNull())
+ {
+ // if it was invalid, then we do not update
+ return false;
+ }
+ // proven field should already be the same as the result
+ Assert(res == pn->d_proven);
+ }
+
+ // we update its value
+ pn->setValue(id, children, args);
+ return true;
+}
+
+} // namespace cvc5
generated by cgit on debian on lair
contact matthew@masot.net with questions or feedback