GCC Code Coverage Report
Directory: . Exec Total Coverage
File: src/theory/strings/arith_entail.cpp Lines: 344 384 89.6 %
Date: 2021-09-29 Branches: 879 1912 46.0 %

Line Exec Source
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
6541
ArithEntail::ArithEntail(Rewriter* r) : d_rr(r) {}
34
35
898
bool ArithEntail::checkEq(Node a, Node b)
36
{
37
898
  if (a == b)
38
  {
39
    return true;
40
  }
41
1796
  Node ar = d_rr->rewrite(a);
42
1796
  Node br = d_rr->rewrite(b);
43
898
  return ar == br;
44
}
45
46
337582
bool ArithEntail::check(Node a, Node b, bool strict)
47
{
48
337582
  if (a == b)
49
  {
50
15123
    return !strict;
51
  }
52
644918
  Node diff = NodeManager::currentNM()->mkNode(kind::MINUS, a, b);
53
322459
  return check(diff, strict);
54
}
55
56
struct StrCheckEntailArithTag
57
{
58
};
59
struct StrCheckEntailArithComputedTag
60
{
61
};
62
/** Attribute true for expressions for which check returned true */
63
typedef expr::Attribute<StrCheckEntailArithTag, bool> StrCheckEntailArithAttr;
64
typedef expr::Attribute<StrCheckEntailArithComputedTag, bool>
65
    StrCheckEntailArithComputedAttr;
66
67
592911
bool ArithEntail::check(Node a, bool strict)
68
{
69
592911
  if (a.isConst())
70
  {
71
69894
    return a.getConst<Rational>().sgn() >= (strict ? 1 : 0);
72
  }
73
74
  Node ar = strict ? NodeManager::currentNM()->mkNode(
75
686525
                kind::MINUS, a, NodeManager::currentNM()->mkConst(Rational(1)))
76
1209542
                   : a;
77
523017
  ar = d_rr->rewrite(ar);
78
79
523017
  if (ar.getAttribute(StrCheckEntailArithComputedAttr()))
80
  {
81
448314
    return ar.getAttribute(StrCheckEntailArithAttr());
82
  }
83
84
74703
  bool ret = checkInternal(ar);
85
74703
  if (!ret)
86
  {
87
    // try with approximations
88
70028
    ret = checkApprox(ar);
89
  }
90
  // cache the result
91
74703
  ar.setAttribute(StrCheckEntailArithAttr(), ret);
92
74703
  ar.setAttribute(StrCheckEntailArithComputedAttr(), true);
93
74703
  return ret;
94
}
95
96
70028
bool ArithEntail::checkApprox(Node ar)
97
{
98
70028
  Assert(d_rr->rewrite(ar) == ar);
99
70028
  NodeManager* nm = NodeManager::currentNM();
100
140056
  std::map<Node, Node> msum;
101
140056
  Trace("strings-ent-approx-debug")
102
70028
      << "Setup arithmetic approximations for " << ar << std::endl;
103
70028
  if (!ArithMSum::getMonomialSum(ar, msum))
104
  {
105
    Trace("strings-ent-approx-debug")
106
        << "...failed to get monomial sum!" << std::endl;
107
    return false;
108
  }
109
  // for each monomial v*c, mApprox[v] a list of
110
  // possibilities for how the term can be soundly approximated, that is,
111
  // if mApprox[v] contains av, then v*c > av*c. Notice that if c
112
  // is positive, then v > av, otherwise if c is negative, then v < av.
113
  // In other words, av is an under-approximation if c is positive, and an
114
  // over-approximation if c is negative.
115
70028
  bool changed = false;
116
140056
  std::map<Node, std::vector<Node> > mApprox;
117
  // map from approximations to their monomial sums
118
140056
  std::map<Node, std::map<Node, Node> > approxMsums;
119
  // aarSum stores each monomial that does not have multiple approximations
120
140056
  std::vector<Node> aarSum;
121
226820
  for (std::pair<const Node, Node>& m : msum)
122
  {
123
313584
    Node v = m.first;
124
313584
    Node c = m.second;
125
313584
    Trace("strings-ent-approx-debug")
126
156792
        << "Get approximations " << v << "..." << std::endl;
127
156792
    if (v.isNull())
128
    {
129
107796
      Node mn = c.isNull() ? nm->mkConst(Rational(1)) : c;
130
53898
      aarSum.push_back(mn);
131
    }
132
    else
133
    {
134
      // c.isNull() means c = 1
135
102894
      bool isOverApprox = !c.isNull() && c.getConst<Rational>().sgn() == -1;
136
102894
      std::vector<Node>& approx = mApprox[v];
137
205788
      std::unordered_set<Node> visited;
138
205788
      std::vector<Node> toProcess;
139
102894
      toProcess.push_back(v);
140
26856
      do
141
      {
142
259500
        Node curr = toProcess.back();
143
129750
        Trace("strings-ent-approx-debug") << "  process " << curr << std::endl;
144
129750
        curr = d_rr->rewrite(curr);
145
129750
        toProcess.pop_back();
146
129750
        if (visited.find(curr) == visited.end())
147
        {
148
128490
          visited.insert(curr);
149
256980
          std::vector<Node> currApprox;
150
128490
          getArithApproximations(curr, currApprox, isOverApprox);
151
128490
          if (currApprox.empty())
152
          {
153
220230
            Trace("strings-ent-approx-debug")
154
110115
                << "...approximation: " << curr << std::endl;
155
            // no approximations, thus curr is a possibility
156
110115
            approx.push_back(curr);
157
          }
158
          else
159
          {
160
18375
            toProcess.insert(
161
36750
                toProcess.end(), currApprox.begin(), currApprox.end());
162
          }
163
        }
164
129750
      } while (!toProcess.empty());
165
102894
      Assert(!approx.empty());
166
      // if we have only one approximation, move it to final
167
102894
      if (approx.size() == 1)
168
      {
169
95778
        changed = v != approx[0];
170
191556
        Node mn = ArithMSum::mkCoeffTerm(c, approx[0]);
171
95778
        aarSum.push_back(mn);
172
95778
        mApprox.erase(v);
173
      }
174
      else
175
      {
176
        // compute monomial sum form for each approximation, used below
177
21453
        for (const Node& aa : approx)
178
        {
179
14337
          if (approxMsums.find(aa) == approxMsums.end())
180
          {
181
            CVC5_UNUSED bool ret =
182
14062
                ArithMSum::getMonomialSum(aa, approxMsums[aa]);
183
14062
            Assert(ret);
184
          }
185
        }
186
7116
        changed = true;
187
      }
188
    }
189
  }
190
70028
  if (!changed)
191
  {
192
    // approximations had no effect, return
193
58056
    Trace("strings-ent-approx-debug") << "...no approximations" << std::endl;
194
58056
    return false;
195
  }
196
  // get the current "fixed" sum for the abstraction of ar
197
11972
  Node aar = aarSum.empty()
198
13147
                 ? nm->mkConst(Rational(0))
199
25119
                 : (aarSum.size() == 1 ? aarSum[0] : nm->mkNode(PLUS, aarSum));
200
11972
  aar = d_rr->rewrite(aar);
201
23944
  Trace("strings-ent-approx-debug")
202
23944
      << "...processed fixed sum " << aar << " with " << mApprox.size()
203
11972
      << " approximated monomials." << std::endl;
204
  // if we have a choice of how to approximate
205
11972
  if (!mApprox.empty())
206
  {
207
    // convert aar back to monomial sum
208
11372
    std::map<Node, Node> msumAar;
209
5686
    if (!ArithMSum::getMonomialSum(aar, msumAar))
210
    {
211
      return false;
212
    }
213
5686
    if (Trace.isOn("strings-ent-approx"))
214
    {
215
      Trace("strings-ent-approx")
216
          << "---- Check arithmetic entailment by under-approximation " << ar
217
          << " >= 0" << std::endl;
218
      Trace("strings-ent-approx") << "FIXED:" << std::endl;
219
      ArithMSum::debugPrintMonomialSum(msumAar, "strings-ent-approx");
220
      Trace("strings-ent-approx") << "APPROX:" << std::endl;
221
      for (std::pair<const Node, std::vector<Node> >& a : mApprox)
222
      {
223
        Node c = msum[a.first];
224
        Trace("strings-ent-approx") << "  ";
225
        if (!c.isNull())
226
        {
227
          Trace("strings-ent-approx") << c << " * ";
228
        }
229
        Trace("strings-ent-approx")
230
            << a.second << " ...from " << a.first << std::endl;
231
      }
232
      Trace("strings-ent-approx") << std::endl;
233
    }
234
11372
    Rational one(1);
235
    // incorporate monomials one at a time that have a choice of approximations
236
17128
    while (!mApprox.empty())
237
    {
238
11442
      Node v;
239
11442
      Node vapprox;
240
5721
      int maxScore = -1;
241
      // Look at each approximation, take the one with the best score.
242
      // Notice that we are in the process of trying to prove
243
      // ( c1*t1 + .. + cn*tn ) + ( approx_1 | ... | approx_m ) >= 0,
244
      // where c1*t1 + .. + cn*tn is the "fixed" component of our sum (aar)
245
      // and approx_1 ... approx_m are possible approximations. The
246
      // intution here is that we want coefficients c1...cn to be positive.
247
      // This is because arithmetic string terms t1...tn (which may be
248
      // applications of len, indexof, str.to.int) are never entailed to be
249
      // negative. Hence, we add the approx_i that contributes the "most"
250
      // towards making all constants c1...cn positive and cancelling negative
251
      // monomials in approx_i itself.
252
5721
      for (std::pair<const Node, std::vector<Node> >& nam : mApprox)
253
      {
254
5721
        Node cr = msum[nam.first];
255
17266
        for (const Node& aa : nam.second)
256
        {
257
11545
          unsigned helpsCancelCount = 0;
258
11545
          unsigned addsObligationCount = 0;
259
11545
          std::map<Node, Node>::iterator it;
260
          // we are processing an approximation cr*( c1*t1 + ... + cn*tn )
261
25667
          for (std::pair<const Node, Node>& aam : approxMsums[aa])
262
          {
263
            // Say aar is of the form t + c*ti, and aam is the monomial ci*ti
264
            // where ci != 0. We say aam:
265
            // (1) helps cancel if c != 0 and c>0 != ci>0
266
            // (2) adds obligation if c>=0 and c+ci<0
267
28244
            Node ti = aam.first;
268
28244
            Node ci = aam.second;
269
14122
            if (!cr.isNull())
270
            {
271
14122
              ci = ci.isNull() ? cr : d_rr->rewrite(nm->mkNode(MULT, ci, cr));
272
            }
273
14122
            Trace("strings-ent-approx-debug") << ci << "*" << ti << " ";
274
14122
            int ciSgn = ci.isNull() ? 1 : ci.getConst<Rational>().sgn();
275
14122
            it = msumAar.find(ti);
276
14122
            if (it != msumAar.end())
277
            {
278
16092
              Node c = it->second;
279
8046
              int cSgn = c.isNull() ? 1 : c.getConst<Rational>().sgn();
280
8046
              if (cSgn == 0)
281
              {
282
1503
                addsObligationCount += (ciSgn == -1 ? 1 : 0);
283
              }
284
6543
              else if (cSgn != ciSgn)
285
              {
286
5657
                helpsCancelCount++;
287
11314
                Rational r1 = c.isNull() ? one : c.getConst<Rational>();
288
11314
                Rational r2 = ci.isNull() ? one : ci.getConst<Rational>();
289
11314
                Rational r12 = r1 + r2;
290
5657
                if (r12.sgn() == -1)
291
                {
292
4070
                  addsObligationCount++;
293
                }
294
              }
295
            }
296
            else
297
            {
298
6076
              addsObligationCount += (ciSgn == -1 ? 1 : 0);
299
            }
300
          }
301
23090
          Trace("strings-ent-approx-debug")
302
11545
              << "counts=" << helpsCancelCount << "," << addsObligationCount
303
11545
              << " for " << aa << " into " << aar << std::endl;
304
23090
          int score = (addsObligationCount > 0 ? 0 : 2)
305
11545
                      + (helpsCancelCount > 0 ? 1 : 0);
306
          // if its the best, update v and vapprox
307
11545
          if (v.isNull() || score > maxScore)
308
          {
309
7752
            v = nam.first;
310
7752
            vapprox = aa;
311
7752
            maxScore = score;
312
          }
313
        }
314
5721
        if (!v.isNull())
315
        {
316
5721
          break;
317
        }
318
      }
319
11442
      Trace("strings-ent-approx")
320
5721
          << "- Decide " << v << " = " << vapprox << std::endl;
321
      // we incorporate v approximated by vapprox into the overall approximation
322
      // for ar
323
5721
      Assert(!v.isNull() && !vapprox.isNull());
324
5721
      Assert(msum.find(v) != msum.end());
325
11442
      Node mn = ArithMSum::mkCoeffTerm(msum[v], vapprox);
326
5721
      aar = nm->mkNode(PLUS, aar, mn);
327
      // update the msumAar map
328
5721
      aar = d_rr->rewrite(aar);
329
5721
      msumAar.clear();
330
5721
      if (!ArithMSum::getMonomialSum(aar, msumAar))
331
      {
332
        Assert(false);
333
        Trace("strings-ent-approx")
334
            << "...failed to get monomial sum!" << std::endl;
335
        return false;
336
      }
337
      // we have processed the approximation for v
338
5721
      mApprox.erase(v);
339
    }
340
5686
    Trace("strings-ent-approx") << "-----------------" << std::endl;
341
  }
342
11972
  if (aar == ar)
343
  {
344
    Trace("strings-ent-approx-debug")
345
        << "...approximation had no effect" << std::endl;
346
    // this should never happen, but we avoid the infinite loop for sanity here
347
    Assert(false);
348
    return false;
349
  }
350
  // Check entailment on the approximation of ar.
351
  // Notice that this may trigger further reasoning by approximation. For
352
  // example, len( replace( x ++ y, substr( x, 0, n ), z ) ) may be
353
  // under-approximated as len( x ) + len( y ) - len( substr( x, 0, n ) ) on
354
  // this call, where in the recursive call we may over-approximate
355
  // len( substr( x, 0, n ) ) as len( x ). In this example, we can infer
356
  // that len( replace( x ++ y, substr( x, 0, n ), z ) ) >= len( y ) in two
357
  // steps.
358
11972
  if (check(aar))
359
  {
360
2360
    Trace("strings-ent-approx")
361
1180
        << "*** StrArithApprox: showed " << ar
362
1180
        << " >= 0 using under-approximation!" << std::endl;
363
2360
    Trace("strings-ent-approx")
364
1180
        << "*** StrArithApprox: under-approximation was " << aar << std::endl;
365
1180
    return true;
366
  }
367
10792
  return false;
368
}
369
370
128771
void ArithEntail::getArithApproximations(Node a,
371
                                         std::vector<Node>& approx,
372
                                         bool isOverApprox)
373
{
374
128771
  NodeManager* nm = NodeManager::currentNM();
375
  // We do not handle PLUS here since this leads to exponential behavior.
376
  // Instead, this is managed, e.g. during checkApprox, where
377
  // PLUS terms are expanded "on-demand" during the reasoning.
378
257542
  Trace("strings-ent-approx-debug")
379
128771
      << "Get arith approximations " << a << std::endl;
380
128771
  Kind ak = a.getKind();
381
128771
  if (ak == MULT)
382
  {
383
562
    Node c;
384
562
    Node v;
385
281
    if (ArithMSum::getMonomial(a, c, v))
386
    {
387
281
      bool isNeg = c.getConst<Rational>().sgn() == -1;
388
281
      getArithApproximations(v, approx, isNeg ? !isOverApprox : isOverApprox);
389
341
      for (unsigned i = 0, size = approx.size(); i < size; i++)
390
      {
391
60
        approx[i] = nm->mkNode(MULT, c, approx[i]);
392
      }
393
    }
394
  }
395
128490
  else if (ak == STRING_LENGTH)
396
  {
397
102886
    Kind aak = a[0].getKind();
398
102886
    if (aak == STRING_SUBSTR)
399
    {
400
      // over,under-approximations for len( substr( x, n, m ) )
401
42654
      Node lenx = nm->mkNode(STRING_LENGTH, a[0][0]);
402
21327
      if (isOverApprox)
403
      {
404
        // m >= 0 implies
405
        //  m >= len( substr( x, n, m ) )
406
12917
        if (check(a[0][2]))
407
        {
408
8169
          approx.push_back(a[0][2]);
409
        }
410
12917
        if (check(lenx, a[0][1]))
411
        {
412
          // n <= len( x ) implies
413
          //   len( x ) - n >= len( substr( x, n, m ) )
414
3922
          approx.push_back(nm->mkNode(MINUS, lenx, a[0][1]));
415
        }
416
        else
417
        {
418
          // len( x ) >= len( substr( x, n, m ) )
419
8995
          approx.push_back(lenx);
420
        }
421
      }
422
      else
423
      {
424
        // 0 <= n and n+m <= len( x ) implies
425
        //   m <= len( substr( x, n, m ) )
426
16820
        Node npm = nm->mkNode(PLUS, a[0][1], a[0][2]);
427
8410
        if (check(a[0][1]) && check(lenx, npm))
428
        {
429
571
          approx.push_back(a[0][2]);
430
        }
431
        // 0 <= n and n+m >= len( x ) implies
432
        //   len(x)-n <= len( substr( x, n, m ) )
433
8410
        if (check(a[0][1]) && check(npm, lenx))
434
        {
435
407
          approx.push_back(nm->mkNode(MINUS, lenx, a[0][1]));
436
        }
437
      }
438
    }
439
81559
    else if (aak == STRING_REPLACE)
440
    {
441
      // over,under-approximations for len( replace( x, y, z ) )
442
      // notice this is either len( x ) or ( len( x ) + len( z ) - len( y ) )
443
3908
      Node lenx = nm->mkNode(STRING_LENGTH, a[0][0]);
444
3908
      Node leny = nm->mkNode(STRING_LENGTH, a[0][1]);
445
3908
      Node lenz = nm->mkNode(STRING_LENGTH, a[0][2]);
446
1954
      if (isOverApprox)
447
      {
448
1092
        if (check(leny, lenz))
449
        {
450
          // len( y ) >= len( z ) implies
451
          //   len( x ) >= len( replace( x, y, z ) )
452
306
          approx.push_back(lenx);
453
        }
454
        else
455
        {
456
          // len( x ) + len( z ) >= len( replace( x, y, z ) )
457
786
          approx.push_back(nm->mkNode(PLUS, lenx, lenz));
458
        }
459
      }
460
      else
461
      {
462
862
        if (check(lenz, leny) || check(lenz, lenx))
463
        {
464
          // len( y ) <= len( z ) or len( x ) <= len( z ) implies
465
          //   len( x ) <= len( replace( x, y, z ) )
466
435
          approx.push_back(lenx);
467
        }
468
        else
469
        {
470
          // len( x ) - len( y ) <= len( replace( x, y, z ) )
471
427
          approx.push_back(nm->mkNode(MINUS, lenx, leny));
472
        }
473
      }
474
    }
475
79605
    else if (aak == STRING_ITOS)
476
    {
477
      // over,under-approximations for len( int.to.str( x ) )
478
200
      if (isOverApprox)
479
      {
480
106
        if (check(a[0][0], false))
481
        {
482
38
          if (check(a[0][0], true))
483
          {
484
            // x > 0 implies
485
            //   x >= len( int.to.str( x ) )
486
5
            approx.push_back(a[0][0]);
487
          }
488
          else
489
          {
490
            // x >= 0 implies
491
            //   x+1 >= len( int.to.str( x ) )
492
33
            approx.push_back(
493
66
                nm->mkNode(PLUS, nm->mkConst(Rational(1)), a[0][0]));
494
          }
495
        }
496
      }
497
      else
498
      {
499
94
        if (check(a[0][0]))
500
        {
501
          // x >= 0 implies
502
          //   len( int.to.str( x ) ) >= 1
503
22
          approx.push_back(nm->mkConst(Rational(1)));
504
        }
505
        // other crazy things are possible here, e.g.
506
        // len( int.to.str( len( y ) + 10 ) ) >= 2
507
      }
508
    }
509
  }
510
25604
  else if (ak == STRING_INDEXOF)
511
  {
512
    // over,under-approximations for indexof( x, y, n )
513
2778
    if (isOverApprox)
514
    {
515
3104
      Node lenx = nm->mkNode(STRING_LENGTH, a[0]);
516
3104
      Node leny = nm->mkNode(STRING_LENGTH, a[1]);
517
1552
      if (check(lenx, leny))
518
      {
519
        // len( x ) >= len( y ) implies
520
        //   len( x ) - len( y ) >= indexof( x, y, n )
521
13
        approx.push_back(nm->mkNode(MINUS, lenx, leny));
522
      }
523
      else
524
      {
525
        // len( x ) >= indexof( x, y, n )
526
1539
        approx.push_back(lenx);
527
      }
528
    }
529
    else
530
    {
531
      // TODO?:
532
      // contains( substr( x, n, len( x ) ), y ) implies
533
      //   n <= indexof( x, y, n )
534
      // ...hard to test, runs risk of non-termination
535
536
      // -1 <= indexof( x, y, n )
537
1226
      approx.push_back(nm->mkConst(Rational(-1)));
538
    }
539
  }
540
22826
  else if (ak == STRING_STOI)
541
  {
542
    // over,under-approximations for str.to.int( x )
543
    if (isOverApprox)
544
    {
545
      // TODO?:
546
      // y >= 0 implies
547
      //   y >= str.to.int( int.to.str( y ) )
548
    }
549
    else
550
    {
551
      // -1 <= str.to.int( x )
552
      approx.push_back(nm->mkConst(Rational(-1)));
553
    }
554
  }
555
128771
  Trace("strings-ent-approx-debug") << "Return " << approx.size() << std::endl;
556
128771
}
557
558
60497
bool ArithEntail::checkWithEqAssumption(Node assumption, Node a, bool strict)
559
{
560
60497
  Assert(assumption.getKind() == kind::EQUAL);
561
60497
  Assert(d_rr->rewrite(assumption) == assumption);
562
120994
  Trace("strings-entail") << "checkWithEqAssumption: " << assumption << " " << a
563
60497
                          << ", strict=" << strict << std::endl;
564
565
  // Find candidates variables to compute substitutions for
566
120994
  std::unordered_set<Node> candVars;
567
120994
  std::vector<Node> toVisit = {assumption};
568
791159
  while (!toVisit.empty())
569
  {
570
730662
    Node curr = toVisit.back();
571
365331
    toVisit.pop_back();
572
573
1045454
    if (curr.getKind() == kind::PLUS || curr.getKind() == kind::MULT
574
653583
        || curr.getKind() == kind::MINUS || curr.getKind() == kind::EQUAL)
575
    {
576
442410
      for (const auto& currChild : curr)
577
      {
578
304834
        toVisit.push_back(currChild);
579
      }
580
    }
581
227755
    else if (curr.isVar() && Theory::theoryOf(curr) == THEORY_ARITH)
582
    {
583
12134
      candVars.insert(curr);
584
    }
585
215621
    else if (curr.getKind() == kind::STRING_LENGTH)
586
    {
587
131597
      candVars.insert(curr);
588
    }
589
  }
590
591
  // Check if any of the candidate variables are in n
592
120994
  Node v;
593
60497
  Assert(toVisit.empty());
594
60497
  toVisit.push_back(a);
595
603527
  while (!toVisit.empty())
596
  {
597
564102
    Node curr = toVisit.back();
598
292587
    toVisit.pop_back();
599
600
583136
    for (const auto& currChild : curr)
601
    {
602
290549
      toVisit.push_back(currChild);
603
    }
604
605
292587
    if (candVars.find(curr) != candVars.end())
606
    {
607
21072
      v = curr;
608
21072
      break;
609
    }
610
  }
611
612
60497
  if (v.isNull())
613
  {
614
    // No suitable candidate found
615
39425
    return false;
616
  }
617
618
42144
  Node solution = ArithMSum::solveEqualityFor(assumption, v);
619
21072
  if (solution.isNull())
620
  {
621
    // Could not solve for v
622
38
    return false;
623
  }
624
42068
  Trace("strings-entail") << "checkWithEqAssumption: subs " << v << " -> "
625
21034
                          << solution << std::endl;
626
627
  // use capture avoiding substitution
628
21034
  a = expr::substituteCaptureAvoiding(a, v, solution);
629
21034
  return check(a, strict);
630
}
631
632
92616
bool ArithEntail::checkWithAssumption(Node assumption,
633
                                      Node a,
634
                                      Node b,
635
                                      bool strict)
636
{
637
92616
  Assert(d_rr->rewrite(assumption) == assumption);
638
639
92616
  NodeManager* nm = NodeManager::currentNM();
640
641
92616
  if (!assumption.isConst() && assumption.getKind() != kind::EQUAL)
642
  {
643
    // We rewrite inequality assumptions from x <= y to x + (str.len s) = y
644
    // where s is some fresh string variable. We use (str.len s) because
645
    // (str.len s) must be non-negative for the equation to hold.
646
120978
    Node x, y;
647
60489
    if (assumption.getKind() == kind::GEQ)
648
    {
649
49919
      x = assumption[0];
650
49919
      y = assumption[1];
651
    }
652
    else
653
    {
654
      // (not (>= s t)) --> (>= (t - 1) s)
655
10570
      Assert(assumption.getKind() == kind::NOT
656
             && assumption[0].getKind() == kind::GEQ);
657
10570
      x = nm->mkNode(kind::MINUS, assumption[0][1], nm->mkConst(Rational(1)));
658
10570
      y = assumption[0][0];
659
    }
660
661
120978
    Node s = nm->mkBoundVar("slackVal", nm->stringType());
662
120978
    Node slen = nm->mkNode(kind::STRING_LENGTH, s);
663
60489
    assumption = d_rr->rewrite(
664
120978
        nm->mkNode(kind::EQUAL, x, nm->mkNode(kind::PLUS, y, slen)));
665
  }
666
667
185232
  Node diff = nm->mkNode(kind::MINUS, a, b);
668
92616
  bool res = false;
669
92616
  if (assumption.isConst())
670
  {
671
32119
    bool assumptionBool = assumption.getConst<bool>();
672
32119
    if (assumptionBool)
673
    {
674
32119
      res = check(diff, strict);
675
    }
676
    else
677
    {
678
      res = true;
679
    }
680
  }
681
  else
682
  {
683
60497
    res = checkWithEqAssumption(assumption, diff, strict);
684
  }
685
185232
  return res;
686
}
687
688
bool ArithEntail::checkWithAssumptions(std::vector<Node> assumptions,
689
                                       Node a,
690
                                       Node b,
691
                                       bool strict)
692
{
693
  // TODO: We currently try to show the entailment with each assumption
694
  // independently. In the future, we should make better use of multiple
695
  // assumptions.
696
  bool res = false;
697
  for (const auto& assumption : assumptions)
698
  {
699
    Assert(d_rr->rewrite(assumption) == assumption);
700
701
    if (checkWithAssumption(assumption, a, b, strict))
702
    {
703
      res = true;
704
      break;
705
    }
706
  }
707
  return res;
708
}
709
710
31988
Node ArithEntail::getConstantBound(Node a, bool isLower)
711
{
712
31988
  Assert(d_rr->rewrite(a) == a);
713
31988
  Node ret;
714
31988
  if (a.isConst())
715
  {
716
14107
    ret = a;
717
  }
718
17881
  else if (a.getKind() == kind::STRING_LENGTH)
719
  {
720
3276
    if (isLower)
721
    {
722
3276
      ret = NodeManager::currentNM()->mkConst(Rational(0));
723
    }
724
  }
725
14605
  else if (a.getKind() == kind::PLUS || a.getKind() == kind::MULT)
726
  {
727
20784
    std::vector<Node> children;
728
10392
    bool success = true;
729
18773
    for (unsigned i = 0; i < a.getNumChildren(); i++)
730
    {
731
25807
      Node ac = getConstantBound(a[i], isLower);
732
17426
      if (ac.isNull())
733
      {
734
4756
        ret = ac;
735
4756
        success = false;
736
4756
        break;
737
      }
738
      else
739
      {
740
12670
        if (ac.getConst<Rational>().sgn() == 0)
741
        {
742
2804
          if (a.getKind() == kind::MULT)
743
          {
744
71
            ret = ac;
745
71
            success = false;
746
71
            break;
747
          }
748
        }
749
        else
750
        {
751
9866
          if (a.getKind() == kind::MULT)
752
          {
753
4293
            if ((ac.getConst<Rational>().sgn() > 0) != isLower)
754
            {
755
4218
              ret = Node::null();
756
4218
              success = false;
757
4218
              break;
758
            }
759
          }
760
5648
          children.push_back(ac);
761
        }
762
      }
763
    }
764
10392
    if (success)
765
    {
766
1347
      if (children.empty())
767
      {
768
238
        ret = NodeManager::currentNM()->mkConst(Rational(0));
769
      }
770
1109
      else if (children.size() == 1)
771
      {
772
1109
        ret = children[0];
773
      }
774
      else
775
      {
776
        ret = NodeManager::currentNM()->mkNode(a.getKind(), children);
777
        ret = d_rr->rewrite(ret);
778
      }
779
    }
780
  }
781
63976
  Trace("strings-rewrite-cbound")
782
31988
      << "Constant " << (isLower ? "lower" : "upper") << " bound for " << a
783
31988
      << " is " << ret << std::endl;
784
31988
  Assert(ret.isNull() || ret.isConst());
785
  // entailment check should be at least as powerful as computing a lower bound
786
31988
  Assert(!isLower || ret.isNull() || ret.getConst<Rational>().sgn() < 0
787
         || check(a, false));
788
31988
  Assert(!isLower || ret.isNull() || ret.getConst<Rational>().sgn() <= 0
789
         || check(a, true));
790
31988
  return ret;
791
}
792
793
184396
bool ArithEntail::checkInternal(Node a)
794
{
795
184396
  Assert(d_rr->rewrite(a) == a);
796
  // check whether a >= 0
797
184396
  if (a.isConst())
798
  {
799
83548
    return a.getConst<Rational>().sgn() >= 0;
800
  }
801
100848
  else if (a.getKind() == kind::STRING_LENGTH)
802
  {
803
    // str.len( t ) >= 0
804
7800
    return true;
805
  }
806
93048
  else if (a.getKind() == kind::PLUS || a.getKind() == kind::MULT)
807
  {
808
111366
    for (unsigned i = 0; i < a.getNumChildren(); i++)
809
    {
810
109693
      if (!checkInternal(a[i]))
811
      {
812
88690
        return false;
813
      }
814
    }
815
    // t1 >= 0 ^ ... ^ tn >= 0 => t1 op ... op tn >= 0
816
1673
    return true;
817
  }
818
819
2685
  return false;
820
}
821
822
644
bool ArithEntail::inferZerosInSumGeq(Node x,
823
                                     std::vector<Node>& ys,
824
                                     std::vector<Node>& zeroYs)
825
{
826
644
  Assert(zeroYs.empty());
827
828
644
  NodeManager* nm = NodeManager::currentNM();
829
830
  // Check if we can show that y1 + ... + yn >= x
831
1288
  Node sum = (ys.size() > 1) ? nm->mkNode(PLUS, ys) : ys[0];
832
644
  if (!check(sum, x))
833
  {
834
566
    return false;
835
  }
836
837
  // Try to remove yi one-by-one and check if we can still show:
838
  //
839
  // y1 + ... + yi-1 +  yi+1 + ... + yn >= x
840
  //
841
  // If that's the case, we know that yi can be zero and the inequality still
842
  // holds.
843
78
  size_t i = 0;
844
422
  while (i < ys.size())
845
  {
846
344
    Node yi = ys[i];
847
172
    std::vector<Node>::iterator pos = ys.erase(ys.begin() + i);
848
172
    if (ys.size() > 1)
849
    {
850
60
      sum = nm->mkNode(PLUS, ys);
851
    }
852
    else
853
    {
854
112
      sum = ys.size() == 1 ? ys[0] : nm->mkConst(Rational(0));
855
    }
856
857
172
    if (check(sum, x))
858
    {
859
32
      zeroYs.push_back(yi);
860
    }
861
    else
862
    {
863
140
      ys.insert(pos, yi);
864
140
      i++;
865
    }
866
  }
867
78
  return true;
868
}
869
870
}  // namespace strings
871
}  // namespace theory
872
22746
}  // namespace cvc5