GCC Code Coverage Report
Directory: . Exec Total Coverage
File: src/theory/strings/arith_entail.cpp Lines: 344 384 89.6 %
Date: 2021-05-22 Branches: 878 1918 45.8 %

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
26
using namespace cvc5::kind;
27
28
namespace cvc5 {
29
namespace theory {
30
namespace strings {
31
32
1494
bool ArithEntail::checkEq(Node a, Node b)
33
{
34
1494
  if (a == b)
35
  {
36
    return true;
37
  }
38
2988
  Node ar = Rewriter::rewrite(a);
39
2988
  Node br = Rewriter::rewrite(b);
40
1494
  return ar == br;
41
}
42
43
512130
bool ArithEntail::check(Node a, Node b, bool strict)
44
{
45
512130
  if (a == b)
46
  {
47
13793
    return !strict;
48
  }
49
996674
  Node diff = NodeManager::currentNM()->mkNode(kind::MINUS, a, b);
50
498337
  return check(diff, strict);
51
}
52
53
struct StrCheckEntailArithTag
54
{
55
};
56
struct StrCheckEntailArithComputedTag
57
{
58
};
59
/** Attribute true for expressions for which check returned true */
60
typedef expr::Attribute<StrCheckEntailArithTag, bool> StrCheckEntailArithAttr;
61
typedef expr::Attribute<StrCheckEntailArithComputedTag, bool>
62
    StrCheckEntailArithComputedAttr;
63
64
754659
bool ArithEntail::check(Node a, bool strict)
65
{
66
754659
  if (a.isConst())
67
  {
68
79318
    return a.getConst<Rational>().sgn() >= (strict ? 1 : 0);
69
  }
70
71
  Node ar = strict ? NodeManager::currentNM()->mkNode(
72
904968
                kind::MINUS, a, NodeManager::currentNM()->mkConst(Rational(1)))
73
1580309
                   : a;
74
675341
  ar = Rewriter::rewrite(ar);
75
76
675341
  if (ar.getAttribute(StrCheckEntailArithComputedAttr()))
77
  {
78
518134
    return ar.getAttribute(StrCheckEntailArithAttr());
79
  }
80
81
157207
  bool ret = checkInternal(ar);
82
157207
  if (!ret)
83
  {
84
    // try with approximations
85
146414
    ret = checkApprox(ar);
86
  }
87
  // cache the result
88
157207
  ar.setAttribute(StrCheckEntailArithAttr(), ret);
89
157207
  ar.setAttribute(StrCheckEntailArithComputedAttr(), true);
90
157207
  return ret;
91
}
92
93
146414
bool ArithEntail::checkApprox(Node ar)
94
{
95
146414
  Assert(Rewriter::rewrite(ar) == ar);
96
146414
  NodeManager* nm = NodeManager::currentNM();
97
292828
  std::map<Node, Node> msum;
98
292828
  Trace("strings-ent-approx-debug")
99
146414
      << "Setup arithmetic approximations for " << ar << std::endl;
100
146414
  if (!ArithMSum::getMonomialSum(ar, msum))
101
  {
102
    Trace("strings-ent-approx-debug")
103
        << "...failed to get monomial sum!" << std::endl;
104
    return false;
105
  }
106
  // for each monomial v*c, mApprox[v] a list of
107
  // possibilities for how the term can be soundly approximated, that is,
108
  // if mApprox[v] contains av, then v*c > av*c. Notice that if c
109
  // is positive, then v > av, otherwise if c is negative, then v < av.
110
  // In other words, av is an under-approximation if c is positive, and an
111
  // over-approximation if c is negative.
112
146414
  bool changed = false;
113
292828
  std::map<Node, std::vector<Node> > mApprox;
114
  // map from approximations to their monomial sums
115
292828
  std::map<Node, std::map<Node, Node> > approxMsums;
116
  // aarSum stores each monomial that does not have multiple approximations
117
292828
  std::vector<Node> aarSum;
118
534891
  for (std::pair<const Node, Node>& m : msum)
119
  {
120
776954
    Node v = m.first;
121
776954
    Node c = m.second;
122
776954
    Trace("strings-ent-approx-debug")
123
388477
        << "Get approximations " << v << "..." << std::endl;
124
388477
    if (v.isNull())
125
    {
126
217052
      Node mn = c.isNull() ? nm->mkConst(Rational(1)) : c;
127
108526
      aarSum.push_back(mn);
128
    }
129
    else
130
    {
131
      // c.isNull() means c = 1
132
279951
      bool isOverApprox = !c.isNull() && c.getConst<Rational>().sgn() == -1;
133
279951
      std::vector<Node>& approx = mApprox[v];
134
559902
      std::unordered_set<Node> visited;
135
559902
      std::vector<Node> toProcess;
136
279951
      toProcess.push_back(v);
137
47923
      do
138
      {
139
655748
        Node curr = toProcess.back();
140
327874
        Trace("strings-ent-approx-debug") << "  process " << curr << std::endl;
141
327874
        curr = Rewriter::rewrite(curr);
142
327874
        toProcess.pop_back();
143
327874
        if (visited.find(curr) == visited.end())
144
        {
145
324296
          visited.insert(curr);
146
648592
          std::vector<Node> currApprox;
147
324296
          getArithApproximations(curr, currApprox, isOverApprox);
148
324296
          if (currApprox.empty())
149
          {
150
581236
            Trace("strings-ent-approx-debug")
151
290618
                << "...approximation: " << curr << std::endl;
152
            // no approximations, thus curr is a possibility
153
290618
            approx.push_back(curr);
154
          }
155
          else
156
          {
157
33678
            toProcess.insert(
158
67356
                toProcess.end(), currApprox.begin(), currApprox.end());
159
          }
160
        }
161
327874
      } while (!toProcess.empty());
162
279951
      Assert(!approx.empty());
163
      // if we have only one approximation, move it to final
164
279951
      if (approx.size() == 1)
165
      {
166
269300
        changed = v != approx[0];
167
538600
        Node mn = ArithMSum::mkCoeffTerm(c, approx[0]);
168
269300
        aarSum.push_back(mn);
169
269300
        mApprox.erase(v);
170
      }
171
      else
172
      {
173
        // compute monomial sum form for each approximation, used below
174
31969
        for (const Node& aa : approx)
175
        {
176
21318
          if (approxMsums.find(aa) == approxMsums.end())
177
          {
178
            CVC5_UNUSED bool ret =
179
20306
                ArithMSum::getMonomialSum(aa, approxMsums[aa]);
180
20306
            Assert(ret);
181
          }
182
        }
183
10651
        changed = true;
184
      }
185
    }
186
  }
187
146414
  if (!changed)
188
  {
189
    // approximations had no effect, return
190
129366
    Trace("strings-ent-approx-debug") << "...no approximations" << std::endl;
191
129366
    return false;
192
  }
193
  // get the current "fixed" sum for the abstraction of ar
194
17048
  Node aar = aarSum.empty()
195
18486
                 ? nm->mkConst(Rational(0))
196
35534
                 : (aarSum.size() == 1 ? aarSum[0] : nm->mkNode(PLUS, aarSum));
197
17048
  aar = Rewriter::rewrite(aar);
198
34096
  Trace("strings-ent-approx-debug")
199
34096
      << "...processed fixed sum " << aar << " with " << mApprox.size()
200
17048
      << " approximated monomials." << std::endl;
201
  // if we have a choice of how to approximate
202
17048
  if (!mApprox.empty())
203
  {
204
    // convert aar back to monomial sum
205
13934
    std::map<Node, Node> msumAar;
206
6967
    if (!ArithMSum::getMonomialSum(aar, msumAar))
207
    {
208
      return false;
209
    }
210
6967
    if (Trace.isOn("strings-ent-approx"))
211
    {
212
      Trace("strings-ent-approx")
213
          << "---- Check arithmetic entailment by under-approximation " << ar
214
          << " >= 0" << std::endl;
215
      Trace("strings-ent-approx") << "FIXED:" << std::endl;
216
      ArithMSum::debugPrintMonomialSum(msumAar, "strings-ent-approx");
217
      Trace("strings-ent-approx") << "APPROX:" << std::endl;
218
      for (std::pair<const Node, std::vector<Node> >& a : mApprox)
219
      {
220
        Node c = msum[a.first];
221
        Trace("strings-ent-approx") << "  ";
222
        if (!c.isNull())
223
        {
224
          Trace("strings-ent-approx") << c << " * ";
225
        }
226
        Trace("strings-ent-approx")
227
            << a.second << " ...from " << a.first << std::endl;
228
      }
229
      Trace("strings-ent-approx") << std::endl;
230
    }
231
13934
    Rational one(1);
232
    // incorporate monomials one at a time that have a choice of approximations
233
21059
    while (!mApprox.empty())
234
    {
235
14092
      Node v;
236
14092
      Node vapprox;
237
7046
      int maxScore = -1;
238
      // Look at each approximation, take the one with the best score.
239
      // Notice that we are in the process of trying to prove
240
      // ( c1*t1 + .. + cn*tn ) + ( approx_1 | ... | approx_m ) >= 0,
241
      // where c1*t1 + .. + cn*tn is the "fixed" component of our sum (aar)
242
      // and approx_1 ... approx_m are possible approximations. The
243
      // intution here is that we want coefficients c1...cn to be positive.
244
      // This is because arithmetic string terms t1...tn (which may be
245
      // applications of len, indexof, str.to.int) are never entailed to be
246
      // negative. Hence, we add the approx_i that contributes the "most"
247
      // towards making all constants c1...cn positive and cancelling negative
248
      // monomials in approx_i itself.
249
7046
      for (std::pair<const Node, std::vector<Node> >& nam : mApprox)
250
      {
251
7046
        Node cr = msum[nam.first];
252
21154
        for (const Node& aa : nam.second)
253
        {
254
14108
          unsigned helpsCancelCount = 0;
255
14108
          unsigned addsObligationCount = 0;
256
14108
          std::map<Node, Node>::iterator it;
257
          // we are processing an approximation cr*( c1*t1 + ... + cn*tn )
258
31375
          for (std::pair<const Node, Node>& aam : approxMsums[aa])
259
          {
260
            // Say aar is of the form t + c*ti, and aam is the monomial ci*ti
261
            // where ci != 0. We say aam:
262
            // (1) helps cancel if c != 0 and c>0 != ci>0
263
            // (2) adds obligation if c>=0 and c+ci<0
264
34534
            Node ti = aam.first;
265
34534
            Node ci = aam.second;
266
17267
            if (!cr.isNull())
267
            {
268
26406
              ci = ci.isNull() ? cr
269
26406
                               : Rewriter::rewrite(nm->mkNode(MULT, ci, cr));
270
            }
271
17267
            Trace("strings-ent-approx-debug") << ci << "*" << ti << " ";
272
17267
            int ciSgn = ci.isNull() ? 1 : ci.getConst<Rational>().sgn();
273
17267
            it = msumAar.find(ti);
274
17267
            if (it != msumAar.end())
275
            {
276
18658
              Node c = it->second;
277
9329
              int cSgn = c.isNull() ? 1 : c.getConst<Rational>().sgn();
278
9329
              if (cSgn == 0)
279
              {
280
1889
                addsObligationCount += (ciSgn == -1 ? 1 : 0);
281
              }
282
7440
              else if (cSgn != ciSgn)
283
              {
284
6239
                helpsCancelCount++;
285
12478
                Rational r1 = c.isNull() ? one : c.getConst<Rational>();
286
12478
                Rational r2 = ci.isNull() ? one : ci.getConst<Rational>();
287
12478
                Rational r12 = r1 + r2;
288
6239
                if (r12.sgn() == -1)
289
                {
290
4024
                  addsObligationCount++;
291
                }
292
              }
293
            }
294
            else
295
            {
296
7938
              addsObligationCount += (ciSgn == -1 ? 1 : 0);
297
            }
298
          }
299
28216
          Trace("strings-ent-approx-debug")
300
14108
              << "counts=" << helpsCancelCount << "," << addsObligationCount
301
14108
              << " for " << aa << " into " << aar << std::endl;
302
28216
          int score = (addsObligationCount > 0 ? 0 : 2)
303
14108
                      + (helpsCancelCount > 0 ? 1 : 0);
304
          // if its the best, update v and vapprox
305
14108
          if (v.isNull() || score > maxScore)
306
          {
307
9604
            v = nam.first;
308
9604
            vapprox = aa;
309
9604
            maxScore = score;
310
          }
311
        }
312
7046
        if (!v.isNull())
313
        {
314
7046
          break;
315
        }
316
      }
317
14092
      Trace("strings-ent-approx")
318
7046
          << "- Decide " << v << " = " << vapprox << std::endl;
319
      // we incorporate v approximated by vapprox into the overall approximation
320
      // for ar
321
7046
      Assert(!v.isNull() && !vapprox.isNull());
322
7046
      Assert(msum.find(v) != msum.end());
323
14092
      Node mn = ArithMSum::mkCoeffTerm(msum[v], vapprox);
324
7046
      aar = nm->mkNode(PLUS, aar, mn);
325
      // update the msumAar map
326
7046
      aar = Rewriter::rewrite(aar);
327
7046
      msumAar.clear();
328
7046
      if (!ArithMSum::getMonomialSum(aar, msumAar))
329
      {
330
        Assert(false);
331
        Trace("strings-ent-approx")
332
            << "...failed to get monomial sum!" << std::endl;
333
        return false;
334
      }
335
      // we have processed the approximation for v
336
7046
      mApprox.erase(v);
337
    }
338
6967
    Trace("strings-ent-approx") << "-----------------" << std::endl;
339
  }
340
17048
  if (aar == ar)
341
  {
342
    Trace("strings-ent-approx-debug")
343
        << "...approximation had no effect" << std::endl;
344
    // this should never happen, but we avoid the infinite loop for sanity here
345
    Assert(false);
346
    return false;
347
  }
348
  // Check entailment on the approximation of ar.
349
  // Notice that this may trigger further reasoning by approximation. For
350
  // example, len( replace( x ++ y, substr( x, 0, n ), z ) ) may be
351
  // under-approximated as len( x ) + len( y ) - len( substr( x, 0, n ) ) on
352
  // this call, where in the recursive call we may over-approximate
353
  // len( substr( x, 0, n ) ) as len( x ). In this example, we can infer
354
  // that len( replace( x ++ y, substr( x, 0, n ), z ) ) >= len( y ) in two
355
  // steps.
356
17048
  if (check(aar))
357
  {
358
3236
    Trace("strings-ent-approx")
359
1618
        << "*** StrArithApprox: showed " << ar
360
1618
        << " >= 0 using under-approximation!" << std::endl;
361
3236
    Trace("strings-ent-approx")
362
1618
        << "*** StrArithApprox: under-approximation was " << aar << std::endl;
363
1618
    return true;
364
  }
365
15430
  return false;
366
}
367
368
324857
void ArithEntail::getArithApproximations(Node a,
369
                                         std::vector<Node>& approx,
370
                                         bool isOverApprox)
371
{
372
324857
  NodeManager* nm = NodeManager::currentNM();
373
  // We do not handle PLUS here since this leads to exponential behavior.
374
  // Instead, this is managed, e.g. during checkApprox, where
375
  // PLUS terms are expanded "on-demand" during the reasoning.
376
649714
  Trace("strings-ent-approx-debug")
377
324857
      << "Get arith approximations " << a << std::endl;
378
324857
  Kind ak = a.getKind();
379
324857
  if (ak == MULT)
380
  {
381
1122
    Node c;
382
1122
    Node v;
383
561
    if (ArithMSum::getMonomial(a, c, v))
384
    {
385
561
      bool isNeg = c.getConst<Rational>().sgn() == -1;
386
561
      getArithApproximations(v, approx, isNeg ? !isOverApprox : isOverApprox);
387
594
      for (unsigned i = 0, size = approx.size(); i < size; i++)
388
      {
389
33
        approx[i] = nm->mkNode(MULT, c, approx[i]);
390
      }
391
    }
392
  }
393
324296
  else if (ak == STRING_LENGTH)
394
  {
395
289908
    Kind aak = a[0].getKind();
396
289908
    if (aak == STRING_SUBSTR)
397
    {
398
      // over,under-approximations for len( substr( x, n, m ) )
399
73488
      Node lenx = nm->mkNode(STRING_LENGTH, a[0][0]);
400
36744
      if (isOverApprox)
401
      {
402
        // m >= 0 implies
403
        //  m >= len( substr( x, n, m ) )
404
21125
        if (check(a[0][2]))
405
        {
406
12762
          approx.push_back(a[0][2]);
407
        }
408
21125
        if (check(lenx, a[0][1]))
409
        {
410
          // n <= len( x ) implies
411
          //   len( x ) - n >= len( substr( x, n, m ) )
412
8147
          approx.push_back(nm->mkNode(MINUS, lenx, a[0][1]));
413
        }
414
        else
415
        {
416
          // len( x ) >= len( substr( x, n, m ) )
417
12978
          approx.push_back(lenx);
418
        }
419
      }
420
      else
421
      {
422
        // 0 <= n and n+m <= len( x ) implies
423
        //   m <= len( substr( x, n, m ) )
424
31238
        Node npm = nm->mkNode(PLUS, a[0][1], a[0][2]);
425
15619
        if (check(a[0][1]) && check(lenx, npm))
426
        {
427
2673
          approx.push_back(a[0][2]);
428
        }
429
        // 0 <= n and n+m >= len( x ) implies
430
        //   len(x)-n <= len( substr( x, n, m ) )
431
15619
        if (check(a[0][1]) && check(npm, lenx))
432
        {
433
1627
          approx.push_back(nm->mkNode(MINUS, lenx, a[0][1]));
434
        }
435
      }
436
    }
437
253164
    else if (aak == STRING_STRREPL)
438
    {
439
      // over,under-approximations for len( replace( x, y, z ) )
440
      // notice this is either len( x ) or ( len( x ) + len( z ) - len( y ) )
441
10578
      Node lenx = nm->mkNode(STRING_LENGTH, a[0][0]);
442
10578
      Node leny = nm->mkNode(STRING_LENGTH, a[0][1]);
443
10578
      Node lenz = nm->mkNode(STRING_LENGTH, a[0][2]);
444
5289
      if (isOverApprox)
445
      {
446
2901
        if (check(leny, lenz))
447
        {
448
          // len( y ) >= len( z ) implies
449
          //   len( x ) >= len( replace( x, y, z ) )
450
915
          approx.push_back(lenx);
451
        }
452
        else
453
        {
454
          // len( x ) + len( z ) >= len( replace( x, y, z ) )
455
1986
          approx.push_back(nm->mkNode(PLUS, lenx, lenz));
456
        }
457
      }
458
      else
459
      {
460
2388
        if (check(lenz, leny) || check(lenz, lenx))
461
        {
462
          // len( y ) <= len( z ) or len( x ) <= len( z ) implies
463
          //   len( x ) <= len( replace( x, y, z ) )
464
1140
          approx.push_back(lenx);
465
        }
466
        else
467
        {
468
          // len( x ) - len( y ) <= len( replace( x, y, z ) )
469
1248
          approx.push_back(nm->mkNode(MINUS, lenx, leny));
470
        }
471
      }
472
    }
473
247875
    else if (aak == STRING_ITOS)
474
    {
475
      // over,under-approximations for len( int.to.str( x ) )
476
441
      if (isOverApprox)
477
      {
478
224
        if (check(a[0][0], false))
479
        {
480
116
          if (check(a[0][0], true))
481
          {
482
            // x > 0 implies
483
            //   x >= len( int.to.str( x ) )
484
7
            approx.push_back(a[0][0]);
485
          }
486
          else
487
          {
488
            // x >= 0 implies
489
            //   x+1 >= len( int.to.str( x ) )
490
109
            approx.push_back(
491
218
                nm->mkNode(PLUS, nm->mkConst(Rational(1)), a[0][0]));
492
          }
493
        }
494
      }
495
      else
496
      {
497
217
        if (check(a[0][0]))
498
        {
499
          // x >= 0 implies
500
          //   len( int.to.str( x ) ) >= 1
501
97
          approx.push_back(nm->mkConst(Rational(1)));
502
        }
503
        // other crazy things are possible here, e.g.
504
        // len( int.to.str( len( y ) + 10 ) ) >= 2
505
      }
506
    }
507
  }
508
34388
  else if (ak == STRING_STRIDOF)
509
  {
510
    // over,under-approximations for indexof( x, y, n )
511
4234
    if (isOverApprox)
512
    {
513
4452
      Node lenx = nm->mkNode(STRING_LENGTH, a[0]);
514
4452
      Node leny = nm->mkNode(STRING_LENGTH, a[1]);
515
2226
      if (check(lenx, leny))
516
      {
517
        // len( x ) >= len( y ) implies
518
        //   len( x ) - len( y ) >= indexof( x, y, n )
519
23
        approx.push_back(nm->mkNode(MINUS, lenx, leny));
520
      }
521
      else
522
      {
523
        // len( x ) >= indexof( x, y, n )
524
2203
        approx.push_back(lenx);
525
      }
526
    }
527
    else
528
    {
529
      // TODO?:
530
      // contains( substr( x, n, len( x ) ), y ) implies
531
      //   n <= indexof( x, y, n )
532
      // ...hard to test, runs risk of non-termination
533
534
      // -1 <= indexof( x, y, n )
535
2008
      approx.push_back(nm->mkConst(Rational(-1)));
536
    }
537
  }
538
30154
  else if (ak == STRING_STOI)
539
  {
540
    // over,under-approximations for str.to.int( x )
541
    if (isOverApprox)
542
    {
543
      // TODO?:
544
      // y >= 0 implies
545
      //   y >= str.to.int( int.to.str( y ) )
546
    }
547
    else
548
    {
549
      // -1 <= str.to.int( x )
550
      approx.push_back(nm->mkConst(Rational(-1)));
551
    }
552
  }
553
324857
  Trace("strings-ent-approx-debug") << "Return " << approx.size() << std::endl;
554
324857
}
555
556
52142
bool ArithEntail::checkWithEqAssumption(Node assumption, Node a, bool strict)
557
{
558
52142
  Assert(assumption.getKind() == kind::EQUAL);
559
52142
  Assert(Rewriter::rewrite(assumption) == assumption);
560
104284
  Trace("strings-entail") << "checkWithEqAssumption: " << assumption << " " << a
561
52142
                          << ", strict=" << strict << std::endl;
562
563
  // Find candidates variables to compute substitutions for
564
104284
  std::unordered_set<Node> candVars;
565
104284
  std::vector<Node> toVisit = {assumption};
566
644968
  while (!toVisit.empty())
567
  {
568
592826
    Node curr = toVisit.back();
569
296413
    toVisit.pop_back();
570
571
845856
    if (curr.getKind() == kind::PLUS || curr.getKind() == kind::MULT
572
532461
        || curr.getKind() == kind::MINUS || curr.getKind() == kind::EQUAL)
573
    {
574
356778
      for (const auto& currChild : curr)
575
      {
576
244271
        toVisit.push_back(currChild);
577
      }
578
    }
579
183906
    else if (curr.isVar() && Theory::theoryOf(curr) == THEORY_ARITH)
580
    {
581
8848
      candVars.insert(curr);
582
    }
583
175058
    else if (curr.getKind() == kind::STRING_LENGTH)
584
    {
585
109026
      candVars.insert(curr);
586
    }
587
  }
588
589
  // Check if any of the candidate variables are in n
590
104284
  Node v;
591
52142
  Assert(toVisit.empty());
592
52142
  toVisit.push_back(a);
593
518378
  while (!toVisit.empty())
594
  {
595
488814
    Node curr = toVisit.back();
596
255696
    toVisit.pop_back();
597
598
522253
    for (const auto& currChild : curr)
599
    {
600
266557
      toVisit.push_back(currChild);
601
    }
602
603
255696
    if (candVars.find(curr) != candVars.end())
604
    {
605
22578
      v = curr;
606
22578
      break;
607
    }
608
  }
609
610
52142
  if (v.isNull())
611
  {
612
    // No suitable candidate found
613
29564
    return false;
614
  }
615
616
45156
  Node solution = ArithMSum::solveEqualityFor(assumption, v);
617
22578
  if (solution.isNull())
618
  {
619
    // Could not solve for v
620
80
    return false;
621
  }
622
44996
  Trace("strings-entail") << "checkWithEqAssumption: subs " << v << " -> "
623
22498
                          << solution << std::endl;
624
625
  // use capture avoiding substitution
626
22498
  a = expr::substituteCaptureAvoiding(a, v, solution);
627
22498
  return check(a, strict);
628
}
629
630
77123
bool ArithEntail::checkWithAssumption(Node assumption,
631
                                      Node a,
632
                                      Node b,
633
                                      bool strict)
634
{
635
77123
  Assert(Rewriter::rewrite(assumption) == assumption);
636
637
77123
  NodeManager* nm = NodeManager::currentNM();
638
639
77123
  if (!assumption.isConst() && assumption.getKind() != kind::EQUAL)
640
  {
641
    // We rewrite inequality assumptions from x <= y to x + (str.len s) = y
642
    // where s is some fresh string variable. We use (str.len s) because
643
    // (str.len s) must be non-negative for the equation to hold.
644
104268
    Node x, y;
645
52134
    if (assumption.getKind() == kind::GEQ)
646
    {
647
42848
      x = assumption[0];
648
42848
      y = assumption[1];
649
    }
650
    else
651
    {
652
      // (not (>= s t)) --> (>= (t - 1) s)
653
9286
      Assert(assumption.getKind() == kind::NOT
654
             && assumption[0].getKind() == kind::GEQ);
655
9286
      x = nm->mkNode(kind::MINUS, assumption[0][1], nm->mkConst(Rational(1)));
656
9286
      y = assumption[0][0];
657
    }
658
659
104268
    Node s = nm->mkBoundVar("slackVal", nm->stringType());
660
104268
    Node slen = nm->mkNode(kind::STRING_LENGTH, s);
661
52134
    assumption = Rewriter::rewrite(
662
104268
        nm->mkNode(kind::EQUAL, x, nm->mkNode(kind::PLUS, y, slen)));
663
  }
664
665
154246
  Node diff = nm->mkNode(kind::MINUS, a, b);
666
77123
  bool res = false;
667
77123
  if (assumption.isConst())
668
  {
669
24981
    bool assumptionBool = assumption.getConst<bool>();
670
24981
    if (assumptionBool)
671
    {
672
24981
      res = check(diff, strict);
673
    }
674
    else
675
    {
676
      res = true;
677
    }
678
  }
679
  else
680
  {
681
52142
    res = checkWithEqAssumption(assumption, diff, strict);
682
  }
683
154246
  return res;
684
}
685
686
bool ArithEntail::checkWithAssumptions(std::vector<Node> assumptions,
687
                                       Node a,
688
                                       Node b,
689
                                       bool strict)
690
{
691
  // TODO: We currently try to show the entailment with each assumption
692
  // independently. In the future, we should make better use of multiple
693
  // assumptions.
694
  bool res = false;
695
  for (const auto& assumption : assumptions)
696
  {
697
    Assert(Rewriter::rewrite(assumption) == assumption);
698
699
    if (checkWithAssumption(assumption, a, b, strict))
700
    {
701
      res = true;
702
      break;
703
    }
704
  }
705
  return res;
706
}
707
708
25982
Node ArithEntail::getConstantBound(Node a, bool isLower)
709
{
710
25982
  Assert(Rewriter::rewrite(a) == a);
711
25982
  Node ret;
712
25982
  if (a.isConst())
713
  {
714
10215
    ret = a;
715
  }
716
15767
  else if (a.getKind() == kind::STRING_LENGTH)
717
  {
718
3082
    if (isLower)
719
    {
720
3082
      ret = NodeManager::currentNM()->mkConst(Rational(0));
721
    }
722
  }
723
12685
  else if (a.getKind() == kind::PLUS || a.getKind() == kind::MULT)
724
  {
725
18090
    std::vector<Node> children;
726
9045
    bool success = true;
727
16892
    for (unsigned i = 0; i < a.getNumChildren(); i++)
728
    {
729
23456
      Node ac = getConstantBound(a[i], isLower);
730
15609
      if (ac.isNull())
731
      {
732
4249
        ret = ac;
733
4249
        success = false;
734
4249
        break;
735
      }
736
      else
737
      {
738
11360
        if (ac.getConst<Rational>().sgn() == 0)
739
        {
740
2793
          if (a.getKind() == kind::MULT)
741
          {
742
4
            ret = ac;
743
4
            success = false;
744
4
            break;
745
          }
746
        }
747
        else
748
        {
749
8567
          if (a.getKind() == kind::MULT)
750
          {
751
3513
            if ((ac.getConst<Rational>().sgn() > 0) != isLower)
752
            {
753
3509
              ret = Node::null();
754
3509
              success = false;
755
3509
              break;
756
            }
757
          }
758
5058
          children.push_back(ac);
759
        }
760
      }
761
    }
762
9045
    if (success)
763
    {
764
1283
      if (children.empty())
765
      {
766
268
        ret = NodeManager::currentNM()->mkConst(Rational(0));
767
      }
768
1015
      else if (children.size() == 1)
769
      {
770
1015
        ret = children[0];
771
      }
772
      else
773
      {
774
        ret = NodeManager::currentNM()->mkNode(a.getKind(), children);
775
        ret = Rewriter::rewrite(ret);
776
      }
777
    }
778
  }
779
51964
  Trace("strings-rewrite-cbound")
780
25982
      << "Constant " << (isLower ? "lower" : "upper") << " bound for " << a
781
25982
      << " is " << ret << std::endl;
782
25982
  Assert(ret.isNull() || ret.isConst());
783
  // entailment check should be at least as powerful as computing a lower bound
784
25982
  Assert(!isLower || ret.isNull() || ret.getConst<Rational>().sgn() < 0
785
         || check(a, false));
786
25982
  Assert(!isLower || ret.isNull() || ret.getConst<Rational>().sgn() <= 0
787
         || check(a, true));
788
25982
  return ret;
789
}
790
791
409773
bool ArithEntail::checkInternal(Node a)
792
{
793
409773
  Assert(Rewriter::rewrite(a) == a);
794
  // check whether a >= 0
795
409773
  if (a.isConst())
796
  {
797
173309
    return a.getConst<Rational>().sgn() >= 0;
798
  }
799
236464
  else if (a.getKind() == kind::STRING_LENGTH)
800
  {
801
    // str.len( t ) >= 0
802
34067
    return true;
803
  }
804
202397
  else if (a.getKind() == kind::PLUS || a.getKind() == kind::MULT)
805
  {
806
254313
    for (unsigned i = 0; i < a.getNumChildren(); i++)
807
    {
808
252566
      if (!checkInternal(a[i]))
809
      {
810
198174
        return false;
811
      }
812
    }
813
    // t1 >= 0 ^ ... ^ tn >= 0 => t1 op ... op tn >= 0
814
1747
    return true;
815
  }
816
817
2476
  return false;
818
}
819
820
1372
bool ArithEntail::inferZerosInSumGeq(Node x,
821
                                     std::vector<Node>& ys,
822
                                     std::vector<Node>& zeroYs)
823
{
824
1372
  Assert(zeroYs.empty());
825
826
1372
  NodeManager* nm = NodeManager::currentNM();
827
828
  // Check if we can show that y1 + ... + yn >= x
829
2744
  Node sum = (ys.size() > 1) ? nm->mkNode(PLUS, ys) : ys[0];
830
1372
  if (!check(sum, x))
831
  {
832
436
    return false;
833
  }
834
835
  // Try to remove yi one-by-one and check if we can still show:
836
  //
837
  // y1 + ... + yi-1 +  yi+1 + ... + yn >= x
838
  //
839
  // If that's the case, we know that yi can be zero and the inequality still
840
  // holds.
841
936
  size_t i = 0;
842
5928
  while (i < ys.size())
843
  {
844
4992
    Node yi = ys[i];
845
2496
    std::vector<Node>::iterator pos = ys.erase(ys.begin() + i);
846
2496
    if (ys.size() > 1)
847
    {
848
936
      sum = nm->mkNode(PLUS, ys);
849
    }
850
    else
851
    {
852
1560
      sum = ys.size() == 1 ? ys[0] : nm->mkConst(Rational(0));
853
    }
854
855
2496
    if (check(sum, x))
856
    {
857
928
      zeroYs.push_back(yi);
858
    }
859
    else
860
    {
861
1568
      ys.insert(pos, yi);
862
1568
      i++;
863
    }
864
  }
865
936
  return true;
866
}
867
868
}  // namespace strings
869
}  // namespace theory
870
28194
}  // namespace cvc5