GCC Code Coverage Report
Directory: . Exec Total Coverage
File: src/theory/strings/extf_solver.cpp Lines: 301 358 84.1 %
Date: 2021-09-10 Branches: 779 1754 44.4 %

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