GCC Code Coverage Report
Directory: . Exec Total Coverage
File: src/theory/strings/extf_solver.cpp Lines: 304 361 84.2 %
Date: 2021-11-05 Branches: 789 1786 44.2 %

Line Exec Source
1
/******************************************************************************
2
 * Top contributors (to current version):
3
 *   Andrew Reynolds, Gereon Kremer, Andres Noetzli
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 solver for extended functions of theory of strings.
14
 */
15
16
#include "theory/strings/extf_solver.h"
17
18
#include "options/strings_options.h"
19
#include "theory/strings/sequences_rewriter.h"
20
#include "theory/strings/theory_strings_preprocess.h"
21
#include "theory/strings/theory_strings_utils.h"
22
#include "util/statistics_registry.h"
23
24
using namespace std;
25
using namespace cvc5::context;
26
using namespace cvc5::kind;
27
28
namespace cvc5 {
29
namespace theory {
30
namespace strings {
31
32
15271
ExtfSolver::ExtfSolver(Env& env,
33
                       SolverState& s,
34
                       InferenceManager& im,
35
                       TermRegistry& tr,
36
                       StringsRewriter& rewriter,
37
                       BaseSolver& bs,
38
                       CoreSolver& cs,
39
                       ExtTheory& et,
40
15271
                       SequencesStatistics& statistics)
41
    : EnvObj(env),
42
      d_state(s),
43
      d_im(im),
44
      d_termReg(tr),
45
      d_rewriter(rewriter),
46
      d_bsolver(bs),
47
      d_csolver(cs),
48
      d_extt(et),
49
      d_statistics(statistics),
50
15271
      d_preproc(d_termReg.getSkolemCache(), &statistics.d_reductions),
51
      d_hasExtf(context(), false),
52
      d_extfInferCache(context()),
53
30542
      d_reduced(userContext())
54
{
55
15271
  d_extt.addFunctionKind(kind::STRING_SUBSTR);
56
15271
  d_extt.addFunctionKind(kind::STRING_UPDATE);
57
15271
  d_extt.addFunctionKind(kind::STRING_INDEXOF);
58
15271
  d_extt.addFunctionKind(kind::STRING_INDEXOF_RE);
59
15271
  d_extt.addFunctionKind(kind::STRING_ITOS);
60
15271
  d_extt.addFunctionKind(kind::STRING_STOI);
61
15271
  d_extt.addFunctionKind(kind::STRING_REPLACE);
62
15271
  d_extt.addFunctionKind(kind::STRING_REPLACE_ALL);
63
15271
  d_extt.addFunctionKind(kind::STRING_REPLACE_RE);
64
15271
  d_extt.addFunctionKind(kind::STRING_REPLACE_RE_ALL);
65
15271
  d_extt.addFunctionKind(kind::STRING_CONTAINS);
66
15271
  d_extt.addFunctionKind(kind::STRING_IN_REGEXP);
67
15271
  d_extt.addFunctionKind(kind::STRING_LEQ);
68
15271
  d_extt.addFunctionKind(kind::STRING_TO_CODE);
69
15271
  d_extt.addFunctionKind(kind::STRING_TOLOWER);
70
15271
  d_extt.addFunctionKind(kind::STRING_TOUPPER);
71
15271
  d_extt.addFunctionKind(kind::STRING_REV);
72
15271
  d_extt.addFunctionKind(kind::SEQ_UNIT);
73
15271
  d_extt.addFunctionKind(kind::SEQ_NTH);
74
75
15271
  d_true = NodeManager::currentNM()->mkConst(true);
76
15271
  d_false = NodeManager::currentNM()->mkConst(false);
77
15271
}
78
79
15266
ExtfSolver::~ExtfSolver() {}
80
81
215540
bool ExtfSolver::doReduction(int effort, Node n)
82
{
83
215540
  Assert(d_extfInfoTmp.find(n) != d_extfInfoTmp.end());
84
215540
  if (!d_extfInfoTmp[n].d_modelActive)
85
  {
86
    // n is not active in the model, no need to reduce
87
  Trace("strings-extf-debug") << "...skip due to model active" << std::endl;
88
    return false;
89
  }
90
215540
  if (d_reduced.find(n)!=d_reduced.end())
91
  {
92
    // already sent a reduction lemma
93
138950
    Trace("strings-extf-debug") << "...skip due to reduced" << std::endl;
94
138950
    return false;
95
  }
96
  // determine the effort level to process the extf at
97
  // 0 - at assertion time, 1+ - after no other reduction is applicable
98
76590
  int r_effort = -1;
99
  // polarity : 1 true, -1 false, 0 neither
100
76590
  int pol = 0;
101
76590
  Kind k = n.getKind();
102
76590
  if (n.getType().isBoolean() && !d_extfInfoTmp[n].d_const.isNull())
103
  {
104
36415
    pol = d_extfInfoTmp[n].d_const.getConst<bool>() ? 1 : -1;
105
  }
106
76590
  if (k == STRING_CONTAINS)
107
  {
108
12619
    if (pol == 1)
109
    {
110
4076
      r_effort = 1;
111
    }
112
8543
    else if (pol == -1)
113
    {
114
7053
      if (effort == 2)
115
      {
116
216
        Node x = n[0];
117
216
        Node s = n[1];
118
216
        std::vector<Node> lexp;
119
216
        Node lenx = d_state.getLength(x, lexp);
120
216
        Node lens = d_state.getLength(s, lexp);
121
116
        if (d_state.areEqual(lenx, lens))
122
        {
123
32
          Trace("strings-extf-debug")
124
16
              << "  resolve extf : " << n
125
16
              << " based on equal lengths disequality." << std::endl;
126
          // We can reduce negative contains to a disequality when lengths are
127
          // equal. In other words, len( x ) = len( s ) implies
128
          //   ~contains( x, s ) reduces to x != s.
129
16
          if (!d_state.areDisequal(x, s))
130
          {
131
            // len( x ) = len( s ) ^ ~contains( x, s ) => x != s
132
8
            lexp.push_back(lenx.eqNode(lens));
133
8
            lexp.push_back(n.negate());
134
16
            Node xneqs = x.eqNode(s).negate();
135
8
            d_im.sendInference(
136
                lexp, xneqs, InferenceId::STRINGS_CTN_NEG_EQUAL, false, true);
137
          }
138
          // this depends on the current assertions, so this
139
          // inference is context-dependent
140
16
          d_extt.markReduced(n, ExtReducedId::STRINGS_NEG_CTN_DEQ, true);
141
16
          return true;
142
        }
143
        else
144
        {
145
100
          r_effort = 2;
146
        }
147
      }
148
    }
149
  }
150
63971
  else if (k == STRING_SUBSTR)
151
  {
152
1444
    r_effort = 1;
153
  }
154
62527
  else if (k == SEQ_UNIT)
155
  {
156
    // never necessary to reduce seq.unit
157
278
    return false;
158
  }
159
62249
  else if (k != STRING_IN_REGEXP)
160
  {
161
36465
    r_effort = 2;
162
  }
163
76296
  if (effort != r_effort)
164
  {
165
69014
    Trace("strings-extf-debug") << "...skip due to effort" << std::endl;
166
    // not the right effort level to reduce
167
69014
    return false;
168
  }
169
14564
  Node c_n = pol == -1 ? n.negate() : n;
170
14564
  Trace("strings-process-debug")
171
7282
      << "Process reduction for " << n << ", pol = " << pol << std::endl;
172
7282
  if (k == STRING_CONTAINS && pol == 1)
173
  {
174
8152
    Node x = n[0];
175
8152
    Node s = n[1];
176
    // positive contains reduces to a equality
177
4076
    SkolemCache* skc = d_termReg.getSkolemCache();
178
8152
    Node eq = d_termReg.eagerReduce(n, skc, d_termReg.getAlphabetCardinality());
179
4076
    Assert(!eq.isNull());
180
4076
    Assert(eq.getKind() == ITE && eq[0] == n);
181
4076
    eq = eq[1];
182
8152
    std::vector<Node> expn;
183
4076
    expn.push_back(n);
184
4076
    d_im.sendInference(expn, expn, eq, InferenceId::STRINGS_CTN_POS, false, true);
185
8152
    Trace("strings-extf-debug")
186
4076
        << "  resolve extf : " << n << " based on positive contain reduction."
187
4076
        << std::endl;
188
8152
    Trace("strings-red-lemma") << "Reduction (positive contains) lemma : " << n
189
4076
                               << " => " << eq << std::endl;
190
    // context-dependent because it depends on the polarity of n itself
191
8152
    d_extt.markReduced(n, ExtReducedId::STRINGS_POS_CTN, true);
192
  }
193
3206
  else if (k != kind::STRING_TO_CODE)
194
  {
195
2167
    NodeManager* nm = NodeManager::currentNM();
196
2167
    Assert(k == STRING_SUBSTR || k == STRING_UPDATE || k == STRING_CONTAINS
197
           || k == STRING_INDEXOF || k == STRING_INDEXOF_RE || k == STRING_ITOS
198
           || k == STRING_STOI || k == STRING_REPLACE || k == STRING_REPLACE_ALL
199
           || k == SEQ_NTH || k == STRING_REPLACE_RE
200
           || k == STRING_REPLACE_RE_ALL || k == STRING_LEQ
201
           || k == STRING_TOLOWER || k == STRING_TOUPPER || k == STRING_REV);
202
4334
    std::vector<Node> new_nodes;
203
4334
    Node res = d_preproc.simplify(n, new_nodes);
204
2167
    Assert(res != n);
205
2167
    new_nodes.push_back(n.eqNode(res));
206
    Node nnlem =
207
4334
        new_nodes.size() == 1 ? new_nodes[0] : nm->mkNode(AND, new_nodes);
208
4334
    Trace("strings-red-lemma")
209
2167
        << "Reduction_" << effort << " lemma : " << nnlem << std::endl;
210
2167
    Trace("strings-red-lemma") << "...from " << n << std::endl;
211
4334
    Trace("strings-red-lemma")
212
2167
        << "Reduction_" << effort << " rewritten : " << Rewriter::rewrite(nnlem) << std::endl;
213
2167
    d_im.sendInference(d_emptyVec, nnlem, InferenceId::STRINGS_REDUCTION, false, true);
214
4334
    Trace("strings-extf-debug")
215
2167
        << "  resolve extf : " << n << " based on reduction." << std::endl;
216
    // add as reduction lemma
217
2167
    d_reduced.insert(n);
218
  }
219
7282
  return true;
220
}
221
222
32088
void ExtfSolver::checkExtfReductions(int effort)
223
{
224
  // Notice we don't make a standard call to ExtTheory::doReductions here,
225
  // since certain optimizations like context-dependent reductions and
226
  // stratifying effort levels are done in doReduction below.
227
57925
  std::vector<Node> extf = d_extt.getActive();
228
64176
  Trace("strings-process") << "  checking " << extf.size() << " active extf"
229
32088
                           << std::endl;
230
241377
  for (const Node& n : extf)
231
  {
232
215540
    Assert(!d_state.isInConflict());
233
431080
    Trace("strings-extf-debug")
234
215540
        << "  check " << n
235
215540
        << ", active in model=" << d_extfInfoTmp[n].d_modelActive << std::endl;
236
215540
    bool ret = doReduction(effort, n);
237
215540
    if (ret)
238
    {
239
      // we do not mark as reduced, since we may want to evaluate
240
7298
      if (d_im.hasProcessed())
241
      {
242
12502
        return;
243
      }
244
    }
245
  }
246
}
247
248
51973
void ExtfSolver::checkExtfEval(int effort)
249
{
250
103946
  Trace("strings-extf-list")
251
51973
      << "Active extended functions, effort=" << effort << " : " << std::endl;
252
51973
  d_extfInfoTmp.clear();
253
51973
  NodeManager* nm = NodeManager::currentNM();
254
51973
  bool has_nreduce = false;
255
103193
  std::vector<Node> terms = d_extt.getActive();
256
  // the set of terms we have done extf inferences for
257
103193
  std::unordered_set<Node> inferProcessed;
258
580773
  for (const Node& n : terms)
259
  {
260
    // Setup information about n, including if it is equal to a constant.
261
529553
    ExtfInfoTmp& einfo = d_extfInfoTmp[n];
262
529553
    Assert(einfo.d_exp.empty());
263
1058353
    Node r = d_state.getRepresentative(n);
264
529553
    einfo.d_const = d_bsolver.getConstantEqc(r);
265
    // Get the current values of the children of n.
266
    // Notice that we look up the value of the direct children of n, and not
267
    // their free variables. In other words, given a term:
268
    //   t = (str.replace "B" (str.replace x "A" "B") "C")
269
    // we may build the explanation that:
270
    //   ((str.replace x "A" "B") = "B") => t = (str.replace "B" "B" "C")
271
    // instead of basing this on the free variable x:
272
    //   (x = "A") => t = (str.replace "B" (str.replace "A" "A" "B") "C")
273
    // Although both allow us to infer t = "C", it is important to use the
274
    // first kind of inference since it ensures that its subterms have the
275
    // expected values. Otherwise, we may in rare cases fail to realize that
276
    // the subterm (str.replace x "A" "B") does not currently have the correct
277
    // value, say in this example that (str.replace x "A" "B") != "B".
278
1058353
    std::vector<Node> exp;
279
1058353
    std::vector<Node> schildren;
280
529553
    bool schanged = false;
281
1730044
    for (const Node& nc : n)
282
    {
283
2400982
      Node sc = getCurrentSubstitutionFor(effort, nc, exp);
284
1200491
      schildren.push_back(sc);
285
1200491
      schanged = schanged || sc != nc;
286
    }
287
    // If there is information involving the children, attempt to do an
288
    // inference and/or mark n as reduced.
289
529553
    bool reduced = false;
290
1058353
    Node to_reduce = n;
291
529553
    if (schanged)
292
    {
293
512460
      Node sn = nm->mkNode(n.getKind(), schildren);
294
512460
      Trace("strings-extf-debug")
295
256230
          << "Check extf " << n << " == " << sn
296
256230
          << ", constant = " << einfo.d_const << ", effort=" << effort
297
256230
          << ", exp " << exp << std::endl;
298
256230
      einfo.d_exp.insert(einfo.d_exp.end(), exp.begin(), exp.end());
299
      // inference is rewriting the substituted node
300
512460
      Node nrc = Rewriter::rewrite(sn);
301
      // if rewrites to a constant, then do the inference and mark as reduced
302
256230
      if (nrc.isConst())
303
      {
304
142770
        if (effort < 3)
305
        {
306
142770
          d_extt.markReduced(n, ExtReducedId::STRINGS_SR_CONST);
307
285540
          Trace("strings-extf-debug")
308
142770
              << "  resolvable by evaluation..." << std::endl;
309
285540
          std::vector<Node> exps;
310
          // The following optimization gets the "symbolic definition" of
311
          // an extended term. The symbolic definition of a term t is a term
312
          // t' where constants are replaced by their corresponding proxy
313
          // variables.
314
          // For example, if lsym is a proxy variable for "", then
315
          // str.replace( lsym, lsym, lsym ) is the symbolic definition for
316
          // str.replace( "", "", "" ). It is generally better to use symbolic
317
          // definitions when doing cd-rewriting for the purpose of minimizing
318
          // clauses, e.g. we infer the unit equality:
319
          //    str.replace( lsym, lsym, lsym ) == ""
320
          // instead of making this inference multiple times:
321
          //    x = "" => str.replace( x, x, x ) == ""
322
          //    y = "" => str.replace( y, y, y ) == ""
323
285540
          Trace("strings-extf-debug")
324
142770
              << "  get symbolic definition..." << std::endl;
325
285540
          Node nrs;
326
          // only use symbolic definitions if option is set
327
142770
          if (options::stringInferSym())
328
          {
329
142770
            nrs = d_termReg.getSymbolicDefinition(sn, exps);
330
          }
331
142770
          if (!nrs.isNull())
332
          {
333
246086
            Trace("strings-extf-debug")
334
123043
                << "  rewrite " << nrs << "..." << std::endl;
335
246086
            Node nrsr = Rewriter::rewrite(nrs);
336
            // ensure the symbolic form is not rewritable
337
123043
            if (nrsr != nrs)
338
            {
339
              // we cannot use the symbolic definition if it rewrites
340
3836
              Trace("strings-extf-debug")
341
1918
                  << "  symbolic definition is trivial..." << std::endl;
342
1918
              nrs = Node::null();
343
            }
344
          }
345
          else
346
          {
347
39454
            Trace("strings-extf-debug")
348
19727
                << "  could not infer symbolic definition." << std::endl;
349
          }
350
285540
          Node conc;
351
142770
          if (!nrs.isNull())
352
          {
353
242250
            Trace("strings-extf-debug")
354
121125
                << "  symbolic def : " << nrs << std::endl;
355
121125
            if (!d_state.areEqual(nrs, nrc))
356
            {
357
              // infer symbolic unit
358
1717
              if (n.getType().isBoolean())
359
              {
360
1323
                conc = nrc == d_true ? nrs : nrs.negate();
361
              }
362
              else
363
              {
364
394
                conc = nrs.eqNode(nrc);
365
              }
366
1717
              einfo.d_exp.clear();
367
            }
368
          }
369
          else
370
          {
371
21645
            if (!d_state.areEqual(n, nrc))
372
            {
373
1448
              if (n.getType().isBoolean())
374
              {
375
714
                if (d_state.areEqual(n, nrc == d_true ? d_false : d_true))
376
                {
377
607
                  einfo.d_exp.push_back(nrc == d_true ? n.negate() : n);
378
607
                  conc = d_false;
379
                }
380
                else
381
                {
382
107
                  conc = nrc == d_true ? n : n.negate();
383
                }
384
              }
385
              else
386
              {
387
734
                conc = n.eqNode(nrc);
388
              }
389
            }
390
          }
391
142770
          if (!conc.isNull())
392
          {
393
6330
            Trace("strings-extf")
394
3165
                << "  resolve extf : " << sn << " -> " << nrc << std::endl;
395
3165
            InferenceId inf = effort == 0 ? InferenceId::STRINGS_EXTF : InferenceId::STRINGS_EXTF_N;
396
3165
            d_im.sendInference(einfo.d_exp, conc, inf, false, true);
397
3165
            d_statistics.d_cdSimplifications << n.getKind();
398
          }
399
        }
400
        else
401
        {
402
          // check if it is already equal, if so, mark as reduced. Otherwise, do
403
          // nothing.
404
          if (d_state.areEqual(n, nrc))
405
          {
406
            Trace("strings-extf")
407
                << "  resolved extf, since satisfied by model: " << n
408
                << std::endl;
409
            einfo.d_modelActive = false;
410
          }
411
        }
412
142770
        reduced = true;
413
      }
414
      else
415
      {
416
        // if this was a predicate which changed after substitution + rewriting
417
113460
        if (!einfo.d_const.isNull() && nrc.getType().isBoolean() && nrc != n)
418
        {
419
25210
          bool pol = einfo.d_const == d_true;
420
50420
          Node nrcAssert = pol ? nrc : nrc.negate();
421
50420
          Node nAssert = pol ? n : n.negate();
422
25210
          Assert(effort < 3);
423
25210
          einfo.d_exp.push_back(nAssert);
424
25210
          Trace("strings-extf-debug") << "  decomposable..." << std::endl;
425
50420
          Trace("strings-extf") << "  resolve extf : " << sn << " -> " << nrc
426
25210
                                << ", const = " << einfo.d_const << std::endl;
427
          // We send inferences internal here, which may help show unsat.
428
          // However, we do not make a determination whether n can be marked
429
          // reduced since this argument may be circular: we may infer than n
430
          // can be reduced to something else, but that thing may argue that it
431
          // can be reduced to n, in theory.
432
25210
          InferenceId infer =
433
25210
              effort == 0 ? InferenceId::STRINGS_EXTF_D : InferenceId::STRINGS_EXTF_D_N;
434
25210
          d_im.sendInternalInference(einfo.d_exp, nrcAssert, infer);
435
        }
436
113460
        to_reduce = nrc;
437
      }
438
    }
439
    // We must use the original n here to avoid circular justifications for
440
    // why extended functions are reduced. In particular, n should never be a
441
    // duplicate of another term considered in the block of code for
442
    // checkExtfInference below.
443
    // if not reduced and not processed
444
1445889
    if (!reduced && !n.isNull()
445
916336
        && inferProcessed.find(n) == inferProcessed.end())
446
    {
447
386783
      inferProcessed.insert(n);
448
386783
      Assert(effort < 3);
449
386783
      if (effort == 1)
450
      {
451
83620
        Trace("strings-extf")
452
41810
            << "  cannot rewrite extf : " << to_reduce << std::endl;
453
      }
454
      // we take to_reduce to be the (partially) reduced version of n, which
455
      // is justified by the explanation in einfo.
456
386783
      checkExtfInference(n, to_reduce, einfo, effort);
457
386783
      if (Trace.isOn("strings-extf-list"))
458
      {
459
        Trace("strings-extf-list") << "  * " << to_reduce;
460
        if (!einfo.d_const.isNull())
461
        {
462
          Trace("strings-extf-list") << ", const = " << einfo.d_const;
463
        }
464
        if (n != to_reduce)
465
        {
466
          Trace("strings-extf-list") << ", from " << n;
467
        }
468
        Trace("strings-extf-list") << std::endl;
469
      }
470
386783
      if (d_extt.isActive(n) && einfo.d_modelActive)
471
      {
472
386783
        has_nreduce = true;
473
      }
474
    }
475
529553
    if (d_state.isInConflict())
476
    {
477
753
      Trace("strings-extf-debug") << "  conflict, return." << std::endl;
478
753
      return;
479
    }
480
  }
481
51220
  d_hasExtf = has_nreduce;
482
}
483
484
386783
void ExtfSolver::checkExtfInference(Node n,
485
                                    Node nr,
486
                                    ExtfInfoTmp& in,
487
                                    int effort)
488
{
489
386783
  if (in.d_const.isNull())
490
  {
491
443079
    return;
492
  }
493
181543
  NodeManager* nm = NodeManager::currentNM();
494
363086
  Trace("strings-extf-infer")
495
181543
      << "checkExtfInference: " << n << " : " << nr << " == " << in.d_const
496
181543
      << " with exp " << in.d_exp << std::endl;
497
498
  // add original to explanation
499
181543
  if (n.getType().isBoolean())
500
  {
501
    // if Boolean, it's easy
502
93319
    in.d_exp.push_back(in.d_const.getConst<bool>() ? n : n.negate());
503
  }
504
  else
505
  {
506
    // otherwise, must explain via base node
507
176448
    Node r = d_state.getRepresentative(n);
508
    // explain using the base solver
509
88224
    d_bsolver.explainConstantEqc(n, r, in.d_exp);
510
  }
511
512
  // d_extfInferCache stores whether we have made the inferences associated
513
  // with a node n,
514
  // this may need to be generalized if multiple inferences apply
515
516
181543
  if (nr.getKind() == STRING_CONTAINS)
517
  {
518
32599
    Assert(in.d_const.isConst());
519
32599
    bool pol = in.d_const.getConst<bool>();
520
41562
    if ((pol && nr[1].getKind() == STRING_CONCAT)
521
65124
        || (!pol && nr[0].getKind() == STRING_CONCAT))
522
    {
523
      // If str.contains( x, str.++( y1, ..., yn ) ),
524
      //   we may infer str.contains( x, y1 ), ..., str.contains( x, yn )
525
      // The following recognizes two situations related to the above reasoning:
526
      // (1) If ~str.contains( x, yi ) holds for some i, we are in conflict,
527
      // (2) If str.contains( x, yj ) already holds for some j, then the term
528
      // str.contains( x, yj ) is irrelevant since it is satisfied by all models
529
      // for str.contains( x, str.++( y1, ..., yn ) ).
530
531
      // Notice that the dual of the above reasoning also holds, i.e.
532
      // If ~str.contains( str.++( x1, ..., xn ), y ),
533
      //   we may infer ~str.contains( x1, y ), ..., ~str.contains( xn, y )
534
      // This is also handled here.
535
3886
      if (d_extfInferCache.find(nr) == d_extfInferCache.end())
536
      {
537
1378
        d_extfInferCache.insert(nr);
538
539
1378
        int index = pol ? 1 : 0;
540
2752
        std::vector<Node> children;
541
1378
        children.push_back(nr[0]);
542
1378
        children.push_back(nr[1]);
543
4574
        for (const Node& nrc : nr[index])
544
        {
545
3200
          children[index] = nrc;
546
6396
          Node conc = nm->mkNode(STRING_CONTAINS, children);
547
3200
          conc = Rewriter::rewrite(pol ? conc : conc.negate());
548
          // check if it already (does not) hold
549
3200
          if (d_state.hasTerm(conc))
550
          {
551
147
            if (d_state.areEqual(conc, d_false))
552
            {
553
              // we are in conflict
554
4
              d_im.addToExplanation(conc, d_false, in.d_exp);
555
4
              d_im.sendInference(
556
                  in.d_exp, d_false, InferenceId::STRINGS_CTN_DECOMPOSE);
557
4
              Assert(d_state.isInConflict());
558
4
              return;
559
            }
560
143
            else if (d_extt.hasFunctionKind(conc.getKind()))
561
            {
562
              // can mark as reduced, since model for n implies model for conc
563
              d_extt.markReduced(conc, ExtReducedId::STRINGS_CTN_DECOMPOSE);
564
            }
565
          }
566
        }
567
      }
568
    }
569
    else
570
    {
571
114852
      if (std::find(d_extfInfoTmp[nr[0]].d_ctn[pol].begin(),
572
57426
                    d_extfInfoTmp[nr[0]].d_ctn[pol].end(),
573
114852
                    nr[1])
574
86139
          == d_extfInfoTmp[nr[0]].d_ctn[pol].end())
575
      {
576
57326
        Trace("strings-extf-debug") << "  store contains info : " << nr[0]
577
28663
                                    << " " << pol << " " << nr[1] << std::endl;
578
        // Store s (does not) contains t, since nr = (~)contains( s, t ) holds.
579
28663
        d_extfInfoTmp[nr[0]].d_ctn[pol].push_back(nr[1]);
580
28663
        d_extfInfoTmp[nr[0]].d_ctnFrom[pol].push_back(n);
581
        // Do transistive closure on contains, e.g.
582
        // if contains( s, t ) and ~contains( s, r ), then ~contains( t, r ).
583
584
        // The following infers new (negative) contains based on the above
585
        // reasoning, provided that ~contains( t, r ) does not
586
        // already hold in the current context. We test this by checking that
587
        // contains( t, r ) is not already asserted false in the current
588
        // context. We also handle the case where contains( t, r ) is equivalent
589
        // to t = r, in which case we check that t != r does not already hold
590
        // in the current context.
591
592
        // Notice that form of the above inference is enough to find
593
        // conflicts purely due to contains predicates. For example, if we
594
        // have only positive occurrences of contains, then no conflicts due to
595
        // contains predicates are possible and this schema does nothing. For
596
        // example, note that contains( s, t ) and contains( t, r ) implies
597
        // contains( s, r ), which we could but choose not to infer. Instead,
598
        // we prefer being lazy: only if ~contains( s, r ) appears later do we
599
        // infer ~contains( t, r ), which suffices to show a conflict.
600
28663
        bool opol = !pol;
601
32231
        for (unsigned i = 0, size = d_extfInfoTmp[nr[0]].d_ctn[opol].size();
602
32231
             i < size;
603
             i++)
604
        {
605
7136
          Node onr = d_extfInfoTmp[nr[0]].d_ctn[opol][i];
606
          Node concOrig =
607
7136
              nm->mkNode(STRING_CONTAINS, pol ? nr[1] : onr, pol ? onr : nr[1]);
608
7136
          Node conc = Rewriter::rewrite(concOrig);
609
          // For termination concerns, we only do the inference if the contains
610
          // does not rewrite (and thus does not introduce new terms).
611
3568
          if (conc == concOrig)
612
          {
613
            bool do_infer = false;
614
            conc = conc.negate();
615
            bool pol2 = conc.getKind() != NOT;
616
            Node lit = pol2 ? conc : conc[0];
617
            if (lit.getKind() == EQUAL)
618
            {
619
              do_infer = pol2 ? !d_state.areEqual(lit[0], lit[1])
620
                              : !d_state.areDisequal(lit[0], lit[1]);
621
            }
622
            else
623
            {
624
              do_infer = !d_state.areEqual(lit, pol2 ? d_true : d_false);
625
            }
626
            if (do_infer)
627
            {
628
              std::vector<Node> exp_c;
629
              exp_c.insert(exp_c.end(), in.d_exp.begin(), in.d_exp.end());
630
              Node ofrom = d_extfInfoTmp[nr[0]].d_ctnFrom[opol][i];
631
              Assert(d_extfInfoTmp.find(ofrom) != d_extfInfoTmp.end());
632
              exp_c.insert(exp_c.end(),
633
                           d_extfInfoTmp[ofrom].d_exp.begin(),
634
                           d_extfInfoTmp[ofrom].d_exp.end());
635
              d_im.sendInference(exp_c, conc, InferenceId::STRINGS_CTN_TRANS);
636
            }
637
          }
638
        }
639
      }
640
      else
641
      {
642
        // If we already know that s (does not) contain t, then n may be
643
        // redundant. However, we do not mark n as reduced here, since strings
644
        // reductions may require dependencies between extended functions.
645
        // Marking reduced here could lead to incorrect models if an
646
        // extended function is marked reduced based on an assignment to
647
        // something that depends on n.
648
50
        Trace("strings-extf-debug") << "  redundant." << std::endl;
649
      }
650
    }
651
32595
    return;
652
  }
653
654
  // If it's not a predicate, see if we can solve the equality n = c, where c
655
  // is the constant that extended term n is equal to.
656
297888
  Node inferEq = nr.eqNode(in.d_const);
657
297888
  Node inferEqr = Rewriter::rewrite(inferEq);
658
297888
  Node inferEqrr = inferEqr;
659
148944
  if (inferEqr.getKind() == EQUAL)
660
  {
661
    // try to use the extended rewriter for equalities
662
89248
    inferEqrr = d_rewriter.rewriteEqualityExt(inferEqr);
663
  }
664
148944
  if (inferEqrr != inferEqr)
665
  {
666
6122
    inferEqrr = Rewriter::rewrite(inferEqrr);
667
12244
    Trace("strings-extf-infer")
668
6122
        << "checkExtfInference: " << inferEq << " ...reduces to " << inferEqrr
669
6122
        << " with explanation " << in.d_exp << std::endl;
670
6122
    d_im.sendInternalInference(in.d_exp, inferEqrr, InferenceId::STRINGS_EXTF_EQ_REW);
671
  }
672
}
673
674
1200491
Node ExtfSolver::getCurrentSubstitutionFor(int effort,
675
                                           Node n,
676
                                           std::vector<Node>& exp)
677
{
678
1200491
  if (effort >= 3)
679
  {
680
    // model values
681
    Node mv = d_state.getModel()->getRepresentative(n);
682
    Trace("strings-subs") << "   model val : " << mv << std::endl;
683
    return mv;
684
  }
685
2400982
  Node nr = d_state.getRepresentative(n);
686
  // if the normal form is available, use it
687
1200491
  if (effort >= 1 && n.getType().isStringLike())
688
  {
689
53930
    Assert(effort < 3);
690
    // normal forms
691
53930
    NormalForm& nfnr = d_csolver.getNormalForm(nr);
692
107860
    Node ns = d_csolver.getNormalString(nfnr.d_base, exp);
693
107860
    Trace("strings-subs") << "   normal eqc : " << ns << " " << nfnr.d_base
694
53930
                          << " " << nr << std::endl;
695
53930
    if (!nfnr.d_base.isNull())
696
    {
697
53930
      d_im.addToExplanation(n, nfnr.d_base, exp);
698
    }
699
53930
    return ns;
700
  }
701
  // otherwise, we use the best content heuristic
702
2293122
  Node c = d_bsolver.explainBestContentEqc(n, nr, exp);
703
1146561
  if (!c.isNull())
704
  {
705
603266
    return c;
706
  }
707
543295
  return n;
708
}
709
710
10689
const std::map<Node, ExtfInfoTmp>& ExtfSolver::getInfo() const
711
{
712
10689
  return d_extfInfoTmp;
713
}
714
bool ExtfSolver::hasExtendedFunctions() const { return d_hasExtf.get(); }
715
716
10689
std::vector<Node> ExtfSolver::getActive(Kind k) const
717
{
718
10689
  return d_extt.getActive(k);
719
}
720
721
bool StringsExtfCallback::getCurrentSubstitution(
722
    int effort,
723
    const std::vector<Node>& vars,
724
    std::vector<Node>& subs,
725
    std::map<Node, std::vector<Node> >& exp)
726
{
727
  Trace("strings-subs") << "getCurrentSubstitution, effort = " << effort
728
                        << std::endl;
729
  for (const Node& v : vars)
730
  {
731
    Trace("strings-subs") << "  get subs for " << v << "..." << std::endl;
732
    Node s = d_esolver->getCurrentSubstitutionFor(effort, v, exp[v]);
733
    subs.push_back(s);
734
  }
735
  return true;
736
}
737
738
std::string ExtfSolver::debugPrintModel()
739
{
740
  std::stringstream ss;
741
  std::vector<Node> extf;
742
  d_extt.getTerms(extf);
743
  // each extended function should have at least one annotation below
744
  for (const Node& n : extf)
745
  {
746
    ss << "- " << n;
747
    ExtReducedId id;
748
    if (!d_extt.isActive(n, id))
749
    {
750
      ss << " :extt-inactive " << id;
751
    }
752
    if (!d_extfInfoTmp[n].d_modelActive)
753
    {
754
      ss << " :model-inactive";
755
    }
756
    if (d_reduced.find(n) != d_reduced.end())
757
    {
758
      ss << " :reduced";
759
    }
760
    ss << std::endl;
761
  }
762
  return ss.str();
763
}
764
765
}  // namespace strings
766
}  // namespace theory
767
31125
}  // namespace cvc5