1 |
|
/****************************************************************************** |
2 |
|
* Top contributors (to current version): |
3 |
|
* Andrew Reynolds, Andres Noetzli, 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 arithmetic entailment computation for string terms. |
14 |
|
*/ |
15 |
|
|
16 |
|
#include "theory/strings/arith_entail.h" |
17 |
|
|
18 |
|
#include "expr/attribute.h" |
19 |
|
#include "expr/node_algorithm.h" |
20 |
|
#include "theory/arith/arith_msum.h" |
21 |
|
#include "theory/rewriter.h" |
22 |
|
#include "theory/strings/theory_strings_utils.h" |
23 |
|
#include "theory/strings/word.h" |
24 |
|
#include "theory/theory.h" |
25 |
|
#include "util/rational.h" |
26 |
|
|
27 |
|
using namespace cvc5::kind; |
28 |
|
|
29 |
|
namespace cvc5 { |
30 |
|
namespace theory { |
31 |
|
namespace strings { |
32 |
|
|
33 |
1738 |
bool ArithEntail::checkEq(Node a, Node b) |
34 |
|
{ |
35 |
1738 |
if (a == b) |
36 |
|
{ |
37 |
|
return true; |
38 |
|
} |
39 |
3476 |
Node ar = Rewriter::rewrite(a); |
40 |
3476 |
Node br = Rewriter::rewrite(b); |
41 |
1738 |
return ar == br; |
42 |
|
} |
43 |
|
|
44 |
373342 |
bool ArithEntail::check(Node a, Node b, bool strict) |
45 |
|
{ |
46 |
373342 |
if (a == b) |
47 |
|
{ |
48 |
20218 |
return !strict; |
49 |
|
} |
50 |
706248 |
Node diff = NodeManager::currentNM()->mkNode(kind::MINUS, a, b); |
51 |
353124 |
return check(diff, strict); |
52 |
|
} |
53 |
|
|
54 |
|
struct StrCheckEntailArithTag |
55 |
|
{ |
56 |
|
}; |
57 |
|
struct StrCheckEntailArithComputedTag |
58 |
|
{ |
59 |
|
}; |
60 |
|
/** Attribute true for expressions for which check returned true */ |
61 |
|
typedef expr::Attribute<StrCheckEntailArithTag, bool> StrCheckEntailArithAttr; |
62 |
|
typedef expr::Attribute<StrCheckEntailArithComputedTag, bool> |
63 |
|
StrCheckEntailArithComputedAttr; |
64 |
|
|
65 |
629060 |
bool ArithEntail::check(Node a, bool strict) |
66 |
|
{ |
67 |
629060 |
if (a.isConst()) |
68 |
|
{ |
69 |
67520 |
return a.getConst<Rational>().sgn() >= (strict ? 1 : 0); |
70 |
|
} |
71 |
|
|
72 |
|
Node ar = strict ? NodeManager::currentNM()->mkNode( |
73 |
709945 |
kind::MINUS, a, NodeManager::currentNM()->mkConst(Rational(1))) |
74 |
1271485 |
: a; |
75 |
561540 |
ar = Rewriter::rewrite(ar); |
76 |
|
|
77 |
561540 |
if (ar.getAttribute(StrCheckEntailArithComputedAttr())) |
78 |
|
{ |
79 |
459163 |
return ar.getAttribute(StrCheckEntailArithAttr()); |
80 |
|
} |
81 |
|
|
82 |
102377 |
bool ret = checkInternal(ar); |
83 |
102377 |
if (!ret) |
84 |
|
{ |
85 |
|
// try with approximations |
86 |
96407 |
ret = checkApprox(ar); |
87 |
|
} |
88 |
|
// cache the result |
89 |
102377 |
ar.setAttribute(StrCheckEntailArithAttr(), ret); |
90 |
102377 |
ar.setAttribute(StrCheckEntailArithComputedAttr(), true); |
91 |
102377 |
return ret; |
92 |
|
} |
93 |
|
|
94 |
96407 |
bool ArithEntail::checkApprox(Node ar) |
95 |
|
{ |
96 |
96407 |
Assert(Rewriter::rewrite(ar) == ar); |
97 |
96407 |
NodeManager* nm = NodeManager::currentNM(); |
98 |
192814 |
std::map<Node, Node> msum; |
99 |
192814 |
Trace("strings-ent-approx-debug") |
100 |
96407 |
<< "Setup arithmetic approximations for " << ar << std::endl; |
101 |
96407 |
if (!ArithMSum::getMonomialSum(ar, msum)) |
102 |
|
{ |
103 |
|
Trace("strings-ent-approx-debug") |
104 |
|
<< "...failed to get monomial sum!" << std::endl; |
105 |
|
return false; |
106 |
|
} |
107 |
|
// for each monomial v*c, mApprox[v] a list of |
108 |
|
// possibilities for how the term can be soundly approximated, that is, |
109 |
|
// if mApprox[v] contains av, then v*c > av*c. Notice that if c |
110 |
|
// is positive, then v > av, otherwise if c is negative, then v < av. |
111 |
|
// In other words, av is an under-approximation if c is positive, and an |
112 |
|
// over-approximation if c is negative. |
113 |
96407 |
bool changed = false; |
114 |
192814 |
std::map<Node, std::vector<Node> > mApprox; |
115 |
|
// map from approximations to their monomial sums |
116 |
192814 |
std::map<Node, std::map<Node, Node> > approxMsums; |
117 |
|
// aarSum stores each monomial that does not have multiple approximations |
118 |
192814 |
std::vector<Node> aarSum; |
119 |
305959 |
for (std::pair<const Node, Node>& m : msum) |
120 |
|
{ |
121 |
419104 |
Node v = m.first; |
122 |
419104 |
Node c = m.second; |
123 |
419104 |
Trace("strings-ent-approx-debug") |
124 |
209552 |
<< "Get approximations " << v << "..." << std::endl; |
125 |
209552 |
if (v.isNull()) |
126 |
|
{ |
127 |
146366 |
Node mn = c.isNull() ? nm->mkConst(Rational(1)) : c; |
128 |
73183 |
aarSum.push_back(mn); |
129 |
|
} |
130 |
|
else |
131 |
|
{ |
132 |
|
// c.isNull() means c = 1 |
133 |
136369 |
bool isOverApprox = !c.isNull() && c.getConst<Rational>().sgn() == -1; |
134 |
136369 |
std::vector<Node>& approx = mApprox[v]; |
135 |
272738 |
std::unordered_set<Node> visited; |
136 |
272738 |
std::vector<Node> toProcess; |
137 |
136369 |
toProcess.push_back(v); |
138 |
41790 |
do |
139 |
|
{ |
140 |
356318 |
Node curr = toProcess.back(); |
141 |
178159 |
Trace("strings-ent-approx-debug") << " process " << curr << std::endl; |
142 |
178159 |
curr = Rewriter::rewrite(curr); |
143 |
178159 |
toProcess.pop_back(); |
144 |
178159 |
if (visited.find(curr) == visited.end()) |
145 |
|
{ |
146 |
175202 |
visited.insert(curr); |
147 |
350404 |
std::vector<Node> currApprox; |
148 |
175202 |
getArithApproximations(curr, currApprox, isOverApprox); |
149 |
175202 |
if (currApprox.empty()) |
150 |
|
{ |
151 |
289952 |
Trace("strings-ent-approx-debug") |
152 |
144976 |
<< "...approximation: " << curr << std::endl; |
153 |
|
// no approximations, thus curr is a possibility |
154 |
144976 |
approx.push_back(curr); |
155 |
|
} |
156 |
|
else |
157 |
|
{ |
158 |
30226 |
toProcess.insert( |
159 |
60452 |
toProcess.end(), currApprox.begin(), currApprox.end()); |
160 |
|
} |
161 |
|
} |
162 |
178159 |
} while (!toProcess.empty()); |
163 |
136369 |
Assert(!approx.empty()); |
164 |
|
// if we have only one approximation, move it to final |
165 |
136369 |
if (approx.size() == 1) |
166 |
|
{ |
167 |
127848 |
changed = v != approx[0]; |
168 |
255696 |
Node mn = ArithMSum::mkCoeffTerm(c, approx[0]); |
169 |
127848 |
aarSum.push_back(mn); |
170 |
127848 |
mApprox.erase(v); |
171 |
|
} |
172 |
|
else |
173 |
|
{ |
174 |
|
// compute monomial sum form for each approximation, used below |
175 |
25649 |
for (const Node& aa : approx) |
176 |
|
{ |
177 |
17128 |
if (approxMsums.find(aa) == approxMsums.end()) |
178 |
|
{ |
179 |
|
CVC5_UNUSED bool ret = |
180 |
16855 |
ArithMSum::getMonomialSum(aa, approxMsums[aa]); |
181 |
16855 |
Assert(ret); |
182 |
|
} |
183 |
|
} |
184 |
8521 |
changed = true; |
185 |
|
} |
186 |
|
} |
187 |
|
} |
188 |
96407 |
if (!changed) |
189 |
|
{ |
190 |
|
// approximations had no effect, return |
191 |
77207 |
Trace("strings-ent-approx-debug") << "...no approximations" << std::endl; |
192 |
77207 |
return false; |
193 |
|
} |
194 |
|
// get the current "fixed" sum for the abstraction of ar |
195 |
19200 |
Node aar = aarSum.empty() |
196 |
20957 |
? nm->mkConst(Rational(0)) |
197 |
40157 |
: (aarSum.size() == 1 ? aarSum[0] : nm->mkNode(PLUS, aarSum)); |
198 |
19200 |
aar = Rewriter::rewrite(aar); |
199 |
38400 |
Trace("strings-ent-approx-debug") |
200 |
38400 |
<< "...processed fixed sum " << aar << " with " << mApprox.size() |
201 |
19200 |
<< " approximated monomials." << std::endl; |
202 |
|
// if we have a choice of how to approximate |
203 |
19200 |
if (!mApprox.empty()) |
204 |
|
{ |
205 |
|
// convert aar back to monomial sum |
206 |
15364 |
std::map<Node, Node> msumAar; |
207 |
7682 |
if (!ArithMSum::getMonomialSum(aar, msumAar)) |
208 |
|
{ |
209 |
|
return false; |
210 |
|
} |
211 |
7682 |
if (Trace.isOn("strings-ent-approx")) |
212 |
|
{ |
213 |
|
Trace("strings-ent-approx") |
214 |
|
<< "---- Check arithmetic entailment by under-approximation " << ar |
215 |
|
<< " >= 0" << std::endl; |
216 |
|
Trace("strings-ent-approx") << "FIXED:" << std::endl; |
217 |
|
ArithMSum::debugPrintMonomialSum(msumAar, "strings-ent-approx"); |
218 |
|
Trace("strings-ent-approx") << "APPROX:" << std::endl; |
219 |
|
for (std::pair<const Node, std::vector<Node> >& a : mApprox) |
220 |
|
{ |
221 |
|
Node c = msum[a.first]; |
222 |
|
Trace("strings-ent-approx") << " "; |
223 |
|
if (!c.isNull()) |
224 |
|
{ |
225 |
|
Trace("strings-ent-approx") << c << " * "; |
226 |
|
} |
227 |
|
Trace("strings-ent-approx") |
228 |
|
<< a.second << " ...from " << a.first << std::endl; |
229 |
|
} |
230 |
|
Trace("strings-ent-approx") << std::endl; |
231 |
|
} |
232 |
15364 |
Rational one(1); |
233 |
|
// incorporate monomials one at a time that have a choice of approximations |
234 |
23112 |
while (!mApprox.empty()) |
235 |
|
{ |
236 |
15430 |
Node v; |
237 |
15430 |
Node vapprox; |
238 |
7715 |
int maxScore = -1; |
239 |
|
// Look at each approximation, take the one with the best score. |
240 |
|
// Notice that we are in the process of trying to prove |
241 |
|
// ( c1*t1 + .. + cn*tn ) + ( approx_1 | ... | approx_m ) >= 0, |
242 |
|
// where c1*t1 + .. + cn*tn is the "fixed" component of our sum (aar) |
243 |
|
// and approx_1 ... approx_m are possible approximations. The |
244 |
|
// intution here is that we want coefficients c1...cn to be positive. |
245 |
|
// This is because arithmetic string terms t1...tn (which may be |
246 |
|
// applications of len, indexof, str.to.int) are never entailed to be |
247 |
|
// negative. Hence, we add the approx_i that contributes the "most" |
248 |
|
// towards making all constants c1...cn positive and cancelling negative |
249 |
|
// monomials in approx_i itself. |
250 |
7715 |
for (std::pair<const Node, std::vector<Node> >& nam : mApprox) |
251 |
|
{ |
252 |
7715 |
Node cr = msum[nam.first]; |
253 |
23185 |
for (const Node& aa : nam.second) |
254 |
|
{ |
255 |
15470 |
unsigned helpsCancelCount = 0; |
256 |
15470 |
unsigned addsObligationCount = 0; |
257 |
15470 |
std::map<Node, Node>::iterator it; |
258 |
|
// we are processing an approximation cr*( c1*t1 + ... + cn*tn ) |
259 |
36842 |
for (std::pair<const Node, Node>& aam : approxMsums[aa]) |
260 |
|
{ |
261 |
|
// Say aar is of the form t + c*ti, and aam is the monomial ci*ti |
262 |
|
// where ci != 0. We say aam: |
263 |
|
// (1) helps cancel if c != 0 and c>0 != ci>0 |
264 |
|
// (2) adds obligation if c>=0 and c+ci<0 |
265 |
42744 |
Node ti = aam.first; |
266 |
42744 |
Node ci = aam.second; |
267 |
21372 |
if (!cr.isNull()) |
268 |
|
{ |
269 |
31645 |
ci = ci.isNull() ? cr |
270 |
31645 |
: Rewriter::rewrite(nm->mkNode(MULT, ci, cr)); |
271 |
|
} |
272 |
21372 |
Trace("strings-ent-approx-debug") << ci << "*" << ti << " "; |
273 |
21372 |
int ciSgn = ci.isNull() ? 1 : ci.getConst<Rational>().sgn(); |
274 |
21372 |
it = msumAar.find(ti); |
275 |
21372 |
if (it != msumAar.end()) |
276 |
|
{ |
277 |
21228 |
Node c = it->second; |
278 |
10614 |
int cSgn = c.isNull() ? 1 : c.getConst<Rational>().sgn(); |
279 |
10614 |
if (cSgn == 0) |
280 |
|
{ |
281 |
2208 |
addsObligationCount += (ciSgn == -1 ? 1 : 0); |
282 |
|
} |
283 |
8406 |
else if (cSgn != ciSgn) |
284 |
|
{ |
285 |
7086 |
helpsCancelCount++; |
286 |
14172 |
Rational r1 = c.isNull() ? one : c.getConst<Rational>(); |
287 |
14172 |
Rational r2 = ci.isNull() ? one : ci.getConst<Rational>(); |
288 |
14172 |
Rational r12 = r1 + r2; |
289 |
7086 |
if (r12.sgn() == -1) |
290 |
|
{ |
291 |
4513 |
addsObligationCount++; |
292 |
|
} |
293 |
|
} |
294 |
|
} |
295 |
|
else |
296 |
|
{ |
297 |
10758 |
addsObligationCount += (ciSgn == -1 ? 1 : 0); |
298 |
|
} |
299 |
|
} |
300 |
30940 |
Trace("strings-ent-approx-debug") |
301 |
15470 |
<< "counts=" << helpsCancelCount << "," << addsObligationCount |
302 |
15470 |
<< " for " << aa << " into " << aar << std::endl; |
303 |
30940 |
int score = (addsObligationCount > 0 ? 0 : 2) |
304 |
15470 |
+ (helpsCancelCount > 0 ? 1 : 0); |
305 |
|
// if its the best, update v and vapprox |
306 |
15470 |
if (v.isNull() || score > maxScore) |
307 |
|
{ |
308 |
10209 |
v = nam.first; |
309 |
10209 |
vapprox = aa; |
310 |
10209 |
maxScore = score; |
311 |
|
} |
312 |
|
} |
313 |
7715 |
if (!v.isNull()) |
314 |
|
{ |
315 |
7715 |
break; |
316 |
|
} |
317 |
|
} |
318 |
15430 |
Trace("strings-ent-approx") |
319 |
7715 |
<< "- Decide " << v << " = " << vapprox << std::endl; |
320 |
|
// we incorporate v approximated by vapprox into the overall approximation |
321 |
|
// for ar |
322 |
7715 |
Assert(!v.isNull() && !vapprox.isNull()); |
323 |
7715 |
Assert(msum.find(v) != msum.end()); |
324 |
15430 |
Node mn = ArithMSum::mkCoeffTerm(msum[v], vapprox); |
325 |
7715 |
aar = nm->mkNode(PLUS, aar, mn); |
326 |
|
// update the msumAar map |
327 |
7715 |
aar = Rewriter::rewrite(aar); |
328 |
7715 |
msumAar.clear(); |
329 |
7715 |
if (!ArithMSum::getMonomialSum(aar, msumAar)) |
330 |
|
{ |
331 |
|
Assert(false); |
332 |
|
Trace("strings-ent-approx") |
333 |
|
<< "...failed to get monomial sum!" << std::endl; |
334 |
|
return false; |
335 |
|
} |
336 |
|
// we have processed the approximation for v |
337 |
7715 |
mApprox.erase(v); |
338 |
|
} |
339 |
7682 |
Trace("strings-ent-approx") << "-----------------" << std::endl; |
340 |
|
} |
341 |
19200 |
if (aar == ar) |
342 |
|
{ |
343 |
|
Trace("strings-ent-approx-debug") |
344 |
|
<< "...approximation had no effect" << std::endl; |
345 |
|
// this should never happen, but we avoid the infinite loop for sanity here |
346 |
|
Assert(false); |
347 |
|
return false; |
348 |
|
} |
349 |
|
// Check entailment on the approximation of ar. |
350 |
|
// Notice that this may trigger further reasoning by approximation. For |
351 |
|
// example, len( replace( x ++ y, substr( x, 0, n ), z ) ) may be |
352 |
|
// under-approximated as len( x ) + len( y ) - len( substr( x, 0, n ) ) on |
353 |
|
// this call, where in the recursive call we may over-approximate |
354 |
|
// len( substr( x, 0, n ) ) as len( x ). In this example, we can infer |
355 |
|
// that len( replace( x ++ y, substr( x, 0, n ), z ) ) >= len( y ) in two |
356 |
|
// steps. |
357 |
19200 |
if (check(aar)) |
358 |
|
{ |
359 |
3290 |
Trace("strings-ent-approx") |
360 |
1645 |
<< "*** StrArithApprox: showed " << ar |
361 |
1645 |
<< " >= 0 using under-approximation!" << std::endl; |
362 |
3290 |
Trace("strings-ent-approx") |
363 |
1645 |
<< "*** StrArithApprox: under-approximation was " << aar << std::endl; |
364 |
1645 |
return true; |
365 |
|
} |
366 |
17555 |
return false; |
367 |
|
} |
368 |
|
|
369 |
175798 |
void ArithEntail::getArithApproximations(Node a, |
370 |
|
std::vector<Node>& approx, |
371 |
|
bool isOverApprox) |
372 |
|
{ |
373 |
175798 |
NodeManager* nm = NodeManager::currentNM(); |
374 |
|
// We do not handle PLUS here since this leads to exponential behavior. |
375 |
|
// Instead, this is managed, e.g. during checkApprox, where |
376 |
|
// PLUS terms are expanded "on-demand" during the reasoning. |
377 |
351596 |
Trace("strings-ent-approx-debug") |
378 |
175798 |
<< "Get arith approximations " << a << std::endl; |
379 |
175798 |
Kind ak = a.getKind(); |
380 |
175798 |
if (ak == MULT) |
381 |
|
{ |
382 |
1192 |
Node c; |
383 |
1192 |
Node v; |
384 |
596 |
if (ArithMSum::getMonomial(a, c, v)) |
385 |
|
{ |
386 |
596 |
bool isNeg = c.getConst<Rational>().sgn() == -1; |
387 |
596 |
getArithApproximations(v, approx, isNeg ? !isOverApprox : isOverApprox); |
388 |
639 |
for (unsigned i = 0, size = approx.size(); i < size; i++) |
389 |
|
{ |
390 |
43 |
approx[i] = nm->mkNode(MULT, c, approx[i]); |
391 |
|
} |
392 |
|
} |
393 |
|
} |
394 |
175202 |
else if (ak == STRING_LENGTH) |
395 |
|
{ |
396 |
137572 |
Kind aak = a[0].getKind(); |
397 |
137572 |
if (aak == STRING_SUBSTR) |
398 |
|
{ |
399 |
|
// over,under-approximations for len( substr( x, n, m ) ) |
400 |
55518 |
Node lenx = nm->mkNode(STRING_LENGTH, a[0][0]); |
401 |
27759 |
if (isOverApprox) |
402 |
|
{ |
403 |
|
// m >= 0 implies |
404 |
|
// m >= len( substr( x, n, m ) ) |
405 |
18009 |
if (check(a[0][2])) |
406 |
|
{ |
407 |
10609 |
approx.push_back(a[0][2]); |
408 |
|
} |
409 |
18009 |
if (check(lenx, a[0][1])) |
410 |
|
{ |
411 |
|
// n <= len( x ) implies |
412 |
|
// len( x ) - n >= len( substr( x, n, m ) ) |
413 |
6287 |
approx.push_back(nm->mkNode(MINUS, lenx, a[0][1])); |
414 |
|
} |
415 |
|
else |
416 |
|
{ |
417 |
|
// len( x ) >= len( substr( x, n, m ) ) |
418 |
11722 |
approx.push_back(lenx); |
419 |
|
} |
420 |
|
} |
421 |
|
else |
422 |
|
{ |
423 |
|
// 0 <= n and n+m <= len( x ) implies |
424 |
|
// m <= len( substr( x, n, m ) ) |
425 |
19500 |
Node npm = nm->mkNode(PLUS, a[0][1], a[0][2]); |
426 |
9750 |
if (check(a[0][1]) && check(lenx, npm)) |
427 |
|
{ |
428 |
1418 |
approx.push_back(a[0][2]); |
429 |
|
} |
430 |
|
// 0 <= n and n+m >= len( x ) implies |
431 |
|
// len(x)-n <= len( substr( x, n, m ) ) |
432 |
9750 |
if (check(a[0][1]) && check(npm, lenx)) |
433 |
|
{ |
434 |
1056 |
approx.push_back(nm->mkNode(MINUS, lenx, a[0][1])); |
435 |
|
} |
436 |
|
} |
437 |
|
} |
438 |
109813 |
else if (aak == STRING_REPLACE) |
439 |
|
{ |
440 |
|
// over,under-approximations for len( replace( x, y, z ) ) |
441 |
|
// notice this is either len( x ) or ( len( x ) + len( z ) - len( y ) ) |
442 |
7706 |
Node lenx = nm->mkNode(STRING_LENGTH, a[0][0]); |
443 |
7706 |
Node leny = nm->mkNode(STRING_LENGTH, a[0][1]); |
444 |
7706 |
Node lenz = nm->mkNode(STRING_LENGTH, a[0][2]); |
445 |
3853 |
if (isOverApprox) |
446 |
|
{ |
447 |
2162 |
if (check(leny, lenz)) |
448 |
|
{ |
449 |
|
// len( y ) >= len( z ) implies |
450 |
|
// len( x ) >= len( replace( x, y, z ) ) |
451 |
586 |
approx.push_back(lenx); |
452 |
|
} |
453 |
|
else |
454 |
|
{ |
455 |
|
// len( x ) + len( z ) >= len( replace( x, y, z ) ) |
456 |
1576 |
approx.push_back(nm->mkNode(PLUS, lenx, lenz)); |
457 |
|
} |
458 |
|
} |
459 |
|
else |
460 |
|
{ |
461 |
1691 |
if (check(lenz, leny) || check(lenz, lenx)) |
462 |
|
{ |
463 |
|
// len( y ) <= len( z ) or len( x ) <= len( z ) implies |
464 |
|
// len( x ) <= len( replace( x, y, z ) ) |
465 |
911 |
approx.push_back(lenx); |
466 |
|
} |
467 |
|
else |
468 |
|
{ |
469 |
|
// len( x ) - len( y ) <= len( replace( x, y, z ) ) |
470 |
780 |
approx.push_back(nm->mkNode(MINUS, lenx, leny)); |
471 |
|
} |
472 |
|
} |
473 |
|
} |
474 |
105960 |
else if (aak == STRING_ITOS) |
475 |
|
{ |
476 |
|
// over,under-approximations for len( int.to.str( x ) ) |
477 |
254 |
if (isOverApprox) |
478 |
|
{ |
479 |
136 |
if (check(a[0][0], false)) |
480 |
|
{ |
481 |
60 |
if (check(a[0][0], true)) |
482 |
|
{ |
483 |
|
// x > 0 implies |
484 |
|
// x >= len( int.to.str( x ) ) |
485 |
7 |
approx.push_back(a[0][0]); |
486 |
|
} |
487 |
|
else |
488 |
|
{ |
489 |
|
// x >= 0 implies |
490 |
|
// x+1 >= len( int.to.str( x ) ) |
491 |
53 |
approx.push_back( |
492 |
106 |
nm->mkNode(PLUS, nm->mkConst(Rational(1)), a[0][0])); |
493 |
|
} |
494 |
|
} |
495 |
|
} |
496 |
|
else |
497 |
|
{ |
498 |
118 |
if (check(a[0][0])) |
499 |
|
{ |
500 |
|
// x >= 0 implies |
501 |
|
// len( int.to.str( x ) ) >= 1 |
502 |
38 |
approx.push_back(nm->mkConst(Rational(1))); |
503 |
|
} |
504 |
|
// other crazy things are possible here, e.g. |
505 |
|
// len( int.to.str( len( y ) + 10 ) ) >= 2 |
506 |
|
} |
507 |
|
} |
508 |
|
} |
509 |
37630 |
else if (ak == STRING_INDEXOF) |
510 |
|
{ |
511 |
|
// over,under-approximations for indexof( x, y, n ) |
512 |
6747 |
if (isOverApprox) |
513 |
|
{ |
514 |
7192 |
Node lenx = nm->mkNode(STRING_LENGTH, a[0]); |
515 |
7192 |
Node leny = nm->mkNode(STRING_LENGTH, a[1]); |
516 |
3596 |
if (check(lenx, leny)) |
517 |
|
{ |
518 |
|
// len( x ) >= len( y ) implies |
519 |
|
// len( x ) - len( y ) >= indexof( x, y, n ) |
520 |
22 |
approx.push_back(nm->mkNode(MINUS, lenx, leny)); |
521 |
|
} |
522 |
|
else |
523 |
|
{ |
524 |
|
// len( x ) >= indexof( x, y, n ) |
525 |
3574 |
approx.push_back(lenx); |
526 |
|
} |
527 |
|
} |
528 |
|
else |
529 |
|
{ |
530 |
|
// TODO?: |
531 |
|
// contains( substr( x, n, len( x ) ), y ) implies |
532 |
|
// n <= indexof( x, y, n ) |
533 |
|
// ...hard to test, runs risk of non-termination |
534 |
|
|
535 |
|
// -1 <= indexof( x, y, n ) |
536 |
3151 |
approx.push_back(nm->mkConst(Rational(-1))); |
537 |
|
} |
538 |
|
} |
539 |
30883 |
else if (ak == STRING_STOI) |
540 |
|
{ |
541 |
|
// over,under-approximations for str.to.int( x ) |
542 |
|
if (isOverApprox) |
543 |
|
{ |
544 |
|
// TODO?: |
545 |
|
// y >= 0 implies |
546 |
|
// y >= str.to.int( int.to.str( y ) ) |
547 |
|
} |
548 |
|
else |
549 |
|
{ |
550 |
|
// -1 <= str.to.int( x ) |
551 |
|
approx.push_back(nm->mkConst(Rational(-1))); |
552 |
|
} |
553 |
|
} |
554 |
175798 |
Trace("strings-ent-approx-debug") << "Return " << approx.size() << std::endl; |
555 |
175798 |
} |
556 |
|
|
557 |
78544 |
bool ArithEntail::checkWithEqAssumption(Node assumption, Node a, bool strict) |
558 |
|
{ |
559 |
78544 |
Assert(assumption.getKind() == kind::EQUAL); |
560 |
78544 |
Assert(Rewriter::rewrite(assumption) == assumption); |
561 |
157088 |
Trace("strings-entail") << "checkWithEqAssumption: " << assumption << " " << a |
562 |
78544 |
<< ", strict=" << strict << std::endl; |
563 |
|
|
564 |
|
// Find candidates variables to compute substitutions for |
565 |
157088 |
std::unordered_set<Node> candVars; |
566 |
157088 |
std::vector<Node> toVisit = {assumption}; |
567 |
982880 |
while (!toVisit.empty()) |
568 |
|
{ |
569 |
904336 |
Node curr = toVisit.back(); |
570 |
452168 |
toVisit.pop_back(); |
571 |
|
|
572 |
1289950 |
if (curr.getKind() == kind::PLUS || curr.getKind() == kind::MULT |
573 |
811763 |
|| curr.getKind() == kind::MINUS || curr.getKind() == kind::EQUAL) |
574 |
|
{ |
575 |
544741 |
for (const auto& currChild : curr) |
576 |
|
{ |
577 |
373624 |
toVisit.push_back(currChild); |
578 |
|
} |
579 |
|
} |
580 |
281051 |
else if (curr.isVar() && Theory::theoryOf(curr) == THEORY_ARITH) |
581 |
|
{ |
582 |
13084 |
candVars.insert(curr); |
583 |
|
} |
584 |
267967 |
else if (curr.getKind() == kind::STRING_LENGTH) |
585 |
|
{ |
586 |
163396 |
candVars.insert(curr); |
587 |
|
} |
588 |
|
} |
589 |
|
|
590 |
|
// Check if any of the candidate variables are in n |
591 |
157088 |
Node v; |
592 |
78544 |
Assert(toVisit.empty()); |
593 |
78544 |
toVisit.push_back(a); |
594 |
880442 |
while (!toVisit.empty()) |
595 |
|
{ |
596 |
834832 |
Node curr = toVisit.back(); |
597 |
433883 |
toVisit.pop_back(); |
598 |
|
|
599 |
884005 |
for (const auto& currChild : curr) |
600 |
|
{ |
601 |
450122 |
toVisit.push_back(currChild); |
602 |
|
} |
603 |
|
|
604 |
433883 |
if (candVars.find(curr) != candVars.end()) |
605 |
|
{ |
606 |
32934 |
v = curr; |
607 |
32934 |
break; |
608 |
|
} |
609 |
|
} |
610 |
|
|
611 |
78544 |
if (v.isNull()) |
612 |
|
{ |
613 |
|
// No suitable candidate found |
614 |
45610 |
return false; |
615 |
|
} |
616 |
|
|
617 |
65868 |
Node solution = ArithMSum::solveEqualityFor(assumption, v); |
618 |
32934 |
if (solution.isNull()) |
619 |
|
{ |
620 |
|
// Could not solve for v |
621 |
96 |
return false; |
622 |
|
} |
623 |
65676 |
Trace("strings-entail") << "checkWithEqAssumption: subs " << v << " -> " |
624 |
32838 |
<< solution << std::endl; |
625 |
|
|
626 |
|
// use capture avoiding substitution |
627 |
32838 |
a = expr::substituteCaptureAvoiding(a, v, solution); |
628 |
32838 |
return check(a, strict); |
629 |
|
} |
630 |
|
|
631 |
113515 |
bool ArithEntail::checkWithAssumption(Node assumption, |
632 |
|
Node a, |
633 |
|
Node b, |
634 |
|
bool strict) |
635 |
|
{ |
636 |
113515 |
Assert(Rewriter::rewrite(assumption) == assumption); |
637 |
|
|
638 |
113515 |
NodeManager* nm = NodeManager::currentNM(); |
639 |
|
|
640 |
113515 |
if (!assumption.isConst() && assumption.getKind() != kind::EQUAL) |
641 |
|
{ |
642 |
|
// We rewrite inequality assumptions from x <= y to x + (str.len s) = y |
643 |
|
// where s is some fresh string variable. We use (str.len s) because |
644 |
|
// (str.len s) must be non-negative for the equation to hold. |
645 |
157072 |
Node x, y; |
646 |
78536 |
if (assumption.getKind() == kind::GEQ) |
647 |
|
{ |
648 |
65736 |
x = assumption[0]; |
649 |
65736 |
y = assumption[1]; |
650 |
|
} |
651 |
|
else |
652 |
|
{ |
653 |
|
// (not (>= s t)) --> (>= (t - 1) s) |
654 |
12800 |
Assert(assumption.getKind() == kind::NOT |
655 |
|
&& assumption[0].getKind() == kind::GEQ); |
656 |
12800 |
x = nm->mkNode(kind::MINUS, assumption[0][1], nm->mkConst(Rational(1))); |
657 |
12800 |
y = assumption[0][0]; |
658 |
|
} |
659 |
|
|
660 |
157072 |
Node s = nm->mkBoundVar("slackVal", nm->stringType()); |
661 |
157072 |
Node slen = nm->mkNode(kind::STRING_LENGTH, s); |
662 |
78536 |
assumption = Rewriter::rewrite( |
663 |
157072 |
nm->mkNode(kind::EQUAL, x, nm->mkNode(kind::PLUS, y, slen))); |
664 |
|
} |
665 |
|
|
666 |
227030 |
Node diff = nm->mkNode(kind::MINUS, a, b); |
667 |
113515 |
bool res = false; |
668 |
113515 |
if (assumption.isConst()) |
669 |
|
{ |
670 |
34971 |
bool assumptionBool = assumption.getConst<bool>(); |
671 |
34971 |
if (assumptionBool) |
672 |
|
{ |
673 |
34971 |
res = check(diff, strict); |
674 |
|
} |
675 |
|
else |
676 |
|
{ |
677 |
|
res = true; |
678 |
|
} |
679 |
|
} |
680 |
|
else |
681 |
|
{ |
682 |
78544 |
res = checkWithEqAssumption(assumption, diff, strict); |
683 |
|
} |
684 |
227030 |
return res; |
685 |
|
} |
686 |
|
|
687 |
|
bool ArithEntail::checkWithAssumptions(std::vector<Node> assumptions, |
688 |
|
Node a, |
689 |
|
Node b, |
690 |
|
bool strict) |
691 |
|
{ |
692 |
|
// TODO: We currently try to show the entailment with each assumption |
693 |
|
// independently. In the future, we should make better use of multiple |
694 |
|
// assumptions. |
695 |
|
bool res = false; |
696 |
|
for (const auto& assumption : assumptions) |
697 |
|
{ |
698 |
|
Assert(Rewriter::rewrite(assumption) == assumption); |
699 |
|
|
700 |
|
if (checkWithAssumption(assumption, a, b, strict)) |
701 |
|
{ |
702 |
|
res = true; |
703 |
|
break; |
704 |
|
} |
705 |
|
} |
706 |
|
return res; |
707 |
|
} |
708 |
|
|
709 |
31311 |
Node ArithEntail::getConstantBound(Node a, bool isLower) |
710 |
|
{ |
711 |
31311 |
Assert(Rewriter::rewrite(a) == a); |
712 |
31311 |
Node ret; |
713 |
31311 |
if (a.isConst()) |
714 |
|
{ |
715 |
12328 |
ret = a; |
716 |
|
} |
717 |
18983 |
else if (a.getKind() == kind::STRING_LENGTH) |
718 |
|
{ |
719 |
3704 |
if (isLower) |
720 |
|
{ |
721 |
3704 |
ret = NodeManager::currentNM()->mkConst(Rational(0)); |
722 |
|
} |
723 |
|
} |
724 |
15279 |
else if (a.getKind() == kind::PLUS || a.getKind() == kind::MULT) |
725 |
|
{ |
726 |
22154 |
std::vector<Node> children; |
727 |
11077 |
bool success = true; |
728 |
20421 |
for (unsigned i = 0; i < a.getNumChildren(); i++) |
729 |
|
{ |
730 |
28168 |
Node ac = getConstantBound(a[i], isLower); |
731 |
18824 |
if (ac.isNull()) |
732 |
|
{ |
733 |
5086 |
ret = ac; |
734 |
5086 |
success = false; |
735 |
5086 |
break; |
736 |
|
} |
737 |
|
else |
738 |
|
{ |
739 |
13738 |
if (ac.getConst<Rational>().sgn() == 0) |
740 |
|
{ |
741 |
3389 |
if (a.getKind() == kind::MULT) |
742 |
|
{ |
743 |
34 |
ret = ac; |
744 |
34 |
success = false; |
745 |
34 |
break; |
746 |
|
} |
747 |
|
} |
748 |
|
else |
749 |
|
{ |
750 |
10349 |
if (a.getKind() == kind::MULT) |
751 |
|
{ |
752 |
4394 |
if ((ac.getConst<Rational>().sgn() > 0) != isLower) |
753 |
|
{ |
754 |
4360 |
ret = Node::null(); |
755 |
4360 |
success = false; |
756 |
4360 |
break; |
757 |
|
} |
758 |
|
} |
759 |
5989 |
children.push_back(ac); |
760 |
|
} |
761 |
|
} |
762 |
|
} |
763 |
11077 |
if (success) |
764 |
|
{ |
765 |
1597 |
if (children.empty()) |
766 |
|
{ |
767 |
362 |
ret = NodeManager::currentNM()->mkConst(Rational(0)); |
768 |
|
} |
769 |
1235 |
else if (children.size() == 1) |
770 |
|
{ |
771 |
1235 |
ret = children[0]; |
772 |
|
} |
773 |
|
else |
774 |
|
{ |
775 |
|
ret = NodeManager::currentNM()->mkNode(a.getKind(), children); |
776 |
|
ret = Rewriter::rewrite(ret); |
777 |
|
} |
778 |
|
} |
779 |
|
} |
780 |
62622 |
Trace("strings-rewrite-cbound") |
781 |
31311 |
<< "Constant " << (isLower ? "lower" : "upper") << " bound for " << a |
782 |
31311 |
<< " is " << ret << std::endl; |
783 |
31311 |
Assert(ret.isNull() || ret.isConst()); |
784 |
|
// entailment check should be at least as powerful as computing a lower bound |
785 |
31311 |
Assert(!isLower || ret.isNull() || ret.getConst<Rational>().sgn() < 0 |
786 |
|
|| check(a, false)); |
787 |
31311 |
Assert(!isLower || ret.isNull() || ret.getConst<Rational>().sgn() <= 0 |
788 |
|
|| check(a, true)); |
789 |
31311 |
return ret; |
790 |
|
} |
791 |
|
|
792 |
249984 |
bool ArithEntail::checkInternal(Node a) |
793 |
|
{ |
794 |
249984 |
Assert(Rewriter::rewrite(a) == a); |
795 |
|
// check whether a >= 0 |
796 |
249984 |
if (a.isConst()) |
797 |
|
{ |
798 |
113099 |
return a.getConst<Rational>().sgn() >= 0; |
799 |
|
} |
800 |
136885 |
else if (a.getKind() == kind::STRING_LENGTH) |
801 |
|
{ |
802 |
|
// str.len( t ) >= 0 |
803 |
10322 |
return true; |
804 |
|
} |
805 |
126563 |
else if (a.getKind() == kind::PLUS || a.getKind() == kind::MULT) |
806 |
|
{ |
807 |
149935 |
for (unsigned i = 0; i < a.getNumChildren(); i++) |
808 |
|
{ |
809 |
147607 |
if (!checkInternal(a[i])) |
810 |
|
{ |
811 |
120021 |
return false; |
812 |
|
} |
813 |
|
} |
814 |
|
// t1 >= 0 ^ ... ^ tn >= 0 => t1 op ... op tn >= 0 |
815 |
2328 |
return true; |
816 |
|
} |
817 |
|
|
818 |
4214 |
return false; |
819 |
|
} |
820 |
|
|
821 |
972 |
bool ArithEntail::inferZerosInSumGeq(Node x, |
822 |
|
std::vector<Node>& ys, |
823 |
|
std::vector<Node>& zeroYs) |
824 |
|
{ |
825 |
972 |
Assert(zeroYs.empty()); |
826 |
|
|
827 |
972 |
NodeManager* nm = NodeManager::currentNM(); |
828 |
|
|
829 |
|
// Check if we can show that y1 + ... + yn >= x |
830 |
1944 |
Node sum = (ys.size() > 1) ? nm->mkNode(PLUS, ys) : ys[0]; |
831 |
972 |
if (!check(sum, x)) |
832 |
|
{ |
833 |
882 |
return false; |
834 |
|
} |
835 |
|
|
836 |
|
// Try to remove yi one-by-one and check if we can still show: |
837 |
|
// |
838 |
|
// y1 + ... + yi-1 + yi+1 + ... + yn >= x |
839 |
|
// |
840 |
|
// If that's the case, we know that yi can be zero and the inequality still |
841 |
|
// holds. |
842 |
90 |
size_t i = 0; |
843 |
506 |
while (i < ys.size()) |
844 |
|
{ |
845 |
416 |
Node yi = ys[i]; |
846 |
208 |
std::vector<Node>::iterator pos = ys.erase(ys.begin() + i); |
847 |
208 |
if (ys.size() > 1) |
848 |
|
{ |
849 |
64 |
sum = nm->mkNode(PLUS, ys); |
850 |
|
} |
851 |
|
else |
852 |
|
{ |
853 |
144 |
sum = ys.size() == 1 ? ys[0] : nm->mkConst(Rational(0)); |
854 |
|
} |
855 |
|
|
856 |
208 |
if (check(sum, x)) |
857 |
|
{ |
858 |
44 |
zeroYs.push_back(yi); |
859 |
|
} |
860 |
|
else |
861 |
|
{ |
862 |
164 |
ys.insert(pos, yi); |
863 |
164 |
i++; |
864 |
|
} |
865 |
|
} |
866 |
90 |
return true; |
867 |
|
} |
868 |
|
|
869 |
|
} // namespace strings |
870 |
|
} // namespace theory |
871 |
29340 |
} // namespace cvc5 |