/********************* */ /*! \file proof_node_algorithm.cpp ** \verbatim ** Top contributors (to current version): ** Andrew Reynolds ** This file is part of the CVC4 project. ** Copyright (c) 2009-2020 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 Implementation of proof node algorithm utilities **/ #include "expr/proof_node_algorithm.h" namespace CVC4 { namespace expr { void getFreeAssumptions(ProofNode* pn, std::vector& assump) { std::map>> amap; std::shared_ptr spn = std::make_shared( pn->getRule(), pn->getChildren(), pn->getArguments()); getFreeAssumptionsMap(spn, amap); for (const std::pair>>& p : amap) { assump.push_back(p.first); } } void getFreeAssumptionsMap( std::shared_ptr pn, std::map>>& amap) { // proof should not be cyclic // visited set false after preorder traversal, true after postorder traversal std::unordered_map visited; std::unordered_map::iterator it; std::vector> visit; std::vector> traversing; // Maps a bound assumption to the number of bindings it is under // e.g. in (SCOPE (SCOPE (ASSUME x) (x y)) (y)), y would be mapped to 2 at // (ASSUME x), and x would be mapped to 1. // // This map is used to track which nodes are in scope while traversing the // DAG. The in-scope assumptions are keys in the map. They're removed when // their binding count drops to zero. Let's annotate the above example to // serve as an illustration: // // (SCOPE0 (SCOPE1 (ASSUME x) (x y)) (y)) // // This is how the map changes during the traversal: // after previsiting SCOPE0: { y: 1 } // after previsiting SCOPE1: { y: 2, x: 1 } // at ASSUME: { y: 2, x: 1 } (so x is in scope!) // after postvisiting SCOPE1: { y: 1 } // after postvisiting SCOPE2: {} // std::unordered_map scopeDepth; std::shared_ptr cur; visit.push_back(pn); do { cur = visit.back(); visit.pop_back(); it = visited.find(cur.get()); const std::vector& cargs = cur->getArguments(); if (it == visited.end()) { PfRule id = cur->getRule(); if (id == PfRule::ASSUME) { visited[cur.get()] = true; Assert(cargs.size() == 1); Node f = cargs[0]; if (!scopeDepth.count(f)) { amap[f].push_back(cur); } } else { if (id == PfRule::SCOPE) { // mark that its arguments are bound in the current scope for (const Node& a : cargs) { scopeDepth[a] += 1; } // will need to unbind the variables below } // The following loop cannot be merged with the loop above because the // same subproof visited[cur.get()] = false; visit.push_back(cur); traversing.push_back(cur); const std::vector>& cs = cur->getChildren(); for (const std::shared_ptr& cp : cs) { if (std::find(traversing.begin(), traversing.end(), cp) != traversing.end()) { Unhandled() << "getFreeAssumptionsMap: cyclic proof! (use " "--proof-new-eager-checking)" << std::endl; } visit.push_back(cp); } } } else if (!it->second) { Assert(!traversing.empty()); traversing.pop_back(); visited[cur.get()] = true; if (cur->getRule() == PfRule::SCOPE) { // unbind its assumptions for (const Node& a : cargs) { auto scopeCt = scopeDepth.find(a); Assert(scopeCt != scopeDepth.end()); scopeCt->second -= 1; if (scopeCt->second == 0) { scopeDepth.erase(scopeCt); } } } } } while (!visit.empty()); } bool containsSubproof(ProofNode* pn, ProofNode* pnc) { std::unordered_set visited; return containsSubproof(pn, pnc, visited); } bool containsSubproof(ProofNode* pn, ProofNode* pnc, std::unordered_set& visited) { std::unordered_map::iterator it; std::vector visit; visit.push_back(pn); const ProofNode* cur; while (!visit.empty()) { cur = visit.back(); visit.pop_back(); if (visited.find(cur) == visited.end()) { visited.insert(cur); if (cur == pnc) { return true; } const std::vector>& children = cur->getChildren(); for (const std::shared_ptr& cp : children) { visit.push_back(cp.get()); } } } return false; } } // namespace expr } // namespace CVC4