GCC Code Coverage Report
Directory: . Exec Total Coverage
File: src/theory/strings/arith_entail.cpp Lines: 344 384 89.6 %
Date: 2021-09-10 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
1841
bool ArithEntail::checkEq(Node a, Node b)
34
{
35
1841
  if (a == b)
36
  {
37
    return true;
38
  }
39
3682
  Node ar = Rewriter::rewrite(a);
40
3682
  Node br = Rewriter::rewrite(b);
41
1841
  return ar == br;
42
}
43
44
469242
bool ArithEntail::check(Node a, Node b, bool strict)
45
{
46
469242
  if (a == b)
47
  {
48
24320
    return !strict;
49
  }
50
889844
  Node diff = NodeManager::currentNM()->mkNode(kind::MINUS, a, b);
51
444922
  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
824973
bool ArithEntail::check(Node a, bool strict)
66
{
67
824973
  if (a.isConst())
68
  {
69
93690
    return a.getConst<Rational>().sgn() >= (strict ? 1 : 0);
70
  }
71
72
  Node ar = strict ? NodeManager::currentNM()->mkNode(
73
947904
                kind::MINUS, a, NodeManager::currentNM()->mkConst(Rational(1)))
74
1679187
                   : a;
75
731283
  ar = Rewriter::rewrite(ar);
76
77
731283
  if (ar.getAttribute(StrCheckEntailArithComputedAttr()))
78
  {
79
618163
    return ar.getAttribute(StrCheckEntailArithAttr());
80
  }
81
82
113120
  bool ret = checkInternal(ar);
83
113120
  if (!ret)
84
  {
85
    // try with approximations
86
106024
    ret = checkApprox(ar);
87
  }
88
  // cache the result
89
113120
  ar.setAttribute(StrCheckEntailArithAttr(), ret);
90
113120
  ar.setAttribute(StrCheckEntailArithComputedAttr(), true);
91
113120
  return ret;
92
}
93
94
106024
bool ArithEntail::checkApprox(Node ar)
95
{
96
106024
  Assert(Rewriter::rewrite(ar) == ar);
97
106024
  NodeManager* nm = NodeManager::currentNM();
98
212048
  std::map<Node, Node> msum;
99
212048
  Trace("strings-ent-approx-debug")
100
106024
      << "Setup arithmetic approximations for " << ar << std::endl;
101
106024
  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
106024
  bool changed = false;
114
212048
  std::map<Node, std::vector<Node> > mApprox;
115
  // map from approximations to their monomial sums
116
212048
  std::map<Node, std::map<Node, Node> > approxMsums;
117
  // aarSum stores each monomial that does not have multiple approximations
118
212048
  std::vector<Node> aarSum;
119
342935
  for (std::pair<const Node, Node>& m : msum)
120
  {
121
473822
    Node v = m.first;
122
473822
    Node c = m.second;
123
473822
    Trace("strings-ent-approx-debug")
124
236911
        << "Get approximations " << v << "..." << std::endl;
125
236911
    if (v.isNull())
126
    {
127
162080
      Node mn = c.isNull() ? nm->mkConst(Rational(1)) : c;
128
81040
      aarSum.push_back(mn);
129
    }
130
    else
131
    {
132
      // c.isNull() means c = 1
133
155871
      bool isOverApprox = !c.isNull() && c.getConst<Rational>().sgn() == -1;
134
155871
      std::vector<Node>& approx = mApprox[v];
135
311742
      std::unordered_set<Node> visited;
136
311742
      std::vector<Node> toProcess;
137
155871
      toProcess.push_back(v);
138
44971
      do
139
      {
140
401684
        Node curr = toProcess.back();
141
200842
        Trace("strings-ent-approx-debug") << "  process " << curr << std::endl;
142
200842
        curr = Rewriter::rewrite(curr);
143
200842
        toProcess.pop_back();
144
200842
        if (visited.find(curr) == visited.end())
145
        {
146
197697
          visited.insert(curr);
147
395394
          std::vector<Node> currApprox;
148
197697
          getArithApproximations(curr, currApprox, isOverApprox);
149
197697
          if (currApprox.empty())
150
          {
151
331848
            Trace("strings-ent-approx-debug")
152
165924
                << "...approximation: " << curr << std::endl;
153
            // no approximations, thus curr is a possibility
154
165924
            approx.push_back(curr);
155
          }
156
          else
157
          {
158
31773
            toProcess.insert(
159
63546
                toProcess.end(), currApprox.begin(), currApprox.end());
160
          }
161
        }
162
200842
      } while (!toProcess.empty());
163
155871
      Assert(!approx.empty());
164
      // if we have only one approximation, move it to final
165
155871
      if (approx.size() == 1)
166
      {
167
145996
        changed = v != approx[0];
168
291992
        Node mn = ArithMSum::mkCoeffTerm(c, approx[0]);
169
145996
        aarSum.push_back(mn);
170
145996
        mApprox.erase(v);
171
      }
172
      else
173
      {
174
        // compute monomial sum form for each approximation, used below
175
29803
        for (const Node& aa : approx)
176
        {
177
19928
          if (approxMsums.find(aa) == approxMsums.end())
178
          {
179
            CVC5_UNUSED bool ret =
180
19647
                ArithMSum::getMonomialSum(aa, approxMsums[aa]);
181
19647
            Assert(ret);
182
          }
183
        }
184
9875
        changed = true;
185
      }
186
    }
187
  }
188
106024
  if (!changed)
189
  {
190
    // approximations had no effect, return
191
86620
    Trace("strings-ent-approx-debug") << "...no approximations" << std::endl;
192
86620
    return false;
193
  }
194
  // get the current "fixed" sum for the abstraction of ar
195
19404
  Node aar = aarSum.empty()
196
21315
                 ? nm->mkConst(Rational(0))
197
40719
                 : (aarSum.size() == 1 ? aarSum[0] : nm->mkNode(PLUS, aarSum));
198
19404
  aar = Rewriter::rewrite(aar);
199
38808
  Trace("strings-ent-approx-debug")
200
38808
      << "...processed fixed sum " << aar << " with " << mApprox.size()
201
19404
      << " approximated monomials." << std::endl;
202
  // if we have a choice of how to approximate
203
19404
  if (!mApprox.empty())
204
  {
205
    // convert aar back to monomial sum
206
16306
    std::map<Node, Node> msumAar;
207
8153
    if (!ArithMSum::getMonomialSum(aar, msumAar))
208
    {
209
      return false;
210
    }
211
8153
    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
16306
    Rational one(1);
233
    // incorporate monomials one at a time that have a choice of approximations
234
24541
    while (!mApprox.empty())
235
    {
236
16388
      Node v;
237
16388
      Node vapprox;
238
8194
      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
8194
      for (std::pair<const Node, std::vector<Node> >& nam : mApprox)
251
      {
252
8194
        Node cr = msum[nam.first];
253
24722
        for (const Node& aa : nam.second)
254
        {
255
16528
          unsigned helpsCancelCount = 0;
256
16528
          unsigned addsObligationCount = 0;
257
16528
          std::map<Node, Node>::iterator it;
258
          // we are processing an approximation cr*( c1*t1 + ... + cn*tn )
259
38754
          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
44452
            Node ti = aam.first;
266
44452
            Node ci = aam.second;
267
22226
            if (!cr.isNull())
268
            {
269
33177
              ci = ci.isNull() ? cr
270
33177
                               : Rewriter::rewrite(nm->mkNode(MULT, ci, cr));
271
            }
272
22226
            Trace("strings-ent-approx-debug") << ci << "*" << ti << " ";
273
22226
            int ciSgn = ci.isNull() ? 1 : ci.getConst<Rational>().sgn();
274
22226
            it = msumAar.find(ti);
275
22226
            if (it != msumAar.end())
276
            {
277
22350
              Node c = it->second;
278
11175
              int cSgn = c.isNull() ? 1 : c.getConst<Rational>().sgn();
279
11175
              if (cSgn == 0)
280
              {
281
2409
                addsObligationCount += (ciSgn == -1 ? 1 : 0);
282
              }
283
8766
              else if (cSgn != ciSgn)
284
              {
285
7309
                helpsCancelCount++;
286
14618
                Rational r1 = c.isNull() ? one : c.getConst<Rational>();
287
14618
                Rational r2 = ci.isNull() ? one : ci.getConst<Rational>();
288
14618
                Rational r12 = r1 + r2;
289
7309
                if (r12.sgn() == -1)
290
                {
291
4696
                  addsObligationCount++;
292
                }
293
              }
294
            }
295
            else
296
            {
297
11051
              addsObligationCount += (ciSgn == -1 ? 1 : 0);
298
            }
299
          }
300
33056
          Trace("strings-ent-approx-debug")
301
16528
              << "counts=" << helpsCancelCount << "," << addsObligationCount
302
16528
              << " for " << aa << " into " << aar << std::endl;
303
33056
          int score = (addsObligationCount > 0 ? 0 : 2)
304
16528
                      + (helpsCancelCount > 0 ? 1 : 0);
305
          // if its the best, update v and vapprox
306
16528
          if (v.isNull() || score > maxScore)
307
          {
308
11161
            v = nam.first;
309
11161
            vapprox = aa;
310
11161
            maxScore = score;
311
          }
312
        }
313
8194
        if (!v.isNull())
314
        {
315
8194
          break;
316
        }
317
      }
318
16388
      Trace("strings-ent-approx")
319
8194
          << "- Decide " << v << " = " << vapprox << std::endl;
320
      // we incorporate v approximated by vapprox into the overall approximation
321
      // for ar
322
8194
      Assert(!v.isNull() && !vapprox.isNull());
323
8194
      Assert(msum.find(v) != msum.end());
324
16388
      Node mn = ArithMSum::mkCoeffTerm(msum[v], vapprox);
325
8194
      aar = nm->mkNode(PLUS, aar, mn);
326
      // update the msumAar map
327
8194
      aar = Rewriter::rewrite(aar);
328
8194
      msumAar.clear();
329
8194
      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
8194
      mApprox.erase(v);
338
    }
339
8153
    Trace("strings-ent-approx") << "-----------------" << std::endl;
340
  }
341
19404
  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
19404
  if (check(aar))
358
  {
359
3580
    Trace("strings-ent-approx")
360
1790
        << "*** StrArithApprox: showed " << ar
361
1790
        << " >= 0 using under-approximation!" << std::endl;
362
3580
    Trace("strings-ent-approx")
363
1790
        << "*** StrArithApprox: under-approximation was " << aar << std::endl;
364
1790
    return true;
365
  }
366
17614
  return false;
367
}
368
369
198236
void ArithEntail::getArithApproximations(Node a,
370
                                         std::vector<Node>& approx,
371
                                         bool isOverApprox)
372
{
373
198236
  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
396472
  Trace("strings-ent-approx-debug")
378
198236
      << "Get arith approximations " << a << std::endl;
379
198236
  Kind ak = a.getKind();
380
198236
  if (ak == MULT)
381
  {
382
1078
    Node c;
383
1078
    Node v;
384
539
    if (ArithMSum::getMonomial(a, c, v))
385
    {
386
539
      bool isNeg = c.getConst<Rational>().sgn() == -1;
387
539
      getArithApproximations(v, approx, isNeg ? !isOverApprox : isOverApprox);
388
599
      for (unsigned i = 0, size = approx.size(); i < size; i++)
389
      {
390
60
        approx[i] = nm->mkNode(MULT, c, approx[i]);
391
      }
392
    }
393
  }
394
197697
  else if (ak == STRING_LENGTH)
395
  {
396
156824
    Kind aak = a[0].getKind();
397
156824
    if (aak == STRING_SUBSTR)
398
    {
399
      // over,under-approximations for len( substr( x, n, m ) )
400
62294
      Node lenx = nm->mkNode(STRING_LENGTH, a[0][0]);
401
31147
      if (isOverApprox)
402
      {
403
        // m >= 0 implies
404
        //  m >= len( substr( x, n, m ) )
405
19864
        if (check(a[0][2]))
406
        {
407
12243
          approx.push_back(a[0][2]);
408
        }
409
19864
        if (check(lenx, a[0][1]))
410
        {
411
          // n <= len( x ) implies
412
          //   len( x ) - n >= len( substr( x, n, m ) )
413
6939
          approx.push_back(nm->mkNode(MINUS, lenx, a[0][1]));
414
        }
415
        else
416
        {
417
          // len( x ) >= len( substr( x, n, m ) )
418
12925
          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
22566
        Node npm = nm->mkNode(PLUS, a[0][1], a[0][2]);
426
11283
        if (check(a[0][1]) && check(lenx, npm))
427
        {
428
1458
          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
11283
        if (check(a[0][1]) && check(npm, lenx))
433
        {
434
1066
          approx.push_back(nm->mkNode(MINUS, lenx, a[0][1]));
435
        }
436
      }
437
    }
438
125677
    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
7998
      Node lenx = nm->mkNode(STRING_LENGTH, a[0][0]);
443
7998
      Node leny = nm->mkNode(STRING_LENGTH, a[0][1]);
444
7998
      Node lenz = nm->mkNode(STRING_LENGTH, a[0][2]);
445
3999
      if (isOverApprox)
446
      {
447
2254
        if (check(leny, lenz))
448
        {
449
          // len( y ) >= len( z ) implies
450
          //   len( x ) >= len( replace( x, y, z ) )
451
617
          approx.push_back(lenx);
452
        }
453
        else
454
        {
455
          // len( x ) + len( z ) >= len( replace( x, y, z ) )
456
1637
          approx.push_back(nm->mkNode(PLUS, lenx, lenz));
457
        }
458
      }
459
      else
460
      {
461
1745
        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
957
          approx.push_back(lenx);
466
        }
467
        else
468
        {
469
          // len( x ) - len( y ) <= len( replace( x, y, z ) )
470
788
          approx.push_back(nm->mkNode(MINUS, lenx, leny));
471
        }
472
      }
473
    }
474
121678
    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
40873
  else if (ak == STRING_INDEXOF)
510
  {
511
    // over,under-approximations for indexof( x, y, n )
512
6243
    if (isOverApprox)
513
    {
514
6626
      Node lenx = nm->mkNode(STRING_LENGTH, a[0]);
515
6626
      Node leny = nm->mkNode(STRING_LENGTH, a[1]);
516
3313
      if (check(lenx, leny))
517
      {
518
        // len( x ) >= len( y ) implies
519
        //   len( x ) - len( y ) >= indexof( x, y, n )
520
20
        approx.push_back(nm->mkNode(MINUS, lenx, leny));
521
      }
522
      else
523
      {
524
        // len( x ) >= indexof( x, y, n )
525
3293
        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
2930
      approx.push_back(nm->mkConst(Rational(-1)));
537
    }
538
  }
539
34630
  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
198236
  Trace("strings-ent-approx-debug") << "Return " << approx.size() << std::endl;
555
198236
}
556
557
92892
bool ArithEntail::checkWithEqAssumption(Node assumption, Node a, bool strict)
558
{
559
92892
  Assert(assumption.getKind() == kind::EQUAL);
560
92892
  Assert(Rewriter::rewrite(assumption) == assumption);
561
185784
  Trace("strings-entail") << "checkWithEqAssumption: " << assumption << " " << a
562
92892
                          << ", strict=" << strict << std::endl;
563
564
  // Find candidates variables to compute substitutions for
565
185784
  std::unordered_set<Node> candVars;
566
185784
  std::vector<Node> toVisit = {assumption};
567
1197318
  while (!toVisit.empty())
568
  {
569
1104426
    Node curr = toVisit.back();
570
552213
    toVisit.pop_back();
571
572
1577976
    if (curr.getKind() == kind::PLUS || curr.getKind() == kind::MULT
573
989033
        || curr.getKind() == kind::MINUS || curr.getKind() == kind::EQUAL)
574
    {
575
667606
      for (const auto& currChild : curr)
576
      {
577
459321
        toVisit.push_back(currChild);
578
      }
579
    }
580
343928
    else if (curr.isVar() && Theory::theoryOf(curr) == THEORY_ARITH)
581
    {
582
15970
      candVars.insert(curr);
583
    }
584
327958
    else if (curr.getKind() == kind::STRING_LENGTH)
585
    {
586
200495
      candVars.insert(curr);
587
    }
588
  }
589
590
  // Check if any of the candidate variables are in n
591
185784
  Node v;
592
92892
  Assert(toVisit.empty());
593
92892
  toVisit.push_back(a);
594
1118158
  while (!toVisit.empty())
595
  {
596
1061228
    Node curr = toVisit.back();
597
548595
    toVisit.pop_back();
598
599
1107326
    for (const auto& currChild : curr)
600
    {
601
558731
      toVisit.push_back(currChild);
602
    }
603
604
548595
    if (candVars.find(curr) != candVars.end())
605
    {
606
35962
      v = curr;
607
35962
      break;
608
    }
609
  }
610
611
92892
  if (v.isNull())
612
  {
613
    // No suitable candidate found
614
56930
    return false;
615
  }
616
617
71924
  Node solution = ArithMSum::solveEqualityFor(assumption, v);
618
35962
  if (solution.isNull())
619
  {
620
    // Could not solve for v
621
78
    return false;
622
  }
623
71768
  Trace("strings-entail") << "checkWithEqAssumption: subs " << v << " -> "
624
35884
                          << solution << std::endl;
625
626
  // use capture avoiding substitution
627
35884
  a = expr::substituteCaptureAvoiding(a, v, solution);
628
35884
  return check(a, strict);
629
}
630
631
137538
bool ArithEntail::checkWithAssumption(Node assumption,
632
                                      Node a,
633
                                      Node b,
634
                                      bool strict)
635
{
636
137538
  Assert(Rewriter::rewrite(assumption) == assumption);
637
638
137538
  NodeManager* nm = NodeManager::currentNM();
639
640
137538
  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
185768
    Node x, y;
646
92884
    if (assumption.getKind() == kind::GEQ)
647
    {
648
77582
      x = assumption[0];
649
77582
      y = assumption[1];
650
    }
651
    else
652
    {
653
      // (not (>= s t)) --> (>= (t - 1) s)
654
15302
      Assert(assumption.getKind() == kind::NOT
655
             && assumption[0].getKind() == kind::GEQ);
656
15302
      x = nm->mkNode(kind::MINUS, assumption[0][1], nm->mkConst(Rational(1)));
657
15302
      y = assumption[0][0];
658
    }
659
660
185768
    Node s = nm->mkBoundVar("slackVal", nm->stringType());
661
185768
    Node slen = nm->mkNode(kind::STRING_LENGTH, s);
662
92884
    assumption = Rewriter::rewrite(
663
185768
        nm->mkNode(kind::EQUAL, x, nm->mkNode(kind::PLUS, y, slen)));
664
  }
665
666
275076
  Node diff = nm->mkNode(kind::MINUS, a, b);
667
137538
  bool res = false;
668
137538
  if (assumption.isConst())
669
  {
670
44646
    bool assumptionBool = assumption.getConst<bool>();
671
44646
    if (assumptionBool)
672
    {
673
44646
      res = check(diff, strict);
674
    }
675
    else
676
    {
677
      res = true;
678
    }
679
  }
680
  else
681
  {
682
92892
    res = checkWithEqAssumption(assumption, diff, strict);
683
  }
684
275076
  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
42093
Node ArithEntail::getConstantBound(Node a, bool isLower)
710
{
711
42093
  Assert(Rewriter::rewrite(a) == a);
712
42093
  Node ret;
713
42093
  if (a.isConst())
714
  {
715
18180
    ret = a;
716
  }
717
23913
  else if (a.getKind() == kind::STRING_LENGTH)
718
  {
719
4456
    if (isLower)
720
    {
721
4456
      ret = NodeManager::currentNM()->mkConst(Rational(0));
722
    }
723
  }
724
19457
  else if (a.getKind() == kind::PLUS || a.getKind() == kind::MULT)
725
  {
726
28452
    std::vector<Node> children;
727
14226
    bool success = true;
728
25908
    for (unsigned i = 0; i < a.getNumChildren(); i++)
729
    {
730
35498
      Node ac = getConstantBound(a[i], isLower);
731
23816
      if (ac.isNull())
732
      {
733
6546
        ret = ac;
734
6546
        success = false;
735
6546
        break;
736
      }
737
      else
738
      {
739
17270
        if (ac.getConst<Rational>().sgn() == 0)
740
        {
741
3892
          if (a.getKind() == kind::MULT)
742
          {
743
98
            ret = ac;
744
98
            success = false;
745
98
            break;
746
          }
747
        }
748
        else
749
        {
750
13378
          if (a.getKind() == kind::MULT)
751
          {
752
5592
            if ((ac.getConst<Rational>().sgn() > 0) != isLower)
753
            {
754
5490
              ret = Node::null();
755
5490
              success = false;
756
5490
              break;
757
            }
758
          }
759
7888
          children.push_back(ac);
760
        }
761
      }
762
    }
763
14226
    if (success)
764
    {
765
2092
      if (children.empty())
766
      {
767
402
        ret = NodeManager::currentNM()->mkConst(Rational(0));
768
      }
769
1690
      else if (children.size() == 1)
770
      {
771
1690
        ret = children[0];
772
      }
773
      else
774
      {
775
        ret = NodeManager::currentNM()->mkNode(a.getKind(), children);
776
        ret = Rewriter::rewrite(ret);
777
      }
778
    }
779
  }
780
84186
  Trace("strings-rewrite-cbound")
781
42093
      << "Constant " << (isLower ? "lower" : "upper") << " bound for " << a
782
42093
      << " is " << ret << std::endl;
783
42093
  Assert(ret.isNull() || ret.isConst());
784
  // entailment check should be at least as powerful as computing a lower bound
785
42093
  Assert(!isLower || ret.isNull() || ret.getConst<Rational>().sgn() < 0
786
         || check(a, false));
787
42093
  Assert(!isLower || ret.isNull() || ret.getConst<Rational>().sgn() <= 0
788
         || check(a, true));
789
42093
  return ret;
790
}
791
792
276123
bool ArithEntail::checkInternal(Node a)
793
{
794
276123
  Assert(Rewriter::rewrite(a) == a);
795
  // check whether a >= 0
796
276123
  if (a.isConst())
797
  {
798
124593
    return a.getConst<Rational>().sgn() >= 0;
799
  }
800
151530
  else if (a.getKind() == kind::STRING_LENGTH)
801
  {
802
    // str.len( t ) >= 0
803
12013
    return true;
804
  }
805
139517
  else if (a.getKind() == kind::PLUS || a.getKind() == kind::MULT)
806
  {
807
165875
    for (unsigned i = 0; i < a.getNumChildren(); i++)
808
    {
809
163003
      if (!checkInternal(a[i]))
810
      {
811
132040
        return false;
812
      }
813
    }
814
    // t1 >= 0 ^ ... ^ tn >= 0 => t1 op ... op tn >= 0
815
2872
    return true;
816
  }
817
818
4605
  return false;
819
}
820
821
926
bool ArithEntail::inferZerosInSumGeq(Node x,
822
                                     std::vector<Node>& ys,
823
                                     std::vector<Node>& zeroYs)
824
{
825
926
  Assert(zeroYs.empty());
826
827
926
  NodeManager* nm = NodeManager::currentNM();
828
829
  // Check if we can show that y1 + ... + yn >= x
830
1852
  Node sum = (ys.size() > 1) ? nm->mkNode(PLUS, ys) : ys[0];
831
926
  if (!check(sum, x))
832
  {
833
824
    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
102
  size_t i = 0;
843
546
  while (i < ys.size())
844
  {
845
444
    Node yi = ys[i];
846
222
    std::vector<Node>::iterator pos = ys.erase(ys.begin() + i);
847
222
    if (ys.size() > 1)
848
    {
849
66
      sum = nm->mkNode(PLUS, ys);
850
    }
851
    else
852
    {
853
156
      sum = ys.size() == 1 ? ys[0] : nm->mkConst(Rational(0));
854
    }
855
856
222
    if (check(sum, x))
857
    {
858
44
      zeroYs.push_back(yi);
859
    }
860
    else
861
    {
862
178
      ys.insert(pos, yi);
863
178
      i++;
864
    }
865
  }
866
102
  return true;
867
}
868
869
}  // namespace strings
870
}  // namespace theory
871
29502
}  // namespace cvc5