1 /********************* */
2 /*! \file ext_solver.cpp
4 ** Top contributors (to current version):
6 ** This file is part of the CVC4 project.
7 ** Copyright (c) 2009-2019 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.\endverbatim
12 ** \brief Implementation of solver for extended functions of theory of strings.
15 #include "theory/strings/extf_solver.h"
17 #include "options/strings_options.h"
18 #include "theory/strings/sequences_rewriter.h"
19 #include "theory/strings/theory_strings_preprocess.h"
20 #include "theory/strings/theory_strings_utils.h"
23 using namespace CVC4::context
;
24 using namespace CVC4::kind
;
30 ExtfSolver::ExtfSolver(context::Context
* c
,
31 context::UserContext
* u
,
35 StringsRewriter
& rewriter
,
39 SequencesStatistics
& statistics
)
47 d_statistics(statistics
),
48 d_preproc(&skc
, u
, statistics
),
52 d_extt
->addFunctionKind(kind::STRING_SUBSTR
);
53 d_extt
->addFunctionKind(kind::STRING_STRIDOF
);
54 d_extt
->addFunctionKind(kind::STRING_ITOS
);
55 d_extt
->addFunctionKind(kind::STRING_STOI
);
56 d_extt
->addFunctionKind(kind::STRING_STRREPL
);
57 d_extt
->addFunctionKind(kind::STRING_STRREPLALL
);
58 d_extt
->addFunctionKind(kind::STRING_STRCTN
);
59 d_extt
->addFunctionKind(kind::STRING_IN_REGEXP
);
60 d_extt
->addFunctionKind(kind::STRING_LEQ
);
61 d_extt
->addFunctionKind(kind::STRING_TO_CODE
);
62 d_extt
->addFunctionKind(kind::STRING_TOLOWER
);
63 d_extt
->addFunctionKind(kind::STRING_TOUPPER
);
64 d_extt
->addFunctionKind(kind::STRING_REV
);
66 d_true
= NodeManager::currentNM()->mkConst(true);
67 d_false
= NodeManager::currentNM()->mkConst(false);
70 ExtfSolver::~ExtfSolver() {}
72 bool ExtfSolver::doReduction(int effort
, Node n
, bool& isCd
)
74 Assert(d_extfInfoTmp
.find(n
) != d_extfInfoTmp
.end());
75 if (!d_extfInfoTmp
[n
].d_modelActive
)
77 // n is not active in the model, no need to reduce
80 // determine the effort level to process the extf at
81 // 0 - at assertion time, 1+ - after no other reduction is applicable
83 // polarity : 1 true, -1 false, 0 neither
86 if (n
.getType().isBoolean() && !d_extfInfoTmp
[n
].d_const
.isNull())
88 pol
= d_extfInfoTmp
[n
].d_const
.getConst
<bool>() ? 1 : -1;
90 if (k
== STRING_STRCTN
)
102 std::vector
<Node
> lexp
;
103 Node lenx
= d_state
.getLength(x
, lexp
);
104 Node lens
= d_state
.getLength(s
, lexp
);
105 if (d_state
.areEqual(lenx
, lens
))
107 Trace("strings-extf-debug")
108 << " resolve extf : " << n
109 << " based on equal lengths disequality." << std::endl
;
110 // We can reduce negative contains to a disequality when lengths are
111 // equal. In other words, len( x ) = len( s ) implies
112 // ~contains( x, s ) reduces to x != s.
113 if (!d_state
.areDisequal(x
, s
))
115 // len( x ) = len( s ) ^ ~contains( x, s ) => x != s
116 lexp
.push_back(lenx
.eqNode(lens
));
117 lexp
.push_back(n
.negate());
118 Node xneqs
= x
.eqNode(s
).negate();
119 d_im
.sendInference(lexp
, xneqs
, Inference::CTN_NEG_EQUAL
, true);
121 // this depends on the current assertions, so we set that this
122 // inference is context-dependent.
135 if (k
== STRING_SUBSTR
)
139 else if (k
!= STRING_IN_REGEXP
)
144 if (effort
!= r_effort
)
146 // not the right effort level to reduce
149 Node c_n
= pol
== -1 ? n
.negate() : n
;
150 Trace("strings-process-debug")
151 << "Process reduction for " << n
<< ", pol = " << pol
<< std::endl
;
152 if (k
== STRING_STRCTN
&& pol
== 1)
156 // positive contains reduces to a equality
158 d_skCache
.mkSkolemCached(x
, s
, SkolemCache::SK_FIRST_CTN_PRE
, "sc1");
160 d_skCache
.mkSkolemCached(x
, s
, SkolemCache::SK_FIRST_CTN_POST
, "sc2");
161 Node eq
= Rewriter::rewrite(x
.eqNode(utils::mkNConcat(sk1
, s
, sk2
)));
162 std::vector
<Node
> exp_vec
;
163 exp_vec
.push_back(n
);
164 d_im
.sendInference(d_emptyVec
, exp_vec
, eq
, Inference::CTN_POS
, true);
165 Trace("strings-extf-debug")
166 << " resolve extf : " << n
<< " based on positive contain reduction."
168 Trace("strings-red-lemma") << "Reduction (positive contains) lemma : " << n
169 << " => " << eq
<< std::endl
;
170 // context-dependent because it depends on the polarity of n itself
173 else if (k
!= kind::STRING_TO_CODE
)
175 NodeManager
* nm
= NodeManager::currentNM();
176 Assert(k
== STRING_SUBSTR
|| k
== STRING_STRCTN
|| k
== STRING_STRIDOF
177 || k
== STRING_ITOS
|| k
== STRING_STOI
|| k
== STRING_STRREPL
178 || k
== STRING_STRREPLALL
|| k
== STRING_LEQ
|| k
== STRING_TOLOWER
179 || k
== STRING_TOUPPER
|| k
== STRING_REV
);
180 std::vector
<Node
> new_nodes
;
181 Node res
= d_preproc
.simplify(n
, new_nodes
);
183 new_nodes
.push_back(res
.eqNode(n
));
185 new_nodes
.size() == 1 ? new_nodes
[0] : nm
->mkNode(AND
, new_nodes
);
186 nnlem
= Rewriter::rewrite(nnlem
);
187 Trace("strings-red-lemma")
188 << "Reduction_" << effort
<< " lemma : " << nnlem
<< std::endl
;
189 Trace("strings-red-lemma") << "...from " << n
<< std::endl
;
190 d_im
.sendInference(d_emptyVec
, nnlem
, Inference::REDUCTION
, true);
191 Trace("strings-extf-debug")
192 << " resolve extf : " << n
<< " based on reduction." << std::endl
;
198 void ExtfSolver::checkExtfReductions(int effort
)
200 // Notice we don't make a standard call to ExtTheory::doReductions here,
201 // since certain optimizations like context-dependent reductions and
202 // stratifying effort levels are done in doReduction below.
203 std::vector
<Node
> extf
= d_extt
->getActive();
204 Trace("strings-process") << " checking " << extf
.size() << " active extf"
206 for (const Node
& n
: extf
)
208 Assert(!d_state
.isInConflict());
209 Trace("strings-process")
211 << ", active in model=" << d_extfInfoTmp
[n
].d_modelActive
<< std::endl
;
212 // whether the reduction was context-dependent
214 bool ret
= doReduction(effort
, n
, isCd
);
217 d_extt
->markReduced(n
, isCd
);
218 if (d_im
.hasProcessed())
226 void ExtfSolver::checkExtfEval(int effort
)
228 Trace("strings-extf-list")
229 << "Active extended functions, effort=" << effort
<< " : " << std::endl
;
230 d_extfInfoTmp
.clear();
231 NodeManager
* nm
= NodeManager::currentNM();
232 bool has_nreduce
= false;
233 std::vector
<Node
> terms
= d_extt
->getActive();
234 for (const Node
& n
: terms
)
236 // Setup information about n, including if it is equal to a constant.
237 ExtfInfoTmp
& einfo
= d_extfInfoTmp
[n
];
238 Node r
= d_state
.getRepresentative(n
);
239 einfo
.d_const
= d_bsolver
.getConstantEqc(r
);
240 // Get the current values of the children of n.
241 // Notice that we look up the value of the direct children of n, and not
242 // their free variables. In other words, given a term:
243 // t = (str.replace "B" (str.replace x "A" "B") "C")
244 // we may build the explanation that:
245 // ((str.replace x "A" "B") = "B") => t = (str.replace "B" "B" "C")
246 // instead of basing this on the free variable x:
247 // (x = "A") => t = (str.replace "B" (str.replace "A" "A" "B") "C")
248 // Although both allow us to infer t = "C", it is important to use the
249 // first kind of inference since it ensures that its subterms have the
250 // expected values. Otherwise, we may in rare cases fail to realize that
251 // the subterm (str.replace x "A" "B") does not currently have the correct
252 // value, say in this example that (str.replace x "A" "B") != "B".
253 std::vector
<Node
> exp
;
254 std::vector
<Node
> schildren
;
255 bool schanged
= false;
256 for (const Node
& nc
: n
)
258 Node sc
= getCurrentSubstitutionFor(effort
, nc
, exp
);
259 schildren
.push_back(sc
);
260 schanged
= schanged
|| sc
!= nc
;
262 // If there is information involving the children, attempt to do an
263 // inference and/or mark n as reduced.
267 Node sn
= nm
->mkNode(n
.getKind(), schildren
);
268 Trace("strings-extf-debug")
269 << "Check extf " << n
<< " == " << sn
270 << ", constant = " << einfo
.d_const
<< ", effort=" << effort
<< "..."
272 einfo
.d_exp
.insert(einfo
.d_exp
.end(), exp
.begin(), exp
.end());
273 // inference is rewriting the substituted node
274 Node nrc
= Rewriter::rewrite(sn
);
275 // if rewrites to a constant, then do the inference and mark as reduced
280 d_extt
->markReduced(n
);
281 Trace("strings-extf-debug")
282 << " resolvable by evaluation..." << std::endl
;
283 std::vector
<Node
> exps
;
284 // The following optimization gets the "symbolic definition" of
285 // an extended term. The symbolic definition of a term t is a term
286 // t' where constants are replaced by their corresponding proxy
288 // For example, if lsym is a proxy variable for "", then
289 // str.replace( lsym, lsym, lsym ) is the symbolic definition for
290 // str.replace( "", "", "" ). It is generally better to use symbolic
291 // definitions when doing cd-rewriting for the purpose of minimizing
292 // clauses, e.g. we infer the unit equality:
293 // str.replace( lsym, lsym, lsym ) == ""
294 // instead of making this inference multiple times:
295 // x = "" => str.replace( x, x, x ) == ""
296 // y = "" => str.replace( y, y, y ) == ""
297 Trace("strings-extf-debug")
298 << " get symbolic definition..." << std::endl
;
300 // only use symbolic definitions if option is set
301 if (options::stringInferSym())
303 nrs
= d_im
.getSymbolicDefinition(sn
, exps
);
307 Trace("strings-extf-debug")
308 << " rewrite " << nrs
<< "..." << std::endl
;
309 Node nrsr
= Rewriter::rewrite(nrs
);
310 // ensure the symbolic form is not rewritable
313 // we cannot use the symbolic definition if it rewrites
314 Trace("strings-extf-debug")
315 << " symbolic definition is trivial..." << std::endl
;
321 Trace("strings-extf-debug")
322 << " could not infer symbolic definition." << std::endl
;
327 Trace("strings-extf-debug")
328 << " symbolic def : " << nrs
<< std::endl
;
329 if (!d_state
.areEqual(nrs
, nrc
))
331 // infer symbolic unit
332 if (n
.getType().isBoolean())
334 conc
= nrc
== d_true
? nrs
: nrs
.negate();
338 conc
= nrs
.eqNode(nrc
);
345 if (!d_state
.areEqual(n
, nrc
))
347 if (n
.getType().isBoolean())
349 if (d_state
.areEqual(n
, nrc
== d_true
? d_false
: d_true
))
351 einfo
.d_exp
.push_back(nrc
== d_true
? n
.negate() : n
);
356 conc
= nrc
== d_true
? n
: n
.negate();
361 conc
= n
.eqNode(nrc
);
367 Trace("strings-extf")
368 << " resolve extf : " << sn
<< " -> " << nrc
<< std::endl
;
369 Inference inf
= effort
== 0 ? Inference::EXTF
: Inference::EXTF_N
;
370 d_im
.sendInference(einfo
.d_exp
, conc
, inf
, true);
371 d_statistics
.d_cdSimplifications
<< n
.getKind();
372 if (d_state
.isInConflict())
374 Trace("strings-extf-debug") << " conflict, return." << std::endl
;
381 // check if it is already equal, if so, mark as reduced. Otherwise, do
383 if (d_state
.areEqual(n
, nrc
))
385 Trace("strings-extf")
386 << " resolved extf, since satisfied by model: " << n
388 einfo
.d_modelActive
= false;
394 // if this was a predicate which changed after substitution + rewriting
395 if (!einfo
.d_const
.isNull() && nrc
.getType().isBoolean() && nrc
!= n
)
397 bool pol
= einfo
.d_const
== d_true
;
398 Node nrcAssert
= pol
? nrc
: nrc
.negate();
399 Node nAssert
= pol
? n
: n
.negate();
401 einfo
.d_exp
.push_back(nAssert
);
402 Trace("strings-extf-debug") << " decomposable..." << std::endl
;
403 Trace("strings-extf") << " resolve extf : " << sn
<< " -> " << nrc
404 << ", const = " << einfo
.d_const
<< std::endl
;
405 // We send inferences internal here, which may help show unsat.
406 // However, we do not make a determination whether n can be marked
407 // reduced since this argument may be circular: we may infer than n
408 // can be reduced to something else, but that thing may argue that it
409 // can be reduced to n, in theory.
411 effort
== 0 ? Inference::EXTF_D
: Inference::EXTF_D_N
;
412 d_im
.sendInternalInference(einfo
.d_exp
, nrcAssert
, infer
);
422 if (!to_reduce
.isNull())
427 Trace("strings-extf")
428 << " cannot rewrite extf : " << to_reduce
<< std::endl
;
430 checkExtfInference(n
, to_reduce
, einfo
, effort
);
431 if (Trace
.isOn("strings-extf-list"))
433 Trace("strings-extf-list") << " * " << to_reduce
;
434 if (!einfo
.d_const
.isNull())
436 Trace("strings-extf-list") << ", const = " << einfo
.d_const
;
440 Trace("strings-extf-list") << ", from " << n
;
442 Trace("strings-extf-list") << std::endl
;
444 if (d_extt
->isActive(n
) && einfo
.d_modelActive
)
450 d_hasExtf
= has_nreduce
;
453 void ExtfSolver::checkExtfInference(Node n
,
458 if (in
.d_const
.isNull())
462 NodeManager
* nm
= NodeManager::currentNM();
463 Trace("strings-extf-infer") << "checkExtfInference: " << n
<< " : " << nr
464 << " == " << in
.d_const
<< std::endl
;
466 // add original to explanation
467 if (n
.getType().isBoolean())
469 // if Boolean, it's easy
470 in
.d_exp
.push_back(in
.d_const
.getConst
<bool>() ? n
: n
.negate());
474 // otherwise, must explain via base node
475 Node r
= d_state
.getRepresentative(n
);
476 // explain using the base solver
477 d_bsolver
.explainConstantEqc(n
, r
, in
.d_exp
);
480 // d_extfInferCache stores whether we have made the inferences associated
482 // this may need to be generalized if multiple inferences apply
484 if (nr
.getKind() == STRING_STRCTN
)
486 Assert(in
.d_const
.isConst());
487 bool pol
= in
.d_const
.getConst
<bool>();
488 if ((pol
&& nr
[1].getKind() == STRING_CONCAT
)
489 || (!pol
&& nr
[0].getKind() == STRING_CONCAT
))
491 // If str.contains( x, str.++( y1, ..., yn ) ),
492 // we may infer str.contains( x, y1 ), ..., str.contains( x, yn )
493 // The following recognizes two situations related to the above reasoning:
494 // (1) If ~str.contains( x, yi ) holds for some i, we are in conflict,
495 // (2) If str.contains( x, yj ) already holds for some j, then the term
496 // str.contains( x, yj ) is irrelevant since it is satisfied by all models
497 // for str.contains( x, str.++( y1, ..., yn ) ).
499 // Notice that the dual of the above reasoning also holds, i.e.
500 // If ~str.contains( str.++( x1, ..., xn ), y ),
501 // we may infer ~str.contains( x1, y ), ..., ~str.contains( xn, y )
502 // This is also handled here.
503 if (d_extfInferCache
.find(nr
) == d_extfInferCache
.end())
505 d_extfInferCache
.insert(nr
);
507 int index
= pol
? 1 : 0;
508 std::vector
<Node
> children
;
509 children
.push_back(nr
[0]);
510 children
.push_back(nr
[1]);
511 for (const Node
& nrc
: nr
[index
])
513 children
[index
] = nrc
;
514 Node conc
= nm
->mkNode(STRING_STRCTN
, children
);
515 conc
= Rewriter::rewrite(pol
? conc
: conc
.negate());
516 // check if it already (does not) hold
517 if (d_state
.hasTerm(conc
))
519 if (d_state
.areEqual(conc
, d_false
))
521 // we are in conflict
522 d_im
.sendInference(in
.d_exp
, conc
, Inference::CTN_DECOMPOSE
);
524 else if (d_extt
->hasFunctionKind(conc
.getKind()))
526 // can mark as reduced, since model for n implies model for conc
527 d_extt
->markReduced(conc
);
535 if (std::find(d_extfInfoTmp
[nr
[0]].d_ctn
[pol
].begin(),
536 d_extfInfoTmp
[nr
[0]].d_ctn
[pol
].end(),
538 == d_extfInfoTmp
[nr
[0]].d_ctn
[pol
].end())
540 Trace("strings-extf-debug") << " store contains info : " << nr
[0]
541 << " " << pol
<< " " << nr
[1] << std::endl
;
542 // Store s (does not) contains t, since nr = (~)contains( s, t ) holds.
543 d_extfInfoTmp
[nr
[0]].d_ctn
[pol
].push_back(nr
[1]);
544 d_extfInfoTmp
[nr
[0]].d_ctnFrom
[pol
].push_back(n
);
545 // Do transistive closure on contains, e.g.
546 // if contains( s, t ) and ~contains( s, r ), then ~contains( t, r ).
548 // The following infers new (negative) contains based on the above
549 // reasoning, provided that ~contains( t, r ) does not
550 // already hold in the current context. We test this by checking that
551 // contains( t, r ) is not already asserted false in the current
552 // context. We also handle the case where contains( t, r ) is equivalent
553 // to t = r, in which case we check that t != r does not already hold
554 // in the current context.
556 // Notice that form of the above inference is enough to find
557 // conflicts purely due to contains predicates. For example, if we
558 // have only positive occurrences of contains, then no conflicts due to
559 // contains predicates are possible and this schema does nothing. For
560 // example, note that contains( s, t ) and contains( t, r ) implies
561 // contains( s, r ), which we could but choose not to infer. Instead,
562 // we prefer being lazy: only if ~contains( s, r ) appears later do we
563 // infer ~contains( t, r ), which suffices to show a conflict.
565 for (unsigned i
= 0, size
= d_extfInfoTmp
[nr
[0]].d_ctn
[opol
].size();
569 Node onr
= d_extfInfoTmp
[nr
[0]].d_ctn
[opol
][i
];
571 nm
->mkNode(STRING_STRCTN
, pol
? nr
[1] : onr
, pol
? onr
: nr
[1]);
572 Node conc
= Rewriter::rewrite(concOrig
);
573 // For termination concerns, we only do the inference if the contains
574 // does not rewrite (and thus does not introduce new terms).
575 if (conc
== concOrig
)
577 bool do_infer
= false;
578 conc
= conc
.negate();
579 bool pol2
= conc
.getKind() != NOT
;
580 Node lit
= pol2
? conc
: conc
[0];
581 if (lit
.getKind() == EQUAL
)
583 do_infer
= pol2
? !d_state
.areEqual(lit
[0], lit
[1])
584 : !d_state
.areDisequal(lit
[0], lit
[1]);
588 do_infer
= !d_state
.areEqual(lit
, pol2
? d_true
: d_false
);
592 std::vector
<Node
> exp_c
;
593 exp_c
.insert(exp_c
.end(), in
.d_exp
.begin(), in
.d_exp
.end());
594 Node ofrom
= d_extfInfoTmp
[nr
[0]].d_ctnFrom
[opol
][i
];
595 Assert(d_extfInfoTmp
.find(ofrom
) != d_extfInfoTmp
.end());
596 exp_c
.insert(exp_c
.end(),
597 d_extfInfoTmp
[ofrom
].d_exp
.begin(),
598 d_extfInfoTmp
[ofrom
].d_exp
.end());
599 d_im
.sendInference(exp_c
, conc
, Inference::CTN_TRANS
);
606 // If we already know that s (does not) contain t, then n is redundant.
607 // For example, if str.contains( x, y ), str.contains( z, y ), and x=z
608 // are asserted in the current context, then str.contains( z, y ) is
609 // satisfied by all models of str.contains( x, y ) ^ x=z and thus can
611 Trace("strings-extf-debug") << " redundant." << std::endl
;
612 d_extt
->markReduced(n
);
618 // If it's not a predicate, see if we can solve the equality n = c, where c
619 // is the constant that extended term n is equal to.
620 Node inferEq
= nr
.eqNode(in
.d_const
);
621 Node inferEqr
= Rewriter::rewrite(inferEq
);
622 Node inferEqrr
= inferEqr
;
623 if (inferEqr
.getKind() == EQUAL
)
625 // try to use the extended rewriter for equalities
626 inferEqrr
= d_rewriter
.rewriteEqualityExt(inferEqr
);
628 if (inferEqrr
!= inferEqr
)
630 inferEqrr
= Rewriter::rewrite(inferEqrr
);
631 Trace("strings-extf-infer") << "checkExtfInference: " << inferEq
632 << " ...reduces to " << inferEqrr
<< std::endl
;
633 d_im
.sendInternalInference(in
.d_exp
, inferEqrr
, Inference::EXTF_EQ_REW
);
637 Node
ExtfSolver::getCurrentSubstitutionFor(int effort
,
639 std::vector
<Node
>& exp
)
644 Node mv
= d_state
.getModel()->getRepresentative(n
);
645 Trace("strings-subs") << " model val : " << mv
<< std::endl
;
648 Node nr
= d_state
.getRepresentative(n
);
649 Node c
= d_bsolver
.explainConstantEqc(n
, nr
, exp
);
654 else if (effort
>= 1 && n
.getType().isStringLike())
658 NormalForm
& nfnr
= d_csolver
.getNormalForm(nr
);
659 Node ns
= d_csolver
.getNormalString(nfnr
.d_base
, exp
);
660 Trace("strings-subs") << " normal eqc : " << ns
<< " " << nfnr
.d_base
661 << " " << nr
<< std::endl
;
662 if (!nfnr
.d_base
.isNull())
664 d_im
.addToExplanation(n
, nfnr
.d_base
, exp
);
671 const std::map
<Node
, ExtfInfoTmp
>& ExtfSolver::getInfo() const
673 return d_extfInfoTmp
;
675 bool ExtfSolver::hasExtendedFunctions() const { return d_hasExtf
.get(); }
677 std::vector
<Node
> ExtfSolver::getActive(Kind k
) const
679 return d_extt
->getActive(k
);
682 } // namespace strings
683 } // namespace theory