GCC Code Coverage Report
Directory: . Exec Total Coverage
File: src/theory/strings/extf_solver.cpp Lines: 311 354 87.9 %
Date: 2021-05-22 Branches: 788 1726 45.7 %

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