GCC Code Coverage Report
Directory: . Exec Total Coverage
File: src/theory/strings/arith_entail.cpp Lines: 344 384 89.6 %
Date: 2021-08-17 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
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
29337
}  // namespace cvc5