/********************* */ /*! \file theory_strings.cpp ** \verbatim ** Top contributors (to current version): ** Andrew Reynolds, Tianyi Liang, Morgan Deters ** This file is part of the CVC4 project. ** Copyright (c) 2009-2019 by the authors listed in the file AUTHORS ** in the top-level source directory) and their institutional affiliations. ** All rights reserved. See the file COPYING in the top-level source ** directory for licensing information.\endverbatim ** ** \brief Implementation of the theory of strings. ** ** Implementation of the theory of strings. **/ #include "theory/strings/theory_strings.h" #include #include "expr/kind.h" #include "options/strings_options.h" #include "options/theory_options.h" #include "smt/command.h" #include "smt/logic_exception.h" #include "smt/smt_statistics_registry.h" #include "theory/ext_theory.h" #include "theory/rewriter.h" #include "theory/strings/theory_strings_rewriter.h" #include "theory/strings/theory_strings_utils.h" #include "theory/strings/type_enumerator.h" #include "theory/theory_model.h" #include "theory/valuation.h" using namespace std; using namespace CVC4::context; using namespace CVC4::kind; namespace CVC4 { namespace theory { namespace strings { std::ostream& operator<<(std::ostream& out, InferStep s) { switch (s) { case BREAK: out << "break"; break; case CHECK_INIT: out << "check_init"; break; case CHECK_CONST_EQC: out << "check_const_eqc"; break; case CHECK_EXTF_EVAL: out << "check_extf_eval"; break; case CHECK_CYCLES: out << "check_cycles"; break; case CHECK_FLAT_FORMS: out << "check_flat_forms"; break; case CHECK_NORMAL_FORMS_EQ: out << "check_normal_forms_eq"; break; case CHECK_NORMAL_FORMS_DEQ: out << "check_normal_forms_deq"; break; case CHECK_CODES: out << "check_codes"; break; case CHECK_LENGTH_EQC: out << "check_length_eqc"; break; case CHECK_EXTF_REDUCTION: out << "check_extf_reduction"; break; case CHECK_MEMBERSHIP: out << "check_membership"; break; case CHECK_CARDINALITY: out << "check_cardinality"; break; default: out << "?"; break; } return out; } TheoryStrings::TheoryStrings(context::Context* c, context::UserContext* u, OutputChannel& out, Valuation valuation, const LogicInfo& logicInfo) : Theory(THEORY_STRINGS, c, u, out, valuation, logicInfo), d_notify(*this), d_equalityEngine(d_notify, c, "theory::strings", true), d_state(c, d_equalityEngine, d_valuation), d_im(*this, c, u, d_state, out), d_pregistered_terms_cache(u), d_registered_terms_cache(u), d_preproc(&d_sk_cache, u), d_extf_infer_cache(c), d_proxy_var(u), d_proxy_var_to_length(u), d_functionsTerms(c), d_has_extf(c, false), d_has_str_code(false), d_bsolver(c, u, d_state, d_im), d_csolver(c, u, d_state, d_im, d_sk_cache, d_bsolver), d_regexp_solver(*this, d_state, d_im, c, u), d_stringsFmf(c, u, valuation, d_sk_cache), d_strategy_init(false) { setupExtTheory(); getExtTheory()->addFunctionKind(kind::STRING_SUBSTR); getExtTheory()->addFunctionKind(kind::STRING_STRIDOF); getExtTheory()->addFunctionKind(kind::STRING_ITOS); getExtTheory()->addFunctionKind(kind::STRING_STOI); getExtTheory()->addFunctionKind(kind::STRING_STRREPL); getExtTheory()->addFunctionKind(kind::STRING_STRREPLALL); getExtTheory()->addFunctionKind(kind::STRING_STRCTN); getExtTheory()->addFunctionKind(kind::STRING_IN_REGEXP); getExtTheory()->addFunctionKind(kind::STRING_LEQ); getExtTheory()->addFunctionKind(kind::STRING_CODE); getExtTheory()->addFunctionKind(kind::STRING_TOLOWER); getExtTheory()->addFunctionKind(kind::STRING_TOUPPER); getExtTheory()->addFunctionKind(kind::STRING_REV); // The kinds we are treating as function application in congruence d_equalityEngine.addFunctionKind(kind::STRING_LENGTH); d_equalityEngine.addFunctionKind(kind::STRING_CONCAT); d_equalityEngine.addFunctionKind(kind::STRING_IN_REGEXP); d_equalityEngine.addFunctionKind(kind::STRING_CODE); // extended functions d_equalityEngine.addFunctionKind(kind::STRING_STRCTN); d_equalityEngine.addFunctionKind(kind::STRING_LEQ); d_equalityEngine.addFunctionKind(kind::STRING_SUBSTR); d_equalityEngine.addFunctionKind(kind::STRING_ITOS); d_equalityEngine.addFunctionKind(kind::STRING_STOI); d_equalityEngine.addFunctionKind(kind::STRING_STRIDOF); d_equalityEngine.addFunctionKind(kind::STRING_STRREPL); d_equalityEngine.addFunctionKind(kind::STRING_STRREPLALL); d_equalityEngine.addFunctionKind(kind::STRING_TOLOWER); d_equalityEngine.addFunctionKind(kind::STRING_TOUPPER); d_equalityEngine.addFunctionKind(kind::STRING_REV); d_zero = NodeManager::currentNM()->mkConst( Rational( 0 ) ); d_one = NodeManager::currentNM()->mkConst( Rational( 1 ) ); d_neg_one = NodeManager::currentNM()->mkConst(Rational(-1)); d_emptyString = NodeManager::currentNM()->mkConst( ::CVC4::String("") ); d_true = NodeManager::currentNM()->mkConst( true ); d_false = NodeManager::currentNM()->mkConst( false ); d_card_size = TheoryStringsRewriter::getAlphabetCardinality(); } TheoryStrings::~TheoryStrings() { } bool TheoryStrings::areCareDisequal( TNode x, TNode y ) { Assert(d_equalityEngine.hasTerm(x)); Assert(d_equalityEngine.hasTerm(y)); if( d_equalityEngine.isTriggerTerm(x, THEORY_STRINGS) && d_equalityEngine.isTriggerTerm(y, THEORY_STRINGS) ){ TNode x_shared = d_equalityEngine.getTriggerTermRepresentative(x, THEORY_STRINGS); TNode y_shared = d_equalityEngine.getTriggerTermRepresentative(y, THEORY_STRINGS); EqualityStatus eqStatus = d_valuation.getEqualityStatus(x_shared, y_shared); if( eqStatus==EQUALITY_FALSE_AND_PROPAGATED || eqStatus==EQUALITY_FALSE || eqStatus==EQUALITY_FALSE_IN_MODEL ){ return true; } } return false; } Node TheoryStrings::getNormalString(Node x, std::vector& nf_exp) { return d_csolver.getNormalString(x, nf_exp); } void TheoryStrings::setMasterEqualityEngine(eq::EqualityEngine* eq) { d_equalityEngine.setMasterEqualityEngine(eq); } void TheoryStrings::addSharedTerm(TNode t) { Debug("strings") << "TheoryStrings::addSharedTerm(): " << t << " " << t.getType().isBoolean() << endl; d_equalityEngine.addTriggerTerm(t, THEORY_STRINGS); if (options::stringExp()) { getExtTheory()->registerTermRec(t); } Debug("strings") << "TheoryStrings::addSharedTerm() finished" << std::endl; } EqualityStatus TheoryStrings::getEqualityStatus(TNode a, TNode b) { if( d_equalityEngine.hasTerm(a) && d_equalityEngine.hasTerm(b) ){ if (d_equalityEngine.areEqual(a, b)) { // The terms are implied to be equal return EQUALITY_TRUE; } if (d_equalityEngine.areDisequal(a, b, false)) { // The terms are implied to be dis-equal return EQUALITY_FALSE; } } return EQUALITY_UNKNOWN; } void TheoryStrings::propagate(Effort e) { // direct propagation now } bool TheoryStrings::propagate(TNode literal) { Debug("strings-propagate") << "TheoryStrings::propagate(" << literal << ")" << std::endl; // If already in conflict, no more propagation if (d_state.isInConflict()) { Debug("strings-propagate") << "TheoryStrings::propagate(" << literal << "): already in conflict" << std::endl; return false; } // Propagate out bool ok = d_out->propagate(literal); if (!ok) { d_state.setConflict(); } return ok; } Node TheoryStrings::explain( TNode literal ){ Debug("strings-explain") << "explain called on " << literal << std::endl; std::vector< TNode > assumptions; d_im.explain(literal, assumptions); if( assumptions.empty() ){ return d_true; }else if( assumptions.size()==1 ){ return assumptions[0]; }else{ return NodeManager::currentNM()->mkNode( kind::AND, assumptions ); } } bool TheoryStrings::getCurrentSubstitution( int effort, std::vector< Node >& vars, std::vector< Node >& subs, std::map< Node, std::vector< Node > >& exp ) { Trace("strings-subs") << "getCurrentSubstitution, effort = " << effort << std::endl; for( unsigned i=0; i& exp) { if (effort >= 3) { // model values Node mv = d_valuation.getModel()->getRepresentative(n); Trace("strings-subs") << " model val : " << mv << std::endl; return mv; } Node nr = d_state.getRepresentative(n); Node c = d_bsolver.explainConstantEqc(n, nr, exp); if (!c.isNull()) { return c; } else if (effort >= 1 && n.getType().isString()) { Assert(effort < 3); // normal forms NormalForm& nfnr = d_csolver.getNormalForm(nr); Node ns = d_csolver.getNormalString(nfnr.d_base, exp); Trace("strings-subs") << " normal eqc : " << ns << " " << nfnr.d_base << " " << nr << std::endl; if (!nfnr.d_base.isNull()) { d_im.addToExplanation(n, nfnr.d_base, exp); } return ns; } return n; } bool TheoryStrings::doReduction(int effort, Node n, bool& isCd) { Assert(d_extf_info_tmp.find(n) != d_extf_info_tmp.end()); if (!d_extf_info_tmp[n].d_model_active) { // n is not active in the model, no need to reduce return false; } //determine the effort level to process the extf at // 0 - at assertion time, 1+ - after no other reduction is applicable int r_effort = -1; // polarity : 1 true, -1 false, 0 neither int pol = 0; Kind k = n.getKind(); if (n.getType().isBoolean() && !d_extf_info_tmp[n].d_const.isNull()) { pol = d_extf_info_tmp[n].d_const.getConst() ? 1 : -1; } if (k == STRING_STRCTN) { if (pol == 1) { r_effort = 1; } else if (pol == -1) { if (effort == 2) { Node x = n[0]; Node s = n[1]; std::vector lexp; Node lenx = d_state.getLength(x, lexp); Node lens = d_state.getLength(s, lexp); if (d_state.areEqual(lenx, lens)) { Trace("strings-extf-debug") << " resolve extf : " << n << " based on equal lengths disequality." << std::endl; // We can reduce negative contains to a disequality when lengths are // equal. In other words, len( x ) = len( s ) implies // ~contains( x, s ) reduces to x != s. if (!d_state.areDisequal(x, s)) { // len( x ) = len( s ) ^ ~contains( x, s ) => x != s lexp.push_back(lenx.eqNode(lens)); lexp.push_back(n.negate()); Node xneqs = x.eqNode(s).negate(); d_im.sendInference(lexp, xneqs, "NEG-CTN-EQL", true); } // this depends on the current assertions, so we set that this // inference is context-dependent. isCd = true; return true; } else { r_effort = 2; } } } } else { if (k == STRING_SUBSTR) { r_effort = 1; } else if (k != STRING_IN_REGEXP) { r_effort = 2; } } if (effort != r_effort) { // not the right effort level to reduce return false; } Node c_n = pol == -1 ? n.negate() : n; Trace("strings-process-debug") << "Process reduction for " << n << ", pol = " << pol << std::endl; if (k == STRING_STRCTN && pol == 1) { Node x = n[0]; Node s = n[1]; // positive contains reduces to a equality Node sk1 = d_sk_cache.mkSkolemCached(x, s, SkolemCache::SK_FIRST_CTN_PRE, "sc1"); Node sk2 = d_sk_cache.mkSkolemCached(x, s, SkolemCache::SK_FIRST_CTN_POST, "sc2"); Node eq = Rewriter::rewrite(x.eqNode(utils::mkNConcat(sk1, s, sk2))); std::vector exp_vec; exp_vec.push_back(n); d_im.sendInference(d_empty_vec, exp_vec, eq, "POS-CTN", true); Trace("strings-extf-debug") << " resolve extf : " << n << " based on positive contain reduction." << std::endl; Trace("strings-red-lemma") << "Reduction (positive contains) lemma : " << n << " => " << eq << std::endl; // context-dependent because it depends on the polarity of n itself isCd = true; } else if (k != kind::STRING_CODE) { NodeManager* nm = NodeManager::currentNM(); Assert(k == STRING_SUBSTR || k == STRING_STRCTN || k == STRING_STRIDOF || k == STRING_ITOS || k == STRING_STOI || k == STRING_STRREPL || k == STRING_STRREPLALL || k == STRING_LEQ || k == STRING_TOLOWER || k == STRING_TOUPPER || k == STRING_REV); std::vector new_nodes; Node res = d_preproc.simplify(n, new_nodes); Assert(res != n); new_nodes.push_back(res.eqNode(n)); Node nnlem = new_nodes.size() == 1 ? new_nodes[0] : nm->mkNode(AND, new_nodes); nnlem = Rewriter::rewrite(nnlem); Trace("strings-red-lemma") << "Reduction_" << effort << " lemma : " << nnlem << std::endl; Trace("strings-red-lemma") << "...from " << n << std::endl; d_im.sendInference(d_empty_vec, nnlem, "Reduction", true); Trace("strings-extf-debug") << " resolve extf : " << n << " based on reduction." << std::endl; isCd = false; } return true; } ///////////////////////////////////////////////////////////////////////////// // NOTIFICATIONS ///////////////////////////////////////////////////////////////////////////// void TheoryStrings::presolve() { Debug("strings-presolve") << "TheoryStrings::Presolving : get fmf options " << (options::stringFMF() ? "true" : "false") << std::endl; initializeStrategy(); // if strings fmf is enabled, register the strategy if (options::stringFMF()) { d_stringsFmf.presolve(); // This strategy is local to a check-sat call, since we refresh the strategy // on every call to presolve. getDecisionManager()->registerStrategy( DecisionManager::STRAT_STRINGS_SUM_LENGTHS, d_stringsFmf.getDecisionStrategy(), DecisionManager::STRAT_SCOPE_LOCAL_SOLVE); } Debug("strings-presolve") << "Finished presolve" << std::endl; } ///////////////////////////////////////////////////////////////////////////// // MODEL GENERATION ///////////////////////////////////////////////////////////////////////////// bool TheoryStrings::collectModelInfo(TheoryModel* m) { Trace("strings-model") << "TheoryStrings : Collect model info" << std::endl; Trace("strings-model") << "TheoryStrings : assertEqualityEngine." << std::endl; std::set termSet; // Compute terms appearing in assertions and shared terms computeRelevantTerms(termSet); // assert the (relevant) portion of the equality engine to the model if (!m->assertEqualityEngine(&d_equalityEngine, &termSet)) { return false; } std::unordered_set repSet; NodeManager* nm = NodeManager::currentNM(); // Generate model // get the relevant string equivalence classes for (const Node& s : termSet) { if (s.getType().isString()) { Node r = d_state.getRepresentative(s); repSet.insert(r); } } std::vector nodes(repSet.begin(), repSet.end()); std::map< Node, Node > processed; std::vector< std::vector< Node > > col; std::vector< Node > lts; d_state.separateByLength(nodes, col, lts); //step 1 : get all values for known lengths std::vector< Node > lts_values; std::map values_used; std::vector len_splits; for( unsigned i=0; i0 ) { Trace("strings-model") << ", "; } Trace("strings-model") << col[i][j]; } Trace("strings-model") << " } (length is " << lts[i] << ")" << std::endl; Node len_value; if( lts[i].isConst() ) { len_value = lts[i]; } else if (!lts[i].isNull()) { // get the model value for lts[i] len_value = d_valuation.getModelValue(lts[i]); } if (len_value.isNull()) { lts_values.push_back(Node::null()); } else { Assert(len_value.getConst() <= Rational(String::maxSize())) << "Exceeded UINT32_MAX in string model"; unsigned lvalue = len_value.getConst().getNumerator().toUnsignedInt(); std::map::iterator itvu = values_used.find(lvalue); if (itvu == values_used.end()) { values_used[lvalue] = lts[i]; } else { len_splits.push_back(lts[i].eqNode(itvu->second)); } lts_values.push_back(len_value); } } ////step 2 : assign arbitrary values for unknown lengths? // confirmed by calculus invariant, see paper Trace("strings-model") << "Assign to equivalence classes..." << std::endl; std::map pure_eq_assign; //step 3 : assign values to equivalence classes that are pure variables for( unsigned i=0; i pure_eq; Trace("strings-model") << "The (" << col[i].size() << ") equivalence classes "; for (const Node& eqc : col[i]) { Trace("strings-model") << eqc << " "; //check if col[i][j] has only variables if (!eqc.isConst()) { NormalForm& nfe = d_csolver.getNormalForm(eqc); if (nfe.d_nf.size() == 1) { // does it have a code and the length of these equivalence classes are // one? if (d_has_str_code && lts_values[i] == d_one) { EqcInfo* eip = d_state.getOrMakeEqcInfo(eqc, false); if (eip && !eip->d_codeTerm.get().isNull()) { // its value must be equal to its code Node ct = nm->mkNode(kind::STRING_CODE, eip->d_codeTerm.get()); Node ctv = d_valuation.getModelValue(ct); unsigned cvalue = ctv.getConst().getNumerator().toUnsignedInt(); Trace("strings-model") << "(code: " << cvalue << ") "; std::vector vec; vec.push_back(String::convertCodeToUnsignedInt(cvalue)); Node mv = nm->mkConst(String(vec)); pure_eq_assign[eqc] = mv; m->getEqualityEngine()->addTerm(mv); } } pure_eq.push_back(eqc); } } else { processed[eqc] = eqc; } } Trace("strings-model") << "have length " << lts_values[i] << std::endl; //assign a new length if necessary if( !pure_eq.empty() ){ if( lts_values[i].isNull() ){ // start with length two (other lengths have special precendence) unsigned lvalue = 2; while( values_used.find( lvalue )!=values_used.end() ){ lvalue++; } Trace("strings-model") << "*** Decide to make length of " << lvalue << std::endl; lts_values[i] = nm->mkConst(Rational(lvalue)); values_used[lvalue] = Node::null(); } Trace("strings-model") << "Need to assign values of length " << lts_values[i] << " to equivalence classes "; for( unsigned j=0; j() <= Rational(String::maxSize())) << "Exceeded UINT32_MAX in string model"; StringEnumeratorLength sel(lts_values[i].getConst().getNumerator().toUnsignedInt()); for (const Node& eqc : pure_eq) { Node c; std::map::iterator itp = pure_eq_assign.find(eqc); if (itp == pure_eq_assign.end()) { do { if (sel.isFinished()) { // We are in a case where model construction is impossible due to // an insufficient number of constants of a given length. // Consider an integer equivalence class E whose value is assigned // n in the model. Let { S_1, ..., S_m } be the set of string // equivalence classes such that len( x ) is a member of E for // some member x of each class S1, ...,Sm. Since our calculus is // saturated with respect to cardinality inference (see Liang // et al, Figure 6, CAV 2014), we have that m <= A^n, where A is // the cardinality of our alphabet. // Now, consider the case where there exists two integer // equivalence classes E1 and E2 that are assigned n, and moreover // we did not received notification from arithmetic that E1 = E2. // This typically should never happen, but assume in the following // that it does. // Now, it may be the case that there are string equivalence // classes { S_1, ..., S_m1 } whose lengths are in E1, // and classes { S'_1, ..., S'_m2 } whose lengths are in E2, where // m1 + m2 > A^n. In this case, we have insufficient strings to // assign to { S_1, ..., S_m1, S'_1, ..., S'_m2 }. If this // happens, we add a split on len( u1 ) = len( u2 ) for some // len( u1 ) in E1, len( u2 ) in E2. We do this for each pair of // integer equivalence classes that are assigned to the same value // in the model. AlwaysAssert(!len_splits.empty()); for (const Node& sl : len_splits) { Node spl = nm->mkNode(OR, sl, sl.negate()); d_out->lemma(spl); } return false; } c = *sel; ++sel; } while (m->hasTerm(c)); } else { c = itp->second; } Trace("strings-model") << "*** Assigned constant " << c << " for " << eqc << std::endl; processed[eqc] = c; if (!m->assertEquality(eqc, c, true)) { return false; } } } } Trace("strings-model") << "String Model : Pure Assigned." << std::endl; //step 4 : assign constants to all other equivalence classes for( unsigned i=0; i 0) { Trace("strings-model") << " ++ "; } Trace("strings-model") << n; Node r = d_state.getRepresentative(n); if (!r.isConst() && processed.find(r) == processed.end()) { Trace("strings-model") << "(UNPROCESSED)"; } } } Trace("strings-model") << std::endl; std::vector< Node > nc; for (const Node& n : nf.d_nf) { Node r = d_state.getRepresentative(n); Assert(r.isConst() || processed.find(r) != processed.end()); nc.push_back(r.isConst() ? r : processed[r]); } Node cc = utils::mkNConcat(nc); Assert(cc.getKind() == kind::CONST_STRING); Trace("strings-model") << "*** Determined constant " << cc << " for " << nodes[i] << std::endl; processed[nodes[i]] = cc; if (!m->assertEquality(nodes[i], cc, true)) { return false; } } } //Trace("strings-model") << "String Model : Assigned." << std::endl; Trace("strings-model") << "String Model : Finished." << std::endl; return true; } ///////////////////////////////////////////////////////////////////////////// // MAIN SOLVER ///////////////////////////////////////////////////////////////////////////// void TheoryStrings::preRegisterTerm(TNode n) { if( d_pregistered_terms_cache.find(n) == d_pregistered_terms_cache.end() ) { d_pregistered_terms_cache.insert(n); Trace("strings-preregister") << "TheoryString::preregister : " << n << std::endl; //check for logic exceptions Kind k = n.getKind(); if( !options::stringExp() ){ if (k == kind::STRING_STRIDOF || k == kind::STRING_ITOS || k == kind::STRING_STOI || k == kind::STRING_STRREPL || k == kind::STRING_STRREPLALL || k == kind::STRING_STRCTN || k == STRING_LEQ || k == STRING_TOLOWER || k == STRING_TOUPPER || k == STRING_REV) { std::stringstream ss; ss << "Term of kind " << k << " not supported in default mode, try --strings-exp"; throw LogicException(ss.str()); } } switch (k) { case kind::EQUAL: { d_equalityEngine.addTriggerEquality(n); break; } case kind::STRING_IN_REGEXP: { d_out->requirePhase(n, true); d_equalityEngine.addTriggerPredicate(n); d_equalityEngine.addTerm(n[0]); d_equalityEngine.addTerm(n[1]); break; } default: { registerTerm(n, 0); TypeNode tn = n.getType(); if (tn.isRegExp() && n.isVar()) { std::stringstream ss; ss << "Regular expression variables are not supported."; throw LogicException(ss.str()); } if( tn.isString() ) { // all characters of constants should fall in the alphabet if (n.isConst()) { std::vector vec = n.getConst().getVec(); for (unsigned u : vec) { if (u >= d_card_size) { std::stringstream ss; ss << "Characters in string \"" << n << "\" are outside of the given alphabet."; throw LogicException(ss.str()); } } } d_equalityEngine.addTerm(n); } else if (tn.isBoolean()) { // Get triggered for both equal and dis-equal d_equalityEngine.addTriggerPredicate(n); } else { // Function applications/predicates d_equalityEngine.addTerm(n); } // Set d_functionsTerms stores all function applications that are // relevant to theory combination. Notice that this is a subset of // the applications whose kinds are function kinds in the equality // engine. This means it does not include applications of operators // like re.++, which is not a function kind in the equality engine. // Concatenation terms do not need to be considered here because // their arguments have string type and do not introduce any shared // terms. if (n.hasOperator() && d_equalityEngine.isFunctionKind(k) && k != kind::STRING_CONCAT) { d_functionsTerms.push_back( n ); } } } // register with finite model finding if (options::stringFMF()) { d_stringsFmf.preRegisterTerm(n); } } } Node TheoryStrings::expandDefinition(LogicRequest &logicRequest, Node node) { Trace("strings-exp-def") << "TheoryStrings::expandDefinition : " << node << std::endl; return node; } void TheoryStrings::check(Effort e) { if (done() && e >::iterator itsr = d_strat_steps.find(e); if (!d_state.isInConflict() && !d_valuation.needCheck() && itsr != d_strat_steps.end()) { Trace("strings-check-debug") << "Theory of strings " << e << " effort check " << std::endl; if(Trace.isOn("strings-eqc")) { for( unsigned t=0; t<2; t++ ) { eq::EqClassesIterator eqcs2_i = eq::EqClassesIterator( &d_equalityEngine ); Trace("strings-eqc") << (t==0 ? "STRINGS:" : "OTHER:") << std::endl; while( !eqcs2_i.isFinished() ){ Node eqc = (*eqcs2_i); bool print = (t==0 && eqc.getType().isString() ) || (t==1 && !eqc.getType().isString() ); if (print) { eq::EqClassIterator eqc2_i = eq::EqClassIterator( eqc, &d_equalityEngine ); Trace("strings-eqc") << "Eqc( " << eqc << " ) : { "; while( !eqc2_i.isFinished() ) { if( (*eqc2_i)!=eqc && (*eqc2_i).getKind()!=kind::EQUAL ){ Trace("strings-eqc") << (*eqc2_i) << " "; } ++eqc2_i; } Trace("strings-eqc") << " } " << std::endl; EqcInfo* ei = d_state.getOrMakeEqcInfo(eqc, false); if( ei ){ Trace("strings-eqc-debug") << "* Length term : " << ei->d_lengthTerm.get() << std::endl; Trace("strings-eqc-debug") << "* Cardinality lemma k : " << ei->d_cardinalityLemK.get() << std::endl; Trace("strings-eqc-debug") << "* Normalization length lemma : " << ei->d_normalizedLength.get() << std::endl; } } ++eqcs2_i; } Trace("strings-eqc") << std::endl; } Trace("strings-eqc") << std::endl; } unsigned sbegin = itsr->second.first; unsigned send = itsr->second.second; bool addedLemma = false; bool addedFact; Trace("strings-check") << "Full effort check..." << std::endl; do{ Trace("strings-check") << " * Run strategy..." << std::endl; runStrategy(sbegin, send); // flush the facts addedFact = d_im.hasPendingFact(); addedLemma = d_im.hasPendingLemma(); d_im.doPendingFacts(); d_im.doPendingLemmas(); if (Trace.isOn("strings-check")) { Trace("strings-check") << " ...finish run strategy: "; Trace("strings-check") << (addedFact ? "addedFact " : ""); Trace("strings-check") << (addedLemma ? "addedLemma " : ""); Trace("strings-check") << (d_state.isInConflict() ? "conflict " : ""); if (!addedFact && !addedLemma && !d_state.isInConflict()) { Trace("strings-check") << "(none)"; } Trace("strings-check") << std::endl; } // repeat if we did not add a lemma or conflict } while (!d_state.isInConflict() && !addedLemma && addedFact); } Trace("strings-check") << "Theory of strings, done check : " << e << std::endl; Assert(!d_im.hasPendingFact()); Assert(!d_im.hasPendingLemma()); } bool TheoryStrings::needsCheckLastEffort() { if( options::stringGuessModel() ){ return d_has_extf.get(); }else{ return false; } } void TheoryStrings::checkExtfReductions( int effort ) { // Notice we don't make a standard call to ExtTheory::doReductions here, // since certain optimizations like context-dependent reductions and // stratifying effort levels are done in doReduction below. std::vector< Node > extf = getExtTheory()->getActive(); Trace("strings-process") << " checking " << extf.size() << " active extf" << std::endl; for( unsigned i=0; iconflict( conflictNode ); } } /** called when a new equivalance class is created */ void TheoryStrings::eqNotifyNewClass(TNode t){ Kind k = t.getKind(); if (k == kind::STRING_LENGTH || k == kind::STRING_CODE) { Trace("strings-debug") << "New length eqc : " << t << std::endl; Node r = d_equalityEngine.getRepresentative(t[0]); EqcInfo* ei = d_state.getOrMakeEqcInfo(r); if (k == kind::STRING_LENGTH) { ei->d_lengthTerm = t[0]; } else { ei->d_codeTerm = t[0]; } //we care about the length of this string registerTerm( t[0], 1 ); return; } else if (k == CONST_STRING) { EqcInfo* ei = d_state.getOrMakeEqcInfo(t); ei->d_prefixC = t; ei->d_suffixC = t; return; } else if (k == STRING_CONCAT) { d_state.addEndpointsToEqcInfo(t, t, t); } } /** called when two equivalance classes will merge */ void TheoryStrings::eqNotifyPreMerge(TNode t1, TNode t2){ d_state.eqNotifyPreMerge(t1, t2); } /** called when two equivalance classes have merged */ void TheoryStrings::eqNotifyPostMerge(TNode t1, TNode t2) { } /** called when two equivalance classes are disequal */ void TheoryStrings::eqNotifyDisequal(TNode t1, TNode t2, TNode reason) { d_state.eqNotifyDisequal(t1, t2, reason); } void TheoryStrings::addCarePairs(TNodeTrie* t1, TNodeTrie* t2, unsigned arity, unsigned depth) { if( depth==arity ){ if( t2!=NULL ){ Node f1 = t1->getData(); Node f2 = t2->getData(); if( !d_equalityEngine.areEqual( f1, f2 ) ){ Trace("strings-cg-debug") << "TheoryStrings::computeCareGraph(): checking function " << f1 << " and " << f2 << std::endl; vector< pair > currentPairs; for (unsigned k = 0; k < f1.getNumChildren(); ++ k) { TNode x = f1[k]; TNode y = f2[k]; Assert(d_equalityEngine.hasTerm(x)); Assert(d_equalityEngine.hasTerm(y)); Assert(!d_equalityEngine.areDisequal(x, y, false)); Assert(!areCareDisequal(x, y)); if( !d_equalityEngine.areEqual( x, y ) ){ if( d_equalityEngine.isTriggerTerm(x, THEORY_STRINGS) && d_equalityEngine.isTriggerTerm(y, THEORY_STRINGS) ){ TNode x_shared = d_equalityEngine.getTriggerTermRepresentative(x, THEORY_STRINGS); TNode y_shared = d_equalityEngine.getTriggerTermRepresentative(y, THEORY_STRINGS); currentPairs.push_back(make_pair(x_shared, y_shared)); } } } for (unsigned c = 0; c < currentPairs.size(); ++ c) { Trace("strings-cg-pair") << "TheoryStrings::computeCareGraph(): pair : " << currentPairs[c].first << " " << currentPairs[c].second << std::endl; addCarePair(currentPairs[c].first, currentPairs[c].second); } } } }else{ if( t2==NULL ){ if( depth<(arity-1) ){ //add care pairs internal to each child for (std::pair& tt : t1->d_data) { addCarePairs(&tt.second, nullptr, arity, depth + 1); } } //add care pairs based on each pair of non-disequal arguments for (std::map::iterator it = t1->d_data.begin(); it != t1->d_data.end(); ++it) { std::map::iterator it2 = it; ++it2; for( ; it2 != t1->d_data.end(); ++it2 ){ if( !d_equalityEngine.areDisequal(it->first, it2->first, false) ){ if( !areCareDisequal(it->first, it2->first) ){ addCarePairs( &it->second, &it2->second, arity, depth+1 ); } } } } }else{ //add care pairs based on product of indices, non-disequal arguments for (std::pair& tt1 : t1->d_data) { for (std::pair& tt2 : t2->d_data) { if (!d_equalityEngine.areDisequal(tt1.first, tt2.first, false)) { if (!areCareDisequal(tt1.first, tt2.first)) { addCarePairs(&tt1.second, &tt2.second, arity, depth + 1); } } } } } } } void TheoryStrings::computeCareGraph(){ //computing the care graph here is probably still necessary, due to operators that take non-string arguments TODO: verify Trace("strings-cg") << "TheoryStrings::computeCareGraph(): Build term indices..." << std::endl; std::map index; std::map< Node, unsigned > arity; unsigned functionTerms = d_functionsTerms.size(); for (unsigned i = 0; i < functionTerms; ++ i) { TNode f1 = d_functionsTerms[i]; Trace("strings-cg") << "...build for " << f1 << std::endl; Node op = f1.getOperator(); std::vector< TNode > reps; bool has_trigger_arg = false; for( unsigned j=0; j& tt : index) { Trace("strings-cg") << "TheoryStrings::computeCareGraph(): Process index " << tt.first << "..." << std::endl; addCarePairs(&tt.second, nullptr, arity[tt.first], 0); } } void TheoryStrings::assertPendingFact(Node atom, bool polarity, Node exp) { Trace("strings-pending") << "Assert pending fact : " << atom << " " << polarity << " from " << exp << std::endl; Assert(atom.getKind() != kind::OR) << "Infer error: a split."; if( atom.getKind()==kind::EQUAL ){ Trace("strings-pending-debug") << " Register term" << std::endl; for( unsigned j=0; j<2; j++ ) { if( !d_equalityEngine.hasTerm( atom[j] ) && atom[j].getType().isString() ) { registerTerm( atom[j], 0 ); } } Trace("strings-pending-debug") << " Now assert equality" << std::endl; d_equalityEngine.assertEquality( atom, polarity, exp ); Trace("strings-pending-debug") << " Finished assert equality" << std::endl; } else { d_equalityEngine.assertPredicate( atom, polarity, exp ); if (atom.getKind() == STRING_IN_REGEXP) { if (polarity && atom[1].getKind() == REGEXP_CONCAT) { Node eqc = d_equalityEngine.getRepresentative(atom[0]); d_state.addEndpointsToEqcInfo(atom, atom[1], eqc); } } } // process the conflict if (!d_state.isInConflict()) { Node pc = d_state.getPendingConflict(); if (!pc.isNull()) { std::vector a; a.push_back(pc); Trace("strings-pending") << "Process pending conflict " << pc << std::endl; Node conflictNode = d_im.mkExplain(a); d_state.setConflict(); Trace("strings-conflict") << "CONFLICT: Eager prefix : " << conflictNode << std::endl; d_out->conflict(conflictNode); } } Trace("strings-pending-debug") << " Now collect terms" << std::endl; // Collect extended function terms in the atom. Notice that we must register // all extended functions occurring in assertions and shared terms. We // make a similar call to registerTermRec in addSharedTerm. getExtTheory()->registerTermRec( atom ); Trace("strings-pending-debug") << " Finished collect terms" << std::endl; } void TheoryStrings::checkExtfEval( int effort ) { Trace("strings-extf-list") << "Active extended functions, effort=" << effort << " : " << std::endl; d_extf_info_tmp.clear(); NodeManager* nm = NodeManager::currentNM(); bool has_nreduce = false; std::vector< Node > terms = getExtTheory()->getActive(); for (const Node& n : terms) { // Setup information about n, including if it is equal to a constant. ExtfInfoTmp& einfo = d_extf_info_tmp[n]; Node r = d_state.getRepresentative(n); einfo.d_const = d_bsolver.getConstantEqc(r); // Get the current values of the children of n. // Notice that we look up the value of the direct children of n, and not // their free variables. In other words, given a term: // t = (str.replace "B" (str.replace x "A" "B") "C") // we may build the explanation that: // ((str.replace x "A" "B") = "B") => t = (str.replace "B" "B" "C") // instead of basing this on the free variable x: // (x = "A") => t = (str.replace "B" (str.replace "A" "A" "B") "C") // Although both allow us to infer t = "C", it is important to use the // first kind of inference since it ensures that its subterms have the // expected values. Otherwise, we may in rare cases fail to realize that // the subterm (str.replace x "A" "B") does not currently have the correct // value, say in this example that (str.replace x "A" "B") != "B". std::vector exp; std::vector schildren; bool schanged = false; for (const Node& nc : n) { Node sc = getCurrentSubstitutionFor(effort, nc, exp); schildren.push_back(sc); schanged = schanged || sc != nc; } // If there is information involving the children, attempt to do an // inference and/or mark n as reduced. Node to_reduce; if (schanged) { Node sn = nm->mkNode(n.getKind(), schildren); Trace("strings-extf-debug") << "Check extf " << n << " == " << sn << ", constant = " << einfo.d_const << ", effort=" << effort << "..." << std::endl; einfo.d_exp.insert(einfo.d_exp.end(), exp.begin(), exp.end()); // inference is rewriting the substituted node Node nrc = Rewriter::rewrite( sn ); //if rewrites to a constant, then do the inference and mark as reduced if( nrc.isConst() ){ if( effort<3 ){ getExtTheory()->markReduced( n ); Trace("strings-extf-debug") << " resolvable by evaluation..." << std::endl; std::vector< Node > exps; // The following optimization gets the "symbolic definition" of // an extended term. The symbolic definition of a term t is a term // t' where constants are replaced by their corresponding proxy // variables. // For example, if lsym is a proxy variable for "", then // str.replace( lsym, lsym, lsym ) is the symbolic definition for // str.replace( "", "", "" ). It is generally better to use symbolic // definitions when doing cd-rewriting for the purpose of minimizing // clauses, e.g. we infer the unit equality: // str.replace( lsym, lsym, lsym ) == "" // instead of making this inference multiple times: // x = "" => str.replace( x, x, x ) == "" // y = "" => str.replace( y, y, y ) == "" Trace("strings-extf-debug") << " get symbolic definition..." << std::endl; Node nrs; // only use symbolic definitions if option is set if (options::stringInferSym()) { nrs = getSymbolicDefinition(sn, exps); } if( !nrs.isNull() ){ Trace("strings-extf-debug") << " rewrite " << nrs << "..." << std::endl; Node nrsr = Rewriter::rewrite(nrs); // ensure the symbolic form is not rewritable if (nrsr != nrs) { // we cannot use the symbolic definition if it rewrites Trace("strings-extf-debug") << " symbolic definition is trivial..." << std::endl; nrs = Node::null(); } }else{ Trace("strings-extf-debug") << " could not infer symbolic definition." << std::endl; } Node conc; if( !nrs.isNull() ){ Trace("strings-extf-debug") << " symbolic def : " << nrs << std::endl; if (!d_state.areEqual(nrs, nrc)) { //infer symbolic unit if( n.getType().isBoolean() ){ conc = nrc==d_true ? nrs : nrs.negate(); }else{ conc = nrs.eqNode( nrc ); } einfo.d_exp.clear(); } }else{ if (!d_state.areEqual(n, nrc)) { if( n.getType().isBoolean() ){ if (d_state.areEqual(n, nrc == d_true ? d_false : d_true)) { einfo.d_exp.push_back(nrc == d_true ? n.negate() : n); conc = d_false; } else { conc = nrc==d_true ? n : n.negate(); } }else{ conc = n.eqNode( nrc ); } } } if( !conc.isNull() ){ Trace("strings-extf") << " resolve extf : " << sn << " -> " << nrc << std::endl; d_im.sendInference( einfo.d_exp, conc, effort == 0 ? "EXTF" : "EXTF-N", true); if (d_state.isInConflict()) { Trace("strings-extf-debug") << " conflict, return." << std::endl; return; } } }else{ //check if it is already equal, if so, mark as reduced. Otherwise, do nothing. if (d_state.areEqual(n, nrc)) { Trace("strings-extf") << " resolved extf, since satisfied by model: " << n << std::endl; einfo.d_model_active = false; } } } else { // if this was a predicate which changed after substitution + rewriting if (!einfo.d_const.isNull() && nrc.getType().isBoolean() && nrc != n) { bool pol = einfo.d_const == d_true; Node nrcAssert = pol ? nrc : nrc.negate(); Node nAssert = pol ? n : n.negate(); Assert(effort < 3); einfo.d_exp.push_back(nAssert); Trace("strings-extf-debug") << " decomposable..." << std::endl; Trace("strings-extf") << " resolve extf : " << sn << " -> " << nrc << ", const = " << einfo.d_const << std::endl; // We send inferences internal here, which may help show unsat. // However, we do not make a determination whether n can be marked // reduced since this argument may be circular: we may infer than n // can be reduced to something else, but that thing may argue that it // can be reduced to n, in theory. d_im.sendInternalInference( einfo.d_exp, nrcAssert, effort == 0 ? "EXTF_d" : "EXTF_d-N"); } to_reduce = nrc; } } else { to_reduce = n; } //if not reduced if( !to_reduce.isNull() ){ Assert(effort < 3); if( effort==1 ){ Trace("strings-extf") << " cannot rewrite extf : " << to_reduce << std::endl; } checkExtfInference(n, to_reduce, einfo, effort); if( Trace.isOn("strings-extf-list") ){ Trace("strings-extf-list") << " * " << to_reduce; if (!einfo.d_const.isNull()) { Trace("strings-extf-list") << ", const = " << einfo.d_const; } if( n!=to_reduce ){ Trace("strings-extf-list") << ", from " << n; } Trace("strings-extf-list") << std::endl; } if (getExtTheory()->isActive(n) && einfo.d_model_active) { has_nreduce = true; } } } d_has_extf = has_nreduce; } void TheoryStrings::checkExtfInference( Node n, Node nr, ExtfInfoTmp& in, int effort ){ if (in.d_const.isNull()) { return; } NodeManager* nm = NodeManager::currentNM(); Trace("strings-extf-infer") << "checkExtfInference: " << n << " : " << nr << " == " << in.d_const << std::endl; // add original to explanation if (n.getType().isBoolean()) { // if Boolean, it's easy in.d_exp.push_back(in.d_const.getConst() ? n : n.negate()); } else { // otherwise, must explain via base node Node r = d_state.getRepresentative(n); // explain using the base solver d_bsolver.explainConstantEqc(n, r, in.d_exp); } // d_extf_infer_cache stores whether we have made the inferences associated // with a node n, // this may need to be generalized if multiple inferences apply if (nr.getKind() == STRING_STRCTN) { Assert(in.d_const.isConst()); bool pol = in.d_const.getConst(); if ((pol && nr[1].getKind() == STRING_CONCAT) || (!pol && nr[0].getKind() == STRING_CONCAT)) { // If str.contains( x, str.++( y1, ..., yn ) ), // we may infer str.contains( x, y1 ), ..., str.contains( x, yn ) // The following recognizes two situations related to the above reasoning: // (1) If ~str.contains( x, yi ) holds for some i, we are in conflict, // (2) If str.contains( x, yj ) already holds for some j, then the term // str.contains( x, yj ) is irrelevant since it is satisfied by all models // for str.contains( x, str.++( y1, ..., yn ) ). // Notice that the dual of the above reasoning also holds, i.e. // If ~str.contains( str.++( x1, ..., xn ), y ), // we may infer ~str.contains( x1, y ), ..., ~str.contains( xn, y ) // This is also handled here. if (d_extf_infer_cache.find(nr) == d_extf_infer_cache.end()) { d_extf_infer_cache.insert(nr); int index = pol ? 1 : 0; std::vector children; children.push_back(nr[0]); children.push_back(nr[1]); for (const Node& nrc : nr[index]) { children[index] = nrc; Node conc = nm->mkNode(STRING_STRCTN, children); conc = Rewriter::rewrite(pol ? conc : conc.negate()); // check if it already (does not) hold if (d_state.hasTerm(conc)) { if (d_state.areEqual(conc, d_false)) { // we are in conflict d_im.sendInference(in.d_exp, conc, "CTN_Decompose"); } else if (getExtTheory()->hasFunctionKind(conc.getKind())) { // can mark as reduced, since model for n implies model for conc getExtTheory()->markReduced(conc); } } } } } else { if (std::find(d_extf_info_tmp[nr[0]].d_ctn[pol].begin(), d_extf_info_tmp[nr[0]].d_ctn[pol].end(), nr[1]) == d_extf_info_tmp[nr[0]].d_ctn[pol].end()) { Trace("strings-extf-debug") << " store contains info : " << nr[0] << " " << pol << " " << nr[1] << std::endl; // Store s (does not) contains t, since nr = (~)contains( s, t ) holds. d_extf_info_tmp[nr[0]].d_ctn[pol].push_back(nr[1]); d_extf_info_tmp[nr[0]].d_ctn_from[pol].push_back(n); // Do transistive closure on contains, e.g. // if contains( s, t ) and ~contains( s, r ), then ~contains( t, r ). // The following infers new (negative) contains based on the above // reasoning, provided that ~contains( t, r ) does not // already hold in the current context. We test this by checking that // contains( t, r ) is not already asserted false in the current // context. We also handle the case where contains( t, r ) is equivalent // to t = r, in which case we check that t != r does not already hold // in the current context. // Notice that form of the above inference is enough to find // conflicts purely due to contains predicates. For example, if we // have only positive occurrences of contains, then no conflicts due to // contains predicates are possible and this schema does nothing. For // example, note that contains( s, t ) and contains( t, r ) implies // contains( s, r ), which we could but choose not to infer. Instead, // we prefer being lazy: only if ~contains( s, r ) appears later do we // infer ~contains( t, r ), which suffices to show a conflict. bool opol = !pol; for (unsigned i = 0, size = d_extf_info_tmp[nr[0]].d_ctn[opol].size(); i < size; i++) { Node onr = d_extf_info_tmp[nr[0]].d_ctn[opol][i]; Node concOrig = nm->mkNode(STRING_STRCTN, pol ? nr[1] : onr, pol ? onr : nr[1]); Node conc = Rewriter::rewrite(concOrig); // For termination concerns, we only do the inference if the contains // does not rewrite (and thus does not introduce new terms). if (conc == concOrig) { bool do_infer = false; conc = conc.negate(); bool pol = conc.getKind() != NOT; Node lit = pol ? conc : conc[0]; if (lit.getKind() == EQUAL) { do_infer = pol ? !d_state.areEqual(lit[0], lit[1]) : !d_state.areDisequal(lit[0], lit[1]); } else { do_infer = !d_state.areEqual(lit, pol ? d_true : d_false); } if (do_infer) { std::vector exp_c; exp_c.insert(exp_c.end(), in.d_exp.begin(), in.d_exp.end()); Node ofrom = d_extf_info_tmp[nr[0]].d_ctn_from[opol][i]; Assert(d_extf_info_tmp.find(ofrom) != d_extf_info_tmp.end()); exp_c.insert(exp_c.end(), d_extf_info_tmp[ofrom].d_exp.begin(), d_extf_info_tmp[ofrom].d_exp.end()); d_im.sendInference(exp_c, conc, "CTN_Trans"); } } } } else { // If we already know that s (does not) contain t, then n is redundant. // For example, if str.contains( x, y ), str.contains( z, y ), and x=z // are asserted in the current context, then str.contains( z, y ) is // satisfied by all models of str.contains( x, y ) ^ x=z and thus can // be ignored. Trace("strings-extf-debug") << " redundant." << std::endl; getExtTheory()->markReduced(n); } } return; } // If it's not a predicate, see if we can solve the equality n = c, where c // is the constant that extended term n is equal to. Node inferEq = nr.eqNode(in.d_const); Node inferEqr = Rewriter::rewrite(inferEq); Node inferEqrr = inferEqr; if (inferEqr.getKind() == EQUAL) { // try to use the extended rewriter for equalities inferEqrr = TheoryStringsRewriter::rewriteEqualityExt(inferEqr); } if (inferEqrr != inferEqr) { inferEqrr = Rewriter::rewrite(inferEqrr); Trace("strings-extf-infer") << "checkExtfInference: " << inferEq << " ...reduces to " << inferEqrr << std::endl; d_im.sendInternalInference(in.d_exp, inferEqrr, "EXTF_equality_rew"); } } Node TheoryStrings::getProxyVariableFor(Node n) const { NodeNodeMap::const_iterator it = d_proxy_var.find(n); if (it != d_proxy_var.end()) { return (*it).second; } return Node::null(); } Node TheoryStrings::getSymbolicDefinition(Node n, std::vector& exp) const { if( n.getNumChildren()==0 ){ Node pn = getProxyVariableFor(n); if (pn.isNull()) { return Node::null(); } Node eq = n.eqNode(pn); eq = Rewriter::rewrite(eq); if (std::find(exp.begin(), exp.end(), eq) == exp.end()) { exp.push_back(eq); } return pn; }else{ std::vector< Node > children; if (n.getMetaKind() == kind::metakind::PARAMETERIZED) { children.push_back( n.getOperator() ); } for( unsigned i=0; imkNode( n.getKind(), children ); } } void TheoryStrings::checkRegisterTermsPreNormalForm() { const std::vector& seqc = d_bsolver.getStringEqc(); for (const Node& eqc : seqc) { eq::EqClassIterator eqc_i = eq::EqClassIterator(eqc, &d_equalityEngine); while (!eqc_i.isFinished()) { Node n = (*eqc_i); if (!d_bsolver.isCongruent(n)) { registerTerm(n, 2); } ++eqc_i; } } } void TheoryStrings::checkCodes() { // ensure that lemmas regarding str.code been added for each constant string // of length one if (d_has_str_code) { NodeManager* nm = NodeManager::currentNM(); // str.code applied to the code term for each equivalence class that has a // code term but is not a constant std::vector nconst_codes; // str.code applied to the proxy variables for each equivalence classes that // are constants of size one std::vector const_codes; const std::vector& seqc = d_bsolver.getStringEqc(); for (const Node& eqc : seqc) { NormalForm& nfe = d_csolver.getNormalForm(eqc); if (nfe.d_nf.size() == 1 && nfe.d_nf[0].isConst()) { Node c = nfe.d_nf[0]; Trace("strings-code-debug") << "Get proxy variable for " << c << std::endl; Node cc = nm->mkNode(kind::STRING_CODE, c); cc = Rewriter::rewrite(cc); Assert(cc.isConst()); Node cp = getProxyVariableFor(c); AlwaysAssert(!cp.isNull()); Node vc = nm->mkNode(STRING_CODE, cp); if (!d_state.areEqual(cc, vc)) { d_im.sendInference(d_empty_vec, cc.eqNode(vc), "Code_Proxy"); } const_codes.push_back(vc); } else { EqcInfo* ei = d_state.getOrMakeEqcInfo(eqc, false); if (ei && !ei->d_codeTerm.get().isNull()) { Node vc = nm->mkNode(kind::STRING_CODE, ei->d_codeTerm.get()); nconst_codes.push_back(vc); } } } if (d_im.hasProcessed()) { return; } // now, ensure that str.code is injective std::vector cmps; cmps.insert(cmps.end(), const_codes.rbegin(), const_codes.rend()); cmps.insert(cmps.end(), nconst_codes.rbegin(), nconst_codes.rend()); for (unsigned i = 0, num_ncc = nconst_codes.size(); i < num_ncc; i++) { Node c1 = nconst_codes[i]; cmps.pop_back(); for (const Node& c2 : cmps) { Trace("strings-code-debug") << "Compare codes : " << c1 << " " << c2 << std::endl; if (!d_state.areDisequal(c1, c2) && !d_state.areEqual(c1, d_neg_one)) { Node eq_no = c1.eqNode(d_neg_one); Node deq = c1.eqNode(c2).negate(); Node eqn = c1[0].eqNode(c2[0]); // str.code(x)==-1 V str.code(x)!=str.code(y) V x==y Node inj_lem = nm->mkNode(kind::OR, eq_no, deq, eqn); d_im.sendPhaseRequirement(deq, false); d_im.sendInference(d_empty_vec, inj_lem, "Code_Inj"); } } } } } void TheoryStrings::registerTerm(Node n, int effort) { TypeNode tn = n.getType(); bool do_register = true; if (!tn.isString()) { if (options::stringEagerLen()) { do_register = effort == 0; } else { do_register = effort > 0 || n.getKind() != STRING_CONCAT; } } if (!do_register) { return; } if (d_registered_terms_cache.find(n) != d_registered_terms_cache.end()) { return; } d_registered_terms_cache.insert(n); NodeManager* nm = NodeManager::currentNM(); Debug("strings-register") << "TheoryStrings::registerTerm() " << n << ", effort = " << effort << std::endl; if (tn.isString()) { // register length information: // for variables, split on empty vs positive length // for concat/const/replace, introduce proxy var and state length relation Node lsum; if (n.getKind() != STRING_CONCAT && n.getKind() != CONST_STRING) { Node lsumb = nm->mkNode(STRING_LENGTH, n); lsum = Rewriter::rewrite(lsumb); // can register length term if it does not rewrite if (lsum == lsumb) { d_im.registerLength(n, LENGTH_SPLIT); return; } } Node sk = d_sk_cache.mkSkolemCached(n, SkolemCache::SK_PURIFY, "lsym"); StringsProxyVarAttribute spva; sk.setAttribute(spva, true); Node eq = Rewriter::rewrite(sk.eqNode(n)); Trace("strings-lemma") << "Strings::Lemma LENGTH Term : " << eq << std::endl; d_proxy_var[n] = sk; // If we are introducing a proxy for a constant or concat term, we do not // need to send lemmas about its length, since its length is already // implied. if (n.isConst() || n.getKind() == STRING_CONCAT) { // do not send length lemma for sk. d_im.registerLength(sk, LENGTH_IGNORE); } Trace("strings-assert") << "(assert " << eq << ")" << std::endl; d_out->lemma(eq); Node skl = nm->mkNode(STRING_LENGTH, sk); if (n.getKind() == STRING_CONCAT) { std::vector node_vec; for (unsigned i = 0; i < n.getNumChildren(); i++) { if (n[i].getAttribute(StringsProxyVarAttribute())) { Assert(d_proxy_var_to_length.find(n[i]) != d_proxy_var_to_length.end()); node_vec.push_back(d_proxy_var_to_length[n[i]]); } else { Node lni = nm->mkNode(STRING_LENGTH, n[i]); node_vec.push_back(lni); } } lsum = nm->mkNode(PLUS, node_vec); lsum = Rewriter::rewrite(lsum); } else if (n.getKind() == CONST_STRING) { lsum = nm->mkConst(Rational(n.getConst().size())); } Assert(!lsum.isNull()); d_proxy_var_to_length[sk] = lsum; Node ceq = Rewriter::rewrite(skl.eqNode(lsum)); Trace("strings-lemma") << "Strings::Lemma LENGTH : " << ceq << std::endl; Trace("strings-lemma-debug") << " prerewrite : " << skl.eqNode(lsum) << std::endl; Trace("strings-assert") << "(assert " << ceq << ")" << std::endl; d_out->lemma(ceq); } else if (n.getKind() == STRING_CODE) { d_has_str_code = true; // ite( str.len(s)==1, 0 <= str.code(s) < num_codes, str.code(s)=-1 ) Node code_len = utils::mkNLength(n[0]).eqNode(d_one); Node code_eq_neg1 = n.eqNode(d_neg_one); Node code_range = nm->mkNode( AND, nm->mkNode(GEQ, n, d_zero), nm->mkNode(LT, n, nm->mkConst(Rational(CVC4::String::num_codes())))); Node lem = nm->mkNode(ITE, code_len, code_range, code_eq_neg1); Trace("strings-lemma") << "Strings::Lemma CODE : " << lem << std::endl; Trace("strings-assert") << "(assert " << lem << ")" << std::endl; d_out->lemma(lem); } else if (n.getKind() == STRING_STRIDOF) { Node len = utils::mkNLength(n[0]); Node lem = nm->mkNode(AND, nm->mkNode(GEQ, n, nm->mkConst(Rational(-1))), nm->mkNode(LEQ, n, len)); Trace("strings-lemma") << "Strings::Lemma IDOF range : " << lem << std::endl; d_out->lemma(lem); } } void TheoryStrings::checkRegisterTermsNormalForms() { const std::vector& seqc = d_bsolver.getStringEqc(); for (const Node& eqc : seqc) { NormalForm& nfi = d_csolver.getNormalForm(eqc); // check if there is a length term for this equivalence class EqcInfo* ei = d_state.getOrMakeEqcInfo(eqc, false); Node lt = ei ? ei->d_lengthTerm : Node::null(); if (lt.isNull()) { Node c = utils::mkNConcat(nfi.d_nf); registerTerm(c, 3); } } } void TheoryStrings::checkCardinality() { //int cardinality = options::stringCharCardinality(); //Trace("strings-solve-debug2") << "get cardinality: " << cardinality << endl; //AJR: this will create a partition of eqc, where each collection has length that are pairwise propagated to be equal. // we do not require disequalities between the lengths of each collection, since we split on disequalities between lengths of string terms that are disequal (DEQ-LENGTH-SP). // TODO: revisit this? const std::vector& seqc = d_bsolver.getStringEqc(); std::vector< std::vector< Node > > cols; std::vector< Node > lts; d_state.separateByLength(seqc, cols, lts); Trace("strings-card") << "Check cardinality...." << std::endl; for( unsigned i = 0; i 1 ) { // size > c^k unsigned card_need = 1; double curr = (double)cols[i].size(); while( curr>d_card_size ){ curr = curr/(double)d_card_size; card_need++; } Trace("strings-card") << "Need length " << card_need << " for this number of strings (where alphabet size is " << d_card_size << ")." << std::endl; //check if we need to split bool needsSplit = true; if( lr.isConst() ){ // if constant, compare Node cmp = NodeManager::currentNM()->mkNode( kind::GEQ, lr, NodeManager::currentNM()->mkConst( Rational( card_need ) ) ); cmp = Rewriter::rewrite( cmp ); needsSplit = cmp!=d_true; }else{ // find the minimimum constant that we are unknown to be disequal from, or otherwise stop if we increment such that cardinality does not apply unsigned r=0; bool success = true; while( rmkConst( Rational(r) ); if (d_state.areDisequal(rr, lr)) { r++; } else { success = false; } } if( r>0 ){ Trace("strings-card") << "Symbolic length " << lr << " must be at least " << r << " due to constant disequalities." << std::endl; } needsSplit = r::iterator itr1 = cols[i].begin(); itr1 != cols[i].end(); ++itr1) { for( std::vector< Node >::iterator itr2 = itr1 + 1; itr2 != cols[i].end(); ++itr2) { if (!d_state.areDisequal(*itr1, *itr2)) { // add split lemma if (d_im.sendSplit(*itr1, *itr2, "CARD-SP")) { return; } } } } EqcInfo* ei = d_state.getOrMakeEqcInfo(lr, true); Trace("strings-card") << "Previous cardinality used for " << lr << " is " << ((int)ei->d_cardinalityLemK.get() - 1) << std::endl; if (int_k + 1 > ei->d_cardinalityLemK.get()) { Node k_node = NodeManager::currentNM()->mkConst( ::CVC4::Rational( int_k ) ); //add cardinality lemma Node dist = NodeManager::currentNM()->mkNode( kind::DISTINCT, cols[i] ); std::vector< Node > vec_node; vec_node.push_back( dist ); for( std::vector< Node >::iterator itr1 = cols[i].begin(); itr1 != cols[i].end(); ++itr1) { Node len = NodeManager::currentNM()->mkNode( kind::STRING_LENGTH, *itr1 ); if( len!=lr ) { Node len_eq_lr = len.eqNode(lr); vec_node.push_back( len_eq_lr ); } } Node len = NodeManager::currentNM()->mkNode( kind::STRING_LENGTH, cols[i][0] ); Node cons = NodeManager::currentNM()->mkNode( kind::GEQ, len, k_node ); cons = Rewriter::rewrite( cons ); ei->d_cardinalityLemK.set(int_k + 1); if( cons!=d_true ){ d_im.sendInference( d_empty_vec, vec_node, cons, "CARDINALITY", true); return; } } } } } Trace("strings-card") << "...end check cardinality" << std::endl; } Node TheoryStrings::ppRewrite(TNode atom) { Trace("strings-ppr") << "TheoryStrings::ppRewrite " << atom << std::endl; Node atomElim; if (options::regExpElim() && atom.getKind() == STRING_IN_REGEXP) { // aggressive elimination of regular expression membership atomElim = d_regexp_elim.eliminate(atom); if (!atomElim.isNull()) { Trace("strings-ppr") << " rewrote " << atom << " -> " << atomElim << " via regular expression elimination." << std::endl; atom = atomElim; } } if( !options::stringLazyPreproc() ){ //eager preprocess here std::vector< Node > new_nodes; Node ret = d_preproc.processAssertion( atom, new_nodes ); if( ret!=atom ){ Trace("strings-ppr") << " rewrote " << atom << " -> " << ret << ", with " << new_nodes.size() << " lemmas." << std::endl; for( unsigned i=0; ilemma( new_nodes[i] ); } return ret; }else{ Assert(new_nodes.empty()); } } return atom; } // Stats TheoryStrings::Statistics::Statistics() : d_splits("theory::strings::NumOfSplitOnDemands", 0), d_eq_splits("theory::strings::NumOfEqSplits", 0), d_deq_splits("theory::strings::NumOfDiseqSplits", 0), d_loop_lemmas("theory::strings::NumOfLoops", 0) { smtStatisticsRegistry()->registerStat(&d_splits); smtStatisticsRegistry()->registerStat(&d_eq_splits); smtStatisticsRegistry()->registerStat(&d_deq_splits); smtStatisticsRegistry()->registerStat(&d_loop_lemmas); } TheoryStrings::Statistics::~Statistics(){ smtStatisticsRegistry()->unregisterStat(&d_splits); smtStatisticsRegistry()->unregisterStat(&d_eq_splits); smtStatisticsRegistry()->unregisterStat(&d_deq_splits); smtStatisticsRegistry()->unregisterStat(&d_loop_lemmas); } /** run the given inference step */ void TheoryStrings::runInferStep(InferStep s, int effort) { Trace("strings-process") << "Run " << s; if (effort > 0) { Trace("strings-process") << ", effort = " << effort; } Trace("strings-process") << "..." << std::endl; switch (s) { case CHECK_INIT: d_bsolver.checkInit(); break; case CHECK_CONST_EQC: d_bsolver.checkConstantEquivalenceClasses(); break; case CHECK_EXTF_EVAL: checkExtfEval(effort); break; case CHECK_CYCLES: d_csolver.checkCycles(); break; case CHECK_FLAT_FORMS: d_csolver.checkFlatForms(); break; case CHECK_REGISTER_TERMS_PRE_NF: checkRegisterTermsPreNormalForm(); break; case CHECK_NORMAL_FORMS_EQ: d_csolver.checkNormalFormsEq(); break; case CHECK_NORMAL_FORMS_DEQ: d_csolver.checkNormalFormsDeq(); break; case CHECK_CODES: checkCodes(); break; case CHECK_LENGTH_EQC: d_csolver.checkLengthsEqc(); break; case CHECK_REGISTER_TERMS_NF: checkRegisterTermsNormalForms(); break; case CHECK_EXTF_REDUCTION: checkExtfReductions(effort); break; case CHECK_MEMBERSHIP: checkMemberships(); break; case CHECK_CARDINALITY: checkCardinality(); break; default: Unreachable(); break; } Trace("strings-process") << "Done " << s << ", addedFact = " << d_im.hasPendingFact() << ", addedLemma = " << d_im.hasPendingLemma() << ", conflict = " << d_state.isInConflict() << std::endl; } bool TheoryStrings::hasStrategyEffort(Effort e) const { return d_strat_steps.find(e) != d_strat_steps.end(); } void TheoryStrings::addStrategyStep(InferStep s, int effort, bool addBreak) { // must run check init first Assert((s == CHECK_INIT) == d_infer_steps.empty()); // must use check cycles when using flat forms Assert(s != CHECK_FLAT_FORMS || std::find(d_infer_steps.begin(), d_infer_steps.end(), CHECK_CYCLES) != d_infer_steps.end()); d_infer_steps.push_back(s); d_infer_step_effort.push_back(effort); if (addBreak) { d_infer_steps.push_back(BREAK); d_infer_step_effort.push_back(0); } } void TheoryStrings::initializeStrategy() { // initialize the strategy if not already done so if (!d_strategy_init) { std::map step_begin; std::map step_end; d_strategy_init = true; // beginning indices step_begin[EFFORT_FULL] = 0; if (options::stringEager()) { step_begin[EFFORT_STANDARD] = 0; } // add the inference steps addStrategyStep(CHECK_INIT); addStrategyStep(CHECK_CONST_EQC); addStrategyStep(CHECK_EXTF_EVAL, 0); addStrategyStep(CHECK_CYCLES); if (options::stringFlatForms()) { addStrategyStep(CHECK_FLAT_FORMS); } addStrategyStep(CHECK_EXTF_REDUCTION, 1); if (options::stringEager()) { // do only the above inferences at standard effort, if applicable step_end[EFFORT_STANDARD] = d_infer_steps.size() - 1; } if (!options::stringEagerLen()) { addStrategyStep(CHECK_REGISTER_TERMS_PRE_NF); } addStrategyStep(CHECK_NORMAL_FORMS_EQ); addStrategyStep(CHECK_EXTF_EVAL, 1); if (!options::stringEagerLen() && options::stringLenNorm()) { addStrategyStep(CHECK_LENGTH_EQC, 0, false); addStrategyStep(CHECK_REGISTER_TERMS_NF); } addStrategyStep(CHECK_NORMAL_FORMS_DEQ); addStrategyStep(CHECK_CODES); if (options::stringEagerLen() && options::stringLenNorm()) { addStrategyStep(CHECK_LENGTH_EQC); } if (options::stringExp() && !options::stringGuessModel()) { addStrategyStep(CHECK_EXTF_REDUCTION, 2); } addStrategyStep(CHECK_MEMBERSHIP); addStrategyStep(CHECK_CARDINALITY); step_end[EFFORT_FULL] = d_infer_steps.size() - 1; if (options::stringExp() && options::stringGuessModel()) { step_begin[EFFORT_LAST_CALL] = d_infer_steps.size(); // these two steps are run in parallel addStrategyStep(CHECK_EXTF_REDUCTION, 2, false); addStrategyStep(CHECK_EXTF_EVAL, 3); step_end[EFFORT_LAST_CALL] = d_infer_steps.size() - 1; } // set the beginning/ending ranges for (const std::pair& it_begin : step_begin) { Effort e = it_begin.first; std::map::iterator it_end = step_end.find(e); Assert(it_end != step_end.end()); d_strat_steps[e] = std::pair(it_begin.second, it_end->second); } } } void TheoryStrings::runStrategy(unsigned sbegin, unsigned send) { Trace("strings-process") << "----check, next round---" << std::endl; for (unsigned i = sbegin; i <= send; i++) { InferStep curr = d_infer_steps[i]; if (curr == BREAK) { if (d_im.hasProcessed()) { break; } } else { runInferStep(curr, d_infer_step_effort[i]); if (d_state.isInConflict()) { break; } } } Trace("strings-process") << "----finished round---" << std::endl; } }/* CVC4::theory::strings namespace */ }/* CVC4::theory namespace */ }/* CVC4 namespace */