1 |
|
/****************************************************************************** |
2 |
|
* Top contributors (to current version): |
3 |
|
* Andrew Reynolds, Haniel Barbosa |
4 |
|
* |
5 |
|
* This file is part of the cvc5 project. |
6 |
|
* |
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 |
|
* **************************************************************************** |
12 |
|
* |
13 |
|
* Implementation of proof node algorithm utilities. |
14 |
|
*/ |
15 |
|
|
16 |
|
#include "proof/proof_node_algorithm.h" |
17 |
|
|
18 |
|
#include "proof/proof_node.h" |
19 |
|
|
20 |
|
namespace cvc5 { |
21 |
|
namespace expr { |
22 |
|
|
23 |
83012 |
void getFreeAssumptions(ProofNode* pn, std::vector<Node>& assump) |
24 |
|
{ |
25 |
166024 |
std::map<Node, std::vector<std::shared_ptr<ProofNode>>> amap; |
26 |
|
std::shared_ptr<ProofNode> spn = std::make_shared<ProofNode>( |
27 |
166024 |
pn->getRule(), pn->getChildren(), pn->getArguments()); |
28 |
83012 |
getFreeAssumptionsMap(spn, amap); |
29 |
108686 |
for (const std::pair<const Node, std::vector<std::shared_ptr<ProofNode>>>& p : |
30 |
83012 |
amap) |
31 |
|
{ |
32 |
108686 |
assump.push_back(p.first); |
33 |
|
} |
34 |
83012 |
} |
35 |
|
|
36 |
382594 |
void getFreeAssumptionsMap( |
37 |
|
std::shared_ptr<ProofNode> pn, |
38 |
|
std::map<Node, std::vector<std::shared_ptr<ProofNode>>>& amap) |
39 |
|
{ |
40 |
|
// proof should not be cyclic |
41 |
|
// visited set false after preorder traversal, true after postorder traversal |
42 |
765188 |
std::unordered_map<ProofNode*, bool> visited; |
43 |
382594 |
std::unordered_map<ProofNode*, bool>::iterator it; |
44 |
765188 |
std::vector<std::shared_ptr<ProofNode>> visit; |
45 |
765188 |
std::vector<std::shared_ptr<ProofNode>> traversing; |
46 |
|
// Maps a bound assumption to the number of bindings it is under |
47 |
|
// e.g. in (SCOPE (SCOPE (ASSUME x) (x y)) (y)), y would be mapped to 2 at |
48 |
|
// (ASSUME x), and x would be mapped to 1. |
49 |
|
// |
50 |
|
// This map is used to track which nodes are in scope while traversing the |
51 |
|
// DAG. The in-scope assumptions are keys in the map. They're removed when |
52 |
|
// their binding count drops to zero. Let's annotate the above example to |
53 |
|
// serve as an illustration: |
54 |
|
// |
55 |
|
// (SCOPE0 (SCOPE1 (ASSUME x) (x y)) (y)) |
56 |
|
// |
57 |
|
// This is how the map changes during the traversal: |
58 |
|
// after previsiting SCOPE0: { y: 1 } |
59 |
|
// after previsiting SCOPE1: { y: 2, x: 1 } |
60 |
|
// at ASSUME: { y: 2, x: 1 } (so x is in scope!) |
61 |
|
// after postvisiting SCOPE1: { y: 1 } |
62 |
|
// after postvisiting SCOPE2: {} |
63 |
|
// |
64 |
765188 |
std::unordered_map<Node, uint32_t> scopeDepth; |
65 |
765188 |
std::shared_ptr<ProofNode> cur; |
66 |
382594 |
visit.push_back(pn); |
67 |
18829858 |
do |
68 |
|
{ |
69 |
19212452 |
cur = visit.back(); |
70 |
19212452 |
visit.pop_back(); |
71 |
19212452 |
it = visited.find(cur.get()); |
72 |
19212452 |
const std::vector<Node>& cargs = cur->getArguments(); |
73 |
19212452 |
if (it == visited.end()) |
74 |
|
{ |
75 |
9634359 |
PfRule id = cur->getRule(); |
76 |
9634359 |
if (id == PfRule::ASSUME) |
77 |
|
{ |
78 |
3494277 |
visited[cur.get()] = true; |
79 |
3494277 |
Assert(cargs.size() == 1); |
80 |
6988554 |
Node f = cargs[0]; |
81 |
3494277 |
if (!scopeDepth.count(f)) |
82 |
|
{ |
83 |
2777476 |
amap[f].push_back(cur); |
84 |
|
} |
85 |
|
} |
86 |
|
else |
87 |
|
{ |
88 |
6140082 |
if (id == PfRule::SCOPE) |
89 |
|
{ |
90 |
|
// mark that its arguments are bound in the current scope |
91 |
931097 |
for (const Node& a : cargs) |
92 |
|
{ |
93 |
753197 |
scopeDepth[a] += 1; |
94 |
|
} |
95 |
|
// will need to unbind the variables below |
96 |
|
} |
97 |
|
// The following loop cannot be merged with the loop above because the |
98 |
|
// same subproof |
99 |
6140082 |
visited[cur.get()] = false; |
100 |
6140082 |
visit.push_back(cur); |
101 |
6140082 |
traversing.push_back(cur); |
102 |
6140082 |
const std::vector<std::shared_ptr<ProofNode>>& cs = cur->getChildren(); |
103 |
18829858 |
for (const std::shared_ptr<ProofNode>& cp : cs) |
104 |
|
{ |
105 |
38069328 |
if (std::find(traversing.begin(), traversing.end(), cp) |
106 |
38069328 |
!= traversing.end()) |
107 |
|
{ |
108 |
|
Unhandled() << "getFreeAssumptionsMap: cyclic proof! (use " |
109 |
|
"--proof-check=eager)" |
110 |
|
<< std::endl; |
111 |
|
} |
112 |
12689776 |
visit.push_back(cp); |
113 |
|
} |
114 |
|
} |
115 |
|
} |
116 |
9578093 |
else if (!it->second) |
117 |
|
{ |
118 |
6140082 |
Assert(!traversing.empty()); |
119 |
6140082 |
traversing.pop_back(); |
120 |
6140082 |
visited[cur.get()] = true; |
121 |
6140082 |
if (cur->getRule() == PfRule::SCOPE) |
122 |
|
{ |
123 |
|
// unbind its assumptions |
124 |
931097 |
for (const Node& a : cargs) |
125 |
|
{ |
126 |
753197 |
auto scopeCt = scopeDepth.find(a); |
127 |
753197 |
Assert(scopeCt != scopeDepth.end()); |
128 |
753197 |
scopeCt->second -= 1; |
129 |
753197 |
if (scopeCt->second == 0) |
130 |
|
{ |
131 |
678929 |
scopeDepth.erase(scopeCt); |
132 |
|
} |
133 |
|
} |
134 |
|
} |
135 |
|
} |
136 |
19212452 |
} while (!visit.empty()); |
137 |
382594 |
} |
138 |
|
|
139 |
2300412 |
bool containsAssumption(const ProofNode* pn, |
140 |
|
std::unordered_map<const ProofNode*, bool>& caMap) |
141 |
|
{ |
142 |
4600824 |
std::unordered_map<const ProofNode*, bool> visited; |
143 |
2300412 |
std::unordered_map<const ProofNode*, bool>::iterator it; |
144 |
4600824 |
std::vector<const ProofNode*> visit; |
145 |
2300412 |
visit.push_back(pn); |
146 |
2300412 |
bool foundAssumption = false; |
147 |
|
const ProofNode* cur; |
148 |
18672884 |
while (!visit.empty()) |
149 |
|
{ |
150 |
8186236 |
cur = visit.back(); |
151 |
8186236 |
visit.pop_back(); |
152 |
|
// have we already computed? |
153 |
8186236 |
it = caMap.find(cur); |
154 |
12022159 |
if (it != caMap.end()) |
155 |
|
{ |
156 |
|
// if cached, we set found assumption to true if applicable and continue |
157 |
3835923 |
if (it->second) |
158 |
|
{ |
159 |
2303897 |
foundAssumption = true; |
160 |
|
} |
161 |
3835923 |
continue; |
162 |
|
} |
163 |
4350313 |
it = visited.find(cur); |
164 |
4350313 |
if (it == visited.end()) |
165 |
|
{ |
166 |
2300412 |
PfRule r = cur->getRule(); |
167 |
2300412 |
if (r == PfRule::ASSUME) |
168 |
|
{ |
169 |
250511 |
visited[cur] = true; |
170 |
250511 |
caMap[cur] = true; |
171 |
250511 |
foundAssumption = true; |
172 |
|
} |
173 |
2049901 |
else if (!foundAssumption) |
174 |
|
{ |
175 |
|
// if we haven't found an assumption yet, recurse. Otherwise, we will |
176 |
|
// not bother computing whether this subproof contains an assumption |
177 |
|
// since we know its parent already contains one by another child. |
178 |
2049901 |
visited[cur] = false; |
179 |
2049901 |
visit.push_back(cur); |
180 |
|
const std::vector<std::shared_ptr<ProofNode>>& children = |
181 |
2049901 |
cur->getChildren(); |
182 |
5885824 |
for (const std::shared_ptr<ProofNode>& cp : children) |
183 |
|
{ |
184 |
3835923 |
visit.push_back(cp.get()); |
185 |
|
} |
186 |
|
} |
187 |
|
} |
188 |
2049901 |
else if (!it->second) |
189 |
|
{ |
190 |
2049901 |
visited[cur] = true; |
191 |
|
// we contain an assumption if we've found an assumption in a child |
192 |
2049901 |
caMap[cur] = foundAssumption; |
193 |
|
} |
194 |
|
} |
195 |
4600824 |
return caMap[cur]; |
196 |
|
} |
197 |
|
|
198 |
|
bool containsAssumption(const ProofNode* pn) |
199 |
|
{ |
200 |
|
std::unordered_map<const ProofNode*, bool> caMap; |
201 |
|
return containsAssumption(pn, caMap); |
202 |
|
} |
203 |
|
|
204 |
|
bool containsSubproof(ProofNode* pn, ProofNode* pnc) |
205 |
|
{ |
206 |
|
std::unordered_set<const ProofNode*> visited; |
207 |
|
return containsSubproof(pn, pnc, visited); |
208 |
|
} |
209 |
|
|
210 |
208 |
bool containsSubproof(ProofNode* pn, |
211 |
|
ProofNode* pnc, |
212 |
|
std::unordered_set<const ProofNode*>& visited) |
213 |
|
{ |
214 |
208 |
std::unordered_map<const ProofNode*, bool>::iterator it; |
215 |
416 |
std::vector<const ProofNode*> visit; |
216 |
208 |
visit.push_back(pn); |
217 |
|
const ProofNode* cur; |
218 |
3250 |
while (!visit.empty()) |
219 |
|
{ |
220 |
1521 |
cur = visit.back(); |
221 |
1521 |
visit.pop_back(); |
222 |
1521 |
if (visited.find(cur) == visited.end()) |
223 |
|
{ |
224 |
1390 |
visited.insert(cur); |
225 |
1390 |
if (cur == pnc) |
226 |
|
{ |
227 |
|
return true; |
228 |
|
} |
229 |
|
const std::vector<std::shared_ptr<ProofNode>>& children = |
230 |
1390 |
cur->getChildren(); |
231 |
2703 |
for (const std::shared_ptr<ProofNode>& cp : children) |
232 |
|
{ |
233 |
1313 |
visit.push_back(cp.get()); |
234 |
|
} |
235 |
|
} |
236 |
|
} |
237 |
208 |
return false; |
238 |
|
} |
239 |
|
|
240 |
49738 |
bool isSingletonClause(TNode res, |
241 |
|
const std::vector<Node>& children, |
242 |
|
const std::vector<Node>& args) |
243 |
|
{ |
244 |
49738 |
if (res.getKind() != kind::OR) |
245 |
|
{ |
246 |
8714 |
return true; |
247 |
|
} |
248 |
|
size_t i; |
249 |
82048 |
Node trueNode = NodeManager::currentNM()->mkConst(true); |
250 |
|
// Find out the last child to introduced res, if any. We only need to |
251 |
|
// look at the last one because any previous introduction would have |
252 |
|
// been eliminated. |
253 |
|
// |
254 |
|
// After the loop finishes i is the index of the child C_i that |
255 |
|
// introduced res. If i=0 none of the children introduced res as a |
256 |
|
// subterm and therefore it cannot be a singleton clause. |
257 |
916165 |
for (i = children.size(); i > 0; --i) |
258 |
|
{ |
259 |
|
// only non-singleton clauses may be introducing |
260 |
|
// res, so we only care about non-singleton or nodes. We check then |
261 |
|
// against the kind and whether the whole or node occurs as a pivot of |
262 |
|
// the respective resolution |
263 |
875235 |
if (children[i - 1].getKind() != kind::OR) |
264 |
|
{ |
265 |
213862 |
continue; |
266 |
|
} |
267 |
661373 |
size_t pivotIndex = (i != 1) ? 2 * (i - 1) - 1 : 1; |
268 |
1322746 |
if (args[pivotIndex] == children[i - 1] |
269 |
1322746 |
|| args[pivotIndex].notNode() == children[i - 1]) |
270 |
|
{ |
271 |
1003 |
continue; |
272 |
|
} |
273 |
|
// if res occurs as a subterm of a non-singleton premise |
274 |
1981110 |
if (std::find(children[i - 1].begin(), children[i - 1].end(), res) |
275 |
1981110 |
!= children[i - 1].end()) |
276 |
|
{ |
277 |
94 |
break; |
278 |
|
} |
279 |
|
} |
280 |
|
|
281 |
|
// If res is a subterm of one of the children we still need to check if |
282 |
|
// that subterm is eliminated |
283 |
41024 |
if (i > 0) |
284 |
|
{ |
285 |
186 |
bool posFirst = (i == 1) ? (args[0] == trueNode) |
286 |
186 |
: (args[(2 * (i - 1)) - 2] == trueNode); |
287 |
188 |
Node pivot = (i == 1) ? args[1] : args[(2 * (i - 1)) - 1]; |
288 |
|
|
289 |
|
// Check if it is eliminated by the previous resolution step |
290 |
188 |
if ((res == pivot && !posFirst) || (res.notNode() == pivot && posFirst) |
291 |
282 |
|| (pivot.notNode() == res && posFirst)) |
292 |
|
{ |
293 |
|
// We decrease i by one, since it could have been the case that i |
294 |
|
// was equal to children.size(), so that we return false in the end |
295 |
|
--i; |
296 |
|
} |
297 |
|
else |
298 |
|
{ |
299 |
|
// Otherwise check if any subsequent premise eliminates it |
300 |
182 |
for (; i < children.size(); ++i) |
301 |
|
{ |
302 |
53 |
posFirst = args[(2 * i) - 2] == trueNode; |
303 |
53 |
pivot = args[(2 * i) - 1]; |
304 |
|
// To eliminate res, the clause must contain it with opposite |
305 |
|
// polarity. There are three successful cases, according to the |
306 |
|
// pivot and its sign |
307 |
|
// |
308 |
|
// - res is the same as the pivot and posFirst is true, which |
309 |
|
// means that the clause contains its negation and eliminates it |
310 |
|
// |
311 |
|
// - res is the negation of the pivot and posFirst is false, so |
312 |
|
// the clause contains the node whose negation is res. Note that |
313 |
|
// this case may either be res.notNode() == pivot or res == |
314 |
|
// pivot.notNode(). |
315 |
115 |
if ((res == pivot && posFirst) || (res.notNode() == pivot && !posFirst) |
316 |
150 |
|| (pivot.notNode() == res && !posFirst)) |
317 |
|
{ |
318 |
9 |
break; |
319 |
|
} |
320 |
|
} |
321 |
|
} |
322 |
|
} |
323 |
|
// if not eliminated (loop went to the end), then it's a singleton |
324 |
|
// clause |
325 |
41024 |
return i == children.size(); |
326 |
|
} |
327 |
|
|
328 |
|
} // namespace expr |
329 |
31137 |
} // namespace cvc5 |