1 |
|
/****************************************************************************** |
2 |
|
* Top contributors (to current version): |
3 |
|
* Andrew Reynolds, Gereon Kremer, 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 inference to proof conversion. |
14 |
|
*/ |
15 |
|
|
16 |
|
#include "theory/strings/infer_proof_cons.h" |
17 |
|
|
18 |
|
#include "expr/skolem_manager.h" |
19 |
|
#include "options/smt_options.h" |
20 |
|
#include "options/strings_options.h" |
21 |
|
#include "proof/proof_node_manager.h" |
22 |
|
#include "theory/builtin/proof_checker.h" |
23 |
|
#include "theory/rewriter.h" |
24 |
|
#include "theory/strings/regexp_operation.h" |
25 |
|
#include "theory/strings/theory_strings_utils.h" |
26 |
|
#include "util/statistics_registry.h" |
27 |
|
|
28 |
|
using namespace cvc5::kind; |
29 |
|
|
30 |
|
namespace cvc5 { |
31 |
|
namespace theory { |
32 |
|
namespace strings { |
33 |
|
|
34 |
1243 |
InferProofCons::InferProofCons(context::Context* c, |
35 |
|
ProofNodeManager* pnm, |
36 |
1243 |
SequencesStatistics& statistics) |
37 |
1243 |
: d_pnm(pnm), d_lazyFactMap(c), d_statistics(statistics) |
38 |
|
{ |
39 |
1243 |
Assert(d_pnm != nullptr); |
40 |
1243 |
} |
41 |
|
|
42 |
14480 |
void InferProofCons::notifyFact(const InferInfo& ii) |
43 |
|
{ |
44 |
28616 |
Node fact = ii.d_conc; |
45 |
28960 |
Trace("strings-ipc-debug") |
46 |
14480 |
<< "InferProofCons::notifyFact: " << ii << std::endl; |
47 |
14480 |
if (d_lazyFactMap.find(fact) != d_lazyFactMap.end()) |
48 |
|
{ |
49 |
331 |
Trace("strings-ipc-debug") << "...duplicate!" << std::endl; |
50 |
331 |
return; |
51 |
|
} |
52 |
28285 |
Node symFact = CDProof::getSymmFact(fact); |
53 |
14149 |
if (!symFact.isNull() && d_lazyFactMap.find(symFact) != d_lazyFactMap.end()) |
54 |
|
{ |
55 |
13 |
Trace("strings-ipc-debug") << "...duplicate (sym)!" << std::endl; |
56 |
13 |
return; |
57 |
|
} |
58 |
28272 |
std::shared_ptr<InferInfo> iic = std::make_shared<InferInfo>(ii); |
59 |
14136 |
d_lazyFactMap.insert(ii.d_conc, iic); |
60 |
|
} |
61 |
|
|
62 |
987 |
bool InferProofCons::addProofTo(CDProof* pf, |
63 |
|
Node conc, |
64 |
|
InferenceId infer, |
65 |
|
bool isRev, |
66 |
|
const std::vector<Node>& exp) |
67 |
|
{ |
68 |
|
// now go back and convert it to proof steps and add to proof |
69 |
987 |
bool useBuffer = false; |
70 |
1974 |
ProofStep ps; |
71 |
1974 |
TheoryProofStepBuffer psb(pf->getManager()->getChecker()); |
72 |
|
// run the conversion |
73 |
987 |
convert(infer, isRev, conc, exp, ps, psb, useBuffer); |
74 |
|
// make the proof based on the step or the buffer |
75 |
987 |
if (useBuffer) |
76 |
|
{ |
77 |
787 |
if (!pf->addSteps(psb)) |
78 |
|
{ |
79 |
|
return false; |
80 |
|
} |
81 |
|
} |
82 |
|
else |
83 |
|
{ |
84 |
200 |
if (!pf->addStep(conc, ps)) |
85 |
|
{ |
86 |
|
return false; |
87 |
|
} |
88 |
|
} |
89 |
987 |
return true; |
90 |
|
} |
91 |
|
|
92 |
6869 |
void InferProofCons::packArgs(Node conc, |
93 |
|
InferenceId infer, |
94 |
|
bool isRev, |
95 |
|
const std::vector<Node>& exp, |
96 |
|
std::vector<Node>& args) |
97 |
|
{ |
98 |
6869 |
args.push_back(conc); |
99 |
6869 |
args.push_back(mkInferenceIdNode(infer)); |
100 |
6869 |
args.push_back(NodeManager::currentNM()->mkConst(isRev)); |
101 |
|
// The vector exp is stored as arguments; its flatten form are premises. We |
102 |
|
// need both since the grouping of exp is important, e.g. { (and a b), c } |
103 |
|
// is different from { a, b, c } in the convert routine, since positions |
104 |
|
// of formulas in exp have special meaning. |
105 |
6869 |
args.insert(args.end(), exp.begin(), exp.end()); |
106 |
6869 |
} |
107 |
|
|
108 |
987 |
bool InferProofCons::unpackArgs(const std::vector<Node>& args, |
109 |
|
Node& conc, |
110 |
|
InferenceId& infer, |
111 |
|
bool& isRev, |
112 |
|
std::vector<Node>& exp) |
113 |
|
{ |
114 |
987 |
Assert(args.size() >= 3); |
115 |
987 |
conc = args[0]; |
116 |
987 |
if (!getInferenceId(args[1], infer)) |
117 |
|
{ |
118 |
|
return false; |
119 |
|
} |
120 |
987 |
isRev = args[2].getConst<bool>(); |
121 |
987 |
exp.insert(exp.end(), args.begin() + 3, args.end()); |
122 |
987 |
return true; |
123 |
|
} |
124 |
|
|
125 |
987 |
void InferProofCons::convert(InferenceId infer, |
126 |
|
bool isRev, |
127 |
|
Node conc, |
128 |
|
const std::vector<Node>& exp, |
129 |
|
ProofStep& ps, |
130 |
|
TheoryProofStepBuffer& psb, |
131 |
|
bool& useBuffer) |
132 |
|
{ |
133 |
|
// by default, don't use the buffer |
134 |
987 |
useBuffer = false; |
135 |
|
// Must flatten children with respect to AND to be ready to explain. |
136 |
|
// We store the index where each flattened vector begins, since some |
137 |
|
// explanations are grouped together using AND. |
138 |
1974 |
std::vector<size_t> startExpIndex; |
139 |
3417 |
for (const Node& ec : exp) |
140 |
|
{ |
141 |
|
// store the index in the flattened vector |
142 |
2430 |
startExpIndex.push_back(ps.d_children.size()); |
143 |
2430 |
utils::flattenOp(AND, ec, ps.d_children); |
144 |
|
} |
145 |
|
// debug print |
146 |
987 |
if (Trace.isOn("strings-ipc-debug")) |
147 |
|
{ |
148 |
|
Trace("strings-ipc-debug") << "InferProofCons::convert: " << infer |
149 |
|
<< (isRev ? " :rev " : " ") << conc << std::endl; |
150 |
|
for (const Node& ec : ps.d_children) |
151 |
|
{ |
152 |
|
Trace("strings-ipc-debug") << " e: " << ec << std::endl; |
153 |
|
} |
154 |
|
} |
155 |
|
// try to find a set of proof steps to incorporate into the buffer |
156 |
987 |
psb.clear(); |
157 |
987 |
NodeManager* nm = NodeManager::currentNM(); |
158 |
1974 |
Node nodeIsRev = nm->mkConst(isRev); |
159 |
987 |
switch (infer) |
160 |
|
{ |
161 |
|
// ========================== equal by substitution+rewriting |
162 |
95 |
case InferenceId::STRINGS_I_NORM_S: |
163 |
|
case InferenceId::STRINGS_I_CONST_MERGE: |
164 |
|
case InferenceId::STRINGS_I_NORM: |
165 |
|
case InferenceId::STRINGS_LEN_NORM: |
166 |
|
case InferenceId::STRINGS_NORMAL_FORM: |
167 |
|
case InferenceId::STRINGS_CODE_PROXY: |
168 |
|
{ |
169 |
190 |
std::vector<Node> pcs = ps.d_children; |
170 |
190 |
Node pconc = conc; |
171 |
|
// purify core substitution proves conc from pconc if necessary, |
172 |
|
// we apply MACRO_SR_PRED_INTRO to prove pconc |
173 |
95 |
if (purifyCoreSubstitution(pconc, pcs, psb, false)) |
174 |
|
{ |
175 |
95 |
if (psb.applyPredIntro(pconc, pcs)) |
176 |
|
{ |
177 |
93 |
useBuffer = true; |
178 |
|
} |
179 |
95 |
} |
180 |
|
} |
181 |
95 |
break; |
182 |
|
// ========================== substitution + rewriting |
183 |
143 |
case InferenceId::STRINGS_RE_NF_CONFLICT: |
184 |
|
case InferenceId::STRINGS_EXTF: |
185 |
|
case InferenceId::STRINGS_EXTF_N: |
186 |
|
case InferenceId::STRINGS_EXTF_D: |
187 |
|
case InferenceId::STRINGS_EXTF_D_N: |
188 |
|
case InferenceId::STRINGS_I_CONST_CONFLICT: |
189 |
|
case InferenceId::STRINGS_UNIT_CONST_CONFLICT: |
190 |
|
{ |
191 |
143 |
if (!ps.d_children.empty()) |
192 |
|
{ |
193 |
264 |
std::vector<Node> exps(ps.d_children.begin(), ps.d_children.end() - 1); |
194 |
264 |
Node src = ps.d_children[ps.d_children.size() - 1]; |
195 |
132 |
if (psb.applyPredTransform(src, conc, exps)) |
196 |
|
{ |
197 |
59 |
useBuffer = true; |
198 |
|
} |
199 |
|
} |
200 |
|
else |
201 |
|
{ |
202 |
|
// use the predicate version? |
203 |
11 |
ps.d_args.push_back(conc); |
204 |
11 |
ps.d_rule = PfRule::MACRO_SR_PRED_INTRO; |
205 |
|
} |
206 |
|
} |
207 |
143 |
break; |
208 |
|
// ========================== rewrite pred |
209 |
24 |
case InferenceId::STRINGS_EXTF_EQ_REW: |
210 |
|
{ |
211 |
|
// the last child is the predicate we are operating on, move to front |
212 |
29 |
Node src = ps.d_children[ps.d_children.size() - 1]; |
213 |
29 |
std::vector<Node> expe(ps.d_children.begin(), ps.d_children.end() - 1); |
214 |
|
// start with a default rewrite |
215 |
29 |
Node mainEqSRew = psb.applyPredElim(src, expe); |
216 |
24 |
if (mainEqSRew == conc) |
217 |
|
{ |
218 |
1 |
useBuffer = true; |
219 |
1 |
break; |
220 |
|
} |
221 |
|
// may need the "extended equality rewrite" |
222 |
|
Node mainEqSRew2 = psb.applyPredElim(mainEqSRew, |
223 |
|
{}, |
224 |
|
MethodId::SB_DEFAULT, |
225 |
|
MethodId::SBA_SEQUENTIAL, |
226 |
28 |
MethodId::RW_REWRITE_EQ_EXT); |
227 |
23 |
if (mainEqSRew2 == conc) |
228 |
|
{ |
229 |
18 |
useBuffer = true; |
230 |
18 |
break; |
231 |
|
} |
232 |
|
// rewrite again with default rewriter |
233 |
10 |
Node mainEqSRew3 = psb.applyPredElim(mainEqSRew2, {}); |
234 |
10 |
useBuffer = (mainEqSRew3 == conc); |
235 |
|
} |
236 |
5 |
break; |
237 |
|
// ========================== substitution+rewriting, CONCAT_EQ, ... |
238 |
476 |
case InferenceId::STRINGS_F_CONST: |
239 |
|
case InferenceId::STRINGS_F_UNIFY: |
240 |
|
case InferenceId::STRINGS_F_ENDPOINT_EMP: |
241 |
|
case InferenceId::STRINGS_F_ENDPOINT_EQ: |
242 |
|
case InferenceId::STRINGS_F_NCTN: |
243 |
|
case InferenceId::STRINGS_N_EQ_CONF: |
244 |
|
case InferenceId::STRINGS_N_CONST: |
245 |
|
case InferenceId::STRINGS_N_UNIFY: |
246 |
|
case InferenceId::STRINGS_N_ENDPOINT_EMP: |
247 |
|
case InferenceId::STRINGS_N_ENDPOINT_EQ: |
248 |
|
case InferenceId::STRINGS_N_NCTN: |
249 |
|
case InferenceId::STRINGS_SSPLIT_CST_PROP: |
250 |
|
case InferenceId::STRINGS_SSPLIT_VAR_PROP: |
251 |
|
case InferenceId::STRINGS_SSPLIT_CST: |
252 |
|
case InferenceId::STRINGS_SSPLIT_VAR: |
253 |
|
{ |
254 |
952 |
Trace("strings-ipc-core") << "Generate core rule for " << infer |
255 |
476 |
<< " (rev=" << isRev << ")" << std::endl; |
256 |
|
// All of the above inferences have the form: |
257 |
|
// (explanation for why t and s have the same prefix/suffix) ^ |
258 |
|
// t = s ^ |
259 |
|
// (length constraint)? |
260 |
|
// We call t=s the "main equality" below. The length constraint is |
261 |
|
// optional, which we split on below. |
262 |
476 |
size_t nchild = ps.d_children.size(); |
263 |
476 |
size_t mainEqIndex = 0; |
264 |
476 |
bool mainEqIndexSet = false; |
265 |
|
// the length constraint |
266 |
935 |
std::vector<Node> lenConstraint; |
267 |
|
// these inferences have a length constraint as the last explain |
268 |
476 |
if (infer == InferenceId::STRINGS_N_UNIFY || infer == InferenceId::STRINGS_F_UNIFY |
269 |
294 |
|| infer == InferenceId::STRINGS_SSPLIT_CST || infer == InferenceId::STRINGS_SSPLIT_VAR |
270 |
183 |
|| infer == InferenceId::STRINGS_SSPLIT_VAR_PROP |
271 |
145 |
|| infer == InferenceId::STRINGS_SSPLIT_CST_PROP) |
272 |
|
{ |
273 |
680 |
if (exp.size() >= 2) |
274 |
|
{ |
275 |
340 |
Assert(exp.size() <= startExpIndex.size()); |
276 |
|
// The index of the "main" equality is the last equality before |
277 |
|
// the length explanation. |
278 |
340 |
mainEqIndex = startExpIndex[exp.size() - 1] - 1; |
279 |
340 |
mainEqIndexSet = true; |
280 |
|
// the remainder is the length constraint |
281 |
1020 |
lenConstraint.insert(lenConstraint.end(), |
282 |
680 |
ps.d_children.begin() + mainEqIndex + 1, |
283 |
1360 |
ps.d_children.end()); |
284 |
|
} |
285 |
|
} |
286 |
136 |
else if (nchild >= 1) |
287 |
|
{ |
288 |
|
// The index of the main equality is the last child. |
289 |
136 |
mainEqIndex = nchild - 1; |
290 |
136 |
mainEqIndexSet = true; |
291 |
|
} |
292 |
935 |
Node mainEq; |
293 |
476 |
if (mainEqIndexSet) |
294 |
|
{ |
295 |
476 |
mainEq = ps.d_children[mainEqIndex]; |
296 |
952 |
Trace("strings-ipc-core") << "Main equality " << mainEq << " at index " |
297 |
476 |
<< mainEqIndex << std::endl; |
298 |
|
} |
299 |
476 |
if (mainEq.isNull() || mainEq.getKind() != EQUAL) |
300 |
|
{ |
301 |
|
Trace("strings-ipc-core") |
302 |
|
<< "...failed to find main equality" << std::endl; |
303 |
|
break; |
304 |
|
} |
305 |
|
// apply MACRO_SR_PRED_ELIM using equalities up to the main eq |
306 |
|
// we purify the core substitution |
307 |
|
std::vector<Node> pcsr(ps.d_children.begin(), |
308 |
935 |
ps.d_children.begin() + mainEqIndex); |
309 |
935 |
Node pmainEq = mainEq; |
310 |
|
// we transform mainEq to pmainEq and then use this as the first |
311 |
|
// argument to MACRO_SR_PRED_ELIM. |
312 |
476 |
if (!purifyCoreSubstitution(pmainEq, pcsr, psb, true)) |
313 |
|
{ |
314 |
|
break; |
315 |
|
} |
316 |
935 |
std::vector<Node> childrenSRew; |
317 |
476 |
childrenSRew.push_back(pmainEq); |
318 |
476 |
childrenSRew.insert(childrenSRew.end(), pcsr.begin(), pcsr.end()); |
319 |
|
// now, conclude the proper equality |
320 |
|
Node mainEqSRew = |
321 |
935 |
psb.tryStep(PfRule::MACRO_SR_PRED_ELIM, childrenSRew, {}); |
322 |
476 |
if (CDProof::isSame(mainEqSRew, pmainEq)) |
323 |
|
{ |
324 |
115 |
Trace("strings-ipc-core") << "...undo step" << std::endl; |
325 |
|
// the rule added above was not necessary |
326 |
115 |
psb.popStep(); |
327 |
|
} |
328 |
361 |
else if (mainEqSRew == conc) |
329 |
|
{ |
330 |
|
Trace("strings-ipc-core") << "...success after rewrite!" << std::endl; |
331 |
|
useBuffer = true; |
332 |
|
break; |
333 |
|
} |
334 |
952 |
Trace("strings-ipc-core") |
335 |
476 |
<< "Main equality after subs+rewrite " << mainEqSRew << std::endl; |
336 |
|
// now, apply CONCAT_EQ to get the remainder |
337 |
935 |
std::vector<Node> childrenCeq; |
338 |
476 |
childrenCeq.push_back(mainEqSRew); |
339 |
935 |
std::vector<Node> argsCeq; |
340 |
476 |
argsCeq.push_back(nodeIsRev); |
341 |
935 |
Node mainEqCeq = psb.tryStep(PfRule::CONCAT_EQ, childrenCeq, argsCeq); |
342 |
952 |
Trace("strings-ipc-core") |
343 |
476 |
<< "Main equality after CONCAT_EQ " << mainEqCeq << std::endl; |
344 |
476 |
if (mainEqCeq.isNull() || mainEqCeq.getKind() != EQUAL) |
345 |
|
{ |
346 |
|
// fail |
347 |
|
break; |
348 |
|
} |
349 |
476 |
else if (mainEqCeq == mainEqSRew) |
350 |
|
{ |
351 |
205 |
Trace("strings-ipc-core") << "...undo step" << std::endl; |
352 |
|
// not necessary, probably first component of equality |
353 |
205 |
psb.popStep(); |
354 |
|
} |
355 |
|
// Now, mainEqCeq is an equality t ++ ... == s ++ ... where the |
356 |
|
// inference involved t and s. |
357 |
476 |
if (infer == InferenceId::STRINGS_N_ENDPOINT_EQ |
358 |
379 |
|| infer == InferenceId::STRINGS_N_ENDPOINT_EMP |
359 |
379 |
|| infer == InferenceId::STRINGS_F_ENDPOINT_EQ |
360 |
362 |
|| infer == InferenceId::STRINGS_F_ENDPOINT_EMP) |
361 |
|
{ |
362 |
|
// Should be equal to conclusion already, or rewrite to it. |
363 |
|
// Notice that this step is necessary to handle the "rproc" |
364 |
|
// optimization in processSimpleNEq. Alternatively, this could |
365 |
|
// possibly be done by CONCAT_EQ with !isRev. |
366 |
228 |
std::vector<Node> cexp; |
367 |
114 |
if (psb.applyPredTransform(mainEqCeq, |
368 |
|
conc, |
369 |
|
cexp, |
370 |
|
MethodId::SB_DEFAULT, |
371 |
|
MethodId::SBA_SEQUENTIAL, |
372 |
|
MethodId::RW_REWRITE_EQ_EXT)) |
373 |
|
{ |
374 |
228 |
Trace("strings-ipc-core") << "Transformed to " << conc |
375 |
114 |
<< " via pred transform" << std::endl; |
376 |
|
// success |
377 |
114 |
useBuffer = true; |
378 |
114 |
Trace("strings-ipc-core") << "...success!" << std::endl; |
379 |
114 |
} |
380 |
|
// Otherwise, note that EMP rules conclude ti = "" where |
381 |
|
// t1 ++ ... ++ tn == "". However, these are very rarely applied, let |
382 |
|
// alone for 2+ children. This case is intentionally unhandled here. |
383 |
|
} |
384 |
362 |
else if (infer == InferenceId::STRINGS_N_CONST || infer == InferenceId::STRINGS_F_CONST |
385 |
357 |
|| infer == InferenceId::STRINGS_N_EQ_CONF) |
386 |
|
{ |
387 |
|
// should be a constant conflict |
388 |
10 |
std::vector<Node> childrenC; |
389 |
5 |
childrenC.push_back(mainEqCeq); |
390 |
10 |
std::vector<Node> argsC; |
391 |
5 |
argsC.push_back(nodeIsRev); |
392 |
10 |
Node mainEqC = psb.tryStep(PfRule::CONCAT_CONFLICT, childrenC, argsC); |
393 |
5 |
if (mainEqC == conc) |
394 |
|
{ |
395 |
5 |
useBuffer = true; |
396 |
5 |
Trace("strings-ipc-core") << "...success!" << std::endl; |
397 |
5 |
} |
398 |
|
} |
399 |
|
else |
400 |
|
{ |
401 |
697 |
std::vector<Node> tvec; |
402 |
697 |
std::vector<Node> svec; |
403 |
357 |
utils::getConcat(mainEqCeq[0], tvec); |
404 |
357 |
utils::getConcat(mainEqCeq[1], svec); |
405 |
697 |
Node t0 = tvec[isRev ? tvec.size() - 1 : 0]; |
406 |
697 |
Node s0 = svec[isRev ? svec.size() - 1 : 0]; |
407 |
357 |
bool applySym = false; |
408 |
|
// may need to apply symmetry |
409 |
714 |
if ((infer == InferenceId::STRINGS_SSPLIT_CST |
410 |
254 |
|| infer == InferenceId::STRINGS_SSPLIT_CST_PROP) |
411 |
469 |
&& t0.isConst()) |
412 |
|
{ |
413 |
73 |
Assert(!s0.isConst()); |
414 |
73 |
applySym = true; |
415 |
73 |
std::swap(t0, s0); |
416 |
|
} |
417 |
357 |
if (infer == InferenceId::STRINGS_N_UNIFY || infer == InferenceId::STRINGS_F_UNIFY) |
418 |
|
{ |
419 |
182 |
if (conc.getKind() != EQUAL) |
420 |
|
{ |
421 |
|
break; |
422 |
|
} |
423 |
|
// one side should match, the other side could be a split constant |
424 |
182 |
if (conc[0] != t0 && conc[1] != s0) |
425 |
|
{ |
426 |
96 |
applySym = true; |
427 |
96 |
std::swap(t0, s0); |
428 |
|
} |
429 |
182 |
Assert(conc[0].isConst() == t0.isConst()); |
430 |
182 |
Assert(conc[1].isConst() == s0.isConst()); |
431 |
|
} |
432 |
357 |
PfRule rule = PfRule::UNKNOWN; |
433 |
|
// the form of the required length constraint expected by the proof |
434 |
697 |
Node lenReq; |
435 |
357 |
bool lenSuccess = false; |
436 |
357 |
if (infer == InferenceId::STRINGS_N_UNIFY || infer == InferenceId::STRINGS_F_UNIFY) |
437 |
|
{ |
438 |
|
// the required premise for unify is always len(x) = len(y), |
439 |
|
// however the explanation may not be literally this. Thus, we |
440 |
|
// need to reconstruct a proof from the given explanation. |
441 |
|
// it should be the case that lenConstraint => lenReq. |
442 |
|
// We use terms in the conclusion equality, not t0, s0 here. |
443 |
546 |
lenReq = nm->mkNode(STRING_LENGTH, conc[0]) |
444 |
364 |
.eqNode(nm->mkNode(STRING_LENGTH, conc[1])); |
445 |
182 |
lenSuccess = convertLengthPf(lenReq, lenConstraint, psb); |
446 |
182 |
rule = PfRule::CONCAT_UNIFY; |
447 |
|
} |
448 |
175 |
else if (infer == InferenceId::STRINGS_SSPLIT_VAR) |
449 |
|
{ |
450 |
|
// it should be the case that lenConstraint => lenReq |
451 |
24 |
lenReq = nm->mkNode(STRING_LENGTH, t0) |
452 |
16 |
.eqNode(nm->mkNode(STRING_LENGTH, s0)) |
453 |
16 |
.notNode(); |
454 |
8 |
lenSuccess = convertLengthPf(lenReq, lenConstraint, psb); |
455 |
8 |
rule = PfRule::CONCAT_SPLIT; |
456 |
|
} |
457 |
167 |
else if (infer == InferenceId::STRINGS_SSPLIT_CST) |
458 |
|
{ |
459 |
|
// it should be the case that lenConstraint => lenReq |
460 |
309 |
lenReq = nm->mkNode(STRING_LENGTH, t0) |
461 |
206 |
.eqNode(nm->mkConst(Rational(0))) |
462 |
206 |
.notNode(); |
463 |
103 |
lenSuccess = convertLengthPf(lenReq, lenConstraint, psb); |
464 |
103 |
rule = PfRule::CONCAT_CSPLIT; |
465 |
|
} |
466 |
64 |
else if (infer == InferenceId::STRINGS_SSPLIT_VAR_PROP) |
467 |
|
{ |
468 |
|
// it should be the case that lenConstraint => lenReq |
469 |
55 |
for (unsigned r = 0; r < 2; r++) |
470 |
|
{ |
471 |
165 |
lenReq = nm->mkNode(GT, |
472 |
110 |
nm->mkNode(STRING_LENGTH, t0), |
473 |
110 |
nm->mkNode(STRING_LENGTH, s0)); |
474 |
55 |
if (convertLengthPf(lenReq, lenConstraint, psb)) |
475 |
|
{ |
476 |
38 |
lenSuccess = true; |
477 |
38 |
break; |
478 |
|
} |
479 |
17 |
if (r == 0) |
480 |
|
{ |
481 |
|
// may be the other direction |
482 |
17 |
applySym = true; |
483 |
17 |
std::swap(t0, s0); |
484 |
|
} |
485 |
|
} |
486 |
38 |
rule = PfRule::CONCAT_LPROP; |
487 |
|
} |
488 |
26 |
else if (infer == InferenceId::STRINGS_SSPLIT_CST_PROP) |
489 |
|
{ |
490 |
|
// it should be the case that lenConstraint => lenReq |
491 |
27 |
lenReq = nm->mkNode(STRING_LENGTH, t0) |
492 |
18 |
.eqNode(nm->mkConst(Rational(0))) |
493 |
18 |
.notNode(); |
494 |
9 |
lenSuccess = convertLengthPf(lenReq, lenConstraint, psb); |
495 |
9 |
rule = PfRule::CONCAT_CPROP; |
496 |
|
} |
497 |
357 |
if (!lenSuccess) |
498 |
|
{ |
499 |
34 |
Trace("strings-ipc-core") |
500 |
17 |
<< "...failed due to length constraint" << std::endl; |
501 |
17 |
break; |
502 |
|
} |
503 |
|
// apply symmetry if necessary |
504 |
340 |
if (applySym) |
505 |
|
{ |
506 |
372 |
std::vector<Node> childrenSymm; |
507 |
186 |
childrenSymm.push_back(mainEqCeq); |
508 |
|
// note this explicit step may not be necessary |
509 |
186 |
mainEqCeq = psb.tryStep(PfRule::SYMM, childrenSymm, {}); |
510 |
372 |
Trace("strings-ipc-core") |
511 |
186 |
<< "Main equality after SYMM " << mainEqCeq << std::endl; |
512 |
|
} |
513 |
340 |
if (rule != PfRule::UNKNOWN) |
514 |
|
{ |
515 |
680 |
Trace("strings-ipc-core") |
516 |
340 |
<< "Core rule length requirement is " << lenReq << std::endl; |
517 |
|
// apply the given rule |
518 |
680 |
std::vector<Node> childrenMain; |
519 |
340 |
childrenMain.push_back(mainEqCeq); |
520 |
340 |
childrenMain.push_back(lenReq); |
521 |
680 |
std::vector<Node> argsMain; |
522 |
340 |
argsMain.push_back(nodeIsRev); |
523 |
680 |
Node mainEqMain = psb.tryStep(rule, childrenMain, argsMain); |
524 |
680 |
Trace("strings-ipc-core") << "Main equality after " << rule << " " |
525 |
340 |
<< mainEqMain << std::endl; |
526 |
340 |
if (mainEqMain == mainEqCeq) |
527 |
|
{ |
528 |
1 |
Trace("strings-ipc-core") << "...undo step" << std::endl; |
529 |
|
// not necessary, probably first component of equality |
530 |
1 |
psb.popStep(); |
531 |
|
} |
532 |
|
// either equal or rewrites to it |
533 |
680 |
std::vector<Node> cexp; |
534 |
340 |
if (psb.applyPredTransform(mainEqMain, conc, cexp)) |
535 |
|
{ |
536 |
|
// requires that length success is also true |
537 |
339 |
useBuffer = true; |
538 |
339 |
Trace("strings-ipc-core") << "...success" << std::endl; |
539 |
|
} |
540 |
|
else |
541 |
|
{ |
542 |
1 |
Trace("strings-ipc-core") << "...fail" << std::endl; |
543 |
|
} |
544 |
|
} |
545 |
|
else |
546 |
|
{ |
547 |
|
// should always have given a rule to try above |
548 |
|
Assert(false) << "No reconstruction rule given for " << infer; |
549 |
|
} |
550 |
459 |
} |
551 |
|
} |
552 |
459 |
break; |
553 |
|
// ========================== Disequalities |
554 |
2 |
case InferenceId::STRINGS_DEQ_DISL_FIRST_CHAR_STRING_SPLIT: |
555 |
|
case InferenceId::STRINGS_DEQ_DISL_STRINGS_SPLIT: |
556 |
|
{ |
557 |
6 |
if (conc.getKind() != AND || conc.getNumChildren() != 2 |
558 |
4 |
|| conc[0].getKind() != EQUAL || !conc[0][0].getType().isStringLike() |
559 |
4 |
|| conc[1].getKind() != EQUAL |
560 |
6 |
|| conc[1][0].getKind() != STRING_LENGTH) |
561 |
|
{ |
562 |
|
Trace("strings-ipc-deq") << "malformed application" << std::endl; |
563 |
|
Assert(false) << "unexpected conclusion " << conc << " for " << infer; |
564 |
|
} |
565 |
|
else |
566 |
|
{ |
567 |
|
Node lenReq = |
568 |
4 |
nm->mkNode(GEQ, nm->mkNode(STRING_LENGTH, conc[0][0]), conc[1][1]); |
569 |
4 |
Trace("strings-ipc-deq") |
570 |
2 |
<< "length requirement is " << lenReq << std::endl; |
571 |
2 |
if (convertLengthPf(lenReq, ps.d_children, psb)) |
572 |
|
{ |
573 |
|
Trace("strings-ipc-deq") << "...success length" << std::endl; |
574 |
|
// make the proof |
575 |
|
std::vector<Node> childrenMain; |
576 |
|
childrenMain.push_back(lenReq); |
577 |
|
std::vector<Node> argsMain; |
578 |
|
argsMain.push_back(nodeIsRev); |
579 |
|
Node mainConc = |
580 |
|
psb.tryStep(PfRule::STRING_DECOMPOSE, childrenMain, argsMain); |
581 |
|
Trace("strings-ipc-deq") |
582 |
|
<< "...main conclusion is " << mainConc << std::endl; |
583 |
|
useBuffer = (mainConc == conc); |
584 |
|
Trace("strings-ipc-deq") |
585 |
|
<< "...success is " << useBuffer << std::endl; |
586 |
|
} |
587 |
|
else |
588 |
|
{ |
589 |
2 |
Trace("strings-ipc-deq") << "...fail length" << std::endl; |
590 |
|
} |
591 |
|
} |
592 |
|
} |
593 |
2 |
break; |
594 |
|
// ========================== Boolean split |
595 |
|
case InferenceId::STRINGS_CARD_SP: |
596 |
|
case InferenceId::STRINGS_LEN_SPLIT: |
597 |
|
case InferenceId::STRINGS_LEN_SPLIT_EMP: |
598 |
|
case InferenceId::STRINGS_DEQ_DISL_EMP_SPLIT: |
599 |
|
case InferenceId::STRINGS_DEQ_DISL_FIRST_CHAR_EQ_SPLIT: |
600 |
|
case InferenceId::STRINGS_DEQ_STRINGS_EQ: |
601 |
|
case InferenceId::STRINGS_DEQ_LENS_EQ: |
602 |
|
case InferenceId::STRINGS_DEQ_LENGTH_SP: |
603 |
|
{ |
604 |
|
if (conc.getKind() != OR) |
605 |
|
{ |
606 |
|
// This should never happen. If it does, we resort to using |
607 |
|
// THEORY_INFERENCE below (in production mode). |
608 |
|
Assert(false) << "Expected OR conclusion for " << infer; |
609 |
|
} |
610 |
|
else |
611 |
|
{ |
612 |
|
ps.d_rule = PfRule::SPLIT; |
613 |
|
Assert(ps.d_children.empty()); |
614 |
|
ps.d_args.push_back(conc[0]); |
615 |
|
} |
616 |
|
} |
617 |
|
break; |
618 |
|
// ========================== Regular expression unfolding |
619 |
38 |
case InferenceId::STRINGS_RE_UNFOLD_POS: |
620 |
|
case InferenceId::STRINGS_RE_UNFOLD_NEG: |
621 |
|
{ |
622 |
38 |
Assert (!ps.d_children.empty()); |
623 |
38 |
size_t nchild = ps.d_children.size(); |
624 |
76 |
Node mem = ps.d_children[nchild-1]; |
625 |
38 |
if (nchild>1) |
626 |
|
{ |
627 |
|
// if more than one, apply MACRO_SR_PRED_ELIM |
628 |
|
std::vector<Node> tcs; |
629 |
|
tcs.insert(tcs.end(), |
630 |
|
ps.d_children.begin(), |
631 |
|
ps.d_children.begin() + (nchild-1)); |
632 |
|
mem = psb.applyPredElim(mem, tcs); |
633 |
|
useBuffer = true; |
634 |
|
} |
635 |
38 |
PfRule r = PfRule::UNKNOWN; |
636 |
38 |
if (mem.isNull()) |
637 |
|
{ |
638 |
|
// failed to eliminate above |
639 |
|
Assert(false) << "Failed to apply MACRO_SR_PRED_ELIM for RE unfold"; |
640 |
|
useBuffer = false; |
641 |
|
} |
642 |
38 |
else if (infer == InferenceId::STRINGS_RE_UNFOLD_POS) |
643 |
|
{ |
644 |
28 |
r = PfRule::RE_UNFOLD_POS; |
645 |
|
} |
646 |
|
else |
647 |
|
{ |
648 |
10 |
r = PfRule::RE_UNFOLD_NEG; |
649 |
|
// it may be an optimized form of concat simplification |
650 |
10 |
Assert(mem.getKind() == NOT && mem[0].getKind() == STRING_IN_REGEXP); |
651 |
10 |
if (mem[0][1].getKind() == REGEXP_CONCAT) |
652 |
|
{ |
653 |
|
size_t index; |
654 |
20 |
Node reLen = RegExpOpr::getRegExpConcatFixed(mem[0][1], index); |
655 |
|
// if we can find a fixed length for a component, use the optimized |
656 |
|
// version |
657 |
10 |
if (!reLen.isNull()) |
658 |
|
{ |
659 |
9 |
r = PfRule::RE_UNFOLD_NEG_CONCAT_FIXED; |
660 |
|
} |
661 |
|
} |
662 |
|
} |
663 |
38 |
if (useBuffer) |
664 |
|
{ |
665 |
|
mem = psb.tryStep(r, {mem}, {}); |
666 |
|
// should match the conclusion |
667 |
|
useBuffer = (mem==conc); |
668 |
|
} |
669 |
|
else |
670 |
|
{ |
671 |
38 |
ps.d_rule = r; |
672 |
38 |
} |
673 |
|
} |
674 |
38 |
break; |
675 |
|
// ========================== Reduction |
676 |
9 |
case InferenceId::STRINGS_CTN_POS: |
677 |
|
case InferenceId::STRINGS_CTN_NEG_EQUAL: |
678 |
|
{ |
679 |
9 |
if (ps.d_children.size() != 1) |
680 |
|
{ |
681 |
|
break; |
682 |
|
} |
683 |
9 |
bool polarity = ps.d_children[0].getKind() != NOT; |
684 |
18 |
Node atom = polarity ? ps.d_children[0] : ps.d_children[0][0]; |
685 |
18 |
std::vector<Node> args; |
686 |
9 |
args.push_back(atom); |
687 |
18 |
Node res = psb.tryStep(PfRule::STRING_EAGER_REDUCTION, {}, args); |
688 |
9 |
if (res.isNull()) |
689 |
|
{ |
690 |
|
break; |
691 |
|
} |
692 |
|
// ite( contains(x,t), x = k1 ++ t ++ k2, x != t ) |
693 |
18 |
std::vector<Node> tiChildren; |
694 |
9 |
tiChildren.push_back(ps.d_children[0]); |
695 |
|
Node ctnt = psb.tryStep( |
696 |
18 |
polarity ? PfRule::TRUE_INTRO : PfRule::FALSE_INTRO, tiChildren, {}); |
697 |
9 |
if (ctnt.isNull() || ctnt.getKind() != EQUAL) |
698 |
|
{ |
699 |
|
break; |
700 |
|
} |
701 |
18 |
std::vector<Node> tchildren; |
702 |
9 |
tchildren.push_back(ctnt); |
703 |
|
// apply substitution { contains(x,t) -> true|false } and rewrite to get |
704 |
|
// conclusion x = k1 ++ t ++ k2 or x != t. |
705 |
9 |
if (psb.applyPredTransform(res, conc, tchildren)) |
706 |
|
{ |
707 |
9 |
useBuffer = true; |
708 |
9 |
} |
709 |
|
} |
710 |
9 |
break; |
711 |
|
|
712 |
150 |
case InferenceId::STRINGS_REDUCTION: |
713 |
|
{ |
714 |
150 |
size_t nchild = conc.getNumChildren(); |
715 |
300 |
Node mainEq; |
716 |
150 |
if (conc.getKind() == EQUAL) |
717 |
|
{ |
718 |
|
mainEq = conc; |
719 |
|
} |
720 |
150 |
else if (conc.getKind() == AND && conc[nchild - 1].getKind() == EQUAL) |
721 |
|
{ |
722 |
150 |
mainEq = conc[nchild - 1]; |
723 |
|
} |
724 |
150 |
if (mainEq.isNull()) |
725 |
|
{ |
726 |
|
Trace("strings-ipc-red") << "Bad Reduction: " << conc << std::endl; |
727 |
|
Assert(false) << "Unexpected reduction " << conc; |
728 |
|
break; |
729 |
|
} |
730 |
300 |
std::vector<Node> argsRed; |
731 |
|
// the left hand side of the last conjunct is the term we are reducing |
732 |
150 |
argsRed.push_back(mainEq[0]); |
733 |
300 |
Node red = psb.tryStep(PfRule::STRING_REDUCTION, {}, argsRed); |
734 |
150 |
Trace("strings-ipc-red") << "Reduction : " << red << std::endl; |
735 |
150 |
if (!red.isNull()) |
736 |
|
{ |
737 |
|
// either equal or rewrites to it |
738 |
300 |
std::vector<Node> cexp; |
739 |
150 |
if (psb.applyPredTransform(red, conc, cexp)) |
740 |
|
{ |
741 |
142 |
Trace("strings-ipc-red") << "...success!" << std::endl; |
742 |
142 |
useBuffer = true; |
743 |
|
} |
744 |
|
else |
745 |
|
{ |
746 |
8 |
Trace("strings-ipc-red") << "...failed to reduce" << std::endl; |
747 |
|
} |
748 |
150 |
} |
749 |
|
} |
750 |
150 |
break; |
751 |
|
// ========================== code injectivity |
752 |
30 |
case InferenceId::STRINGS_CODE_INJ: |
753 |
|
{ |
754 |
30 |
ps.d_rule = PfRule::STRING_CODE_INJ; |
755 |
30 |
Assert(conc.getKind() == OR && conc.getNumChildren() == 3 |
756 |
|
&& conc[2].getKind() == EQUAL); |
757 |
30 |
ps.d_args.push_back(conc[2][0]); |
758 |
30 |
ps.d_args.push_back(conc[2][1]); |
759 |
|
} |
760 |
30 |
break; |
761 |
|
// ========================== unit injectivity |
762 |
3 |
case InferenceId::STRINGS_UNIT_INJ: |
763 |
|
{ |
764 |
3 |
ps.d_rule = PfRule::STRING_SEQ_UNIT_INJ; |
765 |
|
} |
766 |
3 |
break; |
767 |
|
// ========================== prefix conflict |
768 |
7 |
case InferenceId::STRINGS_PREFIX_CONFLICT: |
769 |
|
{ |
770 |
7 |
Trace("strings-ipc-prefix") << "Prefix conflict..." << std::endl; |
771 |
14 |
std::vector<Node> eqs; |
772 |
18 |
for (const Node& e : ps.d_children) |
773 |
|
{ |
774 |
11 |
Kind ek = e.getKind(); |
775 |
11 |
if (ek == EQUAL) |
776 |
|
{ |
777 |
7 |
Trace("strings-ipc-prefix") << "- equality : " << e << std::endl; |
778 |
7 |
eqs.push_back(e); |
779 |
|
} |
780 |
4 |
else if (ek == STRING_IN_REGEXP) |
781 |
|
{ |
782 |
|
// unfold it and extract the equality |
783 |
8 |
std::vector<Node> children; |
784 |
4 |
children.push_back(e); |
785 |
8 |
std::vector<Node> args; |
786 |
8 |
Node eunf = psb.tryStep(PfRule::RE_UNFOLD_POS, children, args); |
787 |
8 |
Trace("strings-ipc-prefix") |
788 |
4 |
<< "--- " << e << " unfolds to " << eunf << std::endl; |
789 |
4 |
if (eunf.isNull()) |
790 |
|
{ |
791 |
|
continue; |
792 |
|
} |
793 |
4 |
else if (eunf.getKind() == AND) |
794 |
|
{ |
795 |
|
// equality is the first conjunct |
796 |
8 |
std::vector<Node> childrenAE; |
797 |
4 |
childrenAE.push_back(eunf); |
798 |
8 |
std::vector<Node> argsAE; |
799 |
4 |
argsAE.push_back(nm->mkConst(Rational(0))); |
800 |
8 |
Node eunfAE = psb.tryStep(PfRule::AND_ELIM, childrenAE, argsAE); |
801 |
8 |
Trace("strings-ipc-prefix") |
802 |
4 |
<< "--- and elim to " << eunfAE << std::endl; |
803 |
4 |
if (eunfAE.isNull() || eunfAE.getKind() != EQUAL) |
804 |
|
{ |
805 |
|
Assert(false) |
806 |
|
<< "Unexpected unfolded premise " << eunf << " for " << infer; |
807 |
|
continue; |
808 |
|
} |
809 |
8 |
Trace("strings-ipc-prefix") |
810 |
4 |
<< "- equality : " << eunfAE << std::endl; |
811 |
4 |
eqs.push_back(eunfAE); |
812 |
|
} |
813 |
|
else if (eunf.getKind() == EQUAL) |
814 |
|
{ |
815 |
|
Trace("strings-ipc-prefix") << "- equality : " << eunf << std::endl; |
816 |
|
eqs.push_back(eunf); |
817 |
|
} |
818 |
|
} |
819 |
|
else |
820 |
|
{ |
821 |
|
// not sure how to use this assumption |
822 |
|
Assert(false) << "Unexpected premise " << e << " for " << infer; |
823 |
|
} |
824 |
|
} |
825 |
7 |
if (eqs.empty()) |
826 |
|
{ |
827 |
|
break; |
828 |
|
} |
829 |
|
// connect via transitivity |
830 |
14 |
Node curr = eqs[0]; |
831 |
11 |
for (size_t i = 1, esize = eqs.size(); i < esize; i++) |
832 |
|
{ |
833 |
8 |
Node prev = curr; |
834 |
4 |
curr = convertTrans(curr, eqs[1], psb); |
835 |
4 |
if (curr.isNull()) |
836 |
|
{ |
837 |
|
break; |
838 |
|
} |
839 |
4 |
Trace("strings-ipc-prefix") << "- Via trans: " << curr << std::endl; |
840 |
|
} |
841 |
7 |
if (curr.isNull()) |
842 |
|
{ |
843 |
|
break; |
844 |
|
} |
845 |
14 |
Trace("strings-ipc-prefix") |
846 |
7 |
<< "- Possible conflicting equality : " << curr << std::endl; |
847 |
14 |
std::vector<Node> emp; |
848 |
14 |
Node concE = psb.applyPredElim(curr, emp); |
849 |
14 |
Trace("strings-ipc-prefix") |
850 |
7 |
<< "- After pred elim: " << concE << std::endl; |
851 |
7 |
if (concE == conc) |
852 |
|
{ |
853 |
|
Trace("strings-ipc-prefix") << "...success!" << std::endl; |
854 |
|
useBuffer = true; |
855 |
7 |
} |
856 |
|
} |
857 |
7 |
break; |
858 |
|
// ========================== regular expressions |
859 |
7 |
case InferenceId::STRINGS_RE_INTER_INCLUDE: |
860 |
|
case InferenceId::STRINGS_RE_INTER_CONF: |
861 |
|
case InferenceId::STRINGS_RE_INTER_INFER: |
862 |
|
{ |
863 |
14 |
std::vector<Node> reiExp; |
864 |
14 |
std::vector<Node> reis; |
865 |
14 |
std::vector<Node> reiChildren; |
866 |
14 |
std::vector<Node> reiChildrenOrig; |
867 |
14 |
Node x; |
868 |
|
// make the regular expression intersection that summarizes all |
869 |
|
// memberships in the explanation |
870 |
23 |
for (const Node& c : ps.d_children) |
871 |
|
{ |
872 |
16 |
bool polarity = c.getKind() != NOT; |
873 |
30 |
Node catom = polarity ? c : c[0]; |
874 |
18 |
if (catom.getKind() != STRING_IN_REGEXP) |
875 |
|
{ |
876 |
2 |
Assert(c.getKind() == EQUAL); |
877 |
2 |
if (c.getKind() == EQUAL) |
878 |
|
{ |
879 |
2 |
reiExp.push_back(c); |
880 |
|
} |
881 |
2 |
continue; |
882 |
|
} |
883 |
14 |
if (x.isNull()) |
884 |
|
{ |
885 |
|
// just take the first LHS; others should be equated to it by exp |
886 |
7 |
x = catom[0]; |
887 |
|
} |
888 |
|
Node rcurr = |
889 |
28 |
polarity ? catom[1] : nm->mkNode(REGEXP_COMPLEMENT, catom[1]); |
890 |
14 |
reis.push_back(rcurr); |
891 |
28 |
Node mem = nm->mkNode(STRING_IN_REGEXP, catom[0], rcurr); |
892 |
14 |
reiChildren.push_back(mem); |
893 |
14 |
reiChildrenOrig.push_back(c); |
894 |
|
} |
895 |
|
// go back and justify each premise |
896 |
7 |
bool successChildren = true; |
897 |
21 |
for (size_t i = 0, nchild = reiChildren.size(); i < nchild; i++) |
898 |
|
{ |
899 |
14 |
if (!psb.applyPredTransform(reiChildrenOrig[i], reiChildren[i], reiExp)) |
900 |
|
{ |
901 |
|
Trace("strings-ipc-re") |
902 |
|
<< "... failed to justify child " << reiChildren[i] << " from " |
903 |
|
<< reiChildrenOrig[i] << std::endl; |
904 |
|
successChildren = false; |
905 |
|
break; |
906 |
|
} |
907 |
|
} |
908 |
7 |
if (!successChildren) |
909 |
|
{ |
910 |
|
break; |
911 |
|
} |
912 |
14 |
Node mem = psb.tryStep(PfRule::RE_INTER, reiChildren, {}); |
913 |
14 |
Trace("strings-ipc-re") |
914 |
7 |
<< "Regular expression summary: " << mem << std::endl; |
915 |
|
// the conclusion is rewritable to the premises via rewriting? |
916 |
7 |
if (psb.applyPredTransform(mem, conc, {})) |
917 |
|
{ |
918 |
2 |
Trace("strings-ipc-re") << "... success!" << std::endl; |
919 |
2 |
useBuffer = true; |
920 |
|
} |
921 |
|
else |
922 |
|
{ |
923 |
10 |
Trace("strings-ipc-re") |
924 |
5 |
<< "...failed to rewrite to conclusion" << std::endl; |
925 |
7 |
} |
926 |
|
} |
927 |
7 |
break; |
928 |
|
// ========================== unknown and currently unsupported |
929 |
3 |
case InferenceId::STRINGS_CARDINALITY: |
930 |
|
case InferenceId::STRINGS_I_CYCLE_E: |
931 |
|
case InferenceId::STRINGS_I_CYCLE: |
932 |
|
case InferenceId::STRINGS_INFER_EMP: |
933 |
|
case InferenceId::STRINGS_RE_DELTA: |
934 |
|
case InferenceId::STRINGS_RE_DELTA_CONF: |
935 |
|
case InferenceId::STRINGS_RE_DERIVE: |
936 |
|
case InferenceId::STRINGS_FLOOP: |
937 |
|
case InferenceId::STRINGS_FLOOP_CONFLICT: |
938 |
|
case InferenceId::STRINGS_DEQ_NORM_EMP: |
939 |
|
case InferenceId::STRINGS_CTN_TRANS: |
940 |
|
case InferenceId::STRINGS_CTN_DECOMPOSE: |
941 |
|
default: |
942 |
|
// do nothing, these will be converted to THEORY_INFERENCE below since the |
943 |
|
// rule is unknown. |
944 |
3 |
break; |
945 |
|
} |
946 |
|
|
947 |
|
// now see if we would succeed with the checker-to-try |
948 |
987 |
bool success = false; |
949 |
987 |
if (ps.d_rule != PfRule::UNKNOWN) |
950 |
|
{ |
951 |
164 |
Trace("strings-ipc") << "For " << infer << ", try proof rule " << ps.d_rule |
952 |
82 |
<< "..."; |
953 |
82 |
Assert(ps.d_rule != PfRule::UNKNOWN); |
954 |
164 |
Node pconc = psb.tryStep(ps.d_rule, ps.d_children, ps.d_args); |
955 |
82 |
if (pconc.isNull() || pconc != conc) |
956 |
|
{ |
957 |
2 |
Trace("strings-ipc") << "failed, pconc is " << pconc << " (expected " |
958 |
1 |
<< conc << ")" << std::endl; |
959 |
1 |
ps.d_rule = PfRule::UNKNOWN; |
960 |
|
} |
961 |
|
else |
962 |
|
{ |
963 |
|
// successfully set up a single step proof in ps |
964 |
81 |
success = true; |
965 |
81 |
Trace("strings-ipc") << "success!" << std::endl; |
966 |
|
} |
967 |
|
} |
968 |
905 |
else if (useBuffer) |
969 |
|
{ |
970 |
|
// successfully set up a multi step proof in psb |
971 |
787 |
success = true; |
972 |
|
} |
973 |
|
else |
974 |
|
{ |
975 |
236 |
Trace("strings-ipc") << "For " << infer << " " << conc |
976 |
118 |
<< ", no proof rule, failed" << std::endl; |
977 |
|
} |
978 |
987 |
if (!success) |
979 |
|
{ |
980 |
|
// debug print |
981 |
119 |
if (Trace.isOn("strings-ipc-fail")) |
982 |
|
{ |
983 |
|
Trace("strings-ipc-fail") |
984 |
|
<< "InferProofCons::convert: Failed " << infer |
985 |
|
<< (isRev ? " :rev " : " ") << conc << std::endl; |
986 |
|
for (const Node& ec : exp) |
987 |
|
{ |
988 |
|
Trace("strings-ipc-fail") << " e: " << ec << std::endl; |
989 |
|
} |
990 |
|
} |
991 |
|
// untrustworthy conversion, the argument of THEORY_INFERENCE is its |
992 |
|
// conclusion |
993 |
119 |
ps.d_args.clear(); |
994 |
119 |
ps.d_args.push_back(conc); |
995 |
238 |
Node t = builtin::BuiltinProofRuleChecker::mkTheoryIdNode(THEORY_STRINGS); |
996 |
119 |
ps.d_args.push_back(t); |
997 |
|
// use the trust rule |
998 |
119 |
ps.d_rule = PfRule::THEORY_INFERENCE; |
999 |
|
} |
1000 |
987 |
if (Trace.isOn("strings-ipc-debug")) |
1001 |
|
{ |
1002 |
|
if (useBuffer) |
1003 |
|
{ |
1004 |
|
Trace("strings-ipc-debug") |
1005 |
|
<< "InferProofCons::convert returned buffer with " |
1006 |
|
<< psb.getNumSteps() << " steps:" << std::endl; |
1007 |
|
const std::vector<std::pair<Node, ProofStep>>& steps = psb.getSteps(); |
1008 |
|
for (const std::pair<Node, ProofStep>& step : steps) |
1009 |
|
{ |
1010 |
|
Trace("strings-ipc-debug") |
1011 |
|
<< "- " << step.first << " via " << step.second << std::endl; |
1012 |
|
} |
1013 |
|
} |
1014 |
|
else |
1015 |
|
{ |
1016 |
|
Trace("strings-ipc-debug") |
1017 |
|
<< "InferProofCons::convert returned " << ps << std::endl; |
1018 |
|
} |
1019 |
|
} |
1020 |
987 |
} |
1021 |
|
|
1022 |
359 |
bool InferProofCons::convertLengthPf(Node lenReq, |
1023 |
|
const std::vector<Node>& lenExp, |
1024 |
|
TheoryProofStepBuffer& psb) |
1025 |
|
{ |
1026 |
653 |
for (const Node& le : lenExp) |
1027 |
|
{ |
1028 |
483 |
if (lenReq == le) |
1029 |
|
{ |
1030 |
189 |
return true; |
1031 |
|
} |
1032 |
|
} |
1033 |
340 |
Trace("strings-ipc-len") << "Must explain " << lenReq << " by " << lenExp |
1034 |
170 |
<< std::endl; |
1035 |
223 |
for (const Node& le : lenExp) |
1036 |
|
{ |
1037 |
|
// probably rewrites to it? |
1038 |
257 |
std::vector<Node> exp; |
1039 |
204 |
if (psb.applyPredTransform(le, lenReq, exp)) |
1040 |
|
{ |
1041 |
86 |
Trace("strings-ipc-len") << "...success by rewrite" << std::endl; |
1042 |
86 |
return true; |
1043 |
|
} |
1044 |
|
// maybe x != "" => len(x) != 0 |
1045 |
171 |
std::vector<Node> children; |
1046 |
118 |
children.push_back(le); |
1047 |
171 |
std::vector<Node> args; |
1048 |
171 |
Node res = psb.tryStep(PfRule::STRING_LENGTH_NON_EMPTY, children, args); |
1049 |
118 |
if (res == lenReq) |
1050 |
|
{ |
1051 |
65 |
Trace("strings-ipc-len") << "...success by LENGTH_NON_EMPTY" << std::endl; |
1052 |
65 |
return true; |
1053 |
|
} |
1054 |
|
} |
1055 |
19 |
Trace("strings-ipc-len") << "...failed" << std::endl; |
1056 |
19 |
return false; |
1057 |
|
} |
1058 |
|
|
1059 |
4 |
Node InferProofCons::convertTrans(Node eqa, |
1060 |
|
Node eqb, |
1061 |
|
TheoryProofStepBuffer& psb) |
1062 |
|
{ |
1063 |
4 |
if (eqa.getKind() != EQUAL || eqb.getKind() != EQUAL) |
1064 |
|
{ |
1065 |
|
return Node::null(); |
1066 |
|
} |
1067 |
4 |
for (uint32_t i = 0; i < 2; i++) |
1068 |
|
{ |
1069 |
4 |
Node eqaSym = i == 0 ? eqa[1].eqNode(eqa[0]) : eqa; |
1070 |
5 |
for (uint32_t j = 0; j < 2; j++) |
1071 |
|
{ |
1072 |
6 |
Node eqbSym = j == 0 ? eqb : eqb[1].eqNode(eqb[1]); |
1073 |
5 |
if (eqa[i] == eqb[j]) |
1074 |
|
{ |
1075 |
8 |
std::vector<Node> children; |
1076 |
4 |
children.push_back(eqaSym); |
1077 |
4 |
children.push_back(eqbSym); |
1078 |
4 |
return psb.tryStep(PfRule::TRANS, children, {}); |
1079 |
|
} |
1080 |
|
} |
1081 |
|
} |
1082 |
|
return Node::null(); |
1083 |
|
} |
1084 |
|
|
1085 |
6869 |
std::shared_ptr<ProofNode> InferProofCons::getProofFor(Node fact) |
1086 |
|
{ |
1087 |
|
// get the inference |
1088 |
6869 |
NodeInferInfoMap::iterator it = d_lazyFactMap.find(fact); |
1089 |
6869 |
if (it == d_lazyFactMap.end()) |
1090 |
|
{ |
1091 |
|
Node factSym = CDProof::getSymmFact(fact); |
1092 |
|
if (!factSym.isNull()) |
1093 |
|
{ |
1094 |
|
// Use the symmetric fact. There is no need to explictly make a |
1095 |
|
// SYMM proof, as this is handled by CDProof::getProofFor below. |
1096 |
|
it = d_lazyFactMap.find(factSym); |
1097 |
|
} |
1098 |
|
} |
1099 |
6869 |
AlwaysAssert(it != d_lazyFactMap.end()); |
1100 |
13738 |
std::shared_ptr<InferInfo> ii = (*it).second; |
1101 |
6869 |
Assert(ii->d_conc == fact); |
1102 |
|
// make a placeholder proof using STRINGS_INFERENCE, which is reconstructed |
1103 |
|
// during post-process |
1104 |
13738 |
CDProof pf(d_pnm); |
1105 |
13738 |
std::vector<Node> args; |
1106 |
6869 |
packArgs(ii->d_conc, ii->getId(), ii->d_idRev, ii->d_premises, args); |
1107 |
|
// must flatten |
1108 |
13738 |
std::vector<Node> exp; |
1109 |
26276 |
for (const Node& ec : ii->d_premises) |
1110 |
|
{ |
1111 |
19407 |
utils::flattenOp(AND, ec, exp); |
1112 |
|
} |
1113 |
6869 |
pf.addStep(fact, PfRule::STRING_INFERENCE, exp, args); |
1114 |
13738 |
return pf.getProofFor(fact); |
1115 |
|
} |
1116 |
|
|
1117 |
19887 |
std::string InferProofCons::identify() const |
1118 |
|
{ |
1119 |
19887 |
return "strings::InferProofCons"; |
1120 |
|
} |
1121 |
|
|
1122 |
571 |
bool InferProofCons::purifyCoreSubstitution(Node& tgt, |
1123 |
|
std::vector<Node>& children, |
1124 |
|
TheoryProofStepBuffer& psb, |
1125 |
|
bool concludeTgtNew) |
1126 |
|
{ |
1127 |
|
// collect the terms to purify, which are the LHS of the substitution |
1128 |
1142 |
std::unordered_set<Node> termsToPurify; |
1129 |
1688 |
for (const Node& nc : children) |
1130 |
|
{ |
1131 |
1117 |
Assert(nc.getKind() == EQUAL && nc[0].getType().isStringLike()); |
1132 |
1117 |
termsToPurify.insert(nc[0]); |
1133 |
|
} |
1134 |
|
// now, purify each of the children of the substitution |
1135 |
1688 |
for (size_t i = 0, nchild = children.size(); i < nchild; i++) |
1136 |
|
{ |
1137 |
2234 |
Node pnc = purifyCorePredicate(children[i], true, psb, termsToPurify); |
1138 |
1117 |
if (pnc.isNull()) |
1139 |
|
{ |
1140 |
|
return false; |
1141 |
|
} |
1142 |
1117 |
if (children[i] != pnc) |
1143 |
|
{ |
1144 |
138 |
Trace("strings-ipc-pure-subs") |
1145 |
69 |
<< "Converted: " << children[i] << " to " << pnc << std::endl; |
1146 |
69 |
children[i] = pnc; |
1147 |
|
} |
1148 |
|
// we now should have a substitution with only atomic terms |
1149 |
1117 |
Assert(children[i][0].getNumChildren() == 0); |
1150 |
|
} |
1151 |
|
// now, purify the target predicate |
1152 |
571 |
tgt = purifyCorePredicate(tgt, concludeTgtNew, psb, termsToPurify); |
1153 |
571 |
return !tgt.isNull(); |
1154 |
|
} |
1155 |
|
|
1156 |
1688 |
Node InferProofCons::purifyCorePredicate( |
1157 |
|
Node lit, |
1158 |
|
bool concludeNew, |
1159 |
|
TheoryProofStepBuffer& psb, |
1160 |
|
std::unordered_set<Node>& termsToPurify) |
1161 |
|
{ |
1162 |
1688 |
bool pol = lit.getKind() != NOT; |
1163 |
3376 |
Node atom = pol ? lit : lit[0]; |
1164 |
1688 |
if (atom.getKind() != EQUAL || !atom[0].getType().isStringLike()) |
1165 |
|
{ |
1166 |
|
// we only purify string (dis)equalities |
1167 |
6 |
return lit; |
1168 |
|
} |
1169 |
|
// purify both sides of the equality |
1170 |
3364 |
std::vector<Node> pcs; |
1171 |
1682 |
bool childChanged = false; |
1172 |
5046 |
for (const Node& lc : atom) |
1173 |
|
{ |
1174 |
6728 |
Node plc = purifyCoreTerm(lc, termsToPurify); |
1175 |
3364 |
childChanged = childChanged || plc != lc; |
1176 |
3364 |
pcs.push_back(plc); |
1177 |
|
} |
1178 |
1682 |
if (!childChanged) |
1179 |
|
{ |
1180 |
1554 |
return lit; |
1181 |
|
} |
1182 |
128 |
NodeManager* nm = NodeManager::currentNM(); |
1183 |
256 |
Node newLit = nm->mkNode(EQUAL, pcs); |
1184 |
128 |
if (!pol) |
1185 |
|
{ |
1186 |
|
newLit = newLit.notNode(); |
1187 |
|
} |
1188 |
128 |
Assert(lit != newLit); |
1189 |
|
// prove by transformation, should always succeed |
1190 |
128 |
if (!psb.applyPredTransform( |
1191 |
|
concludeNew ? lit : newLit, concludeNew ? newLit : lit, {})) |
1192 |
|
{ |
1193 |
|
// failed, return null |
1194 |
|
return Node::null(); |
1195 |
|
} |
1196 |
128 |
return newLit; |
1197 |
|
} |
1198 |
|
|
1199 |
7307 |
Node InferProofCons::purifyCoreTerm(Node n, |
1200 |
|
std::unordered_set<Node>& termsToPurify) |
1201 |
|
{ |
1202 |
7307 |
Assert(n.getType().isStringLike()); |
1203 |
7307 |
if (n.getNumChildren() == 0) |
1204 |
|
{ |
1205 |
5471 |
return n; |
1206 |
|
} |
1207 |
1836 |
NodeManager* nm = NodeManager::currentNM(); |
1208 |
1836 |
if (n.getKind() == STRING_CONCAT) |
1209 |
|
{ |
1210 |
3058 |
std::vector<Node> pcs; |
1211 |
5472 |
for (const Node& nc : n) |
1212 |
|
{ |
1213 |
3943 |
pcs.push_back(purifyCoreTerm(nc, termsToPurify)); |
1214 |
|
} |
1215 |
1529 |
return nm->mkNode(STRING_CONCAT, pcs); |
1216 |
|
} |
1217 |
307 |
if (termsToPurify.find(n) == termsToPurify.end()) |
1218 |
|
{ |
1219 |
|
// did not need to purify |
1220 |
173 |
return n; |
1221 |
|
} |
1222 |
134 |
SkolemManager* sm = nm->getSkolemManager(); |
1223 |
268 |
Node k = sm->mkPurifySkolem(n, "k"); |
1224 |
134 |
return k; |
1225 |
|
} |
1226 |
|
|
1227 |
|
} // namespace strings |
1228 |
|
} // namespace theory |
1229 |
29499 |
} // namespace cvc5 |