GCC Code Coverage Report
Directory: . Exec Total Coverage
File: src/theory/strings/arith_entail.cpp Lines: 344 384 89.6 %
Date: 2021-05-24 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
497977
bool ArithEntail::check(Node a, Node b, bool strict)
44
{
45
497977
  if (a == b)
46
  {
47
13266
    return !strict;
48
  }
49
969422
  Node diff = NodeManager::currentNM()->mkNode(kind::MINUS, a, b);
50
484711
  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
733628
bool ArithEntail::check(Node a, bool strict)
65
{
66
733628
  if (a.isConst())
67
  {
68
78285
    return a.getConst<Rational>().sgn() >= (strict ? 1 : 0);
69
  }
70
71
  Node ar = strict ? NodeManager::currentNM()->mkNode(
72
877504
                kind::MINUS, a, NodeManager::currentNM()->mkConst(Rational(1)))
73
1532847
                   : a;
74
655343
  ar = Rewriter::rewrite(ar);
75
76
655343
  if (ar.getAttribute(StrCheckEntailArithComputedAttr()))
77
  {
78
503719
    return ar.getAttribute(StrCheckEntailArithAttr());
79
  }
80
81
151624
  bool ret = checkInternal(ar);
82
151624
  if (!ret)
83
  {
84
    // try with approximations
85
141077
    ret = checkApprox(ar);
86
  }
87
  // cache the result
88
151624
  ar.setAttribute(StrCheckEntailArithAttr(), ret);
89
151624
  ar.setAttribute(StrCheckEntailArithComputedAttr(), true);
90
151624
  return ret;
91
}
92
93
141077
bool ArithEntail::checkApprox(Node ar)
94
{
95
141077
  Assert(Rewriter::rewrite(ar) == ar);
96
141077
  NodeManager* nm = NodeManager::currentNM();
97
282154
  std::map<Node, Node> msum;
98
282154
  Trace("strings-ent-approx-debug")
99
141077
      << "Setup arithmetic approximations for " << ar << std::endl;
100
141077
  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
141077
  bool changed = false;
113
282154
  std::map<Node, std::vector<Node> > mApprox;
114
  // map from approximations to their monomial sums
115
282154
  std::map<Node, std::map<Node, Node> > approxMsums;
116
  // aarSum stores each monomial that does not have multiple approximations
117
282154
  std::vector<Node> aarSum;
118
514323
  for (std::pair<const Node, Node>& m : msum)
119
  {
120
746492
    Node v = m.first;
121
746492
    Node c = m.second;
122
746492
    Trace("strings-ent-approx-debug")
123
373246
        << "Get approximations " << v << "..." << std::endl;
124
373246
    if (v.isNull())
125
    {
126
208512
      Node mn = c.isNull() ? nm->mkConst(Rational(1)) : c;
127
104256
      aarSum.push_back(mn);
128
    }
129
    else
130
    {
131
      // c.isNull() means c = 1
132
268990
      bool isOverApprox = !c.isNull() && c.getConst<Rational>().sgn() == -1;
133
268990
      std::vector<Node>& approx = mApprox[v];
134
537980
      std::unordered_set<Node> visited;
135
537980
      std::vector<Node> toProcess;
136
268990
      toProcess.push_back(v);
137
47539
      do
138
      {
139
633058
        Node curr = toProcess.back();
140
316529
        Trace("strings-ent-approx-debug") << "  process " << curr << std::endl;
141
316529
        curr = Rewriter::rewrite(curr);
142
316529
        toProcess.pop_back();
143
316529
        if (visited.find(curr) == visited.end())
144
        {
145
313024
          visited.insert(curr);
146
626048
          std::vector<Node> currApprox;
147
313024
          getArithApproximations(curr, currApprox, isOverApprox);
148
313024
          if (currApprox.empty())
149
          {
150
559288
            Trace("strings-ent-approx-debug")
151
279644
                << "...approximation: " << curr << std::endl;
152
            // no approximations, thus curr is a possibility
153
279644
            approx.push_back(curr);
154
          }
155
          else
156
          {
157
33380
            toProcess.insert(
158
66760
                toProcess.end(), currApprox.begin(), currApprox.end());
159
          }
160
        }
161
316529
      } while (!toProcess.empty());
162
268990
      Assert(!approx.empty());
163
      // if we have only one approximation, move it to final
164
268990
      if (approx.size() == 1)
165
      {
166
258352
        changed = v != approx[0];
167
516704
        Node mn = ArithMSum::mkCoeffTerm(c, approx[0]);
168
258352
        aarSum.push_back(mn);
169
258352
        mApprox.erase(v);
170
      }
171
      else
172
      {
173
        // compute monomial sum form for each approximation, used below
174
31930
        for (const Node& aa : approx)
175
        {
176
21292
          if (approxMsums.find(aa) == approxMsums.end())
177
          {
178
            CVC5_UNUSED bool ret =
179
20280
                ArithMSum::getMonomialSum(aa, approxMsums[aa]);
180
20280
            Assert(ret);
181
          }
182
        }
183
10638
        changed = true;
184
      }
185
    }
186
  }
187
141077
  if (!changed)
188
  {
189
    // approximations had no effect, return
190
124153
    Trace("strings-ent-approx-debug") << "...no approximations" << std::endl;
191
124153
    return false;
192
  }
193
  // get the current "fixed" sum for the abstraction of ar
194
16924
  Node aar = aarSum.empty()
195
18361
                 ? nm->mkConst(Rational(0))
196
35285
                 : (aarSum.size() == 1 ? aarSum[0] : nm->mkNode(PLUS, aarSum));
197
16924
  aar = Rewriter::rewrite(aar);
198
33848
  Trace("strings-ent-approx-debug")
199
33848
      << "...processed fixed sum " << aar << " with " << mApprox.size()
200
16924
      << " approximated monomials." << std::endl;
201
  // if we have a choice of how to approximate
202
16924
  if (!mApprox.empty())
203
  {
204
    // convert aar back to monomial sum
205
13912
    std::map<Node, Node> msumAar;
206
6956
    if (!ArithMSum::getMonomialSum(aar, msumAar))
207
    {
208
      return false;
209
    }
210
6956
    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
13912
    Rational one(1);
232
    // incorporate monomials one at a time that have a choice of approximations
233
21026
    while (!mApprox.empty())
234
    {
235
14070
      Node v;
236
14070
      Node vapprox;
237
7035
      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
7035
      for (std::pair<const Node, std::vector<Node> >& nam : mApprox)
250
      {
251
7035
        Node cr = msum[nam.first];
252
21121
        for (const Node& aa : nam.second)
253
        {
254
14086
          unsigned helpsCancelCount = 0;
255
14086
          unsigned addsObligationCount = 0;
256
14086
          std::map<Node, Node>::iterator it;
257
          // we are processing an approximation cr*( c1*t1 + ... + cn*tn )
258
31320
          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
34468
            Node ti = aam.first;
265
34468
            Node ci = aam.second;
266
17234
            if (!cr.isNull())
267
            {
268
26360
              ci = ci.isNull() ? cr
269
26360
                               : Rewriter::rewrite(nm->mkNode(MULT, ci, cr));
270
            }
271
17234
            Trace("strings-ent-approx-debug") << ci << "*" << ti << " ";
272
17234
            int ciSgn = ci.isNull() ? 1 : ci.getConst<Rational>().sgn();
273
17234
            it = msumAar.find(ti);
274
17234
            if (it != msumAar.end())
275
            {
276
18632
              Node c = it->second;
277
9316
              int cSgn = c.isNull() ? 1 : c.getConst<Rational>().sgn();
278
9316
              if (cSgn == 0)
279
              {
280
1887
                addsObligationCount += (ciSgn == -1 ? 1 : 0);
281
              }
282
7429
              else if (cSgn != ciSgn)
283
              {
284
6228
                helpsCancelCount++;
285
12456
                Rational r1 = c.isNull() ? one : c.getConst<Rational>();
286
12456
                Rational r2 = ci.isNull() ? one : ci.getConst<Rational>();
287
12456
                Rational r12 = r1 + r2;
288
6228
                if (r12.sgn() == -1)
289
                {
290
4021
                  addsObligationCount++;
291
                }
292
              }
293
            }
294
            else
295
            {
296
7918
              addsObligationCount += (ciSgn == -1 ? 1 : 0);
297
            }
298
          }
299
28172
          Trace("strings-ent-approx-debug")
300
14086
              << "counts=" << helpsCancelCount << "," << addsObligationCount
301
14086
              << " for " << aa << " into " << aar << std::endl;
302
28172
          int score = (addsObligationCount > 0 ? 0 : 2)
303
14086
                      + (helpsCancelCount > 0 ? 1 : 0);
304
          // if its the best, update v and vapprox
305
14086
          if (v.isNull() || score > maxScore)
306
          {
307
9592
            v = nam.first;
308
9592
            vapprox = aa;
309
9592
            maxScore = score;
310
          }
311
        }
312
7035
        if (!v.isNull())
313
        {
314
7035
          break;
315
        }
316
      }
317
14070
      Trace("strings-ent-approx")
318
7035
          << "- Decide " << v << " = " << vapprox << std::endl;
319
      // we incorporate v approximated by vapprox into the overall approximation
320
      // for ar
321
7035
      Assert(!v.isNull() && !vapprox.isNull());
322
7035
      Assert(msum.find(v) != msum.end());
323
14070
      Node mn = ArithMSum::mkCoeffTerm(msum[v], vapprox);
324
7035
      aar = nm->mkNode(PLUS, aar, mn);
325
      // update the msumAar map
326
7035
      aar = Rewriter::rewrite(aar);
327
7035
      msumAar.clear();
328
7035
      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
7035
      mApprox.erase(v);
337
    }
338
6956
    Trace("strings-ent-approx") << "-----------------" << std::endl;
339
  }
340
16924
  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
16924
  if (check(aar))
357
  {
358
3230
    Trace("strings-ent-approx")
359
1615
        << "*** StrArithApprox: showed " << ar
360
1615
        << " >= 0 using under-approximation!" << std::endl;
361
3230
    Trace("strings-ent-approx")
362
1615
        << "*** StrArithApprox: under-approximation was " << aar << std::endl;
363
1615
    return true;
364
  }
365
15309
  return false;
366
}
367
368
313585
void ArithEntail::getArithApproximations(Node a,
369
                                         std::vector<Node>& approx,
370
                                         bool isOverApprox)
371
{
372
313585
  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
627170
  Trace("strings-ent-approx-debug")
377
313585
      << "Get arith approximations " << a << std::endl;
378
313585
  Kind ak = a.getKind();
379
313585
  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
313024
  else if (ak == STRING_LENGTH)
394
  {
395
279360
    Kind aak = a[0].getKind();
396
279360
    if (aak == STRING_SUBSTR)
397
    {
398
      // over,under-approximations for len( substr( x, n, m ) )
399
72406
      Node lenx = nm->mkNode(STRING_LENGTH, a[0][0]);
400
36203
      if (isOverApprox)
401
      {
402
        // m >= 0 implies
403
        //  m >= len( substr( x, n, m ) )
404
20848
        if (check(a[0][2]))
405
        {
406
12697
          approx.push_back(a[0][2]);
407
        }
408
20848
        if (check(lenx, a[0][1]))
409
        {
410
          // n <= len( x ) implies
411
          //   len( x ) - n >= len( substr( x, n, m ) )
412
8108
          approx.push_back(nm->mkNode(MINUS, lenx, a[0][1]));
413
        }
414
        else
415
        {
416
          // len( x ) >= len( substr( x, n, m ) )
417
12740
          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
30710
        Node npm = nm->mkNode(PLUS, a[0][1], a[0][2]);
425
15355
        if (check(a[0][1]) && check(lenx, npm))
426
        {
427
2652
          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
15355
        if (check(a[0][1]) && check(npm, lenx))
432
        {
433
1606
          approx.push_back(nm->mkNode(MINUS, lenx, a[0][1]));
434
        }
435
      }
436
    }
437
243157
    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
237868
    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
33664
  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
29430
  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
313585
  Trace("strings-ent-approx-debug") << "Return " << approx.size() << std::endl;
554
313585
}
555
556
50370
bool ArithEntail::checkWithEqAssumption(Node assumption, Node a, bool strict)
557
{
558
50370
  Assert(assumption.getKind() == kind::EQUAL);
559
50370
  Assert(Rewriter::rewrite(assumption) == assumption);
560
100740
  Trace("strings-entail") << "checkWithEqAssumption: " << assumption << " " << a
561
50370
                          << ", strict=" << strict << std::endl;
562
563
  // Find candidates variables to compute substitutions for
564
100740
  std::unordered_set<Node> candVars;
565
100740
  std::vector<Node> toVisit = {assumption};
566
623296
  while (!toVisit.empty())
567
  {
568
572926
    Node curr = toVisit.back();
569
286463
    toVisit.pop_back();
570
571
817664
    if (curr.getKind() == kind::PLUS || curr.getKind() == kind::MULT
572
514601
        || curr.getKind() == kind::MINUS || curr.getKind() == kind::EQUAL)
573
    {
574
344788
      for (const auto& currChild : curr)
575
      {
576
236093
        toVisit.push_back(currChild);
577
      }
578
    }
579
177768
    else if (curr.isVar() && Theory::theoryOf(curr) == THEORY_ARITH)
580
    {
581
8806
      candVars.insert(curr);
582
    }
583
168962
    else if (curr.getKind() == kind::STRING_LENGTH)
584
    {
585
105672
      candVars.insert(curr);
586
    }
587
  }
588
589
  // Check if any of the candidate variables are in n
590
100740
  Node v;
591
50370
  Assert(toVisit.empty());
592
50370
  toVisit.push_back(a);
593
502682
  while (!toVisit.empty())
594
  {
595
473746
    Node curr = toVisit.back();
596
247590
    toVisit.pop_back();
597
598
505003
    for (const auto& currChild : curr)
599
    {
600
257413
      toVisit.push_back(currChild);
601
    }
602
603
247590
    if (candVars.find(curr) != candVars.end())
604
    {
605
21434
      v = curr;
606
21434
      break;
607
    }
608
  }
609
610
50370
  if (v.isNull())
611
  {
612
    // No suitable candidate found
613
28936
    return false;
614
  }
615
616
42868
  Node solution = ArithMSum::solveEqualityFor(assumption, v);
617
21434
  if (solution.isNull())
618
  {
619
    // Could not solve for v
620
80
    return false;
621
  }
622
42708
  Trace("strings-entail") << "checkWithEqAssumption: subs " << v << " -> "
623
21354
                          << solution << std::endl;
624
625
  // use capture avoiding substitution
626
21354
  a = expr::substituteCaptureAvoiding(a, v, solution);
627
21354
  return check(a, strict);
628
}
629
630
74657
bool ArithEntail::checkWithAssumption(Node assumption,
631
                                      Node a,
632
                                      Node b,
633
                                      bool strict)
634
{
635
74657
  Assert(Rewriter::rewrite(assumption) == assumption);
636
637
74657
  NodeManager* nm = NodeManager::currentNM();
638
639
74657
  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
100724
    Node x, y;
645
50362
    if (assumption.getKind() == kind::GEQ)
646
    {
647
41352
      x = assumption[0];
648
41352
      y = assumption[1];
649
    }
650
    else
651
    {
652
      // (not (>= s t)) --> (>= (t - 1) s)
653
9010
      Assert(assumption.getKind() == kind::NOT
654
             && assumption[0].getKind() == kind::GEQ);
655
9010
      x = nm->mkNode(kind::MINUS, assumption[0][1], nm->mkConst(Rational(1)));
656
9010
      y = assumption[0][0];
657
    }
658
659
100724
    Node s = nm->mkBoundVar("slackVal", nm->stringType());
660
100724
    Node slen = nm->mkNode(kind::STRING_LENGTH, s);
661
50362
    assumption = Rewriter::rewrite(
662
100724
        nm->mkNode(kind::EQUAL, x, nm->mkNode(kind::PLUS, y, slen)));
663
  }
664
665
149314
  Node diff = nm->mkNode(kind::MINUS, a, b);
666
74657
  bool res = false;
667
74657
  if (assumption.isConst())
668
  {
669
24287
    bool assumptionBool = assumption.getConst<bool>();
670
24287
    if (assumptionBool)
671
    {
672
24287
      res = check(diff, strict);
673
    }
674
    else
675
    {
676
      res = true;
677
    }
678
  }
679
  else
680
  {
681
50370
    res = checkWithEqAssumption(assumption, diff, strict);
682
  }
683
149314
  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
25132
Node ArithEntail::getConstantBound(Node a, bool isLower)
709
{
710
25132
  Assert(Rewriter::rewrite(a) == a);
711
25132
  Node ret;
712
25132
  if (a.isConst())
713
  {
714
9915
    ret = a;
715
  }
716
15217
  else if (a.getKind() == kind::STRING_LENGTH)
717
  {
718
3008
    if (isLower)
719
    {
720
3008
      ret = NodeManager::currentNM()->mkConst(Rational(0));
721
    }
722
  }
723
12209
  else if (a.getKind() == kind::PLUS || a.getKind() == kind::MULT)
724
  {
725
17438
    std::vector<Node> children;
726
8719
    bool success = true;
727
16356
    for (unsigned i = 0; i < a.getNumChildren(); i++)
728
    {
729
22716
      Node ac = getConstantBound(a[i], isLower);
730
15079
      if (ac.isNull())
731
      {
732
4049
        ret = ac;
733
4049
        success = false;
734
4049
        break;
735
      }
736
      else
737
      {
738
11030
        if (ac.getConst<Rational>().sgn() == 0)
739
        {
740
2719
          if (a.getKind() == kind::MULT)
741
          {
742
4
            ret = ac;
743
4
            success = false;
744
4
            break;
745
          }
746
        }
747
        else
748
        {
749
8311
          if (a.getKind() == kind::MULT)
750
          {
751
3393
            if ((ac.getConst<Rational>().sgn() > 0) != isLower)
752
            {
753
3389
              ret = Node::null();
754
3389
              success = false;
755
3389
              break;
756
            }
757
          }
758
4922
          children.push_back(ac);
759
        }
760
      }
761
    }
762
8719
    if (success)
763
    {
764
1277
      if (children.empty())
765
      {
766
268
        ret = NodeManager::currentNM()->mkConst(Rational(0));
767
      }
768
1009
      else if (children.size() == 1)
769
      {
770
1009
        ret = children[0];
771
      }
772
      else
773
      {
774
        ret = NodeManager::currentNM()->mkNode(a.getKind(), children);
775
        ret = Rewriter::rewrite(ret);
776
      }
777
    }
778
  }
779
50264
  Trace("strings-rewrite-cbound")
780
25132
      << "Constant " << (isLower ? "lower" : "upper") << " bound for " << a
781
25132
      << " is " << ret << std::endl;
782
25132
  Assert(ret.isNull() || ret.isConst());
783
  // entailment check should be at least as powerful as computing a lower bound
784
25132
  Assert(!isLower || ret.isNull() || ret.getConst<Rational>().sgn() < 0
785
         || check(a, false));
786
25132
  Assert(!isLower || ret.isNull() || ret.getConst<Rational>().sgn() <= 0
787
         || check(a, true));
788
25132
  return ret;
789
}
790
791
394604
bool ArithEntail::checkInternal(Node a)
792
{
793
394604
  Assert(Rewriter::rewrite(a) == a);
794
  // check whether a >= 0
795
394604
  if (a.isConst())
796
  {
797
166721
    return a.getConst<Rational>().sgn() >= 0;
798
  }
799
227883
  else if (a.getKind() == kind::STRING_LENGTH)
800
  {
801
    // str.len( t ) >= 0
802
32824
    return true;
803
  }
804
195059
  else if (a.getKind() == kind::PLUS || a.getKind() == kind::MULT)
805
  {
806
244701
    for (unsigned i = 0; i < a.getNumChildren(); i++)
807
    {
808
242980
      if (!checkInternal(a[i]))
809
      {
810
190939
        return false;
811
      }
812
    }
813
    // t1 >= 0 ^ ... ^ tn >= 0 => t1 op ... op tn >= 0
814
1721
    return true;
815
  }
816
817
2399
  return false;
818
}
819
820
1348
bool ArithEntail::inferZerosInSumGeq(Node x,
821
                                     std::vector<Node>& ys,
822
                                     std::vector<Node>& zeroYs)
823
{
824
1348
  Assert(zeroYs.empty());
825
826
1348
  NodeManager* nm = NodeManager::currentNM();
827
828
  // Check if we can show that y1 + ... + yn >= x
829
2696
  Node sum = (ys.size() > 1) ? nm->mkNode(PLUS, ys) : ys[0];
830
1348
  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
912
  size_t i = 0;
842
5760
  while (i < ys.size())
843
  {
844
4848
    Node yi = ys[i];
845
2424
    std::vector<Node>::iterator pos = ys.erase(ys.begin() + i);
846
2424
    if (ys.size() > 1)
847
    {
848
912
      sum = nm->mkNode(PLUS, ys);
849
    }
850
    else
851
    {
852
1512
      sum = ys.size() == 1 ? ys[0] : nm->mkConst(Rational(0));
853
    }
854
855
2424
    if (check(sum, x))
856
    {
857
880
      zeroYs.push_back(yi);
858
    }
859
    else
860
    {
861
1544
      ys.insert(pos, yi);
862
1544
      i++;
863
    }
864
  }
865
912
  return true;
866
}
867
868
}  // namespace strings
869
}  // namespace theory
870
28191
}  // namespace cvc5