1 |
|
/****************************************************************************** |
2 |
|
* Top contributors (to current version): |
3 |
|
* Haniel Barbosa, Andrew Reynolds, Aina Niemetz |
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 a proof as produced by the equality engine. |
14 |
|
*/ |
15 |
|
|
16 |
|
#include "theory/uf/eq_proof.h" |
17 |
|
|
18 |
|
#include "base/configuration.h" |
19 |
|
#include "expr/proof.h" |
20 |
|
#include "expr/proof_checker.h" |
21 |
|
#include "options/uf_options.h" |
22 |
|
|
23 |
|
namespace cvc5 { |
24 |
|
namespace theory { |
25 |
|
namespace eq { |
26 |
|
|
27 |
|
void EqProof::debug_print(const char* c, unsigned tb) const |
28 |
|
{ |
29 |
|
std::stringstream ss; |
30 |
|
debug_print(ss, tb); |
31 |
|
Debug(c) << ss.str(); |
32 |
|
} |
33 |
|
|
34 |
|
void EqProof::debug_print(std::ostream& os, unsigned tb) const |
35 |
|
{ |
36 |
|
for (unsigned i = 0; i < tb; i++) |
37 |
|
{ |
38 |
|
os << " "; |
39 |
|
} |
40 |
|
os << d_id << "("; |
41 |
|
if (d_children.empty() && d_node.isNull()) |
42 |
|
{ |
43 |
|
os << ")"; |
44 |
|
return; |
45 |
|
} |
46 |
|
if (!d_node.isNull()) |
47 |
|
{ |
48 |
|
os << std::endl; |
49 |
|
for (unsigned i = 0; i < tb + 1; ++i) |
50 |
|
{ |
51 |
|
os << " "; |
52 |
|
} |
53 |
|
os << d_node << (!d_children.empty() ? "," : ""); |
54 |
|
} |
55 |
|
unsigned size = d_children.size(); |
56 |
|
for (unsigned i = 0; i < size; ++i) |
57 |
|
{ |
58 |
|
os << std::endl; |
59 |
|
d_children[i]->debug_print(os, tb + 1); |
60 |
|
if (i < size - 1) |
61 |
|
{ |
62 |
|
for (unsigned j = 0; j < tb + 1; ++j) |
63 |
|
{ |
64 |
|
os << " "; |
65 |
|
} |
66 |
|
os << ","; |
67 |
|
} |
68 |
|
} |
69 |
|
if (size > 0) |
70 |
|
{ |
71 |
|
for (unsigned i = 0; i < tb; ++i) |
72 |
|
{ |
73 |
|
os << " "; |
74 |
|
} |
75 |
|
} |
76 |
|
os << ")" << std::endl; |
77 |
|
} |
78 |
|
|
79 |
425513 |
void EqProof::cleanReflPremises(std::vector<Node>& premises) const |
80 |
|
{ |
81 |
851026 |
std::vector<Node> newPremises; |
82 |
425513 |
unsigned size = premises.size(); |
83 |
1462477 |
for (unsigned i = 0; i < size; ++i) |
84 |
|
{ |
85 |
1036964 |
if (premises[i][0] == premises[i][1]) |
86 |
|
{ |
87 |
196301 |
continue; |
88 |
|
} |
89 |
840663 |
newPremises.push_back(premises[i]); |
90 |
|
} |
91 |
425513 |
if (newPremises.size() != size) |
92 |
|
{ |
93 |
170002 |
Trace("eqproof-conv") << "EqProof::cleanReflPremises: removed " |
94 |
85001 |
<< (newPremises.size() >= size |
95 |
170002 |
? newPremises.size() - size |
96 |
85001 |
: 0) |
97 |
85001 |
<< " refl premises from " << premises << "\n"; |
98 |
85001 |
premises.clear(); |
99 |
85001 |
premises.insert(premises.end(), newPremises.begin(), newPremises.end()); |
100 |
170002 |
Trace("eqproof-conv") << "EqProof::cleanReflPremises: new premises " |
101 |
85001 |
<< premises << "\n"; |
102 |
|
} |
103 |
425513 |
} |
104 |
|
|
105 |
160610 |
bool EqProof::expandTransitivityForDisequalities( |
106 |
|
Node conclusion, |
107 |
|
std::vector<Node>& premises, |
108 |
|
CDProof* p, |
109 |
|
std::unordered_set<Node>& assumptions) const |
110 |
|
{ |
111 |
321220 |
Trace("eqproof-conv") |
112 |
|
<< "EqProof::expandTransitivityForDisequalities: check if need " |
113 |
160610 |
"to expand transitivity step to conclude " |
114 |
160610 |
<< conclusion << " from premises " << premises << "\n"; |
115 |
|
// Check premises to see if any of the form (= (= t1 t2) false), modulo |
116 |
|
// symmetry |
117 |
160610 |
unsigned size = premises.size(); |
118 |
|
// termPos is, in (= (= t1 t2) false) or (= false (= t1 t2)), the position of |
119 |
|
// the equality. When the i-th premise has that form, offending = i |
120 |
160610 |
unsigned termPos = 2, offending = size; |
121 |
717126 |
for (unsigned i = 0; i < size; ++i) |
122 |
|
{ |
123 |
556516 |
Assert(premises[i].getKind() == kind::EQUAL); |
124 |
1661828 |
for (unsigned j = 0; j < 2; ++j) |
125 |
|
{ |
126 |
3330234 |
if (premises[i][j].getKind() == kind::CONST_BOOLEAN |
127 |
1119657 |
&& !premises[i][j].getConst<bool>() |
128 |
3338056 |
&& premises[i][1 - j].getKind() == kind::EQUAL) |
129 |
|
{ |
130 |
|
// there is only one offending equality |
131 |
4766 |
Assert(offending == size); |
132 |
4766 |
offending = i; |
133 |
4766 |
termPos = 1 - j; |
134 |
4766 |
break; |
135 |
|
} |
136 |
|
} |
137 |
|
} |
138 |
|
// if no equality of the searched form, nothing to do |
139 |
160610 |
if (offending == size) |
140 |
|
{ |
141 |
155844 |
return false; |
142 |
|
} |
143 |
4766 |
NodeManager* nm = NodeManager::currentNM(); |
144 |
4766 |
Assert(termPos == 0 || termPos == 1); |
145 |
9532 |
Trace("eqproof-conv") << "EqProof::expandTransitivityForDisequalities: found " |
146 |
4766 |
"offending equality at index " |
147 |
4766 |
<< offending << " : " << premises[offending] << "\n"; |
148 |
|
// collect the premises to be used in the expansion, which are all but the |
149 |
|
// offending one |
150 |
9532 |
std::vector<Node> expansionPremises; |
151 |
17754 |
for (unsigned i = 0; i < size; ++i) |
152 |
|
{ |
153 |
12988 |
if (i != offending) |
154 |
|
{ |
155 |
8222 |
expansionPremises.push_back(premises[i]); |
156 |
|
} |
157 |
|
} |
158 |
|
// Eliminate spurious premises. Reasoning below assumes no refl steps. |
159 |
4766 |
cleanReflPremises(expansionPremises); |
160 |
4766 |
Assert(!expansionPremises.empty()); |
161 |
|
// Check if we are in the substitution case |
162 |
9532 |
Node expansionConclusion; |
163 |
9532 |
std::vector<Node> substPremises; |
164 |
4766 |
bool inSubstCase = false, substConclusionInReverseOrder = false; |
165 |
9532 |
if ((conclusion[0].getKind() == kind::CONST_BOOLEAN) |
166 |
4766 |
!= (conclusion[1].getKind() == kind::CONST_BOOLEAN)) |
167 |
|
{ |
168 |
3052 |
inSubstCase = true; |
169 |
|
// reorder offending premise if constant is the first argument |
170 |
3052 |
if (termPos == 1) |
171 |
|
{ |
172 |
2869 |
premises[offending] = |
173 |
5738 |
premises[offending][1].eqNode(premises[offending][0]); |
174 |
|
} |
175 |
|
// reorder conclusion if constant is the first argument |
176 |
9156 |
conclusion = conclusion[1].getKind() == kind::CONST_BOOLEAN |
177 |
6104 |
? conclusion |
178 |
|
: conclusion[1].eqNode(conclusion[0]); |
179 |
|
// equality term in premise disequality |
180 |
6104 |
Node premiseTermEq = premises[offending][0]; |
181 |
|
// equality term in conclusion disequality |
182 |
6104 |
Node conclusionTermEq = conclusion[0]; |
183 |
6104 |
Trace("eqproof-conv") |
184 |
|
<< "EqProof::expandTransitivityForDisequalities: Substitition " |
185 |
3052 |
"case. Need to build subst from " |
186 |
3052 |
<< premiseTermEq << " to " << conclusionTermEq << "\n"; |
187 |
|
// If only one argument in the premise is substituted, premiseTermEq and |
188 |
|
// conclusionTermEq will share one argument and the other argument change |
189 |
|
// defines the single substitution. Otherwise both arguments are replaced, |
190 |
|
// so there are two substitutions. |
191 |
6104 |
std::vector<Node> subs[2]; |
192 |
3052 |
subs[0].push_back(premiseTermEq[0]); |
193 |
3052 |
subs[1].push_back(premiseTermEq[1]); |
194 |
|
// which of the arguments of premiseTermEq, if any, is equal to one argument |
195 |
|
// of conclusionTermEq |
196 |
3052 |
int equalArg = -1; |
197 |
9156 |
for (unsigned i = 0; i < 2; ++i) |
198 |
|
{ |
199 |
14297 |
for (unsigned j = 0; j < 2; ++j) |
200 |
|
{ |
201 |
10873 |
if (premiseTermEq[i] == conclusionTermEq[j]) |
202 |
|
{ |
203 |
2680 |
equalArg = i; |
204 |
|
// identity sub |
205 |
2680 |
subs[i].push_back(conclusionTermEq[j]); |
206 |
|
// sub that changes argument |
207 |
2680 |
subs[1 - i].push_back(conclusionTermEq[1 - j]); |
208 |
|
// wither e.g. (= t1 t2), with sub t1->t3, becomes (= t2 t3) |
209 |
2680 |
substConclusionInReverseOrder = i != j; |
210 |
2680 |
break; |
211 |
|
} |
212 |
|
} |
213 |
|
} |
214 |
|
// simple case of single substitution |
215 |
3052 |
if (equalArg >= 0) |
216 |
|
{ |
217 |
|
// case of |
218 |
|
// (= (= t1 t2) false) (= t1 x1) ... (= xn t3) |
219 |
|
// -------------------------------------------- EQP::TR |
220 |
|
// (= (= t3 t2) false) |
221 |
|
// where |
222 |
|
// |
223 |
|
// (= t1 x1) ... (= xn t3) - expansion premises |
224 |
|
// ------------------------ TRANS |
225 |
|
// (= t1 t3) - expansion conclusion |
226 |
|
// |
227 |
|
// will be the expansion made to justify the substitution for t1->t3. |
228 |
2680 |
expansionConclusion = subs[1 - equalArg][0].eqNode(subs[1 - equalArg][1]); |
229 |
5360 |
Trace("eqproof-conv") << "EqProof::expandTransitivityForDisequalities: " |
230 |
2680 |
"Need to expand premises into " |
231 |
2680 |
<< expansionConclusion << "\n"; |
232 |
|
// add refl step for the substitition t2->t2 |
233 |
5360 |
p->addStep(subs[equalArg][0].eqNode(subs[equalArg][1]), |
234 |
|
PfRule::REFL, |
235 |
|
{}, |
236 |
5360 |
{subs[equalArg][0]}); |
237 |
|
} |
238 |
|
else |
239 |
|
{ |
240 |
|
// Hard case. We determine, and justify, the substitutions t1->t3/t4 and |
241 |
|
// t2->t3/t4 based on the expansion premises. |
242 |
744 |
Trace("eqproof-conv") << "EqProof::expandTransitivityForDisequalities: " |
243 |
372 |
"Need two substitutions. Look for " |
244 |
744 |
<< premiseTermEq[0] << " and " << premiseTermEq[1] |
245 |
372 |
<< " in premises " << expansionPremises << "\n"; |
246 |
744 |
Assert(expansionPremises.size() >= 2) |
247 |
|
<< "Less than 2 expansion premises for substituting BOTH terms in " |
248 |
|
"disequality.\nDisequality: " |
249 |
372 |
<< premises[offending] |
250 |
|
<< "\nExpansion premises: " << expansionPremises |
251 |
372 |
<< "\nConclusion: " << conclusion << "\n"; |
252 |
|
// Easier case where we can determine the substitutions by directly |
253 |
|
// looking at the premises, i.e. the two expansion premises are for |
254 |
|
// example (= t1 t3) and (= t2 t4) |
255 |
372 |
if (expansionPremises.size() == 2) |
256 |
|
{ |
257 |
|
// iterate over args to be substituted |
258 |
909 |
for (unsigned i = 0; i < 2; ++i) |
259 |
|
{ |
260 |
|
// iterate over premises |
261 |
1818 |
for (unsigned j = 0; j < 2; ++j) |
262 |
|
{ |
263 |
|
// iterate over args in premise |
264 |
2878 |
for (unsigned k = 0; k < 2; ++k) |
265 |
|
{ |
266 |
2272 |
if (premiseTermEq[i] == expansionPremises[j][k]) |
267 |
|
{ |
268 |
606 |
subs[i].push_back(expansionPremises[j][1 - k]); |
269 |
606 |
break; |
270 |
|
} |
271 |
|
} |
272 |
|
} |
273 |
1212 |
Assert(subs[i].size() == 2) |
274 |
606 |
<< " did not find term " << subs[i][0] << "\n"; |
275 |
|
// check if argument to be substituted is in the same order in the |
276 |
|
// conclusion |
277 |
606 |
substConclusionInReverseOrder = |
278 |
1212 |
premiseTermEq[i] != conclusionTermEq[i]; |
279 |
|
} |
280 |
|
} |
281 |
|
else |
282 |
|
{ |
283 |
138 |
Trace("eqproof-conv") << "EqProof::expandTransitivityForDisequalities: " |
284 |
|
"Build transitivity chains " |
285 |
69 |
"for two subs among more than 2 premises: " |
286 |
69 |
<< expansionPremises << "\n"; |
287 |
|
// Hardest case. Try building a transitivity chain for (= t1 t3). If it |
288 |
|
// can be built, use the remaining expansion premises to build a chain |
289 |
|
// for (= t2 t4). Otherwise build it for (= t1 t4) and then build it for |
290 |
|
// (= t2 t3). It should always succeed. |
291 |
69 |
subs[0].push_back(conclusionTermEq[0]); |
292 |
69 |
subs[1].push_back(conclusionTermEq[1]); |
293 |
207 |
for (unsigned i = 0; i < 2; ++i) |
294 |
|
{ |
295 |
|
// copy premises, since buildTransitivityChain is destructive |
296 |
|
std::vector<Node> copy1ofExpPremises(expansionPremises.begin(), |
297 |
276 |
expansionPremises.end()); |
298 |
276 |
Node transConclusion1 = subs[0][0].eqNode(subs[0][1]); |
299 |
138 |
if (!buildTransitivityChain(transConclusion1, copy1ofExpPremises)) |
300 |
|
{ |
301 |
|
AlwaysAssert(i == 0) |
302 |
|
<< "Couldn't find sub at all for substituting BOTH terms in " |
303 |
|
"disequality.\nDisequality: " |
304 |
|
<< premises[offending] |
305 |
|
<< "\nExpansion premises: " << expansionPremises |
306 |
|
<< "\nConclusion: " << conclusion << "\n"; |
307 |
|
// Failed. So flip sub and try again |
308 |
|
subs[0][1] = conclusionTermEq[1]; |
309 |
|
subs[1][1] = conclusionTermEq[0]; |
310 |
|
substConclusionInReverseOrder = false; |
311 |
|
continue; |
312 |
|
} |
313 |
|
// build next chain |
314 |
|
std::vector<Node> copy2ofExpPremises(expansionPremises.begin(), |
315 |
276 |
expansionPremises.end()); |
316 |
276 |
Node transConclusion2 = subs[1][0].eqNode(subs[1][1]); |
317 |
138 |
if (!buildTransitivityChain(transConclusion2, copy2ofExpPremises)) |
318 |
|
{ |
319 |
|
Unreachable() << "Found sub " << transConclusion1 |
320 |
|
<< " but not other sub " << transConclusion2 |
321 |
|
<< ".\nDisequality: " << premises[offending] |
322 |
|
<< "\nExpansion premises: " << expansionPremises |
323 |
|
<< "\nConclusion: " << conclusion << "\n"; |
324 |
|
} |
325 |
276 |
Trace("eqproof-conv") |
326 |
138 |
<< "EqProof::expandTransitivityForDisequalities: Built trans " |
327 |
|
"chains: " |
328 |
|
"for two subs among more than 2 premises:\n"; |
329 |
276 |
Trace("eqproof-conv") |
330 |
138 |
<< "EqProof::expandTransitivityForDisequalities: " |
331 |
138 |
<< transConclusion1 << " <- " << copy1ofExpPremises << "\n"; |
332 |
276 |
Trace("eqproof-conv") |
333 |
138 |
<< "EqProof::expandTransitivityForDisequalities: " |
334 |
138 |
<< transConclusion2 << " <- " << copy2ofExpPremises << "\n"; |
335 |
|
// do transitivity steps if need be to justify each substitution |
336 |
276 |
if (copy1ofExpPremises.size() > 1 |
337 |
138 |
&& !assumptions.count(transConclusion1)) |
338 |
|
{ |
339 |
72 |
p->addStep( |
340 |
|
transConclusion1, PfRule::TRANS, copy1ofExpPremises, {}, true); |
341 |
|
} |
342 |
276 |
if (copy2ofExpPremises.size() > 1 |
343 |
138 |
&& !assumptions.count(transConclusion2)) |
344 |
|
{ |
345 |
78 |
p->addStep( |
346 |
|
transConclusion2, PfRule::TRANS, copy2ofExpPremises, {}, true); |
347 |
|
} |
348 |
|
} |
349 |
|
} |
350 |
|
} |
351 |
6104 |
Trace("eqproof-conv") |
352 |
3052 |
<< "EqProof::expandTransitivityForDisequalities: Built substutitions " |
353 |
3052 |
<< subs[0] << " and " << subs[1] << " to map " << premiseTermEq |
354 |
3052 |
<< " -> " << conclusionTermEq << "\n"; |
355 |
6104 |
Assert(subs[0][1] == conclusion[0][0] || subs[0][1] == conclusion[0][1]) |
356 |
|
<< "EqProof::expandTransitivityForDisequalities: First substitution " |
357 |
3052 |
<< subs[0] << " doest not map to conclusion " << conclusion << "\n"; |
358 |
6104 |
Assert(subs[1][1] == conclusion[0][0] || subs[1][1] == conclusion[0][1]) |
359 |
|
<< "EqProof::expandTransitivityForDisequalities: Second substitution " |
360 |
3052 |
<< subs[1] << " doest not map to conclusion " << conclusion << "\n"; |
361 |
|
// In the premises for the original conclusion, the substitution of |
362 |
|
// premiseTermEq (= t1 t2) into conclusionTermEq (= t3 t4) is stored |
363 |
|
// reversed, i.e. as (= (= t3 t4) (= t1 t2)), since the transitivity with |
364 |
|
// the disequality is built as as |
365 |
|
// (= (= t3 t4) (= t1 t2)) (= (= t1 t2) false) |
366 |
|
// --------------------------------------------------------------------- TR |
367 |
|
// (= (= t3 t4) false) |
368 |
3052 |
substPremises.push_back(subs[0][1].eqNode(subs[0][0])); |
369 |
3052 |
substPremises.push_back(subs[1][1].eqNode(subs[1][0])); |
370 |
|
} |
371 |
|
else |
372 |
|
{ |
373 |
|
// In simple case the conclusion is always, modulo symmetry, false = true |
374 |
1714 |
Assert(conclusion[0].getKind() == kind::CONST_BOOLEAN |
375 |
|
&& conclusion[1].getKind() == kind::CONST_BOOLEAN); |
376 |
|
// The expansion conclusion is the same as the equality term in the |
377 |
|
// disequality, which is going to be justified by a transitivity step from |
378 |
|
// the expansion premises |
379 |
1714 |
expansionConclusion = premises[offending][termPos]; |
380 |
|
} |
381 |
|
// Unless we are in the double-substitution case, the proof has the shape |
382 |
|
// |
383 |
|
// ... ... ... ... - expansionPremises |
384 |
|
// ------------------ TRANS |
385 |
|
// (= (= (t t') false) (= t'' t''') - expansionConclusion |
386 |
|
// ---------------------------------------------- TRANS or PRED_TRANSFORM |
387 |
|
// conclusion |
388 |
|
// |
389 |
|
// although note that if it's a TRANS step, (= t'' t''') will be turned into a |
390 |
|
// predicate equality and the premises are ordered. |
391 |
|
// |
392 |
|
// We build the transitivity step for the expansionConclusion here. It being |
393 |
|
// non-null marks that we are not in the double-substitution case. |
394 |
4766 |
if (!expansionConclusion.isNull()) |
395 |
|
{ |
396 |
8788 |
Trace("eqproof-conv") |
397 |
4394 |
<< "EqProof::expandTransitivityForDisequalities: need to derive " |
398 |
4394 |
<< expansionConclusion << " with premises " << expansionPremises |
399 |
4394 |
<< "\n"; |
400 |
8788 |
Assert(expansionPremises.size() > 1 |
401 |
|
|| expansionConclusion == expansionPremises.back() |
402 |
|
|| (expansionConclusion[0] == expansionPremises.back()[1] |
403 |
|
&& expansionConclusion[1] == expansionPremises.back()[0])) |
404 |
4394 |
<< "single expansion premise " << expansionPremises.back() |
405 |
4394 |
<< " is not the same as expansionConclusion " << expansionConclusion |
406 |
|
<< " and not its symmetric\n"; |
407 |
|
// We track assumptions to avoid cyclic proofs, which can happen in EqProofs |
408 |
|
// such as: |
409 |
|
// |
410 |
|
// (= l1 "") (= "" t) |
411 |
|
// ----------------------- EQP::TR |
412 |
|
// (= l1 "") (= l1 t) (= (= "" t) false) |
413 |
|
// ----------------------------------------------------------------- EQP::TR |
414 |
|
// (= false true) |
415 |
|
// |
416 |
|
// which would lead to the cyclic expansion proof: |
417 |
|
// |
418 |
|
// (= l1 "") (= l1 "") (= "" t) |
419 |
|
// --------- SYMM ----------------------- TRANS |
420 |
|
// (= "" l1) (= l1 t) |
421 |
|
// ------------------------------------------ TRANS |
422 |
|
// (= "" t) |
423 |
4394 |
if (expansionPremises.size() > 1 && !assumptions.count(expansionConclusion)) |
424 |
|
{ |
425 |
|
// create transitivity step to derive expected premise |
426 |
1599 |
buildTransitivityChain(expansionConclusion, expansionPremises); |
427 |
3198 |
Trace("eqproof-conv") |
428 |
|
<< "EqProof::expandTransitivityForDisequalities: add transitivity " |
429 |
1599 |
"step for " |
430 |
1599 |
<< expansionConclusion << " with premises " << expansionPremises |
431 |
1599 |
<< "\n"; |
432 |
|
// create expansion step |
433 |
1599 |
p->addStep( |
434 |
|
expansionConclusion, PfRule::TRANS, expansionPremises, {}, true); |
435 |
|
} |
436 |
|
} |
437 |
9532 |
Trace("eqproof-conv") |
438 |
4766 |
<< "EqProof::expandTransitivityForDisequalities: now derive conclusion " |
439 |
4766 |
<< conclusion; |
440 |
4766 |
premises.clear(); |
441 |
4766 |
premises.push_back(premises[offending]); |
442 |
4766 |
if (inSubstCase) |
443 |
|
{ |
444 |
6104 |
Trace("eqproof-conv") << (substConclusionInReverseOrder ? " [inverted]" |
445 |
3052 |
: "") |
446 |
3052 |
<< " via subsitution from " << premises[offending] |
447 |
3052 |
<< " and (inverted subst) " << substPremises << "\n"; |
448 |
|
// By this point, for premise disequality (= (= t1 t2) false), we have |
449 |
|
// potentially already built |
450 |
|
// |
451 |
|
// (= t1 x1) ... (= xn t3) (= t2 y1) ... (= ym t4) |
452 |
|
// ------------------------ TR ------------------------ TR |
453 |
|
// (= t1 t3) (= t2 t4) |
454 |
|
// |
455 |
|
// to justify the substitutions t1->t3 and t2->t4 (where note that if t1=t3 |
456 |
|
// or t2=4, the step will actually be a REFL one). Not do |
457 |
|
// |
458 |
|
// ----------- SYMM ----------- SYMM |
459 |
|
// (= t3 t1) (= t4 t2) |
460 |
|
// ---------------------------------------- CONG |
461 |
|
// (= (= t3 t4) (= t1 t2)) (= (= t1 t2) false) |
462 |
|
// --------------------------------------------------------------------- TR |
463 |
|
// (= (= t3 t4) false) |
464 |
|
// |
465 |
|
// where note that the SYMM steps are implicitly added by CDProof. |
466 |
|
Node congConclusion = nm->mkNode( |
467 |
|
kind::EQUAL, |
468 |
6104 |
nm->mkNode(kind::EQUAL, substPremises[0][0], substPremises[1][0]), |
469 |
12208 |
premises[offending][0]); |
470 |
6104 |
p->addStep(congConclusion, |
471 |
|
PfRule::CONG, |
472 |
|
substPremises, |
473 |
|
{ProofRuleChecker::mkKindNode(kind::EQUAL)}, |
474 |
3052 |
true); |
475 |
6104 |
Trace("eqproof-conv") << "EqProof::expandTransitivityForDisequalities: via " |
476 |
3052 |
"congruence derived " |
477 |
3052 |
<< congConclusion << "\n"; |
478 |
|
// transitivity step between that and the original premise |
479 |
3052 |
premises.insert(premises.begin(), congConclusion); |
480 |
|
Node transConclusion = |
481 |
3052 |
!substConclusionInReverseOrder |
482 |
|
? conclusion |
483 |
6104 |
: nm->mkNode(kind::EQUAL, congConclusion[0], conclusion[1]); |
484 |
|
// check to avoid cyclic proofs |
485 |
3052 |
if (!assumptions.count(transConclusion)) |
486 |
|
{ |
487 |
3052 |
p->addStep(transConclusion, PfRule::TRANS, premises, {}, true); |
488 |
6104 |
Trace("eqproof-conv") << "EqProof::expandTransitivityForDisequalities: " |
489 |
3052 |
"via transitivity derived " |
490 |
3052 |
<< transConclusion << "\n"; |
491 |
|
} |
492 |
|
// if order is reversed, finish the proof of conclusion with |
493 |
|
// (= (= t3 t4) false) |
494 |
|
// --------------------- MACRO_SR_PRED_TRANSFORM |
495 |
|
// (= (= t4 t3) false) |
496 |
3052 |
if (substConclusionInReverseOrder) |
497 |
|
{ |
498 |
1119 |
p->addStep(conclusion, |
499 |
|
PfRule::MACRO_SR_PRED_TRANSFORM, |
500 |
|
{transConclusion}, |
501 |
|
{conclusion}, |
502 |
746 |
true); |
503 |
746 |
Trace("eqproof-conv") << "EqProof::expandTransitivityForDisequalities: " |
504 |
373 |
"via macro transform derived " |
505 |
373 |
<< conclusion << "\n"; |
506 |
|
} |
507 |
|
} |
508 |
|
else |
509 |
|
{ |
510 |
|
// create TRUE_INTRO step for expansionConclusion and add it to the premises |
511 |
3428 |
Trace("eqproof-conv") |
512 |
|
<< " via transitivity.\nEqProof::expandTransitivityForDisequalities: " |
513 |
1714 |
"adding " |
514 |
1714 |
<< PfRule::TRUE_INTRO << " step for " << expansionConclusion[0] << "\n"; |
515 |
|
Node newExpansionConclusion = |
516 |
3428 |
expansionConclusion.eqNode(nm->mkConst<bool>(true)); |
517 |
3428 |
p->addStep( |
518 |
1714 |
newExpansionConclusion, PfRule::TRUE_INTRO, {expansionConclusion}, {}); |
519 |
1714 |
premises.push_back(newExpansionConclusion); |
520 |
1714 |
Trace("eqproof-conv") << PfRule::TRANS << " from " << premises << "\n"; |
521 |
1714 |
buildTransitivityChain(conclusion, premises); |
522 |
|
// create final transitivity step |
523 |
1714 |
p->addStep(conclusion, PfRule::TRANS, premises, {}, true); |
524 |
|
} |
525 |
4766 |
return true; |
526 |
|
} |
527 |
|
|
528 |
|
// TEMPORARY NOTE: This may not be enough. Worst case scenario I need to |
529 |
|
// reproduce here a search for arbitrary chains between each of the variables in |
530 |
|
// the conclusion and a constant |
531 |
155844 |
bool EqProof::expandTransitivityForTheoryDisequalities( |
532 |
|
Node conclusion, std::vector<Node>& premises, CDProof* p) const |
533 |
|
{ |
534 |
|
// whether conclusion is a disequality (= (= t1 t2) false), modulo symmetry |
535 |
155844 |
unsigned termPos = -1; |
536 |
467529 |
for (unsigned i = 0; i < 2; ++i) |
537 |
|
{ |
538 |
935064 |
if (conclusion[i].getKind() == kind::CONST_BOOLEAN |
539 |
316104 |
&& !conclusion[i].getConst<bool>() |
540 |
937869 |
&& conclusion[1 - i].getKind() == kind::EQUAL) |
541 |
|
{ |
542 |
3 |
termPos = i - 1; |
543 |
3 |
break; |
544 |
|
} |
545 |
|
} |
546 |
|
// no disequality |
547 |
155844 |
if (termPos == static_cast<unsigned>(-1)) |
548 |
|
{ |
549 |
155841 |
return false; |
550 |
|
} |
551 |
6 |
Trace("eqproof-conv") |
552 |
|
<< "EqProof::expandTransitivityForTheoryDisequalities: check if need " |
553 |
3 |
"to expand transitivity step to conclude disequality " |
554 |
3 |
<< conclusion << " from premises " << premises << "\n"; |
555 |
|
|
556 |
|
// Check if the premises are (= t1 c1) and (= t2 c2), modulo symmetry |
557 |
6 |
std::vector<Node> subChildren, constChildren; |
558 |
9 |
for (unsigned i = 0; i < 2; ++i) |
559 |
|
{ |
560 |
12 |
Node term = conclusion[termPos][i]; |
561 |
18 |
for (const Node& premise : premises) |
562 |
|
{ |
563 |
30 |
for (unsigned j = 0; j < 2; ++j) |
564 |
|
{ |
565 |
24 |
if (premise[j] == term && premise[1 - j].isConst()) |
566 |
|
{ |
567 |
6 |
subChildren.push_back(premise[j].eqNode(premise[1 - j])); |
568 |
6 |
constChildren.push_back(premise[1 - j]); |
569 |
6 |
break; |
570 |
|
} |
571 |
|
} |
572 |
|
} |
573 |
|
} |
574 |
3 |
if (subChildren.size() < 2) |
575 |
|
{ |
576 |
|
return false; |
577 |
|
} |
578 |
|
// Now build |
579 |
|
// (= t1 c1) (= t2 c2) |
580 |
|
// ------------------------- CONG ------------------- MACRO_SR_PRED_INTRO |
581 |
|
// (= (= t1 t2) (= c1 c2)) (= (= c1 c2) false) |
582 |
|
// --------------------------------------------------------------------- TR |
583 |
|
// (= (= t1 t2) false) |
584 |
6 |
Node constApp = NodeManager::currentNM()->mkNode(kind::EQUAL, constChildren); |
585 |
6 |
Node constEquality = constApp.eqNode(conclusion[1 - termPos]); |
586 |
6 |
Trace("eqproof-conv") |
587 |
3 |
<< "EqProof::expandTransitivityForTheoryDisequalities: adding " |
588 |
6 |
<< PfRule::MACRO_SR_PRED_INTRO << " step for " << constApp << " = " |
589 |
3 |
<< conclusion[1 - termPos] << "\n"; |
590 |
3 |
p->addStep(constEquality, PfRule::MACRO_SR_PRED_INTRO, {}, {constEquality}); |
591 |
|
// build congruence conclusion (= (= t1 t2) (t c1 c2)) |
592 |
6 |
Node congConclusion = conclusion[termPos].eqNode(constApp); |
593 |
6 |
Trace("eqproof-conv") |
594 |
3 |
<< "EqProof::expandTransitivityForTheoryDisequalities: adding " |
595 |
6 |
<< PfRule::CONG << " step for " << congConclusion << " from " |
596 |
3 |
<< subChildren << "\n"; |
597 |
6 |
p->addStep(congConclusion, |
598 |
|
PfRule::CONG, |
599 |
|
{subChildren}, |
600 |
|
{ProofRuleChecker::mkKindNode(kind::EQUAL)}, |
601 |
3 |
true); |
602 |
6 |
Trace("eqproof-conv") << "EqProof::expandTransitivityForDisequalities: via " |
603 |
3 |
"congruence derived " |
604 |
3 |
<< congConclusion << "\n"; |
605 |
6 |
std::vector<Node> transitivityChildren{congConclusion, constEquality}; |
606 |
3 |
p->addStep(conclusion, PfRule::TRANS, {transitivityChildren}, {}); |
607 |
3 |
return true; |
608 |
|
} |
609 |
|
|
610 |
575060 |
bool EqProof::buildTransitivityChain(Node conclusion, |
611 |
|
std::vector<Node>& premises) const |
612 |
|
{ |
613 |
1150120 |
Trace("eqproof-conv") << push |
614 |
575060 |
<< "EqProof::buildTransitivityChain: Build chain for " |
615 |
575060 |
<< conclusion << " with premises " << premises << "\n"; |
616 |
1869003 |
for (unsigned i = 0, size = premises.size(); i < size; ++i) |
617 |
|
{ |
618 |
1869003 |
bool occurs = false, correctlyOrdered = false; |
619 |
1869003 |
if (conclusion[0] == premises[i][0]) |
620 |
|
{ |
621 |
245867 |
occurs = correctlyOrdered = true; |
622 |
|
} |
623 |
1623136 |
else if (conclusion[0] == premises[i][1]) |
624 |
|
{ |
625 |
329193 |
occurs = true; |
626 |
|
} |
627 |
1869003 |
if (occurs) |
628 |
|
{ |
629 |
1150120 |
Trace("eqproof-conv") |
630 |
1150120 |
<< "EqProof::buildTransitivityChain: found " << conclusion[0] << " in" |
631 |
1150120 |
<< (correctlyOrdered ? "" : " non-") << " ordered premise " |
632 |
575060 |
<< premises[i] << "\n"; |
633 |
575060 |
if (conclusion[1] == premises[i][correctlyOrdered ? 1 : 0]) |
634 |
|
{ |
635 |
341682 |
Trace("eqproof-conv") |
636 |
341682 |
<< "EqProof::buildTransitivityChain: found " << conclusion[1] |
637 |
170841 |
<< " in same premise. Closed chain.\n" |
638 |
170841 |
<< pop; |
639 |
170841 |
premises.clear(); |
640 |
170841 |
premises.push_back(conclusion); |
641 |
745901 |
return true; |
642 |
|
} |
643 |
|
// Build chain with remaining equalities |
644 |
404219 |
std::vector<Node> recursivePremises; |
645 |
2106927 |
for (unsigned j = 0; j < size; ++j) |
646 |
|
{ |
647 |
1702708 |
if (j != i) |
648 |
|
{ |
649 |
1298489 |
recursivePremises.push_back(premises[j]); |
650 |
|
} |
651 |
|
} |
652 |
|
Node newTarget = |
653 |
404219 |
premises[i][correctlyOrdered ? 1 : 0].eqNode(conclusion[1]); |
654 |
808438 |
Trace("eqproof-conv") |
655 |
404219 |
<< "EqProof::buildTransitivityChain: search recursively for " |
656 |
404219 |
<< newTarget << "\n"; |
657 |
404219 |
if (buildTransitivityChain(newTarget, recursivePremises)) |
658 |
|
{ |
659 |
808438 |
Trace("eqproof-conv") |
660 |
404219 |
<< "EqProof::buildTransitivityChain: closed chain with " |
661 |
808438 |
<< 1 + recursivePremises.size() << " of the original " |
662 |
808438 |
<< premises.size() << " premises\n" |
663 |
404219 |
<< pop; |
664 |
404219 |
premises.clear(); |
665 |
1212657 |
premises.insert(premises.begin(), |
666 |
|
correctlyOrdered |
667 |
1274266 |
? premises[i] |
668 |
1274266 |
: premises[i][1].eqNode(premises[i][0])); |
669 |
404219 |
premises.insert( |
670 |
808438 |
premises.end(), recursivePremises.begin(), recursivePremises.end()); |
671 |
404219 |
return true; |
672 |
|
} |
673 |
|
} |
674 |
|
} |
675 |
|
Trace("eqproof-conv") |
676 |
|
<< "EqProof::buildTransitivityChain: Could not build chain for" |
677 |
|
<< conclusion << " with premises " << premises << "\n"; |
678 |
|
Trace("eqproof-conv") << pop; |
679 |
|
return false; |
680 |
|
} |
681 |
|
|
682 |
764774 |
void EqProof::reduceNestedCongruence( |
683 |
|
unsigned i, |
684 |
|
Node conclusion, |
685 |
|
std::vector<std::vector<Node>>& transitivityMatrix, |
686 |
|
CDProof* p, |
687 |
|
std::unordered_map<Node, Node>& visited, |
688 |
|
std::unordered_set<Node>& assumptions, |
689 |
|
bool isNary) const |
690 |
|
{ |
691 |
1529548 |
Trace("eqproof-conv") << "EqProof::reduceNestedCongruence: building for " << i |
692 |
764774 |
<< "-th arg\n"; |
693 |
764774 |
if (d_id == MERGED_THROUGH_CONGRUENCE) |
694 |
|
{ |
695 |
628453 |
Assert(d_children.size() == 2); |
696 |
1256906 |
Trace("eqproof-conv") << "EqProof::reduceNestedCongruence: it's a " |
697 |
628453 |
"congruence step. Reduce second child\n" |
698 |
628453 |
<< push; |
699 |
1256906 |
transitivityMatrix[i].push_back( |
700 |
1256906 |
d_children[1]->addToProof(p, visited, assumptions)); |
701 |
1256906 |
Trace("eqproof-conv") |
702 |
628453 |
<< pop << "EqProof::reduceNestedCongruence: child conclusion " |
703 |
628453 |
<< transitivityMatrix[i].back() << "\n"; |
704 |
|
// if i == 0, first child must be REFL step, standing for (= f f), which can |
705 |
|
// be ignored in a first-order calculus |
706 |
628453 |
Assert(i > 0 || d_children[0]->d_id == MERGED_THROUGH_REFLEXIVITY |
707 |
|
|| options::ufHo()); |
708 |
|
// recurse |
709 |
628453 |
if (i > 1) |
710 |
|
{ |
711 |
312766 |
Trace("eqproof-conv") |
712 |
156383 |
<< "EqProof::reduceNestedCongruence: Reduce first child\n" |
713 |
156383 |
<< push; |
714 |
156383 |
d_children[0]->reduceNestedCongruence(i - 1, |
715 |
|
conclusion, |
716 |
|
transitivityMatrix, |
717 |
|
p, |
718 |
|
visited, |
719 |
|
assumptions, |
720 |
|
isNary); |
721 |
156383 |
Trace("eqproof-conv") << pop; |
722 |
|
} |
723 |
|
// higher-order case |
724 |
472070 |
else if (d_children[0]->d_id != MERGED_THROUGH_REFLEXIVITY) |
725 |
|
{ |
726 |
8 |
Trace("eqproof-conv") << "EqProof::reduceNestedCongruence: HO case. " |
727 |
|
"Processing first child\n"; |
728 |
|
// we only handle these cases |
729 |
8 |
Assert(d_children[0]->d_id == MERGED_THROUGH_EQUALITY |
730 |
|
|| d_children[0]->d_id == MERGED_THROUGH_TRANS); |
731 |
16 |
transitivityMatrix[0].push_back( |
732 |
16 |
d_children[0]->addToProof(p, visited, assumptions)); |
733 |
|
} |
734 |
628453 |
return; |
735 |
|
} |
736 |
136321 |
Assert(d_id == MERGED_THROUGH_TRANS) |
737 |
|
<< "id is " << static_cast<MergeReasonType>(d_id) << "\n"; |
738 |
136321 |
Trace("eqproof-conv") << "EqProof::reduceNestedCongruence: it's a " |
739 |
|
"transitivity step.\n"; |
740 |
136321 |
Assert(d_node.isNull() |
741 |
|
|| d_node[0].getNumChildren() == d_node[1].getNumChildren() || isNary) |
742 |
|
<< "Non-null (internal cong) transitivity conclusion of different arity " |
743 |
|
"but not marked by isNary flag\n"; |
744 |
|
// If handling n-ary kinds and got a transitivity conclusion, we process it |
745 |
|
// with addToProof, store the result into row i, and stop. This marks an |
746 |
|
// "adjustment" of the arity, with empty rows 0..i-1 in the matrix determining |
747 |
|
// the adjustment in addToProof processing the congruence of the original |
748 |
|
// conclusion. See details there. |
749 |
136321 |
if (isNary && !d_node.isNull()) |
750 |
|
{ |
751 |
24 |
Trace("eqproof-conv") << "EqProof::reduceNestedCongruence: n-ary case, " |
752 |
12 |
"break recursion and indepedently process " |
753 |
12 |
<< d_node << "\n" |
754 |
12 |
<< push; |
755 |
12 |
transitivityMatrix[i].push_back(addToProof(p, visited, assumptions)); |
756 |
24 |
Trace("eqproof-conv") << pop |
757 |
12 |
<< "EqProof::reduceNestedCongruence: Got conclusion " |
758 |
12 |
<< transitivityMatrix[i].back() |
759 |
12 |
<< " from n-ary transitivity processing\n"; |
760 |
12 |
return; |
761 |
|
} |
762 |
|
// Regular recursive processing of each transitivity premise |
763 |
533779 |
for (unsigned j = 0, sizeTrans = d_children.size(); j < sizeTrans; ++j) |
764 |
|
{ |
765 |
397470 |
if (d_children[j]->d_id == MERGED_THROUGH_CONGRUENCE) |
766 |
|
{ |
767 |
794940 |
Trace("eqproof-conv") << "EqProof::reduceNestedCongruence: Reduce " << j |
768 |
397470 |
<< "-th transitivity congruence child\n" |
769 |
397470 |
<< push; |
770 |
397470 |
d_children[j]->reduceNestedCongruence( |
771 |
|
i, conclusion, transitivityMatrix, p, visited, assumptions, isNary); |
772 |
397470 |
Trace("eqproof-conv") << pop; |
773 |
|
} |
774 |
|
else |
775 |
|
{ |
776 |
|
Trace("eqproof-conv") << "EqProof::reduceNestedCongruence: Add " << j |
777 |
|
<< "-th transitivity child to proof\n" |
778 |
|
<< push; |
779 |
|
transitivityMatrix[i].push_back( |
780 |
|
d_children[j]->addToProof(p, visited, assumptions)); |
781 |
|
Trace("eqproof-conv") << pop; |
782 |
|
} |
783 |
|
} |
784 |
|
} |
785 |
|
|
786 |
74387 |
Node EqProof::addToProof(CDProof* p) const |
787 |
|
{ |
788 |
148774 |
std::unordered_map<Node, Node> cache; |
789 |
148774 |
std::unordered_set<Node> assumptions; |
790 |
148774 |
Node conclusion = addToProof(p, cache, assumptions); |
791 |
148774 |
Trace("eqproof-conv") << "EqProof::addToProof: root of proof: " << conclusion |
792 |
74387 |
<< "\n"; |
793 |
148774 |
Trace("eqproof-conv") << "EqProof::addToProof: tracked assumptions: " |
794 |
74387 |
<< assumptions << "\n"; |
795 |
|
// If conclusion t1 = tn is, modulo symmetry, of the form (= t true/false), in |
796 |
|
// which t is not true/false, it must be turned into t or (not t) with |
797 |
|
// TRUE/FALSE_ELIM. |
798 |
74387 |
Node newConclusion = conclusion; |
799 |
74387 |
Assert(conclusion.getKind() == kind::EQUAL); |
800 |
148774 |
if ((conclusion[0].getKind() == kind::CONST_BOOLEAN) |
801 |
74387 |
!= (conclusion[1].getKind() == kind::CONST_BOOLEAN)) |
802 |
|
{ |
803 |
15510 |
Trace("eqproof-conv") |
804 |
7755 |
<< "EqProof::addToProof: process root for TRUE/FALSE_ELIM\n"; |
805 |
|
// Index of constant in equality |
806 |
|
unsigned constIndex = |
807 |
7755 |
conclusion[0].getKind() == kind::CONST_BOOLEAN ? 0 : 1; |
808 |
|
// The premise for the elimination rule must have the constant as the second |
809 |
|
// argument of the equality. If that's not the case, build it as such, |
810 |
|
// relying on an implicit SYMM step to be added to the proof when justifying |
811 |
|
// t / (not t). |
812 |
|
Node elimPremise = |
813 |
15510 |
constIndex == 1 ? conclusion : conclusion[1].eqNode(conclusion[0]); |
814 |
|
// Determine whether TRUE_ELIM or FALSE_ELIM, depending on the constant |
815 |
|
// value. The new conclusion, whether t or (not t), is also determined |
816 |
|
// accordingly. |
817 |
|
PfRule elimRule; |
818 |
7755 |
if (conclusion[constIndex].getConst<bool>()) |
819 |
|
{ |
820 |
1196 |
elimRule = PfRule::TRUE_ELIM; |
821 |
1196 |
newConclusion = conclusion[1 - constIndex]; |
822 |
|
} |
823 |
|
else |
824 |
|
{ |
825 |
6559 |
elimRule = PfRule::FALSE_ELIM; |
826 |
6559 |
newConclusion = conclusion[1 - constIndex].notNode(); |
827 |
|
} |
828 |
|
// We also check if the final conclusion t / (not t) has already been |
829 |
|
// justified, so that we can avoid a cyclic proof, which can be due to |
830 |
|
// either t / (not t) being assumption in the original EqProof or it having |
831 |
|
// a non-assumption proof step in the proof of (= t true/false). |
832 |
7755 |
if (!assumptions.count(newConclusion) && !p->hasStep(newConclusion)) |
833 |
|
{ |
834 |
9660 |
Trace("eqproof-conv") |
835 |
4830 |
<< "EqProof::addToProof: conclude " << newConclusion << " via " |
836 |
4830 |
<< elimRule << " step for " << elimPremise << "\n"; |
837 |
4830 |
p->addStep(newConclusion, elimRule, {elimPremise}, {}); |
838 |
|
} |
839 |
|
} |
840 |
148774 |
return newConclusion; |
841 |
|
} |
842 |
|
|
843 |
1265574 |
Node EqProof::addToProof(CDProof* p, |
844 |
|
std::unordered_map<Node, Node>& visited, |
845 |
|
std::unordered_set<Node>& assumptions) const |
846 |
|
{ |
847 |
1265574 |
std::unordered_map<Node, Node>::const_iterator it = visited.find(d_node); |
848 |
1265574 |
if (it != visited.end()) |
849 |
|
{ |
850 |
831474 |
Trace("eqproof-conv") << "EqProof::addToProof: already processed " << d_node |
851 |
415737 |
<< ", returning " << it->second << "\n"; |
852 |
415737 |
return it->second; |
853 |
|
} |
854 |
1699674 |
Trace("eqproof-conv") << "EqProof::addToProof: adding step for " << d_id |
855 |
849837 |
<< " with conclusion " << d_node << "\n"; |
856 |
|
// Assumption |
857 |
849837 |
if (d_id == MERGED_THROUGH_EQUALITY) |
858 |
|
{ |
859 |
|
// Check that no (= true/false true/false) assumptions |
860 |
393207 |
if (Configuration::isDebugBuild() && d_node.getKind() == kind::EQUAL) |
861 |
|
{ |
862 |
1179621 |
for (unsigned i = 0; i < 2; ++i) |
863 |
|
{ |
864 |
1572828 |
Assert(d_node[i].getKind() != kind::CONST_BOOLEAN |
865 |
|
|| d_node[1 - i].getKind() != kind::CONST_BOOLEAN) |
866 |
|
<< "EqProof::addToProof: fully boolean constant assumption " |
867 |
786414 |
<< d_node << " is disallowed\n"; |
868 |
|
} |
869 |
|
} |
870 |
|
// If conclusion is (= t true/false), we add a proof step |
871 |
|
// t |
872 |
|
// ---------------- TRUE/FALSE_INTRO |
873 |
|
// (= t true/false) |
874 |
|
// according to the value of the Boolean constant |
875 |
786414 |
if (d_node.getKind() == kind::EQUAL |
876 |
1966035 |
&& ((d_node[0].getKind() == kind::CONST_BOOLEAN) |
877 |
1179621 |
!= (d_node[1].getKind() == kind::CONST_BOOLEAN))) |
878 |
|
{ |
879 |
24510 |
Trace("eqproof-conv") |
880 |
12255 |
<< "EqProof::addToProof: add an intro step for " << d_node << "\n"; |
881 |
|
// Index of constant in equality |
882 |
12255 |
unsigned constIndex = d_node[0].getKind() == kind::CONST_BOOLEAN ? 0 : 1; |
883 |
|
// The premise for the intro rule is either t or (not t), according to the |
884 |
|
// Boolean constant. |
885 |
24510 |
Node introPremise; |
886 |
|
PfRule introRule; |
887 |
12255 |
if (d_node[constIndex].getConst<bool>()) |
888 |
|
{ |
889 |
2725 |
introRule = PfRule::TRUE_INTRO; |
890 |
2725 |
introPremise = d_node[1 - constIndex]; |
891 |
|
// Track the new assumption. If it's an equality, also its symmetric |
892 |
2725 |
assumptions.insert(introPremise); |
893 |
2725 |
if (introPremise.getKind() == kind::EQUAL) |
894 |
|
{ |
895 |
|
assumptions.insert(introPremise[1].eqNode(introPremise[0])); |
896 |
|
} |
897 |
|
} |
898 |
|
else |
899 |
|
{ |
900 |
9530 |
introRule = PfRule::FALSE_INTRO; |
901 |
9530 |
introPremise = d_node[1 - constIndex].notNode(); |
902 |
|
// Track the new assumption. If it's a disequality, also its symmetric |
903 |
9530 |
assumptions.insert(introPremise); |
904 |
9530 |
if (introPremise[0].getKind() == kind::EQUAL) |
905 |
|
{ |
906 |
6340 |
assumptions.insert( |
907 |
12680 |
introPremise[0][1].eqNode(introPremise[0][0]).notNode()); |
908 |
|
} |
909 |
|
} |
910 |
|
// The original assumption can be e.g. (= false (= t1 t2)) in which case |
911 |
|
// the necessary proof to be built is |
912 |
|
// (not (= t1 t2)) |
913 |
|
// -------------------- FALSE_INTRO |
914 |
|
// (= (= t1 t2) false) |
915 |
|
// -------------------- SYMM |
916 |
|
// (= false (= t1 t2)) |
917 |
|
// |
918 |
|
// with the SYMM step happening automatically whenever the assumption is |
919 |
|
// used in the proof p |
920 |
|
Node introConclusion = |
921 |
24510 |
constIndex == 1 ? d_node : d_node[1].eqNode(d_node[0]); |
922 |
12255 |
p->addStep(introConclusion, introRule, {introPremise}, {}); |
923 |
|
} |
924 |
|
else |
925 |
|
{ |
926 |
380952 |
p->addStep(d_node, PfRule::ASSUME, {}, {d_node}); |
927 |
|
} |
928 |
|
// If non-equality predicate, turn into one via TRUE/FALSE intro |
929 |
786414 |
Node conclusion = d_node; |
930 |
393207 |
if (d_node.getKind() != kind::EQUAL) |
931 |
|
{ |
932 |
|
// Track original assumption |
933 |
|
assumptions.insert(d_node); |
934 |
|
PfRule intro; |
935 |
|
if (d_node.getKind() == kind::NOT) |
936 |
|
{ |
937 |
|
intro = PfRule::FALSE_INTRO; |
938 |
|
conclusion = |
939 |
|
d_node[0].eqNode(NodeManager::currentNM()->mkConst<bool>(false)); |
940 |
|
} |
941 |
|
else |
942 |
|
{ |
943 |
|
intro = PfRule::TRUE_INTRO; |
944 |
|
conclusion = |
945 |
|
d_node.eqNode(NodeManager::currentNM()->mkConst<bool>(true)); |
946 |
|
} |
947 |
|
Trace("eqproof-conv") << "EqProof::addToProof: adding " << intro |
948 |
|
<< " step for " << d_node << "\n"; |
949 |
|
p->addStep(conclusion, intro, {d_node}, {}); |
950 |
|
} |
951 |
|
// Keep track of assumptions to avoid cyclic proofs. Both the assumption and |
952 |
|
// its symmetric are added |
953 |
393207 |
assumptions.insert(conclusion); |
954 |
393207 |
assumptions.insert(conclusion[1].eqNode(conclusion[0])); |
955 |
786414 |
Trace("eqproof-conv") << "EqProof::addToProof: tracking assumptions " |
956 |
786414 |
<< conclusion << ", (= " << conclusion[1] << " " |
957 |
393207 |
<< conclusion[0] << ")\n"; |
958 |
393207 |
visited[d_node] = conclusion; |
959 |
393207 |
return conclusion; |
960 |
|
} |
961 |
|
// Refl and laborious congruence steps for (= (f t1 ... tn) (f t1 ... tn)), |
962 |
|
// which can be safely turned into reflexivity steps. These laborious |
963 |
|
// congruence steps are currently generated in the equality engine because of |
964 |
|
// the suboptimal handling of n-ary operators. |
965 |
913260 |
if (d_id == MERGED_THROUGH_REFLEXIVITY |
966 |
913260 |
|| (d_node.getKind() == kind::EQUAL && d_node[0] == d_node[1])) |
967 |
|
{ |
968 |
|
Node conclusion = |
969 |
167210 |
d_node.getKind() == kind::EQUAL ? d_node : d_node.eqNode(d_node); |
970 |
83605 |
p->addStep(conclusion, PfRule::REFL, {}, {conclusion[0]}); |
971 |
83605 |
visited[d_node] = conclusion; |
972 |
83605 |
return conclusion; |
973 |
|
} |
974 |
|
// Equalities due to theory reasoning |
975 |
373025 |
if (d_id == MERGED_THROUGH_CONSTANTS) |
976 |
|
{ |
977 |
2988 |
Assert(!d_node.isNull() && d_node.getKind() == kind::EQUAL |
978 |
|
&& d_node[1].isConst()) |
979 |
1494 |
<< ". Conclusion " << d_node << " from " << d_id |
980 |
|
<< " was expected to be (= (f t1 ... tn) c)\n"; |
981 |
2988 |
Assert(!assumptions.count(d_node)) |
982 |
1494 |
<< "Conclusion " << d_node << " from " << d_id << " is an assumption\n"; |
983 |
|
// The step has the form |
984 |
|
// [(= t1 c1)] ... [(= tn cn)] |
985 |
|
// ------------------------ |
986 |
|
// (= (f t1 ... tn) c) |
987 |
|
// where premises equating ti to constants are present when they are not |
988 |
|
// already constants. Note that the premises may be in any order, e.g. with |
989 |
|
// the equality for the second term being justified in the first premise. |
990 |
|
// Moreover, they may be of the form (= ci ti). |
991 |
|
// |
992 |
|
// First recursively process premises, if any |
993 |
2988 |
std::vector<Node> premises; |
994 |
4666 |
for (unsigned i = 0; i < d_children.size(); ++i) |
995 |
|
{ |
996 |
6344 |
Trace("eqproof-conv") |
997 |
3172 |
<< "EqProof::addToProof: recurse on child " << i << "\n" |
998 |
3172 |
<< push; |
999 |
3172 |
premises.push_back(d_children[i]->addToProof(p, visited, assumptions)); |
1000 |
3172 |
Trace("eqproof-conv") << pop; |
1001 |
|
} |
1002 |
|
// After building the proper premises we could build a step like |
1003 |
|
// [(= t1 c1)] ... [(= tn cn)] |
1004 |
|
// ---------------------------- MACRO_SR_PRED_INTRO |
1005 |
|
// (= (f t1 ... tn) c) |
1006 |
|
// but note that since the substitution applied by MACRO_SR_PRED_INTRO is |
1007 |
|
// *not* simultenous this could lead to issues if t_{i+1} occurred in some |
1008 |
|
// t_{i}. So we build proofs as |
1009 |
|
// |
1010 |
|
// [(= t1 c1)] ... [(= tn cn)] |
1011 |
|
// ------------------------------- CONG -------------- MACRO_SR_PRED_INTRO |
1012 |
|
// (= (f t1 ... tn) (f c1 ... cn)) (= (f c1 ... cn) c) |
1013 |
|
// ---------------------------------------------------------- TRANS |
1014 |
|
// (= (f t1 ... tn) c) |
1015 |
2988 |
std::vector<Node> subChildren, constChildren; |
1016 |
4714 |
for (unsigned i = 0, size = d_node[0].getNumChildren(); i < size; ++i) |
1017 |
|
{ |
1018 |
5244 |
Node term = d_node[0][i]; |
1019 |
|
// term already is a constant, add a REFL step |
1020 |
4416 |
if (term.isConst()) |
1021 |
|
{ |
1022 |
1196 |
subChildren.push_back(term.eqNode(term)); |
1023 |
1196 |
p->addStep(subChildren.back(), PfRule::REFL, {}, {term}); |
1024 |
1196 |
constChildren.push_back(term); |
1025 |
1196 |
continue; |
1026 |
|
} |
1027 |
|
// Build the equality (= ti ci) as a premise, finding the respective ci is |
1028 |
|
// the original premises |
1029 |
4048 |
Node constant; |
1030 |
3414 |
for (const Node& premise : premises) |
1031 |
|
{ |
1032 |
3414 |
Assert(premise.getKind() == kind::EQUAL); |
1033 |
3414 |
if (premise[0] == term) |
1034 |
|
{ |
1035 |
623 |
Assert(premise[1].isConst()); |
1036 |
623 |
constant = premise[1]; |
1037 |
623 |
break; |
1038 |
|
} |
1039 |
2791 |
if (premise[1] == term) |
1040 |
|
{ |
1041 |
1401 |
Assert(premise[0].isConst()); |
1042 |
1401 |
constant = premise[0]; |
1043 |
1401 |
break; |
1044 |
|
} |
1045 |
|
} |
1046 |
2024 |
Assert(!constant.isNull()); |
1047 |
2024 |
subChildren.push_back(term.eqNode(constant)); |
1048 |
2024 |
constChildren.push_back(constant); |
1049 |
|
} |
1050 |
|
// build constant application (f c1 ... cn) and equality (= (f c1 ... cn) c) |
1051 |
1494 |
Kind k = d_node[0].getKind(); |
1052 |
2988 |
Node constApp = NodeManager::currentNM()->mkNode(k, constChildren); |
1053 |
2988 |
Node constEquality = constApp.eqNode(d_node[1]); |
1054 |
2988 |
Trace("eqproof-conv") << "EqProof::addToProof: adding " |
1055 |
2988 |
<< PfRule::MACRO_SR_PRED_INTRO << " step for " |
1056 |
1494 |
<< constApp << " = " << d_node[1] << "\n"; |
1057 |
1494 |
p->addStep(constEquality, PfRule::MACRO_SR_PRED_INTRO, {}, {constEquality}); |
1058 |
|
// build congruence conclusion (= (f t1 ... tn) (f c1 ... cn)) |
1059 |
2988 |
Node congConclusion = d_node[0].eqNode(constApp); |
1060 |
2988 |
Trace("eqproof-conv") << "EqProof::addToProof: adding " << PfRule::CONG |
1061 |
1494 |
<< " step for " << congConclusion << " from " |
1062 |
1494 |
<< subChildren << "\n"; |
1063 |
2988 |
p->addStep(congConclusion, |
1064 |
|
PfRule::CONG, |
1065 |
|
{subChildren}, |
1066 |
|
{ProofRuleChecker::mkKindNode(k)}, |
1067 |
1494 |
true); |
1068 |
2988 |
Trace("eqproof-conv") << "EqProof::addToProof: adding " << PfRule::TRANS |
1069 |
1494 |
<< " step for original conclusion " << d_node << "\n"; |
1070 |
2988 |
std::vector<Node> transitivityChildren{congConclusion, constEquality}; |
1071 |
1494 |
p->addStep(d_node, PfRule::TRANS, {transitivityChildren}, {}); |
1072 |
1494 |
visited[d_node] = d_node; |
1073 |
1494 |
return d_node; |
1074 |
|
} |
1075 |
|
// Transtivity and disequality reasoning steps |
1076 |
371531 |
if (d_id == MERGED_THROUGH_TRANS) |
1077 |
|
{ |
1078 |
321220 |
Assert(d_node.getKind() == kind::EQUAL |
1079 |
|
|| (d_node.getKind() == kind::NOT |
1080 |
|
&& d_node[0].getKind() == kind::EQUAL)) |
1081 |
160610 |
<< "EqProof::addToProof: transitivity step conclusion " << d_node |
1082 |
|
<< " is not equality or negated equality\n"; |
1083 |
|
// If conclusion is (not (= t1 t2)) change it to (= (= t1 t2) false), which |
1084 |
|
// is the correct conclusion of the equality reasoning step. A FALSE_ELIM |
1085 |
|
// step to revert this is only necessary when this is the root. That step is |
1086 |
|
// done in the non-recursive caller of this function. |
1087 |
|
Node conclusion = |
1088 |
160610 |
d_node.getKind() != kind::NOT |
1089 |
|
? d_node |
1090 |
321220 |
: d_node[0].eqNode(NodeManager::currentNM()->mkConst<bool>(false)); |
1091 |
|
// If the conclusion is an assumption, its derivation was spurious, so it |
1092 |
|
// can be discarded. Moreover, reconstructing the step may lead to cyclic |
1093 |
|
// proofs, so we *must* cut here. |
1094 |
160610 |
if (assumptions.count(conclusion)) |
1095 |
|
{ |
1096 |
|
visited[d_node] = conclusion; |
1097 |
|
return conclusion; |
1098 |
|
} |
1099 |
|
// Process premises recursively |
1100 |
321220 |
std::vector<Node> children; |
1101 |
718466 |
for (unsigned i = 0, size = d_children.size(); i < size; ++i) |
1102 |
|
{ |
1103 |
|
// If one of the steps is a "fake congruence" one, marked by a null |
1104 |
|
// conclusion, it must deleted. Its premises are moved down to premises of |
1105 |
|
// the transitivity step. |
1106 |
557856 |
EqProof* childProof = d_children[i].get(); |
1107 |
1115712 |
if (childProof->d_id == MERGED_THROUGH_CONGRUENCE |
1108 |
557856 |
&& childProof->d_node.isNull()) |
1109 |
|
{ |
1110 |
3372 |
Trace("eqproof-conv") << "EqProof::addToProof: child proof " << i |
1111 |
1686 |
<< " is fake cong step. Fold it.\n"; |
1112 |
1686 |
Assert(childProof->d_children.size() == 2); |
1113 |
1686 |
Trace("eqproof-conv") << push; |
1114 |
5058 |
for (unsigned j = 0, sizeJ = childProof->d_children.size(); j < sizeJ; |
1115 |
|
++j) |
1116 |
|
{ |
1117 |
6744 |
Trace("eqproof-conv") |
1118 |
3372 |
<< "EqProof::addToProof: recurse on child " << j << "\n" |
1119 |
3372 |
<< push; |
1120 |
3372 |
children.push_back( |
1121 |
6744 |
childProof->d_children[j]->addToProof(p, visited, assumptions)); |
1122 |
3372 |
Trace("eqproof-conv") << pop; |
1123 |
|
} |
1124 |
1686 |
Trace("eqproof-conv") << pop; |
1125 |
1686 |
continue; |
1126 |
|
} |
1127 |
1112340 |
Trace("eqproof-conv") |
1128 |
556170 |
<< "EqProof::addToProof: recurse on child " << i << "\n" |
1129 |
556170 |
<< push; |
1130 |
556170 |
children.push_back(childProof->addToProof(p, visited, assumptions)); |
1131 |
556170 |
Trace("eqproof-conv") << pop; |
1132 |
|
} |
1133 |
|
// Eliminate spurious premises. Reasoning below assumes no refl steps. |
1134 |
160610 |
cleanReflPremises(children); |
1135 |
|
// If any premise is of the form (= (t1 t2) false), then the transitivity |
1136 |
|
// step may be coarse-grained and needs to be expanded. If the expansion |
1137 |
|
// happens it also finalizes the proof of conclusion. |
1138 |
160610 |
if (!expandTransitivityForDisequalities( |
1139 |
|
conclusion, children, p, assumptions)) |
1140 |
|
{ |
1141 |
155844 |
Assert(!children.empty()); |
1142 |
|
// similarly, if a disequality is concluded because of theory reasoning, |
1143 |
|
// the step is coarse-grained and needs to be expanded, in which case the |
1144 |
|
// proof is finalized in the call |
1145 |
155844 |
if (!expandTransitivityForTheoryDisequalities(conclusion, children, p)) |
1146 |
|
{ |
1147 |
311682 |
Trace("eqproof-conv") |
1148 |
155841 |
<< "EqProof::addToProof: build chain for transitivity premises" |
1149 |
155841 |
<< children << " to conclude " << conclusion << "\n"; |
1150 |
|
// Build ordered transitivity chain from children to derive the |
1151 |
|
// conclusion |
1152 |
155841 |
buildTransitivityChain(conclusion, children); |
1153 |
155841 |
Assert( |
1154 |
|
children.size() > 1 |
1155 |
|
|| (!children.empty() |
1156 |
|
&& (children[0] == conclusion |
1157 |
|
|| children[0][1].eqNode(children[0][0]) == conclusion))); |
1158 |
|
// Only add transitivity step if there is more than one premise in the |
1159 |
|
// chain. Otherwise the premise will be the conclusion itself and it'll |
1160 |
|
// already have had a step added to it when the premises were |
1161 |
|
// recursively processed. |
1162 |
155841 |
if (children.size() > 1) |
1163 |
|
{ |
1164 |
155830 |
p->addStep(conclusion, PfRule::TRANS, children, {}, true); |
1165 |
|
} |
1166 |
|
} |
1167 |
|
} |
1168 |
160610 |
Assert(p->hasStep(conclusion)); |
1169 |
160610 |
visited[d_node] = conclusion; |
1170 |
160610 |
return conclusion; |
1171 |
|
} |
1172 |
210921 |
Assert(d_id == MERGED_THROUGH_CONGRUENCE); |
1173 |
|
// The processing below is mainly dedicated to flattening congruence steps |
1174 |
|
// (since EqProof assumes currying) and to prossibly reconstructing the |
1175 |
|
// conclusion in case it involves n-ary steps. |
1176 |
421842 |
Assert(d_node.getKind() == kind::EQUAL) |
1177 |
210921 |
<< "EqProof::addToProof: conclusion " << d_node << " is not equality\n"; |
1178 |
|
// The given conclusion is taken as ground truth. If the premises do not |
1179 |
|
// align, for example with (= (f t1) (f t2)) but a premise being (= t2 t1), we |
1180 |
|
// use (= t1 t2) as a premise and rely on a symmetry step to justify it. |
1181 |
210921 |
unsigned arity = d_node[0].getNumChildren(); |
1182 |
210921 |
Kind k = d_node[0].getKind(); |
1183 |
210921 |
bool isNary = NodeManager::isNAryKind(k); |
1184 |
|
|
1185 |
|
// N-ary operators are fun. The following proof is a valid EqProof |
1186 |
|
// |
1187 |
|
// (= (f t1 t2 t3) (f t6 t5)) (= (f t6 t5) (f t5 t6)) |
1188 |
|
// -------------------------------------------------- TRANS |
1189 |
|
// (= (f t1 t2 t3) (f t5 t6)) (= t4 t7) |
1190 |
|
// ------------------------------------------------------------ CONG |
1191 |
|
// (= (f t1 t2 t3 t4) (f t5 t6 t7)) |
1192 |
|
// |
1193 |
|
// We modify the above proof to conclude |
1194 |
|
// |
1195 |
|
// (= (f (f t1 t2 t3) t4) (f (f t5 t6) t7)) |
1196 |
|
// |
1197 |
|
// which is a valid congruence conclusion (applications of f with the same |
1198 |
|
// arity). For the processing below to be// performed correctly we update |
1199 |
|
// arity to be maximal one among the two applications (4 in the above |
1200 |
|
// example). |
1201 |
210921 |
if (d_node[0].getNumChildren() != d_node[1].getNumChildren()) |
1202 |
|
{ |
1203 |
|
Assert(isNary) << "We only handle congruences of apps with different " |
1204 |
|
"number of children for theory n-ary operators"; |
1205 |
|
arity = |
1206 |
|
d_node[1].getNumChildren() < arity ? arity : d_node[1].getNumChildren(); |
1207 |
|
Trace("eqproof-conv") |
1208 |
|
<< "EqProof::addToProof: Mismatching arities in cong conclusion " |
1209 |
|
<< d_node << ". Use tentative arity " << arity << "\n"; |
1210 |
|
} |
1211 |
|
// For a congruence proof of (= (f a0 ... an-1) (g b0 ... bn-1)), build a |
1212 |
|
// transitivity matrix of n rows where the first row contains a transitivity |
1213 |
|
// chain justifying (= f g) and the next rows (= ai bi) |
1214 |
421842 |
std::vector<std::vector<Node>> transitivityChildren; |
1215 |
788948 |
for (unsigned i = 0; i < arity + 1; ++i) |
1216 |
|
{ |
1217 |
578027 |
transitivityChildren.push_back(std::vector<Node>()); |
1218 |
|
} |
1219 |
210921 |
reduceNestedCongruence( |
1220 |
|
arity, d_node, transitivityChildren, p, visited, assumptions, isNary); |
1221 |
|
// Congruences over n-ary operators may require changing the conclusion (as in |
1222 |
|
// the above example). This is handled in a general manner below according to |
1223 |
|
// whether the transitivity matrix computed by reduceNestedCongruence contains |
1224 |
|
// empty rows |
1225 |
421842 |
Node conclusion = d_node; |
1226 |
210921 |
NodeManager* nm = NodeManager::currentNM(); |
1227 |
210921 |
if (isNary) |
1228 |
|
{ |
1229 |
200289 |
unsigned emptyRows = 0; |
1230 |
551409 |
for (unsigned i = 1; i <= arity; ++i) |
1231 |
|
{ |
1232 |
351120 |
if (transitivityChildren[i].empty()) |
1233 |
|
{ |
1234 |
12 |
emptyRows++; |
1235 |
|
} |
1236 |
|
} |
1237 |
|
// Given two n-ary applications f1:(f a0 ... an-1), f2:(f b0 ... bm-1), of |
1238 |
|
// arities n and m, arity = max(n,m), the number emptyRows establishes the |
1239 |
|
// sizes of the prefixes of f1 of f2 that have been equated via a |
1240 |
|
// transitivity step. The prefixes necessarily have different sizes. The |
1241 |
|
// suffixes have the same sizes. The new conclusion will be of the form |
1242 |
|
// (= (f (f a0 ... ak1) ... an-1) (f (f b0 ... bk2) ... bm-1)) |
1243 |
|
// where |
1244 |
|
// k1 = emptyRows + 1 - (arity - n) |
1245 |
|
// k2 = emptyRows + 1 - (arity - m) |
1246 |
|
// k1 != k2 |
1247 |
|
// n - k1 == m - k2 |
1248 |
|
// Note that by construction the equality between the first emptyRows + 1 |
1249 |
|
// arguments of each application is justified by the transitivity step in |
1250 |
|
// the row emptyRows + 1 in the matrix. |
1251 |
|
// |
1252 |
|
// All of the above is with the very first row in the matrix, reserved for |
1253 |
|
// justifying the equality between the functions, which is not necessary in |
1254 |
|
// the n-ary case, notwithstanding. |
1255 |
200289 |
if (emptyRows > 0) |
1256 |
|
{ |
1257 |
24 |
Trace("eqproof-conv") |
1258 |
12 |
<< "EqProof::addToProof: Found " << emptyRows |
1259 |
12 |
<< " empty rows. Rebuild conclusion " << d_node << "\n"; |
1260 |
|
// New transitivity matrix is as before except that the empty rows in the |
1261 |
|
// beginning are eliminated, as the new arity is the maximal arity among |
1262 |
|
// the applications minus the number of empty rows. |
1263 |
|
std::vector<std::vector<Node>> newTransitivityChildren{ |
1264 |
24 |
transitivityChildren.begin() + 1 + emptyRows, |
1265 |
36 |
transitivityChildren.end()}; |
1266 |
12 |
transitivityChildren.clear(); |
1267 |
12 |
transitivityChildren.push_back(std::vector<Node>()); |
1268 |
36 |
transitivityChildren.insert(transitivityChildren.end(), |
1269 |
|
newTransitivityChildren.begin(), |
1270 |
36 |
newTransitivityChildren.end()); |
1271 |
|
unsigned arityPrefix1 = |
1272 |
12 |
emptyRows + 1 - (arity - d_node[0].getNumChildren()); |
1273 |
24 |
Assert(arityPrefix1 < d_node[0].getNumChildren()) |
1274 |
|
<< "arityPrefix1 " << arityPrefix1 << " not smaller than " |
1275 |
12 |
<< d_node[0] << "'s arity " << d_node[0].getNumChildren() << "\n"; |
1276 |
|
unsigned arityPrefix2 = |
1277 |
12 |
emptyRows + 1 - (arity - d_node[1].getNumChildren()); |
1278 |
24 |
Assert(arityPrefix2 < d_node[1].getNumChildren()) |
1279 |
|
<< "arityPrefix2 " << arityPrefix2 << " not smaller than " |
1280 |
12 |
<< d_node[1] << "'s arity " << d_node[1].getNumChildren() << "\n"; |
1281 |
24 |
Trace("eqproof-conv") << "EqProof::addToProof: New internal " |
1282 |
12 |
"applications with arities " |
1283 |
12 |
<< arityPrefix1 << ", " << arityPrefix2 << ":\n"; |
1284 |
|
std::vector<Node> childrenPrefix1{d_node[0].begin(), |
1285 |
24 |
d_node[0].begin() + arityPrefix1}; |
1286 |
|
std::vector<Node> childrenPrefix2{d_node[1].begin(), |
1287 |
24 |
d_node[1].begin() + arityPrefix2}; |
1288 |
24 |
Node newFirstChild1 = nm->mkNode(k, childrenPrefix1); |
1289 |
24 |
Node newFirstChild2 = nm->mkNode(k, childrenPrefix2); |
1290 |
24 |
Trace("eqproof-conv") |
1291 |
12 |
<< "EqProof::addToProof:\t " << newFirstChild1 << "\n"; |
1292 |
24 |
Trace("eqproof-conv") |
1293 |
12 |
<< "EqProof::addToProof:\t " << newFirstChild2 << "\n"; |
1294 |
24 |
std::vector<Node> newChildren1{newFirstChild1}; |
1295 |
36 |
newChildren1.insert(newChildren1.end(), |
1296 |
24 |
d_node[0].begin() + arityPrefix1, |
1297 |
60 |
d_node[0].end()); |
1298 |
24 |
std::vector<Node> newChildren2{newFirstChild2}; |
1299 |
36 |
newChildren2.insert(newChildren2.end(), |
1300 |
24 |
d_node[1].begin() + arityPrefix2, |
1301 |
60 |
d_node[1].end()); |
1302 |
36 |
conclusion = nm->mkNode(kind::EQUAL, |
1303 |
24 |
nm->mkNode(k, newChildren1), |
1304 |
24 |
nm->mkNode(k, newChildren2)); |
1305 |
|
// update arity |
1306 |
12 |
Assert((arity - emptyRows) == conclusion[0].getNumChildren()); |
1307 |
12 |
arity = arity - emptyRows; |
1308 |
24 |
Trace("eqproof-conv") |
1309 |
12 |
<< "EqProof::addToProof: New conclusion " << conclusion << "\n"; |
1310 |
|
} |
1311 |
|
} |
1312 |
210921 |
if (Trace.isOn("eqproof-conv")) |
1313 |
|
{ |
1314 |
|
Trace("eqproof-conv") |
1315 |
|
<< "EqProof::addToProof: premises from reduced cong of " << conclusion |
1316 |
|
<< ":\n"; |
1317 |
|
for (unsigned i = 0; i <= arity; ++i) |
1318 |
|
{ |
1319 |
|
Trace("eqproof-conv") << "EqProof::addToProof:\t" << i |
1320 |
|
<< "-th arg: " << transitivityChildren[i] << "\n"; |
1321 |
|
} |
1322 |
|
} |
1323 |
421842 |
std::vector<Node> children(arity + 1); |
1324 |
|
// Check if there is a justification for equality between functions (HO case) |
1325 |
210921 |
if (!transitivityChildren[0].empty()) |
1326 |
|
{ |
1327 |
8 |
Assert(k == kind::APPLY_UF) << "Congruence with different functions only " |
1328 |
|
"allowed for uninterpreted functions.\n"; |
1329 |
|
|
1330 |
8 |
children[0] = |
1331 |
16 |
conclusion[0].getOperator().eqNode(conclusion[1].getOperator()); |
1332 |
8 |
Assert(transitivityChildren[0].size() == 1 |
1333 |
|
&& CDProof::isSame(children[0], transitivityChildren[0][0])) |
1334 |
|
<< "Justification of operators equality is wrong: " |
1335 |
|
<< transitivityChildren[0] << "\n"; |
1336 |
|
} |
1337 |
|
// Proccess transitivity matrix to (possibly) generate transitivity steps for |
1338 |
|
// congruence premises (= ai bi) |
1339 |
578015 |
for (unsigned i = 1; i <= arity; ++i) |
1340 |
|
{ |
1341 |
484965 |
Node transConclusion = conclusion[0][i - 1].eqNode(conclusion[1][i - 1]); |
1342 |
367094 |
children[i] = transConclusion; |
1343 |
734188 |
Assert(!transitivityChildren[i].empty()) |
1344 |
|
<< "EqProof::addToProof: did not add any justification for " << i |
1345 |
367094 |
<< "-th arg of congruence " << conclusion << "\n"; |
1346 |
|
// If the transitivity conclusion is a reflexivity step, just add it. Note |
1347 |
|
// that this can happen even with the respective transitivityChildren row |
1348 |
|
// containing several equalities in the case of (= ai bi) being the same |
1349 |
|
// n-ary application that was justified by a congruence step, which can |
1350 |
|
// happen in the current equality engine. |
1351 |
474051 |
if (transConclusion[0] == transConclusion[1]) |
1352 |
|
{ |
1353 |
106957 |
p->addStep(transConclusion, PfRule::REFL, {}, {transConclusion[0]}); |
1354 |
106957 |
continue; |
1355 |
|
} |
1356 |
|
// Remove spurious refl steps from the premises for (= ai bi) |
1357 |
260137 |
cleanReflPremises(transitivityChildren[i]); |
1358 |
520274 |
Assert(transitivityChildren[i].size() > 1 || transitivityChildren[i].empty() |
1359 |
|
|| CDProof::isSame(transitivityChildren[i][0], transConclusion)) |
1360 |
|
<< "EqProof::addToProof: premises " << transitivityChildren[i] << "for " |
1361 |
260137 |
<< i << "-th cong premise " << transConclusion << " don't justify it\n"; |
1362 |
260137 |
unsigned sizeTrans = transitivityChildren[i].size(); |
1363 |
|
// If no transitivity premise left or if (= ai bi) is an assumption (which |
1364 |
|
// might lead to a cycle with a transtivity step), nothing else to do. |
1365 |
260137 |
if (sizeTrans == 0 || assumptions.count(transConclusion) > 0) |
1366 |
|
{ |
1367 |
142266 |
continue; |
1368 |
|
} |
1369 |
|
// If the transitivity conclusion, or its symmetric, occurs in the |
1370 |
|
// transitivity premises, nothing to do, as it is already justified and |
1371 |
|
// doing so again would lead to a cycle. |
1372 |
117871 |
bool occurs = false; |
1373 |
247847 |
for (unsigned j = 0; j < sizeTrans && !occurs; ++j) |
1374 |
|
{ |
1375 |
129976 |
if (CDProof::isSame(transitivityChildren[i][j], transConclusion)) |
1376 |
|
{ |
1377 |
106460 |
occurs = true; |
1378 |
|
} |
1379 |
|
} |
1380 |
117871 |
if (!occurs) |
1381 |
|
{ |
1382 |
|
// Build transitivity step |
1383 |
11411 |
buildTransitivityChain(transConclusion, transitivityChildren[i]); |
1384 |
22822 |
Trace("eqproof-conv") |
1385 |
11411 |
<< "EqProof::addToProof: adding trans step for cong premise " |
1386 |
11411 |
<< transConclusion << " with children " << transitivityChildren[i] |
1387 |
11411 |
<< "\n"; |
1388 |
22822 |
p->addStep( |
1389 |
11411 |
transConclusion, PfRule::TRANS, transitivityChildren[i], {}, true); |
1390 |
|
} |
1391 |
|
} |
1392 |
|
// first-order case |
1393 |
210921 |
if (children[0].isNull()) |
1394 |
|
{ |
1395 |
|
// remove placehold for function equality case |
1396 |
210913 |
children.erase(children.begin()); |
1397 |
|
// Get node of the function operator over which congruence is being |
1398 |
|
// applied. |
1399 |
421826 |
std::vector<Node> args; |
1400 |
210913 |
args.push_back(ProofRuleChecker::mkKindNode(k)); |
1401 |
210913 |
if (kind::metaKindOf(k) == kind::metakind::PARAMETERIZED) |
1402 |
|
{ |
1403 |
203952 |
args.push_back(conclusion[0].getOperator()); |
1404 |
|
} |
1405 |
|
// Add congruence step |
1406 |
421826 |
Trace("eqproof-conv") << "EqProof::addToProof: build cong step of " |
1407 |
210913 |
<< conclusion << " with op " << args[0] |
1408 |
210913 |
<< " and children " << children << "\n"; |
1409 |
210913 |
p->addStep(conclusion, PfRule::CONG, children, args, true); |
1410 |
|
} |
1411 |
|
// higher-order case |
1412 |
|
else |
1413 |
|
{ |
1414 |
|
// Add congruence step |
1415 |
16 |
Trace("eqproof-conv") << "EqProof::addToProof: build HO-cong step of " |
1416 |
8 |
<< conclusion << " with children " << children |
1417 |
8 |
<< "\n"; |
1418 |
8 |
p->addStep(conclusion, PfRule::HO_CONG, children, {}, true); |
1419 |
|
} |
1420 |
|
// If the conclusion of the congruence step changed due to the n-ary handling, |
1421 |
|
// we obtained for example (= (f (f t1 t2 t3) t4) (f (f t5 t6) t7)), which is |
1422 |
|
// flattened into the original conclusion (= (f t1 t2 t3 t4) (f t5 t6 t7)) via |
1423 |
|
// rewriting |
1424 |
210921 |
if (conclusion != d_node) |
1425 |
|
{ |
1426 |
24 |
Trace("eqproof-conv") << "EqProof::addToProof: add " |
1427 |
24 |
<< PfRule::MACRO_SR_PRED_TRANSFORM |
1428 |
12 |
<< " step to flatten rebuilt conclusion " |
1429 |
12 |
<< conclusion << "into " << d_node << "\n"; |
1430 |
36 |
p->addStep( |
1431 |
24 |
d_node, PfRule::MACRO_SR_PRED_TRANSFORM, {conclusion}, {d_node}, true); |
1432 |
|
} |
1433 |
210921 |
visited[d_node] = d_node; |
1434 |
210921 |
return d_node; |
1435 |
|
} |
1436 |
|
|
1437 |
|
} // namespace eq |
1438 |
|
} // Namespace theory |
1439 |
27735 |
} // namespace cvc5 |