From b71f00097394c5f292abb002e31f49a07aff0b58 Mon Sep 17 00:00:00 2001 From: Andrew Reynolds Date: Wed, 25 Mar 2020 08:48:14 -0500 Subject: Generalize more uses of string-specific functions (#4145) Towards theory of sequences. --- src/theory/quantifiers/quantifiers_rewriter.cpp | 2 +- .../quantifiers/sygus/sygus_grammar_cons.cpp | 15 ++++++---- src/theory/quantifiers/term_util.cpp | 5 ++-- src/theory/strings/core_solver.cpp | 35 +++++++++++----------- src/theory/strings/eqc_info.cpp | 4 +-- src/theory/strings/extf_solver.cpp | 2 +- src/theory/strings/inference_manager.cpp | 8 +++-- src/theory/strings/normal_form.cpp | 2 +- src/theory/strings/sequences_rewriter.cpp | 4 +-- src/theory/strings/solver_state.cpp | 2 +- src/theory/strings/strings_fmf.cpp | 2 +- src/theory/strings/theory_strings.cpp | 11 ++++--- src/theory/strings/theory_strings_type_rules.h | 3 +- 13 files changed, 53 insertions(+), 42 deletions(-) diff --git a/src/theory/quantifiers/quantifiers_rewriter.cpp b/src/theory/quantifiers/quantifiers_rewriter.cpp index 231c81bbf..10c5741fe 100644 --- a/src/theory/quantifiers/quantifiers_rewriter.cpp +++ b/src/theory/quantifiers/quantifiers_rewriter.cpp @@ -1009,7 +1009,7 @@ bool QuantifiersRewriter::getVarElimLit(Node lit, { slv = getVarElimLitBv(lit, args, var); } - else if (tt.isString()) + else if (tt.isStringLike()) { slv = getVarElimLitString(lit, args, var); } diff --git a/src/theory/quantifiers/sygus/sygus_grammar_cons.cpp b/src/theory/quantifiers/sygus/sygus_grammar_cons.cpp index d33c72ede..f15b6780c 100644 --- a/src/theory/quantifiers/sygus/sygus_grammar_cons.cpp +++ b/src/theory/quantifiers/sygus/sygus_grammar_cons.cpp @@ -27,6 +27,7 @@ #include "theory/quantifiers/sygus/term_database_sygus.h" #include "theory/quantifiers/term_util.h" #include "theory/quantifiers_engine.h" +#include "theory/strings/word.h" using namespace CVC4::kind; @@ -405,11 +406,15 @@ void CegGrammarConstructor::mkSygusConstantsForType(TypeNode type, ops.push_back(nm->mkConst(true)); ops.push_back(nm->mkConst(false)); } - else if (type.isString()) + else if (type.isStringLike()) { - ops.push_back(nm->mkConst(String(""))); - // dummy character "A" - ops.push_back(nm->mkConst(String("A"))); + ops.push_back(strings::Word::mkEmptyWord(type)); + if (type.isString()) + { + // Dummy character "A". This is not necessary for sequences which + // have the generic constructor seq.unit. + ops.push_back(nm->mkConst(String("A"))); + } } else if (type.isArray() || type.isSet()) { @@ -449,7 +454,7 @@ void CegGrammarConstructor::collectSygusGrammarTypesFor( { collectSygusGrammarTypesFor(range.getSetElementType(), types); } - else if (range.isString() ) + else if (range.isStringLike()) { // theory of strings shares the integer type TypeNode intType = NodeManager::currentNM()->integerType(); diff --git a/src/theory/quantifiers/term_util.cpp b/src/theory/quantifiers/term_util.cpp index 7f94130f3..cc920f1d7 100644 --- a/src/theory/quantifiers/term_util.cpp +++ b/src/theory/quantifiers/term_util.cpp @@ -25,6 +25,7 @@ #include "theory/quantifiers/term_database.h" #include "theory/quantifiers/term_enumeration.h" #include "theory/quantifiers_engine.h" +#include "theory/strings/word.h" #include "theory/theory_engine.h" using namespace std; @@ -464,11 +465,11 @@ Node TermUtil::mkTypeValue(TypeNode tn, int val) n = NodeManager::currentNM()->mkConst(false); } } - else if (tn.isString()) + else if (tn.isStringLike()) { if (val == 0) { - n = NodeManager::currentNM()->mkConst(::CVC4::String("")); + n = strings::Word::mkEmptyWord(tn); } } return n; diff --git a/src/theory/strings/core_solver.cpp b/src/theory/strings/core_solver.cpp index 556ae28c3..876984503 100644 --- a/src/theory/strings/core_solver.cpp +++ b/src/theory/strings/core_solver.cpp @@ -719,11 +719,11 @@ void CoreSolver::getNormalForms(Node eqc, while( !eqc_i.isFinished() ){ Node n = (*eqc_i); if( !d_bsolver.isCongruent(n) ){ - if (n.getKind() == CONST_STRING || n.getKind() == STRING_CONCAT) + if (n.isConst() || n.getKind() == STRING_CONCAT) { Trace("strings-process-debug") << "Get Normal Form : Process term " << n << " in eqc " << eqc << std::endl; NormalForm nf_curr; - if (n.getKind() == CONST_STRING) + if (n.isConst()) { nf_curr.init(n); } @@ -803,8 +803,7 @@ void CoreSolver::getNormalForms(Node eqc, } //if not equal to self std::vector& currv = nf_curr.d_nf; - if (currv.size() > 1 - || (currv.size() == 1 && currv[0].getKind() == CONST_STRING)) + if (currv.size() > 1 || (currv.size() == 1 && currv[0].isConst())) { // if in a build with assertions, check that normal form is acyclic if (Configuration::isAssertionBuild()) @@ -1082,9 +1081,8 @@ void CoreSolver::processSimpleNEq(NormalForm& nfi, d_im.sendInference(lenExp, eq, Inference::N_UNIFY); break; } - else if ((x.getKind() != CONST_STRING && index == nfiv.size() - rproc - 1) - || (y.getKind() != CONST_STRING - && index == nfjv.size() - rproc - 1)) + else if ((!x.isConst() && index == nfiv.size() - rproc - 1) + || (!y.isConst() && index == nfjv.size() - rproc - 1)) { // We have reached the last component in one of the normal forms and it // is not a constant. Thus, the last component must be equal to the @@ -1253,7 +1251,7 @@ void CoreSolver::processSimpleNEq(NormalForm& nfi, NormalForm& nfnc = x.isConst() ? nfj : nfi; const std::vector& nfncv = nfnc.d_nf; Node nc = nfncv[index]; - Assert(nc.getKind() != CONST_STRING) << "Other string is not constant."; + Assert(!nc.isConst()) << "Other string is not constant."; Assert(nc.getKind() != STRING_CONCAT) << "Other string is not CONCAT."; if (!ee->areDisequal(nc, d_emptyString, true)) { @@ -1391,8 +1389,8 @@ void CoreSolver::processSimpleNEq(NormalForm& nfi, // // E.g. x ++ ... = y ++ ... ---> (x = y ++ k) v (y = x ++ k) Assert(d_state.areDisequal(xLenTerm, yLenTerm)); - Assert(x.getKind() != CONST_STRING); - Assert(y.getKind() != CONST_STRING); + Assert(!x.isConst()); + Assert(!y.isConst()); int32_t lentTestSuccess = -1; Node lentTestExp; @@ -1404,7 +1402,7 @@ void CoreSolver::processSimpleNEq(NormalForm& nfi, { Node t = e == 0 ? x : y; // do not infer constants are larger than variables - if (t.getKind() != CONST_STRING) + if (!t.isConst()) { Node lt1 = e == 0 ? xLenTerm : yLenTerm; Node lt2 = e == 0 ? yLenTerm : xLenTerm; @@ -1745,18 +1743,18 @@ void CoreSolver::processDeq( Node ni, Node nj ) { Trace("strings-solve-debug") << "...Processing(DEQ) " << i << " " << j << std::endl; if (!d_state.areEqual(i, j)) { - Assert(i.getKind() != kind::CONST_STRING - || j.getKind() != kind::CONST_STRING); + Assert(!i.isConst() || !j.isConst()); std::vector< Node > lexp; Node li = d_state.getLength(i, lexp); Node lj = d_state.getLength(j, lexp); if (d_state.areDisequal(li, lj)) { - if( i.getKind()==kind::CONST_STRING || j.getKind()==kind::CONST_STRING ){ + if (i.isConst() || j.isConst()) + { //check if empty - Node const_k = i.getKind() == kind::CONST_STRING ? i : j; - Node nconst_k = i.getKind() == kind::CONST_STRING ? j : i; - Node lnck = i.getKind() == kind::CONST_STRING ? lj : li; + Node const_k = i.isConst() ? i : j; + Node nconst_k = i.isConst() ? j : i; + Node lnck = i.isConst() ? lj : li; if( !ee->areDisequal( nconst_k, d_emptyString, true ) ){ Node eq = nconst_k.eqNode( d_emptyString ); Node conc = NodeManager::currentNM()->mkNode( kind::OR, eq, eq.negate() ); @@ -1941,7 +1939,8 @@ int CoreSolver::processSimpleDeq( std::vector< Node >& nfi, std::vector< Node >& Trace("strings-solve-debug") << "...Processing(QED) " << i << " " << j << std::endl; if (!d_state.areEqual(i, j)) { - if( i.getKind()==kind::CONST_STRING && j.getKind()==kind::CONST_STRING ) { + if (i.isConst() && j.isConst()) + { size_t lenI = Word::getLength(i); size_t lenJ = Word::getLength(j); unsigned int len_short = lenI < lenJ ? lenI : lenJ; diff --git a/src/theory/strings/eqc_info.cpp b/src/theory/strings/eqc_info.cpp index ab6d473bd..4e9b0f8cd 100644 --- a/src/theory/strings/eqc_info.cpp +++ b/src/theory/strings/eqc_info.cpp @@ -44,13 +44,13 @@ Node EqcInfo::addEndpointConst(Node t, Node c, bool isSuf) << " post=" << isSuf << std::endl; Node prevC = utils::getConstantEndpoint(prev, isSuf); Assert(!prevC.isNull()); - Assert(prevC.getKind() == CONST_STRING); + Assert(prevC.isConst()); if (c.isNull()) { c = utils::getConstantEndpoint(t, isSuf); Assert(!c.isNull()); } - Assert(c.getKind() == CONST_STRING); + Assert(c.isConst()); bool conflict = false; // if the constant prefixes are different if (c != prevC) diff --git a/src/theory/strings/extf_solver.cpp b/src/theory/strings/extf_solver.cpp index c586df6dd..6a3209344 100644 --- a/src/theory/strings/extf_solver.cpp +++ b/src/theory/strings/extf_solver.cpp @@ -645,7 +645,7 @@ Node ExtfSolver::getCurrentSubstitutionFor(int effort, { return c; } - else if (effort >= 1 && n.getType().isString()) + else if (effort >= 1 && n.getType().isStringLike()) { Assert(effort < 3); // normal forms diff --git a/src/theory/strings/inference_manager.cpp b/src/theory/strings/inference_manager.cpp index eebad2274..e931c5c1a 100644 --- a/src/theory/strings/inference_manager.cpp +++ b/src/theory/strings/inference_manager.cpp @@ -20,6 +20,7 @@ #include "theory/rewriter.h" #include "theory/strings/theory_strings.h" #include "theory/strings/theory_strings_utils.h" +#include "theory/strings/word.h" using namespace std; using namespace CVC4::context; @@ -370,12 +371,13 @@ Node InferenceManager::getSymbolicDefinition(Node n, void InferenceManager::registerLength(Node n) { + Assert(n.getType().isStringLike()); NodeManager* nm = NodeManager::currentNM(); // 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) + if (n.getKind() != STRING_CONCAT && !n.isConst()) { Node lsumb = nm->mkNode(STRING_LENGTH, n); lsum = Rewriter::rewrite(lsumb); @@ -422,9 +424,9 @@ void InferenceManager::registerLength(Node n) lsum = nm->mkNode(PLUS, nodeVec); lsum = Rewriter::rewrite(lsum); } - else if (n.getKind() == CONST_STRING) + else if (n.isConst()) { - lsum = nm->mkConst(Rational(n.getConst().size())); + lsum = nm->mkConst(Rational(Word::getLength(n))); } Assert(!lsum.isNull()); d_proxyVarToLength[sk] = lsum; diff --git a/src/theory/strings/normal_form.cpp b/src/theory/strings/normal_form.cpp index 7a2323d89..4dd273eff 100644 --- a/src/theory/strings/normal_form.cpp +++ b/src/theory/strings/normal_form.cpp @@ -28,7 +28,7 @@ namespace strings { void NormalForm::init(Node base) { - Assert(base.getType().isString()); + Assert(base.getType().isStringLike()); Assert(base.getKind() != STRING_CONCAT); d_base = base; d_nf.clear(); diff --git a/src/theory/strings/sequences_rewriter.cpp b/src/theory/strings/sequences_rewriter.cpp index 200d7a734..716634d5f 100644 --- a/src/theory/strings/sequences_rewriter.cpp +++ b/src/theory/strings/sequences_rewriter.cpp @@ -400,7 +400,7 @@ Node SequencesRewriter::rewriteEqualityExt(Node node) { return rewriteArithEqualityExt(node); } - if (node[0].getType().isString()) + if (node[0].getType().isStringLike()) { return rewriteStrEqualityExt(node); } @@ -409,7 +409,7 @@ Node SequencesRewriter::rewriteEqualityExt(Node node) Node SequencesRewriter::rewriteStrEqualityExt(Node node) { - Assert(node.getKind() == EQUAL && node[0].getType().isString()); + Assert(node.getKind() == EQUAL && node[0].getType().isStringLike()); TypeNode stype = node[0].getType(); NodeManager* nm = NodeManager::currentNM(); diff --git a/src/theory/strings/solver_state.cpp b/src/theory/strings/solver_state.cpp index a38bf2c50..30acba9fd 100644 --- a/src/theory/strings/solver_state.cpp +++ b/src/theory/strings/solver_state.cpp @@ -108,7 +108,7 @@ void SolverState::eqNotifyNewClass(TNode t) ei->d_codeTerm = t[0]; } } - else if (k == CONST_STRING) + else if (t.isConst()) { EqcInfo* ei = getOrMakeEqcInfo(t); ei->d_prefixC = t; diff --git a/src/theory/strings/strings_fmf.cpp b/src/theory/strings/strings_fmf.cpp index 8ca6d2de1..02af3949e 100644 --- a/src/theory/strings/strings_fmf.cpp +++ b/src/theory/strings/strings_fmf.cpp @@ -40,7 +40,7 @@ StringsFmf::~StringsFmf() {} void StringsFmf::preRegisterTerm(TNode n) { - if (!n.getType().isString()) + if (!n.getType().isStringLike()) { return; } diff --git a/src/theory/strings/theory_strings.cpp b/src/theory/strings/theory_strings.cpp index 1006076d5..83f0159b5 100644 --- a/src/theory/strings/theory_strings.cpp +++ b/src/theory/strings/theory_strings.cpp @@ -700,7 +700,8 @@ void TheoryStrings::check(Effort e) { 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() ); + bool print = (t == 0 && eqc.getType().isStringLike()) + || (t == 1 && !eqc.getType().isStringLike()); if (print) { eq::EqClassIterator eqc2_i = eq::EqClassIterator( eqc, &d_equalityEngine ); Trace("strings-eqc") << "Eqc( " << eqc << " ) : { "; @@ -907,7 +908,9 @@ void TheoryStrings::assertPendingFact(Node atom, bool polarity, Node exp) { 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() ) { + if (!d_equalityEngine.hasTerm(atom[j]) + && atom[j].getType().isStringLike()) + { registerTerm( atom[j], 0 ); } } @@ -1047,7 +1050,7 @@ void TheoryStrings::registerTerm(Node n, int effort) { TypeNode tn = n.getType(); bool do_register = true; - if (!tn.isString()) + if (!tn.isStringLike()) { if (options::stringEagerLen()) { @@ -1070,7 +1073,7 @@ void TheoryStrings::registerTerm(Node n, int effort) NodeManager* nm = NodeManager::currentNM(); Debug("strings-register") << "TheoryStrings::registerTerm() " << n << ", effort = " << effort << std::endl; - if (tn.isString()) + if (tn.isStringLike()) { // register length information: // for variables, split on empty vs positive length diff --git a/src/theory/strings/theory_strings_type_rules.h b/src/theory/strings/theory_strings_type_rules.h index 6abb57504..7ef31a92a 100644 --- a/src/theory/strings/theory_strings_type_rules.h +++ b/src/theory/strings/theory_strings_type_rules.h @@ -291,7 +291,8 @@ public: if (!t.isString()) { throw TypeCheckingExceptionPrivate(n, "expecting a string term in regexp range"); } - if( (*it).getKind() != kind::CONST_STRING ) { + if (!(*it).isConst()) + { throw TypeCheckingExceptionPrivate(n, "expecting a constant string term in regexp range"); } if( (*it).getConst().size() != 1 ) { -- cgit v1.2.3 From 34a0e4420960a2f6a34d02e53636fecd63b5c4de Mon Sep 17 00:00:00 2001 From: Ahmed Irfan <43099566+ahmed-irfan@users.noreply.github.com> Date: Wed, 25 Mar 2020 09:37:57 -0700 Subject: bv2int : linear mult opt (#4142) --- src/preprocessing/passes/bv_to_int.cpp | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/preprocessing/passes/bv_to_int.cpp b/src/preprocessing/passes/bv_to_int.cpp index cb78b0897..0e4bc41c0 100644 --- a/src/preprocessing/passes/bv_to_int.cpp +++ b/src/preprocessing/passes/bv_to_int.cpp @@ -401,7 +401,28 @@ Node BVToInt::bvToInt(Node n) d_nm->mkNode(kind::MINUS, mult, multSig); d_rangeAssertions.insert( mkRangeConstraint(d_bvToIntCache[current], bvsize)); - d_rangeAssertions.insert(mkRangeConstraint(sigma, bvsize)); + if (translated_children[0].isConst() + || translated_children[1].isConst()) + { + /* + * based on equation (23), section 3.2.3 of: + * Bozzano et al. + * Encoding RTL Constructs for MathSAT: a Preliminary Report. + */ + // this is an optimization when one of the children is constant + Node c = translated_children[0].isConst() + ? translated_children[0] + : translated_children[1]; + d_rangeAssertions.insert( + d_nm->mkNode(kind::LEQ, d_zero, sigma)); + // the value of sigma is bounded by (c - 1) + // where c is the constant multiplicand + d_rangeAssertions.insert(d_nm->mkNode(kind::LT, sigma, c)); + } + else + { + d_rangeAssertions.insert(mkRangeConstraint(sigma, bvsize)); + } break; } case kind::BITVECTOR_UDIV_TOTAL: -- cgit v1.2.3 From 482df8bb2914d9c470ab8dd290bf6abe590505c6 Mon Sep 17 00:00:00 2001 From: Andres Noetzli Date: Wed, 25 Mar 2020 12:22:25 -0700 Subject: Support async-signal-safe printing of inferences (#4148) Commit c9b7c3d introduced code for counting the number of string inferences done per type of inference. However, it did not add support for printing the inference names in an async-signal-safe manner via safe_print() (note that printing in signal handlers is done differently from the regular stats printing). This commit adds the corresponding code, s.t. we get the inference names when printing the stats when CVC4 is interrupted or crashes. --- src/theory/strings/infer_info.cpp | 35 +++++++++++++------------ src/theory/strings/infer_info.h | 21 +++++++++++++++ src/util/safe_print.h | 55 ++++++++++++++++++++++++++++++++++++--- 3 files changed, 92 insertions(+), 19 deletions(-) diff --git a/src/theory/strings/infer_info.cpp b/src/theory/strings/infer_info.cpp index aa7fe8974..cdf764aa8 100644 --- a/src/theory/strings/infer_info.cpp +++ b/src/theory/strings/infer_info.cpp @@ -14,30 +14,33 @@ #include "theory/strings/infer_info.h" -using namespace CVC4::kind; - namespace CVC4 { namespace theory { namespace strings { -std::ostream& operator<<(std::ostream& out, Inference i) +const char* toString(Inference i) { switch (i) { - case Inference::N_ENDPOINT_EMP: out << "N_EndpointEmp"; break; - case Inference::N_UNIFY: out << "N_Unify"; break; - case Inference::N_ENDPOINT_EQ: out << "N_EndpointEq"; break; - case Inference::N_CONST: out << "N_Const"; break; - case Inference::INFER_EMP: out << "Infer-Emp"; break; - case Inference::SSPLIT_CST_PROP: out << "S-Split(CST-P)-prop"; break; - case Inference::SSPLIT_VAR_PROP: out << "S-Split(VAR)-prop"; break; - case Inference::LEN_SPLIT: out << "Len-Split(Len)"; break; - case Inference::LEN_SPLIT_EMP: out << "Len-Split(Emp)"; break; - case Inference::SSPLIT_CST: out << "S-Split(CST-P)"; break; - case Inference::SSPLIT_VAR: out << "S-Split(VAR)"; break; - case Inference::FLOOP: out << "F-Loop"; break; - default: out << "?"; break; + case Inference::N_ENDPOINT_EMP: return "N_EndpointEmp"; + case Inference::N_UNIFY: return "N_Unify"; + case Inference::N_ENDPOINT_EQ: return "N_EndpointEq"; + case Inference::N_CONST: return "N_Const"; + case Inference::INFER_EMP: return "Infer-Emp"; + case Inference::SSPLIT_CST_PROP: return "S-Split(CST-P)-prop"; + case Inference::SSPLIT_VAR_PROP: return "S-Split(VAR)-prop"; + case Inference::LEN_SPLIT: return "Len-Split(Len)"; + case Inference::LEN_SPLIT_EMP: return "Len-Split(Emp)"; + case Inference::SSPLIT_CST: return "S-Split(CST-P)"; + case Inference::SSPLIT_VAR: return "S-Split(VAR)"; + case Inference::FLOOP: return "F-Loop"; + default: return "?"; } +} + +std::ostream& operator<<(std::ostream& out, Inference i) +{ + out << toString(i); return out; } diff --git a/src/theory/strings/infer_info.h b/src/theory/strings/infer_info.h index 9d2f71b53..cfabe5c51 100644 --- a/src/theory/strings/infer_info.h +++ b/src/theory/strings/infer_info.h @@ -19,7 +19,9 @@ #include #include + #include "expr/node.h" +#include "util/safe_print.h" namespace CVC4 { namespace theory { @@ -94,6 +96,25 @@ enum class Inference : uint32_t FLOOP, NONE, }; + +/** + * Converts an inference to a string. Note: This function is also used in + * `safe_print()`. Changing this functions name or signature will result in + * `safe_print()` printing "" instead of the proper strings for + * the enum values. + * + * @param i The inference + * @return The name of the inference + */ +const char* toString(Inference i); + +/** + * Writes an inference name to a stream. + * + * @param out The stream to write to + * @param i The inference to write to the stream + * @return The stream + */ std::ostream& operator<<(std::ostream& out, Inference i); /** diff --git a/src/util/safe_print.h b/src/util/safe_print.h index 75a517b18..fa9430f2c 100644 --- a/src/util/safe_print.h +++ b/src/util/safe_print.h @@ -21,6 +21,11 @@ ** in statistics_registry.h would require specialization or we would have to ** use function overloading). ** + ** If there exists a function `toString(obj)` for a given object, it will be + ** used automatically. This is useful for printing enum values for example. + ** IMPORTANT: The `toString(obj)` function *must not* perform any allocations + ** or call other functions that are not async-signal-safe. + ** ** This header is a "cvc4_private_library.h" header because it is private but ** the safe_print functions are used in the driver. See also the description ** of "statistics_registry.h" for more information on @@ -41,6 +46,9 @@ #include +#include +#include + #include "lib/clock_gettime.h" #include "util/result.h" @@ -58,10 +66,51 @@ void CVC4_PUBLIC safe_print(int fd, const char (&msg)[N]) { } } -/** Prints a variable of type T. Safe to use in a signal handler. */ +/** + * The default method for converting an object to a string for safe printing. + * This method simply returns "". The `long` argument is used to + * indicate that we do not prefer this method over the version that calls + * `toString()`. + */ +template +const char* toStringImpl(const T& obj, long) +{ + return ""; +} + +/** + * Returns the result of calling `toString(obj)`. This method is only defined + * if such an overload of `toString()` exists. To detect the existence of such + * a method, we use SFINAE and a trailing return type. The trailing return type + * is necessary because it allows us to refer to `obj`. The `int` argument is + * used to prefer this version of the function instead of the one that prints + * "". + */ +template +auto toStringImpl(const T& obj, int) -> decltype(toString(obj)) +{ + return toString(obj); +} + +/** + * Prints a variable of type T. Safe to use in a signal handler. The default + * implementation either prints "" or the result of calling + * `toString(obj)` if such a method exists (this is useful for printing enum + * values for example without implementing a template specialization here). + * + * @param fd The file descriptor to print to + * @param obj The object to print + */ template -void CVC4_PUBLIC safe_print(int fd, const T& msg) { - safe_print(fd, ""); +void CVC4_PUBLIC safe_print(int fd, const T& obj) +{ + const char* s = + toStringImpl(obj, /* prefer the method that uses `toString()` */ 0); + ssize_t slen = static_cast(strlen(s)); + if (write(fd, s, slen) != slen) + { + abort(); + } } template <> -- cgit v1.2.3 From e7edc09b227af1f58573cf5a636a91674dc2d936 Mon Sep 17 00:00:00 2001 From: Andrew Reynolds Date: Thu, 26 Mar 2020 01:38:08 -0500 Subject: Care graphs based on polymorphic operators in strings (#4150) Towards theory of sequences. To compute a care graphs, we need to group function applications by the string type they are associated with. This PR introduces a utility to function to get the "owning string type" for a function application, and updates the care graph computation so that it partitions function applications according to this type. --- src/theory/strings/theory_strings.cpp | 15 ++++++++++----- src/theory/strings/theory_strings_utils.cpp | 30 +++++++++++++++++++++++++++++ src/theory/strings/theory_strings_utils.h | 13 +++++++++++++ 3 files changed, 53 insertions(+), 5 deletions(-) diff --git a/src/theory/strings/theory_strings.cpp b/src/theory/strings/theory_strings.cpp index 83f0159b5..789099ee4 100644 --- a/src/theory/strings/theory_strings.cpp +++ b/src/theory/strings/theory_strings.cpp @@ -873,7 +873,9 @@ void TheoryStrings::addCarePairs(TNodeTrie* t1, 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; + // Term index for each (type, operator) pair. We require the operator here + // since operators are polymorphic, taking strings/sequences. + std::map, TNodeTrie> index; std::map< Node, unsigned > arity; unsigned functionTerms = d_functionsTerms.size(); for (unsigned i = 0; i < functionTerms; ++ i) { @@ -889,16 +891,19 @@ void TheoryStrings::computeCareGraph(){ } } if( has_trigger_arg ){ - index[op].addTerm( f1, reps ); + TypeNode ft = utils::getOwnerStringType(f1); + std::pair ikey = std::pair(ft, op); + index[ikey].addTerm(f1, reps); arity[op] = reps.size(); } } //for each index - for (std::pair& tt : index) + for (std::pair, TNodeTrie>& ti : index) { Trace("strings-cg") << "TheoryStrings::computeCareGraph(): Process index " - << tt.first << "..." << std::endl; - addCarePairs(&tt.second, nullptr, arity[tt.first], 0); + << ti.first << "..." << std::endl; + Node op = ti.first.second; + addCarePairs(&ti.second, nullptr, arity[op], 0); } } diff --git a/src/theory/strings/theory_strings_utils.cpp b/src/theory/strings/theory_strings_utils.cpp index 5d27b8e2b..74cd6c4a3 100644 --- a/src/theory/strings/theory_strings_utils.cpp +++ b/src/theory/strings/theory_strings_utils.cpp @@ -264,6 +264,36 @@ void printConcatTrace(std::vector& n, const char* c) Trace(c) << ss.str(); } +bool isStringKind(Kind k) +{ + return k == STRING_STOI || k == STRING_ITOS || k == STRING_TOLOWER + || k == STRING_TOUPPER || k == STRING_LEQ || k == STRING_LT + || k == STRING_FROM_CODE || k == STRING_TO_CODE; +} + +TypeNode getOwnerStringType(Node n) +{ + TypeNode tn; + Kind k = n.getKind(); + if (k == STRING_STRIDOF || k == STRING_LENGTH || k == STRING_STRCTN + || k == STRING_PREFIX || k == STRING_SUFFIX) + { + // owning string type is the type of first argument + tn = n[0].getType(); + } + else if (isStringKind(k)) + { + tn = NodeManager::currentNM()->stringType(); + } + else + { + tn = n.getType(); + } + AlwaysAssert(tn.isStringLike()) + << "Unexpected term in getOwnerStringType : " << n << ", type " << tn; + return tn; +} + } // namespace utils } // namespace strings } // namespace theory diff --git a/src/theory/strings/theory_strings_utils.h b/src/theory/strings/theory_strings_utils.h index 5f18d3936..578c224df 100644 --- a/src/theory/strings/theory_strings_utils.h +++ b/src/theory/strings/theory_strings_utils.h @@ -140,6 +140,19 @@ void printConcat(std::ostream& out, std::vector& n); /** Print the vector n as a concatentation term on trace given by c */ void printConcatTrace(std::vector& n, const char* c); +/** Is k a string-specific kind? */ +bool isStringKind(Kind k); + +/** Get owner string type + * + * This returns a string-like type for a term n that belongs to the theory of + * strings. This type conceptually represents the subtheory of strings + * (Sequence(T) or String) that owns n. This is typically the type of n, + * but for instance, operators like str.indexof( s, t, n ), this is the type + * of s. + */ +TypeNode getOwnerStringType(Node n); + } // namespace utils } // namespace strings } // namespace theory -- cgit v1.2.3 From 9523b4248da9764fa14f078659b42085a7fe2654 Mon Sep 17 00:00:00 2001 From: Andrew Reynolds Date: Thu, 26 Mar 2020 08:37:13 -0500 Subject: Generalize more string-specific functions (#4152) Towards theory of sequences. Note this PR also changes design in core/base solver. Previously I had estimated that we should have separate instances per type for these solvers, but I think it is better to have these classes handle all string-like types simultaneously. This means they should not have a d_type field. --- src/theory/strings/base_solver.cpp | 3 +- src/theory/strings/base_solver.h | 2 - src/theory/strings/core_solver.cpp | 196 +++++++++++++++------------- src/theory/strings/core_solver.h | 21 ++- src/theory/strings/eqc_info.cpp | 15 +-- src/theory/strings/normal_form.cpp | 3 +- src/theory/strings/theory_strings_utils.cpp | 7 +- 7 files changed, 135 insertions(+), 112 deletions(-) diff --git a/src/theory/strings/base_solver.cpp b/src/theory/strings/base_solver.cpp index 128893cf0..af0e7cc37 100644 --- a/src/theory/strings/base_solver.cpp +++ b/src/theory/strings/base_solver.cpp @@ -35,7 +35,6 @@ BaseSolver::BaseSolver(context::Context* c, d_emptyString = NodeManager::currentNM()->mkConst(::CVC4::String("")); d_false = NodeManager::currentNM()->mkConst(false); d_cardSize = utils::getAlphabetCardinality(); - d_type = NodeManager::currentNM()->stringType(); } BaseSolver::~BaseSolver() {} @@ -254,7 +253,7 @@ void BaseSolver::checkConstantEquivalenceClasses(TermIndex* ti, if (!n.isNull()) { // construct the constant - Node c = utils::mkNConcat(vecc, d_type); + Node c = utils::mkNConcat(vecc, n.getType()); if (!d_state.areEqual(n, c)) { if (Trace.isOn("strings-debug")) diff --git a/src/theory/strings/base_solver.h b/src/theory/strings/base_solver.h index bf223bc0a..3681b49a4 100644 --- a/src/theory/strings/base_solver.h +++ b/src/theory/strings/base_solver.h @@ -191,8 +191,6 @@ class BaseSolver std::map d_termIndex; /** the cardinality of the alphabet */ uint32_t d_cardSize; - /** The string-like type for this base solver */ - TypeNode d_type; }; /* class BaseSolver */ } // namespace strings diff --git a/src/theory/strings/core_solver.cpp b/src/theory/strings/core_solver.cpp index 876984503..ab3270016 100644 --- a/src/theory/strings/core_solver.cpp +++ b/src/theory/strings/core_solver.cpp @@ -37,10 +37,8 @@ d_nf_pairs(c) d_zero = NodeManager::currentNM()->mkConst( Rational( 0 ) ); d_one = NodeManager::currentNM()->mkConst( Rational( 1 ) ); d_neg_one = NodeManager::currentNM()->mkConst(Rational(-1)); - d_emptyString = Word::mkEmptyWord(CONST_STRING); d_true = NodeManager::currentNM()->mkConst( true ); d_false = NodeManager::currentNM()->mkConst( false ); - d_type = NodeManager::currentNM()->stringType(); } CoreSolver::~CoreSolver() { @@ -286,10 +284,11 @@ void CoreSolver::checkFlatForm(std::vector& eqc, << "Found endpoint (in a) with non-empty b = " << b << ", whose flat form is " << d_flat_form[b] << std::endl; // endpoint + Node emp = Word::mkEmptyWord(a.getType()); std::vector conc_c; for (unsigned j = count; j < bsize; j++) { - conc_c.push_back(b[d_flat_form_index[b][j]].eqNode(d_emptyString)); + conc_c.push_back(b[d_flat_form_index[b][j]].eqNode(emp)); } Assert(!conc_c.empty()); conc = utils::mkAnd(conc_c); @@ -325,10 +324,11 @@ void CoreSolver::checkFlatForm(std::vector& eqc, << "Found endpoint in b = " << b << ", whose flat form is " << d_flat_form[b] << std::endl; // endpoint + Node emp = Word::mkEmptyWord(a.getType()); std::vector conc_c; for (size_t j = count; j < asize; j++) { - conc_c.push_back(a[d_flat_form_index[a][j]].eqNode(d_emptyString)); + conc_c.push_back(a[d_flat_form_index[a][j]].eqNode(emp)); } Assert(!conc_c.empty()); conc = utils::mkAnd(conc_c); @@ -438,11 +438,12 @@ void CoreSolver::checkFlatForm(std::vector& eqc, } ssize_t startj = isRev ? jj + 1 : 0; ssize_t endj = isRev ? c.getNumChildren() : jj; + Node emp = Word::mkEmptyWord(a.getType()); for (ssize_t j = startj; j < endj; j++) { - if (d_state.areEqual(c[j], d_emptyString)) + if (d_state.areEqual(c[j], emp)) { - d_im.addToExplanation(c[j], d_emptyString, exp); + d_im.addToExplanation(c[j], emp, exp); } } } @@ -470,6 +471,7 @@ Node CoreSolver::checkCycles( Node eqc, std::vector< Node >& curr, std::vector< return eqc; }else if( std::find( d_strings_eqc.begin(), d_strings_eqc.end(), eqc )==d_strings_eqc.end() ){ curr.push_back( eqc ); + Node emp = Word::mkEmptyWord(eqc.getType()); //look at all terms in this equivalence class eq::EqualityEngine* ee = d_state.getEqualityEngine(); eq::EqClassIterator eqc_i = eq::EqClassIterator( eqc, ee ); @@ -478,22 +480,25 @@ Node CoreSolver::checkCycles( Node eqc, std::vector< Node >& curr, std::vector< if( !d_bsolver.isCongruent(n) ){ if( n.getKind() == kind::STRING_CONCAT ){ Trace("strings-cycle") << eqc << " check term : " << n << " in " << eqc << std::endl; - if( eqc!=d_emptyString ){ + if (eqc != emp) + { d_eqc[eqc].push_back( n ); } for( unsigned i=0; i exps; - exps.push_back(n.eqNode(d_emptyString)); - d_im.sendInference( - exps, n[i].eqNode(d_emptyString), "I_CYCLE_E"); + exps.push_back(n.eqNode(emp)); + d_im.sendInference(exps, n[i].eqNode(emp), "I_CYCLE_E"); return Node::null(); } }else{ - if( nr!=d_emptyString ){ + if (nr != emp) + { d_flat_form[n].push_back( nr ); d_flat_form_index[n].push_back( i ); } @@ -507,10 +512,9 @@ Node CoreSolver::checkCycles( Node eqc, std::vector< Node >& curr, std::vector< //can infer all other components must be empty for( unsigned j=0; j eqc_to_exp; for (const Node& eqc : d_strings_eqc) { + TypeNode stype = eqc.getType(); Trace("strings-process-debug") << "- Verify normal forms are the same for " << eqc << std::endl; - normalizeEquivalenceClass(eqc); + normalizeEquivalenceClass(eqc, stype); Trace("strings-debug") << "Finished normalizing eqc..." << std::endl; if (d_im.hasProcessed()) { return; } NormalForm& nfe = getNormalForm(eqc); - Node nf_term = utils::mkNConcat(nfe.d_nf, d_type); + Node nf_term = utils::mkNConcat(nfe.d_nf, stype); std::map::iterator itn = nf_to_eqc.find(nf_term); if (itn != nf_to_eqc.end()) { @@ -602,21 +607,23 @@ void CoreSolver::checkNormalFormsEq() } //compute d_normal_forms_(base,exp,exp_depend)[eqc] -void CoreSolver::normalizeEquivalenceClass( Node eqc ) { +void CoreSolver::normalizeEquivalenceClass(Node eqc, TypeNode stype) +{ Trace("strings-process-debug") << "Process equivalence class " << eqc << std::endl; - if (d_state.areEqual(eqc, d_emptyString)) + Node emp = Word::mkEmptyWord(stype); + if (d_state.areEqual(eqc, emp)) { #ifdef CVC4_ASSERTIONS for( unsigned j=0; j term_to_nf_index; // get the normal forms - getNormalForms(eqc, normal_forms, term_to_nf_index); + getNormalForms(eqc, normal_forms, term_to_nf_index, stype); if (d_im.hasProcessed()) { return; } // process the normal forms - processNEqc(normal_forms); + processNEqc(normal_forms, stype); if (d_im.hasProcessed()) { return; @@ -679,11 +686,12 @@ Node CoreSolver::getNormalString(Node x, std::vector& nf_exp) if (!x.isConst()) { Node xr = d_state.getRepresentative(x); + TypeNode stype = xr.getType(); std::map::iterator it = d_normal_form.find(xr); if (it != d_normal_form.end()) { NormalForm& nf = it->second; - Node ret = utils::mkNConcat(nf.d_nf, d_type); + Node ret = utils::mkNConcat(nf.d_nf, stype); nf_exp.insert(nf_exp.end(), nf.d_exp.begin(), nf.d_exp.end()); d_im.addToExplanation(x, nf.d_base, nf_exp); Trace("strings-debug") @@ -701,16 +709,18 @@ Node CoreSolver::getNormalString(Node x, std::vector& nf_exp) Node nc = getNormalString(x[i], nf_exp); vec_nodes.push_back(nc); } - return utils::mkNConcat(vec_nodes, d_type); + return utils::mkNConcat(vec_nodes, stype); } } return x; } void CoreSolver::getNormalForms(Node eqc, - std::vector& normal_forms, - std::map& term_to_nf_index) + std::vector& normal_forms, + std::map& term_to_nf_index, + TypeNode stype) { + Node emp = Word::mkEmptyWord(stype); //constant for equivalence class Node eqc_non_c = eqc; Trace("strings-process-debug") << "Get normal forms " << eqc << std::endl; @@ -826,7 +836,7 @@ void CoreSolver::getNormalForms(Node eqc, normal_forms.push_back(nf_curr); }else{ //this was redundant: combination of self + empty string(s) - Node nn = currv.size() == 0 ? d_emptyString : currv[0]; + Node nn = currv.size() == 0 ? emp : currv[0]; Assert(d_state.areEqual(nn, eqc)); } }else{ @@ -925,7 +935,8 @@ void CoreSolver::getNormalForms(Node eqc, } } -void CoreSolver::processNEqc(std::vector& normal_forms) +void CoreSolver::processNEqc(std::vector& normal_forms, + TypeNode stype) { //the possible inferences std::vector< InferInfo > pinfer; @@ -945,7 +956,7 @@ void CoreSolver::processNEqc(std::vector& normal_forms) unsigned rindex = 0; nfi.reverse(); nfj.reverse(); - processSimpleNEq(nfi, nfj, rindex, true, 0, pinfer); + processSimpleNEq(nfi, nfj, rindex, true, 0, pinfer, stype); nfi.reverse(); nfj.reverse(); if (d_im.hasProcessed()) @@ -956,7 +967,7 @@ void CoreSolver::processNEqc(std::vector& normal_forms) //rindex = 0; unsigned index = 0; - processSimpleNEq(nfi, nfj, index, false, rindex, pinfer); + processSimpleNEq(nfi, nfj, index, false, rindex, pinfer, stype); if (d_im.hasProcessed()) { return; @@ -1000,10 +1011,12 @@ void CoreSolver::processSimpleNEq(NormalForm& nfi, unsigned& index, bool isRev, unsigned rproc, - std::vector& pinfer) + std::vector& pinfer, + TypeNode stype) { NodeManager* nm = NodeManager::currentNM(); eq::EqualityEngine* ee = d_state.getEqualityEngine(); + Node emp = Word::mkEmptyWord(stype); const std::vector& nfiv = nfi.d_nf; const std::vector& nfjv = nfj.d_nf; @@ -1028,8 +1041,8 @@ void CoreSolver::processSimpleNEq(NormalForm& nfi, while (!d_state.isInConflict() && index_k < (nfkv.size() - rproc)) { // can infer that this string must be empty - Node eq = nfkv[index_k].eqNode(d_emptyString); - Assert(!d_state.areEqual(d_emptyString, nfkv[index_k])); + Node eq = nfkv[index_k].eqNode(emp); + Assert(!d_state.areEqual(emp, nfkv[index_k])); d_im.sendInference(curr_exp, eq, Inference::N_ENDPOINT_EMP); index_k++; } @@ -1108,7 +1121,7 @@ void CoreSolver::processSimpleNEq(NormalForm& nfi, eqnc.push_back(nfkv[i]); } } - eqn[r] = utils::mkNConcat(eqnc, d_type); + eqn[r] = utils::mkNConcat(eqnc, stype); } if (!d_state.areEqual(eqn[0], eqn[1])) { @@ -1253,13 +1266,13 @@ void CoreSolver::processSimpleNEq(NormalForm& nfi, Node nc = nfncv[index]; Assert(!nc.isConst()) << "Other string is not constant."; Assert(nc.getKind() != STRING_CONCAT) << "Other string is not CONCAT."; - if (!ee->areDisequal(nc, d_emptyString, true)) + if (!ee->areDisequal(nc, emp, true)) { // The non-constant side may be equal to the empty string. Split on // whether it is. // // E.g. "abc" ++ ... = nc ++ ... ---> (nc = "") v (nc != "") - Node eq = nc.eqNode(d_emptyString); + Node eq = nc.eqNode(emp); eq = Rewriter::rewrite(eq); if (eq.isConst()) { @@ -1267,7 +1280,7 @@ void CoreSolver::processSimpleNEq(NormalForm& nfi, // purify variable for this string to communicate that // we have inferred whether it is empty. Node p = d_skCache.mkSkolemCached(nc, SkolemCache::SK_PURIFY, "lsym"); - Node pEq = p.eqNode(d_emptyString); + Node pEq = p.eqNode(emp); // should not be constant Assert(!Rewriter::rewrite(pEq).isConst()); // infer the purification equality, and the (dis)equality @@ -1288,7 +1301,7 @@ void CoreSolver::processSimpleNEq(NormalForm& nfi, // At this point, we know that `nc` is non-empty, so we add that to our // explanation. - Node ncnz = nc.eqNode(d_emptyString).negate(); + Node ncnz = nc.eqNode(emp).negate(); info.d_ant.push_back(ncnz); size_t ncIndex = index + 1; @@ -1300,19 +1313,19 @@ void CoreSolver::processSimpleNEq(NormalForm& nfi, // // E.g. "abc" ++ ... = nc ++ "b" ++ ... ---> nc = "a" ++ k size_t cIndex = index; - Node constStr = nfc.collectConstantStringAt(cIndex); - Assert(!constStr.isNull()); - CVC4::String stra = constStr.getConst(); - CVC4::String strb = nextConstStr.getConst(); + Node stra = nfc.collectConstantStringAt(cIndex); + size_t straLen = Word::getLength(stra); + Assert(!stra.isNull()); + Node strb = nextConstStr; // Since `nc` is non-empty, we start with character 1 size_t p; if (isRev) { - CVC4::String stra1 = stra.prefix(stra.size() - 1); - p = stra.size() - stra1.roverlap(strb); - Trace("strings-csp-debug") << "Compute roverlap : " << constStr << " " - << nextConstStr << std::endl; - size_t p2 = stra1.rfind(strb); + Node stra1 = Word::prefix(stra, straLen - 1); + p = straLen - Word::roverlap(stra1, strb); + Trace("strings-csp-debug") + << "Compute roverlap : " << stra1 << " " << strb << std::endl; + size_t p2 = Word::rfind(stra1, strb); p = p2 == std::string::npos ? p : (p > p2 + 1 ? p2 + 1 : p); Trace("strings-csp-debug") << "roverlap : " << stra1 << " " << strb << " returned " << p @@ -1320,11 +1333,11 @@ void CoreSolver::processSimpleNEq(NormalForm& nfi, } else { - CVC4::String stra1 = stra.substr(1); - p = stra.size() - stra1.overlap(strb); - Trace("strings-csp-debug") << "Compute overlap : " << constStr << " " - << nextConstStr << std::endl; - size_t p2 = stra1.find(strb); + Node stra1 = Word::substr(stra, 1); + p = straLen - Word::overlap(stra1, strb); + Trace("strings-csp-debug") + << "Compute overlap : " << stra1 << " " << strb << std::endl; + size_t p2 = Word::find(stra1, strb); p = p2 == std::string::npos ? p : (p > p2 + 1 ? p2 + 1 : p); Trace("strings-csp-debug") << "overlap : " << stra1 << " " << strb << " returned " << p @@ -1338,9 +1351,9 @@ void CoreSolver::processSimpleNEq(NormalForm& nfi, { NormalForm::getExplanationForPrefixEq( nfc, nfnc, cIndex, ncIndex, info.d_ant); - Node prea = p == stra.size() ? constStr - : nm->mkConst(isRev ? stra.suffix(p) - : stra.prefix(p)); + Node prea = p == straLen ? stra + : (isRev ? Word::suffix(stra, p) + : Word::prefix(stra, p)); Node sk = d_skCache.mkSkolemCached( nc, prea, @@ -1362,17 +1375,17 @@ void CoreSolver::processSimpleNEq(NormalForm& nfi, // to start with the first character of the constant. // // E.g. "abc" ++ ... = nc ++ ... ---> nc = "a" ++ k - Node constStr = nfcv[index]; - CVC4::String stra = constStr.getConst(); - Node firstChar = stra.size() == 1 ? constStr - : nm->mkConst(isRev ? stra.suffix(1) - : stra.prefix(1)); + Node stra = nfcv[index]; + size_t straLen = Word::getLength(stra); + Node firstChar = straLen == 1 ? stra + : (isRev ? Word::suffix(stra, 1) + : Word::prefix(stra, 1)); Node sk = d_skCache.mkSkolemCached( nc, isRev ? SkolemCache::SK_ID_VC_SPT_REV : SkolemCache::SK_ID_VC_SPT, "c_spt"); Trace("strings-csp") << "Const Split: " << firstChar - << " is removed from " << constStr << " (serial) " + << " is removed from " << stra << " (serial) " << std::endl; NormalForm::getExplanationForPrefixEq(nfi, nfj, index, index, info.d_ant); info.d_conc = nc.eqNode(isRev ? utils::mkNConcat(sk, firstChar) @@ -1429,8 +1442,8 @@ void CoreSolver::processSimpleNEq(NormalForm& nfi, for (unsigned xory = 0; xory < 2; xory++) { Node t = xory == 0 ? x : y; - Node tnz = x.eqNode(d_emptyString).negate(); - if (ee->areDisequal(x, d_emptyString, true)) + Node tnz = x.eqNode(emp).negate(); + if (ee->areDisequal(x, emp, true)) { info.d_ant.push_back(tnz); } @@ -1527,23 +1540,27 @@ CoreSolver::ProcessLoopResult CoreSolver::processLoop(NormalForm& nfi, const std::vector& veci = nfi.d_nf; const std::vector& vecoi = nfj.d_nf; + TypeNode stype = veci[loop_index].getType(); + Trace("strings-loop") << "Detected possible loop for " << veci[loop_index] << std::endl; Trace("strings-loop") << " ... (X)= " << vecoi[index] << std::endl; Trace("strings-loop") << " ... T(Y.Z)= "; std::vector vec_t(veci.begin() + index, veci.begin() + loop_index); - Node t_yz = utils::mkNConcat(vec_t, d_type); + Node t_yz = utils::mkNConcat(vec_t, stype); Trace("strings-loop") << " (" << t_yz << ")" << std::endl; Trace("strings-loop") << " ... S(Z.Y)= "; std::vector vec_s(vecoi.begin() + index + 1, vecoi.end()); - Node s_zy = utils::mkNConcat(vec_s, d_type); + Node s_zy = utils::mkNConcat(vec_s, stype); Trace("strings-loop") << s_zy << std::endl; Trace("strings-loop") << " ... R= "; std::vector vec_r(veci.begin() + loop_index + 1, veci.end()); - Node r = utils::mkNConcat(vec_r, d_type); + Node r = utils::mkNConcat(vec_r, stype); Trace("strings-loop") << r << std::endl; - if (s_zy.isConst() && r.isConst() && r != d_emptyString) + Node emp = Word::mkEmptyWord(stype); + + if (s_zy.isConst() && r.isConst() && r != emp) { int c; bool flag = true; @@ -1551,8 +1568,8 @@ CoreSolver::ProcessLoopResult CoreSolver::processLoop(NormalForm& nfi, { if (c >= 0) { - s_zy = nm->mkConst(s_zy.getConst().substr(0, c)); - r = d_emptyString; + s_zy = Word::substr(s_zy, 0, c); + r = emp; vec_r.clear(); Trace("strings-loop") << "Strings::Loop: Refactor S(Z.Y)= " << s_zy << ", c=" << c << std::endl; @@ -1572,12 +1589,12 @@ CoreSolver::ProcessLoopResult CoreSolver::processLoop(NormalForm& nfi, for (unsigned i = 0; i < 2; i++) { Node t = i == 0 ? veci[loop_index] : t_yz; - split_eq = t.eqNode(d_emptyString); + split_eq = t.eqNode(emp); Node split_eqr = Rewriter::rewrite(split_eq); // the equality could rewrite to false if (!split_eqr.isConst()) { - if (!d_state.areDisequal(t, d_emptyString)) + if (!d_state.areDisequal(t, emp)) { // try to make t equal to empty to avoid loop info.d_conc = nm->mkNode(kind::OR, split_eq, split_eq.negate()); @@ -1600,10 +1617,10 @@ CoreSolver::ProcessLoopResult CoreSolver::processLoop(NormalForm& nfi, info.d_antn.push_back(ant); Node str_in_re; - if (s_zy == t_yz && r == d_emptyString && s_zy.isConst() + if (s_zy == t_yz && r == emp && s_zy.isConst() && s_zy.getConst().isRepeated()) { - Node rep_c = nm->mkConst(s_zy.getConst().substr(0, 1)); + Node rep_c = Word::substr(s_zy, 0, 1); Trace("strings-loop") << "Special case (X)=" << vecoi[index] << " " << std::endl; Trace("strings-loop") << "... (C)=" << rep_c << " " << std::endl; @@ -1626,13 +1643,13 @@ CoreSolver::ProcessLoopResult CoreSolver::processLoop(NormalForm& nfi, Node z = Word::substr(t_yz, len, size - len); Node restr = s_zy; Node cc; - if (r != d_emptyString) + if (r != emp) { std::vector v2(vec_r); v2.insert(v2.begin(), y); v2.insert(v2.begin(), z); restr = utils::mkNConcat(z, y); - cc = Rewriter::rewrite(s_zy.eqNode(utils::mkNConcat(v2, d_type))); + cc = Rewriter::rewrite(s_zy.eqNode(utils::mkNConcat(v2, stype))); } else { @@ -1682,9 +1699,9 @@ CoreSolver::ProcessLoopResult CoreSolver::processLoop(NormalForm& nfi, // s1 * ... * sk = z * y * r vec_r.insert(vec_r.begin(), sk_y); vec_r.insert(vec_r.begin(), sk_z); - Node conc2 = s_zy.eqNode(utils::mkNConcat(vec_r, d_type)); + Node conc2 = s_zy.eqNode(utils::mkNConcat(vec_r, stype)); Node conc3 = vecoi[index].eqNode(utils::mkNConcat(sk_y, sk_w)); - Node restr = r == d_emptyString ? s_zy : utils::mkNConcat(sk_z, sk_y); + Node restr = r == emp ? s_zy : utils::mkNConcat(sk_z, sk_y); str_in_re = nm->mkNode(kind::STRING_IN_REGEXP, sk_w, @@ -1696,7 +1713,6 @@ CoreSolver::ProcessLoopResult CoreSolver::processLoop(NormalForm& nfi, vec_conc.push_back(conc2); vec_conc.push_back(conc3); vec_conc.push_back(str_in_re); - // vec_conc.push_back(sk_y.eqNode(d_emptyString).negate());//by mkskolems conc = nm->mkNode(kind::AND, vec_conc); } // normal case @@ -1755,8 +1771,10 @@ void CoreSolver::processDeq( Node ni, Node nj ) { Node const_k = i.isConst() ? i : j; Node nconst_k = i.isConst() ? j : i; Node lnck = i.isConst() ? lj : li; - if( !ee->areDisequal( nconst_k, d_emptyString, true ) ){ - Node eq = nconst_k.eqNode( d_emptyString ); + Node emp = Word::mkEmptyWord(nconst_k.getType()); + if (!ee->areDisequal(nconst_k, emp, true)) + { + Node eq = nconst_k.eqNode(emp); Node conc = NodeManager::currentNM()->mkNode( kind::OR, eq, eq.negate() ); d_im.sendInference(d_emptyVec, conc, "D-DISL-Emp-Split"); return; @@ -1798,7 +1816,7 @@ void CoreSolver::processDeq( Node ni, Node nj ) { antec.end(), nfni.d_exp.begin(), nfni.d_exp.end()); antec.insert( antec.end(), nfnj.d_exp.begin(), nfnj.d_exp.end()); - antec.push_back( nconst_k.eqNode( d_emptyString ).negate() ); + antec.push_back(nconst_k.eqNode(emp).negate()); d_im.sendInference( antec, nm->mkNode( @@ -1835,8 +1853,6 @@ void CoreSolver::processDeq( Node ni, Node nj ) { Node sk3 = d_skCache.mkSkolemCached( i, j, SkolemCache::SK_ID_DEQ_Z, "z_dsplit"); d_im.registerLength(sk3, LENGTH_GEQ_ONE); - //Node nemp = sk3.eqNode(d_emptyString).negate(); - //conc.push_back(nemp); Node lsk1 = utils::mkNLength(sk1); conc.push_back( lsk1.eqNode( li ) ); Node lsk2 = utils::mkNLength(sk2); @@ -1912,6 +1928,8 @@ int CoreSolver::processSimpleDeq( std::vector< Node >& nfi, std::vector< Node >& } } } + TypeNode stype = ni.getType(); + Node emp = Word::mkEmptyWord(stype); NormalForm& nfni = getNormalForm(ni); NormalForm& nfnj = getNormalForm(nj); while( index& nfi, std::vector< Node >& std::vector< Node > cc; std::vector< Node >& nfk = index>=nfi.size() ? nfj : nfi; for( unsigned index_k=index; index_kmkNode( kind::AND, cc ); conc = Rewriter::rewrite( conc ); @@ -1944,7 +1962,8 @@ int CoreSolver::processSimpleDeq( std::vector< Node >& nfi, std::vector< Node >& size_t lenI = Word::getLength(i); size_t lenJ = Word::getLength(j); unsigned int len_short = lenI < lenJ ? lenI : lenJ; - bool isSameFix = isRev ? i.getConst().rstrncmp(j.getConst(), len_short): i.getConst().strncmp(j.getConst(), len_short); + bool isSameFix = isRev ? Word::rstrncmp(i, j, len_short) + : Word::strncmp(i, j, len_short); if( isSameFix ) { //same prefix/suffix //k is the index of the string that is shorter @@ -2124,6 +2143,7 @@ void CoreSolver::checkNormalFormsDeq() void CoreSolver::checkLengthsEqc() { for (unsigned i = 0; i < d_strings_eqc.size(); i++) { + TypeNode stype = d_strings_eqc[i].getType(); NormalForm& nfi = getNormalForm(d_strings_eqc[i]); Trace("strings-process-debug") << "Process length constraints for " << d_strings_eqc[i] << std::endl; @@ -2140,7 +2160,7 @@ void CoreSolver::checkLengthsEqc() { // now, check if length normalization has occurred if (ei->d_normalizedLength.get().isNull()) { - Node nf = utils::mkNConcat(nfi.d_nf, d_type); + Node nf = utils::mkNConcat(nfi.d_nf, stype); if (Trace.isOn("strings-process-debug")) { Trace("strings-process-debug") diff --git a/src/theory/strings/core_solver.h b/src/theory/strings/core_solver.h index c549fa886..3fea5e8de 100644 --- a/src/theory/strings/core_solver.h +++ b/src/theory/strings/core_solver.h @@ -218,16 +218,21 @@ class CoreSolver * current normal form for each term in this equivalence class is identical. * If it is not, then we add an inference via sendInference and abort the * call. + * + * stype is the string-like type of the equivalence class we are processing. */ - void normalizeEquivalenceClass(Node n); + void normalizeEquivalenceClass(Node n, TypeNode stype); /** * For each term in the equivalence class of eqc, this adds data regarding its * normal form to normal_forms. The map term_to_nf_index maps terms to the * index in normal_forms where their normal form data is located. + * + * stype is the string-like type of the equivalence class we are processing. */ void getNormalForms(Node eqc, std::vector& normal_forms, - std::map& term_to_nf_index); + std::map& term_to_nf_index, + TypeNode stype); /** process normalize equivalence class * * This is called when an equivalence class contains a set of terms that @@ -240,8 +245,10 @@ class CoreSolver * corresponding to processing the normal form pair in the (forward, reverse) * directions. Once all possible inferences are recorded, it executes the * one with highest priority based on the enumeration type Inference. + * + * stype is the string-like type of the equivalence class we are processing. */ - void processNEqc(std::vector& normal_forms); + void processNEqc(std::vector& normal_forms, TypeNode stype); /** process simple normal equality * * This method is called when two equal terms have normal forms nfi and nfj. @@ -265,13 +272,16 @@ class CoreSolver * fowards/backwards traversals of normal forms to ensure that duplicate * inferences are not processed. * pinfer: the set of possible inferences we add to. + * + * stype is the string-like type of the equivalence class we are processing. */ void processSimpleNEq(NormalForm& nfi, NormalForm& nfj, unsigned& index, bool isRev, unsigned rproc, - std::vector& pinfer); + std::vector& pinfer, + TypeNode stype); //--------------------------end for checkNormalFormsEq //--------------------------for checkNormalFormsEq with loops @@ -325,7 +335,6 @@ class CoreSolver /** reference to the base solver, used for certain queries */ BaseSolver& d_bsolver; /** Commonly used constants */ - Node d_emptyString; Node d_true; Node d_false; Node d_zero; @@ -368,8 +377,6 @@ class CoreSolver * the argument number of the t1 ... tn they were generated from. */ std::map > d_flat_form_index; - /** The string-like type for this solver */ - TypeNode d_type; }; /* class CoreSolver */ } // namespace strings diff --git a/src/theory/strings/eqc_info.cpp b/src/theory/strings/eqc_info.cpp index 4e9b0f8cd..3c0dbc2a7 100644 --- a/src/theory/strings/eqc_info.cpp +++ b/src/theory/strings/eqc_info.cpp @@ -15,6 +15,7 @@ #include "theory/strings/eqc_info.h" #include "theory/strings/theory_strings_utils.h" +#include "theory/strings/word.h" using namespace std; using namespace CVC4::context; @@ -59,10 +60,8 @@ Node EqcInfo::addEndpointConst(Node t, Node c, bool isSuf) Assert(!t.isConst() || !prev.isConst()); Trace("strings-eager-pconf-debug") << "Check conflict constants " << prevC << ", " << c << std::endl; - const String& ps = prevC.getConst(); - const String& cs = c.getConst(); - unsigned pvs = ps.size(); - unsigned cvs = cs.size(); + size_t pvs = Word::getLength(prevC); + size_t cvs = Word::getLength(c); if (pvs == cvs || (pvs > cvs && t.isConst()) || (cvs > pvs && prev.isConst())) { @@ -73,15 +72,15 @@ Node EqcInfo::addEndpointConst(Node t, Node c, bool isSuf) } else { - const String& larges = pvs > cvs ? ps : cs; - const String& smalls = pvs > cvs ? cs : ps; + Node larges = pvs > cvs ? prevC : c; + Node smalls = pvs > cvs ? c : prevC; if (isSuf) { - conflict = !larges.hasSuffix(smalls); + conflict = !Word::hasSuffix(larges, smalls); } else { - conflict = !larges.hasPrefix(smalls); + conflict = !Word::hasPrefix(larges, smalls); } } if (!conflict && (pvs > cvs || prev.isConst())) diff --git a/src/theory/strings/normal_form.cpp b/src/theory/strings/normal_form.cpp index 4dd273eff..05be5f12a 100644 --- a/src/theory/strings/normal_form.cpp +++ b/src/theory/strings/normal_form.cpp @@ -18,6 +18,7 @@ #include "options/strings_options.h" #include "theory/rewriter.h" #include "theory/strings/theory_strings_utils.h" +#include "theory/strings/word.h" using namespace std; using namespace CVC4::kind; @@ -37,7 +38,7 @@ void NormalForm::init(Node base) d_expDep.clear(); // add to normal form - if (!base.isConst() || !base.getConst().isEmptyString()) + if (!base.isConst() || Word::getLength(base) > 0) { d_nf.push_back(base); } diff --git a/src/theory/strings/theory_strings_utils.cpp b/src/theory/strings/theory_strings_utils.cpp index 74cd6c4a3..a3f6f4255 100644 --- a/src/theory/strings/theory_strings_utils.cpp +++ b/src/theory/strings/theory_strings_utils.cpp @@ -232,11 +232,10 @@ void getRegexpComponents(Node r, std::vector& result) } else if (r.getKind() == STRING_TO_REGEXP && r[0].isConst()) { - String s = r[0].getConst(); - for (size_t i = 0, size = s.size(); i < size; i++) + size_t rlen = Word::getLength(r[0]); + for (size_t i = 0; i < rlen; i++) { - result.push_back( - nm->mkNode(STRING_TO_REGEXP, nm->mkConst(s.substr(i, 1)))); + result.push_back(nm->mkNode(STRING_TO_REGEXP, Word::substr(r[0], i, 1))); } } else -- cgit v1.2.3 From c9fd28a391cfff767b899a65ad365742745910fe Mon Sep 17 00:00:00 2001 From: Andrew Reynolds Date: Thu, 26 Mar 2020 09:52:28 -0500 Subject: Add stats for string reductions, lemmas and conflicts (#4149) This PR adds comprehensive stats for reductions, lemmas and conflicts in TheoryStrings. Remaining stats will track all inferences (which are finer grained steps that may lead to lemmas/conflicts). Additionally this PR refactors calls to OutputChannel::lemma in TheoryStrings / InferenceManager. There are now only 2 calls to lemma(...) during registerTerm(...), one for "atomic" string terms (corresponding to length splits typically) and one for non-atomic terms. --- src/theory/strings/core_solver.cpp | 8 +-- src/theory/strings/extf_solver.cpp | 5 +- src/theory/strings/extf_solver.h | 4 +- src/theory/strings/inference_manager.cpp | 62 +++++++++++------ src/theory/strings/inference_manager.h | 34 ++++++--- src/theory/strings/sequences_stats.cpp | 30 +++++++- src/theory/strings/sequences_stats.h | 26 ++++++- src/theory/strings/theory_strings.cpp | 40 +++++++---- src/theory/strings/theory_strings_preprocess.cpp | 7 +- src/theory/strings/theory_strings_preprocess.h | 87 +++++++++++++----------- 10 files changed, 206 insertions(+), 97 deletions(-) diff --git a/src/theory/strings/core_solver.cpp b/src/theory/strings/core_solver.cpp index ab3270016..3384499a2 100644 --- a/src/theory/strings/core_solver.cpp +++ b/src/theory/strings/core_solver.cpp @@ -1692,7 +1692,7 @@ CoreSolver::ProcessLoopResult CoreSolver::processLoop(NormalForm& nfi, // right Node sk_w = d_skCache.mkSkolem("w_loop"); Node sk_y = d_skCache.mkSkolem("y_loop"); - d_im.registerLength(sk_y, LENGTH_GEQ_ONE); + d_im.registerTermAtomic(sk_y, LENGTH_GEQ_ONE); Node sk_z = d_skCache.mkSkolem("z_loop"); // t1 * ... * tn = y * z Node conc1 = t_yz.eqNode(utils::mkNConcat(sk_y, sk_z)); @@ -1803,7 +1803,7 @@ void CoreSolver::processDeq( Node ni, Node nj ) { { Node sk = d_skCache.mkSkolemCached( nconst_k, SkolemCache::SK_ID_DC_SPT, "dc_spt"); - d_im.registerLength(sk, LENGTH_ONE); + d_im.registerTermAtomic(sk, LENGTH_ONE); Node skr = d_skCache.mkSkolemCached(nconst_k, SkolemCache::SK_ID_DC_SPT_REM, @@ -1852,7 +1852,7 @@ void CoreSolver::processDeq( Node ni, Node nj ) { i, j, SkolemCache::SK_ID_DEQ_Y, "y_dsplit"); Node sk3 = d_skCache.mkSkolemCached( i, j, SkolemCache::SK_ID_DEQ_Z, "z_dsplit"); - d_im.registerLength(sk3, LENGTH_GEQ_ONE); + d_im.registerTermAtomic(sk3, LENGTH_GEQ_ONE); Node lsk1 = utils::mkNLength(sk1); conc.push_back( lsk1.eqNode( li ) ); Node lsk2 = utils::mkNLength(sk2); @@ -2209,7 +2209,7 @@ void CoreSolver::doInferInfo(const InferInfo& ii) { for (const Node& n : sks.second) { - d_im.registerLength(n, sks.first); + d_im.registerTermAtomic(n, sks.first); } } } diff --git a/src/theory/strings/extf_solver.cpp b/src/theory/strings/extf_solver.cpp index 6a3209344..47f36af4c 100644 --- a/src/theory/strings/extf_solver.cpp +++ b/src/theory/strings/extf_solver.cpp @@ -34,14 +34,15 @@ ExtfSolver::ExtfSolver(context::Context* c, SkolemCache& skc, BaseSolver& bs, CoreSolver& cs, - ExtTheory* et) + ExtTheory* et, + SequencesStatistics& stats) : d_state(s), d_im(im), d_skCache(skc), d_bsolver(bs), d_csolver(cs), d_extt(et), - d_preproc(&skc, u), + d_preproc(&skc, u, stats), d_hasExtf(c, false), d_extfInferCache(c) { diff --git a/src/theory/strings/extf_solver.h b/src/theory/strings/extf_solver.h index 4c848f430..040871ffa 100644 --- a/src/theory/strings/extf_solver.h +++ b/src/theory/strings/extf_solver.h @@ -26,6 +26,7 @@ #include "theory/strings/base_solver.h" #include "theory/strings/core_solver.h" #include "theory/strings/inference_manager.h" +#include "theory/strings/sequences_stats.h" #include "theory/strings/skolem_cache.h" #include "theory/strings/solver_state.h" #include "theory/strings/theory_strings_preprocess.h" @@ -88,7 +89,8 @@ class ExtfSolver SkolemCache& skc, BaseSolver& bs, CoreSolver& cs, - ExtTheory* et); + ExtTheory* et, + SequencesStatistics& stats); ~ExtfSolver(); /** check extended functions evaluation diff --git a/src/theory/strings/inference_manager.cpp b/src/theory/strings/inference_manager.cpp index e931c5c1a..cb0c807cc 100644 --- a/src/theory/strings/inference_manager.cpp +++ b/src/theory/strings/inference_manager.cpp @@ -226,6 +226,7 @@ void InferenceManager::sendLemma(Node ant, Node conc, const char* c) << std::endl; Trace("strings-assert") << "(assert (not " << ant << ")) ; conflict " << c << std::endl; + ++(d_statistics.d_conflictsInfer); d_out.conflict(ant); d_state.setConflict(); return; @@ -369,7 +370,7 @@ Node InferenceManager::getSymbolicDefinition(Node n, return NodeManager::currentNM()->mkNode(n.getKind(), children); } -void InferenceManager::registerLength(Node n) +Node InferenceManager::registerTerm(Node n) { Assert(n.getType().isStringLike()); NodeManager* nm = NodeManager::currentNM(); @@ -384,15 +385,14 @@ void InferenceManager::registerLength(Node n) // can register length term if it does not rewrite if (lsum == lsumb) { - registerLength(n, LENGTH_SPLIT); - return; + registerTermAtomic(n, LENGTH_SPLIT); + return Node::null(); } } Node sk = d_skCache.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_proxyVar[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 @@ -400,10 +400,8 @@ void InferenceManager::registerLength(Node n) if (n.isConst() || n.getKind() == STRING_CONCAT) { // do not send length lemma for sk. - registerLength(sk, LENGTH_IGNORE); + registerTermAtomic(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) { @@ -431,14 +429,11 @@ void InferenceManager::registerLength(Node n) Assert(!lsum.isNull()); d_proxyVarToLength[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); + + return nm->mkNode(AND, eq, ceq); } -void InferenceManager::registerLength(Node n, LengthStatus s) +void InferenceManager::registerTermAtomic(Node n, LengthStatus s) { if (d_lengthLemmaTermsCache.find(n) != d_lengthLemmaTermsCache.end()) { @@ -451,7 +446,25 @@ void InferenceManager::registerLength(Node n, LengthStatus s) // ignore it return; } + std::map reqPhase; + Node lenLem = getRegisterTermAtomicLemma(n, s, reqPhase); + if (!lenLem.isNull()) + { + Trace("strings-lemma") << "Strings::Lemma REGISTER-TERM-ATOMIC : " << lenLem + << std::endl; + Trace("strings-assert") << "(assert " << lenLem << ")" << std::endl; + ++(d_statistics.d_lemmasRegisterTermAtomic); + d_out.lemma(lenLem); + } + for (const std::pair& rp : reqPhase) + { + d_out.requirePhase(rp.first, rp.second); + } +} +Node InferenceManager::getRegisterTermAtomicLemma( + Node n, LengthStatus s, std::map& reqPhase) +{ NodeManager* nm = NodeManager::currentNM(); Node n_len = nm->mkNode(kind::STRING_LENGTH, n); @@ -463,8 +476,7 @@ void InferenceManager::registerLength(Node n, LengthStatus s) Trace("strings-lemma") << "Strings::Lemma SK-GEQ-ONE : " << len_geq_one << std::endl; Trace("strings-assert") << "(assert " << len_geq_one << ")" << std::endl; - d_out.lemma(len_geq_one); - return; + return len_geq_one; } if (s == LENGTH_ONE) @@ -473,11 +485,11 @@ void InferenceManager::registerLength(Node n, LengthStatus s) Trace("strings-lemma") << "Strings::Lemma SK-ONE : " << len_one << std::endl; Trace("strings-assert") << "(assert " << len_one << ")" << std::endl; - d_out.lemma(len_one); - return; + return len_one; } Assert(s == LENGTH_SPLIT); + std::vector lems; if (options::stringSplitEmp() || !options::stringLenGeqZ()) { Node n_len_eq_z = n_len.eqNode(d_zero); @@ -488,7 +500,7 @@ void InferenceManager::registerLength(Node n, LengthStatus s) if (!case_empty.isConst()) { Node lem = nm->mkNode(OR, case_empty, case_nempty); - d_out.lemma(lem); + lems.push_back(lem); Trace("strings-lemma") << "Strings::Lemma LENGTH >= 0 : " << lem << std::endl; // prefer trying the empty case first @@ -496,10 +508,10 @@ void InferenceManager::registerLength(Node n, LengthStatus s) // occur in the CNF stream. n_len_eq_z = Rewriter::rewrite(n_len_eq_z); Assert(!n_len_eq_z.isConst()); - d_out.requirePhase(n_len_eq_z, true); + reqPhase[n_len_eq_z] = true; n_len_eq_z_2 = Rewriter::rewrite(n_len_eq_z_2); Assert(!n_len_eq_z_2.isConst()); - d_out.requirePhase(n_len_eq_z_2, true); + reqPhase[n_len_eq_z_2] = true; } else if (!case_empty.getConst()) { @@ -507,7 +519,7 @@ void InferenceManager::registerLength(Node n, LengthStatus s) Trace("strings-lemma") << "Strings::Lemma LENGTH > 0 (non-empty): " << case_nempty << std::endl; - d_out.lemma(case_nempty); + lems.push_back(case_nempty); } else { @@ -523,8 +535,14 @@ void InferenceManager::registerLength(Node n, LengthStatus s) { Node n_len_geq = nm->mkNode(kind::GEQ, n_len, d_zero); n_len_geq = Rewriter::rewrite(n_len_geq); - d_out.lemma(n_len_geq); + lems.push_back(n_len_geq); + } + + if (lems.empty()) + { + return Node::null(); } + return lems.size() == 1 ? lems[0] : nm->mkNode(AND, lems); } void InferenceManager::addToExplanation(Node a, diff --git a/src/theory/strings/inference_manager.h b/src/theory/strings/inference_manager.h index c9d483c73..bd2f85d29 100644 --- a/src/theory/strings/inference_manager.h +++ b/src/theory/strings/inference_manager.h @@ -210,18 +210,23 @@ class InferenceManager * exists, otherwise it returns null. */ Node getProxyVariableFor(Node n) const; - /** register length + /** register term + * + * This method is called on non-constant string terms n. It returns a lemma + * that should be sent on the output channel of theory of strings upon + * registration of this term, or null if no lemma is necessary. * - * This method is called on non-constant string terms n. It sends a lemma - * on the output channel that ensures that the length n satisfies its assigned - * status (given by argument s). + * If n is an atomic term, the method registerTermAtomic is called for n + * and s = LENGTH_SPLIT and no lemma is returned. */ - void registerLength(Node n); + Node registerTerm(Node n); /** register length * - * This method is called on non-constant string terms n. It sends a lemma - * on the output channel that ensures that the length n satisfies its assigned - * status (given by argument s). + * This method is called on non-constant string terms n that are "atomic" + * with respect to length. That is, the rewritten form of len(n) is itself. + * + * It sends a lemma on the output channel that ensures that the length n + * satisfies its assigned status (given by argument s). * * If the status is LENGTH_ONE, we send the lemma len( n ) = 1. * @@ -238,7 +243,7 @@ class InferenceManager * In contrast to the above functions, it makes immediate calls to the output * channel instead of adding them to pending lists. */ - void registerLength(Node n, LengthStatus s); + void registerTermAtomic(Node n, LengthStatus s); //---------------------------- end proxy variables and length elaboration //----------------------------constructing antecedants @@ -337,6 +342,17 @@ class InferenceManager * equality engine of this class. */ void sendInfer(Node eq_exp, Node eq, const char* c); + /** + * Get the lemma required for registering the length information for + * atomic term n given length status s. For details, see registerTermAtomic. + * + * Additionally, this method may map literals to a required polarity in the + * argument reqPhase, which should be processed by a call to requiredPhase by + * the caller of this method. + */ + Node getRegisterTermAtomicLemma(Node n, + LengthStatus s, + std::map& reqPhase); /** the parent theory of strings object */ TheoryStrings& d_parent; diff --git a/src/theory/strings/sequences_stats.cpp b/src/theory/strings/sequences_stats.cpp index 0f1e93599..fb13cdab2 100644 --- a/src/theory/strings/sequences_stats.cpp +++ b/src/theory/strings/sequences_stats.cpp @@ -22,14 +22,42 @@ namespace theory { namespace strings { SequencesStatistics::SequencesStatistics() - : d_inferences("theory::strings::inferences") + : d_inferences("theory::strings::inferences"), + d_reductions("theory::strings::reductions"), + d_conflictsEqEngine("theory::strings::conflictsEqEngine", 0), + d_conflictsEagerPrefix("theory::strings::conflictsEagerPrefix", 0), + d_conflictsInfer("theory::strings::conflictsInfer", 0), + d_lemmasEagerPreproc("theory::strings::lemmasEagerPreproc", 0), + d_lemmasCmiSplit("theory::strings::lemmasCmiSplit", 0), + d_lemmasRegisterTerm("theory::strings::lemmasRegisterTerm", 0), + d_lemmasRegisterTermAtomic("theory::strings::lemmasRegisterTermAtomic", + 0), + d_lemmasInfer("theory::strings::lemmasInfer", 0) { smtStatisticsRegistry()->registerStat(&d_inferences); + smtStatisticsRegistry()->registerStat(&d_reductions); + smtStatisticsRegistry()->registerStat(&d_conflictsEqEngine); + smtStatisticsRegistry()->registerStat(&d_conflictsEagerPrefix); + smtStatisticsRegistry()->registerStat(&d_conflictsInfer); + smtStatisticsRegistry()->registerStat(&d_lemmasEagerPreproc); + smtStatisticsRegistry()->registerStat(&d_lemmasCmiSplit); + smtStatisticsRegistry()->registerStat(&d_lemmasRegisterTerm); + smtStatisticsRegistry()->registerStat(&d_lemmasRegisterTermAtomic); + smtStatisticsRegistry()->registerStat(&d_lemmasInfer); } SequencesStatistics::~SequencesStatistics() { smtStatisticsRegistry()->unregisterStat(&d_inferences); + smtStatisticsRegistry()->unregisterStat(&d_reductions); + smtStatisticsRegistry()->unregisterStat(&d_conflictsEqEngine); + smtStatisticsRegistry()->unregisterStat(&d_conflictsEagerPrefix); + smtStatisticsRegistry()->unregisterStat(&d_conflictsInfer); + smtStatisticsRegistry()->unregisterStat(&d_lemmasEagerPreproc); + smtStatisticsRegistry()->unregisterStat(&d_lemmasCmiSplit); + smtStatisticsRegistry()->unregisterStat(&d_lemmasRegisterTerm); + smtStatisticsRegistry()->unregisterStat(&d_lemmasRegisterTermAtomic); + smtStatisticsRegistry()->unregisterStat(&d_lemmasInfer); } } diff --git a/src/theory/strings/sequences_stats.h b/src/theory/strings/sequences_stats.h index b55178f4c..83a16cb23 100644 --- a/src/theory/strings/sequences_stats.h +++ b/src/theory/strings/sequences_stats.h @@ -17,6 +17,7 @@ #ifndef CVC4__THEORY__STRINGS__SEQUENCES_STATS_H #define CVC4__THEORY__STRINGS__SEQUENCES_STATS_H +#include "expr/kind.h" #include "theory/strings/infer_info.h" #include "util/statistics_registry.h" @@ -30,11 +31,32 @@ class SequencesStatistics SequencesStatistics(); ~SequencesStatistics(); - /** Counts the number of inferences made of each type of inference */ + /** Counts the number of applications of each type of inference */ HistogramStat d_inferences; + /** Counts the number of applications of each type of reduction */ + HistogramStat d_reductions; + //--------------- conflicts, partition of calls to OutputChannel::conflict + /** Number of equality engine conflicts */ + IntStat d_conflictsEqEngine; + /** Number of eager prefix conflicts */ + IntStat d_conflictsEagerPrefix; + /** Number of inference conflicts */ + IntStat d_conflictsInfer; + //--------------- end of conflicts + //--------------- lemmas, partition of calls to OutputChannel::lemma + /** Number of lemmas added due to eager preprocessing */ + IntStat d_lemmasEagerPreproc; + /** Number of collect model info splits */ + IntStat d_lemmasCmiSplit; + /** Number of lemmas added due to registering terms */ + IntStat d_lemmasRegisterTerm; + /** Number of lemmas added due to registering atomic terms */ + IntStat d_lemmasRegisterTermAtomic; + /** Number of lemmas added due to inferences */ + IntStat d_lemmasInfer; + //--------------- end of lemmas }; - } } } diff --git a/src/theory/strings/theory_strings.cpp b/src/theory/strings/theory_strings.cpp index 789099ee4..16183abdd 100644 --- a/src/theory/strings/theory_strings.cpp +++ b/src/theory/strings/theory_strings.cpp @@ -69,7 +69,7 @@ TheoryStrings::TheoryStrings(context::Context* c, const LogicInfo& logicInfo) : Theory(THEORY_STRINGS, c, u, out, valuation, logicInfo), d_notify(*this), - d_equalityEngine(d_notify, c, "theory::strings", true), + d_equalityEngine(d_notify, c, "theory::strings::ee", true), d_state(c, d_equalityEngine, d_valuation), d_im(*this, c, u, d_state, d_sk_cache, out, d_statistics), d_pregistered_terms_cache(u), @@ -85,8 +85,15 @@ TheoryStrings::TheoryStrings(context::Context* c, { setupExtTheory(); ExtTheory* extt = getExtTheory(); - d_esolver.reset(new ExtfSolver( - c, u, d_state, d_im, d_sk_cache, d_bsolver, d_csolver, extt)); + d_esolver.reset(new ExtfSolver(c, + u, + d_state, + d_im, + d_sk_cache, + d_bsolver, + d_csolver, + extt, + d_statistics)); d_rsolver.reset(new RegExpSolver(*this, d_state, d_im, *d_esolver, c, u)); // The kinds we are treating as function application in congruence @@ -464,6 +471,7 @@ bool TheoryStrings::collectModelInfoType( for (const Node& sl : len_splits) { Node spl = nm->mkNode(OR, sl, sl.negate()); + ++(d_statistics.d_lemmasCmiSplit); d_out->lemma(spl); } return false; @@ -779,6 +787,7 @@ void TheoryStrings::conflict(TNode a, TNode b){ Node conflictNode; conflictNode = explain( a.eqNode(b) ); Trace("strings-conflict") << "CONFLICT: Eq engine conflict : " << conflictNode << std::endl; + ++(d_statistics.d_conflictsEqEngine); d_out->conflict( conflictNode ); } } @@ -947,6 +956,7 @@ void TheoryStrings::assertPendingFact(Node atom, bool polarity, Node exp) { d_state.setConflict(); Trace("strings-conflict") << "CONFLICT: Eager prefix : " << conflictNode << std::endl; + ++(d_statistics.d_conflictsEagerPrefix); d_out->conflict(conflictNode); } } @@ -1078,12 +1088,13 @@ void TheoryStrings::registerTerm(Node n, int effort) NodeManager* nm = NodeManager::currentNM(); Debug("strings-register") << "TheoryStrings::registerTerm() " << n << ", effort = " << effort << std::endl; + Node regTermLem; if (tn.isStringLike()) { // register length information: // for variables, split on empty vs positive length // for concat/const/replace, introduce proxy var and state length relation - d_im.registerLength(n); + regTermLem = d_im.registerTerm(n); } else if (n.getKind() == STRING_TO_CODE) { @@ -1095,20 +1106,22 @@ void TheoryStrings::registerTerm(Node n, int effort) 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); + regTermLem = nm->mkNode(ITE, code_len, code_range, code_eq_neg1); } 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 + regTermLem = nm->mkNode(AND, + nm->mkNode(GEQ, n, nm->mkConst(Rational(-1))), + nm->mkNode(LEQ, n, len)); + } + if (!regTermLem.isNull()) + { + Trace("strings-lemma") << "Strings::Lemma REG-TERM : " << regTermLem << std::endl; - d_out->lemma(lem); + Trace("strings-assert") << "(assert " << regTermLem << ")" << std::endl; + ++(d_statistics.d_lemmasRegisterTerm); + d_out->lemma(regTermLem); } } @@ -1153,6 +1166,7 @@ Node TheoryStrings::ppRewrite(TNode atom) { Trace("strings-ppr") << " rewrote " << atom << " -> " << ret << ", with " << new_nodes.size() << " lemmas." << std::endl; for( unsigned i=0; ilemma( new_nodes[i] ); } return ret; diff --git a/src/theory/strings/theory_strings_preprocess.cpp b/src/theory/strings/theory_strings_preprocess.cpp index d4183700d..b35c4a921 100644 --- a/src/theory/strings/theory_strings_preprocess.cpp +++ b/src/theory/strings/theory_strings_preprocess.cpp @@ -31,8 +31,10 @@ namespace CVC4 { namespace theory { namespace strings { -StringsPreprocess::StringsPreprocess(SkolemCache *sc, context::UserContext *u) - : d_sc(sc) +StringsPreprocess::StringsPreprocess(SkolemCache* sc, + context::UserContext* u, + SequencesStatistics& stats) + : d_sc(sc), d_statistics(stats) { //Constants d_zero = NodeManager::currentNM()->mkConst(Rational(0)); @@ -637,6 +639,7 @@ Node StringsPreprocess::simplify( Node t, std::vector< Node > &new_nodes ) { Trace("strings-preprocess") << " " << new_nodes[i] << std::endl; } } + d_statistics.d_reductions << t.getKind(); } else { diff --git a/src/theory/strings/theory_strings_preprocess.h b/src/theory/strings/theory_strings_preprocess.h index b96d619ef..155b9014c 100644 --- a/src/theory/strings/theory_strings_preprocess.h +++ b/src/theory/strings/theory_strings_preprocess.h @@ -22,6 +22,7 @@ #include #include "context/cdhashmap.h" #include "theory/rewriter.h" +#include "theory/strings/sequences_stats.h" #include "theory/strings/skolem_cache.h" #include "theory/theory.h" #include "util/hash.h" @@ -38,48 +39,52 @@ namespace strings { * reductions" inference schema of TheoryStrings. */ class StringsPreprocess { -public: - StringsPreprocess(SkolemCache *sc, context::UserContext *u); - ~StringsPreprocess(); - /** - * Returns a node t' such that - * (exists k) new_nodes => t = t' - * is valid, where k are the free skolems introduced when constructing - * new_nodes. - */ - Node simplify(Node t, std::vector &new_nodes); - /** - * Applies simplifyRec on t until a fixed point is reached, and returns - * the resulting term t', which is such that - * (exists k) new_nodes => t = t' - * is valid, where k are the free skolems introduced when constructing - * new_nodes. - */ - Node processAssertion(Node t, std::vector &new_nodes); - /** - * Replaces all formulas t in vec_node with an equivalent formula t' that - * contains no free instances of extended functions (that is, extended - * functions may only appear beneath quantifiers). This applies simplifyRec - * on each assertion in vec_node until a fixed point is reached. - */ - void processAssertions(std::vector &vec_node); + public: + StringsPreprocess(SkolemCache* sc, + context::UserContext* u, + SequencesStatistics& stats); + ~StringsPreprocess(); + /** + * Returns a node t' such that + * (exists k) new_nodes => t = t' + * is valid, where k are the free skolems introduced when constructing + * new_nodes. + */ + Node simplify(Node t, std::vector& new_nodes); + /** + * Applies simplifyRec on t until a fixed point is reached, and returns + * the resulting term t', which is such that + * (exists k) new_nodes => t = t' + * is valid, where k are the free skolems introduced when constructing + * new_nodes. + */ + Node processAssertion(Node t, std::vector& new_nodes); + /** + * Replaces all formulas t in vec_node with an equivalent formula t' that + * contains no free instances of extended functions (that is, extended + * functions may only appear beneath quantifiers). This applies simplifyRec + * on each assertion in vec_node until a fixed point is reached. + */ + void processAssertions(std::vector& vec_node); -private: - /** commonly used constants */ - Node d_zero; - Node d_one; - Node d_neg_one; - Node d_empty_str; - /** pointer to the skolem cache used by this class */ - SkolemCache *d_sc; - /** - * Applies simplify to all top-level extended function subterms of t. New - * assertions created in this reduction are added to new_nodes. The argument - * visited stores a cache of previous results. - */ - Node simplifyRec(Node t, - std::vector &new_nodes, - std::map &visited); + private: + /** commonly used constants */ + Node d_zero; + Node d_one; + Node d_neg_one; + Node d_empty_str; + /** pointer to the skolem cache used by this class */ + SkolemCache* d_sc; + /** Reference to the statistics for the theory of strings/sequences. */ + SequencesStatistics& d_statistics; + /** + * Applies simplify to all top-level extended function subterms of t. New + * assertions created in this reduction are added to new_nodes. The argument + * visited stores a cache of previous results. + */ + Node simplifyRec(Node t, + std::vector& new_nodes, + std::map& visited); }; }/* CVC4::theory::strings namespace */ -- cgit v1.2.3 From ea8937689b097d41c70060ed17495feed5d6b95b Mon Sep 17 00:00:00 2001 From: Andrew Reynolds Date: Thu, 26 Mar 2020 14:42:49 -0500 Subject: Disable slow regression (#4157) Should fix timeout in asan build. --- test/regress/CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/regress/CMakeLists.txt b/test/regress/CMakeLists.txt index 0eb6bc2d2..8fab16b44 100644 --- a/test/regress/CMakeLists.txt +++ b/test/regress/CMakeLists.txt @@ -1857,7 +1857,6 @@ set(regress_1_tests regress1/sygus/issue3498.smt2 regress1/sygus/issue3514.smt2 regress1/sygus/issue3507.smt2 - regress1/sygus/issue3580.sy regress1/sygus/issue3633.smt2 regress1/sygus/issue3634.smt2 regress1/sygus/issue3635.smt2 @@ -2388,6 +2387,8 @@ set(regression_disabled_tests regress1/sygus/array_search_2.sy regress1/sygus/array_sum_2_5.sy regress1/sygus/crcy-si-rcons.sy + # currently slow at c9fd28a + regress1/sygus/issue3580.sy regress2/arith/arith-int-098.cvc regress2/arith/miplib-opt1217--27.smt2 regress2/arith/miplib-pp08a-3000.smt2 -- cgit v1.2.3 From 479584c063e01e8a5f79ab039c4fb7003e244bbd Mon Sep 17 00:00:00 2001 From: Amalee Date: Thu, 26 Mar 2020 16:32:51 -0700 Subject: Added unit-cube-like test for branch and bound (#3922) * unit-cude test wip * test for wip unit cube test * fixed simple rounding * wip * Passing tests except for sat vs unknown ones * added flag for cube test * put example back to normal * Fixed for style guidelines. * fixed rewrite bug * removed extra comments * unit-cude test wip * test for wip unit cube test * fixed simple rounding * wip * Passing tests except for sat vs unknown ones * added flag for cube test * put example back to normal * Fixed for style guidelines. * fixed rewrite bug * removed extra comments * Small fixes based on PR feedback * replace NodeManager::currentNM with nm and clang formatted * renamed test * Added a regression test that triggers branch and bound * Added ; COMMAND-LINE: --arith-brab * Updated arith-brab test * arith-brab enabled by default * Added --nl-ext-tplanes to regress0/nl/ext-rew-aggr-test.smt2 Co-authored-by: Amalee Wilson Co-authored-by: Ahmed Irfan <43099566+ahmed-irfan@users.noreply.github.com> Co-authored-by: Andrew Reynolds --- src/options/arith_options.toml | 9 ++++++ src/theory/arith/theory_arith_private.cpp | 39 +++++++++++++++++++----- test/regress/regress0/nl/ext-rew-aggr-test.smt2 | 2 +- test/regress/regress1/arith/arith-brab-test.smt2 | 23 ++++++++++++++ 4 files changed, 64 insertions(+), 9 deletions(-) create mode 100644 test/regress/regress1/arith/arith-brab-test.smt2 diff --git a/src/options/arith_options.toml b/src/options/arith_options.toml index ab8164130..1c0351bcb 100644 --- a/src/options/arith_options.toml +++ b/src/options/arith_options.toml @@ -551,3 +551,12 @@ header = "options/arith_options.h" default = "true" read_only = true help = "whether to increment the precision for irrational function constraints" + +[[option]] + name = "brabTest" + category = "regular" + long = "arith-brab" + type = "bool" + default = "true" + read_only = true + help = "whether to use simple rounding, similar to a unit-cube test, for integers" diff --git a/src/theory/arith/theory_arith_private.cpp b/src/theory/arith/theory_arith_private.cpp index 4e2a5bba1..bed59baf5 100644 --- a/src/theory/arith/theory_arith_private.cpp +++ b/src/theory/arith/theory_arith_private.cpp @@ -3943,15 +3943,38 @@ Node TheoryArithPrivate::branchIntegerVariable(ArithVar x) const { TNode var = d_partialModel.asNode(x); Integer floor_d = d.floor(); - //Node eq = Rewriter::rewrite(NodeManager::currentNM()->mkNode(kind::EQUAL, var, mkRationalNode(floor_d+1))); - //Node diseq = eq.notNode(); - - Node ub = Rewriter::rewrite(NodeManager::currentNM()->mkNode(kind::LEQ, var, mkRationalNode(floor_d))); - Node lb = ub.notNode(); - + Node lem; + NodeManager* nm = NodeManager::currentNM(); + if (options::brabTest()) + { + Trace("integers") << "branch-round-and-bound enabled" << endl; + Integer ceil_d = d.ceiling(); + Rational f = r - floor_d; + // Multiply by -1 to get abs value. + Rational c = (r - ceil_d) * (-1); + Integer nearest = (c > f) ? floor_d : ceil_d; + + // Prioritize trying a simple rounding of the real solution first, + // it that fails, fall back on original branch and bound strategy. + Node ub = Rewriter::rewrite( + nm->mkNode(kind::LEQ, var, mkRationalNode(nearest - 1))); + Node lb = Rewriter::rewrite( + nm->mkNode(kind::GEQ, var, mkRationalNode(nearest + 1))); + lem = nm->mkNode(kind::OR, ub, lb); + Node eq = Rewriter::rewrite( + nm->mkNode(kind::EQUAL, var, mkRationalNode(nearest))); + Node literal = d_containing.getValuation().ensureLiteral(eq); + d_containing.getOutputChannel().requirePhase(literal, true); + lem = nm->mkNode(kind::OR, literal, lem); + } + else + { + Node ub = + Rewriter::rewrite(nm->mkNode(kind::LEQ, var, mkRationalNode(floor_d))); + Node lb = ub.notNode(); + lem = nm->mkNode(kind::OR, ub, lb); + } - //Node lem = NodeManager::currentNM()->mkNode(kind::OR, eq, diseq); - Node lem = NodeManager::currentNM()->mkNode(kind::OR, ub, lb); Trace("integers") << "integers: branch & bound: " << lem << endl; if(isSatLiteral(lem[0])) { Debug("integers") << " " << lem[0] << " == " << getSatValue(lem[0]) << endl; diff --git a/test/regress/regress0/nl/ext-rew-aggr-test.smt2 b/test/regress/regress0/nl/ext-rew-aggr-test.smt2 index 47006622d..c540ecbe5 100644 --- a/test/regress/regress0/nl/ext-rew-aggr-test.smt2 +++ b/test/regress/regress0/nl/ext-rew-aggr-test.smt2 @@ -1,4 +1,4 @@ -; COMMAND-LINE: --ext-rew-prep --ext-rew-prep-agg --no-new-prop +; COMMAND-LINE: --ext-rew-prep --ext-rew-prep-agg --no-new-prop --nl-ext-tplanes ; EXPECT: sat (set-info :smt-lib-version 2.6) (set-logic QF_NIA) diff --git a/test/regress/regress1/arith/arith-brab-test.smt2 b/test/regress/regress1/arith/arith-brab-test.smt2 new file mode 100644 index 000000000..7856ae0e6 --- /dev/null +++ b/test/regress/regress1/arith/arith-brab-test.smt2 @@ -0,0 +1,23 @@ +; COMMAND-LINE: --arith-brab +; COMMAND-LINE: --no-arith-brab +; EXPECT: sat +(set-logic ALL) + +(declare-fun x1 () Real) +(declare-fun y1 () Real) +(declare-fun m1 () Real) +(declare-fun b1 () Real) + +(declare-fun x () Int) +(declare-fun y () Int) + +(assert (= y1 (+ b1 (* m1 x1)))) +(assert (= x1 (/ m1 (- y1 b1)))) +(assert (= b1 1.25)) +(assert (= m1 (/ 1 3))) + +(assert (and (> x x1) (> y y1))) + +(check-sat) +(exit) + -- cgit v1.2.3 From fa2ba76ef83497108942ebb91cdb07fdfeed505b Mon Sep 17 00:00:00 2001 From: Andrew Reynolds Date: Thu, 26 Mar 2020 23:13:39 -0500 Subject: Move set defaults function to its own file (#4154) This moves SmtEngine::setDefaults to its own file. This design is not final. One could imagine this being a part of a "OptionsSetter" utility. I am leaving this as is until we refactor the relationship between SmtEngine and Options. Regardless, the general file structure should be such that this method is separate from SmtEngine, since setting default options is a large task that should be addressed independently from the core of SmtEngine. This is initial preparation towards converting the SmtEngine from Expr -> Node. A few very minor changes were made to the code to make the separation possible. --- src/CMakeLists.txt | 2 + src/smt/set_defaults.cpp | 1353 ++++++++++++++++++++++++++++++++++++++++++++++ src/smt/set_defaults.h | 42 ++ src/smt/smt_engine.cpp | 1223 +---------------------------------------- src/smt/smt_engine.h | 8 +- 5 files changed, 1409 insertions(+), 1219 deletions(-) create mode 100644 src/smt/set_defaults.cpp create mode 100644 src/smt/set_defaults.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 4520ee421..dd58c74ee 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -236,6 +236,8 @@ libcvc4_add_sources( smt/model_core_builder.h smt/model_blocker.cpp smt/model_blocker.h + smt/set_defaults.cpp + smt/set_defaults.h smt/smt_engine.cpp smt/smt_engine.h smt/smt_engine_scope.cpp diff --git a/src/smt/set_defaults.cpp b/src/smt/set_defaults.cpp new file mode 100644 index 000000000..e0493b180 --- /dev/null +++ b/src/smt/set_defaults.cpp @@ -0,0 +1,1353 @@ +/********************* */ +/*! \file set_defaults.cpp + ** \verbatim + ** Top contributors (to current version): + ** Andrew Reynolds + ** 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 setting default options. + **/ + +#include "smt/set_defaults.h" + +#include "base/output.h" +#include "options/arith_options.h" +#include "options/arrays_options.h" +#include "options/base_options.h" +#include "options/booleans_options.h" +#include "options/bv_options.h" +#include "options/datatypes_options.h" +#include "options/decision_options.h" +#include "options/language.h" +#include "options/main_options.h" +#include "options/open_ostream.h" +#include "options/option_exception.h" +#include "options/printer_options.h" +#include "options/proof_options.h" +#include "options/prop_options.h" +#include "options/quantifiers_options.h" +#include "options/sep_options.h" +#include "options/set_language.h" +#include "options/smt_options.h" +#include "options/strings_options.h" +#include "options/theory_options.h" +#include "options/uf_options.h" +#include "theory/theory.h" + +using namespace CVC4::theory; + +namespace CVC4 { +namespace smt { + +void setDefaults(SmtEngine& smte, LogicInfo& logic) +{ + // Language-based defaults + if (!options::bitvectorDivByZeroConst.wasSetByUser()) + { + // Bitvector-divide-by-zero changed semantics in SMT LIB 2.6, thus we + // set this option if the input format is SMT LIB 2.6. We also set this + // option if we are sygus, since we assume SMT LIB 2.6 semantics for sygus. + options::bitvectorDivByZeroConst.set( + language::isInputLang_smt2_6(options::inputLanguage()) + || language::isInputLangSygus(options::inputLanguage())); + } + bool is_sygus = language::isInputLangSygus(options::inputLanguage()); + + if (options::bitblastMode() == options::BitblastMode::EAGER) + { + if (options::produceModels() + && (logic.isTheoryEnabled(THEORY_ARRAYS) + || logic.isTheoryEnabled(THEORY_UF))) + { + if (options::bitblastMode.wasSetByUser() + || options::produceModels.wasSetByUser()) + { + throw OptionException(std::string( + "Eager bit-blasting currently does not support model generation " + "for the combination of bit-vectors with arrays or uinterpreted " + "functions. Try --bitblast=lazy")); + } + Notice() << "SmtEngine: setting bit-blast mode to lazy to support model" + << "generation" << std::endl; + smte.setOption("bitblastMode", SExpr("lazy")); + } + else if (!options::incrementalSolving()) + { + options::ackermann.set(true); + } + + if (options::incrementalSolving() && !logic.isPure(THEORY_BV)) + { + throw OptionException( + "Incremental eager bit-blasting is currently " + "only supported for QF_BV. Try --bitblast=lazy."); + } + } + + if (options::solveIntAsBV() > 0) + { + logic = logic.getUnlockedCopy(); + logic.enableTheory(THEORY_BV); + logic.lock(); + } + + if (options::solveBVAsInt() > 0) + { + if (logic.isTheoryEnabled(THEORY_BV)) + { + logic = logic.getUnlockedCopy(); + logic.enableTheory(THEORY_ARITH); + logic.arithNonLinear(); + logic.lock(); + } + } + + // set options about ackermannization + if (options::ackermann() && options::produceModels() + && (logic.isTheoryEnabled(THEORY_ARRAYS) + || logic.isTheoryEnabled(THEORY_UF))) + { + if (options::produceModels.wasSetByUser()) + { + throw OptionException(std::string( + "Ackermannization currently does not support model generation.")); + } + Notice() << "SmtEngine: turn off ackermannization to support model" + << "generation" << std::endl; + options::ackermann.set(false); + } + + if (options::ackermann()) + { + if (options::incrementalSolving()) + { + throw OptionException( + "Incremental Ackermannization is currently not supported."); + } + + if (logic.isQuantified()) + { + throw LogicException("Cannot use Ackermannization on quantified formula"); + } + + if (logic.isTheoryEnabled(THEORY_UF)) + { + logic = logic.getUnlockedCopy(); + logic.disableTheory(THEORY_UF); + logic.lock(); + } + if (logic.isTheoryEnabled(THEORY_ARRAYS)) + { + logic = logic.getUnlockedCopy(); + logic.disableTheory(THEORY_ARRAYS); + logic.lock(); + } + } + + // Set default options associated with strings-exp. We also set these options + // if we are using eager string preprocessing, which may introduce quantified + // formulas at preprocess time. + if (options::stringExp() || !options::stringLazyPreproc()) + { + // We require quantifiers since extended functions reduce using them. + if (!logic.isQuantified()) + { + logic = logic.getUnlockedCopy(); + logic.enableQuantifiers(); + logic.lock(); + Trace("smt") << "turning on quantifier logic, for strings-exp" + << std::endl; + } + // We require bounded quantifier handling. + if (!options::fmfBound.wasSetByUser()) + { + options::fmfBound.set(true); + Trace("smt") << "turning on fmf-bound-int, for strings-exp" << std::endl; + } + // Turn off E-matching, since some bounded quantifiers introduced by strings + // (e.g. for replaceall) admit matching loops. + if (!options::eMatching.wasSetByUser()) + { + options::eMatching.set(false); + Trace("smt") << "turning off E-matching, for strings-exp" << std::endl; + } + // Do not eliminate extended arithmetic symbols from quantified formulas, + // since some strategies, e.g. --re-elim-agg, introduce them. + if (!options::elimExtArithQuant.wasSetByUser()) + { + options::elimExtArithQuant.set(false); + Trace("smt") << "turning off elim-ext-arith-quant, for strings-exp" + << std::endl; + } + } + + // sygus inference may require datatypes + if (!smte.isInternalSubsolver()) + { + if (options::produceAbducts() || options::sygusInference() + || options::sygusRewSynthInput()) + { + // since we are trying to recast as sygus, we assume the input is sygus + is_sygus = true; + } + } + + // We now know whether the input is sygus. Update the logic to incorporate + // the theories we need internally for handling sygus problems. + if (is_sygus) + { + logic = logic.getUnlockedCopy(); + logic.enableSygus(); + logic.lock(); + } + + // sygus core connective requires unsat cores + if (options::sygusCoreConnective()) + { + options::unsatCores.set(true); + } + + if ((options::checkModels() || options::checkSynthSol() + || options::produceAbducts() + || options::modelCoresMode() != options::ModelCoresMode::NONE + || options::blockModelsMode() != options::BlockModelsMode::NONE) + && !options::produceAssertions()) + { + Notice() << "SmtEngine: turning on produce-assertions to support " + << "option requiring assertions." << std::endl; + smte.setOption("produce-assertions", SExpr("true")); + } + + // Disable options incompatible with incremental solving, unsat cores, and + // proofs or output an error if enabled explicitly + if (options::incrementalSolving() || options::unsatCores() + || options::proof()) + { + if (options::unconstrainedSimp()) + { + if (options::unconstrainedSimp.wasSetByUser()) + { + throw OptionException( + "unconstrained simplification not supported with unsat " + "cores/proofs/incremental solving"); + } + Notice() << "SmtEngine: turning off unconstrained simplification to " + "support unsat cores/proofs/incremental solving" + << std::endl; + options::unconstrainedSimp.set(false); + } + } + else + { + // Turn on unconstrained simplification for QF_AUFBV + if (!options::unconstrainedSimp.wasSetByUser()) + { + bool uncSimp = !logic.isQuantified() && !options::produceModels() + && !options::produceAssignments() + && !options::checkModels() + && (logic.isTheoryEnabled(THEORY_ARRAYS) + && logic.isTheoryEnabled(THEORY_BV)); + Trace("smt") << "setting unconstrained simplification to " << uncSimp + << std::endl; + options::unconstrainedSimp.set(uncSimp); + } + } + + if (options::incrementalSolving() || options::proof()) + { + if (options::sygusInference()) + { + if (options::sygusInference.wasSetByUser()) + { + throw OptionException( + "sygus inference not supported with proofs/incremental solving"); + } + Notice() << "SmtEngine: turning off sygus inference to support " + "proofs/incremental solving" + << std::endl; + options::sygusInference.set(false); + } + } + + // Disable options incompatible with unsat cores and proofs or output an + // error if enabled explicitly + if (options::unsatCores() || options::proof()) + { + if (options::simplificationMode() != options::SimplificationMode::NONE) + { + if (options::simplificationMode.wasSetByUser()) + { + throw OptionException( + "simplification not supported with unsat cores/proofs"); + } + Notice() << "SmtEngine: turning off simplification to support unsat " + "cores/proofs" + << std::endl; + options::simplificationMode.set(options::SimplificationMode::NONE); + } + + if (options::pbRewrites()) + { + if (options::pbRewrites.wasSetByUser()) + { + throw OptionException( + "pseudoboolean rewrites not supported with unsat cores/proofs"); + } + Notice() << "SmtEngine: turning off pseudoboolean rewrites to support " + "unsat cores/proofs" + << std::endl; + smte.setOption("pb-rewrites", false); + } + + if (options::sortInference()) + { + if (options::sortInference.wasSetByUser()) + { + throw OptionException( + "sort inference not supported with unsat cores/proofs"); + } + Notice() << "SmtEngine: turning off sort inference to support unsat " + "cores/proofs" + << std::endl; + options::sortInference.set(false); + } + + if (options::preSkolemQuant()) + { + if (options::preSkolemQuant.wasSetByUser()) + { + throw OptionException( + "pre-skolemization not supported with unsat cores/proofs"); + } + Notice() << "SmtEngine: turning off pre-skolemization to support unsat " + "cores/proofs" + << std::endl; + options::preSkolemQuant.set(false); + } + + if (options::solveBVAsInt() > 0) + { + /** + * Operations on 1 bits are better handled as Boolean operations + * than as integer operations. + * Therefore, we enable bv-to-bool, which runs before + * the translation to integers. + */ + options::bitvectorToBool.set(true); + } + + if (options::bitvectorToBool()) + { + if (options::bitvectorToBool.wasSetByUser()) + { + throw OptionException( + "bv-to-bool not supported with unsat cores/proofs"); + } + Notice() << "SmtEngine: turning off bitvector-to-bool to support unsat " + "cores/proofs" + << std::endl; + options::bitvectorToBool.set(false); + } + + if (options::boolToBitvector() != options::BoolToBVMode::OFF) + { + if (options::boolToBitvector.wasSetByUser()) + { + throw OptionException( + "bool-to-bv != off not supported with unsat cores/proofs"); + } + Notice() << "SmtEngine: turning off bool-to-bv to support unsat " + "cores/proofs" + << std::endl; + smte.setOption("boolToBitvector", SExpr("off")); + } + + if (options::bvIntroducePow2()) + { + if (options::bvIntroducePow2.wasSetByUser()) + { + throw OptionException( + "bv-intro-pow2 not supported with unsat cores/proofs"); + } + Notice() << "SmtEngine: turning off bv-intro-pow2 to support " + "unsat-cores/proofs" + << std::endl; + smte.setOption("bv-intro-pow2", false); + } + + if (options::repeatSimp()) + { + if (options::repeatSimp.wasSetByUser()) + { + throw OptionException( + "repeat-simp not supported with unsat cores/proofs"); + } + Notice() << "SmtEngine: turning off repeat-simp to support unsat " + "cores/proofs" + << std::endl; + smte.setOption("repeat-simp", false); + } + + if (options::globalNegate()) + { + if (options::globalNegate.wasSetByUser()) + { + throw OptionException( + "global-negate not supported with unsat cores/proofs"); + } + Notice() << "SmtEngine: turning off global-negate to support unsat " + "cores/proofs" + << std::endl; + smte.setOption("global-negate", false); + } + + if (options::bitvectorAig()) + { + throw OptionException( + "bitblast-aig not supported with unsat cores/proofs"); + } + } + else + { + // by default, nonclausal simplification is off for QF_SAT + if (!options::simplificationMode.wasSetByUser()) + { + bool qf_sat = logic.isPure(THEORY_BOOL) && !logic.isQuantified(); + Trace("smt") << "setting simplification mode to <" + << logic.getLogicString() << "> " << (!qf_sat) << std::endl; + // simplification=none works better for SMT LIB benchmarks with + // quantifiers, not others options::simplificationMode.set(qf_sat || + // quantifiers ? options::SimplificationMode::NONE : + // options::SimplificationMode::BATCH); + options::simplificationMode.set(qf_sat + ? options::SimplificationMode::NONE + : options::SimplificationMode::BATCH); + } + } + + if (options::cbqiBv() && logic.isQuantified()) + { + if (options::boolToBitvector() != options::BoolToBVMode::OFF) + { + if (options::boolToBitvector.wasSetByUser()) + { + throw OptionException( + "bool-to-bv != off not supported with CBQI BV for quantified " + "logics"); + } + Notice() << "SmtEngine: turning off bool-to-bitvector to support CBQI BV" + << std::endl; + smte.setOption("boolToBitvector", SExpr("off")); + } + } + + // cases where we need produce models + if (!options::produceModels() + && (options::produceAssignments() || options::sygusRewSynthCheck() + || is_sygus)) + { + Notice() << "SmtEngine: turning on produce-models" << std::endl; + smte.setOption("produce-models", SExpr("true")); + } + + // Set the options for the theoryOf + if (!options::theoryOfMode.wasSetByUser()) + { + if (logic.isSharingEnabled() && !logic.isTheoryEnabled(THEORY_BV) + && !logic.isTheoryEnabled(THEORY_STRINGS) + && !logic.isTheoryEnabled(THEORY_SETS)) + { + Trace("smt") << "setting theoryof-mode to term-based" << std::endl; + options::theoryOfMode.set(options::TheoryOfMode::THEORY_OF_TERM_BASED); + } + } + + // strings require LIA, UF; widen the logic + if (logic.isTheoryEnabled(THEORY_STRINGS)) + { + LogicInfo log(logic.getUnlockedCopy()); + // Strings requires arith for length constraints, and also UF + if (!logic.isTheoryEnabled(THEORY_UF)) + { + Trace("smt") << "because strings are enabled, also enabling UF" + << std::endl; + log.enableTheory(THEORY_UF); + } + if (!logic.isTheoryEnabled(THEORY_ARITH) || logic.isDifferenceLogic() + || !logic.areIntegersUsed()) + { + Trace("smt") << "because strings are enabled, also enabling linear " + "integer arithmetic" + << std::endl; + log.enableTheory(THEORY_ARITH); + log.enableIntegers(); + log.arithOnlyLinear(); + } + logic = log; + logic.lock(); + } + if (logic.isTheoryEnabled(THEORY_ARRAYS) + || logic.isTheoryEnabled(THEORY_DATATYPES) + || logic.isTheoryEnabled(THEORY_SETS)) + { + if (!logic.isTheoryEnabled(THEORY_UF)) + { + LogicInfo log(logic.getUnlockedCopy()); + Trace("smt") << "because a theory that permits Boolean terms is enabled, " + "also enabling UF" + << std::endl; + log.enableTheory(THEORY_UF); + logic = log; + logic.lock(); + } + } + + // by default, symmetry breaker is on only for non-incremental QF_UF + if (!options::ufSymmetryBreaker.wasSetByUser()) + { + bool qf_uf_noinc = logic.isPure(THEORY_UF) && !logic.isQuantified() + && !options::incrementalSolving() && !options::proof() + && !options::unsatCores(); + Trace("smt") << "setting uf symmetry breaker to " << qf_uf_noinc + << std::endl; + options::ufSymmetryBreaker.set(qf_uf_noinc); + } + + // If in arrays, set the UF handler to arrays + if (logic.isTheoryEnabled(THEORY_ARRAYS) + && (!logic.isQuantified() + || (logic.isQuantified() && !logic.isTheoryEnabled(THEORY_UF)))) + { + Theory::setUninterpretedSortOwner(THEORY_ARRAYS); + } + else + { + Theory::setUninterpretedSortOwner(THEORY_UF); + } + + if (!options::simplifyWithCareEnabled.wasSetByUser()) + { + bool qf_aufbv = + !logic.isQuantified() && logic.isTheoryEnabled(THEORY_ARRAYS) + && logic.isTheoryEnabled(THEORY_UF) && logic.isTheoryEnabled(THEORY_BV); + + bool withCare = qf_aufbv; + Trace("smt") << "setting ite simplify with care to " << withCare + << std::endl; + options::simplifyWithCareEnabled.set(withCare); + } + // Turn off array eager index splitting for QF_AUFLIA + if (!options::arraysEagerIndexSplitting.wasSetByUser()) + { + if (not logic.isQuantified() && logic.isTheoryEnabled(THEORY_ARRAYS) + && logic.isTheoryEnabled(THEORY_UF) + && logic.isTheoryEnabled(THEORY_ARITH)) + { + Trace("smt") << "setting array eager index splitting to false" + << std::endl; + options::arraysEagerIndexSplitting.set(false); + } + } + // Turn on multiple-pass non-clausal simplification for QF_AUFBV + if (!options::repeatSimp.wasSetByUser()) + { + bool repeatSimp = !logic.isQuantified() + && (logic.isTheoryEnabled(THEORY_ARRAYS) + && logic.isTheoryEnabled(THEORY_UF) + && logic.isTheoryEnabled(THEORY_BV)) + && !options::unsatCores(); + Trace("smt") << "setting repeat simplification to " << repeatSimp + << std::endl; + options::repeatSimp.set(repeatSimp); + } + + if (options::boolToBitvector() == options::BoolToBVMode::ALL + && !logic.isTheoryEnabled(THEORY_BV)) + { + if (options::boolToBitvector.wasSetByUser()) + { + throw OptionException( + "bool-to-bv=all not supported for non-bitvector logics."); + } + Notice() << "SmtEngine: turning off bool-to-bv for non-bv logic: " + << logic.getLogicString() << std::endl; + smte.setOption("boolToBitvector", SExpr("off")); + } + + if (!options::bvEagerExplanations.wasSetByUser() + && logic.isTheoryEnabled(THEORY_ARRAYS) + && logic.isTheoryEnabled(THEORY_BV)) + { + Trace("smt") << "enabling eager bit-vector explanations " << std::endl; + options::bvEagerExplanations.set(true); + } + + // Turn on arith rewrite equalities only for pure arithmetic + if (!options::arithRewriteEq.wasSetByUser()) + { + bool arithRewriteEq = + logic.isPure(THEORY_ARITH) && logic.isLinear() && !logic.isQuantified(); + Trace("smt") << "setting arith rewrite equalities " << arithRewriteEq + << std::endl; + options::arithRewriteEq.set(arithRewriteEq); + } + if (!options::arithHeuristicPivots.wasSetByUser()) + { + int16_t heuristicPivots = 5; + if (logic.isPure(THEORY_ARITH) && !logic.isQuantified()) + { + if (logic.isDifferenceLogic()) + { + heuristicPivots = -1; + } + else if (!logic.areIntegersUsed()) + { + heuristicPivots = 0; + } + } + Trace("smt") << "setting arithHeuristicPivots " << heuristicPivots + << std::endl; + options::arithHeuristicPivots.set(heuristicPivots); + } + if (!options::arithPivotThreshold.wasSetByUser()) + { + uint16_t pivotThreshold = 2; + if (logic.isPure(THEORY_ARITH) && !logic.isQuantified()) + { + if (logic.isDifferenceLogic()) + { + pivotThreshold = 16; + } + } + Trace("smt") << "setting arith arithPivotThreshold " << pivotThreshold + << std::endl; + options::arithPivotThreshold.set(pivotThreshold); + } + if (!options::arithStandardCheckVarOrderPivots.wasSetByUser()) + { + int16_t varOrderPivots = -1; + if (logic.isPure(THEORY_ARITH) && !logic.isQuantified()) + { + varOrderPivots = 200; + } + Trace("smt") << "setting arithStandardCheckVarOrderPivots " + << varOrderPivots << std::endl; + options::arithStandardCheckVarOrderPivots.set(varOrderPivots); + } + if (logic.isPure(THEORY_ARITH) && !logic.areRealsUsed()) + { + if (!options::nlExtTangentPlanesInterleave.wasSetByUser()) + { + Trace("smt") << "setting nlExtTangentPlanesInterleave to true" + << std::endl; + options::nlExtTangentPlanesInterleave.set(true); + } + } + + // Set decision mode based on logic (if not set by user) + if (!options::decisionMode.wasSetByUser()) + { + options::DecisionMode decMode = + // sygus uses internal + is_sygus ? options::DecisionMode::INTERNAL : + // ALL + logic.hasEverything() + ? options::DecisionMode::JUSTIFICATION + : ( // QF_BV + (not logic.isQuantified() && logic.isPure(THEORY_BV)) || + // QF_AUFBV or QF_ABV or QF_UFBV + (not logic.isQuantified() + && (logic.isTheoryEnabled(THEORY_ARRAYS) + || logic.isTheoryEnabled(THEORY_UF)) + && logic.isTheoryEnabled(THEORY_BV)) + || + // QF_AUFLIA (and may be ends up enabling + // QF_AUFLRA?) + (not logic.isQuantified() + && logic.isTheoryEnabled(THEORY_ARRAYS) + && logic.isTheoryEnabled(THEORY_UF) + && logic.isTheoryEnabled(THEORY_ARITH)) + || + // QF_LRA + (not logic.isQuantified() + && logic.isPure(THEORY_ARITH) && logic.isLinear() + && !logic.isDifferenceLogic() + && !logic.areIntegersUsed()) + || + // Quantifiers + logic.isQuantified() || + // Strings + logic.isTheoryEnabled(THEORY_STRINGS) + ? options::DecisionMode::JUSTIFICATION + : options::DecisionMode::INTERNAL); + + bool stoponly = + // ALL + logic.hasEverything() || logic.isTheoryEnabled(THEORY_STRINGS) + ? false + : ( // QF_AUFLIA + (not logic.isQuantified() + && logic.isTheoryEnabled(THEORY_ARRAYS) + && logic.isTheoryEnabled(THEORY_UF) + && logic.isTheoryEnabled(THEORY_ARITH)) + || + // QF_LRA + (not logic.isQuantified() + && logic.isPure(THEORY_ARITH) && logic.isLinear() + && !logic.isDifferenceLogic() + && !logic.areIntegersUsed()) + ? true + : false); + + Trace("smt") << "setting decision mode to " << decMode << std::endl; + options::decisionMode.set(decMode); + options::decisionStopOnly.set(stoponly); + } + if (options::incrementalSolving()) + { + // disable modes not supported by incremental + options::sortInference.set(false); + options::ufssFairnessMonotone.set(false); + options::quantEpr.set(false); + options::globalNegate.set(false); + } + if (logic.hasCardinalityConstraints()) + { + // must have finite model finding on + options::finiteModelFind.set(true); + } + + // if it contains a theory with non-termination, do not strictly enforce that + // quantifiers and theory combination must be interleaved + if (logic.isTheoryEnabled(THEORY_STRINGS) + || (logic.isTheoryEnabled(THEORY_ARITH) && !logic.isLinear())) + { + if (!options::instWhenStrictInterleave.wasSetByUser()) + { + options::instWhenStrictInterleave.set(false); + } + } + + if (options::instMaxLevel() != -1) + { + Notice() << "SmtEngine: turning off cbqi to support instMaxLevel" + << std::endl; + options::cbqi.set(false); + } + // Do we need to track instantiations? + // Needed for sygus due to single invocation techniques. + if (options::cbqiNestedQE() + || (options::proof() && !options::trackInstLemmas.wasSetByUser()) + || is_sygus) + { + options::trackInstLemmas.set(true); + } + + if ((options::fmfBoundLazy.wasSetByUser() && options::fmfBoundLazy()) + || (options::fmfBoundInt.wasSetByUser() && options::fmfBoundInt())) + { + options::fmfBound.set(true); + } + // now have determined whether fmfBoundInt is on/off + // apply fmfBoundInt options + if (options::fmfBound()) + { + if (!options::mbqiMode.wasSetByUser() + || (options::mbqiMode() != options::MbqiMode::NONE + && options::mbqiMode() != options::MbqiMode::FMC)) + { + // if bounded integers are set, use no MBQI by default + options::mbqiMode.set(options::MbqiMode::NONE); + } + if (!options::prenexQuant.wasSetByUser()) + { + options::prenexQuant.set(options::PrenexQuantMode::NONE); + } + } + if (options::ufHo()) + { + // if higher-order, then current variants of model-based instantiation + // cannot be used + if (options::mbqiMode() != options::MbqiMode::NONE) + { + options::mbqiMode.set(options::MbqiMode::NONE); + } + if (!options::hoElimStoreAx.wasSetByUser()) + { + // by default, use store axioms only if --ho-elim is set + options::hoElimStoreAx.set(options::hoElim()); + } + } + if (options::fmfFunWellDefinedRelevant()) + { + if (!options::fmfFunWellDefined.wasSetByUser()) + { + options::fmfFunWellDefined.set(true); + } + } + if (options::fmfFunWellDefined()) + { + if (!options::finiteModelFind.wasSetByUser()) + { + options::finiteModelFind.set(true); + } + } + // EPR + if (options::quantEpr()) + { + if (!options::preSkolemQuant.wasSetByUser()) + { + options::preSkolemQuant.set(true); + } + } + + // now, have determined whether finite model find is on/off + // apply finite model finding options + if (options::finiteModelFind()) + { + // apply conservative quantifiers splitting + if (!options::quantDynamicSplit.wasSetByUser()) + { + options::quantDynamicSplit.set(options::QuantDSplitMode::DEFAULT); + } + // do not eliminate extended arithmetic symbols from quantified formulas + if (!options::elimExtArithQuant.wasSetByUser()) + { + options::elimExtArithQuant.set(false); + } + if (!options::eMatching.wasSetByUser()) + { + options::eMatching.set(options::fmfInstEngine()); + } + if (!options::instWhenMode.wasSetByUser()) + { + // instantiate only on last call + if (options::eMatching()) + { + options::instWhenMode.set(options::InstWhenMode::LAST_CALL); + } + } + } + + // apply sygus options + // if we are attempting to rewrite everything to SyGuS, use sygus() + if (is_sygus) + { + if (!options::sygus()) + { + Trace("smt") << "turning on sygus" << std::endl; + } + options::sygus.set(true); + // must use Ferrante/Rackoff for real arithmetic + if (!options::cbqiMidpoint.wasSetByUser()) + { + options::cbqiMidpoint.set(true); + } + if (options::sygusRepairConst()) + { + if (!options::cbqi.wasSetByUser()) + { + options::cbqi.set(true); + } + } + if (options::sygusInference()) + { + // optimization: apply preskolemization, makes it succeed more often + if (!options::preSkolemQuant.wasSetByUser()) + { + options::preSkolemQuant.set(true); + } + if (!options::preSkolemQuantNested.wasSetByUser()) + { + options::preSkolemQuantNested.set(true); + } + } + // counterexample-guided instantiation for sygus + if (!options::cegqiSingleInvMode.wasSetByUser()) + { + options::cegqiSingleInvMode.set(options::CegqiSingleInvMode::USE); + } + if (!options::quantConflictFind.wasSetByUser()) + { + options::quantConflictFind.set(false); + } + if (!options::instNoEntail.wasSetByUser()) + { + options::instNoEntail.set(false); + } + if (!options::cbqiFullEffort.wasSetByUser()) + { + // should use full effort cbqi for single invocation and repair const + options::cbqiFullEffort.set(true); + } + if (options::sygusRew()) + { + options::sygusRewSynth.set(true); + options::sygusRewVerify.set(true); + } + if (options::sygusRewSynthInput()) + { + // If we are using synthesis rewrite rules from input, we use + // sygusRewSynth after preprocessing. See passes/synth_rew_rules.h for + // details on this technique. + options::sygusRewSynth.set(true); + // we should not use the extended rewriter, since we are interested + // in rewrites that are not in the main rewriter + if (!options::sygusExtRew.wasSetByUser()) + { + options::sygusExtRew.set(false); + } + } + // Whether we must use "basic" sygus algorithms. A non-basic sygus algorithm + // is one that is specialized for returning a single solution. Non-basic + // sygus algorithms currently include the PBE solver, UNIF+PI, static + // template inference for invariant synthesis, and single invocation + // techniques. + bool reqBasicSygus = false; + if (options::produceAbducts()) + { + // if doing abduction, we should filter strong solutions + if (!options::sygusFilterSolMode.wasSetByUser()) + { + options::sygusFilterSolMode.set(options::SygusFilterSolMode::STRONG); + } + // we must use basic sygus algorithms, since e.g. we require checking + // a sygus side condition for consistency with axioms. + reqBasicSygus = true; + } + if (options::sygusRewSynth() || options::sygusRewVerify() + || options::sygusQueryGen()) + { + // rewrite rule synthesis implies that sygus stream must be true + options::sygusStream.set(true); + } + if (options::sygusStream() || options::incrementalSolving()) + { + // Streaming and incremental mode are incompatible with techniques that + // focus the search towards finding a single solution. + reqBasicSygus = true; + } + // Now, disable options for non-basic sygus algorithms, if necessary. + if (reqBasicSygus) + { + if (!options::sygusUnifPbe.wasSetByUser()) + { + options::sygusUnifPbe.set(false); + } + if (options::sygusUnifPi.wasSetByUser()) + { + options::sygusUnifPi.set(options::SygusUnifPiMode::NONE); + } + if (!options::sygusInvTemplMode.wasSetByUser()) + { + options::sygusInvTemplMode.set(options::SygusInvTemplMode::NONE); + } + if (!options::cegqiSingleInvMode.wasSetByUser()) + { + options::cegqiSingleInvMode.set(options::CegqiSingleInvMode::NONE); + } + } + // do not allow partial functions + if (!options::bitvectorDivByZeroConst()) + { + if (options::bitvectorDivByZeroConst.wasSetByUser()) + { + throw OptionException( + "--no-bv-div-zero-const is not supported with SyGuS"); + } + Notice() + << "SmtEngine: setting bv-div-zero-const to true to support SyGuS" + << std::endl; + options::bitvectorDivByZeroConst.set(true); + } + if (!options::dtRewriteErrorSel.wasSetByUser()) + { + options::dtRewriteErrorSel.set(true); + } + // do not miniscope + if (!options::miniscopeQuant.wasSetByUser()) + { + options::miniscopeQuant.set(false); + } + if (!options::miniscopeQuantFreeVar.wasSetByUser()) + { + options::miniscopeQuantFreeVar.set(false); + } + if (!options::quantSplit.wasSetByUser()) + { + options::quantSplit.set(false); + } + // rewrite divk + if (!options::rewriteDivk.wasSetByUser()) + { + options::rewriteDivk.set(true); + } + // do not do macros + if (!options::macrosQuant.wasSetByUser()) + { + options::macrosQuant.set(false); + } + if (!options::cbqiPreRegInst.wasSetByUser()) + { + options::cbqiPreRegInst.set(true); + } + } + // counterexample-guided instantiation for non-sygus + // enable if any possible quantifiers with arithmetic, datatypes or bitvectors + if ((logic.isQuantified() + && (logic.isTheoryEnabled(THEORY_ARITH) + || logic.isTheoryEnabled(THEORY_DATATYPES) + || logic.isTheoryEnabled(THEORY_BV) + || logic.isTheoryEnabled(THEORY_FP))) + || options::cbqiAll()) + { + if (!options::cbqi.wasSetByUser()) + { + options::cbqi.set(true); + } + // check whether we should apply full cbqi + if (logic.isPure(THEORY_BV)) + { + if (!options::cbqiFullEffort.wasSetByUser()) + { + options::cbqiFullEffort.set(true); + } + } + } + if (options::cbqi()) + { + // must rewrite divk + if (!options::rewriteDivk.wasSetByUser()) + { + options::rewriteDivk.set(true); + } + if (options::incrementalSolving()) + { + // cannot do nested quantifier elimination in incremental mode + options::cbqiNestedQE.set(false); + } + if (logic.isPure(THEORY_ARITH) || logic.isPure(THEORY_BV)) + { + if (!options::quantConflictFind.wasSetByUser()) + { + options::quantConflictFind.set(false); + } + if (!options::instNoEntail.wasSetByUser()) + { + options::instNoEntail.set(false); + } + if (!options::instWhenMode.wasSetByUser() && options::cbqiModel()) + { + // only instantiation should happen at last call when model is avaiable + options::instWhenMode.set(options::InstWhenMode::LAST_CALL); + } + } + else + { + // only supported in pure arithmetic or pure BV + options::cbqiNestedQE.set(false); + } + // prenexing + if (options::cbqiNestedQE()) + { + // only complete with prenex = disj_normal or normal + if (options::prenexQuant() <= options::PrenexQuantMode::DISJ_NORMAL) + { + options::prenexQuant.set(options::PrenexQuantMode::DISJ_NORMAL); + } + } + else if (options::globalNegate()) + { + if (!options::prenexQuant.wasSetByUser()) + { + options::prenexQuant.set(options::PrenexQuantMode::NONE); + } + } + } + // implied options... + if (options::strictTriggers()) + { + if (!options::userPatternsQuant.wasSetByUser()) + { + options::userPatternsQuant.set(options::UserPatMode::TRUST); + } + } + if (options::qcfMode.wasSetByUser() || options::qcfTConstraint()) + { + options::quantConflictFind.set(true); + } + if (options::cbqiNestedQE()) + { + options::prenexQuantUser.set(true); + if (!options::preSkolemQuant.wasSetByUser()) + { + options::preSkolemQuant.set(true); + } + } + // for induction techniques + if (options::quantInduction()) + { + if (!options::dtStcInduction.wasSetByUser()) + { + options::dtStcInduction.set(true); + } + if (!options::intWfInduction.wasSetByUser()) + { + options::intWfInduction.set(true); + } + } + if (options::dtStcInduction()) + { + // try to remove ITEs from quantified formulas + if (!options::iteDtTesterSplitQuant.wasSetByUser()) + { + options::iteDtTesterSplitQuant.set(true); + } + if (!options::iteLiftQuant.wasSetByUser()) + { + options::iteLiftQuant.set(options::IteLiftQuantMode::ALL); + } + } + if (options::intWfInduction()) + { + if (!options::purifyTriggers.wasSetByUser()) + { + options::purifyTriggers.set(true); + } + } + if (options::conjectureNoFilter()) + { + if (!options::conjectureFilterActiveTerms.wasSetByUser()) + { + options::conjectureFilterActiveTerms.set(false); + } + if (!options::conjectureFilterCanonical.wasSetByUser()) + { + options::conjectureFilterCanonical.set(false); + } + if (!options::conjectureFilterModel.wasSetByUser()) + { + options::conjectureFilterModel.set(false); + } + } + if (options::conjectureGenPerRound.wasSetByUser()) + { + if (options::conjectureGenPerRound() > 0) + { + options::conjectureGen.set(true); + } + else + { + options::conjectureGen.set(false); + } + } + // can't pre-skolemize nested quantifiers without UF theory + if (!logic.isTheoryEnabled(THEORY_UF) && options::preSkolemQuant()) + { + if (!options::preSkolemQuantNested.wasSetByUser()) + { + options::preSkolemQuantNested.set(false); + } + } + if (!logic.isTheoryEnabled(THEORY_DATATYPES)) + { + options::quantDynamicSplit.set(options::QuantDSplitMode::NONE); + } + + // until bugs 371,431 are fixed + if (!options::minisatUseElim.wasSetByUser()) + { + // cannot use minisat elimination for logics where a theory solver + // introduces new literals into the search. This includes quantifiers + // (quantifier instantiation), and the lemma schemas used in non-linear + // and sets. We also can't use it if models are enabled. + if (logic.isTheoryEnabled(THEORY_SETS) || logic.isQuantified() + || options::produceModels() || options::produceAssignments() + || options::checkModels() + || (logic.isTheoryEnabled(THEORY_ARITH) && !logic.isLinear())) + { + options::minisatUseElim.set(false); + } + } + + // For now, these array theory optimizations do not support model-building + if (options::produceModels() || options::produceAssignments() + || options::checkModels()) + { + options::arraysOptimizeLinear.set(false); + options::arraysLazyRIntro1.set(false); + } + + if (options::proof()) + { + if (options::incrementalSolving()) + { + if (options::incrementalSolving.wasSetByUser()) + { + throw OptionException("--incremental is not supported with proofs"); + } + Warning() + << "SmtEngine: turning off incremental solving mode (not yet " + "supported with --proof, try --tear-down-incremental instead)" + << std::endl; + smte.setOption("incremental", SExpr("false")); + } + if (logic > LogicInfo("QF_AUFBVLRA")) + { + throw OptionException( + "Proofs are only supported for sub-logics of QF_AUFBVLIA."); + } + if (options::bitvectorAlgebraicSolver()) + { + if (options::bitvectorAlgebraicSolver.wasSetByUser()) + { + throw OptionException( + "--bv-algebraic-solver is not supported with proofs"); + } + Notice() << "SmtEngine: turning off bv algebraic solver to support proofs" + << std::endl; + options::bitvectorAlgebraicSolver.set(false); + } + if (options::bitvectorEqualitySolver()) + { + if (options::bitvectorEqualitySolver.wasSetByUser()) + { + throw OptionException("--bv-eq-solver is not supported with proofs"); + } + Notice() << "SmtEngine: turning off bv eq solver to support proofs" + << std::endl; + options::bitvectorEqualitySolver.set(false); + } + if (options::bitvectorInequalitySolver()) + { + if (options::bitvectorInequalitySolver.wasSetByUser()) + { + throw OptionException( + "--bv-inequality-solver is not supported with proofs"); + } + Notice() << "SmtEngine: turning off bv ineq solver to support proofs" + << std::endl; + options::bitvectorInequalitySolver.set(false); + } + } + + if (!options::bitvectorEqualitySolver()) + { + if (options::bvLazyRewriteExtf()) + { + if (options::bvLazyRewriteExtf.wasSetByUser()) + { + throw OptionException( + "--bv-lazy-rewrite-extf requires --bv-eq-solver to be set"); + } + } + Trace("smt") + << "disabling bvLazyRewriteExtf since equality solver is disabled" + << std::endl; + options::bvLazyRewriteExtf.set(false); + } + + if (!options::sygusExprMinerCheckUseExport()) + { + if (options::sygusExprMinerCheckTimeout.wasSetByUser()) + { + throw OptionException( + "--sygus-expr-miner-check-timeout=N requires " + "--sygus-expr-miner-check-use-export"); + } + if (options::sygusRewSynthInput() || options::produceAbducts()) + { + std::stringstream ss; + ss << (options::sygusRewSynthInput() ? "--sygus-rr-synth-input" + : "--produce-abducts"); + ss << "requires --sygus-expr-miner-check-use-export"; + throw OptionException(ss.str()); + } + } + + if (options::stringFMF() && !options::stringProcessLoopMode.wasSetByUser()) + { + Trace("smt") << "settting stringProcessLoopMode to 'simple' since " + "--strings-fmf enabled" + << std::endl; + options::stringProcessLoopMode.set(options::ProcessLoopMode::SIMPLE); + } + + // !!! All options that require disabling models go here + bool disableModels = false; + std::string sOptNoModel; + if (options::unconstrainedSimp.wasSetByUser() && options::unconstrainedSimp()) + { + disableModels = true; + sOptNoModel = "unconstrained-simp"; + } + else if (options::sortInference()) + { + disableModels = true; + sOptNoModel = "sort-inference"; + } + else if (options::minisatUseElim()) + { + disableModels = true; + sOptNoModel = "minisat-elimination"; + } + else if (logic.isTheoryEnabled(THEORY_ARITH) && !logic.isLinear() + && !options::nlExt()) + { + disableModels = true; + sOptNoModel = "nonlinear arithmetic without nl-ext"; + } + else if (options::globalNegate()) + { + disableModels = true; + sOptNoModel = "global-negate"; + } + if (disableModels) + { + if (options::produceModels()) + { + if (options::produceModels.wasSetByUser()) + { + std::stringstream ss; + ss << "Cannot use " << sOptNoModel << " with model generation."; + throw OptionException(ss.str()); + } + Notice() << "SmtEngine: turning off produce-models to support " + << sOptNoModel << std::endl; + smte.setOption("produce-models", SExpr("false")); + } + if (options::produceAssignments()) + { + if (options::produceAssignments.wasSetByUser()) + { + std::stringstream ss; + ss << "Cannot use " << sOptNoModel + << " with model generation (produce-assignments)."; + throw OptionException(ss.str()); + } + Notice() << "SmtEngine: turning off produce-assignments to support " + << sOptNoModel << std::endl; + smte.setOption("produce-assignments", SExpr("false")); + } + if (options::checkModels()) + { + if (options::checkModels.wasSetByUser()) + { + std::stringstream ss; + ss << "Cannot use " << sOptNoModel + << " with model generation (check-models)."; + throw OptionException(ss.str()); + } + Notice() << "SmtEngine: turning off check-models to support " + << sOptNoModel << std::endl; + smte.setOption("check-models", SExpr("false")); + } + } +} + +} // namespace smt +} // namespace CVC4 diff --git a/src/smt/set_defaults.h b/src/smt/set_defaults.h new file mode 100644 index 000000000..8871b0b38 --- /dev/null +++ b/src/smt/set_defaults.h @@ -0,0 +1,42 @@ +/********************* */ +/*! \file set_defaults.h + ** \verbatim + ** Top contributors (to current version): + ** Andrew Reynolds + ** 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 Method for setting the default options of an SMT engine. + **/ + +#ifndef CVC4__SMT__SET_DEFAULTS_H +#define CVC4__SMT__SET_DEFAULTS_H + +#include "smt/smt_engine.h" +#include "theory/logic_info.h" + +namespace CVC4 { +namespace smt { + +/** + * The purpose of this method is to set the default options and update the logic + * info for SMT engine smte. + * + * The argument logic is a reference to the logic of SmtEngine, which can be + * updated by this method based on the current options and the logic itself. + * + * Note that currently, options are associated with the ExprManager. Thus, this + * call updates the options associated with the current ExprManager. + * If this designed is updated in the future so that SmtEngine has its own + * copy of options, this method should be updated accordingly so that it + * is responsible for updating this copy. + */ +void setDefaults(SmtEngine& smte, LogicInfo& logic); + +} // namespace smt +} // namespace CVC4 + +#endif /* CVC4__SMT__SET_DEFAULTS_H */ diff --git a/src/smt/smt_engine.cpp b/src/smt/smt_engine.cpp index 7d78e93f9..299cc357b 100644 --- a/src/smt/smt_engine.cpp +++ b/src/smt/smt_engine.cpp @@ -86,6 +86,7 @@ #include "smt/managed_ostreams.h" #include "smt/model_blocker.h" #include "smt/model_core_builder.h" +#include "smt/set_defaults.h" #include "smt/smt_engine_scope.h" #include "smt/term_formula_removal.h" #include "smt/update_ostream.h" @@ -869,7 +870,6 @@ SmtEngine::SmtEngine(ExprManager* em) d_fullyInited(false), d_queryMade(false), d_needPostsolve(false), - d_earlyTheoryPP(true), d_globalNegation(false), d_status(), d_expectedStatus(), @@ -925,8 +925,11 @@ void SmtEngine::finishInit() d_private->addUseTheoryListListener(getTheoryEngine()); + // set the random seed + Random::getRandom().setSeed(options::seed()); + // ensure that our heuristics are properly set up - setDefaults(); + setDefaults(*this, d_logic); Trace("smt-debug") << "Making decision engine..." << std::endl; @@ -1145,1216 +1148,6 @@ void SmtEngine::setLogicInternal() d_logic.lock(); } -void SmtEngine::setDefaults() { - Random::getRandom().setSeed(options::seed()); - // Language-based defaults - if (!options::bitvectorDivByZeroConst.wasSetByUser()) - { - // Bitvector-divide-by-zero changed semantics in SMT LIB 2.6, thus we - // set this option if the input format is SMT LIB 2.6. We also set this - // option if we are sygus, since we assume SMT LIB 2.6 semantics for sygus. - options::bitvectorDivByZeroConst.set( - language::isInputLang_smt2_6(options::inputLanguage()) - || language::isInputLangSygus(options::inputLanguage())); - } - bool is_sygus = language::isInputLangSygus(options::inputLanguage()); - - if (options::bitblastMode() == options::BitblastMode::EAGER) - { - if (options::produceModels() - && (d_logic.isTheoryEnabled(THEORY_ARRAYS) - || d_logic.isTheoryEnabled(THEORY_UF))) - { - if (options::bitblastMode.wasSetByUser() - || options::produceModels.wasSetByUser()) - { - throw OptionException(std::string( - "Eager bit-blasting currently does not support model generation " - "for the combination of bit-vectors with arrays or uinterpreted " - "functions. Try --bitblast=lazy")); - } - Notice() << "SmtEngine: setting bit-blast mode to lazy to support model" - << "generation" << endl; - setOption("bitblastMode", SExpr("lazy")); - } - else if (!options::incrementalSolving()) - { - options::ackermann.set(true); - } - - if (options::incrementalSolving() && !d_logic.isPure(THEORY_BV)) - { - throw OptionException( - "Incremental eager bit-blasting is currently " - "only supported for QF_BV. Try --bitblast=lazy."); - } - } - - if (options::solveIntAsBV() > 0) - { - d_logic = d_logic.getUnlockedCopy(); - d_logic.enableTheory(THEORY_BV); - d_logic.lock(); - } - - if (options::solveBVAsInt() > 0) - { - if (d_logic.isTheoryEnabled(THEORY_BV)) - { - d_logic = d_logic.getUnlockedCopy(); - d_logic.enableTheory(THEORY_ARITH); - d_logic.arithNonLinear(); - d_logic.lock(); - } - } - - // set options about ackermannization - if (options::ackermann() && options::produceModels() - && (d_logic.isTheoryEnabled(THEORY_ARRAYS) - || d_logic.isTheoryEnabled(THEORY_UF))) - { - if (options::produceModels.wasSetByUser()) - { - throw OptionException(std::string( - "Ackermannization currently does not support model generation.")); - } - Notice() << "SmtEngine: turn off ackermannization to support model" - << "generation" << endl; - options::ackermann.set(false); - } - - if (options::ackermann()) - { - if (options::incrementalSolving()) - { - throw OptionException( - "Incremental Ackermannization is currently not supported."); - } - - if (d_logic.isQuantified()) - { - throw LogicException("Cannot use Ackermannization on quantified formula"); - } - - if (d_logic.isTheoryEnabled(THEORY_UF)) - { - d_logic = d_logic.getUnlockedCopy(); - d_logic.disableTheory(THEORY_UF); - d_logic.lock(); - } - if (d_logic.isTheoryEnabled(THEORY_ARRAYS)) - { - d_logic = d_logic.getUnlockedCopy(); - d_logic.disableTheory(THEORY_ARRAYS); - d_logic.lock(); - } - } - - // Set default options associated with strings-exp. We also set these options - // if we are using eager string preprocessing, which may introduce quantified - // formulas at preprocess time. - if (options::stringExp() || !options::stringLazyPreproc()) - { - // We require quantifiers since extended functions reduce using them. - if (!d_logic.isQuantified()) - { - d_logic = d_logic.getUnlockedCopy(); - d_logic.enableQuantifiers(); - d_logic.lock(); - Trace("smt") << "turning on quantifier logic, for strings-exp" - << std::endl; - } - // We require bounded quantifier handling. - if (!options::fmfBound.wasSetByUser()) - { - options::fmfBound.set( true ); - Trace("smt") << "turning on fmf-bound-int, for strings-exp" << std::endl; - } - // Turn off E-matching, since some bounded quantifiers introduced by strings - // (e.g. for replaceall) admit matching loops. - if (!options::eMatching.wasSetByUser()) - { - options::eMatching.set(false); - Trace("smt") << "turning off E-matching, for strings-exp" << std::endl; - } - // Do not eliminate extended arithmetic symbols from quantified formulas, - // since some strategies, e.g. --re-elim-agg, introduce them. - if (!options::elimExtArithQuant.wasSetByUser()) - { - options::elimExtArithQuant.set(false); - Trace("smt") << "turning off elim-ext-arith-quant, for strings-exp" - << std::endl; - } - } - - // sygus inference may require datatypes - if (!d_isInternalSubsolver) - { - if (options::produceAbducts() || options::sygusInference() - || options::sygusRewSynthInput()) - { - // since we are trying to recast as sygus, we assume the input is sygus - is_sygus = true; - } - } - - // We now know whether the input is sygus. Update the logic to incorporate - // the theories we need internally for handling sygus problems. - if (is_sygus) - { - d_logic = d_logic.getUnlockedCopy(); - d_logic.enableSygus(); - d_logic.lock(); - } - - // sygus core connective requires unsat cores - if (options::sygusCoreConnective()) - { - options::unsatCores.set(true); - } - - if ((options::checkModels() || options::checkSynthSol() - || options::produceAbducts() - || options::modelCoresMode() != options::ModelCoresMode::NONE - || options::blockModelsMode() != options::BlockModelsMode::NONE) - && !options::produceAssertions()) - { - Notice() << "SmtEngine: turning on produce-assertions to support " - << "option requiring assertions." << endl; - setOption("produce-assertions", SExpr("true")); - } - - // Disable options incompatible with incremental solving, unsat cores, and - // proofs or output an error if enabled explicitly - if (options::incrementalSolving() || options::unsatCores() - || options::proof()) - { - if (options::unconstrainedSimp()) - { - if (options::unconstrainedSimp.wasSetByUser()) - { - throw OptionException( - "unconstrained simplification not supported with unsat " - "cores/proofs/incremental solving"); - } - Notice() << "SmtEngine: turning off unconstrained simplification to " - "support unsat cores/proofs/incremental solving" - << endl; - options::unconstrainedSimp.set(false); - } - } - else - { - // Turn on unconstrained simplification for QF_AUFBV - if (!options::unconstrainedSimp.wasSetByUser()) - { - // bool qf_sat = d_logic.isPure(THEORY_BOOL) && - // !d_logic.isQuantified(); bool uncSimp = false && !qf_sat && - // !options::incrementalSolving(); - bool uncSimp = !d_logic.isQuantified() && !options::produceModels() - && !options::produceAssignments() - && !options::checkModels() - && (d_logic.isTheoryEnabled(THEORY_ARRAYS) - && d_logic.isTheoryEnabled(THEORY_BV)); - Trace("smt") << "setting unconstrained simplification to " << uncSimp - << endl; - options::unconstrainedSimp.set(uncSimp); - } - } - - if (options::incrementalSolving() || options::proof()) - { - if (options::sygusInference()) - { - if (options::sygusInference.wasSetByUser()) - { - throw OptionException( - "sygus inference not supported with proofs/incremental solving"); - } - Notice() << "SmtEngine: turning off sygus inference to support " - "proofs/incremental solving" - << std::endl; - options::sygusInference.set(false); - } - } - - // Disable options incompatible with unsat cores and proofs or output an - // error if enabled explicitly - if (options::unsatCores() || options::proof()) - { - if (options::simplificationMode() != options::SimplificationMode::NONE) - { - if (options::simplificationMode.wasSetByUser()) - { - throw OptionException( - "simplification not supported with unsat cores/proofs"); - } - Notice() << "SmtEngine: turning off simplification to support unsat " - "cores/proofs" - << endl; - options::simplificationMode.set(options::SimplificationMode::NONE); - } - - if (options::pbRewrites()) - { - if (options::pbRewrites.wasSetByUser()) - { - throw OptionException( - "pseudoboolean rewrites not supported with unsat cores/proofs"); - } - Notice() << "SmtEngine: turning off pseudoboolean rewrites to support " - "unsat cores/proofs" - << endl; - setOption("pb-rewrites", false); - } - - if (options::sortInference()) - { - if (options::sortInference.wasSetByUser()) - { - throw OptionException( - "sort inference not supported with unsat cores/proofs"); - } - Notice() << "SmtEngine: turning off sort inference to support unsat " - "cores/proofs" - << endl; - options::sortInference.set(false); - } - - if (options::preSkolemQuant()) - { - if (options::preSkolemQuant.wasSetByUser()) - { - throw OptionException( - "pre-skolemization not supported with unsat cores/proofs"); - } - Notice() << "SmtEngine: turning off pre-skolemization to support unsat " - "cores/proofs" - << endl; - options::preSkolemQuant.set(false); - } - - if (options::solveBVAsInt() > 0) - { - /** - * Operations on 1 bits are better handled as Boolean operations - * than as integer operations. - * Therefore, we enable bv-to-bool, which runs before - * the translation to integers. - */ - options::bitvectorToBool.set(true); - } - - if (options::bitvectorToBool()) - { - if (options::bitvectorToBool.wasSetByUser()) - { - throw OptionException( - "bv-to-bool not supported with unsat cores/proofs"); - } - Notice() << "SmtEngine: turning off bitvector-to-bool to support unsat " - "cores/proofs" - << endl; - options::bitvectorToBool.set(false); - } - - if (options::boolToBitvector() != options::BoolToBVMode::OFF) - { - if (options::boolToBitvector.wasSetByUser()) - { - throw OptionException( - "bool-to-bv != off not supported with unsat cores/proofs"); - } - Notice() << "SmtEngine: turning off bool-to-bv to support unsat " - "cores/proofs" - << endl; - setOption("boolToBitvector", SExpr("off")); - } - - if (options::bvIntroducePow2()) - { - if (options::bvIntroducePow2.wasSetByUser()) - { - throw OptionException( - "bv-intro-pow2 not supported with unsat cores/proofs"); - } - Notice() << "SmtEngine: turning off bv-intro-pow2 to support " - "unsat-cores/proofs" - << endl; - setOption("bv-intro-pow2", false); - } - - if (options::repeatSimp()) - { - if (options::repeatSimp.wasSetByUser()) - { - throw OptionException( - "repeat-simp not supported with unsat cores/proofs"); - } - Notice() << "SmtEngine: turning off repeat-simp to support unsat " - "cores/proofs" - << endl; - setOption("repeat-simp", false); - } - - if (options::globalNegate()) - { - if (options::globalNegate.wasSetByUser()) - { - throw OptionException( - "global-negate not supported with unsat cores/proofs"); - } - Notice() << "SmtEngine: turning off global-negate to support unsat " - "cores/proofs" - << endl; - setOption("global-negate", false); - } - - if (options::bitvectorAig()) - { - throw OptionException( - "bitblast-aig not supported with unsat cores/proofs"); - } - } - else - { - // by default, nonclausal simplification is off for QF_SAT - if (!options::simplificationMode.wasSetByUser()) - { - bool qf_sat = d_logic.isPure(THEORY_BOOL) && !d_logic.isQuantified(); - Trace("smt") << "setting simplification mode to <" - << d_logic.getLogicString() << "> " << (!qf_sat) << endl; - // simplification=none works better for SMT LIB benchmarks with - // quantifiers, not others options::simplificationMode.set(qf_sat || - // quantifiers ? options::SimplificationMode::NONE : - // options::SimplificationMode::BATCH); - options::simplificationMode.set(qf_sat - ? options::SimplificationMode::NONE - : options::SimplificationMode::BATCH); - } - } - - if (options::cbqiBv() && d_logic.isQuantified()) - { - if (options::boolToBitvector() != options::BoolToBVMode::OFF) - { - if (options::boolToBitvector.wasSetByUser()) - { - throw OptionException( - "bool-to-bv != off not supported with CBQI BV for quantified " - "logics"); - } - Notice() << "SmtEngine: turning off bool-to-bitvector to support CBQI BV" - << endl; - setOption("boolToBitvector", SExpr("off")); - } - } - - // cases where we need produce models - if (!options::produceModels() - && (options::produceAssignments() || options::sygusRewSynthCheck() - || is_sygus)) - { - Notice() << "SmtEngine: turning on produce-models" << endl; - setOption("produce-models", SExpr("true")); - } - - // Set the options for the theoryOf - if(!options::theoryOfMode.wasSetByUser()) { - if(d_logic.isSharingEnabled() && - !d_logic.isTheoryEnabled(THEORY_BV) && - !d_logic.isTheoryEnabled(THEORY_STRINGS) && - !d_logic.isTheoryEnabled(THEORY_SETS) ) { - Trace("smt") << "setting theoryof-mode to term-based" << endl; - options::theoryOfMode.set(options::TheoryOfMode::THEORY_OF_TERM_BASED); - } - } - - // strings require LIA, UF; widen the logic - if(d_logic.isTheoryEnabled(THEORY_STRINGS)) { - LogicInfo log(d_logic.getUnlockedCopy()); - // Strings requires arith for length constraints, and also UF - if(!d_logic.isTheoryEnabled(THEORY_UF)) { - Trace("smt") << "because strings are enabled, also enabling UF" << endl; - log.enableTheory(THEORY_UF); - } - if(!d_logic.isTheoryEnabled(THEORY_ARITH) || d_logic.isDifferenceLogic() || !d_logic.areIntegersUsed()) { - Trace("smt") << "because strings are enabled, also enabling linear integer arithmetic" << endl; - log.enableTheory(THEORY_ARITH); - log.enableIntegers(); - log.arithOnlyLinear(); - } - d_logic = log; - d_logic.lock(); - } - if(d_logic.isTheoryEnabled(THEORY_ARRAYS) || d_logic.isTheoryEnabled(THEORY_DATATYPES) || d_logic.isTheoryEnabled(THEORY_SETS)) { - if(!d_logic.isTheoryEnabled(THEORY_UF)) { - LogicInfo log(d_logic.getUnlockedCopy()); - Trace("smt") << "because a theory that permits Boolean terms is enabled, also enabling UF" << endl; - log.enableTheory(THEORY_UF); - d_logic = log; - d_logic.lock(); - } - } - - // by default, symmetry breaker is on only for non-incremental QF_UF - if(! options::ufSymmetryBreaker.wasSetByUser()) { - bool qf_uf_noinc = d_logic.isPure(THEORY_UF) && !d_logic.isQuantified() - && !options::incrementalSolving() && !options::proof() - && !options::unsatCores(); - Trace("smt") << "setting uf symmetry breaker to " << qf_uf_noinc << endl; - options::ufSymmetryBreaker.set(qf_uf_noinc); - } - - // If in arrays, set the UF handler to arrays - if(d_logic.isTheoryEnabled(THEORY_ARRAYS) && ( !d_logic.isQuantified() || - (d_logic.isQuantified() && !d_logic.isTheoryEnabled(THEORY_UF)))) { - Theory::setUninterpretedSortOwner(THEORY_ARRAYS); - } else { - Theory::setUninterpretedSortOwner(THEORY_UF); - } - - if(! options::simplifyWithCareEnabled.wasSetByUser() ){ - bool qf_aufbv = !d_logic.isQuantified() && - d_logic.isTheoryEnabled(THEORY_ARRAYS) && - d_logic.isTheoryEnabled(THEORY_UF) && - d_logic.isTheoryEnabled(THEORY_BV); - - bool withCare = qf_aufbv; - Trace("smt") << "setting ite simplify with care to " << withCare << endl; - options::simplifyWithCareEnabled.set(withCare); - } - // Turn off array eager index splitting for QF_AUFLIA - if(! options::arraysEagerIndexSplitting.wasSetByUser()) { - if (not d_logic.isQuantified() && - d_logic.isTheoryEnabled(THEORY_ARRAYS) && - d_logic.isTheoryEnabled(THEORY_UF) && - d_logic.isTheoryEnabled(THEORY_ARITH)) { - Trace("smt") << "setting array eager index splitting to false" << endl; - options::arraysEagerIndexSplitting.set(false); - } - } - // Turn on model-based arrays for QF_AX (unless models are enabled) - // if(! options::arraysModelBased.wasSetByUser()) { - // if (not d_logic.isQuantified() && - // d_logic.isTheoryEnabled(THEORY_ARRAYS) && - // d_logic.isPure(THEORY_ARRAYS) && - // !options::produceModels() && - // !options::checkModels()) { - // Trace("smt") << "turning on model-based array solver" << endl; - // options::arraysModelBased.set(true); - // } - // } - // Turn on multiple-pass non-clausal simplification for QF_AUFBV - if(! options::repeatSimp.wasSetByUser()) { - bool repeatSimp = !d_logic.isQuantified() && - (d_logic.isTheoryEnabled(THEORY_ARRAYS) && - d_logic.isTheoryEnabled(THEORY_UF) && - d_logic.isTheoryEnabled(THEORY_BV)) && - !options::unsatCores(); - Trace("smt") << "setting repeat simplification to " << repeatSimp << endl; - options::repeatSimp.set(repeatSimp); - } - - if (options::boolToBitvector() == options::BoolToBVMode::ALL - && !d_logic.isTheoryEnabled(THEORY_BV)) - { - if (options::boolToBitvector.wasSetByUser()) - { - throw OptionException( - "bool-to-bv=all not supported for non-bitvector logics."); - } - Notice() << "SmtEngine: turning off bool-to-bv for non-bv logic: " - << d_logic.getLogicString() << std::endl; - setOption("boolToBitvector", SExpr("off")); - } - - if (! options::bvEagerExplanations.wasSetByUser() && - d_logic.isTheoryEnabled(THEORY_ARRAYS) && - d_logic.isTheoryEnabled(THEORY_BV)) { - Trace("smt") << "enabling eager bit-vector explanations " << endl; - options::bvEagerExplanations.set(true); - } - - // Turn on arith rewrite equalities only for pure arithmetic - if(! options::arithRewriteEq.wasSetByUser()) { - bool arithRewriteEq = d_logic.isPure(THEORY_ARITH) && d_logic.isLinear() && !d_logic.isQuantified(); - Trace("smt") << "setting arith rewrite equalities " << arithRewriteEq << endl; - options::arithRewriteEq.set(arithRewriteEq); - } - if(! options::arithHeuristicPivots.wasSetByUser()) { - int16_t heuristicPivots = 5; - if(d_logic.isPure(THEORY_ARITH) && !d_logic.isQuantified()) { - if(d_logic.isDifferenceLogic()) { - heuristicPivots = -1; - } else if(!d_logic.areIntegersUsed()) { - heuristicPivots = 0; - } - } - Trace("smt") << "setting arithHeuristicPivots " << heuristicPivots << endl; - options::arithHeuristicPivots.set(heuristicPivots); - } - if(! options::arithPivotThreshold.wasSetByUser()){ - uint16_t pivotThreshold = 2; - if(d_logic.isPure(THEORY_ARITH) && !d_logic.isQuantified()){ - if(d_logic.isDifferenceLogic()){ - pivotThreshold = 16; - } - } - Trace("smt") << "setting arith arithPivotThreshold " << pivotThreshold << endl; - options::arithPivotThreshold.set(pivotThreshold); - } - if(! options::arithStandardCheckVarOrderPivots.wasSetByUser()){ - int16_t varOrderPivots = -1; - if(d_logic.isPure(THEORY_ARITH) && !d_logic.isQuantified()){ - varOrderPivots = 200; - } - Trace("smt") << "setting arithStandardCheckVarOrderPivots " << varOrderPivots << endl; - options::arithStandardCheckVarOrderPivots.set(varOrderPivots); - } - // Turn off early theory preprocessing if arithRewriteEq is on - if (options::arithRewriteEq()) { - d_earlyTheoryPP = false; - } - if (d_logic.isPure(THEORY_ARITH) && !d_logic.areRealsUsed()) - { - if (!options::nlExtTangentPlanesInterleave.wasSetByUser()) - { - Trace("smt") << "setting nlExtTangentPlanesInterleave to true" << endl; - options::nlExtTangentPlanesInterleave.set(true); - } - } - - // Set decision mode based on logic (if not set by user) - if(!options::decisionMode.wasSetByUser()) { - options::DecisionMode decMode = - // sygus uses internal - is_sygus ? options::DecisionMode::INTERNAL : - // ALL - d_logic.hasEverything() - ? options::DecisionMode::JUSTIFICATION - : ( // QF_BV - (not d_logic.isQuantified() && d_logic.isPure(THEORY_BV)) || - // QF_AUFBV or QF_ABV or QF_UFBV - (not d_logic.isQuantified() - && (d_logic.isTheoryEnabled(THEORY_ARRAYS) - || d_logic.isTheoryEnabled(THEORY_UF)) - && d_logic.isTheoryEnabled(THEORY_BV)) - || - // QF_AUFLIA (and may be ends up enabling - // QF_AUFLRA?) - (not d_logic.isQuantified() - && d_logic.isTheoryEnabled(THEORY_ARRAYS) - && d_logic.isTheoryEnabled(THEORY_UF) - && d_logic.isTheoryEnabled(THEORY_ARITH)) - || - // QF_LRA - (not d_logic.isQuantified() - && d_logic.isPure(THEORY_ARITH) - && d_logic.isLinear() - && !d_logic.isDifferenceLogic() - && !d_logic.areIntegersUsed()) - || - // Quantifiers - d_logic.isQuantified() || - // Strings - d_logic.isTheoryEnabled(THEORY_STRINGS) - ? options::DecisionMode::JUSTIFICATION - : options::DecisionMode::INTERNAL); - - bool stoponly = - // ALL - d_logic.hasEverything() || d_logic.isTheoryEnabled(THEORY_STRINGS) ? false : - ( // QF_AUFLIA - (not d_logic.isQuantified() && - d_logic.isTheoryEnabled(THEORY_ARRAYS) && - d_logic.isTheoryEnabled(THEORY_UF) && - d_logic.isTheoryEnabled(THEORY_ARITH) - ) || - // QF_LRA - (not d_logic.isQuantified() && - d_logic.isPure(THEORY_ARITH) && d_logic.isLinear() && !d_logic.isDifferenceLogic() && !d_logic.areIntegersUsed() - ) - ? true : false - ); - - Trace("smt") << "setting decision mode to " << decMode << endl; - options::decisionMode.set(decMode); - options::decisionStopOnly.set(stoponly); - } - if( options::incrementalSolving() ){ - //disable modes not supported by incremental - options::sortInference.set( false ); - options::ufssFairnessMonotone.set( false ); - options::quantEpr.set( false ); - options::globalNegate.set(false); - } - if( d_logic.hasCardinalityConstraints() ){ - //must have finite model finding on - options::finiteModelFind.set( true ); - } - - //if it contains a theory with non-termination, do not strictly enforce that quantifiers and theory combination must be interleaved - if( d_logic.isTheoryEnabled(THEORY_STRINGS) || (d_logic.isTheoryEnabled(THEORY_ARITH) && !d_logic.isLinear()) ){ - if( !options::instWhenStrictInterleave.wasSetByUser() ){ - options::instWhenStrictInterleave.set( false ); - } - } - - if( options::instMaxLevel()!=-1 ){ - Notice() << "SmtEngine: turning off cbqi to support instMaxLevel" << endl; - options::cbqi.set(false); - } - // Do we need to track instantiations? - // Needed for sygus due to single invocation techniques. - if (options::cbqiNestedQE() - || (options::proof() && !options::trackInstLemmas.wasSetByUser()) - || is_sygus) - { - options::trackInstLemmas.set( true ); - } - - if( ( options::fmfBoundLazy.wasSetByUser() && options::fmfBoundLazy() ) || - ( options::fmfBoundInt.wasSetByUser() && options::fmfBoundInt() ) ) { - options::fmfBound.set( true ); - } - //now have determined whether fmfBoundInt is on/off - //apply fmfBoundInt options - if( options::fmfBound() ){ - if (!options::mbqiMode.wasSetByUser() - || (options::mbqiMode() != options::MbqiMode::NONE - && options::mbqiMode() != options::MbqiMode::FMC)) - { - //if bounded integers are set, use no MBQI by default - options::mbqiMode.set(options::MbqiMode::NONE); - } - if( ! options::prenexQuant.wasSetByUser() ){ - options::prenexQuant.set(options::PrenexQuantMode::NONE); - } - } - if( options::ufHo() ){ - //if higher-order, then current variants of model-based instantiation cannot be used - if (options::mbqiMode() != options::MbqiMode::NONE) - { - options::mbqiMode.set(options::MbqiMode::NONE); - } - if (!options::hoElimStoreAx.wasSetByUser()) - { - // by default, use store axioms only if --ho-elim is set - options::hoElimStoreAx.set(options::hoElim()); - } - } - if( options::fmfFunWellDefinedRelevant() ){ - if( !options::fmfFunWellDefined.wasSetByUser() ){ - options::fmfFunWellDefined.set( true ); - } - } - if( options::fmfFunWellDefined() ){ - if( !options::finiteModelFind.wasSetByUser() ){ - options::finiteModelFind.set( true ); - } - } - //EPR - if( options::quantEpr() ){ - if( !options::preSkolemQuant.wasSetByUser() ){ - options::preSkolemQuant.set( true ); - } - } - - //now, have determined whether finite model find is on/off - //apply finite model finding options - if( options::finiteModelFind() ){ - //apply conservative quantifiers splitting - if( !options::quantDynamicSplit.wasSetByUser() ){ - options::quantDynamicSplit.set(options::QuantDSplitMode::DEFAULT); - } - //do not eliminate extended arithmetic symbols from quantified formulas - if( !options::elimExtArithQuant.wasSetByUser() ){ - options::elimExtArithQuant.set( false ); - } - if( !options::eMatching.wasSetByUser() ){ - options::eMatching.set( options::fmfInstEngine() ); - } - if( !options::instWhenMode.wasSetByUser() ){ - //instantiate only on last call - if( options::eMatching() ){ - options::instWhenMode.set(options::InstWhenMode::LAST_CALL); - } - } - } - - // apply sygus options - // if we are attempting to rewrite everything to SyGuS, use sygus() - if (is_sygus) - { - if (!options::sygus()) - { - Trace("smt") << "turning on sygus" << std::endl; - } - options::sygus.set(true); - // must use Ferrante/Rackoff for real arithmetic - if (!options::cbqiMidpoint.wasSetByUser()) - { - options::cbqiMidpoint.set(true); - } - if (options::sygusRepairConst()) - { - if (!options::cbqi.wasSetByUser()) - { - options::cbqi.set(true); - } - } - if (options::sygusInference()) - { - // optimization: apply preskolemization, makes it succeed more often - if (!options::preSkolemQuant.wasSetByUser()) - { - options::preSkolemQuant.set(true); - } - if (!options::preSkolemQuantNested.wasSetByUser()) - { - options::preSkolemQuantNested.set(true); - } - } - //counterexample-guided instantiation for sygus - if( !options::cegqiSingleInvMode.wasSetByUser() ){ - options::cegqiSingleInvMode.set(options::CegqiSingleInvMode::USE); - } - if( !options::quantConflictFind.wasSetByUser() ){ - options::quantConflictFind.set( false ); - } - if( !options::instNoEntail.wasSetByUser() ){ - options::instNoEntail.set( false ); - } - if (!options::cbqiFullEffort.wasSetByUser()) - { - // should use full effort cbqi for single invocation and repair const - options::cbqiFullEffort.set(true); - } - if (options::sygusRew()) - { - options::sygusRewSynth.set(true); - options::sygusRewVerify.set(true); - } - if (options::sygusRewSynthInput()) - { - // If we are using synthesis rewrite rules from input, we use - // sygusRewSynth after preprocessing. See passes/synth_rew_rules.h for - // details on this technique. - options::sygusRewSynth.set(true); - // we should not use the extended rewriter, since we are interested - // in rewrites that are not in the main rewriter - if (!options::sygusExtRew.wasSetByUser()) - { - options::sygusExtRew.set(false); - } - } - // Whether we must use "basic" sygus algorithms. A non-basic sygus algorithm - // is one that is specialized for returning a single solution. Non-basic - // sygus algorithms currently include the PBE solver, UNIF+PI, static - // template inference for invariant synthesis, and single invocation - // techniques. - bool reqBasicSygus = false; - if (options::produceAbducts()) - { - // if doing abduction, we should filter strong solutions - if (!options::sygusFilterSolMode.wasSetByUser()) - { - options::sygusFilterSolMode.set(options::SygusFilterSolMode::STRONG); - } - // we must use basic sygus algorithms, since e.g. we require checking - // a sygus side condition for consistency with axioms. - reqBasicSygus = true; - } - if (options::sygusRewSynth() || options::sygusRewVerify() - || options::sygusQueryGen()) - { - // rewrite rule synthesis implies that sygus stream must be true - options::sygusStream.set(true); - } - if (options::sygusStream() || options::incrementalSolving()) - { - // Streaming and incremental mode are incompatible with techniques that - // focus the search towards finding a single solution. - reqBasicSygus = true; - } - // Now, disable options for non-basic sygus algorithms, if necessary. - if (reqBasicSygus) - { - if (!options::sygusUnifPbe.wasSetByUser()) - { - options::sygusUnifPbe.set(false); - } - if (options::sygusUnifPi.wasSetByUser()) - { - options::sygusUnifPi.set(options::SygusUnifPiMode::NONE); - } - if (!options::sygusInvTemplMode.wasSetByUser()) - { - options::sygusInvTemplMode.set(options::SygusInvTemplMode::NONE); - } - if (!options::cegqiSingleInvMode.wasSetByUser()) - { - options::cegqiSingleInvMode.set(options::CegqiSingleInvMode::NONE); - } - } - //do not allow partial functions - if (!options::bitvectorDivByZeroConst()) - { - if (options::bitvectorDivByZeroConst.wasSetByUser()) - { - throw OptionException( - "--no-bv-div-zero-const is not supported with SyGuS"); - } - Notice() - << "SmtEngine: setting bv-div-zero-const to true to support SyGuS" - << std::endl; - options::bitvectorDivByZeroConst.set( true ); - } - if( !options::dtRewriteErrorSel.wasSetByUser() ){ - options::dtRewriteErrorSel.set( true ); - } - //do not miniscope - if( !options::miniscopeQuant.wasSetByUser() ){ - options::miniscopeQuant.set( false ); - } - if( !options::miniscopeQuantFreeVar.wasSetByUser() ){ - options::miniscopeQuantFreeVar.set( false ); - } - if (!options::quantSplit.wasSetByUser()) - { - options::quantSplit.set(false); - } - //rewrite divk - if( !options::rewriteDivk.wasSetByUser()) { - options::rewriteDivk.set( true ); - } - //do not do macros - if( !options::macrosQuant.wasSetByUser()) { - options::macrosQuant.set( false ); - } - if( !options::cbqiPreRegInst.wasSetByUser()) { - options::cbqiPreRegInst.set( true ); - } - } - //counterexample-guided instantiation for non-sygus - // enable if any possible quantifiers with arithmetic, datatypes or bitvectors - if ((d_logic.isQuantified() - && (d_logic.isTheoryEnabled(THEORY_ARITH) - || d_logic.isTheoryEnabled(THEORY_DATATYPES) - || d_logic.isTheoryEnabled(THEORY_BV) - || d_logic.isTheoryEnabled(THEORY_FP))) - || options::cbqiAll()) - { - if( !options::cbqi.wasSetByUser() ){ - options::cbqi.set( true ); - } - // check whether we should apply full cbqi - if (d_logic.isPure(THEORY_BV)) - { - if (!options::cbqiFullEffort.wasSetByUser()) - { - options::cbqiFullEffort.set(true); - } - } - } - if( options::cbqi() ){ - //must rewrite divk - if( !options::rewriteDivk.wasSetByUser()) { - options::rewriteDivk.set( true ); - } - if (options::incrementalSolving()) - { - // cannot do nested quantifier elimination in incremental mode - options::cbqiNestedQE.set(false); - } - if (d_logic.isPure(THEORY_ARITH) || d_logic.isPure(THEORY_BV)) - { - if( !options::quantConflictFind.wasSetByUser() ){ - options::quantConflictFind.set( false ); - } - if( !options::instNoEntail.wasSetByUser() ){ - options::instNoEntail.set( false ); - } - if( !options::instWhenMode.wasSetByUser() && options::cbqiModel() ){ - //only instantiation should happen at last call when model is avaiable - options::instWhenMode.set(options::InstWhenMode::LAST_CALL); - } - }else{ - // only supported in pure arithmetic or pure BV - options::cbqiNestedQE.set(false); - } - // prenexing - if (options::cbqiNestedQE()) - { - // only complete with prenex = disj_normal or normal - if (options::prenexQuant() <= options::PrenexQuantMode::DISJ_NORMAL) - { - options::prenexQuant.set(options::PrenexQuantMode::DISJ_NORMAL); - } - } - else if (options::globalNegate()) - { - if (!options::prenexQuant.wasSetByUser()) - { - options::prenexQuant.set(options::PrenexQuantMode::NONE); - } - } - } - //implied options... - if( options::strictTriggers() ){ - if( !options::userPatternsQuant.wasSetByUser() ){ - options::userPatternsQuant.set(options::UserPatMode::TRUST); - } - } - if( options::qcfMode.wasSetByUser() || options::qcfTConstraint() ){ - options::quantConflictFind.set( true ); - } - if( options::cbqiNestedQE() ){ - options::prenexQuantUser.set( true ); - if( !options::preSkolemQuant.wasSetByUser() ){ - options::preSkolemQuant.set( true ); - } - } - //for induction techniques - if( options::quantInduction() ){ - if( !options::dtStcInduction.wasSetByUser() ){ - options::dtStcInduction.set( true ); - } - if( !options::intWfInduction.wasSetByUser() ){ - options::intWfInduction.set( true ); - } - } - if( options::dtStcInduction() ){ - //try to remove ITEs from quantified formulas - if( !options::iteDtTesterSplitQuant.wasSetByUser() ){ - options::iteDtTesterSplitQuant.set( true ); - } - if( !options::iteLiftQuant.wasSetByUser() ){ - options::iteLiftQuant.set(options::IteLiftQuantMode::ALL); - } - } - if( options::intWfInduction() ){ - if( !options::purifyTriggers.wasSetByUser() ){ - options::purifyTriggers.set( true ); - } - } - if( options::conjectureNoFilter() ){ - if( !options::conjectureFilterActiveTerms.wasSetByUser() ){ - options::conjectureFilterActiveTerms.set( false ); - } - if( !options::conjectureFilterCanonical.wasSetByUser() ){ - options::conjectureFilterCanonical.set( false ); - } - if( !options::conjectureFilterModel.wasSetByUser() ){ - options::conjectureFilterModel.set( false ); - } - } - if( options::conjectureGenPerRound.wasSetByUser() ){ - if( options::conjectureGenPerRound()>0 ){ - options::conjectureGen.set( true ); - }else{ - options::conjectureGen.set( false ); - } - } - //can't pre-skolemize nested quantifiers without UF theory - if( !d_logic.isTheoryEnabled(THEORY_UF) && options::preSkolemQuant() ){ - if( !options::preSkolemQuantNested.wasSetByUser() ){ - options::preSkolemQuantNested.set( false ); - } - } - if( !d_logic.isTheoryEnabled(THEORY_DATATYPES) ){ - options::quantDynamicSplit.set(options::QuantDSplitMode::NONE); - } - - //until bugs 371,431 are fixed - if( ! options::minisatUseElim.wasSetByUser()){ - // cannot use minisat elimination for logics where a theory solver - // introduces new literals into the search. This includes quantifiers - // (quantifier instantiation), and the lemma schemas used in non-linear - // and sets. We also can't use it if models are enabled. - if (d_logic.isTheoryEnabled(THEORY_SETS) || d_logic.isQuantified() - || options::produceModels() || options::produceAssignments() - || options::checkModels() - || (d_logic.isTheoryEnabled(THEORY_ARITH) && !d_logic.isLinear())) - { - options::minisatUseElim.set( false ); - } - } - - // For now, these array theory optimizations do not support model-building - if (options::produceModels() || options::produceAssignments() || options::checkModels()) { - options::arraysOptimizeLinear.set(false); - options::arraysLazyRIntro1.set(false); - } - - if (options::proof()) - { - if (options::incrementalSolving()) - { - if (options::incrementalSolving.wasSetByUser()) - { - throw OptionException("--incremental is not supported with proofs"); - } - Warning() - << "SmtEngine: turning off incremental solving mode (not yet " - "supported with --proof, try --tear-down-incremental instead)" - << endl; - setOption("incremental", SExpr("false")); - } - if (d_logic > LogicInfo("QF_AUFBVLRA")) - { - throw OptionException( - "Proofs are only supported for sub-logics of QF_AUFBVLIA."); - } - if (options::bitvectorAlgebraicSolver()) - { - if (options::bitvectorAlgebraicSolver.wasSetByUser()) - { - throw OptionException( - "--bv-algebraic-solver is not supported with proofs"); - } - Notice() << "SmtEngine: turning off bv algebraic solver to support proofs" - << std::endl; - options::bitvectorAlgebraicSolver.set(false); - } - if (options::bitvectorEqualitySolver()) - { - if (options::bitvectorEqualitySolver.wasSetByUser()) - { - throw OptionException("--bv-eq-solver is not supported with proofs"); - } - Notice() << "SmtEngine: turning off bv eq solver to support proofs" - << std::endl; - options::bitvectorEqualitySolver.set(false); - } - if (options::bitvectorInequalitySolver()) - { - if (options::bitvectorInequalitySolver.wasSetByUser()) - { - throw OptionException( - "--bv-inequality-solver is not supported with proofs"); - } - Notice() << "SmtEngine: turning off bv ineq solver to support proofs" - << std::endl; - options::bitvectorInequalitySolver.set(false); - } - } - - if (!options::bitvectorEqualitySolver()) - { - if (options::bvLazyRewriteExtf()) - { - if (options::bvLazyRewriteExtf.wasSetByUser()) - { - throw OptionException( - "--bv-lazy-rewrite-extf requires --bv-eq-solver to be set"); - } - } - Trace("smt") - << "disabling bvLazyRewriteExtf since equality solver is disabled" - << endl; - options::bvLazyRewriteExtf.set(false); - } - - if (!options::sygusExprMinerCheckUseExport()) - { - if (options::sygusExprMinerCheckTimeout.wasSetByUser()) - { - throw OptionException( - "--sygus-expr-miner-check-timeout=N requires " - "--sygus-expr-miner-check-use-export"); - } - if (options::sygusRewSynthInput() || options::produceAbducts()) - { - std::stringstream ss; - ss << (options::sygusRewSynthInput() ? "--sygus-rr-synth-input" - : "--produce-abducts"); - ss << "requires --sygus-expr-miner-check-use-export"; - throw OptionException(ss.str()); - } - } - - if (options::stringFMF() && !options::stringProcessLoopMode.wasSetByUser()) - { - Trace("smt") << "settting stringProcessLoopMode to 'simple' since " - "--strings-fmf enabled" - << endl; - options::stringProcessLoopMode.set(options::ProcessLoopMode::SIMPLE); - } - - // !!! All options that require disabling models go here - bool disableModels = false; - std::string sOptNoModel; - if (options::unconstrainedSimp.wasSetByUser() && options::unconstrainedSimp()) - { - disableModels = true; - sOptNoModel = "unconstrained-simp"; - } - else if (options::sortInference()) - { - disableModels = true; - sOptNoModel = "sort-inference"; - } - else if (options::minisatUseElim()) - { - disableModels = true; - sOptNoModel = "minisat-elimination"; - } - else if (d_logic.isTheoryEnabled(THEORY_ARITH) && !d_logic.isLinear() - && !options::nlExt()) - { - disableModels = true; - sOptNoModel = "nonlinear arithmetic without nl-ext"; - } - else if (options::globalNegate()) - { - disableModels = true; - sOptNoModel = "global-negate"; - } - if (disableModels) - { - if (options::produceModels()) - { - if (options::produceModels.wasSetByUser()) - { - std::stringstream ss; - ss << "Cannot use " << sOptNoModel << " with model generation."; - throw OptionException(ss.str()); - } - Notice() << "SmtEngine: turning off produce-models to support " - << sOptNoModel << endl; - setOption("produce-models", SExpr("false")); - } - if (options::produceAssignments()) - { - if (options::produceAssignments.wasSetByUser()) - { - std::stringstream ss; - ss << "Cannot use " << sOptNoModel - << " with model generation (produce-assignments)."; - throw OptionException(ss.str()); - } - Notice() << "SmtEngine: turning off produce-assignments to support " - << sOptNoModel << endl; - setOption("produce-assignments", SExpr("false")); - } - if (options::checkModels()) - { - if (options::checkModels.wasSetByUser()) - { - std::stringstream ss; - ss << "Cannot use " << sOptNoModel - << " with model generation (check-models)."; - throw OptionException(ss.str()); - } - Notice() << "SmtEngine: turning off check-models to support " - << sOptNoModel << endl; - setOption("check-models", SExpr("false")); - } - } -} - void SmtEngine::setProblemExtended() { d_smtMode = SMT_MODE_ASSERT; @@ -2966,7 +1759,8 @@ bool SmtEnginePrivate::simplifyAssertions() d_smt.d_theoryEngine->staticInitializeBVOptions(d_assertions.ref()); // Theory preprocessing - if (d_smt.d_earlyTheoryPP) + bool doEarlyTheoryPp = !options::arithRewriteEq(); + if (doEarlyTheoryPp) { d_passes["theory-preprocess"]->apply(&d_assertions); } @@ -5684,6 +4478,9 @@ void SmtEngine::setOption(const std::string& key, const CVC4::SExpr& value) } void SmtEngine::setIsInternalSubsolver() { d_isInternalSubsolver = true; } + +bool SmtEngine::isInternalSubsolver() const { return d_isInternalSubsolver; } + CVC4::SExpr SmtEngine::getOption(const std::string& key) const { NodeManagerScope nms(d_nodeManager); diff --git a/src/smt/smt_engine.h b/src/smt/smt_engine.h index fbd92bcf2..f5abda1b0 100644 --- a/src/smt/smt_engine.h +++ b/src/smt/smt_engine.h @@ -203,6 +203,8 @@ class CVC4_PUBLIC SmtEngine * --sygus-abduct. */ void setIsInternalSubsolver(); + /** Is this an internal subsolver? */ + bool isInternalSubsolver() const; /** set the input name */ void setFilename(std::string filename); @@ -938,12 +940,6 @@ class CVC4_PUBLIC SmtEngine */ void finalOptionsAreSet(); - /** - * Apply heuristics settings and other defaults. Done once, at - * finishInit() time. - */ - void setDefaults(); - /** * Sets that the problem has been extended. This sets the smt mode of the * solver to SMT_MODE_ASSERT, and clears the list of assumptions from the -- cgit v1.2.3 From 27ac2ce712b0bcfdef83e2d44dd210f667ab7959 Mon Sep 17 00:00:00 2001 From: Andrew Reynolds Date: Fri, 27 Mar 2020 09:01:38 -0500 Subject: Support unicode internal representation and escape sequences (#3852) Work towards support for the strings standard. This updates the string solver and parser such that: The internal representation of strings is vectors of code points, Generation of the previous internal representation of strings has been relegated to the type enumerator. This is the code that ensures that "A" is the first character chosen for values of strings in models, The previous ad-hoc escape sequence handling is moved from the String class to the parser. It will live there for at least one version of CVC4, until we no longer support non-smt-lib complaint escape sequences or non-printable characters in strings, Handle unicode escape sequences according to the SMT-LIB standard in String, Simplify a number of calls to String utility functions, since the conversion between the previous internal format and code points is now unnecessary, Fixed a bug in the handling of TO_CODE: it should be based on the alphabet cardinality, not the number of internal code points. --- src/parser/cvc/Cvc.g | 2 +- src/parser/parser.cpp | 148 +++++++++ src/parser/parser.h | 22 ++ src/parser/smt2/Smt2.g | 2 +- src/preprocessing/passes/synth_rew_rules.cpp | 2 +- src/printer/cvc/cvc_printer.cpp | 5 + src/printer/smt2/smt2_printer.cpp | 2 +- src/theory/evaluator.cpp | 3 +- src/theory/quantifiers/sygus_sampler.cpp | 3 +- src/theory/strings/regexp_operation.cpp | 10 +- src/theory/strings/sequences_rewriter.cpp | 3 - src/theory/strings/strings_rewriter.cpp | 5 +- src/theory/strings/theory_strings.cpp | 7 +- src/theory/strings/theory_strings_type_rules.h | 2 +- src/theory/strings/type_enumerator.cpp | 40 ++- src/theory/strings/type_enumerator.h | 20 ++ src/util/regexp.cpp | 349 ++++++++++----------- src/util/regexp.h | 138 ++++---- test/regress/CMakeLists.txt | 4 + test/regress/regress0/strings/gen-esc-seq.smt2 | 9 + .../regress/regress0/strings/model-code-point.smt2 | 13 + test/regress/regress0/strings/model-friendly.smt2 | 9 + test/regress/regress0/strings/unicode-esc.smt2 | 30 ++ test/unit/api/solver_black.h | 6 +- 24 files changed, 549 insertions(+), 285 deletions(-) create mode 100644 test/regress/regress0/strings/gen-esc-seq.smt2 create mode 100644 test/regress/regress0/strings/model-code-point.smt2 create mode 100644 test/regress/regress0/strings/model-friendly.smt2 create mode 100644 test/regress/regress0/strings/unicode-esc.smt2 diff --git a/src/parser/cvc/Cvc.g b/src/parser/cvc/Cvc.g index 82c0581ce..033389610 100644 --- a/src/parser/cvc/Cvc.g +++ b/src/parser/cvc/Cvc.g @@ -2083,7 +2083,7 @@ stringTerm[CVC4::api::Term& f] /* string literal */ | str[s] - { f = SOLVER->mkString(s, true); } + { f = PARSER_STATE->mkStringConstant(s); } | setsTerm[f] ; diff --git a/src/parser/parser.cpp b/src/parser/parser.cpp index b36f36a93..5dca92370 100644 --- a/src/parser/parser.cpp +++ b/src/parser/parser.cpp @@ -757,5 +757,153 @@ void Parser::attributeNotSupported(const std::string& attr) { } } +std::vector Parser::processAdHocStringEsc(const std::string& s) +{ + std::vector str; + unsigned i = 0; + while (i < s.size()) + { + // get the current character + if (s[i] != '\\') + { + // don't worry about printable here + str.push_back(static_cast(s[i])); + ++i; + continue; + } + // slash is always escaped + ++i; + if (i >= s.size()) + { + // slash cannot be the last character if we are parsing escape sequences + std::stringstream serr; + serr << "Escape sequence at the end of string: \"" << s + << "\" should be handled by lexer"; + parseError(serr.str()); + } + switch (s[i]) + { + case 'n': + { + str.push_back(static_cast('\n')); + i++; + } + break; + case 't': + { + str.push_back(static_cast('\t')); + i++; + } + break; + case 'v': + { + str.push_back(static_cast('\v')); + i++; + } + break; + case 'b': + { + str.push_back(static_cast('\b')); + i++; + } + break; + case 'r': + { + str.push_back(static_cast('\r')); + i++; + } + break; + case 'f': + { + str.push_back(static_cast('\f')); + i++; + } + break; + case 'a': + { + str.push_back(static_cast('\a')); + i++; + } + break; + case '\\': + { + str.push_back(static_cast('\\')); + i++; + } + break; + case 'x': + { + bool isValid = false; + if (i + 2 < s.size()) + { + if (std::isxdigit(s[i + 1]) && std::isxdigit(s[i + 2])) + { + std::stringstream shex; + shex << s[i + 1] << s[i + 2]; + unsigned val; + shex >> std::hex >> val; + str.push_back(val); + i += 3; + isValid = true; + } + } + if (!isValid) + { + std::stringstream serr; + serr << "Illegal String Literal: \"" << s + << "\", must have two digits after \\x"; + parseError(serr.str()); + } + } + break; + default: + { + if (std::isdigit(s[i])) + { + // octal escape sequences TODO : revisit (issue #1251). + unsigned num = static_cast(s[i]) - 48; + bool flag = num < 4; + if (i + 1 < s.size() && num < 8 && std::isdigit(s[i + 1]) + && s[i + 1] < '8') + { + num = num * 8 + static_cast(s[i + 1]) - 48; + if (flag && i + 2 < s.size() && std::isdigit(s[i + 2]) + && s[i + 2] < '8') + { + num = num * 8 + static_cast(s[i + 2]) - 48; + str.push_back(num); + i += 3; + } + else + { + str.push_back(num); + i += 2; + } + } + else + { + str.push_back(num); + i++; + } + } + } + } + } + return str; +} + +Expr Parser::mkStringConstant(const std::string& s) +{ + ExprManager* em = d_solver->getExprManager(); + if (em->getOptions().getInputLanguage() + == language::input::LANG_SMTLIB_V2_6_1) + { + return d_solver->mkString(s, true).getExpr(); + } + // otherwise, we must process ad-hoc escape sequences + std::vector str = processAdHocStringEsc(s); + return d_solver->mkString(str).getExpr(); +} + } /* CVC4::parser namespace */ } /* CVC4 namespace */ diff --git a/src/parser/parser.h b/src/parser/parser.h index ecea4d3bd..d6c0e0e15 100644 --- a/src/parser/parser.h +++ b/src/parser/parser.h @@ -889,6 +889,28 @@ public: name, api::sortVectorToTypes(argTypes)); } //------------------------ end operator overloading + /** + * Make string constant + * + * This makes the string constant based on the string s. This may involve + * processing ad-hoc escape sequences (if the language is not + * SMT-LIB 2.6.1 or higher), or otherwise calling the solver to construct + * the string. + */ + Expr mkStringConstant(const std::string& s); + + private: + /** ad-hoc string escaping + * + * Returns the (internal) vector of code points corresponding to processing + * the escape sequences in string s. This is to support string inputs that + * do no comply with the SMT-LIB standard. + * + * This method handles escape sequences, including \n, \t, \v, \b, \r, \f, \a, + * \\, \x[N] and octal escape sequences of the form \[c1]([c2]([c3])?)? where + * c1, c2, c3 are digits from 0 to 7. + */ + std::vector processAdHocStringEsc(const std::string& s); };/* class Parser */ }/* CVC4::parser namespace */ diff --git a/src/parser/smt2/Smt2.g b/src/parser/smt2/Smt2.g index ec1eae7da..69f21acb7 100644 --- a/src/parser/smt2/Smt2.g +++ b/src/parser/smt2/Smt2.g @@ -2091,7 +2091,7 @@ termAtomic[CVC4::api::Term& atomTerm] } // String constant - | str[s,false] { atomTerm = SOLVER->mkString(s, true); } + | str[s,false] { atomTerm = PARSER_STATE->mkStringConstant(s); } // NOTE: Theory constants go here diff --git a/src/preprocessing/passes/synth_rew_rules.cpp b/src/preprocessing/passes/synth_rew_rules.cpp index 7b8e61359..f1e9e39c5 100644 --- a/src/preprocessing/passes/synth_rew_rules.cpp +++ b/src/preprocessing/passes/synth_rew_rules.cpp @@ -169,7 +169,7 @@ PreprocessingPassResult SynthRewRulesPass::applyInternal( std::stringstream ssv; if (varCounter < 26) { - ssv << String::convertUnsignedIntToChar(varCounter + 32); + ssv << static_cast(varCounter + 61); } else { diff --git a/src/printer/cvc/cvc_printer.cpp b/src/printer/cvc/cvc_printer.cpp index cad3c4640..1178c7299 100644 --- a/src/printer/cvc/cvc_printer.cpp +++ b/src/printer/cvc/cvc_printer.cpp @@ -160,6 +160,11 @@ void CvcPrinter::toStream( toStreamRational(out, n, false); break; } + case kind::CONST_STRING: + { + out << '"' << n.getConst().toString() << '"'; + break; + } case kind::TYPE_CONSTANT: switch(TypeConstant tc = n.getConst()) { case REAL_TYPE: diff --git a/src/printer/smt2/smt2_printer.cpp b/src/printer/smt2/smt2_printer.cpp index 541827f89..6e4fcb63a 100644 --- a/src/printer/smt2/smt2_printer.cpp +++ b/src/printer/smt2/smt2_printer.cpp @@ -202,7 +202,7 @@ void Smt2Printer::toStream(std::ostream& out, } case kind::CONST_STRING: { - std::string s = n.getConst().toString(true); + std::string s = n.getConst().toString(); out << '"'; for(size_t i = 0; i < s.size(); ++i) { char c = s[i]; diff --git a/src/theory/evaluator.cpp b/src/theory/evaluator.cpp index b827912d5..646f903f5 100644 --- a/src/theory/evaluator.cpp +++ b/src/theory/evaluator.cpp @@ -626,8 +626,7 @@ EvalResult Evaluator::evalInternal( const String& s = results[currNode[0]].d_str; if (s.size() == 1) { - results[currNode] = EvalResult( - Rational(String::convertUnsignedIntToCode(s.getVec()[0]))); + results[currNode] = EvalResult(Rational(s.getVec()[0])); } else { diff --git a/src/theory/quantifiers/sygus_sampler.cpp b/src/theory/quantifiers/sygus_sampler.cpp index 28cfa69df..e9c858814 100644 --- a/src/theory/quantifiers/sygus_sampler.cpp +++ b/src/theory/quantifiers/sygus_sampler.cpp @@ -560,8 +560,7 @@ Node SygusSampler::getRandomValue(TypeNode tn) for (unsigned ch : alphas) { d_rstring_alphabet.push_back(ch); - Trace("sygus-sample-str-alpha") - << " \"" << String::convertUnsignedIntToChar(ch) << "\""; + Trace("sygus-sample-str-alpha") << " \\u" << ch; } Trace("sygus-sample-str-alpha") << std::endl; } diff --git a/src/theory/strings/regexp_operation.cpp b/src/theory/strings/regexp_operation.cpp index d5105a489..9a2091eac 100644 --- a/src/theory/strings/regexp_operation.cpp +++ b/src/theory/strings/regexp_operation.cpp @@ -739,9 +739,7 @@ void RegExpOpr::firstChars(Node r, std::set &pcset, SetNodes &pvset) } case kind::REGEXP_RANGE: { unsigned a = r[0].getConst().front(); - a = String::convertUnsignedIntToCode(a); unsigned b = r[1].getConst().front(); - b = String::convertUnsignedIntToCode(b); Assert(a < b); Assert(b < std::numeric_limits::max()); for (unsigned c = a; c <= b; c++) @@ -756,7 +754,6 @@ void RegExpOpr::firstChars(Node r, std::set &pcset, SetNodes &pvset) String s = st.getConst(); if(s.size() != 0) { unsigned sc = s.front(); - sc = String::convertUnsignedIntToCode(sc); cset.insert(sc); } } @@ -765,7 +762,6 @@ void RegExpOpr::firstChars(Node r, std::set &pcset, SetNodes &pvset) if(st[0].isConst()) { String s = st[0].getConst(); unsigned sc = s.front(); - sc = String::convertUnsignedIntToCode(sc); cset.insert(sc); } else { vset.insert( st[0] ); @@ -887,13 +883,11 @@ void RegExpOpr::simplifyNRegExp( Node s, Node r, std::vector< Node > &new_nodes case kind::REGEXP_RANGE: { std::vector< Node > vec; unsigned a = r[0].getConst().front(); - a = String::convertUnsignedIntToCode(a); unsigned b = r[1].getConst().front(); - b = String::convertUnsignedIntToCode(b); for (unsigned c = a; c <= b; c++) { std::vector tmpVec; - tmpVec.push_back(String::convertCodeToUnsignedInt(c)); + tmpVec.push_back(c); Node tmp = s.eqNode(nm->mkConst(String(tmpVec))).negate(); vec.push_back( tmp ); } @@ -1522,7 +1516,7 @@ Node RegExpOpr::intersectInternal( Node r1, Node r2, std::map< PairNodes, Node > ++it) { std::vector cvec; - cvec.push_back(String::convertCodeToUnsignedInt(*it)); + cvec.push_back(*it); String c(cvec); Trace("regexp-int-debug") << "Try character " << c << " ... " << std::endl; Node r1l = derivativeSingle(r1, c); diff --git a/src/theory/strings/sequences_rewriter.cpp b/src/theory/strings/sequences_rewriter.cpp index 716634d5f..b0940b7e1 100644 --- a/src/theory/strings/sequences_rewriter.cpp +++ b/src/theory/strings/sequences_rewriter.cpp @@ -1421,11 +1421,8 @@ bool SequencesRewriter::testConstStringInRegExp(CVC4::String& s, if (s.size() == index_start + 1) { unsigned a = r[0].getConst().front(); - a = String::convertUnsignedIntToCode(a); unsigned b = r[1].getConst().front(); - b = String::convertUnsignedIntToCode(b); unsigned c = s.back(); - c = String::convertUnsignedIntToCode(c); return (a <= c && c <= b); } else diff --git a/src/theory/strings/strings_rewriter.cpp b/src/theory/strings/strings_rewriter.cpp index 75dfe7432..c7676d049 100644 --- a/src/theory/strings/strings_rewriter.cpp +++ b/src/theory/strings/strings_rewriter.cpp @@ -93,7 +93,7 @@ Node StringsRewriter::rewriteStrConvert(Node node) std::vector nvec = node[0].getConst().getVec(); for (unsigned i = 0, nvsize = nvec.size(); i < nvsize; i++) { - unsigned newChar = String::convertUnsignedIntToCode(nvec[i]); + unsigned newChar = nvec[i]; // transform it // upper 65 ... 90 // lower 97 ... 122 @@ -111,7 +111,6 @@ Node StringsRewriter::rewriteStrConvert(Node node) newChar = newChar + 32; } } - newChar = String::convertCodeToUnsignedInt(newChar); nvec[i] = newChar; } Node retNode = nm->mkConst(String(nvec)); @@ -231,7 +230,7 @@ Node StringsRewriter::rewriteStringToCode(Node n) { std::vector vec = s.getVec(); Assert(vec.size() == 1); - ret = nm->mkConst(Rational(String::convertUnsignedIntToCode(vec[0]))); + ret = nm->mkConst(Rational(vec[0])); } else { diff --git a/src/theory/strings/theory_strings.cpp b/src/theory/strings/theory_strings.cpp index 16183abdd..a81c96318 100644 --- a/src/theory/strings/theory_strings.cpp +++ b/src/theory/strings/theory_strings.cpp @@ -382,7 +382,7 @@ bool TheoryStrings::collectModelInfoType( ctv.getConst().getNumerator().toUnsignedInt(); Trace("strings-model") << "(code: " << cvalue << ") "; std::vector vec; - vec.push_back(String::convertCodeToUnsignedInt(cvalue)); + vec.push_back(cvalue); Node mv = nm->mkConst(String(vec)); pure_eq_assign[eqc] = mv; m->getEqualityEngine()->addTerm(mv); @@ -1099,13 +1099,14 @@ void TheoryStrings::registerTerm(Node n, int effort) else if (n.getKind() == STRING_TO_CODE) { d_has_str_code = true; - // ite( str.len(s)==1, 0 <= str.code(s) < num_codes, str.code(s)=-1 ) + // ite( str.len(s)==1, 0 <= str.code(s) < |A|, 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())))); + nm->mkNode( + LT, n, nm->mkConst(Rational(utils::getAlphabetCardinality())))); regTermLem = nm->mkNode(ITE, code_len, code_range, code_eq_neg1); } else if (n.getKind() == STRING_STRIDOF) diff --git a/src/theory/strings/theory_strings_type_rules.h b/src/theory/strings/theory_strings_type_rules.h index 7ef31a92a..93a32f26e 100644 --- a/src/theory/strings/theory_strings_type_rules.h +++ b/src/theory/strings/theory_strings_type_rules.h @@ -299,7 +299,7 @@ public: throw TypeCheckingExceptionPrivate(n, "expecting a single constant string term in regexp range"); } unsigned ci = (*it).getConst().front(); - ch[i] = String::convertUnsignedIntToCode(ci); + ch[i] = ci; ++it; } if(ch[0] > ch[1]) { diff --git a/src/theory/strings/type_enumerator.cpp b/src/theory/strings/type_enumerator.cpp index 12cf899b4..7352ae5de 100644 --- a/src/theory/strings/type_enumerator.cpp +++ b/src/theory/strings/type_enumerator.cpp @@ -21,6 +21,44 @@ namespace CVC4 { namespace theory { namespace strings { +Node makeStandardModelConstant(const std::vector& vec, + uint32_t cardinality) +{ + std::vector mvec; + // if we contain all of the printable characters + if (cardinality >= 255) + { + for (unsigned i = 0, vsize = vec.size(); i < vsize; i++) + { + unsigned curr = vec[i]; + // convert + Assert(vec[i] < cardinality); + if (vec[i] <= 61) + { + // first 62 printable characters [\u{65}-\u{126}]: 'A', 'B', 'C', ... + curr = vec[i] + 65; + } + else if (vec[i] <= 94) + { + // remaining 33 printable characters [\u{32}-\u{64}]: ' ', '!', '"', ... + curr = vec[i] - 30; + } + else + { + // the remaining characters, starting with \u{127} and wrapping around + // the first 32 non-printable characters. + curr = (vec[i] + 32) % cardinality; + } + mvec.push_back(curr); + } + } + else + { + mvec = vec; + } + return NodeManager::currentNM()->mkConst(String(mvec)); +} + WordIter::WordIter(uint32_t startLength) : d_hasEndLength(false), d_endLength(0) { for (uint32_t i = 0; i < startLength; i++) @@ -117,7 +155,7 @@ bool StringEnumLen::increment() void StringEnumLen::mkCurr() { - d_curr = NodeManager::currentNM()->mkConst(String(d_witer->getData())); + d_curr = makeStandardModelConstant(d_witer->getData(), d_cardinality); } StringEnumerator::StringEnumerator(TypeNode type, TypeEnumeratorProperties* tep) diff --git a/src/theory/strings/type_enumerator.h b/src/theory/strings/type_enumerator.h index 2061628a5..b379ce5c3 100644 --- a/src/theory/strings/type_enumerator.h +++ b/src/theory/strings/type_enumerator.h @@ -27,6 +27,26 @@ namespace CVC4 { namespace theory { namespace strings { +/** + * Make standard model constant + * + * In our string representation, we represent characters using vectors + * of unsigned integers indicating code points for the characters of that + * string. + * + * To make models user-friendly, we make unsigned integer 0 correspond to the + * 65th character ("A") in the ASCII alphabet to make models intuitive. In + * particular, say if we have a set of string variables that are distinct but + * otherwise unconstrained, then the model may assign them "A", "B", "C", ... + * + * @param vec The code points of the string in a given model, + * @param cardinality The cardinality of the alphabet, + * @return A string whose characters have the code points corresponding + * to vec in the standard model construction described above. + */ +Node makeStandardModelConstant(const std::vector& vec, + uint32_t cardinality); + /** * Generic iteration over vectors of indices of a given start/end length. */ diff --git a/src/util/regexp.cpp b/src/util/regexp.cpp index 00066edb6..36ba7182b 100644 --- a/src/util/regexp.cpp +++ b/src/util/regexp.cpp @@ -32,38 +32,12 @@ namespace CVC4 { static_assert(UCHAR_MAX == 255, "Unsigned char is assumed to have 256 values."); -unsigned String::convertCharToUnsignedInt(unsigned char c) -{ - return convertCodeToUnsignedInt(static_cast(c)); -} -unsigned char String::convertUnsignedIntToChar(unsigned i) -{ - Assert(i < num_codes()); - return static_cast(convertUnsignedIntToCode(i)); -} -bool String::isPrintable(unsigned i) -{ - Assert(i < num_codes()); - unsigned char c = convertUnsignedIntToChar(i); - return (c >= ' ' && c <= '~'); -} -unsigned String::convertCodeToUnsignedInt(unsigned c) -{ - Assert(c < num_codes()); - return (c < start_code() ? c + num_codes() : c) - start_code(); -} -unsigned String::convertUnsignedIntToCode(unsigned i) -{ - Assert(i < num_codes()); - return (i + start_code()) % num_codes(); -} - String::String(const std::vector &s) : d_str(s) { #ifdef CVC4_ASSERTIONS for (unsigned u : d_str) { - Assert(convertUnsignedIntToCode(u) < num_codes()); + Assert(u < num_codes()); } #endif } @@ -74,8 +48,8 @@ int String::cmp(const String &y) const { } for (unsigned int i = 0; i < size(); ++i) { if (d_str[i] != y.d_str[i]) { - unsigned cp = convertUnsignedIntToCode(d_str[i]); - unsigned cpy = convertUnsignedIntToCode(y.d_str[i]); + unsigned cp = d_str[i]; + unsigned cpy = y.d_str[i]; return cp < cpy ? -1 : 1; } } @@ -122,107 +96,143 @@ bool String::rstrncmp(const String& y, std::size_t n) const return true; } -std::vector String::toInternal(const std::string &s, - bool useEscSequences) { +void String::addCharToInternal(unsigned char ch, std::vector& str) +{ + // if not a printable character + if (ch > 127 || ch < 32) + { + std::stringstream serr; + serr << "Illegal string character: \"" << ch + << "\", must use escape sequence"; + throw CVC4::Exception(serr.str()); + } + else + { + str.push_back(static_cast(ch)); + } +} + +std::vector String::toInternal(const std::string& s, + bool useEscSequences) +{ std::vector str; unsigned i = 0; - while (i < s.size()) { - if (s[i] == '\\' && useEscSequences) { - i++; - if (i < s.size()) { - switch (s[i]) { - case 'n': { - str.push_back(convertCharToUnsignedInt('\n')); - i++; - } break; - case 't': { - str.push_back(convertCharToUnsignedInt('\t')); - i++; - } break; - case 'v': { - str.push_back(convertCharToUnsignedInt('\v')); - i++; - } break; - case 'b': { - str.push_back(convertCharToUnsignedInt('\b')); - i++; - } break; - case 'r': { - str.push_back(convertCharToUnsignedInt('\r')); - i++; - } break; - case 'f': { - str.push_back(convertCharToUnsignedInt('\f')); - i++; - } break; - case 'a': { - str.push_back(convertCharToUnsignedInt('\a')); - i++; - } break; - case '\\': { - str.push_back(convertCharToUnsignedInt('\\')); - i++; - } break; - case 'x': { - if (i + 2 < s.size()) { - if (isxdigit(s[i + 1]) && isxdigit(s[i + 2])) { - str.push_back(convertCharToUnsignedInt(hexToDec(s[i + 1]) * 16 + - hexToDec(s[i + 2]))); - i += 3; - } else { - throw CVC4::Exception("Illegal String Literal: \"" + s + "\""); - } - } else { - throw CVC4::Exception("Illegal String Literal: \"" + s + - "\", must have two digits after \\x"); - } - } break; - default: { - if (isdigit(s[i])) { - // octal escape sequences TODO : revisit (issue #1251). - int num = (int)s[i] - (int)'0'; - bool flag = num < 4; - if (i + 1 < s.size() && num < 8 && isdigit(s[i + 1]) && - s[i + 1] < '8') { - num = num * 8 + (int)s[i + 1] - (int)'0'; - if (flag && i + 2 < s.size() && isdigit(s[i + 2]) && - s[i + 2] < '8') { - num = num * 8 + (int)s[i + 2] - (int)'0'; - str.push_back(convertCharToUnsignedInt((unsigned char)num)); - i += 3; - } else { - str.push_back(convertCharToUnsignedInt((unsigned char)num)); - i += 2; - } - } else { - str.push_back(convertCharToUnsignedInt((unsigned char)num)); - i++; - } - } else if ((unsigned)s[i] > 127) { - throw CVC4::Exception("Illegal String Literal: \"" + s + - "\", must use escaped sequence"); - } else { - str.push_back(convertCharToUnsignedInt(s[i])); - i++; - } + while (i < s.size()) + { + // get the current character + char si = s[i]; + if (si != '\\' || !useEscSequences) + { + addCharToInternal(si, str); + ++i; + continue; + } + // the vector of characters, in case we fail to read an escape sequence + std::vector nonEscCache; + // process the '\' + addCharToInternal(si, nonEscCache); + ++i; + // are we an escape sequence? + bool isEscapeSequence = true; + // the string corresponding to the hexidecimal code point + std::stringstream hexString; + // is the slash followed by a 'u'? Could be last character. + if (i >= s.size() || s[i] != 'u') + { + isEscapeSequence = false; + } + else + { + // process the 'u' + addCharToInternal(s[i], nonEscCache); + ++i; + bool isStart = true; + bool isEnd = false; + bool hasBrace = false; + while (i < s.size()) + { + // add the next character + si = s[i]; + if (isStart) + { + isStart = false; + // possibly read '{' + if (si == '{') + { + hasBrace = true; + addCharToInternal(si, nonEscCache); + ++i; + continue; } } - } else { - throw CVC4::Exception("should be handled by lexer: \"" + s + "\""); - // str.push_back( convertCharToUnsignedInt('\\') ); + else if (si == '}') + { + // can only end if we had an open brace and read at least one digit + isEscapeSequence = hasBrace && !hexString.str().empty(); + isEnd = true; + addCharToInternal(si, nonEscCache); + ++i; + break; + } + // must be a hex digit at this point + if (!isHexDigit(static_cast(si))) + { + isEscapeSequence = false; + break; + } + hexString << si; + addCharToInternal(si, nonEscCache); + ++i; + if (!hasBrace && hexString.str().size() == 4) + { + // will be finished reading \ u d_3 d_2 d_1 d_0 with no parens + isEnd = true; + break; + } + else if (hasBrace && hexString.str().size() > 5) + { + // too many digits enclosed in brace, not an escape sequence + isEscapeSequence = false; + break; + } + } + if (!isEnd) + { + // if we were interupted before ending, then this is not a valid + // escape sequence + isEscapeSequence = false; + } + } + if (isEscapeSequence) + { + Assert(!hexString.str().empty() && hexString.str().size() <= 5); + // Otherwise, we add the escaped character. + // This is guaranteed not to overflow due to the length of hstr. + uint32_t val; + hexString >> std::hex >> val; + if (val > num_codes()) + { + // Failed due to being out of range. This can happen for strings of + // the form \ u { d_4 d_3 d_2 d_1 d_0 } where d_4 is a hexidecimal not + // in the range [0-2]. + isEscapeSequence = false; + } + else + { + str.push_back(val); } - } else if ((unsigned)s[i] > 127 && useEscSequences) { - throw CVC4::Exception("Illegal String Literal: \"" + s + - "\", must use escaped sequence"); - } else { - str.push_back(convertCharToUnsignedInt(s[i])); - i++; + } + // if we did not successfully parse an escape sequence, we add back all + // characters that we cached + if (!isEscapeSequence) + { + str.insert(str.end(), nonEscCache.begin(), nonEscCache.end()); } } #ifdef CVC4_ASSERTIONS for (unsigned u : str) { - Assert(convertUnsignedIntToCode(u) < num_codes()); + Assert(u < num_codes()); } #endif return str; @@ -265,62 +275,23 @@ std::size_t String::roverlap(const String &y) const { } std::string String::toString(bool useEscSequences) const { - std::string str; + std::stringstream str; for (unsigned int i = 0; i < size(); ++i) { - unsigned char c = convertUnsignedIntToChar(d_str[i]); - if (!useEscSequences) { - str += c; - } else if (isprint(c)) { - if (c == '\\') { - str += "\\\\"; - } - // else if(c == '\"') { - // str += "\\\""; - //} - else { - str += c; - } - } else { - std::string s; - switch (c) { - case '\a': - s = "\\a"; - break; - case '\b': - s = "\\b"; - break; - case '\t': - s = "\\t"; - break; - case '\r': - s = "\\r"; - break; - case '\v': - s = "\\v"; - break; - case '\f': - s = "\\f"; - break; - case '\n': - s = "\\n"; - break; - case '\e': - s = "\\e"; - break; - default: { - std::stringstream ss; - ss << std::setfill('0') << std::setw(2) << std::hex << ((int)c); - std::string t = ss.str(); - t = t.substr(t.size() - 2, 2); - s = "\\x" + t; - // std::string s2 = static_cast( - // &(std::ostringstream() << (int)c) )->str(); - } - } - str += s; + // we always print forward slash as a code point so that it cannot + // be interpreted as specifying part of a code point, e.g. the string + // '\' + 'u' + '0' of length three. + if (isPrintable(d_str[i]) && d_str[i] != '\\' && !useEscSequences) + { + str << static_cast(d_str[i]); + } + else + { + std::stringstream ss; + ss << std::hex << d_str[i]; + str << "\\u{" << ss.str() << "}"; } } - return str; + return str.str(); } bool String::isLeq(const String &y) const @@ -331,8 +302,8 @@ bool String::isLeq(const String &y) const { return false; } - unsigned ci = convertUnsignedIntToCode(d_str[i]); - unsigned cyi = convertUnsignedIntToCode(y.d_str[i]); + unsigned ci = d_str[i]; + unsigned cyi = y.d_str[i]; if (ci > cyi) { return false; @@ -484,8 +455,21 @@ bool String::isNumber() const { bool String::isDigit(unsigned character) { - unsigned char c = convertUnsignedIntToChar(character); - return c >= '0' && c <= '9'; + // '0' to '9' + return 48 <= character && character <= 57; +} + +bool String::isHexDigit(unsigned character) +{ + // '0' to '9' or 'A' to 'F' or 'a' to 'f' + return isDigit(character) || (65 <= character && character <= 70) + || (97 <= character && character <= 102); +} + +bool String::isPrintable(unsigned character) +{ + // Unicode 0x00020 (' ') to 0x0007E ('~') + return 32 <= character && character <= 126; } size_t String::maxSize() { return std::numeric_limits::max(); } @@ -497,17 +481,6 @@ Rational String::toNumber() const return Rational(toString()); } -unsigned char String::hexToDec(unsigned char c) { - if (c >= '0' && c <= '9') { - return c - '0'; - } else if (c >= 'a' && c <= 'f') { - return c - 'a' + 10; - } else { - Assert(c >= 'A' && c <= 'F'); - return c - 'A' + 10; - } -} - std::ostream &operator<<(std::ostream &os, const String &s) { return os << "\"" << s.toString(true) << "\""; } diff --git a/src/util/regexp.h b/src/util/regexp.h index 731736f72..56fb969a3 100644 --- a/src/util/regexp.h +++ b/src/util/regexp.h @@ -36,61 +36,45 @@ namespace CVC4 { */ class CVC4_PUBLIC String { public: - /** - * The start ASCII code. In our string representation below, we represent - * characters using a vector d_str of unsigned integers. We refer to this as - * the "internal representation" for the string. - * - * We make unsigned integer 0 correspond to the 65th character ("A") in the - * ASCII alphabet to make models intuitive. In particular, say if we have - * a set of string variables that are distinct but otherwise unconstrained, - * then the model may assign them "A", "B", "C", ... - */ - static inline unsigned start_code() { return 65; } /** * This is the cardinality of the alphabet that is representable by this * class. Notice that this must be greater than or equal to the cardinality * of the alphabet that the string theory reasons about. * * This must be strictly less than std::numeric_limits::max(). + * + * As per the SMT-LIB standard for strings, we support the first 3 planes of + * Unicode characters, where 196608 = 3*16^4. */ - static inline unsigned num_codes() { return 256; } - /** - * Convert unsigned char to the unsigned used in the internal representation - * in d_str below. - */ - static unsigned convertCharToUnsignedInt(unsigned char c); - /** Convert the internal unsigned to a unsigned char. */ - static unsigned char convertUnsignedIntToChar(unsigned i); - /** Does the internal unsigned correspond to a printable character? */ - static bool isPrintable(unsigned i); - /** get the internal unsigned for ASCII code c. */ - static unsigned convertCodeToUnsignedInt(unsigned c); - /** get the ASCII code number that internal unsigned i corresponds to. */ - static unsigned convertUnsignedIntToCode(unsigned i); - + static inline unsigned num_codes() { return 196608; } /** constructors for String - * - * Internally, a CVC4::String is represented by a vector of unsigned - * integers (d_str), where the correspondence between C++ characters - * to and from unsigned integers is determined by - * by convertCharToUnsignedInt and convertUnsignedIntToChar. - * - * If useEscSequences is true, then the escape sequences in the input - * are converted to the corresponding character. This constructor may - * throw an exception if the input contains unrecognized escape sequences. - * Currently supported escape sequences are \n, \t, \v, \b, \r, \f, \a, \\, - * \x[N] where N is a hexidecimal, and octal escape sequences of the - * form \[c1]([c2]([c3])?)? where c1, c2, c3 are digits from 0 to 7. - * - * If useEscSequences is false, then the characters of the constructed - * CVC4::String correspond one-to-one with the input string. - */ + * + * Internally, a CVC4::String is represented by a vector of unsigned + * integers (d_str) representing the code points of the characters. + * + * To build a string from a C++ string, we may process escape sequences + * according to the SMT-LIB standard. In particular, if useEscSequences is + * true, we convert unicode escape sequences: + * \u d_3 d_2 d_1 d_0 + * \u{d_0} + * \u{d_1 d_0} + * \u{d_2 d_1 d_0} + * \u{d_3 d_2 d_1 d_0} + * \u{d_4 d_3 d_2 d_1 d_0} + * where d_0 ... d_4 are hexidecimal digits, to the appropriate character. + * + * If useEscSequences is false, then the characters of the constructed + * CVC4::String correspond one-to-one with the input string. + */ String() = default; explicit String(const std::string& s, bool useEscSequences = false) - : d_str(toInternal(s, useEscSequences)) {} + : d_str(toInternal(s, useEscSequences)) + { + } explicit String(const char* s, bool useEscSequences = false) - : d_str(toInternal(std::string(s), useEscSequences)) {} + : d_str(toInternal(std::string(s), useEscSequences)) + { + } explicit String(const std::vector& s); String& operator=(const String& y) { @@ -123,20 +107,16 @@ class CVC4_PUBLIC String { bool rstrncmp(const String& y, std::size_t n) const; /* toString - * Converts this string to a std::string. - * - * If useEscSequences is true, then unprintable characters - * are converted to escape sequences. The escape sequences - * \n, \t, \v, \b, \r, \f, \a, \\ are printed in this way. - * For all other unprintable characters, we print \x[N] where - * [N] is the 2 digit hexidecimal corresponding to value of - * the character. - * - * If useEscSequences is false, the returned std::string's characters - * map one-to-one with the characters in this string. - * Notice that for all std::string s, we have that - * CVC4::String( s ).toString() = s. - */ + * Converts this string to a std::string. + * + * The unprintable characters are converted to unicode escape sequences as + * described above. + * + * If useEscSequences is false, the string's printable characters are + * printed as characters. Notice that for all std::string s having only + * printable characters, we have that + * CVC4::String( s ).toString() = s. + */ std::string toString(bool useEscSequences = false) const; /** is this the empty string? */ bool empty() const { return d_str.empty(); } @@ -221,16 +201,32 @@ class CVC4_PUBLIC String { bool isNumber() const; /** Returns the corresponding rational for the text of this string. */ Rational toNumber() const; - /** get the internal unsigned representation of this string */ + /** Get the unsigned representation (code points) of this string */ const std::vector& getVec() const { return d_str; } - /** get the internal unsigned value of the first character in this string */ + /** + * Get the unsigned (code point) value of the first character in this string + */ unsigned front() const; - /** get the internal unsigned value of the last character in this string */ + /** + * Get the unsigned (code point) value of the last character in this string + */ unsigned back() const; /** is the unsigned a digit? - * The input should be the same type as the element type of d_str - */ + * + * This is true for code points between 48 ('0') and 57 ('9'). + */ static bool isDigit(unsigned character); + /** is the unsigned a hexidecimal digit? + * + * This is true for code points between 48 ('0') and 57 ('9'), code points + * between 65 ('A') and 70 ('F) and code points between 97 ('a') and 102 ('f). + */ + static bool isHexDigit(unsigned character); + /** is the unsigned a printable code point? + * + * This is true for Unicode 32 (' ') to 126 ('~'). + */ + static bool isPrintable(unsigned character); /** * Returns the maximum length of string representable by this class. @@ -238,11 +234,19 @@ class CVC4_PUBLIC String { */ static size_t maxSize(); private: - // guarded - static unsigned char hexToDec(unsigned char c); - + /** + * Helper for toInternal: add character ch to vector vec, storing a string in + * internal format. This throws an error if ch is not a printable character, + * since non-printable characters must be escaped in SMT-LIB. + */ + static void addCharToInternal(unsigned char ch, std::vector& vec); + /** + * Convert the string s to the internal format (vector of code points). + * The argument useEscSequences is whether to process unicode escape + * sequences. + */ static std::vector toInternal(const std::string& s, - bool useEscSequences = true); + bool useEscSequences); /** * Returns a negative number if *this < y, 0 if *this and y are equal and a diff --git a/test/regress/CMakeLists.txt b/test/regress/CMakeLists.txt index 8fab16b44..8382e40fc 100644 --- a/test/regress/CMakeLists.txt +++ b/test/regress/CMakeLists.txt @@ -922,6 +922,7 @@ set(regress_0_tests regress0/strings/escchar.smt2 regress0/strings/escchar_25.smt2 regress0/strings/from_code.smt2 + regress0/strings/gen-esc-seq.smt2 regress0/strings/hconst-092618.smt2 regress0/strings/idof-rewrites.smt2 regress0/strings/idof-sem.smt2 @@ -939,6 +940,8 @@ set(regress_0_tests regress0/strings/leadingzero001.smt2 regress0/strings/loop001.smt2 regress0/strings/model001.smt2 + regress0/strings/model-code-point.smt2 + regress0/strings/model-friendly.smt2 regress0/strings/ncontrib-rewrites.smt2 regress0/strings/norn-31.smt2 regress0/strings/norn-simp-rew.smt2 @@ -967,6 +970,7 @@ set(regress_0_tests regress0/strings/tolower-rrs.smt2 regress0/strings/tolower-simple.smt2 regress0/strings/type001.smt2 + regress0/strings/unicode-esc.smt2 regress0/strings/unsound-0908.smt2 regress0/strings/unsound-repl-rewrite.smt2 regress0/sygus/General_plus10.sy diff --git a/test/regress/regress0/strings/gen-esc-seq.smt2 b/test/regress/regress0/strings/gen-esc-seq.smt2 new file mode 100644 index 000000000..59f66046f --- /dev/null +++ b/test/regress/regress0/strings/gen-esc-seq.smt2 @@ -0,0 +1,9 @@ +; COMMAND-LINE: --produce-models --lang=smt2.6.1 +; EXPECT: sat +; EXPECT: ((x "\u{5c}u1000")) +(set-logic ALL) +(set-info :status sat) +(declare-const x String) +(assert (= x (str.++ "\u" "1000"))) +(check-sat) +(get-value (x)) diff --git a/test/regress/regress0/strings/model-code-point.smt2 b/test/regress/regress0/strings/model-code-point.smt2 new file mode 100644 index 000000000..1200ae704 --- /dev/null +++ b/test/regress/regress0/strings/model-code-point.smt2 @@ -0,0 +1,13 @@ +; COMMAND-LINE: --lang=smt2.6.1 --produce-models +; EXPECT: sat +; EXPECT: ((x "\u{a}")) +; EXPECT: ((y "\u{7f}")) +(set-logic ALL) +(set-info :status sat) +(declare-fun x () String) +(declare-fun y () String) +(assert (= (str.to_code x) 10)) +(assert (= (str.to_code y) 127)) +(check-sat) +(get-value (x)) +(get-value (y)) diff --git a/test/regress/regress0/strings/model-friendly.smt2 b/test/regress/regress0/strings/model-friendly.smt2 new file mode 100644 index 000000000..985ffaa62 --- /dev/null +++ b/test/regress/regress0/strings/model-friendly.smt2 @@ -0,0 +1,9 @@ +; COMMAND-LINE: --lang=smt2.6.1 --produce-models +; EXPECT: sat +; EXPECT: ((x "AAAAA")) +(set-logic ALL) +(set-info :status sat) +(declare-fun x () String) +(assert (= (str.len x) 5)) +(check-sat) +(get-value (x)) diff --git a/test/regress/regress0/strings/unicode-esc.smt2 b/test/regress/regress0/strings/unicode-esc.smt2 new file mode 100644 index 000000000..01f5f30ab --- /dev/null +++ b/test/regress/regress0/strings/unicode-esc.smt2 @@ -0,0 +1,30 @@ +; COMMAND-LINE: --strings-exp --lang=smt2.6.1 +; EXPECT: sat +(set-logic ALL) + +(assert (= "\u{14}" "\u0014")) +(assert (= "\u{00}" "\u{0}")) +(assert (= "\u0000" "\u{0}")) +(assert (= (str.len "\u1234") 1)) +(assert (= (str.len "\u{1}") 1)) +(assert (= (str.len "\u{99}") 1)) +(assert (= (str.len "\u{779}") 1)) +(assert (= (str.len "\u{0779}") 1)) +(assert (= (str.len "\u{01779}") 1)) +(assert (= (str.len "\u{001779}") 10)) +(assert (= (str.len "\u{0vv79}") 9)) +(assert (= (str.len "\u{11\u1234}") 7)) +(assert (= (str.len "\u12345") 2)) +(assert (= (str.len "\uu") 3)) +(assert (= (str.len "\u{123}\u{567}") 2)) +(assert (= (str.len "\u{0017") 7)) +(assert (= (str.len "\\u00178") 3)) +(assert (= (str.len "2\u{}") 5)) +(assert (= (str.len "\uaaaa") 1)) +(assert (= (str.len "\uAAAA") 1)) +(assert (= (str.len "\u{0AbC}") 1)) +(assert (= (str.len "\u{E}") 1)) +(assert (= (str.len "\u{44444}") 9)) +(assert (= (str.len "\u") 2)) + +(check-sat) diff --git a/test/unit/api/solver_black.h b/test/unit/api/solver_black.h index 27f5aca12..0eefde700 100644 --- a/test/unit/api/solver_black.h +++ b/test/unit/api/solver_black.h @@ -556,9 +556,9 @@ void SolverBlack::testMkString() TS_ASSERT_THROWS_NOTHING(d_solver->mkString("")); TS_ASSERT_THROWS_NOTHING(d_solver->mkString("asdfasdf")); TS_ASSERT_EQUALS(d_solver->mkString("asdf\\nasdf").toString(), - "\"asdf\\\\nasdf\""); - TS_ASSERT_EQUALS(d_solver->mkString("asdf\\nasdf", true).toString(), - "\"asdf\\nasdf\""); + "\"asdf\\u{5c}nasdf\""); + TS_ASSERT_EQUALS(d_solver->mkString("asdf\\u{005c}nasdf", true).toString(), + "\"asdf\\u{5c}nasdf\""); } void SolverBlack::testMkTerm() -- cgit v1.2.3 From a64866663f10db4ffadd2d48500cda05c4831f0e Mon Sep 17 00:00:00 2001 From: Andrew Reynolds Date: Fri, 27 Mar 2020 10:23:42 -0500 Subject: Do not require that function sorts are first class internally (#4128) This PR removes the requirement in the NodeManager that argument types to the function sort are first class. Notice that the new API does this check (as it should): https://github.com/CVC4/CVC4/blob/master/src/api/cvc4cpp.cpp#L2633 Moreover, SyGuS v2 internally requires constructing function types having arguments that are not first class (e.g. regular expression type). This is required to update the regression https://github.com/CVC4/CVC4/blob/master/test/regress/regress1/sygus/re-concat.sy to SyGuS v2. FYI @abdoo8080 . --- src/expr/node_manager.cpp | 36 ++++++++++++++++++++++++++++++ src/expr/node_manager.h | 56 +++++------------------------------------------ 2 files changed, 41 insertions(+), 51 deletions(-) diff --git a/src/expr/node_manager.cpp b/src/expr/node_manager.cpp index 5d409f748..16ffd8306 100644 --- a/src/expr/node_manager.cpp +++ b/src/expr/node_manager.cpp @@ -575,6 +575,42 @@ TypeNode NodeManager::RecTypeCache::getRecordType( NodeManager * nm, const Recor } } +TypeNode NodeManager::mkFunctionType(const std::vector& sorts) +{ + Assert(sorts.size() >= 2); + CheckArgument(!sorts[sorts.size() - 1].isFunction(), + sorts[sorts.size() - 1], + "must flatten function types"); + return mkTypeNode(kind::FUNCTION_TYPE, sorts); +} + +TypeNode NodeManager::mkPredicateType(const std::vector& sorts) +{ + Assert(sorts.size() >= 1); + std::vector sortNodes; + sortNodes.insert(sortNodes.end(), sorts.begin(), sorts.end()); + sortNodes.push_back(booleanType()); + return mkFunctionType(sortNodes); +} + +TypeNode NodeManager::mkFunctionType(const TypeNode& domain, + const TypeNode& range) +{ + std::vector sorts; + sorts.push_back(domain); + sorts.push_back(range); + return mkFunctionType(sorts); +} + +TypeNode NodeManager::mkFunctionType(const std::vector& argTypes, + const TypeNode& range) +{ + Assert(argTypes.size() >= 1); + std::vector sorts(argTypes); + sorts.push_back(range); + return mkFunctionType(sorts); +} + TypeNode NodeManager::mkTupleType(const std::vector& types) { std::vector< TypeNode > ts; Debug("tuprec-debug") << "Make tuple type : "; diff --git a/src/expr/node_manager.h b/src/expr/node_manager.h index eced00c48..2e8f40fff 100644 --- a/src/expr/node_manager.h +++ b/src/expr/node_manager.h @@ -807,7 +807,7 @@ public: * @param range the range type * @returns the functional type domain -> range */ - inline TypeNode mkFunctionType(const TypeNode& domain, const TypeNode& range); + TypeNode mkFunctionType(const TypeNode& domain, const TypeNode& range); /** * Make a function type with input types from @@ -817,8 +817,8 @@ public: * @param range the range type * @returns the functional type (argTypes[0], ..., argTypes[n]) -> range */ - inline TypeNode mkFunctionType(const std::vector& argTypes, - const TypeNode& range); + TypeNode mkFunctionType(const std::vector& argTypes, + const TypeNode& range); /** * Make a function type with input types from @@ -826,7 +826,7 @@ public: * sorts[sorts.size()-1]. sorts must have * at least 2 elements. */ - inline TypeNode mkFunctionType(const std::vector& sorts); + TypeNode mkFunctionType(const std::vector& sorts); /** * Make a predicate type with input types from @@ -834,7 +834,7 @@ public: * BOOLEAN. sorts must have at least one * element. */ - inline TypeNode mkPredicateType(const std::vector& sorts); + TypeNode mkPredicateType(const std::vector& sorts); /** * Make a tuple type with types from @@ -1086,52 +1086,6 @@ inline TypeNode NodeManager::builtinOperatorType() { return TypeNode(mkTypeConst(BUILTIN_OPERATOR_TYPE)); } -/** Make a function type from domain to range. */ -inline TypeNode NodeManager::mkFunctionType(const TypeNode& domain, const TypeNode& range) { - std::vector sorts; - sorts.push_back(domain); - sorts.push_back(range); - return mkFunctionType(sorts); -} - -inline TypeNode NodeManager::mkFunctionType(const std::vector& argTypes, const TypeNode& range) { - Assert(argTypes.size() >= 1); - std::vector sorts(argTypes); - sorts.push_back(range); - return mkFunctionType(sorts); -} - -inline TypeNode -NodeManager::mkFunctionType(const std::vector& sorts) { - Assert(sorts.size() >= 2); - std::vector sortNodes; - for (unsigned i = 0; i < sorts.size(); ++ i) { - CheckArgument(sorts[i].isFirstClass(), - sorts, - "cannot create function types for argument types that are " - "not first-class. Try option --uf-ho."); - sortNodes.push_back(sorts[i]); - } - CheckArgument(!sorts[sorts.size()-1].isFunction(), sorts[sorts.size()-1], - "must flatten function types"); - return mkTypeNode(kind::FUNCTION_TYPE, sortNodes); -} - -inline TypeNode -NodeManager::mkPredicateType(const std::vector& sorts) { - Assert(sorts.size() >= 1); - std::vector sortNodes; - for (unsigned i = 0; i < sorts.size(); ++ i) { - CheckArgument(sorts[i].isFirstClass(), - sorts, - "cannot create predicate types for argument types that are " - "not first-class. Try option --uf-ho."); - sortNodes.push_back(sorts[i]); - } - sortNodes.push_back(booleanType()); - return mkTypeNode(kind::FUNCTION_TYPE, sortNodes); -} - inline TypeNode NodeManager::mkSExprType(const std::vector& types) { std::vector typeNodes; for (unsigned i = 0; i < types.size(); ++ i) { -- cgit v1.2.3 From 4b7fc20dcce9eefdf568937f5e2c54141c4f5c5b Mon Sep 17 00:00:00 2001 From: Andrew Reynolds Date: Fri, 27 Mar 2020 14:04:44 -0500 Subject: Move string utility file (#4164) Moves the string file to string.h. This is required since other required utilities will soon need to be added to regexp.h. --- src/CMakeLists.txt | 2 +- src/cvc4.i | 2 +- src/theory/evaluator.h | 2 +- src/theory/strings/kinds | 10 +- src/theory/strings/regexp_operation.h | 3 +- src/theory/strings/regexp_solver.h | 2 +- src/theory/strings/type_enumerator.cpp | 2 +- src/theory/strings/word.cpp | 2 +- src/util/CMakeLists.txt | 4 +- src/util/regexp.cpp | 488 --------------------- src/util/regexp.h | 274 ------------ src/util/regexp.i | 25 -- src/util/string.cpp | 485 ++++++++++++++++++++ src/util/string.h | 271 ++++++++++++ src/util/string.i | 25 ++ .../theory/theory_strings_skolem_cache_black.h | 2 +- 16 files changed, 796 insertions(+), 803 deletions(-) delete mode 100644 src/util/regexp.cpp delete mode 100644 src/util/regexp.h delete mode 100644 src/util/regexp.i create mode 100644 src/util/string.cpp create mode 100644 src/util/string.h create mode 100644 src/util/string.i diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index dd58c74ee..809b00b04 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -961,11 +961,11 @@ install(FILES util/proof.h util/rational_cln_imp.h util/rational_gmp_imp.h - util/regexp.h util/resource_manager.h util/result.h util/sexpr.h util/statistics.h + util/string.h util/tuple.h util/unsafe_interrupt_exception.h ${CMAKE_CURRENT_BINARY_DIR}/util/floatingpoint.h diff --git a/src/cvc4.i b/src/cvc4.i index f9f8f5743..9dcff7f8e 100644 --- a/src/cvc4.i +++ b/src/cvc4.i @@ -274,10 +274,10 @@ std::set CVC4::JavaInputStreamAdapter::s_adapters; %include "util/cardinality.i" %include "util/hash.i" %include "util/proof.i" -%include "util/regexp.i" %include "util/result.i" %include "util/sexpr.i" %include "util/statistics.i" +%include "util/string.i" %include "util/tuple.i" %include "util/unsafe_interrupt_exception.i" diff --git a/src/theory/evaluator.h b/src/theory/evaluator.h index 58e179fbe..b9b15c6c6 100644 --- a/src/theory/evaluator.h +++ b/src/theory/evaluator.h @@ -27,7 +27,7 @@ #include "expr/node.h" #include "util/bitvector.h" #include "util/rational.h" -#include "util/regexp.h" +#include "util/string.h" namespace CVC4 { namespace theory { diff --git a/src/theory/strings/kinds b/src/theory/strings/kinds index 5b988061b..3f7abdb7c 100644 --- a/src/theory/strings/kinds +++ b/src/theory/strings/kinds @@ -37,15 +37,15 @@ operator STRING_REV 1 "string reverse" sort STRING_TYPE \ Cardinality::INTEGERS \ well-founded \ - "NodeManager::currentNM()->mkConst(::CVC4::String())" \ - "util/regexp.h" \ + "NodeManager::currentNM()->mkConst(::CVC4::String())" \ + "util/string.h" \ "String type" sort REGEXP_TYPE \ Cardinality::INTEGERS \ well-founded \ - "NodeManager::currentNM()->mkNode(REGEXP_EMPTY, std::vector() )" \ - "util/regexp.h" \ + "NodeManager::currentNM()->mkNode(REGEXP_EMPTY, std::vector() )" \ + "util/string.h" \ "RegExp type" enumerator STRING_TYPE \ @@ -55,7 +55,7 @@ enumerator STRING_TYPE \ constant CONST_STRING \ ::CVC4::String \ ::CVC4::strings::StringHashFunction \ - "util/regexp.h" \ + "util/string.h" \ "a string of characters" # equal equal / less than / output diff --git a/src/theory/strings/regexp_operation.h b/src/theory/strings/regexp_operation.h index b9dbedba5..7845b2e00 100644 --- a/src/theory/strings/regexp_operation.h +++ b/src/theory/strings/regexp_operation.h @@ -24,10 +24,9 @@ #include #include #include "util/hash.h" -#include "util/regexp.h" +#include "util/string.h" #include "theory/theory.h" #include "theory/rewriter.h" -//#include "context/cdhashmap.h" namespace CVC4 { namespace theory { diff --git a/src/theory/strings/regexp_solver.h b/src/theory/strings/regexp_solver.h index 4880af905..d18604752 100644 --- a/src/theory/strings/regexp_solver.h +++ b/src/theory/strings/regexp_solver.h @@ -27,7 +27,7 @@ #include "theory/strings/inference_manager.h" #include "theory/strings/regexp_operation.h" #include "theory/strings/solver_state.h" -#include "util/regexp.h" +#include "util/string.h" namespace CVC4 { namespace theory { diff --git a/src/theory/strings/type_enumerator.cpp b/src/theory/strings/type_enumerator.cpp index 7352ae5de..d24206860 100644 --- a/src/theory/strings/type_enumerator.cpp +++ b/src/theory/strings/type_enumerator.cpp @@ -15,7 +15,7 @@ #include "theory/strings/type_enumerator.h" #include "theory/strings/theory_strings_utils.h" -#include "util/regexp.h" +#include "util/string.h" namespace CVC4 { namespace theory { diff --git a/src/theory/strings/word.cpp b/src/theory/strings/word.cpp index dd573b68c..0faeffd99 100644 --- a/src/theory/strings/word.cpp +++ b/src/theory/strings/word.cpp @@ -14,7 +14,7 @@ #include "theory/strings/word.h" -#include "util/regexp.h" +#include "util/string.h" using namespace CVC4::kind; diff --git a/src/util/CMakeLists.txt b/src/util/CMakeLists.txt index 75597edac..6895dc01a 100644 --- a/src/util/CMakeLists.txt +++ b/src/util/CMakeLists.txt @@ -25,8 +25,6 @@ libcvc4_add_sources( proof.h random.cpp random.h - regexp.cpp - regexp.h resource_manager.cpp resource_manager.h result.cpp @@ -43,6 +41,8 @@ libcvc4_add_sources( statistics.h statistics_registry.cpp statistics_registry.h + string.cpp + string.h tuple.h unsafe_interrupt_exception.h utility.cpp diff --git a/src/util/regexp.cpp b/src/util/regexp.cpp deleted file mode 100644 index 36ba7182b..000000000 --- a/src/util/regexp.cpp +++ /dev/null @@ -1,488 +0,0 @@ -/********************* */ -/*! \file regexp.cpp - ** \verbatim - ** Top contributors (to current version): - ** Tim King, Tianyi Liang, Andrew Reynolds - ** 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 [[ Add one-line brief description here ]] - ** - ** [[ Add lengthier description here ]] - ** \todo document this file - **/ - -#include "util/regexp.h" - -#include -#include -#include -#include -#include - -#include "base/check.h" -#include "base/exception.h" - -using namespace std; - -namespace CVC4 { - -static_assert(UCHAR_MAX == 255, "Unsigned char is assumed to have 256 values."); - -String::String(const std::vector &s) : d_str(s) -{ -#ifdef CVC4_ASSERTIONS - for (unsigned u : d_str) - { - Assert(u < num_codes()); - } -#endif -} - -int String::cmp(const String &y) const { - if (size() != y.size()) { - return size() < y.size() ? -1 : 1; - } - for (unsigned int i = 0; i < size(); ++i) { - if (d_str[i] != y.d_str[i]) { - unsigned cp = d_str[i]; - unsigned cpy = y.d_str[i]; - return cp < cpy ? -1 : 1; - } - } - return 0; -} - -String String::concat(const String &other) const { - std::vector ret_vec(d_str); - ret_vec.insert(ret_vec.end(), other.d_str.begin(), other.d_str.end()); - return String(ret_vec); -} - -bool String::strncmp(const String& y, std::size_t n) const -{ - std::size_t b = (size() >= y.size()) ? size() : y.size(); - std::size_t s = (size() <= y.size()) ? size() : y.size(); - if (n > s) { - if (b == s) { - n = s; - } else { - return false; - } - } - for (std::size_t i = 0; i < n; ++i) { - if (d_str[i] != y.d_str[i]) return false; - } - return true; -} - -bool String::rstrncmp(const String& y, std::size_t n) const -{ - std::size_t b = (size() >= y.size()) ? size() : y.size(); - std::size_t s = (size() <= y.size()) ? size() : y.size(); - if (n > s) { - if (b == s) { - n = s; - } else { - return false; - } - } - for (std::size_t i = 0; i < n; ++i) { - if (d_str[size() - i - 1] != y.d_str[y.size() - i - 1]) return false; - } - return true; -} - -void String::addCharToInternal(unsigned char ch, std::vector& str) -{ - // if not a printable character - if (ch > 127 || ch < 32) - { - std::stringstream serr; - serr << "Illegal string character: \"" << ch - << "\", must use escape sequence"; - throw CVC4::Exception(serr.str()); - } - else - { - str.push_back(static_cast(ch)); - } -} - -std::vector String::toInternal(const std::string& s, - bool useEscSequences) -{ - std::vector str; - unsigned i = 0; - while (i < s.size()) - { - // get the current character - char si = s[i]; - if (si != '\\' || !useEscSequences) - { - addCharToInternal(si, str); - ++i; - continue; - } - // the vector of characters, in case we fail to read an escape sequence - std::vector nonEscCache; - // process the '\' - addCharToInternal(si, nonEscCache); - ++i; - // are we an escape sequence? - bool isEscapeSequence = true; - // the string corresponding to the hexidecimal code point - std::stringstream hexString; - // is the slash followed by a 'u'? Could be last character. - if (i >= s.size() || s[i] != 'u') - { - isEscapeSequence = false; - } - else - { - // process the 'u' - addCharToInternal(s[i], nonEscCache); - ++i; - bool isStart = true; - bool isEnd = false; - bool hasBrace = false; - while (i < s.size()) - { - // add the next character - si = s[i]; - if (isStart) - { - isStart = false; - // possibly read '{' - if (si == '{') - { - hasBrace = true; - addCharToInternal(si, nonEscCache); - ++i; - continue; - } - } - else if (si == '}') - { - // can only end if we had an open brace and read at least one digit - isEscapeSequence = hasBrace && !hexString.str().empty(); - isEnd = true; - addCharToInternal(si, nonEscCache); - ++i; - break; - } - // must be a hex digit at this point - if (!isHexDigit(static_cast(si))) - { - isEscapeSequence = false; - break; - } - hexString << si; - addCharToInternal(si, nonEscCache); - ++i; - if (!hasBrace && hexString.str().size() == 4) - { - // will be finished reading \ u d_3 d_2 d_1 d_0 with no parens - isEnd = true; - break; - } - else if (hasBrace && hexString.str().size() > 5) - { - // too many digits enclosed in brace, not an escape sequence - isEscapeSequence = false; - break; - } - } - if (!isEnd) - { - // if we were interupted before ending, then this is not a valid - // escape sequence - isEscapeSequence = false; - } - } - if (isEscapeSequence) - { - Assert(!hexString.str().empty() && hexString.str().size() <= 5); - // Otherwise, we add the escaped character. - // This is guaranteed not to overflow due to the length of hstr. - uint32_t val; - hexString >> std::hex >> val; - if (val > num_codes()) - { - // Failed due to being out of range. This can happen for strings of - // the form \ u { d_4 d_3 d_2 d_1 d_0 } where d_4 is a hexidecimal not - // in the range [0-2]. - isEscapeSequence = false; - } - else - { - str.push_back(val); - } - } - // if we did not successfully parse an escape sequence, we add back all - // characters that we cached - if (!isEscapeSequence) - { - str.insert(str.end(), nonEscCache.begin(), nonEscCache.end()); - } - } -#ifdef CVC4_ASSERTIONS - for (unsigned u : str) - { - Assert(u < num_codes()); - } -#endif - return str; -} - -unsigned String::front() const -{ - Assert(!d_str.empty()); - return d_str.front(); -} - -unsigned String::back() const -{ - Assert(!d_str.empty()); - return d_str.back(); -} - -std::size_t String::overlap(const String &y) const { - std::size_t i = size() < y.size() ? size() : y.size(); - for (; i > 0; i--) { - String s = suffix(i); - String p = y.prefix(i); - if (s == p) { - return i; - } - } - return i; -} - -std::size_t String::roverlap(const String &y) const { - std::size_t i = size() < y.size() ? size() : y.size(); - for (; i > 0; i--) { - String s = prefix(i); - String p = y.suffix(i); - if (s == p) { - return i; - } - } - return i; -} - -std::string String::toString(bool useEscSequences) const { - std::stringstream str; - for (unsigned int i = 0; i < size(); ++i) { - // we always print forward slash as a code point so that it cannot - // be interpreted as specifying part of a code point, e.g. the string - // '\' + 'u' + '0' of length three. - if (isPrintable(d_str[i]) && d_str[i] != '\\' && !useEscSequences) - { - str << static_cast(d_str[i]); - } - else - { - std::stringstream ss; - ss << std::hex << d_str[i]; - str << "\\u{" << ss.str() << "}"; - } - } - return str.str(); -} - -bool String::isLeq(const String &y) const -{ - for (unsigned i = 0; i < size(); ++i) - { - if (i >= y.size()) - { - return false; - } - unsigned ci = d_str[i]; - unsigned cyi = y.d_str[i]; - if (ci > cyi) - { - return false; - } - if (ci < cyi) - { - return true; - } - } - return true; -} - -bool String::isRepeated() const { - if (size() > 1) { - unsigned int f = d_str[0]; - for (unsigned i = 1; i < size(); ++i) { - if (f != d_str[i]) return false; - } - } - return true; -} - -bool String::tailcmp(const String &y, int &c) const { - int id_x = size() - 1; - int id_y = y.size() - 1; - while (id_x >= 0 && id_y >= 0) { - if (d_str[id_x] != y.d_str[id_y]) { - c = id_x; - return false; - } - --id_x; - --id_y; - } - c = id_x == -1 ? (-(id_y + 1)) : (id_x + 1); - return true; -} - -std::size_t String::find(const String &y, const std::size_t start) const { - if (size() < y.size() + start) return std::string::npos; - if (y.empty()) return start; - if (empty()) return std::string::npos; - - std::vector::const_iterator itr = std::search( - d_str.begin() + start, d_str.end(), y.d_str.begin(), y.d_str.end()); - if (itr != d_str.end()) { - return itr - d_str.begin(); - } - return std::string::npos; -} - -std::size_t String::rfind(const String &y, const std::size_t start) const { - if (size() < y.size() + start) return std::string::npos; - if (y.empty()) return start; - if (empty()) return std::string::npos; - - std::vector::const_reverse_iterator itr = std::search( - d_str.rbegin() + start, d_str.rend(), y.d_str.rbegin(), y.d_str.rend()); - if (itr != d_str.rend()) { - return itr - d_str.rbegin(); - } - return std::string::npos; -} - -bool String::hasPrefix(const String& y) const -{ - size_t s = size(); - size_t ys = y.size(); - if (ys > s) - { - return false; - } - for (size_t i = 0; i < ys; i++) - { - if (d_str[i] != y.d_str[i]) - { - return false; - } - } - return true; -} - -bool String::hasSuffix(const String& y) const -{ - size_t s = size(); - size_t ys = y.size(); - if (ys > s) - { - return false; - } - size_t idiff = s - ys; - for (size_t i = 0; i < ys; i++) - { - if (d_str[i + idiff] != y.d_str[i]) - { - return false; - } - } - return true; -} - -String String::replace(const String &s, const String &t) const { - std::size_t ret = find(s); - if (ret != std::string::npos) { - std::vector vec; - vec.insert(vec.begin(), d_str.begin(), d_str.begin() + ret); - vec.insert(vec.end(), t.d_str.begin(), t.d_str.end()); - vec.insert(vec.end(), d_str.begin() + ret + s.size(), d_str.end()); - return String(vec); - } else { - return *this; - } -} - -String String::substr(std::size_t i) const { - Assert(i <= size()); - std::vector ret_vec; - std::vector::const_iterator itr = d_str.begin() + i; - ret_vec.insert(ret_vec.end(), itr, d_str.end()); - return String(ret_vec); -} - -String String::substr(std::size_t i, std::size_t j) const { - Assert(i + j <= size()); - std::vector ret_vec; - std::vector::const_iterator itr = d_str.begin() + i; - ret_vec.insert(ret_vec.end(), itr, itr + j); - return String(ret_vec); -} - -bool String::noOverlapWith(const String& y) const -{ - return y.find(*this) == std::string::npos - && this->find(y) == std::string::npos && this->overlap(y) == 0 - && y.overlap(*this) == 0; -} - -bool String::isNumber() const { - if (d_str.empty()) { - return false; - } - for (unsigned character : d_str) { - if (!isDigit(character)) - { - return false; - } - } - return true; -} - -bool String::isDigit(unsigned character) -{ - // '0' to '9' - return 48 <= character && character <= 57; -} - -bool String::isHexDigit(unsigned character) -{ - // '0' to '9' or 'A' to 'F' or 'a' to 'f' - return isDigit(character) || (65 <= character && character <= 70) - || (97 <= character && character <= 102); -} - -bool String::isPrintable(unsigned character) -{ - // Unicode 0x00020 (' ') to 0x0007E ('~') - return 32 <= character && character <= 126; -} - -size_t String::maxSize() { return std::numeric_limits::max(); } - -Rational String::toNumber() const -{ - // when smt2 standard for strings is set, this may change, based on the - // semantics of str.from.int for leading zeros - return Rational(toString()); -} - -std::ostream &operator<<(std::ostream &os, const String &s) { - return os << "\"" << s.toString(true) << "\""; -} - -} // namespace CVC4 diff --git a/src/util/regexp.h b/src/util/regexp.h deleted file mode 100644 index 56fb969a3..000000000 --- a/src/util/regexp.h +++ /dev/null @@ -1,274 +0,0 @@ -/********************* */ -/*! \file regexp.h - ** \verbatim - ** Top contributors (to current version): - ** Andrew Reynolds, Tim King, Tianyi Liang - ** 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 [[ Add one-line brief description here ]] - ** - ** [[ Add lengthier description here ]] - ** \todo document this file - **/ - -#include "cvc4_public.h" - -#ifndef CVC4__REGEXP_H -#define CVC4__REGEXP_H - -#include -#include -#include -#include -#include -#include "util/rational.h" - -namespace CVC4 { - -/** The CVC4 string class - * - * This data structure is the domain of values for the string type. It can also - * be used as a generic utility for representing strings. - */ -class CVC4_PUBLIC String { - public: - /** - * This is the cardinality of the alphabet that is representable by this - * class. Notice that this must be greater than or equal to the cardinality - * of the alphabet that the string theory reasons about. - * - * This must be strictly less than std::numeric_limits::max(). - * - * As per the SMT-LIB standard for strings, we support the first 3 planes of - * Unicode characters, where 196608 = 3*16^4. - */ - static inline unsigned num_codes() { return 196608; } - /** constructors for String - * - * Internally, a CVC4::String is represented by a vector of unsigned - * integers (d_str) representing the code points of the characters. - * - * To build a string from a C++ string, we may process escape sequences - * according to the SMT-LIB standard. In particular, if useEscSequences is - * true, we convert unicode escape sequences: - * \u d_3 d_2 d_1 d_0 - * \u{d_0} - * \u{d_1 d_0} - * \u{d_2 d_1 d_0} - * \u{d_3 d_2 d_1 d_0} - * \u{d_4 d_3 d_2 d_1 d_0} - * where d_0 ... d_4 are hexidecimal digits, to the appropriate character. - * - * If useEscSequences is false, then the characters of the constructed - * CVC4::String correspond one-to-one with the input string. - */ - String() = default; - explicit String(const std::string& s, bool useEscSequences = false) - : d_str(toInternal(s, useEscSequences)) - { - } - explicit String(const char* s, bool useEscSequences = false) - : d_str(toInternal(std::string(s), useEscSequences)) - { - } - explicit String(const std::vector& s); - - String& operator=(const String& y) { - if (this != &y) { - d_str = y.d_str; - } - return *this; - } - - String concat(const String& other) const; - - bool operator==(const String& y) const { return cmp(y) == 0; } - bool operator!=(const String& y) const { return cmp(y) != 0; } - bool operator<(const String& y) const { return cmp(y) < 0; } - bool operator>(const String& y) const { return cmp(y) > 0; } - bool operator<=(const String& y) const { return cmp(y) <= 0; } - bool operator>=(const String& y) const { return cmp(y) >= 0; } - - /** - * Returns true if this string is equal to y for their first n characters. - * If n is larger than the length of this string or y, this method returns - * true if and only if this string is equal to y. - */ - bool strncmp(const String& y, std::size_t n) const; - /** - * Returns true if this string is equal to y for their last n characters. - * Similar to strncmp, if n is larger than the length of this string or y, - * this method returns true if and only if this string is equal to y. - */ - bool rstrncmp(const String& y, std::size_t n) const; - - /* toString - * Converts this string to a std::string. - * - * The unprintable characters are converted to unicode escape sequences as - * described above. - * - * If useEscSequences is false, the string's printable characters are - * printed as characters. Notice that for all std::string s having only - * printable characters, we have that - * CVC4::String( s ).toString() = s. - */ - std::string toString(bool useEscSequences = false) const; - /** is this the empty string? */ - bool empty() const { return d_str.empty(); } - /** is this the empty string? */ - bool isEmptyString() const { return empty(); } - /** is less than or equal to string y */ - bool isLeq(const String& y) const; - /** Return the length of the string */ - std::size_t size() const { return d_str.size(); } - - bool isRepeated() const; - bool tailcmp(const String& y, int& c) const; - - /** - * Return the first position y occurs in this string, or std::string::npos - * otherwise. - */ - std::size_t find(const String& y, const std::size_t start = 0) const; - /** - * Return the first position y occurs in this string searching from the end, - * or std::string::npos otherwise. - */ - std::size_t rfind(const String& y, const std::size_t start = 0) const; - /** Returns true if y is a prefix of this */ - bool hasPrefix(const String& y) const; - /** Returns true if y is a suffix of this */ - bool hasSuffix(const String& y) const; - /** Replace the first occurrence of s in this string with t */ - String replace(const String& s, const String& t) const; - /** Return the substring of this string starting at index i */ - String substr(std::size_t i) const; - /** Return the substring of this string starting at index i with size at most - * j */ - String substr(std::size_t i, std::size_t j) const; - /** Return the prefix of this string of size at most i */ - String prefix(std::size_t i) const { return substr(0, i); } - /** Return the suffix of this string of size at most i */ - String suffix(std::size_t i) const { return substr(size() - i, i); } - - /** - * Checks if there is any overlap between this string and another string. This - * corresponds to checking whether one string contains the other and whether a - * substring of one is a prefix of the other and vice-versa. - * - * @param y The other string - * @return True if there is an overlap, false otherwise - */ - bool noOverlapWith(const String& y) const; - - /** string overlap - * - * if overlap returns m>0, - * then the maximal suffix of this string that is a prefix of y is of length m. - * - * For example, if x is "abcdef", then: - * x.overlap("defg") = 3 - * x.overlap("ab") = 0 - * x.overlap("d") = 0 - * x.overlap("bcdefdef") = 5 - */ - std::size_t overlap(const String& y) const; - /** string reverse overlap - * - * if roverlap returns m>0, - * then the maximal prefix of this string that is a suffix of y is of length m. - * - * For example, if x is "abcdef", then: - * x.roverlap("aaabc") = 3 - * x.roverlap("def") = 0 - * x.roverlap("d") = 0 - * x.roverlap("defabcde") = 5 - * - * Notice that x.overlap(y) = y.roverlap(x) - */ - std::size_t roverlap(const String& y) const; - - /** - * Returns true if this string corresponds in text to a number, for example - * this returns true for strings "7", "12", "004", "0" and false for strings - * "abc", "4a", "-4", "". - */ - bool isNumber() const; - /** Returns the corresponding rational for the text of this string. */ - Rational toNumber() const; - /** Get the unsigned representation (code points) of this string */ - const std::vector& getVec() const { return d_str; } - /** - * Get the unsigned (code point) value of the first character in this string - */ - unsigned front() const; - /** - * Get the unsigned (code point) value of the last character in this string - */ - unsigned back() const; - /** is the unsigned a digit? - * - * This is true for code points between 48 ('0') and 57 ('9'). - */ - static bool isDigit(unsigned character); - /** is the unsigned a hexidecimal digit? - * - * This is true for code points between 48 ('0') and 57 ('9'), code points - * between 65 ('A') and 70 ('F) and code points between 97 ('a') and 102 ('f). - */ - static bool isHexDigit(unsigned character); - /** is the unsigned a printable code point? - * - * This is true for Unicode 32 (' ') to 126 ('~'). - */ - static bool isPrintable(unsigned character); - - /** - * Returns the maximum length of string representable by this class. - * Corresponds to the maximum size of d_str. - */ - static size_t maxSize(); - private: - /** - * Helper for toInternal: add character ch to vector vec, storing a string in - * internal format. This throws an error if ch is not a printable character, - * since non-printable characters must be escaped in SMT-LIB. - */ - static void addCharToInternal(unsigned char ch, std::vector& vec); - /** - * Convert the string s to the internal format (vector of code points). - * The argument useEscSequences is whether to process unicode escape - * sequences. - */ - static std::vector toInternal(const std::string& s, - bool useEscSequences); - - /** - * Returns a negative number if *this < y, 0 if *this and y are equal and a - * positive number if *this > y. - */ - int cmp(const String& y) const; - - std::vector d_str; -}; /* class String */ - -namespace strings { - -struct CVC4_PUBLIC StringHashFunction { - size_t operator()(const ::CVC4::String& s) const { - return std::hash()(s.toString()); - } -}; /* struct StringHashFunction */ - -} // namespace strings - -std::ostream& operator<<(std::ostream& os, const String& s) CVC4_PUBLIC; - -} // namespace CVC4 - -#endif /* CVC4__REGEXP_H */ diff --git a/src/util/regexp.i b/src/util/regexp.i deleted file mode 100644 index afc51abd7..000000000 --- a/src/util/regexp.i +++ /dev/null @@ -1,25 +0,0 @@ -%{ -#include "util/regexp.h" -%} - -%rename(CVC4String) String; -%rename(CVC4StringHashFunction) CVC4::strings::StringHashFunction; - -%ignore CVC4::String::String(const std::string&); - -%rename(assign) CVC4::String::operator=(const String&); -%rename(getChar) CVC4::String::operator[](const unsigned int) const; -%rename(equals) CVC4::String::operator==(const String&) const; -%ignore CVC4::String::operator!=(const String&) const; -%rename(less) CVC4::String::operator<(const String&) const; -%rename(lessEqual) CVC4::String::operator<=(const String&) const; -%rename(greater) CVC4::String::operator>(const String&) const; -%rename(greaterEqual) CVC4::String::operator>=(const String&) const; - -%rename(apply) CVC4::strings::StringHashFunction::operator()(const ::CVC4::String&) const; - -%ignore CVC4::operator<<(std::ostream&, const String&); - -%apply int &OUTPUT { int &c }; -%include "util/regexp.h" -%clear int &c; diff --git a/src/util/string.cpp b/src/util/string.cpp new file mode 100644 index 000000000..ff522ba7b --- /dev/null +++ b/src/util/string.cpp @@ -0,0 +1,485 @@ +/********************* */ +/*! \file string.cpp + ** \verbatim + ** Top contributors (to current version): + ** Tim King, Tianyi Liang, Andrew Reynolds + ** 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 string data type. + **/ + +#include "util/string.h" + +#include +#include +#include +#include +#include + +#include "base/check.h" +#include "base/exception.h" + +using namespace std; + +namespace CVC4 { + +static_assert(UCHAR_MAX == 255, "Unsigned char is assumed to have 256 values."); + +String::String(const std::vector &s) : d_str(s) +{ +#ifdef CVC4_ASSERTIONS + for (unsigned u : d_str) + { + Assert(u < num_codes()); + } +#endif +} + +int String::cmp(const String &y) const { + if (size() != y.size()) { + return size() < y.size() ? -1 : 1; + } + for (unsigned int i = 0; i < size(); ++i) { + if (d_str[i] != y.d_str[i]) { + unsigned cp = d_str[i]; + unsigned cpy = y.d_str[i]; + return cp < cpy ? -1 : 1; + } + } + return 0; +} + +String String::concat(const String &other) const { + std::vector ret_vec(d_str); + ret_vec.insert(ret_vec.end(), other.d_str.begin(), other.d_str.end()); + return String(ret_vec); +} + +bool String::strncmp(const String& y, std::size_t n) const +{ + std::size_t b = (size() >= y.size()) ? size() : y.size(); + std::size_t s = (size() <= y.size()) ? size() : y.size(); + if (n > s) { + if (b == s) { + n = s; + } else { + return false; + } + } + for (std::size_t i = 0; i < n; ++i) { + if (d_str[i] != y.d_str[i]) return false; + } + return true; +} + +bool String::rstrncmp(const String& y, std::size_t n) const +{ + std::size_t b = (size() >= y.size()) ? size() : y.size(); + std::size_t s = (size() <= y.size()) ? size() : y.size(); + if (n > s) { + if (b == s) { + n = s; + } else { + return false; + } + } + for (std::size_t i = 0; i < n; ++i) { + if (d_str[size() - i - 1] != y.d_str[y.size() - i - 1]) return false; + } + return true; +} + +void String::addCharToInternal(unsigned char ch, std::vector& str) +{ + // if not a printable character + if (ch > 127 || ch < 32) + { + std::stringstream serr; + serr << "Illegal string character: \"" << ch + << "\", must use escape sequence"; + throw CVC4::Exception(serr.str()); + } + else + { + str.push_back(static_cast(ch)); + } +} + +std::vector String::toInternal(const std::string& s, + bool useEscSequences) +{ + std::vector str; + unsigned i = 0; + while (i < s.size()) + { + // get the current character + char si = s[i]; + if (si != '\\' || !useEscSequences) + { + addCharToInternal(si, str); + ++i; + continue; + } + // the vector of characters, in case we fail to read an escape sequence + std::vector nonEscCache; + // process the '\' + addCharToInternal(si, nonEscCache); + ++i; + // are we an escape sequence? + bool isEscapeSequence = true; + // the string corresponding to the hexidecimal code point + std::stringstream hexString; + // is the slash followed by a 'u'? Could be last character. + if (i >= s.size() || s[i] != 'u') + { + isEscapeSequence = false; + } + else + { + // process the 'u' + addCharToInternal(s[i], nonEscCache); + ++i; + bool isStart = true; + bool isEnd = false; + bool hasBrace = false; + while (i < s.size()) + { + // add the next character + si = s[i]; + if (isStart) + { + isStart = false; + // possibly read '{' + if (si == '{') + { + hasBrace = true; + addCharToInternal(si, nonEscCache); + ++i; + continue; + } + } + else if (si == '}') + { + // can only end if we had an open brace and read at least one digit + isEscapeSequence = hasBrace && !hexString.str().empty(); + isEnd = true; + addCharToInternal(si, nonEscCache); + ++i; + break; + } + // must be a hex digit at this point + if (!isHexDigit(static_cast(si))) + { + isEscapeSequence = false; + break; + } + hexString << si; + addCharToInternal(si, nonEscCache); + ++i; + if (!hasBrace && hexString.str().size() == 4) + { + // will be finished reading \ u d_3 d_2 d_1 d_0 with no parens + isEnd = true; + break; + } + else if (hasBrace && hexString.str().size() > 5) + { + // too many digits enclosed in brace, not an escape sequence + isEscapeSequence = false; + break; + } + } + if (!isEnd) + { + // if we were interupted before ending, then this is not a valid + // escape sequence + isEscapeSequence = false; + } + } + if (isEscapeSequence) + { + Assert(!hexString.str().empty() && hexString.str().size() <= 5); + // Otherwise, we add the escaped character. + // This is guaranteed not to overflow due to the length of hstr. + uint32_t val; + hexString >> std::hex >> val; + if (val > num_codes()) + { + // Failed due to being out of range. This can happen for strings of + // the form \ u { d_4 d_3 d_2 d_1 d_0 } where d_4 is a hexidecimal not + // in the range [0-2]. + isEscapeSequence = false; + } + else + { + str.push_back(val); + } + } + // if we did not successfully parse an escape sequence, we add back all + // characters that we cached + if (!isEscapeSequence) + { + str.insert(str.end(), nonEscCache.begin(), nonEscCache.end()); + } + } +#ifdef CVC4_ASSERTIONS + for (unsigned u : str) + { + Assert(u < num_codes()); + } +#endif + return str; +} + +unsigned String::front() const +{ + Assert(!d_str.empty()); + return d_str.front(); +} + +unsigned String::back() const +{ + Assert(!d_str.empty()); + return d_str.back(); +} + +std::size_t String::overlap(const String &y) const { + std::size_t i = size() < y.size() ? size() : y.size(); + for (; i > 0; i--) { + String s = suffix(i); + String p = y.prefix(i); + if (s == p) { + return i; + } + } + return i; +} + +std::size_t String::roverlap(const String &y) const { + std::size_t i = size() < y.size() ? size() : y.size(); + for (; i > 0; i--) { + String s = prefix(i); + String p = y.suffix(i); + if (s == p) { + return i; + } + } + return i; +} + +std::string String::toString(bool useEscSequences) const { + std::stringstream str; + for (unsigned int i = 0; i < size(); ++i) { + // we always print forward slash as a code point so that it cannot + // be interpreted as specifying part of a code point, e.g. the string + // '\' + 'u' + '0' of length three. + if (isPrintable(d_str[i]) && d_str[i] != '\\' && !useEscSequences) + { + str << static_cast(d_str[i]); + } + else + { + std::stringstream ss; + ss << std::hex << d_str[i]; + str << "\\u{" << ss.str() << "}"; + } + } + return str.str(); +} + +bool String::isLeq(const String &y) const +{ + for (unsigned i = 0; i < size(); ++i) + { + if (i >= y.size()) + { + return false; + } + unsigned ci = d_str[i]; + unsigned cyi = y.d_str[i]; + if (ci > cyi) + { + return false; + } + if (ci < cyi) + { + return true; + } + } + return true; +} + +bool String::isRepeated() const { + if (size() > 1) { + unsigned int f = d_str[0]; + for (unsigned i = 1; i < size(); ++i) { + if (f != d_str[i]) return false; + } + } + return true; +} + +bool String::tailcmp(const String &y, int &c) const { + int id_x = size() - 1; + int id_y = y.size() - 1; + while (id_x >= 0 && id_y >= 0) { + if (d_str[id_x] != y.d_str[id_y]) { + c = id_x; + return false; + } + --id_x; + --id_y; + } + c = id_x == -1 ? (-(id_y + 1)) : (id_x + 1); + return true; +} + +std::size_t String::find(const String &y, const std::size_t start) const { + if (size() < y.size() + start) return std::string::npos; + if (y.empty()) return start; + if (empty()) return std::string::npos; + + std::vector::const_iterator itr = std::search( + d_str.begin() + start, d_str.end(), y.d_str.begin(), y.d_str.end()); + if (itr != d_str.end()) { + return itr - d_str.begin(); + } + return std::string::npos; +} + +std::size_t String::rfind(const String &y, const std::size_t start) const { + if (size() < y.size() + start) return std::string::npos; + if (y.empty()) return start; + if (empty()) return std::string::npos; + + std::vector::const_reverse_iterator itr = std::search( + d_str.rbegin() + start, d_str.rend(), y.d_str.rbegin(), y.d_str.rend()); + if (itr != d_str.rend()) { + return itr - d_str.rbegin(); + } + return std::string::npos; +} + +bool String::hasPrefix(const String& y) const +{ + size_t s = size(); + size_t ys = y.size(); + if (ys > s) + { + return false; + } + for (size_t i = 0; i < ys; i++) + { + if (d_str[i] != y.d_str[i]) + { + return false; + } + } + return true; +} + +bool String::hasSuffix(const String& y) const +{ + size_t s = size(); + size_t ys = y.size(); + if (ys > s) + { + return false; + } + size_t idiff = s - ys; + for (size_t i = 0; i < ys; i++) + { + if (d_str[i + idiff] != y.d_str[i]) + { + return false; + } + } + return true; +} + +String String::replace(const String &s, const String &t) const { + std::size_t ret = find(s); + if (ret != std::string::npos) { + std::vector vec; + vec.insert(vec.begin(), d_str.begin(), d_str.begin() + ret); + vec.insert(vec.end(), t.d_str.begin(), t.d_str.end()); + vec.insert(vec.end(), d_str.begin() + ret + s.size(), d_str.end()); + return String(vec); + } else { + return *this; + } +} + +String String::substr(std::size_t i) const { + Assert(i <= size()); + std::vector ret_vec; + std::vector::const_iterator itr = d_str.begin() + i; + ret_vec.insert(ret_vec.end(), itr, d_str.end()); + return String(ret_vec); +} + +String String::substr(std::size_t i, std::size_t j) const { + Assert(i + j <= size()); + std::vector ret_vec; + std::vector::const_iterator itr = d_str.begin() + i; + ret_vec.insert(ret_vec.end(), itr, itr + j); + return String(ret_vec); +} + +bool String::noOverlapWith(const String& y) const +{ + return y.find(*this) == std::string::npos + && this->find(y) == std::string::npos && this->overlap(y) == 0 + && y.overlap(*this) == 0; +} + +bool String::isNumber() const { + if (d_str.empty()) { + return false; + } + for (unsigned character : d_str) { + if (!isDigit(character)) + { + return false; + } + } + return true; +} + +bool String::isDigit(unsigned character) +{ + // '0' to '9' + return 48 <= character && character <= 57; +} + +bool String::isHexDigit(unsigned character) +{ + // '0' to '9' or 'A' to 'F' or 'a' to 'f' + return isDigit(character) || (65 <= character && character <= 70) + || (97 <= character && character <= 102); +} + +bool String::isPrintable(unsigned character) +{ + // Unicode 0x00020 (' ') to 0x0007E ('~') + return 32 <= character && character <= 126; +} + +size_t String::maxSize() { return std::numeric_limits::max(); } + +Rational String::toNumber() const +{ + // when smt2 standard for strings is set, this may change, based on the + // semantics of str.from.int for leading zeros + return Rational(toString()); +} + +std::ostream &operator<<(std::ostream &os, const String &s) { + return os << "\"" << s.toString(true) << "\""; +} + +} // namespace CVC4 diff --git a/src/util/string.h b/src/util/string.h new file mode 100644 index 000000000..032105812 --- /dev/null +++ b/src/util/string.h @@ -0,0 +1,271 @@ +/********************* */ +/*! \file string.h + ** \verbatim + ** Top contributors (to current version): + ** Andrew Reynolds, Tim King, Tianyi Liang + ** 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 The string data type. + **/ + +#include "cvc4_public.h" + +#ifndef CVC4__UTIL__STRING_H +#define CVC4__UTIL__STRING_H + +#include +#include +#include +#include +#include +#include "util/rational.h" + +namespace CVC4 { + +/** The CVC4 string class + * + * This data structure is the domain of values for the string type. It can also + * be used as a generic utility for representing strings. + */ +class CVC4_PUBLIC String { + public: + /** + * This is the cardinality of the alphabet that is representable by this + * class. Notice that this must be greater than or equal to the cardinality + * of the alphabet that the string theory reasons about. + * + * This must be strictly less than std::numeric_limits::max(). + * + * As per the SMT-LIB standard for strings, we support the first 3 planes of + * Unicode characters, where 196608 = 3*16^4. + */ + static inline unsigned num_codes() { return 196608; } + /** constructors for String + * + * Internally, a CVC4::String is represented by a vector of unsigned + * integers (d_str) representing the code points of the characters. + * + * To build a string from a C++ string, we may process escape sequences + * according to the SMT-LIB standard. In particular, if useEscSequences is + * true, we convert unicode escape sequences: + * \u d_3 d_2 d_1 d_0 + * \u{d_0} + * \u{d_1 d_0} + * \u{d_2 d_1 d_0} + * \u{d_3 d_2 d_1 d_0} + * \u{d_4 d_3 d_2 d_1 d_0} + * where d_0 ... d_4 are hexidecimal digits, to the appropriate character. + * + * If useEscSequences is false, then the characters of the constructed + * CVC4::String correspond one-to-one with the input string. + */ + String() = default; + explicit String(const std::string& s, bool useEscSequences = false) + : d_str(toInternal(s, useEscSequences)) + { + } + explicit String(const char* s, bool useEscSequences = false) + : d_str(toInternal(std::string(s), useEscSequences)) + { + } + explicit String(const std::vector& s); + + String& operator=(const String& y) { + if (this != &y) { + d_str = y.d_str; + } + return *this; + } + + String concat(const String& other) const; + + bool operator==(const String& y) const { return cmp(y) == 0; } + bool operator!=(const String& y) const { return cmp(y) != 0; } + bool operator<(const String& y) const { return cmp(y) < 0; } + bool operator>(const String& y) const { return cmp(y) > 0; } + bool operator<=(const String& y) const { return cmp(y) <= 0; } + bool operator>=(const String& y) const { return cmp(y) >= 0; } + + /** + * Returns true if this string is equal to y for their first n characters. + * If n is larger than the length of this string or y, this method returns + * true if and only if this string is equal to y. + */ + bool strncmp(const String& y, std::size_t n) const; + /** + * Returns true if this string is equal to y for their last n characters. + * Similar to strncmp, if n is larger than the length of this string or y, + * this method returns true if and only if this string is equal to y. + */ + bool rstrncmp(const String& y, std::size_t n) const; + + /* toString + * Converts this string to a std::string. + * + * The unprintable characters are converted to unicode escape sequences as + * described above. + * + * If useEscSequences is false, the string's printable characters are + * printed as characters. Notice that for all std::string s having only + * printable characters, we have that + * CVC4::String( s ).toString() = s. + */ + std::string toString(bool useEscSequences = false) const; + /** is this the empty string? */ + bool empty() const { return d_str.empty(); } + /** is this the empty string? */ + bool isEmptyString() const { return empty(); } + /** is less than or equal to string y */ + bool isLeq(const String& y) const; + /** Return the length of the string */ + std::size_t size() const { return d_str.size(); } + + bool isRepeated() const; + bool tailcmp(const String& y, int& c) const; + + /** + * Return the first position y occurs in this string, or std::string::npos + * otherwise. + */ + std::size_t find(const String& y, const std::size_t start = 0) const; + /** + * Return the first position y occurs in this string searching from the end, + * or std::string::npos otherwise. + */ + std::size_t rfind(const String& y, const std::size_t start = 0) const; + /** Returns true if y is a prefix of this */ + bool hasPrefix(const String& y) const; + /** Returns true if y is a suffix of this */ + bool hasSuffix(const String& y) const; + /** Replace the first occurrence of s in this string with t */ + String replace(const String& s, const String& t) const; + /** Return the substring of this string starting at index i */ + String substr(std::size_t i) const; + /** Return the substring of this string starting at index i with size at most + * j */ + String substr(std::size_t i, std::size_t j) const; + /** Return the prefix of this string of size at most i */ + String prefix(std::size_t i) const { return substr(0, i); } + /** Return the suffix of this string of size at most i */ + String suffix(std::size_t i) const { return substr(size() - i, i); } + + /** + * Checks if there is any overlap between this string and another string. This + * corresponds to checking whether one string contains the other and whether a + * substring of one is a prefix of the other and vice-versa. + * + * @param y The other string + * @return True if there is an overlap, false otherwise + */ + bool noOverlapWith(const String& y) const; + + /** string overlap + * + * if overlap returns m>0, + * then the maximal suffix of this string that is a prefix of y is of length m. + * + * For example, if x is "abcdef", then: + * x.overlap("defg") = 3 + * x.overlap("ab") = 0 + * x.overlap("d") = 0 + * x.overlap("bcdefdef") = 5 + */ + std::size_t overlap(const String& y) const; + /** string reverse overlap + * + * if roverlap returns m>0, + * then the maximal prefix of this string that is a suffix of y is of length m. + * + * For example, if x is "abcdef", then: + * x.roverlap("aaabc") = 3 + * x.roverlap("def") = 0 + * x.roverlap("d") = 0 + * x.roverlap("defabcde") = 5 + * + * Notice that x.overlap(y) = y.roverlap(x) + */ + std::size_t roverlap(const String& y) const; + + /** + * Returns true if this string corresponds in text to a number, for example + * this returns true for strings "7", "12", "004", "0" and false for strings + * "abc", "4a", "-4", "". + */ + bool isNumber() const; + /** Returns the corresponding rational for the text of this string. */ + Rational toNumber() const; + /** Get the unsigned representation (code points) of this string */ + const std::vector& getVec() const { return d_str; } + /** + * Get the unsigned (code point) value of the first character in this string + */ + unsigned front() const; + /** + * Get the unsigned (code point) value of the last character in this string + */ + unsigned back() const; + /** is the unsigned a digit? + * + * This is true for code points between 48 ('0') and 57 ('9'). + */ + static bool isDigit(unsigned character); + /** is the unsigned a hexidecimal digit? + * + * This is true for code points between 48 ('0') and 57 ('9'), code points + * between 65 ('A') and 70 ('F) and code points between 97 ('a') and 102 ('f). + */ + static bool isHexDigit(unsigned character); + /** is the unsigned a printable code point? + * + * This is true for Unicode 32 (' ') to 126 ('~'). + */ + static bool isPrintable(unsigned character); + + /** + * Returns the maximum length of string representable by this class. + * Corresponds to the maximum size of d_str. + */ + static size_t maxSize(); + private: + /** + * Helper for toInternal: add character ch to vector vec, storing a string in + * internal format. This throws an error if ch is not a printable character, + * since non-printable characters must be escaped in SMT-LIB. + */ + static void addCharToInternal(unsigned char ch, std::vector& vec); + /** + * Convert the string s to the internal format (vector of code points). + * The argument useEscSequences is whether to process unicode escape + * sequences. + */ + static std::vector toInternal(const std::string& s, + bool useEscSequences); + + /** + * Returns a negative number if *this < y, 0 if *this and y are equal and a + * positive number if *this > y. + */ + int cmp(const String& y) const; + + std::vector d_str; +}; /* class String */ + +namespace strings { + +struct CVC4_PUBLIC StringHashFunction { + size_t operator()(const ::CVC4::String& s) const { + return std::hash()(s.toString()); + } +}; /* struct StringHashFunction */ + +} // namespace strings + +std::ostream& operator<<(std::ostream& os, const String& s) CVC4_PUBLIC; + +} // namespace CVC4 + +#endif /* CVC4__UTIL__STRING_H */ diff --git a/src/util/string.i b/src/util/string.i new file mode 100644 index 000000000..1ded901aa --- /dev/null +++ b/src/util/string.i @@ -0,0 +1,25 @@ +%{ +#include "util/string.h" +%} + +%rename(CVC4String) String; +%rename(CVC4StringHashFunction) CVC4::strings::StringHashFunction; + +%ignore CVC4::String::String(const std::string&); + +%rename(assign) CVC4::String::operator=(const String&); +%rename(getChar) CVC4::String::operator[](const unsigned int) const; +%rename(equals) CVC4::String::operator==(const String&) const; +%ignore CVC4::String::operator!=(const String&) const; +%rename(less) CVC4::String::operator<(const String&) const; +%rename(lessEqual) CVC4::String::operator<=(const String&) const; +%rename(greater) CVC4::String::operator>(const String&) const; +%rename(greaterEqual) CVC4::String::operator>=(const String&) const; + +%rename(apply) CVC4::strings::StringHashFunction::operator()(const ::CVC4::String&) const; + +%ignore CVC4::operator<<(std::ostream&, const String&); + +%apply int &OUTPUT { int &c }; +%include "util/string.h" +%clear int &c; diff --git a/test/unit/theory/theory_strings_skolem_cache_black.h b/test/unit/theory/theory_strings_skolem_cache_black.h index 34e8d88c6..e1b84492c 100644 --- a/test/unit/theory/theory_strings_skolem_cache_black.h +++ b/test/unit/theory/theory_strings_skolem_cache_black.h @@ -20,7 +20,7 @@ #include "expr/node_manager.h" #include "theory/strings/skolem_cache.h" #include "util/rational.h" -#include "util/regexp.h" +#include "util/string.h" using namespace CVC4; using namespace CVC4::theory::strings; -- cgit v1.2.3 From ea1f107a92f22961a50fbc51d93780f89cbd66e0 Mon Sep 17 00:00:00 2001 From: Andrew Reynolds Date: Fri, 27 Mar 2020 17:45:30 -0500 Subject: Fix expected output on arith regression (#4162) A benchmark went unknown -> sat, likely due to the arith-brab commit, thus leading to a failure on regress1.This updates the status on this benchmark (also adds --nl-ext-tplanes to it). --- test/regress/regress1/arith/bug547.1.smt2 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/regress/regress1/arith/bug547.1.smt2 b/test/regress/regress1/arith/bug547.1.smt2 index 4b7cf9780..38d1dfcb1 100644 --- a/test/regress/regress1/arith/bug547.1.smt2 +++ b/test/regress/regress1/arith/bug547.1.smt2 @@ -1,5 +1,5 @@ -; COMMAND-LINE: --rewrite-divk -; EXPECT: unknown +; COMMAND-LINE: --rewrite-divk --nl-ext-tplanes +; EXPECT: sat (set-logic QF_NIA) (declare-fun x () Int) (declare-fun y () Int) -- cgit v1.2.3 From 97f1e4592b617a5682a8e990b4f82d3cbb6ee037 Mon Sep 17 00:00:00 2001 From: Andres Noetzli Date: Fri, 27 Mar 2020 16:37:14 -0700 Subject: Fix issues with unsat cores and reset-assertions (#4159) Fixes #4151. Commit e9f4cec2cad02e270747759223090c16b9d2d44c fixed how `(reset-assertions)` is handled by destroying and recreating the `PropEngine` owned by `SmtEngine`. When unsat cores are enabled, creating a `PropEngine` triggers the creation of a SAT proof and a CNF proof. In the `ProofManager`, we had assertions that checked that those kinds of proofs were only created once, which is not true anymore. This commit removes the assertions, cleans up the memory management in `ProofManager` to use `std::unique_ptr` and makes all the `ProofManager::init*` methods non-static for consistency. The commit also fixes an additional issue that I encountered while testing the fix: When creating the new `PropEngine`, we were not asserting `true` and `(not false)`, which lead to an error if we tried to get the unsat core after a `(reset-assertion)` command and we had asserted `(assert false)`. The commit fixes this by asserting `true` and `(not false)` in the constructor of `PropEngine`. The regression test is an extension of the example in #4151 and covers both issues. --- src/proof/proof_manager.cpp | 83 +++++++++++++++-------------- src/proof/proof_manager.h | 13 +++-- src/prop/prop_engine.cpp | 5 ++ src/smt/smt_engine.cpp | 3 -- test/regress/CMakeLists.txt | 1 + test/regress/regress0/smtlib/issue4151.smt2 | 13 +++++ 6 files changed, 67 insertions(+), 51 deletions(-) create mode 100644 test/regress/regress0/smtlib/issue4151.smt2 diff --git a/src/proof/proof_manager.cpp b/src/proof/proof_manager.cpp index 14556708b..f9e3293fa 100644 --- a/src/proof/proof_manager.cpp +++ b/src/proof/proof_manager.cpp @@ -60,9 +60,9 @@ std::string append(const std::string& str, uint64_t num) { ProofManager::ProofManager(context::Context* context, ProofFormat format) : d_context(context), - d_satProof(NULL), - d_cnfProof(NULL), - d_theoryProof(NULL), + d_satProof(nullptr), + d_cnfProof(nullptr), + d_theoryProof(nullptr), d_inputFormulas(), d_inputCoreFormulas(context), d_outputCoreFormulas(context), @@ -73,11 +73,7 @@ ProofManager::ProofManager(context::Context* context, ProofFormat format) { } -ProofManager::~ProofManager() { - if (d_satProof) delete d_satProof; - if (d_cnfProof) delete d_cnfProof; - if (d_theoryProof) delete d_theoryProof; -} +ProofManager::~ProofManager() {} ProofManager* ProofManager::currentPM() { return smt::currentProofManager(); @@ -89,26 +85,29 @@ const Proof& ProofManager::getProof(SmtEngine* smt) Assert(currentPM()->d_format == LFSC); currentPM()->d_fullProof.reset(new LFSCProof( smt, - static_cast(getSatProof()), + getSatProof(), static_cast(getCnfProof()), static_cast(getTheoryProofEngine()))); } return *(currentPM()->d_fullProof); } -CoreSatProof* ProofManager::getSatProof() { +CoreSatProof* ProofManager::getSatProof() +{ Assert(currentPM()->d_satProof); - return currentPM()->d_satProof; + return currentPM()->d_satProof.get(); } -CnfProof* ProofManager::getCnfProof() { +CnfProof* ProofManager::getCnfProof() +{ Assert(currentPM()->d_cnfProof); - return currentPM()->d_cnfProof; + return currentPM()->d_cnfProof.get(); } -TheoryProofEngine* ProofManager::getTheoryProofEngine() { +TheoryProofEngine* ProofManager::getTheoryProofEngine() +{ Assert(currentPM()->d_theoryProof != NULL); - return currentPM()->d_theoryProof; + return currentPM()->d_theoryProof.get(); } UFProof* ProofManager::getUfProof() { @@ -141,43 +140,45 @@ SkolemizationManager* ProofManager::getSkolemizationManager() { return &(currentPM()->d_skolemizationManager); } -void ProofManager::initSatProof(Minisat::Solver* solver) { - Assert(currentPM()->d_satProof == NULL); - Assert(currentPM()->d_format == LFSC); - currentPM()->d_satProof = new CoreSatProof(solver, d_context, ""); +void ProofManager::initSatProof(Minisat::Solver* solver) +{ + Assert(d_format == LFSC); + // Destroy old instance before initializing new one to avoid issues with + // registering stats + d_satProof.reset(); + d_satProof.reset(new CoreSatProof(solver, d_context, "")); } void ProofManager::initCnfProof(prop::CnfStream* cnfStream, - context::Context* ctx) { - ProofManager* pm = currentPM(); - Assert(pm->d_satProof != NULL); - Assert(pm->d_cnfProof == NULL); - Assert(pm->d_format == LFSC); - CnfProof* cnf = new LFSCCnfProof(cnfStream, ctx, ""); - pm->d_cnfProof = cnf; + context::Context* ctx) +{ + Assert(d_satProof != nullptr); + Assert(d_format == LFSC); + + d_cnfProof.reset(new LFSCCnfProof(cnfStream, ctx, "")); // true and false have to be setup in a special way Node true_node = NodeManager::currentNM()->mkConst(true); Node false_node = NodeManager::currentNM()->mkConst(false).notNode(); - pm->d_cnfProof->pushCurrentAssertion(true_node); - pm->d_cnfProof->pushCurrentDefinition(true_node); - pm->d_cnfProof->registerConvertedClause(pm->d_satProof->getTrueUnit()); - pm->d_cnfProof->popCurrentAssertion(); - pm->d_cnfProof->popCurrentDefinition(); - - pm->d_cnfProof->pushCurrentAssertion(false_node); - pm->d_cnfProof->pushCurrentDefinition(false_node); - pm->d_cnfProof->registerConvertedClause(pm->d_satProof->getFalseUnit()); - pm->d_cnfProof->popCurrentAssertion(); - pm->d_cnfProof->popCurrentDefinition(); + d_cnfProof->pushCurrentAssertion(true_node); + d_cnfProof->pushCurrentDefinition(true_node); + d_cnfProof->registerConvertedClause(d_satProof->getTrueUnit()); + d_cnfProof->popCurrentAssertion(); + d_cnfProof->popCurrentDefinition(); + d_cnfProof->pushCurrentAssertion(false_node); + d_cnfProof->pushCurrentDefinition(false_node); + d_cnfProof->registerConvertedClause(d_satProof->getFalseUnit()); + d_cnfProof->popCurrentAssertion(); + d_cnfProof->popCurrentDefinition(); } -void ProofManager::initTheoryProofEngine() { - Assert(currentPM()->d_theoryProof == NULL); - Assert(currentPM()->d_format == LFSC); - currentPM()->d_theoryProof = new LFSCTheoryProofEngine(); +void ProofManager::initTheoryProofEngine() +{ + Assert(d_theoryProof == NULL); + Assert(d_format == LFSC); + d_theoryProof.reset(new LFSCTheoryProofEngine()); } std::string ProofManager::getInputClauseName(ClauseId id, diff --git a/src/proof/proof_manager.h b/src/proof/proof_manager.h index ec845e41d..a59f36858 100644 --- a/src/proof/proof_manager.h +++ b/src/proof/proof_manager.h @@ -143,9 +143,9 @@ private: class ProofManager { context::Context* d_context; - CoreSatProof* d_satProof; - CnfProof* d_cnfProof; - TheoryProofEngine* d_theoryProof; + std::unique_ptr d_satProof; + std::unique_ptr d_cnfProof; + std::unique_ptr d_theoryProof; // information that will need to be shared across proofs ExprSet d_inputFormulas; @@ -179,10 +179,9 @@ public: static ProofManager* currentPM(); // initialization - void initSatProof(Minisat::Solver* solver); - static void initCnfProof(CVC4::prop::CnfStream* cnfStream, - context::Context* ctx); - static void initTheoryProofEngine(); + void initSatProof(Minisat::Solver* solver); + void initCnfProof(CVC4::prop::CnfStream* cnfStream, context::Context* ctx); + void initTheoryProofEngine(); // getting various proofs static const Proof& getProof(SmtEngine* smt); diff --git a/src/prop/prop_engine.cpp b/src/prop/prop_engine.cpp index 19ee29191..2436aed04 100644 --- a/src/prop/prop_engine.cpp +++ b/src/prop/prop_engine.cpp @@ -115,6 +115,11 @@ PropEngine::PropEngine(TheoryEngine* te, PROOF ( ProofManager::currentPM()->initCnfProof(d_cnfStream, userContext); ); + + NodeManager* nm = NodeManager::currentNM(); + d_cnfStream->convertAndAssert(nm->mkConst(true), false, false, RULE_GIVEN); + d_cnfStream->convertAndAssert( + nm->mkConst(false).notNode(), false, false, RULE_GIVEN); } PropEngine::~PropEngine() { diff --git a/src/smt/smt_engine.cpp b/src/smt/smt_engine.cpp index 299cc357b..30c1cd0f5 100644 --- a/src/smt/smt_engine.cpp +++ b/src/smt/smt_engine.cpp @@ -1017,9 +1017,6 @@ void SmtEngine::finalOptionsAreSet() { d_fullyInited = true; Assert(d_logic.isLocked()); - - d_propEngine->assertFormula(NodeManager::currentNM()->mkConst(true)); - d_propEngine->assertFormula(NodeManager::currentNM()->mkConst(false).notNode()); } void SmtEngine::shutdown() { diff --git a/test/regress/CMakeLists.txt b/test/regress/CMakeLists.txt index 8382e40fc..a5acd62fb 100644 --- a/test/regress/CMakeLists.txt +++ b/test/regress/CMakeLists.txt @@ -902,6 +902,7 @@ set(regress_0_tests regress0/smtlib/global-decls.smt2 regress0/smtlib/issue4028.smt2 regress0/smtlib/issue4077.smt2 + regress0/smtlib/issue4151.smt2 regress0/smtlib/reason-unknown.smt2 regress0/smtlib/reset.smt2 regress0/smtlib/reset-assertions1.smt2 diff --git a/test/regress/regress0/smtlib/issue4151.smt2 b/test/regress/regress0/smtlib/issue4151.smt2 new file mode 100644 index 000000000..629ec48b6 --- /dev/null +++ b/test/regress/regress0/smtlib/issue4151.smt2 @@ -0,0 +1,13 @@ +; EXPECT: sat +; EXPECT: unsat +; EXPECT: ( +; EXPECT: ) +(set-logic ALL) +(set-option :incremental true) +(set-option :produce-unsat-assumptions true) +(set-option :produce-unsat-cores true) +(check-sat) +(reset-assertions) +(assert false) +(check-sat) +(get-unsat-core) -- cgit v1.2.3 From 8ee4da5904e15c7900109a82ec126ce87715e548 Mon Sep 17 00:00:00 2001 From: Andrew Reynolds Date: Fri, 27 Mar 2020 19:53:59 -0500 Subject: Split transcendental solver to its own file (#4156) * Attempting to split transcendental function solver * Clean * Format * More * Format * Attempt link * Format * Fix * Another refactor * More * More * Rename * Format Co-authored-by: Ahmed Irfan <43099566+ahmed-irfan@users.noreply.github.com> Co-authored-by: Aina Niemetz --- src/CMakeLists.txt | 3 + src/theory/arith/arith_utilities.cpp | 6 + src/theory/arith/arith_utilities.h | 3 + src/theory/arith/nl_lemma_utils.cpp | 63 ++ src/theory/arith/nl_lemma_utils.h | 52 + src/theory/arith/nonlinear_extension.cpp | 1506 +--------------------------- src/theory/arith/nonlinear_extension.h | 333 +----- src/theory/arith/transcendental_solver.cpp | 1475 +++++++++++++++++++++++++++ src/theory/arith/transcendental_solver.h | 419 ++++++++ 9 files changed, 2051 insertions(+), 1809 deletions(-) create mode 100644 src/theory/arith/nl_lemma_utils.cpp create mode 100644 src/theory/arith/transcendental_solver.cpp create mode 100644 src/theory/arith/transcendental_solver.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 809b00b04..760c3fba7 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -293,6 +293,7 @@ libcvc4_add_sources( theory/arith/linear_equality.h theory/arith/matrix.cpp theory/arith/matrix.h + theory/arith/nl_lemma_utils.cpp theory/arith/nl_lemma_utils.h theory/arith/nl_model.cpp theory/arith/nl_model.h @@ -318,6 +319,8 @@ libcvc4_add_sources( theory/arith/theory_arith_private.h theory/arith/theory_arith_private_forward.h theory/arith/theory_arith_type_rules.h + theory/arith/transcendental_solver.cpp + theory/arith/transcendental_solver.h theory/arith/type_enumerator.h theory/arrays/array_info.cpp theory/arrays/array_info.h diff --git a/src/theory/arith/arith_utilities.cpp b/src/theory/arith/arith_utilities.cpp index cbb27197f..cb8524a58 100644 --- a/src/theory/arith/arith_utilities.cpp +++ b/src/theory/arith/arith_utilities.cpp @@ -272,6 +272,12 @@ Node arithSubstitute(Node n, std::vector& vars, std::vector& subs) return visited[n]; } +Node mkBounded(Node l, Node a, Node u) +{ + NodeManager* nm = NodeManager::currentNM(); + return nm->mkNode(AND, nm->mkNode(GEQ, a, l), nm->mkNode(LEQ, a, u)); +} + } // namespace arith } // namespace theory } // namespace CVC4 diff --git a/src/theory/arith/arith_utilities.h b/src/theory/arith/arith_utilities.h index f87a908b4..2d466f52f 100644 --- a/src/theory/arith/arith_utilities.h +++ b/src/theory/arith/arith_utilities.h @@ -335,6 +335,9 @@ void printRationalApprox(const char* c, Node cr, unsigned prec = 5); */ Node arithSubstitute(Node n, std::vector& vars, std::vector& subs); +/** Make the node u >= a ^ a >= l */ +Node mkBounded(Node l, Node a, Node u); + }/* CVC4::theory::arith namespace */ }/* CVC4::theory namespace */ }/* CVC4 namespace */ diff --git a/src/theory/arith/nl_lemma_utils.cpp b/src/theory/arith/nl_lemma_utils.cpp new file mode 100644 index 000000000..e43a77b06 --- /dev/null +++ b/src/theory/arith/nl_lemma_utils.cpp @@ -0,0 +1,63 @@ +/********************* */ +/*! \file nl_lemma_utils.cpp + ** \verbatim + ** Top contributors (to current version): + ** Andrew Reynolds + ** 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 utilities for the non-linear solver + **/ + +#include "theory/arith/nl_lemma_utils.h" + +#include "theory/arith/nl_model.h" + +namespace CVC4 { +namespace theory { +namespace arith { + +bool SortNlModel::operator()(Node i, Node j) +{ + int cv = d_nlm->compare(i, j, d_isConcrete, d_isAbsolute); + if (cv == 0) + { + return i < j; + } + return d_reverse_order ? cv < 0 : cv > 0; +} + +bool SortNonlinearDegree::operator()(Node i, Node j) +{ + unsigned i_count = getDegree(i); + unsigned j_count = getDegree(j); + return i_count == j_count ? (i < j) : (i_count < j_count ? true : false); +} + +unsigned SortNonlinearDegree::getDegree(Node n) const +{ + std::map::const_iterator it = d_mdegree.find(n); + Assert(it != d_mdegree.end()); + return it->second; +} + +Node ArgTrie::add(Node d, const std::vector& args) +{ + ArgTrie* at = this; + for (const Node& a : args) + { + at = &(at->d_children[a]); + } + if (at->d_data.isNull()) + { + at->d_data = d; + } + return at->d_data; +} + +} // namespace arith +} // namespace theory +} // namespace CVC4 diff --git a/src/theory/arith/nl_lemma_utils.h b/src/theory/arith/nl_lemma_utils.h index 74886a1fb..9ad9f2ca5 100644 --- a/src/theory/arith/nl_lemma_utils.h +++ b/src/theory/arith/nl_lemma_utils.h @@ -23,6 +23,8 @@ namespace CVC4 { namespace theory { namespace arith { +class NlModel; + /** * A side effect of adding a lemma in the non-linear solver. This is used * to specify how the state of the non-linear solver should update. This @@ -46,6 +48,56 @@ struct NlLemmaSideEffect std::vector > d_secantPoint; }; +struct SortNlModel +{ + SortNlModel() + : d_nlm(nullptr), + d_isConcrete(true), + d_isAbsolute(false), + d_reverse_order(false) + { + } + /** pointer to the model */ + NlModel* d_nlm; + /** are we comparing concrete model values? */ + bool d_isConcrete; + /** are we comparing absolute values? */ + bool d_isAbsolute; + /** are we in reverse order? */ + bool d_reverse_order; + /** the comparison */ + bool operator()(Node i, Node j); +}; + +struct SortNonlinearDegree +{ + SortNonlinearDegree(std::map& m) : d_mdegree(m) {} + /** pointer to the non-linear extension */ + std::map& d_mdegree; + /** Get the degree of n in d_mdegree */ + unsigned getDegree(Node n) const; + /** + * Sorts by degree of the monomials, where lower degree monomials come + * first. + */ + bool operator()(Node i, Node j); +}; + +/** An argument trie, for computing congruent terms */ +class ArgTrie +{ + public: + /** children of this node */ + std::map d_children; + /** the data of this node */ + Node d_data; + /** + * Set d as the data on the node whose path is [args], return either d if + * that node has no data, or the data that already occurs there. + */ + Node add(Node d, const std::vector& args); +}; + } // namespace arith } // namespace theory } // namespace CVC4 diff --git a/src/theory/arith/nonlinear_extension.cpp b/src/theory/arith/nonlinear_extension.cpp index f11d55855..61a8b18b0 100644 --- a/src/theory/arith/nonlinear_extension.cpp +++ b/src/theory/arith/nonlinear_extension.cpp @@ -113,49 +113,6 @@ void debugPrintBound(const char* c, Node coeff, Node x, Kind type, Node rhs) { Trace(c) << t << " " << type << " " << rhs; } -struct SortNlModel -{ - SortNlModel() - : d_nlm(nullptr), - d_isConcrete(true), - d_isAbsolute(false), - d_reverse_order(false) - { - } - /** pointer to the model */ - NlModel* d_nlm; - /** are we comparing concrete model values? */ - bool d_isConcrete; - /** are we comparing absolute values? */ - bool d_isAbsolute; - /** are we in reverse order? */ - bool d_reverse_order; - /** the comparison */ - bool operator()(Node i, Node j) { - int cv = d_nlm->compare(i, j, d_isConcrete, d_isAbsolute); - if (cv == 0) { - return i < j; - } - return d_reverse_order ? cv < 0 : cv > 0; - } -}; -struct SortNonlinearDegree -{ - SortNonlinearDegree(NodeMultiset& m) : d_mdegree(m) {} - /** pointer to the non-linear extension */ - NodeMultiset& d_mdegree; - /** - * Sorts by degree of the monomials, where lower degree monomials come - * first. - */ - bool operator()(Node i, Node j) - { - unsigned i_count = getCount(d_mdegree, i); - unsigned j_count = getCount(d_mdegree, j); - return i_count == j_count ? (i < j) : (i_count < j_count ? true : false); - } -}; - bool hasNewMonomials(Node n, const std::vector& existing) { std::set visited; @@ -189,6 +146,7 @@ NonlinearExtension::NonlinearExtension(TheoryArith& containing, d_ee(ee), d_needsLastCall(false), d_model(containing.getSatContext()), + d_trSlv(d_model), d_builtModel(containing.getSatContext(), false) { d_true = NodeManager::currentNM()->mkConst(true); @@ -200,13 +158,6 @@ NonlinearExtension::NonlinearExtension(TheoryArith& containing, d_order_points.push_back(d_neg_one); d_order_points.push_back(d_zero); d_order_points.push_back(d_one); - d_taylor_real_fv = NodeManager::currentNM()->mkBoundVar( - "x", NodeManager::currentNM()->realType()); - d_taylor_real_fv_base = NodeManager::currentNM()->mkBoundVar( - "a", NodeManager::currentNM()->realType()); - d_taylor_real_fv_base_rem = NodeManager::currentNM()->mkBoundVar( - "b", NodeManager::currentNM()->realType()); - d_taylor_degree = options::nlExtTfTaylorDegree(); } NonlinearExtension::~NonlinearExtension() {} @@ -509,13 +460,6 @@ Node NonlinearExtension::mkValidPhase(Node a, Node pi) { NodeManager::currentNM()->mkNode(MULT, mkRationalNode(-1), pi), a, pi); } -Node NonlinearExtension::mkBounded( Node l, Node a, Node u ) { - return NodeManager::currentNM()->mkNode( - AND, - NodeManager::currentNM()->mkNode(GEQ, a, l), - NodeManager::currentNM()->mkNode(LEQ, a, u)); -} - Node NonlinearExtension::mkMonomialRemFactor( Node n, const NodeMultiset& n_exp_rem) const { std::vector children; @@ -566,13 +510,7 @@ void NonlinearExtension::sendLemmas(const std::vector& out, void NonlinearExtension::processSideEffect(const NlLemmaSideEffect& se) { - for (const std::tuple& sp : se.d_secantPoint) - { - Node tf = std::get<0>(sp); - unsigned d = std::get<1>(sp); - Node c = std::get<2>(sp); - d_secant_points[tf][d].push_back(c); - } + d_trSlv.processSideEffect(se); } unsigned NonlinearExtension::filterLemma(Node lem, std::vector& out) @@ -779,90 +717,18 @@ bool NonlinearExtension::checkModel(const std::vector& assertions, // get the presubstitution Trace("nl-ext-cm-debug") << " apply pre-substitution..." << std::endl; - std::vector pvars; - std::vector psubs; - for (std::pair& tb : d_trMaster) - { - pvars.push_back(tb.first); - psubs.push_back(tb.second); - } - // initialize representation of assertions - std::vector passertions; - for (const Node& a : assertions) - { - Node pa = a; - if (!pvars.empty()) - { - pa = arithSubstitute(pa, pvars, psubs); - pa = Rewriter::rewrite(pa); - } - if (!pa.isConst() || !pa.getConst()) - { - Trace("nl-ext-cm-assert") << "- assert : " << pa << std::endl; - passertions.push_back(pa); - } - } + std::vector passertions = assertions; - // get model bounds for all transcendental functions - Trace("nl-ext-cm") << "----- Get bounds for transcendental functions..." - << std::endl; - for (std::pair >& tfs : d_funcMap) + // preprocess the assertions with the trancendental solver + if (!d_trSlv.preprocessAssertionsCheckModel(passertions)) { - Kind k = tfs.first; - for (const Node& tf : tfs.second) - { - Trace("nl-ext-cm") << "- Term: " << tf << std::endl; - bool success = true; - // tf is Figure 3 : tf( x ) - Node bl; - Node bu; - if (k == PI) - { - bl = d_pi_bound[0]; - bu = d_pi_bound[1]; - } - else - { - std::pair bounds = getTfModelBounds(tf, d_taylor_degree); - bl = bounds.first; - bu = bounds.second; - if (bl != bu) - { - d_model.setUsedApproximate(); - } - } - if (!bl.isNull() && !bu.isNull()) - { - // for each function in the congruence classe - for (const Node& ctf : d_funcCongClass[tf]) - { - // each term in congruence classes should be master terms - Assert(d_trSlaves.find(ctf) != d_trSlaves.end()); - // we set the bounds for each slave of tf - for (const Node& stf : d_trSlaves[ctf]) - { - Trace("nl-ext-cm") << "...bound for " << stf << " : [" << bl << ", " - << bu << "]" << std::endl; - success = d_model.addCheckModelBound(stf, bl, bu); - } - } - } - else - { - Trace("nl-ext-cm") << "...no bound for " << tf << std::endl; - } - if (!success) - { - // a bound was conflicting - Trace("nl-ext-cm") << "...failed to set bound for " << tf << std::endl; - Trace("nl-ext-cm") << "-----" << std::endl; - return false; - } - } + return false; } + Trace("nl-ext-cm") << "-----" << std::endl; - bool ret = d_model.checkModel( - passertions, false_asserts, d_taylor_degree, lemmas, gs); + unsigned tdegree = d_trSlv.getTaylorDegree(); + bool ret = + d_model.checkModel(passertions, false_asserts, tdegree, lemmas, gs); return ret; } @@ -882,33 +748,6 @@ std::vector NonlinearExtension::checkSplitZero() { return lemmas; } -/** An argument trie, for computing congruent terms */ -class ArgTrie -{ - public: - /** children of this node */ - std::map d_children; - /** the data of this node */ - Node d_data; - /** - * Set d as the data on the node whose path is [args], return either d if - * that node has no data, or the data that already occurs there. - */ - Node add(Node d, const std::vector& args) - { - ArgTrie* at = this; - for (const Node& a : args) - { - at = &(at->d_children[a]); - } - if (at->d_data.isNull()) - { - at->d_data = d; - } - return at->d_data; - } -}; - int NonlinearExtension::checkLastCall(const std::vector& assertions, const std::vector& false_asserts, const std::vector& xts, @@ -926,18 +765,8 @@ int NonlinearExtension::checkLastCall(const std::vector& assertions, d_ci.clear(); d_ci_exp.clear(); d_ci_max.clear(); - d_funcCongClass.clear(); - d_funcMap.clear(); - d_tf_region.clear(); - - std::vector lemmas; - NodeManager* nm = NodeManager::currentNM(); Trace("nl-ext-mv") << "Extended terms : " << std::endl; - // register the extended function terms - std::map< Node, Node > mvarg_to_term; - std::vector trNeedsMaster; - bool needPi = false; // for computing congruence std::map argTrie; for (unsigned i = 0, xsize = xts.size(); i < xsize; i++) @@ -947,47 +776,6 @@ int NonlinearExtension::checkLastCall(const std::vector& assertions, d_model.computeAbstractModelValue(a); d_model.printModelValue("nl-ext-mv", a); Kind ak = a.getKind(); - bool consider = true; - // if is an unpurified application of SINE, or it is a transcendental - // applied to a trancendental, purify. - if (isTranscendentalKind(ak)) - { - // if we've already computed master for a - if (d_trMaster.find(a) != d_trMaster.end()) - { - // a master has at least one slave - consider = (d_trSlaves.find(a) != d_trSlaves.end()); - } - else - { - if (ak == SINE) - { - // always not a master - consider = false; - } - else - { - for (const Node& ac : a) - { - if (isTranscendentalKind(ac.getKind())) - { - consider = false; - break; - } - } - } - if (!consider) - { - // wait to assign a master below - trNeedsMaster.push_back(a); - } - else - { - d_trMaster[a] = a; - d_trSlaves[a].insert(a); - } - } - } if (ak == NONLINEAR_MULT) { d_ms.push_back( a ); @@ -1008,126 +796,19 @@ int NonlinearExtension::checkLastCall(const std::vector& assertions, } // mark processed if has a "one" factor (will look at reduced monomial)? } - else if (a.getNumChildren() > 0) - { - if (ak == SINE) - { - needPi = true; - } - // if we didn't indicate that it should be purified above - if( consider ){ - std::vector repList; - for (const Node& ac : a) - { - Node r = d_model.computeConcreteModelValue(ac); - repList.push_back(r); - } - Node aa = argTrie[ak].add(a, repList); - if (aa != a) - { - // apply congruence to pairs of terms that are disequal and congruent - Assert(aa.getNumChildren() == a.getNumChildren()); - Node mvaa = d_model.computeAbstractModelValue(a); - Node mvaaa = d_model.computeAbstractModelValue(aa); - if (mvaa != mvaaa) - { - std::vector exp; - for (unsigned j = 0, size = a.getNumChildren(); j < size; j++) - { - exp.push_back(a[j].eqNode(aa[j])); - } - Node expn = exp.size() == 1 ? exp[0] : nm->mkNode(AND, exp); - Node cong_lemma = nm->mkNode(OR, expn.negate(), a.eqNode(aa)); - lemmas.push_back( cong_lemma ); - } - } - else - { - // new representative of congruence class - d_funcMap[ak].push_back(a); - } - // add to congruence class - d_funcCongClass[aa].push_back(a); - } - } - else if (ak == PI) - { - Assert(consider); - needPi = true; - d_funcMap[ak].push_back(a); - d_funcCongClass[a].push_back(a); - } - else - { - Assert(false); - } - } - // initialize pi if necessary - if (needPi && d_pi.isNull()) - { - mkPi(); - getCurrentPiBounds(lemmas); } + // initialize the trancendental function solver + std::vector lemmas; + d_trSlv.initLastCall(assertions, false_asserts, xts, lemmas, lemsPp); + + // process lemmas that may have been generated by the transcendental solver filterLemmas(lemmas, lems); - if (!lems.empty()) + if (!lems.empty() || !lemsPp.empty()) { Trace("nl-ext") << " ...finished with " << lems.size() << " new lemmas during registration." << std::endl; - return lems.size(); - } - - // process SINE phase shifting - for (const Node& a : trNeedsMaster) - { - // should not have processed this already - Assert(d_trMaster.find(a) == d_trMaster.end()); - Kind k = a.getKind(); - Assert(k == SINE || k == EXPONENTIAL); - Node y = - nm->mkSkolem("y", nm->realType(), "phase shifted trigonometric arg"); - Node new_a = nm->mkNode(k, y); - d_trSlaves[new_a].insert(new_a); - d_trSlaves[new_a].insert(a); - d_trMaster[a] = new_a; - d_trMaster[new_a] = new_a; - Node lem; - if (k == SINE) - { - Trace("nl-ext-tf") << "Basis sine : " << new_a << " for " << a - << std::endl; - Assert(!d_pi.isNull()); - Node shift = nm->mkSkolem("s", nm->integerType(), "number of shifts"); - // TODO : do not introduce shift here, instead needs model-based - // refinement for constant shifts (cvc4-projects #1284) - lem = nm->mkNode( - AND, - mkValidPhase(y, d_pi), - nm->mkNode( - ITE, - mkValidPhase(a[0], d_pi), - a[0].eqNode(y), - a[0].eqNode(nm->mkNode( - PLUS, - y, - nm->mkNode(MULT, nm->mkConst(Rational(2)), shift, d_pi)))), - new_a.eqNode(a)); - } - else - { - // do both equalities to ensure that new_a becomes a preregistered term - lem = nm->mkNode(AND, a.eqNode(new_a), a[0].eqNode(y)); - } - // note we must do preprocess on this lemma - Trace("nl-ext-lemma") << "NonlinearExtension::Lemma : purify : " << lem - << std::endl; - lemsPp.push_back(lem); - } - if (!lemsPp.empty()) - { - Trace("nl-ext") << " ...finished with " << lemsPp.size() - << " new lemmas SINE phase shifting." << std::endl; - return lemsPp.size(); + return lems.size() + lemsPp.size(); } Trace("nl-ext") << "We have " << d_ms.size() << " monomials." << std::endl; @@ -1148,25 +829,6 @@ int NonlinearExtension::checkLastCall(const std::vector& assertions, d_model.computeAbstractModelValue(v); d_model.printModelValue("nl-ext-mv", v); } - if (Trace.isOn("nl-ext-mv")) - { - Trace("nl-ext-mv") << "Arguments of trancendental functions : " - << std::endl; - for (std::pair >& tfl : d_funcMap) - { - Kind k = tfl.first; - if (k == SINE || k == EXPONENTIAL) - { - for (const Node& tf : tfl.second) - { - Node v = tf[0]; - d_model.computeConcreteModelValue(v); - d_model.computeAbstractModelValue(v); - d_model.printModelValue("nl-ext-mv", v); - } - } - } - } //----------------------------------- possibly split on zero if (options::nlExtSplitZero()) { @@ -1182,7 +844,7 @@ int NonlinearExtension::checkLastCall(const std::vector& assertions, } //-----------------------------------initial lemmas for transcendental functions - lemmas = checkTranscendentalInitialRefine(); + lemmas = d_trSlv.checkTranscendentalInitialRefine(); filterLemmas(lemmas, lems); if (!lems.empty()) { @@ -1202,7 +864,7 @@ int NonlinearExtension::checkLastCall(const std::vector& assertions, } //-----------------------------------monotonicity of transdental functions - lemmas = checkTranscendentalMonotonic(); + lemmas = d_trSlv.checkTranscendentalMonotonic(); filterLemmas(lemmas, lems); if (!lems.empty()) { @@ -1313,7 +975,7 @@ int NonlinearExtension::checkLastCall(const std::vector& assertions, } if (options::nlExtTfTangentPlanes()) { - lemmas = checkTranscendentalTangentPlanes(lemSE); + lemmas = d_trSlv.checkTranscendentalTangentPlanes(lemSE); filterLemmas(lemmas, wlems); } Trace("nl-ext") << " ...finished with " << wlems.size() << " waiting lemmas." @@ -1543,12 +1205,12 @@ bool NonlinearExtension::modelBasedRefinement( // we are incomplete if (options::nlExtIncPrecision() && d_model.usedApproximate()) { - d_taylor_degree++; + d_trSlv.incrementTaylorDegree(); needsRecheck = true; // increase precision for PI? // Difficult since Taylor series is very slow to converge - Trace("nl-ext") << "...increment Taylor degree to " << d_taylor_degree - << std::endl; + Trace("nl-ext") << "...increment Taylor degree to " + << d_trSlv.getTaylorDegree() << std::endl; } else { @@ -1660,36 +1322,6 @@ void NonlinearExtension::assignOrderIds(std::vector& vars, } } -void NonlinearExtension::mkPi(){ - if( d_pi.isNull() ){ - d_pi = NodeManager::currentNM()->mkNullaryOperator( - NodeManager::currentNM()->realType(), PI); - d_pi_2 = Rewriter::rewrite(NodeManager::currentNM()->mkNode( - MULT, - d_pi, - NodeManager::currentNM()->mkConst(Rational(1) / Rational(2)))); - d_pi_neg_2 = Rewriter::rewrite(NodeManager::currentNM()->mkNode( - MULT, - d_pi, - NodeManager::currentNM()->mkConst(Rational(-1) / Rational(2)))); - d_pi_neg = Rewriter::rewrite(NodeManager::currentNM()->mkNode( - MULT, d_pi, NodeManager::currentNM()->mkConst(Rational(-1)))); - //initialize bounds - d_pi_bound[0] = - NodeManager::currentNM()->mkConst(Rational(103993) / Rational(33102)); - d_pi_bound[1] = - NodeManager::currentNM()->mkConst(Rational(104348) / Rational(33215)); - } -} - -void NonlinearExtension::getCurrentPiBounds( std::vector< Node >& lemmas ) { - Node pi_lem = NodeManager::currentNM()->mkNode( - AND, - NodeManager::currentNM()->mkNode(GEQ, d_pi, d_pi_bound[0]), - NodeManager::currentNM()->mkNode(LEQ, d_pi, d_pi_bound[1])); - lemmas.push_back( pi_lem ); -} - bool NonlinearExtension::getApproximateSqrt(Node c, Node& l, Node& u, @@ -2807,1098 +2439,6 @@ std::vector NonlinearExtension::checkMonomialInferResBounds() { } return lemmas; } - -std::vector NonlinearExtension::checkTranscendentalInitialRefine() { - std::vector< Node > lemmas; - Trace("nl-ext") << "Get initial refinement lemmas for transcendental functions..." << std::endl; - for (std::pair >& tfl : d_funcMap) - { - Kind k = tfl.first; - for (const Node& t : tfl.second) - { - //initial refinements - if( d_tf_initial_refine.find( t )==d_tf_initial_refine.end() ){ - d_tf_initial_refine[t] = true; - Node lem; - if (k == SINE) - { - Node symn = NodeManager::currentNM()->mkNode( - SINE, NodeManager::currentNM()->mkNode(MULT, d_neg_one, t[0])); - symn = Rewriter::rewrite( symn ); - // Can assume it is its own master since phase is split over 0, - // hence -pi <= t[0] <= pi implies -pi <= -t[0] <= pi. - d_trMaster[symn] = symn; - d_trSlaves[symn].insert(symn); - Assert(d_trSlaves.find(t) != d_trSlaves.end()); - std::vector< Node > children; - - lem = NodeManager::currentNM()->mkNode( - AND, - // bounds - NodeManager::currentNM()->mkNode( - AND, - NodeManager::currentNM()->mkNode(LEQ, t, d_one), - NodeManager::currentNM()->mkNode(GEQ, t, d_neg_one)), - // symmetry - NodeManager::currentNM()->mkNode(PLUS, t, symn).eqNode(d_zero), - // sign - NodeManager::currentNM()->mkNode( - EQUAL, - NodeManager::currentNM()->mkNode(LT, t[0], d_zero), - NodeManager::currentNM()->mkNode(LT, t, d_zero)), - // zero val - NodeManager::currentNM()->mkNode( - EQUAL, - NodeManager::currentNM()->mkNode(GT, t[0], d_zero), - NodeManager::currentNM()->mkNode(GT, t, d_zero))); - lem = NodeManager::currentNM()->mkNode( - AND, - lem, - // zero tangent - NodeManager::currentNM()->mkNode( - AND, - NodeManager::currentNM()->mkNode( - IMPLIES, - NodeManager::currentNM()->mkNode(GT, t[0], d_zero), - NodeManager::currentNM()->mkNode(LT, t, t[0])), - NodeManager::currentNM()->mkNode( - IMPLIES, - NodeManager::currentNM()->mkNode(LT, t[0], d_zero), - NodeManager::currentNM()->mkNode(GT, t, t[0]))), - // pi tangent - NodeManager::currentNM()->mkNode( - AND, - NodeManager::currentNM()->mkNode( - IMPLIES, - NodeManager::currentNM()->mkNode(LT, t[0], d_pi), - NodeManager::currentNM()->mkNode( - LT, - t, - NodeManager::currentNM()->mkNode(MINUS, d_pi, t[0]))), - NodeManager::currentNM()->mkNode( - IMPLIES, - NodeManager::currentNM()->mkNode(GT, t[0], d_pi_neg), - NodeManager::currentNM()->mkNode( - GT, - t, - NodeManager::currentNM()->mkNode( - MINUS, d_pi_neg, t[0]))))); - } - else if (k == EXPONENTIAL) - { - // ( exp(x) > 0 ) ^ ( x=0 <=> exp( x ) = 1 ) ^ ( x < 0 <=> exp( x ) < - // 1 ) ^ ( x <= 0 V exp( x ) > x + 1 ) - lem = NodeManager::currentNM()->mkNode( - AND, - NodeManager::currentNM()->mkNode(GT, t, d_zero), - NodeManager::currentNM()->mkNode( - EQUAL, t[0].eqNode(d_zero), t.eqNode(d_one)), - NodeManager::currentNM()->mkNode( - EQUAL, - NodeManager::currentNM()->mkNode(LT, t[0], d_zero), - NodeManager::currentNM()->mkNode(LT, t, d_one)), - NodeManager::currentNM()->mkNode( - OR, - NodeManager::currentNM()->mkNode(LEQ, t[0], d_zero), - NodeManager::currentNM()->mkNode( - GT, - t, - NodeManager::currentNM()->mkNode(PLUS, t[0], d_one)))); - } - if( !lem.isNull() ){ - lemmas.push_back( lem ); - } - } - } - } - - return lemmas; -} - -std::vector NonlinearExtension::checkTranscendentalMonotonic() { - std::vector< Node > lemmas; - Trace("nl-ext") << "Get monotonicity lemmas for transcendental functions..." << std::endl; - - //sort arguments of all transcendentals - std::map< Kind, std::vector< Node > > sorted_tf_args; - std::map< Kind, std::map< Node, Node > > tf_arg_to_term; - - for (std::pair >& tfl : d_funcMap) - { - Kind k = tfl.first; - if (k == EXPONENTIAL || k == SINE) - { - for (const Node& tf : tfl.second) - { - Node a = tf[0]; - Node mvaa = d_model.computeAbstractModelValue(a); - if (mvaa.isConst()) - { - Trace("nl-ext-tf-mono-debug") << "...tf term : " << a << std::endl; - sorted_tf_args[k].push_back(a); - tf_arg_to_term[k][a] = tf; - } - } - } - } - - SortNlModel smv; - smv.d_nlm = &d_model; - //sort by concrete values - smv.d_isConcrete = true; - smv.d_reverse_order = true; - for (std::pair >& tfl : d_funcMap) - { - Kind k = tfl.first; - if( !sorted_tf_args[k].empty() ){ - std::sort( sorted_tf_args[k].begin(), sorted_tf_args[k].end(), smv ); - Trace("nl-ext-tf-mono") << "Sorted transcendental function list for " << k << " : " << std::endl; - for (unsigned i = 0; i < sorted_tf_args[k].size(); i++) - { - Node targ = sorted_tf_args[k][i]; - Node mvatarg = d_model.computeAbstractModelValue(targ); - Trace("nl-ext-tf-mono") - << " " << targ << " -> " << mvatarg << std::endl; - Node t = tf_arg_to_term[k][targ]; - Node mvat = d_model.computeAbstractModelValue(t); - Trace("nl-ext-tf-mono") << " f-val : " << mvat << std::endl; - } - std::vector< Node > mpoints; - std::vector< Node > mpoints_vals; - if (k == SINE) - { - mpoints.push_back( d_pi ); - mpoints.push_back( d_pi_2 ); - mpoints.push_back(d_zero); - mpoints.push_back( d_pi_neg_2 ); - mpoints.push_back( d_pi_neg ); - } - else if (k == EXPONENTIAL) - { - mpoints.push_back( Node::null() ); - } - if( !mpoints.empty() ){ - //get model values for points - for( unsigned i=0; i() < pval.getConst() ){ - increment = true; - Trace("nl-ext-tf-mono") << "...increment at " << sarg << " since model value is less than " << mpoints[mdir_index] << std::endl; - } - } - if( increment ){ - tval = Node::null(); - mono_bounds[1] = mpoints[mdir_index]; - mdir_index++; - monotonic_dir = regionToMonotonicityDir(k, mdir_index); - if (mdir_index < mpoints.size()) - { - mono_bounds[0] = mpoints[mdir_index]; - }else{ - mono_bounds[0] = Node::null(); - } - } - } - // store the concavity region - d_tf_region[s] = mdir_index; - Trace("nl-ext-concavity") << "Transcendental function " << s - << " is in region #" << mdir_index; - Trace("nl-ext-concavity") << ", arg model value = " << sargval - << std::endl; - - if( !tval.isNull() ){ - Node mono_lem; - if( monotonic_dir==1 && sval.getConst() > tval.getConst() ){ - mono_lem = NodeManager::currentNM()->mkNode( - IMPLIES, - NodeManager::currentNM()->mkNode(GEQ, targ, sarg), - NodeManager::currentNM()->mkNode(GEQ, t, s)); - }else if( monotonic_dir==-1 && sval.getConst() < tval.getConst() ){ - mono_lem = NodeManager::currentNM()->mkNode( - IMPLIES, - NodeManager::currentNM()->mkNode(LEQ, targ, sarg), - NodeManager::currentNM()->mkNode(LEQ, t, s)); - } - if( !mono_lem.isNull() ){ - if( !mono_bounds[0].isNull() ){ - Assert(!mono_bounds[1].isNull()); - mono_lem = NodeManager::currentNM()->mkNode( - IMPLIES, - NodeManager::currentNM()->mkNode( - AND, - mkBounded(mono_bounds[0], targ, mono_bounds[1]), - mkBounded(mono_bounds[0], sarg, mono_bounds[1])), - mono_lem); - } - Trace("nl-ext-tf-mono") << "Monotonicity lemma : " << mono_lem << std::endl; - lemmas.push_back( mono_lem ); - } - } - // store the previous values - targ = sarg; - targval = sargval; - t = s; - tval = sval; - } - } - } - } - return lemmas; -} - -std::vector NonlinearExtension::checkTranscendentalTangentPlanes( - std::map& lemSE) -{ - std::vector lemmas; - Trace("nl-ext") << "Get tangent plane lemmas for transcendental functions..." - << std::endl; - // this implements Figure 3 of "Satisfiaility Modulo Transcendental Functions - // via Incremental Linearization" by Cimatti et al - for (std::pair >& tfs : d_funcMap) - { - Kind k = tfs.first; - if (k == PI) - { - // We do not use Taylor approximation for PI currently. - // This is because the convergence is extremely slow, and hence an - // initial approximation is superior. - continue; - } - Trace("nl-ext-tftp-debug2") << "Taylor variables: " << std::endl; - Trace("nl-ext-tftp-debug2") - << " taylor_real_fv : " << d_taylor_real_fv << std::endl; - Trace("nl-ext-tftp-debug2") - << " taylor_real_fv_base : " << d_taylor_real_fv_base << std::endl; - Trace("nl-ext-tftp-debug2") - << " taylor_real_fv_base_rem : " << d_taylor_real_fv_base_rem - << std::endl; - Trace("nl-ext-tftp-debug2") << std::endl; - - // we substitute into the Taylor sum P_{n,f(0)}( x ) - - for (const Node& tf : tfs.second) - { - // tf is Figure 3 : tf( x ) - Trace("nl-ext-tftp") << "Compute tangent planes " << tf << std::endl; - // go until max degree is reached, or we don't meet bound criteria - for (unsigned d = 1; d <= d_taylor_degree; d++) - { - Trace("nl-ext-tftp") << "- run at degree " << d << "..." << std::endl; - unsigned prev = lemmas.size(); - if (checkTfTangentPlanesFun(tf, d, lemmas, lemSE)) - { - Trace("nl-ext-tftp") - << "...fail, #lemmas = " << (lemmas.size() - prev) << std::endl; - break; - } - else - { - Trace("nl-ext-tftp") << "...success" << std::endl; - } - } - } - } - - return lemmas; -} - -bool NonlinearExtension::checkTfTangentPlanesFun( - Node tf, - unsigned d, - std::vector& lemmas, - std::map& lemSE) -{ - NodeManager* nm = NodeManager::currentNM(); - Kind k = tf.getKind(); - // this should only be run on master applications - Assert(d_trSlaves.find(tf) != d_trSlaves.end()); - - // Figure 3 : c - Node c = d_model.computeAbstractModelValue(tf[0]); - int csign = c.getConst().sgn(); - if (csign == 0) - { - // no secant/tangent plane is necessary - return true; - } - Assert(csign == 1 || csign == -1); - - // Figure 3: P_l, P_u - // mapped to for signs of c - std::map poly_approx_bounds[2]; - std::vector pbounds; - getPolynomialApproximationBoundForArg(k, c, d, pbounds); - poly_approx_bounds[0][1] = pbounds[0]; - poly_approx_bounds[0][-1] = pbounds[1]; - poly_approx_bounds[1][1] = pbounds[2]; - poly_approx_bounds[1][-1] = pbounds[3]; - - // Figure 3 : v - Node v = d_model.computeAbstractModelValue(tf); - - // check value of tf - Trace("nl-ext-tftp-debug") << "Process tangent plane refinement for " << tf - << ", degree " << d << "..." << std::endl; - Trace("nl-ext-tftp-debug") << " value in model : " << v << std::endl; - Trace("nl-ext-tftp-debug") << " arg value in model : " << c << std::endl; - - std::vector taylor_vars; - taylor_vars.push_back(d_taylor_real_fv); - - // compute the concavity - int region = -1; - std::unordered_map::iterator itr = - d_tf_region.find(tf); - if (itr != d_tf_region.end()) - { - region = itr->second; - Trace("nl-ext-tftp-debug") << " region is : " << region << std::endl; - } - // Figure 3 : conc - int concavity = regionToConcavity(k, itr->second); - Trace("nl-ext-tftp-debug") << " concavity is : " << concavity << std::endl; - if (concavity == 0) - { - // no secant/tangent plane is necessary - return true; - } - // bounds for which we are this concavity - // Figure 3: < l, u > - Node bounds[2]; - if (k == SINE) - { - bounds[0] = regionToLowerBound(k, region); - Assert(!bounds[0].isNull()); - bounds[1] = regionToUpperBound(k, region); - Assert(!bounds[1].isNull()); - } - - // Figure 3: P - Node poly_approx; - - // compute whether this is a tangent refinement or a secant refinement - bool is_tangent = false; - bool is_secant = false; - std::pair mvb = getTfModelBounds(tf, d); - for (unsigned r = 0; r < 2; r++) - { - Node pab = poly_approx_bounds[r][csign]; - Node v_pab = r == 0 ? mvb.first : mvb.second; - if (!v_pab.isNull()) - { - Trace("nl-ext-tftp-debug2") << "...model value of " << pab << " is " - << v_pab << std::endl; - - Assert(v_pab.isConst()); - Node comp = nm->mkNode(r == 0 ? LT : GT, v, v_pab); - Trace("nl-ext-tftp-debug2") << "...compare : " << comp << std::endl; - Node compr = Rewriter::rewrite(comp); - Trace("nl-ext-tftp-debug2") << "...got : " << compr << std::endl; - if (compr == d_true) - { - // beyond the bounds - if (r == 0) - { - poly_approx = poly_approx_bounds[r][csign]; - is_tangent = concavity == 1; - is_secant = concavity == -1; - } - else - { - poly_approx = poly_approx_bounds[r][csign]; - is_tangent = concavity == -1; - is_secant = concavity == 1; - } - if (Trace.isOn("nl-ext-tftp")) - { - Trace("nl-ext-tftp") << "*** Outside boundary point ("; - Trace("nl-ext-tftp") << (r == 0 ? "low" : "high") << ") "; - printRationalApprox("nl-ext-tftp", v_pab); - Trace("nl-ext-tftp") << ", will refine..." << std::endl; - Trace("nl-ext-tftp") << " poly_approx = " << poly_approx - << std::endl; - Trace("nl-ext-tftp") << " is_tangent = " << is_tangent - << std::endl; - Trace("nl-ext-tftp") << " is_secant = " << is_secant << std::endl; - } - break; - } - else - { - Trace("nl-ext-tftp") << " ...within " << (r == 0 ? "low" : "high") - << " bound : "; - printRationalApprox("nl-ext-tftp", v_pab); - Trace("nl-ext-tftp") << std::endl; - } - } - } - - // Figure 3: P( c ) - Node poly_approx_c; - if (is_tangent || is_secant) - { - Assert(!poly_approx.isNull()); - std::vector taylor_subs; - taylor_subs.push_back(c); - Assert(taylor_vars.size() == taylor_subs.size()); - poly_approx_c = poly_approx.substitute(taylor_vars.begin(), - taylor_vars.end(), - taylor_subs.begin(), - taylor_subs.end()); - Trace("nl-ext-tftp-debug2") << "...poly approximation at c is " - << poly_approx_c << std::endl; - } - else - { - // we may want to continue getting better bounds - return false; - } - - if (is_tangent) - { - // compute tangent plane - // Figure 3: T( x ) - // We use zero slope tangent planes, since the concavity of the Taylor - // approximation cannot be easily established. - Node tplane = poly_approx_c; - - Node lem = nm->mkNode(concavity == 1 ? GEQ : LEQ, tf, tplane); - std::vector antec; - int mdir = regionToMonotonicityDir(k, region); - for (unsigned i = 0; i < 2; i++) - { - // Tangent plane is valid in the interval [c,u) if the slope of the - // function matches its concavity, and is valid in (l, c] otherwise. - Node use_bound = (mdir == concavity) == (i == 0) ? c : bounds[i]; - if (!use_bound.isNull()) - { - Node ant = nm->mkNode(i == 0 ? GEQ : LEQ, tf[0], use_bound); - antec.push_back(ant); - } - } - if (!antec.empty()) - { - Node antec_n = antec.size() == 1 ? antec[0] : nm->mkNode(AND, antec); - lem = nm->mkNode(IMPLIES, antec_n, lem); - } - Trace("nl-ext-tftp-debug2") - << "*** Tangent plane lemma (pre-rewrite): " << lem << std::endl; - lem = Rewriter::rewrite(lem); - Trace("nl-ext-tftp-lemma") << "*** Tangent plane lemma : " << lem - << std::endl; - Assert(d_model.computeAbstractModelValue(lem) == d_false); - // Figure 3 : line 9 - lemmas.push_back(lem); - } - else if (is_secant) - { - // bounds are the minimum and maximum previous secant points - // should not repeat secant points: secant lemmas should suffice to - // rule out previous assignment - Assert(std::find( - d_secant_points[tf][d].begin(), d_secant_points[tf][d].end(), c) - == d_secant_points[tf][d].end()); - // Insert into the (temporary) vector. We do not update this vector - // until we are sure this secant plane lemma has been processed. We do - // this by mapping the lemma to a side effect below. - std::vector spoints = d_secant_points[tf][d]; - spoints.push_back(c); - - // sort - SortNlModel smv; - smv.d_nlm = &d_model; - smv.d_isConcrete = true; - std::sort(spoints.begin(), spoints.end(), smv); - // get the resulting index of c - unsigned index = - std::find(spoints.begin(), spoints.end(), c) - spoints.begin(); - // bounds are the next closest upper/lower bound values - if (index > 0) - { - bounds[0] = spoints[index - 1]; - } - else - { - // otherwise, we use the lower boundary point for this concavity - // region - if (k == SINE) - { - Assert(!bounds[0].isNull()); - } - else if (k == EXPONENTIAL) - { - // pick c-1 - bounds[0] = Rewriter::rewrite(nm->mkNode(MINUS, c, d_one)); - } - } - if (index < spoints.size() - 1) - { - bounds[1] = spoints[index + 1]; - } - else - { - // otherwise, we use the upper boundary point for this concavity - // region - if (k == SINE) - { - Assert(!bounds[1].isNull()); - } - else if (k == EXPONENTIAL) - { - // pick c+1 - bounds[1] = Rewriter::rewrite(nm->mkNode(PLUS, c, d_one)); - } - } - Trace("nl-ext-tftp-debug2") << "...secant bounds are : " << bounds[0] - << " ... " << bounds[1] << std::endl; - - // the secant plane may be conjunction of 1-2 guarded inequalities - std::vector lemmaConj; - for (unsigned s = 0; s < 2; s++) - { - // compute secant plane - Assert(!poly_approx.isNull()); - Assert(!bounds[s].isNull()); - // take the model value of l or u (since may contain PI) - Node b = d_model.computeAbstractModelValue(bounds[s]); - Trace("nl-ext-tftp-debug2") << "...model value of bound " << bounds[s] - << " is " << b << std::endl; - Assert(b.isConst()); - if (c != b) - { - // Figure 3 : P(l), P(u), for s = 0,1 - Node poly_approx_b; - std::vector taylor_subs; - taylor_subs.push_back(b); - Assert(taylor_vars.size() == taylor_subs.size()); - poly_approx_b = poly_approx.substitute(taylor_vars.begin(), - taylor_vars.end(), - taylor_subs.begin(), - taylor_subs.end()); - // Figure 3: S_l( x ), S_u( x ) for s = 0,1 - Node splane; - Node rcoeff_n = Rewriter::rewrite(nm->mkNode(MINUS, b, c)); - Assert(rcoeff_n.isConst()); - Rational rcoeff = rcoeff_n.getConst(); - Assert(rcoeff.sgn() != 0); - poly_approx_b = Rewriter::rewrite(poly_approx_b); - poly_approx_c = Rewriter::rewrite(poly_approx_c); - splane = nm->mkNode( - PLUS, - poly_approx_b, - nm->mkNode(MULT, - nm->mkNode(MINUS, poly_approx_b, poly_approx_c), - nm->mkConst(Rational(1) / rcoeff), - nm->mkNode(MINUS, tf[0], b))); - - Node lem = nm->mkNode(concavity == 1 ? LEQ : GEQ, tf, splane); - // With respect to Figure 3, this is slightly different. - // In particular, we chose b to be the model value of bounds[s], - // which is a constant although bounds[s] may not be (e.g. if it - // contains PI). - // To ensure that c...b does not cross an inflection point, - // we guard with the symbolic version of bounds[s]. - // This leads to lemmas e.g. of this form: - // ( c <= x <= PI/2 ) => ( sin(x) < ( P( b ) - P( c ) )*( x - - // b ) + P( b ) ) - // where b = (PI/2)^M, the current value of PI/2 in the model. - // This is sound since we are guarded by the symbolic - // representation of PI/2. - Node antec_n = - nm->mkNode(AND, - nm->mkNode(GEQ, tf[0], s == 0 ? bounds[s] : c), - nm->mkNode(LEQ, tf[0], s == 0 ? c : bounds[s])); - lem = nm->mkNode(IMPLIES, antec_n, lem); - Trace("nl-ext-tftp-debug2") - << "*** Secant plane lemma (pre-rewrite) : " << lem << std::endl; - lem = Rewriter::rewrite(lem); - Trace("nl-ext-tftp-lemma") << "*** Secant plane lemma : " << lem - << std::endl; - lemmaConj.push_back(lem); - Assert(d_model.computeAbstractModelValue(lem) == d_false); - } - } - // Figure 3 : line 22 - Assert(!lemmaConj.empty()); - Node lem = - lemmaConj.size() == 1 ? lemmaConj[0] : nm->mkNode(AND, lemmaConj); - lemmas.push_back(lem); - // The side effect says that if lem is added, then we should add the - // secant point c for (tf,d). - lemSE[lem].d_secantPoint.push_back(std::make_tuple(tf, d, c)); - } - return true; -} - -int NonlinearExtension::regionToMonotonicityDir(Kind k, int region) -{ - if (k == EXPONENTIAL) - { - if (region == 1) - { - return 1; - } - } - else if (k == SINE) - { - if (region == 1 || region == 4) - { - return -1; - } - else if (region == 2 || region == 3) - { - return 1; - } - } - return 0; -} - -int NonlinearExtension::regionToConcavity(Kind k, int region) -{ - if (k == EXPONENTIAL) - { - if (region == 1) - { - return 1; - } - } - else if (k == SINE) - { - if (region == 1 || region == 2) - { - return -1; - } - else if (region == 3 || region == 4) - { - return 1; - } - } - return 0; -} - -Node NonlinearExtension::regionToLowerBound(Kind k, int region) -{ - if (k == SINE) - { - if (region == 1) - { - return d_pi_2; - } - else if (region == 2) - { - return d_zero; - } - else if (region == 3) - { - return d_pi_neg_2; - } - else if (region == 4) - { - return d_pi_neg; - } - } - return Node::null(); -} - -Node NonlinearExtension::regionToUpperBound(Kind k, int region) -{ - if (k == SINE) - { - if (region == 1) - { - return d_pi; - } - else if (region == 2) - { - return d_pi_2; - } - else if (region == 3) - { - return d_zero; - } - else if (region == 4) - { - return d_pi_neg_2; - } - } - return Node::null(); -} - -Node NonlinearExtension::getDerivative(Node n, Node x) -{ - Assert(x.isVar()); - // only handle the cases of the taylor expansion of d - if (n.getKind() == EXPONENTIAL) - { - if (n[0] == x) - { - return n; - } - } - else if (n.getKind() == SINE) - { - if (n[0] == x) - { - Node na = NodeManager::currentNM()->mkNode(MINUS, d_pi_2, n[0]); - Node ret = NodeManager::currentNM()->mkNode(SINE, na); - ret = Rewriter::rewrite(ret); - return ret; - } - } - else if (n.getKind() == PLUS) - { - std::vector dchildren; - for (unsigned i = 0; i < n.getNumChildren(); i++) - { - // PLUS is flattened in rewriter, recursion depth is bounded by 1 - Node dc = getDerivative(n[i], x); - if (dc.isNull()) - { - return dc; - }else{ - dchildren.push_back(dc); - } - } - return NodeManager::currentNM()->mkNode(PLUS, dchildren); - } - else if (n.getKind() == MULT) - { - Assert(n[0].isConst()); - Node dc = getDerivative(n[1], x); - if (!dc.isNull()) - { - return NodeManager::currentNM()->mkNode(MULT, n[0], dc); - } - } - else if (n.getKind() == NONLINEAR_MULT) - { - unsigned xcount = 0; - std::vector children; - unsigned xindex = 0; - for (unsigned i = 0, size = n.getNumChildren(); i < size; i++) - { - if (n[i] == x) - { - xcount++; - xindex = i; - } - children.push_back(n[i]); - } - if (xcount == 0) - { - return d_zero; - } - else - { - children[xindex] = NodeManager::currentNM()->mkConst(Rational(xcount)); - } - return NodeManager::currentNM()->mkNode(MULT, children); - } - else if (n.isVar()) - { - return n == x ? d_one : d_zero; - } - else if (n.isConst()) - { - return d_zero; - } - Trace("nl-ext-debug") << "No derivative computed for " << n; - Trace("nl-ext-debug") << " for d/d{" << x << "}" << std::endl; - return Node::null(); -} - -std::pair NonlinearExtension::getTaylor(Node fa, unsigned n) -{ - Assert(n > 0); - Node fac; // what term we cache for fa - if (fa[0] == d_zero) - { - // optimization : simpler to compute (x-fa[0])^n if we are centered around 0 - fac = fa; - } - else - { - // otherwise we use a standard factor a in (x-a)^n - fac = NodeManager::currentNM()->mkNode(fa.getKind(), d_taylor_real_fv_base); - } - Node taylor_rem; - Node taylor_sum; - // check if we have already computed this Taylor series - std::unordered_map::iterator itt = d_taylor_sum[fac].find(n); - if (itt == d_taylor_sum[fac].end()) - { - Node i_exp_base; - if (fa[0] == d_zero) - { - i_exp_base = d_taylor_real_fv; - } - else - { - i_exp_base = Rewriter::rewrite(NodeManager::currentNM()->mkNode( - MINUS, d_taylor_real_fv, d_taylor_real_fv_base)); - } - Node i_derv = fac; - Node i_fact = d_one; - Node i_exp = d_one; - int i_derv_status = 0; - unsigned counter = 0; - std::vector sum; - do - { - counter++; - if (fa.getKind() == EXPONENTIAL) - { - // unchanged - } - else if (fa.getKind() == SINE) - { - if (i_derv_status % 2 == 1) - { - Node arg = NodeManager::currentNM()->mkNode( - PLUS, d_pi_2, d_taylor_real_fv_base); - i_derv = NodeManager::currentNM()->mkNode(SINE, arg); - } - else - { - i_derv = fa; - } - if (i_derv_status >= 2) - { - i_derv = NodeManager::currentNM()->mkNode(MINUS, d_zero, i_derv); - } - i_derv = Rewriter::rewrite(i_derv); - i_derv_status = i_derv_status == 3 ? 0 : i_derv_status + 1; - } - if (counter == (n + 1)) - { - TNode x = d_taylor_real_fv_base; - i_derv = i_derv.substitute(x, d_taylor_real_fv_base_rem); - } - Node curr = NodeManager::currentNM()->mkNode( - MULT, - NodeManager::currentNM()->mkNode(DIVISION, i_derv, i_fact), - i_exp); - if (counter == (n + 1)) - { - taylor_rem = curr; - } - else - { - sum.push_back(curr); - i_fact = Rewriter::rewrite(NodeManager::currentNM()->mkNode( - MULT, - NodeManager::currentNM()->mkConst(Rational(counter)), - i_fact)); - i_exp = Rewriter::rewrite( - NodeManager::currentNM()->mkNode(MULT, i_exp_base, i_exp)); - } - } while (counter <= n); - taylor_sum = - sum.size() == 1 ? sum[0] : NodeManager::currentNM()->mkNode(PLUS, sum); - - if (fac[0] != d_taylor_real_fv_base) - { - TNode x = d_taylor_real_fv_base; - taylor_sum = taylor_sum.substitute(x, fac[0]); - } - - // cache - d_taylor_sum[fac][n] = taylor_sum; - d_taylor_rem[fac][n] = taylor_rem; - } - else - { - taylor_sum = itt->second; - Assert(d_taylor_rem[fac].find(n) != d_taylor_rem[fac].end()); - taylor_rem = d_taylor_rem[fac][n]; - } - - // must substitute for the argument if we were using a different lookup - if (fa[0] != fac[0]) - { - TNode x = d_taylor_real_fv_base; - taylor_sum = taylor_sum.substitute(x, fa[0]); - } - return std::pair(taylor_sum, taylor_rem); -} - -void NonlinearExtension::getPolynomialApproximationBounds( - Kind k, unsigned d, std::vector& pbounds) -{ - if (d_poly_bounds[k][d].empty()) - { - NodeManager* nm = NodeManager::currentNM(); - Node tft = nm->mkNode(k, d_zero); - // n is the Taylor degree we are currently considering - unsigned n = 2 * d; - // n must be even - std::pair taylor = getTaylor(tft, n); - Trace("nl-ext-tftp-debug2") << "Taylor for " << k - << " is : " << taylor.first << std::endl; - Node taylor_sum = Rewriter::rewrite(taylor.first); - Trace("nl-ext-tftp-debug2") << "Taylor for " << k - << " is (post-rewrite) : " << taylor_sum - << std::endl; - Assert(taylor.second.getKind() == MULT); - Assert(taylor.second.getNumChildren() == 2); - Assert(taylor.second[0].getKind() == DIVISION); - Trace("nl-ext-tftp-debug2") << "Taylor remainder for " << k << " is " - << taylor.second << std::endl; - // ru is x^{n+1}/(n+1)! - Node ru = nm->mkNode(DIVISION, taylor.second[1], taylor.second[0][1]); - ru = Rewriter::rewrite(ru); - Trace("nl-ext-tftp-debug2") - << "Taylor remainder factor is (post-rewrite) : " << ru << std::endl; - if (k == EXPONENTIAL) - { - pbounds.push_back(taylor_sum); - pbounds.push_back(taylor_sum); - pbounds.push_back(Rewriter::rewrite( - nm->mkNode(MULT, taylor_sum, nm->mkNode(PLUS, d_one, ru)))); - pbounds.push_back(Rewriter::rewrite(nm->mkNode(PLUS, taylor_sum, ru))); - } - else - { - Assert(k == SINE); - Node l = Rewriter::rewrite(nm->mkNode(MINUS, taylor_sum, ru)); - Node u = Rewriter::rewrite(nm->mkNode(PLUS, taylor_sum, ru)); - pbounds.push_back(l); - pbounds.push_back(l); - pbounds.push_back(u); - pbounds.push_back(u); - } - Trace("nl-ext-tf-tplanes") << "Polynomial approximation for " << k - << " is: " << std::endl; - Trace("nl-ext-tf-tplanes") << " Lower (pos): " << pbounds[0] << std::endl; - Trace("nl-ext-tf-tplanes") << " Upper (pos): " << pbounds[2] << std::endl; - Trace("nl-ext-tf-tplanes") << " Lower (neg): " << pbounds[1] << std::endl; - Trace("nl-ext-tf-tplanes") << " Upper (neg): " << pbounds[3] << std::endl; - d_poly_bounds[k][d].insert( - d_poly_bounds[k][d].end(), pbounds.begin(), pbounds.end()); - } - else - { - pbounds.insert( - pbounds.end(), d_poly_bounds[k][d].begin(), d_poly_bounds[k][d].end()); - } -} - -void NonlinearExtension::getPolynomialApproximationBoundForArg( - Kind k, Node c, unsigned d, std::vector& pbounds) -{ - getPolynomialApproximationBounds(k, d, pbounds); - Assert(c.isConst()); - if (k == EXPONENTIAL && c.getConst().sgn() == 1) - { - NodeManager* nm = NodeManager::currentNM(); - Node tft = nm->mkNode(k, d_zero); - bool success = false; - unsigned ds = d; - TNode ttrf = d_taylor_real_fv; - TNode tc = c; - do - { - success = true; - unsigned n = 2 * ds; - std::pair taylor = getTaylor(tft, n); - // check that 1-c^{n+1}/(n+1)! > 0 - Node ru = nm->mkNode(DIVISION, taylor.second[1], taylor.second[0][1]); - Node rus = ru.substitute(ttrf, tc); - rus = Rewriter::rewrite(rus); - Assert(rus.isConst()); - if (rus.getConst() > d_one.getConst()) - { - success = false; - ds = ds + 1; - } - } while (!success); - if (ds > d) - { - Trace("nl-ext-exp-taylor") - << "*** Increase Taylor bound to " << ds << " > " << d << " for (" - << k << " " << c << ")" << std::endl; - // must use sound upper bound - std::vector pboundss; - getPolynomialApproximationBounds(k, ds, pboundss); - pbounds[2] = pboundss[2]; - } - } -} - -std::pair NonlinearExtension::getTfModelBounds(Node tf, unsigned d) -{ - // compute the model value of the argument - Node c = d_model.computeAbstractModelValue(tf[0]); - Assert(c.isConst()); - int csign = c.getConst().sgn(); - Kind k = tf.getKind(); - if (csign == 0) - { - // at zero, its trivial - if (k == SINE) - { - return std::pair(d_zero, d_zero); - } - Assert(k == EXPONENTIAL); - return std::pair(d_one, d_one); - } - bool isNeg = csign == -1; - - std::vector pbounds; - getPolynomialApproximationBoundForArg(k, c, d, pbounds); - - std::vector bounds; - TNode tfv = d_taylor_real_fv; - TNode tfs = tf[0]; - for (unsigned d2 = 0; d2 < 2; d2++) - { - int index = d2 == 0 ? (isNeg ? 1 : 0) : (isNeg ? 3 : 2); - Node pab = pbounds[index]; - if (!pab.isNull()) - { - // { x -> tf[0] } - pab = pab.substitute(tfv, tfs); - pab = Rewriter::rewrite(pab); - Node v_pab = d_model.computeAbstractModelValue(pab); - bounds.push_back(v_pab); - } - else - { - bounds.push_back(Node::null()); - } - } - return std::pair(bounds[0], bounds[1]); -} } // namespace arith } // namespace theory diff --git a/src/theory/arith/nonlinear_extension.h b/src/theory/arith/nonlinear_extension.h index 19810730f..bfc713b12 100644 --- a/src/theory/arith/nonlinear_extension.h +++ b/src/theory/arith/nonlinear_extension.h @@ -37,6 +37,7 @@ #include "theory/arith/nl_lemma_utils.h" #include "theory/arith/nl_model.h" #include "theory/arith/theory_arith.h" +#include "theory/arith/transcendental_solver.h" #include "theory/uf/equality_engine.h" namespace CVC4 { @@ -253,7 +254,6 @@ class NonlinearExtension { static Node mkLit(Node a, Node b, int status, bool isAbsolute = false); static Node mkAbs(Node a); static Node mkValidPhase(Node a, Node pi); - static Node mkBounded( Node l, Node a, Node u ); Node mkMonomialRemFactor(Node n, const NodeMultiset& n_exp_rem) const; //---------------------------------------end term utilities @@ -449,21 +449,6 @@ class NonlinearExtension { Node d_two; Node d_true; Node d_false; - /** PI - * - * Note that PI is a (symbolic, non-constant) nullary operator. This is - * because its value cannot be computed exactly. We constraint PI to concrete - * lower and upper bounds stored in d_pi_bound below. - */ - Node d_pi; - /** PI/2 */ - Node d_pi_2; - /** -PI/2 */ - Node d_pi_neg_2; - /** -PI */ - Node d_pi_neg; - /** the concrete lower and upper bounds for PI */ - Node d_pi_bound[2]; // The theory of arithmetic containing this extension. TheoryArith& d_containing; @@ -488,6 +473,12 @@ class NonlinearExtension { * and for establishing when we are able to answer "SAT". */ NlModel d_model; + /** The transcendental extension object + * + * This is the subsolver responsible for running the procedure for + * transcendental functions. + */ + TranscendentalSolver d_trSlv; /** * The lemmas we computed during collectModelInfo. We store two vectors of * lemmas to be sent out on the output channel of TheoryArith. The first @@ -509,27 +500,6 @@ class NonlinearExtension { std::map d_order_vars; std::vector d_order_points; - //transcendental functions - /** - * Some transcendental functions f(t) are "purified", e.g. we add - * t = y ^ f(t) = f(y) where y is a fresh variable. Those that are not - * purified we call "master terms". - * - * The maps below maintain a master/slave relationship over - * transcendental functions (SINE, EXPONENTIAL, PI), where above - * f(y) is the master of itself and of f(t). - * - * This is used for ensuring that the argument y of SINE we process is on the - * interval [-pi .. pi], and that exponentials are not applied to arguments - * that contain transcendental functions. - */ - std::map d_trMaster; - std::map> d_trSlaves; - /** The transcendental functions we have done initial refinements on */ - std::map< Node, bool > d_tf_initial_refine; - - void mkPi(); - void getCurrentPiBounds( std::vector< Node >& lemmas ); private: //per last-call effort check @@ -552,25 +522,6 @@ class NonlinearExtension { std::map > > d_ci_exp; std::map > > d_ci_max; - /** - * Maps representives of a congruence class to the members of that class. - * - * In detail, a congruence class is a set of terms of the form - * { f(t1), ..., f(tn) } - * such that t1 = ... = tn in the current context. We choose an arbitrary - * term among these to be the repesentative of this congruence class. - * - * Moreover, notice we compute congruence classes only over terms that - * are transcendental function applications that are "master terms", - * see d_trMaster/d_trSlave. - */ - std::map > d_funcCongClass; - /** - * A list of all functions for each kind in { EXPONENTIAL, SINE, POW, PI } - * that are representives of their congruence class. - */ - std::map > d_funcMap; - // factor skolems std::map< Node, Node > d_factor_skolem; Node getFactorSkolem( Node n, std::vector< Node >& lemmas ); @@ -578,96 +529,6 @@ class NonlinearExtension { // tangent plane bounds std::map< Node, std::map< Node, Node > > d_tangent_val_bound[4]; - /** secant points (sorted list) for transcendental functions - * - * This is used for tangent plane refinements for - * transcendental functions. This is the set - * "get-previous-secant-points" in "Satisfiability - * Modulo Transcendental Functions via Incremental - * Linearization" by Cimatti et al., CADE 2017, for - * each transcendental function application. We store this set for each - * Taylor degree. - */ - std::unordered_map >, - NodeHashFunction> - d_secant_points; - - /** get Taylor series of degree n for function fa centered around point fa[0]. - * - * Return value is ( P_{n,f(a)}( x ), R_{n+1,f(a)}( x ) ) where - * the first part of the pair is the Taylor series expansion : - * P_{n,f(a)}( x ) = sum_{i=0}^n (f^i( a )/i!)*(x-a)^i - * and the second part of the pair is the Taylor series remainder : - * R_{n+1,f(a),b}( x ) = (f^{n+1}( b )/(n+1)!)*(x-a)^{n+1} - * - * The above values are cached for each (f,n) for a fixed variable "a". - * To compute the Taylor series for fa, we compute the Taylor series - * for ( fa.getKind(), n ) then substitute { a -> fa[0] } if fa[0]!=0. - * We compute P_{n,f(0)}( x )/R_{n+1,f(0),b}( x ) for ( fa.getKind(), n ) - * if fa[0]=0. - * In the latter case, note we compute the exponential x^{n+1} - * instead of (x-a)^{n+1}, which can be done faster. - */ - std::pair getTaylor(Node fa, unsigned n); - - /** internal variables used for constructing (cached) versions of the Taylor - * series above. - */ - Node d_taylor_real_fv; // x above - Node d_taylor_real_fv_base; // a above - Node d_taylor_real_fv_base_rem; // b above - - /** cache of sum and remainder terms for getTaylor */ - std::unordered_map, NodeHashFunction> - d_taylor_sum; - std::unordered_map, NodeHashFunction> - d_taylor_rem; - /** taylor degree - * - * Indicates that the degree of the polynomials in the Taylor approximation of - * all transcendental functions is 2*d_taylor_degree. This value is set - * initially to options::nlExtTfTaylorDegree() and may be incremented - * if the option options::nlExtTfIncPrecision() is enabled. - */ - unsigned d_taylor_degree; - /** polynomial approximation bounds - * - * This adds P_l+[x], P_l-[x], P_u+[x], P_u-[x] to pbounds, where x is - * d_taylor_real_fv. These are polynomial approximations of the Taylor series - * of ( 0 ) for degree 2*d where k is SINE or EXPONENTIAL. - * These correspond to P_l and P_u from Figure 3 of Cimatti et al., CADE 2017, - * for positive/negative (+/-) values of the argument of ( 0 ). - * - * Notice that for certain bounds (e.g. upper bounds for exponential), the - * Taylor approximation for a fixed degree is only sound up to a given - * upper bound on the argument. To obtain sound lower/upper bounds for a - * given ( c ), use the function below. - */ - void getPolynomialApproximationBounds(Kind k, - unsigned d, - std::vector& pbounds); - /** polynomial approximation bounds - * - * This computes polynomial approximations P_l+[x], P_l-[x], P_u+[x], P_u-[x] - * that are sound (lower, upper) bounds for ( c ). Notice that these - * polynomials may depend on c. In particular, for P_u+[x] for ( c ) where - * c>0, we return the P_u+[x] from the function above for the minimum degree - * d' >= d such that (1-c^{2*d'+1}/(2*d'+1)!) is positive. - */ - void getPolynomialApproximationBoundForArg(Kind k, - Node c, - unsigned d, - std::vector& pbounds); - /** cache of the above function */ - std::map > > d_poly_bounds; - /** get transcendental function model bounds - * - * This returns the current lower and upper bounds of transcendental - * function application tf based on Taylor of degree 2*d, which is dependent - * on the model value of its argument. - */ - std::pair getTfModelBounds(Node tf, unsigned d); /** get approximate sqrt * * This approximates the square root of positive constant c. If this method @@ -680,70 +541,6 @@ class NonlinearExtension { */ bool getApproximateSqrt(Node c, Node& l, Node& u, unsigned iter = 15) const; - /** concavity region for transcendental functions - * - * This stores an integer that identifies an interval in - * which the current model value for an argument of an - * application of a transcendental function resides. - * - * For exp( x ): - * region #1 is -infty < x < infty - * For sin( x ): - * region #0 is pi < x < infty (this is an invalid region) - * region #1 is pi/2 < x <= pi - * region #2 is 0 < x <= pi/2 - * region #3 is -pi/2 < x <= 0 - * region #4 is -pi < x <= -pi/2 - * region #5 is -infty < x <= -pi (this is an invalid region) - * All regions not listed above, as well as regions 0 and 5 - * for SINE are "invalid". We only process applications - * of transcendental functions whose arguments have model - * values that reside in valid regions. - */ - std::unordered_map d_tf_region; - /** get monotonicity direction - * - * Returns whether the slope is positive (+1) or negative(-1) - * in region of transcendental function with kind k. - * Returns 0 if region is invalid. - */ - int regionToMonotonicityDir(Kind k, int region); - /** get concavity - * - * Returns whether we are concave (+1) or convex (-1) - * in region of transcendental function with kind k, - * where region is defined above. - * Returns 0 if region is invalid. - */ - int regionToConcavity(Kind k, int region); - /** region to lower bound - * - * Returns the term corresponding to the lower - * bound of the region of transcendental function - * with kind k. Returns Node::null if the region - * is invalid, or there is no lower bound for the - * region. - */ - Node regionToLowerBound(Kind k, int region); - /** region to upper bound - * - * Returns the term corresponding to the upper - * bound of the region of transcendental function - * with kind k. Returns Node::null if the region - * is invalid, or there is no upper bound for the - * region. - */ - Node regionToUpperBound(Kind k, int region); - /** get derivative - * - * Returns d/dx n. Supports cases of n - * for transcendental functions applied to x, - * multiplication, addition, constants and variables. - * Returns Node::null() if derivative is an - * unhandled case. - */ - Node getDerivative(Node n, Node x); - private: //-------------------------------------------- lemma schemas /** check split zero @@ -873,122 +670,6 @@ class NonlinearExtension { */ std::vector checkTangentPlanes(); - /** check transcendental initial refine - * - * Returns a set of valid theory lemmas, based on - * simple facts about transcendental functions. - * This mostly follows the initial axioms described in - * Section 4 of "Satisfiability - * Modulo Transcendental Functions via Incremental - * Linearization" by Cimatti et al., CADE 2017. - * - * Examples: - * - * sin( x ) = -sin( -x ) - * ( PI > x > 0 ) => 0 < sin( x ) < 1 - * exp( x )>0 - * x<0 => exp( x )<1 - */ - std::vector checkTranscendentalInitialRefine(); - - /** check transcendental monotonic - * - * Returns a set of valid theory lemmas, based on a - * lemma scheme that ensures that applications - * of transcendental functions respect monotonicity. - * - * Examples: - * - * x > y => exp( x ) > exp( y ) - * PI/2 > x > y > 0 => sin( x ) > sin( y ) - * PI > x > y > PI/2 => sin( x ) < sin( y ) - */ - std::vector checkTranscendentalMonotonic(); - - /** check transcendental tangent planes - * - * Returns a set of valid theory lemmas, based on - * computing an "incremental linearization" of - * transcendental functions based on the model values - * of transcendental functions and their arguments. - * It is based on Figure 3 of "Satisfiability - * Modulo Transcendental Functions via Incremental - * Linearization" by Cimatti et al., CADE 2017. - * This schema is not terminating in general. - * It is not enabled by default, and can - * be enabled by --nl-ext-tf-tplanes. - * - * Example: - * - * Assume we have a term sin(y) where M( y ) = 1 where M is the current model. - * Note that: - * sin(1) ~= .841471 - * - * The Taylor series and remainder of sin(y) of degree 7 is - * P_{7,sin(0)}( x ) = x + (-1/6)*x^3 + (1/20)*x^5 - * R_{7,sin(0),b}( x ) = (-1/5040)*x^7 - * - * This gives us lower and upper bounds : - * P_u( x ) = P_{7,sin(0)}( x ) + R_{7,sin(0),b}( x ) - * ...where note P_u( 1 ) = 4243/5040 ~= .841865 - * P_l( x ) = P_{7,sin(0)}( x ) - R_{7,sin(0),b}( x ) - * ...where note P_l( 1 ) = 4241/5040 ~= .841468 - * - * Assume that M( sin(y) ) > P_u( 1 ). - * Since the concavity of sine in the region 0 < x < PI/2 is -1, - * we add a tangent plane refinement. - * The tangent plane at the point 1 in P_u is - * given by the formula: - * T( x ) = P_u( 1 ) + ((d/dx)(P_u(x)))( 1 )*( x - 1 ) - * We add the lemma: - * ( 0 < y < PI/2 ) => sin( y ) <= T( y ) - * which is: - * ( 0 < y < PI/2 ) => sin( y ) <= (391/720)*(y - 2737/1506) - * - * Assume that M( sin(y) ) < P_u( 1 ). - * Since the concavity of sine in the region 0 < x < PI/2 is -1, - * we add a secant plane refinement for some constants ( l, u ) - * such that 0 <= l < M( y ) < u <= PI/2. Assume we choose - * l = 0 and u = M( PI/2 ) = 150517/47912. - * The secant planes at point 1 for P_l - * are given by the formulas: - * S_l( x ) = (x-l)*(P_l( l )-P_l(c))/(l-1) + P_l( l ) - * S_u( x ) = (x-u)*(P_l( u )-P_l(c))/(u-1) + P_l( u ) - * We add the lemmas: - * ( 0 < y < 1 ) => sin( y ) >= S_l( y ) - * ( 1 < y < PI/2 ) => sin( y ) >= S_u( y ) - * which are: - * ( 0 < y < 1 ) => (sin y) >= 4251/5040*y - * ( 1 < y < PI/2 ) => (sin y) >= c1*(y+c2) - * where c1, c2 are rationals (for brevity, omitted here) - * such that c1 ~= .277 and c2 ~= 2.032. - * - * The argument lemSE is the "side effect" of the lemmas in the return - * value of this function (for details, see checkLastCall). - */ - std::vector checkTranscendentalTangentPlanes( - std::map& lemSE); - /** check transcendental function refinement for tf - * - * This method is called by the above method for each "master" - * transcendental function application that occurs in an assertion in the - * current context. For example, an application like sin(t) is not a master - * if we have introduced the constraints: - * t=y+2*pi*n ^ -pi <= y <= pi ^ sin(t) = sin(y). - * See d_trMaster/d_trSlaves for more detail. - * - * This runs Figure 3 of Cimatti et al., CADE 2017 for transcendental - * function application tf for Taylor degree d. It may add a secant or - * tangent plane lemma to lems and its side effect (if one exists) - * to lemSE. - * - * It returns false if the bounds are not precise enough to add a - * secant or tangent plane lemma. - */ - bool checkTfTangentPlanesFun(Node tf, - unsigned d, - std::vector& lems, - std::map& lemSE); //-------------------------------------------- end lemma schemas }; /* class NonlinearExtension */ diff --git a/src/theory/arith/transcendental_solver.cpp b/src/theory/arith/transcendental_solver.cpp new file mode 100644 index 000000000..665accc0a --- /dev/null +++ b/src/theory/arith/transcendental_solver.cpp @@ -0,0 +1,1475 @@ +/********************* */ +/*! \file transcendental_solver.cpp + ** \verbatim + ** Top contributors (to current version): + ** Andrew Reynolds + ** 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 solver for handling transcendental functions. + **/ + +#include "theory/arith/transcendental_solver.h" + +#include +#include + +#include "expr/node_algorithm.h" +#include "expr/node_builder.h" +#include "options/arith_options.h" +#include "theory/arith/arith_msum.h" +#include "theory/arith/arith_utilities.h" +#include "theory/rewriter.h" + +using namespace CVC4::kind; + +namespace CVC4 { +namespace theory { +namespace arith { + +TranscendentalSolver::TranscendentalSolver(NlModel& m) : d_model(m) +{ + NodeManager* nm = NodeManager::currentNM(); + d_true = nm->mkConst(true); + d_false = nm->mkConst(false); + d_zero = nm->mkConst(Rational(0)); + d_one = nm->mkConst(Rational(1)); + d_neg_one = nm->mkConst(Rational(-1)); + d_taylor_real_fv = nm->mkBoundVar("x", nm->realType()); + d_taylor_real_fv_base = nm->mkBoundVar("a", nm->realType()); + d_taylor_real_fv_base_rem = nm->mkBoundVar("b", nm->realType()); + d_taylor_degree = options::nlExtTfTaylorDegree(); +} + +TranscendentalSolver::~TranscendentalSolver() {} + +void TranscendentalSolver::initLastCall(const std::vector& assertions, + const std::vector& false_asserts, + const std::vector& xts, + std::vector& lems, + std::vector& lemsPp) +{ + d_funcCongClass.clear(); + d_funcMap.clear(); + d_tf_region.clear(); + + NodeManager* nm = NodeManager::currentNM(); + + // register the extended function terms + std::vector trNeedsMaster; + bool needPi = false; + // for computing congruence + std::map argTrie; + for (unsigned i = 0, xsize = xts.size(); i < xsize; i++) + { + Node a = xts[i]; + Kind ak = a.getKind(); + bool consider = true; + // if is an unpurified application of SINE, or it is a transcendental + // applied to a trancendental, purify. + if (isTranscendentalKind(ak)) + { + // if we've already computed master for a + if (d_trMaster.find(a) != d_trMaster.end()) + { + // a master has at least one slave + consider = (d_trSlaves.find(a) != d_trSlaves.end()); + } + else + { + if (ak == SINE) + { + // always not a master + consider = false; + } + else + { + for (const Node& ac : a) + { + if (isTranscendentalKind(ac.getKind())) + { + consider = false; + break; + } + } + } + if (!consider) + { + // wait to assign a master below + trNeedsMaster.push_back(a); + } + else + { + d_trMaster[a] = a; + d_trSlaves[a].insert(a); + } + } + } + if (ak == EXPONENTIAL || ak == SINE) + { + needPi = needPi || (ak == SINE); + // if we didn't indicate that it should be purified above + if (consider) + { + std::vector repList; + for (const Node& ac : a) + { + Node r = d_model.computeConcreteModelValue(ac); + repList.push_back(r); + } + Node aa = argTrie[ak].add(a, repList); + if (aa != a) + { + // apply congruence to pairs of terms that are disequal and congruent + Assert(aa.getNumChildren() == a.getNumChildren()); + Node mvaa = d_model.computeAbstractModelValue(a); + Node mvaaa = d_model.computeAbstractModelValue(aa); + if (mvaa != mvaaa) + { + std::vector exp; + for (unsigned j = 0, size = a.getNumChildren(); j < size; j++) + { + exp.push_back(a[j].eqNode(aa[j])); + } + Node expn = exp.size() == 1 ? exp[0] : nm->mkNode(AND, exp); + Node cong_lemma = nm->mkNode(OR, expn.negate(), a.eqNode(aa)); + lems.push_back(cong_lemma); + } + } + else + { + // new representative of congruence class + d_funcMap[ak].push_back(a); + } + // add to congruence class + d_funcCongClass[aa].push_back(a); + } + } + else if (ak == PI) + { + Assert(consider); + needPi = true; + d_funcMap[ak].push_back(a); + d_funcCongClass[a].push_back(a); + } + } + // initialize pi if necessary + if (needPi && d_pi.isNull()) + { + mkPi(); + getCurrentPiBounds(lems); + } + + if (!lems.empty()) + { + return; + } + + // process SINE phase shifting + for (const Node& a : trNeedsMaster) + { + // should not have processed this already + Assert(d_trMaster.find(a) == d_trMaster.end()); + Kind k = a.getKind(); + Assert(k == SINE || k == EXPONENTIAL); + Node y = + nm->mkSkolem("y", nm->realType(), "phase shifted trigonometric arg"); + Node new_a = nm->mkNode(k, y); + d_trSlaves[new_a].insert(new_a); + d_trSlaves[new_a].insert(a); + d_trMaster[a] = new_a; + d_trMaster[new_a] = new_a; + Node lem; + if (k == SINE) + { + Trace("nl-ext-tf") << "Basis sine : " << new_a << " for " << a + << std::endl; + Assert(!d_pi.isNull()); + Node shift = nm->mkSkolem("s", nm->integerType(), "number of shifts"); + // TODO : do not introduce shift here, instead needs model-based + // refinement for constant shifts (cvc4-projects #1284) + lem = nm->mkNode( + AND, + mkValidPhase(y, d_pi), + nm->mkNode( + ITE, + mkValidPhase(a[0], d_pi), + a[0].eqNode(y), + a[0].eqNode(nm->mkNode( + PLUS, + y, + nm->mkNode(MULT, nm->mkConst(Rational(2)), shift, d_pi)))), + new_a.eqNode(a)); + } + else + { + // do both equalities to ensure that new_a becomes a preregistered term + lem = nm->mkNode(AND, a.eqNode(new_a), a[0].eqNode(y)); + } + // note we must do preprocess on this lemma + Trace("nl-ext-lemma") << "NonlinearExtension::Lemma : purify : " << lem + << std::endl; + lemsPp.push_back(lem); + } + + if (Trace.isOn("nl-ext-mv")) + { + Trace("nl-ext-mv") << "Arguments of trancendental functions : " + << std::endl; + for (std::pair >& tfl : d_funcMap) + { + Kind k = tfl.first; + if (k == SINE || k == EXPONENTIAL) + { + for (const Node& tf : tfl.second) + { + Node v = tf[0]; + d_model.computeConcreteModelValue(v); + d_model.computeAbstractModelValue(v); + d_model.printModelValue("nl-ext-mv", v); + } + } + } + } +} + +bool TranscendentalSolver::preprocessAssertionsCheckModel( + std::vector& assertions) +{ + std::vector pvars; + std::vector psubs; + for (const std::pair& tb : d_trMaster) + { + pvars.push_back(tb.first); + psubs.push_back(tb.second); + } + + // initialize representation of assertions + std::vector passertions; + for (const Node& a : assertions) + + { + Node pa = a; + if (!pvars.empty()) + { + pa = arithSubstitute(pa, pvars, psubs); + pa = Rewriter::rewrite(pa); + } + if (!pa.isConst() || !pa.getConst()) + { + Trace("nl-ext-cm-assert") << "- assert : " << pa << std::endl; + passertions.push_back(pa); + } + } + // get model bounds for all transcendental functions + Trace("nl-ext-cm") << "----- Get bounds for transcendental functions..." + << std::endl; + for (std::pair >& tfs : d_funcMap) + { + Kind k = tfs.first; + for (const Node& tf : tfs.second) + { + Trace("nl-ext-cm") << "- Term: " << tf << std::endl; + bool success = true; + // tf is Figure 3 : tf( x ) + Node bl; + Node bu; + if (k == PI) + { + bl = d_pi_bound[0]; + bu = d_pi_bound[1]; + } + else + { + std::pair bounds = getTfModelBounds(tf, d_taylor_degree); + bl = bounds.first; + bu = bounds.second; + if (bl != bu) + { + d_model.setUsedApproximate(); + } + } + if (!bl.isNull() && !bu.isNull()) + { + // for each function in the congruence classe + for (const Node& ctf : d_funcCongClass[tf]) + { + // each term in congruence classes should be master terms + Assert(d_trSlaves.find(ctf) != d_trSlaves.end()); + // we set the bounds for each slave of tf + for (const Node& stf : d_trSlaves[ctf]) + { + Trace("nl-ext-cm") << "...bound for " << stf << " : [" << bl << ", " + << bu << "]" << std::endl; + success = d_model.addCheckModelBound(stf, bl, bu); + } + } + } + else + { + Trace("nl-ext-cm") << "...no bound for " << tf << std::endl; + } + if (!success) + { + // a bound was conflicting + Trace("nl-ext-cm") << "...failed to set bound for " << tf << std::endl; + Trace("nl-ext-cm") << "-----" << std::endl; + return false; + } + } + } + // replace the assertions + assertions = passertions; + return true; +} + +void TranscendentalSolver::incrementTaylorDegree() { d_taylor_degree++; } +unsigned TranscendentalSolver::getTaylorDegree() const +{ + return d_taylor_degree; +} + +void TranscendentalSolver::processSideEffect(const NlLemmaSideEffect& se) +{ + for (const std::tuple& sp : se.d_secantPoint) + { + Node tf = std::get<0>(sp); + unsigned d = std::get<1>(sp); + Node c = std::get<2>(sp); + d_secant_points[tf][d].push_back(c); + } +} + +void TranscendentalSolver::mkPi() +{ + NodeManager* nm = NodeManager::currentNM(); + if (d_pi.isNull()) + { + d_pi = nm->mkNullaryOperator(nm->realType(), PI); + d_pi_2 = Rewriter::rewrite( + nm->mkNode(MULT, d_pi, nm->mkConst(Rational(1) / Rational(2)))); + d_pi_neg_2 = Rewriter::rewrite( + nm->mkNode(MULT, d_pi, nm->mkConst(Rational(-1) / Rational(2)))); + d_pi_neg = + Rewriter::rewrite(nm->mkNode(MULT, d_pi, nm->mkConst(Rational(-1)))); + // initialize bounds + d_pi_bound[0] = nm->mkConst(Rational(103993) / Rational(33102)); + d_pi_bound[1] = nm->mkConst(Rational(104348) / Rational(33215)); + } +} + +void TranscendentalSolver::getCurrentPiBounds(std::vector& lemmas) +{ + NodeManager* nm = NodeManager::currentNM(); + Node pi_lem = nm->mkNode(AND, + nm->mkNode(GEQ, d_pi, d_pi_bound[0]), + nm->mkNode(LEQ, d_pi, d_pi_bound[1])); + lemmas.push_back(pi_lem); +} + +std::vector TranscendentalSolver::checkTranscendentalInitialRefine() +{ + NodeManager* nm = NodeManager::currentNM(); + std::vector lemmas; + Trace("nl-ext") + << "Get initial refinement lemmas for transcendental functions..." + << std::endl; + for (std::pair >& tfl : d_funcMap) + { + Kind k = tfl.first; + for (const Node& t : tfl.second) + { + // initial refinements + if (d_tf_initial_refine.find(t) == d_tf_initial_refine.end()) + { + d_tf_initial_refine[t] = true; + Node lem; + if (k == SINE) + { + Node symn = nm->mkNode(SINE, nm->mkNode(MULT, d_neg_one, t[0])); + symn = Rewriter::rewrite(symn); + // Can assume it is its own master since phase is split over 0, + // hence -pi <= t[0] <= pi implies -pi <= -t[0] <= pi. + d_trMaster[symn] = symn; + d_trSlaves[symn].insert(symn); + Assert(d_trSlaves.find(t) != d_trSlaves.end()); + std::vector children; + + lem = nm->mkNode(AND, + // bounds + nm->mkNode(AND, + nm->mkNode(LEQ, t, d_one), + nm->mkNode(GEQ, t, d_neg_one)), + // symmetry + nm->mkNode(PLUS, t, symn).eqNode(d_zero), + // sign + nm->mkNode(EQUAL, + nm->mkNode(LT, t[0], d_zero), + nm->mkNode(LT, t, d_zero)), + // zero val + nm->mkNode(EQUAL, + nm->mkNode(GT, t[0], d_zero), + nm->mkNode(GT, t, d_zero))); + lem = nm->mkNode( + AND, + lem, + // zero tangent + nm->mkNode(AND, + nm->mkNode(IMPLIES, + nm->mkNode(GT, t[0], d_zero), + nm->mkNode(LT, t, t[0])), + nm->mkNode(IMPLIES, + nm->mkNode(LT, t[0], d_zero), + nm->mkNode(GT, t, t[0]))), + // pi tangent + nm->mkNode( + AND, + nm->mkNode(IMPLIES, + nm->mkNode(LT, t[0], d_pi), + nm->mkNode(LT, t, nm->mkNode(MINUS, d_pi, t[0]))), + nm->mkNode( + IMPLIES, + nm->mkNode(GT, t[0], d_pi_neg), + nm->mkNode(GT, t, nm->mkNode(MINUS, d_pi_neg, t[0]))))); + } + else if (k == EXPONENTIAL) + { + // ( exp(x) > 0 ) ^ ( x=0 <=> exp( x ) = 1 ) ^ ( x < 0 <=> exp( x ) < + // 1 ) ^ ( x <= 0 V exp( x ) > x + 1 ) + lem = nm->mkNode( + AND, + nm->mkNode(GT, t, d_zero), + nm->mkNode(EQUAL, t[0].eqNode(d_zero), t.eqNode(d_one)), + nm->mkNode(EQUAL, + nm->mkNode(LT, t[0], d_zero), + nm->mkNode(LT, t, d_one)), + nm->mkNode(OR, + nm->mkNode(LEQ, t[0], d_zero), + nm->mkNode(GT, t, nm->mkNode(PLUS, t[0], d_one)))); + } + if (!lem.isNull()) + { + lemmas.push_back(lem); + } + } + } + } + + return lemmas; +} + +std::vector TranscendentalSolver::checkTranscendentalMonotonic() +{ + std::vector lemmas; + Trace("nl-ext") << "Get monotonicity lemmas for transcendental functions..." + << std::endl; + + // sort arguments of all transcendentals + std::map > sorted_tf_args; + std::map > tf_arg_to_term; + + for (std::pair >& tfl : d_funcMap) + { + Kind k = tfl.first; + if (k == EXPONENTIAL || k == SINE) + { + for (const Node& tf : tfl.second) + { + Node a = tf[0]; + Node mvaa = d_model.computeAbstractModelValue(a); + if (mvaa.isConst()) + { + Trace("nl-ext-tf-mono-debug") << "...tf term : " << a << std::endl; + sorted_tf_args[k].push_back(a); + tf_arg_to_term[k][a] = tf; + } + } + } + } + + SortNlModel smv; + smv.d_nlm = &d_model; + // sort by concrete values + smv.d_isConcrete = true; + smv.d_reverse_order = true; + for (std::pair >& tfl : d_funcMap) + { + Kind k = tfl.first; + if (!sorted_tf_args[k].empty()) + { + std::sort(sorted_tf_args[k].begin(), sorted_tf_args[k].end(), smv); + Trace("nl-ext-tf-mono") << "Sorted transcendental function list for " << k + << " : " << std::endl; + for (unsigned i = 0; i < sorted_tf_args[k].size(); i++) + { + Node targ = sorted_tf_args[k][i]; + Node mvatarg = d_model.computeAbstractModelValue(targ); + Trace("nl-ext-tf-mono") + << " " << targ << " -> " << mvatarg << std::endl; + Node t = tf_arg_to_term[k][targ]; + Node mvat = d_model.computeAbstractModelValue(t); + Trace("nl-ext-tf-mono") << " f-val : " << mvat << std::endl; + } + std::vector mpoints; + std::vector mpoints_vals; + if (k == SINE) + { + mpoints.push_back(d_pi); + mpoints.push_back(d_pi_2); + mpoints.push_back(d_zero); + mpoints.push_back(d_pi_neg_2); + mpoints.push_back(d_pi_neg); + } + else if (k == EXPONENTIAL) + { + mpoints.push_back(Node::null()); + } + if (!mpoints.empty()) + { + // get model values for points + for (unsigned i = 0; i < mpoints.size(); i++) + { + Node mpv; + if (!mpoints[i].isNull()) + { + mpv = d_model.computeAbstractModelValue(mpoints[i]); + Assert(mpv.isConst()); + } + mpoints_vals.push_back(mpv); + } + + unsigned mdir_index = 0; + int monotonic_dir = -1; + Node mono_bounds[2]; + Node targ, targval, t, tval; + for (unsigned i = 0, size = sorted_tf_args[k].size(); i < size; i++) + { + Node sarg = sorted_tf_args[k][i]; + Node sargval = d_model.computeAbstractModelValue(sarg); + Assert(sargval.isConst()); + Node s = tf_arg_to_term[k][sarg]; + Node sval = d_model.computeAbstractModelValue(s); + Assert(sval.isConst()); + + // increment to the proper monotonicity region + bool increment = true; + while (increment && mdir_index < mpoints.size()) + { + increment = false; + if (mpoints[mdir_index].isNull()) + { + increment = true; + } + else + { + Node pval = mpoints_vals[mdir_index]; + Assert(pval.isConst()); + if (sargval.getConst() < pval.getConst()) + { + increment = true; + Trace("nl-ext-tf-mono") << "...increment at " << sarg + << " since model value is less than " + << mpoints[mdir_index] << std::endl; + } + } + if (increment) + { + tval = Node::null(); + mono_bounds[1] = mpoints[mdir_index]; + mdir_index++; + monotonic_dir = regionToMonotonicityDir(k, mdir_index); + if (mdir_index < mpoints.size()) + { + mono_bounds[0] = mpoints[mdir_index]; + } + else + { + mono_bounds[0] = Node::null(); + } + } + } + // store the concavity region + d_tf_region[s] = mdir_index; + Trace("nl-ext-concavity") << "Transcendental function " << s + << " is in region #" << mdir_index; + Trace("nl-ext-concavity") + << ", arg model value = " << sargval << std::endl; + + if (!tval.isNull()) + { + NodeManager* nm = NodeManager::currentNM(); + Node mono_lem; + if (monotonic_dir == 1 + && sval.getConst() > tval.getConst()) + { + mono_lem = nm->mkNode( + IMPLIES, nm->mkNode(GEQ, targ, sarg), nm->mkNode(GEQ, t, s)); + } + else if (monotonic_dir == -1 + && sval.getConst() < tval.getConst()) + { + mono_lem = nm->mkNode( + IMPLIES, nm->mkNode(LEQ, targ, sarg), nm->mkNode(LEQ, t, s)); + } + if (!mono_lem.isNull()) + { + if (!mono_bounds[0].isNull()) + { + Assert(!mono_bounds[1].isNull()); + mono_lem = nm->mkNode( + IMPLIES, + nm->mkNode(AND, + mkBounded(mono_bounds[0], targ, mono_bounds[1]), + mkBounded(mono_bounds[0], sarg, mono_bounds[1])), + mono_lem); + } + Trace("nl-ext-tf-mono") + << "Monotonicity lemma : " << mono_lem << std::endl; + lemmas.push_back(mono_lem); + } + } + // store the previous values + targ = sarg; + targval = sargval; + t = s; + tval = sval; + } + } + } + } + return lemmas; +} + +std::vector TranscendentalSolver::checkTranscendentalTangentPlanes( + std::map& lemSE) +{ + std::vector lemmas; + Trace("nl-ext") << "Get tangent plane lemmas for transcendental functions..." + << std::endl; + // this implements Figure 3 of "Satisfiaility Modulo Transcendental Functions + // via Incremental Linearization" by Cimatti et al + for (std::pair >& tfs : d_funcMap) + { + Kind k = tfs.first; + if (k == PI) + { + // We do not use Taylor approximation for PI currently. + // This is because the convergence is extremely slow, and hence an + // initial approximation is superior. + continue; + } + Trace("nl-ext-tftp-debug2") << "Taylor variables: " << std::endl; + Trace("nl-ext-tftp-debug2") + << " taylor_real_fv : " << d_taylor_real_fv << std::endl; + Trace("nl-ext-tftp-debug2") + << " taylor_real_fv_base : " << d_taylor_real_fv_base << std::endl; + Trace("nl-ext-tftp-debug2") + << " taylor_real_fv_base_rem : " << d_taylor_real_fv_base_rem + << std::endl; + Trace("nl-ext-tftp-debug2") << std::endl; + + // we substitute into the Taylor sum P_{n,f(0)}( x ) + + for (const Node& tf : tfs.second) + { + // tf is Figure 3 : tf( x ) + Trace("nl-ext-tftp") << "Compute tangent planes " << tf << std::endl; + // go until max degree is reached, or we don't meet bound criteria + for (unsigned d = 1; d <= d_taylor_degree; d++) + { + Trace("nl-ext-tftp") << "- run at degree " << d << "..." << std::endl; + unsigned prev = lemmas.size(); + if (checkTfTangentPlanesFun(tf, d, lemmas, lemSE)) + { + Trace("nl-ext-tftp") + << "...fail, #lemmas = " << (lemmas.size() - prev) << std::endl; + break; + } + else + { + Trace("nl-ext-tftp") << "...success" << std::endl; + } + } + } + } + + return lemmas; +} + +bool TranscendentalSolver::checkTfTangentPlanesFun( + Node tf, + unsigned d, + std::vector& lemmas, + std::map& lemSE) +{ + NodeManager* nm = NodeManager::currentNM(); + Kind k = tf.getKind(); + // this should only be run on master applications + Assert(d_trSlaves.find(tf) != d_trSlaves.end()); + + // Figure 3 : c + Node c = d_model.computeAbstractModelValue(tf[0]); + int csign = c.getConst().sgn(); + if (csign == 0) + { + // no secant/tangent plane is necessary + return true; + } + Assert(csign == 1 || csign == -1); + + // Figure 3: P_l, P_u + // mapped to for signs of c + std::map poly_approx_bounds[2]; + std::vector pbounds; + getPolynomialApproximationBoundForArg(k, c, d, pbounds); + poly_approx_bounds[0][1] = pbounds[0]; + poly_approx_bounds[0][-1] = pbounds[1]; + poly_approx_bounds[1][1] = pbounds[2]; + poly_approx_bounds[1][-1] = pbounds[3]; + + // Figure 3 : v + Node v = d_model.computeAbstractModelValue(tf); + + // check value of tf + Trace("nl-ext-tftp-debug") << "Process tangent plane refinement for " << tf + << ", degree " << d << "..." << std::endl; + Trace("nl-ext-tftp-debug") << " value in model : " << v << std::endl; + Trace("nl-ext-tftp-debug") << " arg value in model : " << c << std::endl; + + std::vector taylor_vars; + taylor_vars.push_back(d_taylor_real_fv); + + // compute the concavity + int region = -1; + std::unordered_map::iterator itr = + d_tf_region.find(tf); + if (itr != d_tf_region.end()) + { + region = itr->second; + Trace("nl-ext-tftp-debug") << " region is : " << region << std::endl; + } + // Figure 3 : conc + int concavity = regionToConcavity(k, itr->second); + Trace("nl-ext-tftp-debug") << " concavity is : " << concavity << std::endl; + if (concavity == 0) + { + // no secant/tangent plane is necessary + return true; + } + // bounds for which we are this concavity + // Figure 3: < l, u > + Node bounds[2]; + if (k == SINE) + { + bounds[0] = regionToLowerBound(k, region); + Assert(!bounds[0].isNull()); + bounds[1] = regionToUpperBound(k, region); + Assert(!bounds[1].isNull()); + } + + // Figure 3: P + Node poly_approx; + + // compute whether this is a tangent refinement or a secant refinement + bool is_tangent = false; + bool is_secant = false; + std::pair mvb = getTfModelBounds(tf, d); + for (unsigned r = 0; r < 2; r++) + { + Node pab = poly_approx_bounds[r][csign]; + Node v_pab = r == 0 ? mvb.first : mvb.second; + if (!v_pab.isNull()) + { + Trace("nl-ext-tftp-debug2") + << "...model value of " << pab << " is " << v_pab << std::endl; + + Assert(v_pab.isConst()); + Node comp = nm->mkNode(r == 0 ? LT : GT, v, v_pab); + Trace("nl-ext-tftp-debug2") << "...compare : " << comp << std::endl; + Node compr = Rewriter::rewrite(comp); + Trace("nl-ext-tftp-debug2") << "...got : " << compr << std::endl; + if (compr == d_true) + { + // beyond the bounds + if (r == 0) + { + poly_approx = poly_approx_bounds[r][csign]; + is_tangent = concavity == 1; + is_secant = concavity == -1; + } + else + { + poly_approx = poly_approx_bounds[r][csign]; + is_tangent = concavity == -1; + is_secant = concavity == 1; + } + if (Trace.isOn("nl-ext-tftp")) + { + Trace("nl-ext-tftp") << "*** Outside boundary point ("; + Trace("nl-ext-tftp") << (r == 0 ? "low" : "high") << ") "; + printRationalApprox("nl-ext-tftp", v_pab); + Trace("nl-ext-tftp") << ", will refine..." << std::endl; + Trace("nl-ext-tftp") + << " poly_approx = " << poly_approx << std::endl; + Trace("nl-ext-tftp") + << " is_tangent = " << is_tangent << std::endl; + Trace("nl-ext-tftp") << " is_secant = " << is_secant << std::endl; + } + break; + } + else + { + Trace("nl-ext-tftp") + << " ...within " << (r == 0 ? "low" : "high") << " bound : "; + printRationalApprox("nl-ext-tftp", v_pab); + Trace("nl-ext-tftp") << std::endl; + } + } + } + + // Figure 3: P( c ) + Node poly_approx_c; + if (is_tangent || is_secant) + { + Assert(!poly_approx.isNull()); + std::vector taylor_subs; + taylor_subs.push_back(c); + Assert(taylor_vars.size() == taylor_subs.size()); + poly_approx_c = poly_approx.substitute(taylor_vars.begin(), + taylor_vars.end(), + taylor_subs.begin(), + taylor_subs.end()); + Trace("nl-ext-tftp-debug2") + << "...poly approximation at c is " << poly_approx_c << std::endl; + } + else + { + // we may want to continue getting better bounds + return false; + } + + if (is_tangent) + { + // compute tangent plane + // Figure 3: T( x ) + // We use zero slope tangent planes, since the concavity of the Taylor + // approximation cannot be easily established. + Node tplane = poly_approx_c; + + Node lem = nm->mkNode(concavity == 1 ? GEQ : LEQ, tf, tplane); + std::vector antec; + int mdir = regionToMonotonicityDir(k, region); + for (unsigned i = 0; i < 2; i++) + { + // Tangent plane is valid in the interval [c,u) if the slope of the + // function matches its concavity, and is valid in (l, c] otherwise. + Node use_bound = (mdir == concavity) == (i == 0) ? c : bounds[i]; + if (!use_bound.isNull()) + { + Node ant = nm->mkNode(i == 0 ? GEQ : LEQ, tf[0], use_bound); + antec.push_back(ant); + } + } + if (!antec.empty()) + { + Node antec_n = antec.size() == 1 ? antec[0] : nm->mkNode(AND, antec); + lem = nm->mkNode(IMPLIES, antec_n, lem); + } + Trace("nl-ext-tftp-debug2") + << "*** Tangent plane lemma (pre-rewrite): " << lem << std::endl; + lem = Rewriter::rewrite(lem); + Trace("nl-ext-tftp-lemma") + << "*** Tangent plane lemma : " << lem << std::endl; + Assert(d_model.computeAbstractModelValue(lem) == d_false); + // Figure 3 : line 9 + lemmas.push_back(lem); + } + else if (is_secant) + { + // bounds are the minimum and maximum previous secant points + // should not repeat secant points: secant lemmas should suffice to + // rule out previous assignment + Assert(std::find( + d_secant_points[tf][d].begin(), d_secant_points[tf][d].end(), c) + == d_secant_points[tf][d].end()); + // Insert into the (temporary) vector. We do not update this vector + // until we are sure this secant plane lemma has been processed. We do + // this by mapping the lemma to a side effect below. + std::vector spoints = d_secant_points[tf][d]; + spoints.push_back(c); + + // sort + SortNlModel smv; + smv.d_nlm = &d_model; + smv.d_isConcrete = true; + std::sort(spoints.begin(), spoints.end(), smv); + // get the resulting index of c + unsigned index = + std::find(spoints.begin(), spoints.end(), c) - spoints.begin(); + // bounds are the next closest upper/lower bound values + if (index > 0) + { + bounds[0] = spoints[index - 1]; + } + else + { + // otherwise, we use the lower boundary point for this concavity + // region + if (k == SINE) + { + Assert(!bounds[0].isNull()); + } + else if (k == EXPONENTIAL) + { + // pick c-1 + bounds[0] = Rewriter::rewrite(nm->mkNode(MINUS, c, d_one)); + } + } + if (index < spoints.size() - 1) + { + bounds[1] = spoints[index + 1]; + } + else + { + // otherwise, we use the upper boundary point for this concavity + // region + if (k == SINE) + { + Assert(!bounds[1].isNull()); + } + else if (k == EXPONENTIAL) + { + // pick c+1 + bounds[1] = Rewriter::rewrite(nm->mkNode(PLUS, c, d_one)); + } + } + Trace("nl-ext-tftp-debug2") << "...secant bounds are : " << bounds[0] + << " ... " << bounds[1] << std::endl; + + // the secant plane may be conjunction of 1-2 guarded inequalities + std::vector lemmaConj; + for (unsigned s = 0; s < 2; s++) + { + // compute secant plane + Assert(!poly_approx.isNull()); + Assert(!bounds[s].isNull()); + // take the model value of l or u (since may contain PI) + Node b = d_model.computeAbstractModelValue(bounds[s]); + Trace("nl-ext-tftp-debug2") << "...model value of bound " << bounds[s] + << " is " << b << std::endl; + Assert(b.isConst()); + if (c != b) + { + // Figure 3 : P(l), P(u), for s = 0,1 + Node poly_approx_b; + std::vector taylor_subs; + taylor_subs.push_back(b); + Assert(taylor_vars.size() == taylor_subs.size()); + poly_approx_b = poly_approx.substitute(taylor_vars.begin(), + taylor_vars.end(), + taylor_subs.begin(), + taylor_subs.end()); + // Figure 3: S_l( x ), S_u( x ) for s = 0,1 + Node splane; + Node rcoeff_n = Rewriter::rewrite(nm->mkNode(MINUS, b, c)); + Assert(rcoeff_n.isConst()); + Rational rcoeff = rcoeff_n.getConst(); + Assert(rcoeff.sgn() != 0); + poly_approx_b = Rewriter::rewrite(poly_approx_b); + poly_approx_c = Rewriter::rewrite(poly_approx_c); + splane = nm->mkNode( + PLUS, + poly_approx_b, + nm->mkNode(MULT, + nm->mkNode(MINUS, poly_approx_b, poly_approx_c), + nm->mkConst(Rational(1) / rcoeff), + nm->mkNode(MINUS, tf[0], b))); + + Node lem = nm->mkNode(concavity == 1 ? LEQ : GEQ, tf, splane); + // With respect to Figure 3, this is slightly different. + // In particular, we chose b to be the model value of bounds[s], + // which is a constant although bounds[s] may not be (e.g. if it + // contains PI). + // To ensure that c...b does not cross an inflection point, + // we guard with the symbolic version of bounds[s]. + // This leads to lemmas e.g. of this form: + // ( c <= x <= PI/2 ) => ( sin(x) < ( P( b ) - P( c ) )*( x - + // b ) + P( b ) ) + // where b = (PI/2)^M, the current value of PI/2 in the model. + // This is sound since we are guarded by the symbolic + // representation of PI/2. + Node antec_n = + nm->mkNode(AND, + nm->mkNode(GEQ, tf[0], s == 0 ? bounds[s] : c), + nm->mkNode(LEQ, tf[0], s == 0 ? c : bounds[s])); + lem = nm->mkNode(IMPLIES, antec_n, lem); + Trace("nl-ext-tftp-debug2") + << "*** Secant plane lemma (pre-rewrite) : " << lem << std::endl; + lem = Rewriter::rewrite(lem); + Trace("nl-ext-tftp-lemma") + << "*** Secant plane lemma : " << lem << std::endl; + lemmaConj.push_back(lem); + Assert(d_model.computeAbstractModelValue(lem) == d_false); + } + } + // Figure 3 : line 22 + Assert(!lemmaConj.empty()); + Node lem = + lemmaConj.size() == 1 ? lemmaConj[0] : nm->mkNode(AND, lemmaConj); + lemmas.push_back(lem); + // The side effect says that if lem is added, then we should add the + // secant point c for (tf,d). + lemSE[lem].d_secantPoint.push_back(std::make_tuple(tf, d, c)); + } + return true; +} + +int TranscendentalSolver::regionToMonotonicityDir(Kind k, int region) +{ + if (k == EXPONENTIAL) + { + if (region == 1) + { + return 1; + } + } + else if (k == SINE) + { + if (region == 1 || region == 4) + { + return -1; + } + else if (region == 2 || region == 3) + { + return 1; + } + } + return 0; +} + +int TranscendentalSolver::regionToConcavity(Kind k, int region) +{ + if (k == EXPONENTIAL) + { + if (region == 1) + { + return 1; + } + } + else if (k == SINE) + { + if (region == 1 || region == 2) + { + return -1; + } + else if (region == 3 || region == 4) + { + return 1; + } + } + return 0; +} + +Node TranscendentalSolver::regionToLowerBound(Kind k, int region) +{ + if (k == SINE) + { + if (region == 1) + { + return d_pi_2; + } + else if (region == 2) + { + return d_zero; + } + else if (region == 3) + { + return d_pi_neg_2; + } + else if (region == 4) + { + return d_pi_neg; + } + } + return Node::null(); +} + +Node TranscendentalSolver::regionToUpperBound(Kind k, int region) +{ + if (k == SINE) + { + if (region == 1) + { + return d_pi; + } + else if (region == 2) + { + return d_pi_2; + } + else if (region == 3) + { + return d_zero; + } + else if (region == 4) + { + return d_pi_neg_2; + } + } + return Node::null(); +} + +Node TranscendentalSolver::getDerivative(Node n, Node x) +{ + NodeManager* nm = NodeManager::currentNM(); + Assert(x.isVar()); + // only handle the cases of the taylor expansion of d + if (n.getKind() == EXPONENTIAL) + { + if (n[0] == x) + { + return n; + } + } + else if (n.getKind() == SINE) + { + if (n[0] == x) + { + Node na = nm->mkNode(MINUS, d_pi_2, n[0]); + Node ret = nm->mkNode(SINE, na); + ret = Rewriter::rewrite(ret); + return ret; + } + } + else if (n.getKind() == PLUS) + { + std::vector dchildren; + for (unsigned i = 0; i < n.getNumChildren(); i++) + { + // PLUS is flattened in rewriter, recursion depth is bounded by 1 + Node dc = getDerivative(n[i], x); + if (dc.isNull()) + { + return dc; + } + else + { + dchildren.push_back(dc); + } + } + return nm->mkNode(PLUS, dchildren); + } + else if (n.getKind() == MULT) + { + Assert(n[0].isConst()); + Node dc = getDerivative(n[1], x); + if (!dc.isNull()) + { + return nm->mkNode(MULT, n[0], dc); + } + } + else if (n.getKind() == NONLINEAR_MULT) + { + unsigned xcount = 0; + std::vector children; + unsigned xindex = 0; + for (unsigned i = 0, size = n.getNumChildren(); i < size; i++) + { + if (n[i] == x) + { + xcount++; + xindex = i; + } + children.push_back(n[i]); + } + if (xcount == 0) + { + return d_zero; + } + else + { + children[xindex] = nm->mkConst(Rational(xcount)); + } + return nm->mkNode(MULT, children); + } + else if (n.isVar()) + { + return n == x ? d_one : d_zero; + } + else if (n.isConst()) + { + return d_zero; + } + Trace("nl-ext-debug") << "No derivative computed for " << n; + Trace("nl-ext-debug") << " for d/d{" << x << "}" << std::endl; + return Node::null(); +} + +std::pair TranscendentalSolver::getTaylor(Node fa, unsigned n) +{ + NodeManager* nm = NodeManager::currentNM(); + Assert(n > 0); + Node fac; // what term we cache for fa + if (fa[0] == d_zero) + { + // optimization : simpler to compute (x-fa[0])^n if we are centered around 0 + fac = fa; + } + else + { + // otherwise we use a standard factor a in (x-a)^n + fac = nm->mkNode(fa.getKind(), d_taylor_real_fv_base); + } + Node taylor_rem; + Node taylor_sum; + // check if we have already computed this Taylor series + std::unordered_map::iterator itt = d_taylor_sum[fac].find(n); + if (itt == d_taylor_sum[fac].end()) + { + Node i_exp_base; + if (fa[0] == d_zero) + { + i_exp_base = d_taylor_real_fv; + } + else + { + i_exp_base = Rewriter::rewrite( + nm->mkNode(MINUS, d_taylor_real_fv, d_taylor_real_fv_base)); + } + Node i_derv = fac; + Node i_fact = d_one; + Node i_exp = d_one; + int i_derv_status = 0; + unsigned counter = 0; + std::vector sum; + do + { + counter++; + if (fa.getKind() == EXPONENTIAL) + { + // unchanged + } + else if (fa.getKind() == SINE) + { + if (i_derv_status % 2 == 1) + { + Node arg = nm->mkNode(PLUS, d_pi_2, d_taylor_real_fv_base); + i_derv = nm->mkNode(SINE, arg); + } + else + { + i_derv = fa; + } + if (i_derv_status >= 2) + { + i_derv = nm->mkNode(MINUS, d_zero, i_derv); + } + i_derv = Rewriter::rewrite(i_derv); + i_derv_status = i_derv_status == 3 ? 0 : i_derv_status + 1; + } + if (counter == (n + 1)) + { + TNode x = d_taylor_real_fv_base; + i_derv = i_derv.substitute(x, d_taylor_real_fv_base_rem); + } + Node curr = nm->mkNode(MULT, nm->mkNode(DIVISION, i_derv, i_fact), i_exp); + if (counter == (n + 1)) + { + taylor_rem = curr; + } + else + { + sum.push_back(curr); + i_fact = Rewriter::rewrite( + nm->mkNode(MULT, nm->mkConst(Rational(counter)), i_fact)); + i_exp = Rewriter::rewrite(nm->mkNode(MULT, i_exp_base, i_exp)); + } + } while (counter <= n); + taylor_sum = sum.size() == 1 ? sum[0] : nm->mkNode(PLUS, sum); + + if (fac[0] != d_taylor_real_fv_base) + { + TNode x = d_taylor_real_fv_base; + taylor_sum = taylor_sum.substitute(x, fac[0]); + } + + // cache + d_taylor_sum[fac][n] = taylor_sum; + d_taylor_rem[fac][n] = taylor_rem; + } + else + { + taylor_sum = itt->second; + Assert(d_taylor_rem[fac].find(n) != d_taylor_rem[fac].end()); + taylor_rem = d_taylor_rem[fac][n]; + } + + // must substitute for the argument if we were using a different lookup + if (fa[0] != fac[0]) + { + TNode x = d_taylor_real_fv_base; + taylor_sum = taylor_sum.substitute(x, fa[0]); + } + return std::pair(taylor_sum, taylor_rem); +} + +void TranscendentalSolver::getPolynomialApproximationBounds( + Kind k, unsigned d, std::vector& pbounds) +{ + if (d_poly_bounds[k][d].empty()) + { + NodeManager* nm = NodeManager::currentNM(); + Node tft = nm->mkNode(k, d_zero); + // n is the Taylor degree we are currently considering + unsigned n = 2 * d; + // n must be even + std::pair taylor = getTaylor(tft, n); + Trace("nl-ext-tftp-debug2") + << "Taylor for " << k << " is : " << taylor.first << std::endl; + Node taylor_sum = Rewriter::rewrite(taylor.first); + Trace("nl-ext-tftp-debug2") + << "Taylor for " << k << " is (post-rewrite) : " << taylor_sum + << std::endl; + Assert(taylor.second.getKind() == MULT); + Assert(taylor.second.getNumChildren() == 2); + Assert(taylor.second[0].getKind() == DIVISION); + Trace("nl-ext-tftp-debug2") + << "Taylor remainder for " << k << " is " << taylor.second << std::endl; + // ru is x^{n+1}/(n+1)! + Node ru = nm->mkNode(DIVISION, taylor.second[1], taylor.second[0][1]); + ru = Rewriter::rewrite(ru); + Trace("nl-ext-tftp-debug2") + << "Taylor remainder factor is (post-rewrite) : " << ru << std::endl; + if (k == EXPONENTIAL) + { + pbounds.push_back(taylor_sum); + pbounds.push_back(taylor_sum); + pbounds.push_back(Rewriter::rewrite( + nm->mkNode(MULT, taylor_sum, nm->mkNode(PLUS, d_one, ru)))); + pbounds.push_back(Rewriter::rewrite(nm->mkNode(PLUS, taylor_sum, ru))); + } + else + { + Assert(k == SINE); + Node l = Rewriter::rewrite(nm->mkNode(MINUS, taylor_sum, ru)); + Node u = Rewriter::rewrite(nm->mkNode(PLUS, taylor_sum, ru)); + pbounds.push_back(l); + pbounds.push_back(l); + pbounds.push_back(u); + pbounds.push_back(u); + } + Trace("nl-ext-tf-tplanes") + << "Polynomial approximation for " << k << " is: " << std::endl; + Trace("nl-ext-tf-tplanes") << " Lower (pos): " << pbounds[0] << std::endl; + Trace("nl-ext-tf-tplanes") << " Upper (pos): " << pbounds[2] << std::endl; + Trace("nl-ext-tf-tplanes") << " Lower (neg): " << pbounds[1] << std::endl; + Trace("nl-ext-tf-tplanes") << " Upper (neg): " << pbounds[3] << std::endl; + d_poly_bounds[k][d].insert( + d_poly_bounds[k][d].end(), pbounds.begin(), pbounds.end()); + } + else + { + pbounds.insert( + pbounds.end(), d_poly_bounds[k][d].begin(), d_poly_bounds[k][d].end()); + } +} + +void TranscendentalSolver::getPolynomialApproximationBoundForArg( + Kind k, Node c, unsigned d, std::vector& pbounds) +{ + getPolynomialApproximationBounds(k, d, pbounds); + Assert(c.isConst()); + if (k == EXPONENTIAL && c.getConst().sgn() == 1) + { + NodeManager* nm = NodeManager::currentNM(); + Node tft = nm->mkNode(k, d_zero); + bool success = false; + unsigned ds = d; + TNode ttrf = d_taylor_real_fv; + TNode tc = c; + do + { + success = true; + unsigned n = 2 * ds; + std::pair taylor = getTaylor(tft, n); + // check that 1-c^{n+1}/(n+1)! > 0 + Node ru = nm->mkNode(DIVISION, taylor.second[1], taylor.second[0][1]); + Node rus = ru.substitute(ttrf, tc); + rus = Rewriter::rewrite(rus); + Assert(rus.isConst()); + if (rus.getConst() > d_one.getConst()) + { + success = false; + ds = ds + 1; + } + } while (!success); + if (ds > d) + { + Trace("nl-ext-exp-taylor") + << "*** Increase Taylor bound to " << ds << " > " << d << " for (" + << k << " " << c << ")" << std::endl; + // must use sound upper bound + std::vector pboundss; + getPolynomialApproximationBounds(k, ds, pboundss); + pbounds[2] = pboundss[2]; + } + } +} + +std::pair TranscendentalSolver::getTfModelBounds(Node tf, + unsigned d) +{ + // compute the model value of the argument + Node c = d_model.computeAbstractModelValue(tf[0]); + Assert(c.isConst()); + int csign = c.getConst().sgn(); + Kind k = tf.getKind(); + if (csign == 0) + { + // at zero, its trivial + if (k == SINE) + { + return std::pair(d_zero, d_zero); + } + Assert(k == EXPONENTIAL); + return std::pair(d_one, d_one); + } + bool isNeg = csign == -1; + + std::vector pbounds; + getPolynomialApproximationBoundForArg(k, c, d, pbounds); + + std::vector bounds; + TNode tfv = d_taylor_real_fv; + TNode tfs = tf[0]; + for (unsigned d2 = 0; d2 < 2; d2++) + { + int index = d2 == 0 ? (isNeg ? 1 : 0) : (isNeg ? 3 : 2); + Node pab = pbounds[index]; + if (!pab.isNull()) + { + // { x -> tf[0] } + pab = pab.substitute(tfv, tfs); + pab = Rewriter::rewrite(pab); + Node v_pab = d_model.computeAbstractModelValue(pab); + bounds.push_back(v_pab); + } + else + { + bounds.push_back(Node::null()); + } + } + return std::pair(bounds[0], bounds[1]); +} + +Node TranscendentalSolver::mkValidPhase(Node a, Node pi) +{ + return mkBounded( + NodeManager::currentNM()->mkNode(MULT, mkRationalNode(-1), pi), a, pi); +} + +} // namespace arith +} // namespace theory +} // namespace CVC4 diff --git a/src/theory/arith/transcendental_solver.h b/src/theory/arith/transcendental_solver.h new file mode 100644 index 000000000..8e539bbf0 --- /dev/null +++ b/src/theory/arith/transcendental_solver.h @@ -0,0 +1,419 @@ +/********************* */ +/*! \file transcendental_solver.h + ** \verbatim + ** Top contributors (to current version): + ** Andrew Reynolds + ** 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 Solving for handling transcendental functions. + **/ + +#ifndef CVC4__THEORY__ARITH__TRANSCENDENTAL_SOLVER_H +#define CVC4__THEORY__ARITH__TRANSCENDENTAL_SOLVER_H + +#include +#include +#include +#include + +#include "expr/node.h" +#include "theory/arith/nl_lemma_utils.h" +#include "theory/arith/nl_model.h" + +namespace CVC4 { +namespace theory { +namespace arith { + +/** Transcendental solver class + * + * This class implements model-based refinement schemes + * for transcendental functions, described in: + * + * - "Satisfiability Modulo Transcendental + * Functions via Incremental Linearization" by Cimatti + * et al., CADE 2017. + * + * It's main functionality are methods that implement lemma schemas below, + * which return a set of lemmas that should be sent on the output channel. + */ +class TranscendentalSolver +{ + public: + TranscendentalSolver(NlModel& m); + ~TranscendentalSolver(); + + /** init last call + */ + void initLastCall(const std::vector& assertions, + const std::vector& false_asserts, + const std::vector& xts, + std::vector& lems, + std::vector& lemsPp); + /** increment taylor degree */ + void incrementTaylorDegree(); + /** get taylor degree */ + unsigned getTaylorDegree() const; + /** preprocess assertions check model + * + * This modifies the given assertions in preparation for running a call + * to check model. + * + * This method returns false if a bound for a transcendental function + * was conflicting. + */ + bool preprocessAssertionsCheckModel(std::vector& assertions); + /** Process side effect se */ + void processSideEffect(const NlLemmaSideEffect& se); + //-------------------------------------------- lemma schemas + /** check transcendental initial refine + * + * Returns a set of valid theory lemmas, based on + * simple facts about transcendental functions. + * This mostly follows the initial axioms described in + * Section 4 of "Satisfiability + * Modulo Transcendental Functions via Incremental + * Linearization" by Cimatti et al., CADE 2017. + * + * Examples: + * + * sin( x ) = -sin( -x ) + * ( PI > x > 0 ) => 0 < sin( x ) < 1 + * exp( x )>0 + * x<0 => exp( x )<1 + */ + std::vector checkTranscendentalInitialRefine(); + + /** check transcendental monotonic + * + * Returns a set of valid theory lemmas, based on a + * lemma scheme that ensures that applications + * of transcendental functions respect monotonicity. + * + * Examples: + * + * x > y => exp( x ) > exp( y ) + * PI/2 > x > y > 0 => sin( x ) > sin( y ) + * PI > x > y > PI/2 => sin( x ) < sin( y ) + */ + std::vector checkTranscendentalMonotonic(); + + /** check transcendental tangent planes + * + * Returns a set of valid theory lemmas, based on + * computing an "incremental linearization" of + * transcendental functions based on the model values + * of transcendental functions and their arguments. + * It is based on Figure 3 of "Satisfiability + * Modulo Transcendental Functions via Incremental + * Linearization" by Cimatti et al., CADE 2017. + * This schema is not terminating in general. + * It is not enabled by default, and can + * be enabled by --nl-ext-tf-tplanes. + * + * Example: + * + * Assume we have a term sin(y) where M( y ) = 1 where M is the current model. + * Note that: + * sin(1) ~= .841471 + * + * The Taylor series and remainder of sin(y) of degree 7 is + * P_{7,sin(0)}( x ) = x + (-1/6)*x^3 + (1/20)*x^5 + * R_{7,sin(0),b}( x ) = (-1/5040)*x^7 + * + * This gives us lower and upper bounds : + * P_u( x ) = P_{7,sin(0)}( x ) + R_{7,sin(0),b}( x ) + * ...where note P_u( 1 ) = 4243/5040 ~= .841865 + * P_l( x ) = P_{7,sin(0)}( x ) - R_{7,sin(0),b}( x ) + * ...where note P_l( 1 ) = 4241/5040 ~= .841468 + * + * Assume that M( sin(y) ) > P_u( 1 ). + * Since the concavity of sine in the region 0 < x < PI/2 is -1, + * we add a tangent plane refinement. + * The tangent plane at the point 1 in P_u is + * given by the formula: + * T( x ) = P_u( 1 ) + ((d/dx)(P_u(x)))( 1 )*( x - 1 ) + * We add the lemma: + * ( 0 < y < PI/2 ) => sin( y ) <= T( y ) + * which is: + * ( 0 < y < PI/2 ) => sin( y ) <= (391/720)*(y - 2737/1506) + * + * Assume that M( sin(y) ) < P_u( 1 ). + * Since the concavity of sine in the region 0 < x < PI/2 is -1, + * we add a secant plane refinement for some constants ( l, u ) + * such that 0 <= l < M( y ) < u <= PI/2. Assume we choose + * l = 0 and u = M( PI/2 ) = 150517/47912. + * The secant planes at point 1 for P_l + * are given by the formulas: + * S_l( x ) = (x-l)*(P_l( l )-P_l(c))/(l-1) + P_l( l ) + * S_u( x ) = (x-u)*(P_l( u )-P_l(c))/(u-1) + P_l( u ) + * We add the lemmas: + * ( 0 < y < 1 ) => sin( y ) >= S_l( y ) + * ( 1 < y < PI/2 ) => sin( y ) >= S_u( y ) + * which are: + * ( 0 < y < 1 ) => (sin y) >= 4251/5040*y + * ( 1 < y < PI/2 ) => (sin y) >= c1*(y+c2) + * where c1, c2 are rationals (for brevity, omitted here) + * such that c1 ~= .277 and c2 ~= 2.032. + * + * The argument lemSE is the "side effect" of the lemmas in the return + * value of this function (for details, see checkLastCall). + */ + std::vector checkTranscendentalTangentPlanes( + std::map& lemSE); + /** check transcendental function refinement for tf + * + * This method is called by the above method for each "master" + * transcendental function application that occurs in an assertion in the + * current context. For example, an application like sin(t) is not a master + * if we have introduced the constraints: + * t=y+2*pi*n ^ -pi <= y <= pi ^ sin(t) = sin(y). + * See d_trMaster/d_trSlaves for more detail. + * + * This runs Figure 3 of Cimatti et al., CADE 2017 for transcendental + * function application tf for Taylor degree d. It may add a secant or + * tangent plane lemma to lems and its side effect (if one exists) + * to lemSE. + * + * It returns false if the bounds are not precise enough to add a + * secant or tangent plane lemma. + */ + bool checkTfTangentPlanesFun(Node tf, + unsigned d, + std::vector& lems, + std::map& lemSE); + //-------------------------------------------- end lemma schemas + private: + /** polynomial approximation bounds + * + * This adds P_l+[x], P_l-[x], P_u+[x], P_u-[x] to pbounds, where x is + * d_taylor_real_fv. These are polynomial approximations of the Taylor series + * of ( 0 ) for degree 2*d where k is SINE or EXPONENTIAL. + * These correspond to P_l and P_u from Figure 3 of Cimatti et al., CADE 2017, + * for positive/negative (+/-) values of the argument of ( 0 ). + * + * Notice that for certain bounds (e.g. upper bounds for exponential), the + * Taylor approximation for a fixed degree is only sound up to a given + * upper bound on the argument. To obtain sound lower/upper bounds for a + * given ( c ), use the function below. + */ + void getPolynomialApproximationBounds(Kind k, + unsigned d, + std::vector& pbounds); + /** polynomial approximation bounds + * + * This computes polynomial approximations P_l+[x], P_l-[x], P_u+[x], P_u-[x] + * that are sound (lower, upper) bounds for ( c ). Notice that these + * polynomials may depend on c. In particular, for P_u+[x] for ( c ) where + * c>0, we return the P_u+[x] from the function above for the minimum degree + * d' >= d such that (1-c^{2*d'+1}/(2*d'+1)!) is positive. + */ + void getPolynomialApproximationBoundForArg(Kind k, + Node c, + unsigned d, + std::vector& pbounds); + /** get transcendental function model bounds + * + * This returns the current lower and upper bounds of transcendental + * function application tf based on Taylor of degree 2*d, which is dependent + * on the model value of its argument. + */ + std::pair getTfModelBounds(Node tf, unsigned d); + /** get monotonicity direction + * + * Returns whether the slope is positive (+1) or negative(-1) + * in region of transcendental function with kind k. + * Returns 0 if region is invalid. + */ + int regionToMonotonicityDir(Kind k, int region); + /** get concavity + * + * Returns whether we are concave (+1) or convex (-1) + * in region of transcendental function with kind k, + * where region is defined above. + * Returns 0 if region is invalid. + */ + int regionToConcavity(Kind k, int region); + /** region to lower bound + * + * Returns the term corresponding to the lower + * bound of the region of transcendental function + * with kind k. Returns Node::null if the region + * is invalid, or there is no lower bound for the + * region. + */ + Node regionToLowerBound(Kind k, int region); + /** region to upper bound + * + * Returns the term corresponding to the upper + * bound of the region of transcendental function + * with kind k. Returns Node::null if the region + * is invalid, or there is no upper bound for the + * region. + */ + Node regionToUpperBound(Kind k, int region); + /** get derivative + * + * Returns d/dx n. Supports cases of n + * for transcendental functions applied to x, + * multiplication, addition, constants and variables. + * Returns Node::null() if derivative is an + * unhandled case. + */ + Node getDerivative(Node n, Node x); + + void mkPi(); + void getCurrentPiBounds(std::vector& lemmas); + /** Make the node -pi <= a <= pi */ + static Node mkValidPhase(Node a, Node pi); + + /** Reference to the non-linear model object */ + NlModel& d_model; + /** commonly used terms */ + Node d_zero; + Node d_one; + Node d_neg_one; + Node d_true; + Node d_false; + /** + * Some transcendental functions f(t) are "purified", e.g. we add + * t = y ^ f(t) = f(y) where y is a fresh variable. Those that are not + * purified we call "master terms". + * + * The maps below maintain a master/slave relationship over + * transcendental functions (SINE, EXPONENTIAL, PI), where above + * f(y) is the master of itself and of f(t). + * + * This is used for ensuring that the argument y of SINE we process is on the + * interval [-pi .. pi], and that exponentials are not applied to arguments + * that contain transcendental functions. + */ + std::map d_trMaster; + std::map> d_trSlaves; + /** The transcendental functions we have done initial refinements on */ + std::map d_tf_initial_refine; + + /** concavity region for transcendental functions + * + * This stores an integer that identifies an interval in + * which the current model value for an argument of an + * application of a transcendental function resides. + * + * For exp( x ): + * region #1 is -infty < x < infty + * For sin( x ): + * region #0 is pi < x < infty (this is an invalid region) + * region #1 is pi/2 < x <= pi + * region #2 is 0 < x <= pi/2 + * region #3 is -pi/2 < x <= 0 + * region #4 is -pi < x <= -pi/2 + * region #5 is -infty < x <= -pi (this is an invalid region) + * All regions not listed above, as well as regions 0 and 5 + * for SINE are "invalid". We only process applications + * of transcendental functions whose arguments have model + * values that reside in valid regions. + */ + std::unordered_map d_tf_region; + /** cache of the above function */ + std::map>> d_poly_bounds; + + /** + * Maps representives of a congruence class to the members of that class. + * + * In detail, a congruence class is a set of terms of the form + * { f(t1), ..., f(tn) } + * such that t1 = ... = tn in the current context. We choose an arbitrary + * term among these to be the repesentative of this congruence class. + * + * Moreover, notice we compute congruence classes only over terms that + * are transcendental function applications that are "master terms", + * see d_trMaster/d_trSlave. + */ + std::map> d_funcCongClass; + /** + * A list of all functions for each kind in { EXPONENTIAL, SINE, POW, PI } + * that are representives of their congruence class. + */ + std::map> d_funcMap; + + // tangent plane bounds + std::map> d_tangent_val_bound[4]; + + /** secant points (sorted list) for transcendental functions + * + * This is used for tangent plane refinements for + * transcendental functions. This is the set + * "get-previous-secant-points" in "Satisfiability + * Modulo Transcendental Functions via Incremental + * Linearization" by Cimatti et al., CADE 2017, for + * each transcendental function application. We store this set for each + * Taylor degree. + */ + std::unordered_map>, + NodeHashFunction> + d_secant_points; + + /** get Taylor series of degree n for function fa centered around point fa[0]. + * + * Return value is ( P_{n,f(a)}( x ), R_{n+1,f(a)}( x ) ) where + * the first part of the pair is the Taylor series expansion : + * P_{n,f(a)}( x ) = sum_{i=0}^n (f^i( a )/i!)*(x-a)^i + * and the second part of the pair is the Taylor series remainder : + * R_{n+1,f(a),b}( x ) = (f^{n+1}( b )/(n+1)!)*(x-a)^{n+1} + * + * The above values are cached for each (f,n) for a fixed variable "a". + * To compute the Taylor series for fa, we compute the Taylor series + * for ( fa.getKind(), n ) then substitute { a -> fa[0] } if fa[0]!=0. + * We compute P_{n,f(0)}( x )/R_{n+1,f(0),b}( x ) for ( fa.getKind(), n ) + * if fa[0]=0. + * In the latter case, note we compute the exponential x^{n+1} + * instead of (x-a)^{n+1}, which can be done faster. + */ + std::pair getTaylor(Node fa, unsigned n); + + /** internal variables used for constructing (cached) versions of the Taylor + * series above. + */ + Node d_taylor_real_fv; // x above + Node d_taylor_real_fv_base; // a above + Node d_taylor_real_fv_base_rem; // b above + + /** cache of sum and remainder terms for getTaylor */ + std::unordered_map, NodeHashFunction> + d_taylor_sum; + std::unordered_map, NodeHashFunction> + d_taylor_rem; + /** taylor degree + * + * Indicates that the degree of the polynomials in the Taylor approximation of + * all transcendental functions is 2*d_taylor_degree. This value is set + * initially to options::nlExtTfTaylorDegree() and may be incremented + * if the option options::nlExtTfIncPrecision() is enabled. + */ + unsigned d_taylor_degree; + /** PI + * + * Note that PI is a (symbolic, non-constant) nullary operator. This is + * because its value cannot be computed exactly. We constraint PI to concrete + * lower and upper bounds stored in d_pi_bound below. + */ + Node d_pi; + /** PI/2 */ + Node d_pi_2; + /** -PI/2 */ + Node d_pi_neg_2; + /** -PI */ + Node d_pi_neg; + /** the concrete lower and upper bounds for PI */ + Node d_pi_bound[2]; +}; /* class TranscendentalSolver */ + +} // namespace arith +} // namespace theory +} // namespace CVC4 + +#endif /* CVC4__THEORY__ARITH__TRANSCENDENTAL_SOLVER_H */ -- cgit v1.2.3 From 9023d348d0f30fdd81805f224e77e90ecef1350d Mon Sep 17 00:00:00 2001 From: Alex Ozdemir Date: Fri, 27 Mar 2020 23:22:41 -0700 Subject: Node traversal iterator (#3845) Implement an iterator for pre- and post-order traversals. I believe that this will be useful in pre-processing passes, many of which do postorder traversals that they implement by hand. Right now this iterator does not support modification of the traversal pattern, but we could add this later on, if we want it. Co-authored-by: Andres Noetzli Co-authored-by: Mathias Preiner --- src/expr/CMakeLists.txt | 2 + src/expr/node_traversal.cpp | 150 ++++++++++++++++++ src/expr/node_traversal.h | 131 ++++++++++++++++ test/unit/expr/CMakeLists.txt | 1 + test/unit/expr/node_traversal_black.h | 281 ++++++++++++++++++++++++++++++++++ 5 files changed, 565 insertions(+) create mode 100644 src/expr/node_traversal.cpp create mode 100644 src/expr/node_traversal.h create mode 100644 test/unit/expr/node_traversal_black.h diff --git a/src/expr/CMakeLists.txt b/src/expr/CMakeLists.txt index d1faa8ffb..00bd121cb 100644 --- a/src/expr/CMakeLists.txt +++ b/src/expr/CMakeLists.txt @@ -29,6 +29,8 @@ libcvc4_add_sources( node_self_iterator.h node_trie.cpp node_trie.h + node_traversal.cpp + node_traversal.h node_value.cpp node_value.h node_visitor.h diff --git a/src/expr/node_traversal.cpp b/src/expr/node_traversal.cpp new file mode 100644 index 000000000..9e7a82c24 --- /dev/null +++ b/src/expr/node_traversal.cpp @@ -0,0 +1,150 @@ +/********************* */ +/*! \file node_traversal.cpp + ** \verbatim + ** Top contributors (to current version): + ** Alex Ozdemir + ** This file is part of the CVC4 project. + ** Copyright (c) 2009-2020 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 Iterators for traversing nodes. + **/ + +#include "node_traversal.h" + +namespace CVC4 { + +NodeDfsIterator::NodeDfsIterator(TNode n, bool postorder) + : d_stack{n}, + d_visited(), + d_postorder(postorder), + d_current(TNode()) +{ +} + +NodeDfsIterator::NodeDfsIterator(bool postorder) + : d_stack(), + d_visited(), + d_postorder(postorder), + d_current(TNode()) +{ +} + +NodeDfsIterator& NodeDfsIterator::operator++() +{ + // If we were just constructed, advance to first visit, **before** + // advancing past it to the next visit (below). + initializeIfUninitialized(); + + // Advance to the next visit + advanceToNextVisit(); + return *this; +} + +NodeDfsIterator NodeDfsIterator::operator++(int) +{ + NodeDfsIterator copyOfOld(*this); + ++*this; + return copyOfOld; +} + +TNode& NodeDfsIterator::operator*() +{ + // If we were just constructed, advance to first visit + initializeIfUninitialized(); + Assert(!d_current.isNull()); + + return d_current; +} + +bool NodeDfsIterator::operator==(const NodeDfsIterator& other) const +{ + // The stack and current node uniquely represent traversal state. We need not + // use the scheduled node set. + // + // Users should not compare iterators for traversals of different nodes. + Assert(d_postorder == other.d_postorder); + return d_stack == other.d_stack && d_current == other.d_current; +} + +bool NodeDfsIterator::operator!=(const NodeDfsIterator& other) const +{ + return !(*this == other); +} + +void NodeDfsIterator::advanceToNextVisit() +{ + // While a node is enqueued and we're not at the right visit type + while (!d_stack.empty()) + { + TNode back = d_stack.back(); + auto visitEntry = d_visited.find(back); + if (visitEntry == d_visited.end()) + { + // if we haven't pre-visited this node, pre-visit it + d_visited[back] = false; + d_current = back; + // Use integer underflow to reverse-iterate + for (size_t n = back.getNumChildren(), i = n - 1; i < n; --i) + { + d_stack.push_back(back[i]); + } + if (!d_postorder) + { + return; + } + } + else if (!d_postorder || visitEntry->second) + { + // if we're previsiting or we've already post-visited this node: skip it + d_stack.pop_back(); + } + else + { + // otherwise, this is a post-visit + visitEntry->second = true; + d_current = back; + d_stack.pop_back(); + return; + } + } + // We're at the end of the traversal: nullify the current node to agree + // with the "end" iterator. + d_current = TNode(); +} + +void NodeDfsIterator::initializeIfUninitialized() +{ + if (d_current.isNull()) + { + advanceToNextVisit(); + } +} + +NodeDfsIterable::NodeDfsIterable(TNode n) : d_node(n), d_postorder(true) {} + +NodeDfsIterable& NodeDfsIterable::inPostorder() +{ + d_postorder = true; + return *this; +} + +NodeDfsIterable& NodeDfsIterable::inPreorder() +{ + d_postorder = false; + return *this; +} + +NodeDfsIterator NodeDfsIterable::begin() const +{ + return NodeDfsIterator(d_node, d_postorder); +} + +NodeDfsIterator NodeDfsIterable::end() const +{ + return NodeDfsIterator(d_postorder); +} + +} // namespace CVC4 diff --git a/src/expr/node_traversal.h b/src/expr/node_traversal.h new file mode 100644 index 000000000..fffc1d746 --- /dev/null +++ b/src/expr/node_traversal.h @@ -0,0 +1,131 @@ +/********************* */ +/*! \file node_traversal.h + ** \verbatim + ** Top contributors (to current version): + ** Alex Ozdemir + ** This file is part of the CVC4 project. + ** Copyright (c) 2009-2020 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 Iterators for traversing nodes. + **/ + +#include "cvc4_private.h" + +#ifndef CVC4__EXPR__NODE_TRAVERSAL_H +#define CVC4__EXPR__NODE_TRAVERSAL_H + +#include +#include +#include +#include + +#include "expr/node.h" + +namespace CVC4 { + +// Iterator for traversing a node in post-order +// It does DAG-traversal, so indentical sub-nodes will be visited once only. +class NodeDfsIterator +{ + public: + // STL type definitions for an iterator + using value_type = TNode; + using pointer = TNode*; + using reference = TNode&; + using iterator_category = std::forward_iterator_tag; + using difference_type = std::ptrdiff_t; + + // Construct a traversal iterator beginning at `n` + NodeDfsIterator(TNode n, bool postorder); + // Construct an end-of-traversal iterator + NodeDfsIterator(bool postorder); + + // Move/copy construction and assignment. Destructor. + NodeDfsIterator(NodeDfsIterator&&) = default; + NodeDfsIterator& operator=(NodeDfsIterator&&) = default; + NodeDfsIterator(NodeDfsIterator&) = default; + NodeDfsIterator& operator=(NodeDfsIterator&) = default; + ~NodeDfsIterator() = default; + + // Preincrement + NodeDfsIterator& operator++(); + // Postincrement + NodeDfsIterator operator++(int); + // Dereference + reference operator*(); + // Equals + bool operator==(const NodeDfsIterator&) const; + // Not equals + bool operator!=(const NodeDfsIterator&) const; + + private: + // While we're not at an appropriate visit (see d_postorder), advance. + // In each step: + // * enqueue children of a not-yet-pre-visited node (and mark it + // previsited) + // * pop a not-yet-post-visited node (and mark it post-visited) + // * pop an already post-visited node. + // After calling this, `d_current` will be changed to the next node, if there + // is another node in the traversal. + void advanceToNextVisit(); + + // If this iterator hasn't been dereferenced or incremented yet, advance to + // first visit. + // Necessary because we are lazy and don't find our first visit node at + // construction time. + void initializeIfUninitialized(); + + // Stack of nodes to visit. + std::vector d_stack; + + // Whether (and how) we've visited a node. + // Absent if we haven't visited it. + // Set to `false` if we've already pre-visited it (enqueued its children). + // Set to `true` if we've also already post-visited it. + std::unordered_map d_visited; + + // Whether this is a post-order iterator (the alternative is pre-order) + bool d_postorder; + + // Whether this iterator has been initialized (advanced to its first + // visit) + bool d_initialized; + + // Current referent node. A valid node to visit if non-null. + // Null after construction (but before first access) and at the end. + TNode d_current; +}; + +// Node wrapper that is iterable in DAG post-order +class NodeDfsIterable +{ + public: + NodeDfsIterable(TNode n); + + // Modifying the traversal order + // Modify this iterable to be in post-order (default) + NodeDfsIterable& inPostorder(); + // Modify this iterable to be in pre-order + NodeDfsIterable& inPreorder(); + + // Move/copy construction and assignment. Destructor. + NodeDfsIterable(NodeDfsIterable&&) = default; + NodeDfsIterable& operator=(NodeDfsIterable&&) = default; + NodeDfsIterable(NodeDfsIterable&) = default; + NodeDfsIterable& operator=(NodeDfsIterable&) = default; + ~NodeDfsIterable() = default; + + NodeDfsIterator begin() const; + NodeDfsIterator end() const; + + private: + TNode d_node; + bool d_postorder; +}; + +} // namespace CVC4 + +#endif // CVC4__EXPR__NODE_TRAVERSAL_H diff --git a/test/unit/expr/CMakeLists.txt b/test/unit/expr/CMakeLists.txt index d487bf560..438b7f7b6 100644 --- a/test/unit/expr/CMakeLists.txt +++ b/test/unit/expr/CMakeLists.txt @@ -13,6 +13,7 @@ cvc4_add_unit_test_black(node_builder_black expr) cvc4_add_unit_test_black(node_manager_black expr) cvc4_add_unit_test_white(node_manager_white expr) cvc4_add_unit_test_black(node_self_iterator_black expr) +cvc4_add_unit_test_black(node_traversal_black expr) cvc4_add_unit_test_white(node_white expr) cvc4_add_unit_test_black(symbol_table_black expr) cvc4_add_unit_test_black(type_cardinality_public expr) diff --git a/test/unit/expr/node_traversal_black.h b/test/unit/expr/node_traversal_black.h new file mode 100644 index 000000000..b4a7c449c --- /dev/null +++ b/test/unit/expr/node_traversal_black.h @@ -0,0 +1,281 @@ +/********************* */ +/*! \file node_traversal_black.h + ** \verbatim + ** Top contributors (to current version): + ** Alex Ozdemir + ** This file is part of the CVC4 project. + ** Copyright (c) 2009-2020 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 Black box testing of node traversal iterators. + **/ + +#include + +// Used in some of the tests +#include +#include +#include +#include +#include +#include + +#include "expr/expr_manager.h" +#include "expr/node.h" +#include "expr/node_builder.h" +#include "expr/node_manager.h" +#include "expr/node_traversal.h" +#include "expr/node_value.h" + +using namespace CVC4; +using namespace CVC4::kind; +using namespace std; + +class NodePostorderTraversalBlack : public CxxTest::TestSuite +{ + private: + NodeManager* d_nodeManager; + NodeManagerScope* d_scope; + + public: + void setUp() override + { + d_nodeManager = new NodeManager(NULL); + d_scope = new NodeManagerScope(d_nodeManager); + } + + void tearDown() override + { + delete d_scope; + delete d_nodeManager; + } + + void testPreincrementIteration() + { + Node tb = d_nodeManager->mkConst(true); + Node eb = d_nodeManager->mkConst(false); + Node cnd = d_nodeManager->mkNode(XOR, tb, eb); + + auto traversal = NodeDfsIterable(cnd).inPostorder(); + NodeDfsIterator i = traversal.begin(); + NodeDfsIterator end = traversal.end(); + TS_ASSERT_EQUALS(*i, tb); + TS_ASSERT_DIFFERS(i, end); + ++i; + TS_ASSERT_EQUALS(*i, eb); + TS_ASSERT_DIFFERS(i, end); + ++i; + TS_ASSERT_EQUALS(*i, cnd); + TS_ASSERT_DIFFERS(i, end); + ++i; + TS_ASSERT_EQUALS(i, end); + } + + void testPostincrementIteration() + { + Node tb = d_nodeManager->mkConst(true); + Node eb = d_nodeManager->mkConst(false); + Node cnd = d_nodeManager->mkNode(XOR, tb, eb); + + auto traversal = NodeDfsIterable(cnd).inPostorder(); + NodeDfsIterator i = traversal.begin(); + NodeDfsIterator end = traversal.end(); + TS_ASSERT_EQUALS(*(i++), tb); + TS_ASSERT_EQUALS(*(i++), eb); + TS_ASSERT_EQUALS(*(i++), cnd); + TS_ASSERT_EQUALS(i, end); + } + + void testPostorderIsDefault() + { + Node tb = d_nodeManager->mkConst(true); + Node eb = d_nodeManager->mkConst(false); + Node cnd = d_nodeManager->mkNode(XOR, tb, eb); + + auto traversal = NodeDfsIterable(cnd); + NodeDfsIterator i = traversal.begin(); + NodeDfsIterator end = traversal.end(); + TS_ASSERT_EQUALS(*i, tb); + TS_ASSERT_DIFFERS(i, end); + } + + void testRangeForLoop() + { + Node tb = d_nodeManager->mkConst(true); + Node eb = d_nodeManager->mkConst(false); + Node cnd = d_nodeManager->mkNode(XOR, tb, eb); + + size_t count = 0; + for (auto i : NodeDfsIterable(cnd).inPostorder()) + { + ++count; + } + TS_ASSERT_EQUALS(count, 3); + } + + void testCountIfWithLoop() + { + Node tb = d_nodeManager->mkConst(true); + Node eb = d_nodeManager->mkConst(false); + Node cnd = d_nodeManager->mkNode(XOR, tb, eb); + + size_t count = 0; + for (auto i : NodeDfsIterable(cnd).inPostorder()) + { + if (i.isConst()) + { + ++count; + } + } + TS_ASSERT_EQUALS(count, 2); + } + + void testStlCountIf() + { + Node tb = d_nodeManager->mkConst(true); + Node eb = d_nodeManager->mkConst(false); + Node cnd = d_nodeManager->mkNode(XOR, tb, eb); + Node top = d_nodeManager->mkNode(XOR, cnd, cnd); + + auto traversal = NodeDfsIterable(top).inPostorder(); + + size_t count = std::count_if(traversal.begin(), + traversal.end(), + [](TNode n) { return n.isConst(); }); + TS_ASSERT_EQUALS(count, 2); + } + + void testStlCopy() + { + Node tb = d_nodeManager->mkConst(true); + Node eb = d_nodeManager->mkConst(false); + Node cnd = d_nodeManager->mkNode(XOR, tb, eb); + Node top = d_nodeManager->mkNode(XOR, cnd, cnd); + std::vector expected = {tb, eb, cnd, top}; + + auto traversal = NodeDfsIterable(top).inPostorder(); + + std::vector actual; + std::copy(traversal.begin(), traversal.end(), std::back_inserter(actual)); + TS_ASSERT_EQUALS(actual, expected); + } +}; + +class NodePreorderTraversalBlack : public CxxTest::TestSuite +{ + private: + NodeManager* d_nodeManager; + NodeManagerScope* d_scope; + + public: + void setUp() override + { + d_nodeManager = new NodeManager(NULL); + d_scope = new NodeManagerScope(d_nodeManager); + } + + void tearDown() override + { + delete d_scope; + delete d_nodeManager; + } + + void testPreincrementIteration() + { + Node tb = d_nodeManager->mkConst(true); + Node eb = d_nodeManager->mkConst(false); + Node cnd = d_nodeManager->mkNode(XOR, tb, eb); + + auto traversal = NodeDfsIterable(cnd).inPreorder(); + NodeDfsIterator i = traversal.begin(); + NodeDfsIterator end = traversal.end(); + TS_ASSERT_EQUALS(*i, cnd); + TS_ASSERT_DIFFERS(i, end); + ++i; + TS_ASSERT_EQUALS(*i, tb); + TS_ASSERT_DIFFERS(i, end); + ++i; + TS_ASSERT_EQUALS(*i, eb); + TS_ASSERT_DIFFERS(i, end); + ++i; + TS_ASSERT_EQUALS(i, end); + } + + void testPostincrementIteration() + { + Node tb = d_nodeManager->mkConst(true); + Node eb = d_nodeManager->mkConst(false); + Node cnd = d_nodeManager->mkNode(XOR, tb, eb); + + auto traversal = NodeDfsIterable(cnd).inPreorder(); + NodeDfsIterator i = traversal.begin(); + NodeDfsIterator end = traversal.end(); + TS_ASSERT_EQUALS(*(i++), cnd); + TS_ASSERT_EQUALS(*(i++), tb); + TS_ASSERT_EQUALS(*(i++), eb); + TS_ASSERT_EQUALS(i, end); + } + + void testRangeForLoop() + { + Node tb = d_nodeManager->mkConst(true); + Node eb = d_nodeManager->mkConst(false); + Node cnd = d_nodeManager->mkNode(XOR, tb, eb); + + size_t count = 0; + for (auto i : NodeDfsIterable(cnd).inPreorder()) + { + ++count; + } + TS_ASSERT_EQUALS(count, 3); + } + + void testCountIfWithLoop() + { + Node tb = d_nodeManager->mkConst(true); + Node eb = d_nodeManager->mkConst(false); + Node cnd = d_nodeManager->mkNode(XOR, tb, eb); + + size_t count = 0; + for (auto i : NodeDfsIterable(cnd).inPreorder()) + { + if (i.isConst()) + { + ++count; + } + } + TS_ASSERT_EQUALS(count, 2); + } + + void testStlCountIf() + { + Node tb = d_nodeManager->mkConst(true); + Node eb = d_nodeManager->mkConst(false); + Node cnd = d_nodeManager->mkNode(XOR, tb, eb); + Node top = d_nodeManager->mkNode(XOR, cnd, cnd); + + auto traversal = NodeDfsIterable(top).inPreorder(); + + size_t count = std::count_if(traversal.begin(), + traversal.end(), + [](TNode n) { return n.isConst(); }); + TS_ASSERT_EQUALS(count, 2); + } + + void testStlCopy() + { + Node tb = d_nodeManager->mkConst(true); + Node eb = d_nodeManager->mkConst(false); + Node cnd = d_nodeManager->mkNode(XOR, tb, eb); + Node top = d_nodeManager->mkNode(XOR, cnd, cnd); + std::vector expected = {top, cnd, tb, eb}; + + auto traversal = NodeDfsIterable(top).inPreorder(); + + std::vector actual; + std::copy(traversal.begin(), traversal.end(), std::back_inserter(actual)); + TS_ASSERT_EQUALS(actual, expected); + } +}; -- cgit v1.2.3 From 2c6b35d8ce7dcacd2f13bcdd5365629ee315dc8d Mon Sep 17 00:00:00 2001 From: Abdalrhman Mohamed <32971963+abdoo8080@users.noreply.github.com> Date: Sat, 28 Mar 2020 11:41:22 -0500 Subject: Stop printing datatype declaration for Sygus V1 grammar. (#4168) --- src/parser/parser.h | 3 +++ src/parser/smt2/Smt2.g | 20 ++++++++++++++++++-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/parser/parser.h b/src/parser/parser.h index d6c0e0e15..72e175a58 100644 --- a/src/parser/parser.h +++ b/src/parser/parser.h @@ -273,6 +273,9 @@ public: return d_input; } + /** Get unresolved sorts */ + inline std::set& getUnresolvedSorts() { return d_unresolved; } + /** Deletes and replaces the current parser input. */ void setInput(Input* input) { delete d_input; diff --git a/src/parser/smt2/Smt2.g b/src/parser/smt2/Smt2.g index 69f21acb7..0c42678aa 100644 --- a/src/parser/smt2/Smt2.g +++ b/src/parser/smt2/Smt2.g @@ -782,8 +782,24 @@ sygusGrammarV1[CVC4::api::Sort & ret, Debug("parser-sygus") << " " << i << " : " << datatypes[i].getName() << std::endl; } - std::vector datatypeTypes = - PARSER_STATE->bindMutualDatatypeTypes(datatypes, false); + + std::vector dtypes; + dtypes.reserve(ndatatypes); + + for (api::DatatypeDecl i : datatypes) + { + dtypes.push_back(i.getDatatype()); + } + + std::set tset = + api::sortSetToTypes(PARSER_STATE->getUnresolvedSorts()); + + std::vector datatypeTypes = + SOLVER->getExprManager()->mkMutualDatatypeTypes( + dtypes, tset, ExprManager::DATATYPE_FLAG_PLACEHOLDER); + + PARSER_STATE->getUnresolvedSorts().clear(); + ret = datatypeTypes[0]; }; -- cgit v1.2.3 From 830c09d3cadc119845aff27684bd68c16e442692 Mon Sep 17 00:00:00 2001 From: Abdalrhman Mohamed <32971963+abdoo8080@users.noreply.github.com> Date: Sat, 28 Mar 2020 12:39:47 -0500 Subject: Convert the last few Sygus benchmarks to V2. (#4172) --- test/regress/regress0/sygus/c100.sy | 4 +- test/regress/regress0/sygus/check-generic-red.sy | 3 +- test/regress/regress0/sygus/const-var-test.sy | 5 +-- test/regress/regress0/sygus/sygus-uf.sy | 7 +-- test/regress/regress1/rr-verify/regex.sy | 9 ++-- .../regress/regress1/sygus/hd-19-d1-prog-dup-op.sy | 18 ++++---- test/regress/regress1/sygus/issue3461.sy | 7 +-- test/regress/regress1/sygus/max.sy | 4 +- test/regress/regress1/sygus/parity-si-rcons.sy | 3 +- test/regress/regress1/sygus/re-concat.sy | 8 ++-- test/regress/regress1/sygus/simple-regexp.sy | 50 +++++++++++----------- test/regress/regress1/sygus/sygus-uf-ex.sy | 30 +++++++------ 12 files changed, 81 insertions(+), 67 deletions(-) diff --git a/test/regress/regress0/sygus/c100.sy b/test/regress/regress0/sygus/c100.sy index ef124c953..994fb6de3 100644 --- a/test/regress/regress0/sygus/c100.sy +++ b/test/regress/regress0/sygus/c100.sy @@ -1,9 +1,10 @@ ; EXPECT: unsat -; COMMAND-LINE: --cegqi-si=all --sygus-out=status +; COMMAND-LINE: --lang=sygus2 --cegqi-si=all --sygus-out=status (set-logic LIA) (synth-fun constant ((x Int)) Int + ((Start Int)) ((Start Int (0 2 3 @@ -15,4 +16,3 @@ (declare-var x Int) (constraint (= (constant x) 100)) (check-synth) - diff --git a/test/regress/regress0/sygus/check-generic-red.sy b/test/regress/regress0/sygus/check-generic-red.sy index e169e1a5c..d593a7d9e 100644 --- a/test/regress/regress0/sygus/check-generic-red.sy +++ b/test/regress/regress0/sygus/check-generic-red.sy @@ -1,8 +1,9 @@ ; EXPECT: unsat -; COMMAND-LINE: --cegqi-si=all --sygus-out=status --decision=justification +; COMMAND-LINE: --lang=sygus2 --cegqi-si=all --sygus-out=status --decision=justification (set-logic LIA) (synth-fun P ((x Int) (y Int)) Bool + ((Start Bool) (StartIntC Int) (StartInt Int)) ((Start Bool ((and Start Start) (not Start) (<= StartInt StartIntC) diff --git a/test/regress/regress0/sygus/const-var-test.sy b/test/regress/regress0/sygus/const-var-test.sy index 305f5783a..31e88f523 100644 --- a/test/regress/regress0/sygus/const-var-test.sy +++ b/test/regress/regress0/sygus/const-var-test.sy @@ -1,9 +1,10 @@ ; EXPECT: unsat -; COMMAND-LINE: --cegqi-si=all --sygus-out=status +; COMMAND-LINE: --lang=sygus2 --cegqi-si=all --sygus-out=status (set-logic LIA) (synth-fun max2 ((x Int) (y Int)) Int + ((Start Int) (StartBool Bool)) ((Start Int ((Variable Int) (Constant Int) (+ Start Start) @@ -21,6 +22,4 @@ (constraint (= (max2 x y) (+ x y 500))) - (check-synth) - diff --git a/test/regress/regress0/sygus/sygus-uf.sy b/test/regress/regress0/sygus/sygus-uf.sy index d506dd5b2..b08aa8929 100644 --- a/test/regress/regress0/sygus/sygus-uf.sy +++ b/test/regress/regress0/sygus/sygus-uf.sy @@ -1,10 +1,11 @@ -; COMMAND-LINE: --sygus-out=status --uf-ho +; COMMAND-LINE: --lang=sygus2 --sygus-out=status --uf-ho ; EXPECT: unsat -(set-logic UFLIA) +(set-logic ALL) -(declare-fun uf (Int) Int) +(declare-var uf (-> Int Int)) (synth-fun f ((x Int) (y Int)) Bool + ((Start Bool) (IntExpr Int)) ((Start Bool (true false (<= IntExpr IntExpr) (= IntExpr IntExpr) diff --git a/test/regress/regress1/rr-verify/regex.sy b/test/regress/regress1/rr-verify/regex.sy index 6c6da3dd2..2d911e56a 100644 --- a/test/regress/regress1/rr-verify/regex.sy +++ b/test/regress/regress1/rr-verify/regex.sy @@ -1,17 +1,18 @@ -; COMMAND-LINE: --sygus-rr --sygus-samples=1000 --sygus-abort-size=3 --sygus-rr-verify-abort --no-sygus-sym-break +; COMMAND-LINE: --lang=sygus2 --sygus-rr --sygus-samples=1000 --sygus-abort-size=3 --sygus-rr-verify-abort --no-sygus-sym-break ; EXPECT: (error "Maximum term size (3) for enumerative SyGuS exceeded.") ; SCRUBBER: grep -v -E '(\(define-fun|\(candidate-rewrite)' ; EXIT: 1 (set-logic SLIA) -(synth-fun f ((x String) (y String)) Bool ( +(synth-fun f ((x String) (y String)) Bool +((Start Bool) (StartRe RegLan) (StartStr String)) ( (Start Bool ( true false (= StartStr StartStr) - (str.in.re StartStr StartRe) + (str.in_re StartStr StartRe) )) (StartRe RegLan ( @@ -19,7 +20,7 @@ (re.++ StartRe StartRe) (re.union StartRe StartRe) (re.* StartRe) - (str.to.re StartStr) + (str.to_re StartStr) )) (StartStr String ( diff --git a/test/regress/regress1/sygus/hd-19-d1-prog-dup-op.sy b/test/regress/regress1/sygus/hd-19-d1-prog-dup-op.sy index abcfc2217..089a8f11f 100644 --- a/test/regress/regress1/sygus/hd-19-d1-prog-dup-op.sy +++ b/test/regress/regress1/sygus/hd-19-d1-prog-dup-op.sy @@ -1,14 +1,15 @@ ; EXPECT: unsat -; COMMAND-LINE: --cegqi-si=all --sygus-out=status +; COMMAND-LINE: --lang=sygus2 --cegqi-si=all --sygus-out=status (set-logic BV) -(define-fun hd19 ((x (BitVec 32)) (m (BitVec 32)) (k (BitVec 32))) (BitVec 32) - (bvxor x (bvxor (bvshl (bvand (bvxor (bvlshr x k) x) m) k) (bvand (bvxor (bvlshr x k) x) m)))) +(define-fun hd19 ((x (_ BitVec 32)) (m (_ BitVec 32)) (k (_ BitVec 32))) (_ BitVec 32) + (bvxor x (bvxor (bvshl (bvand (bvxor (bvlshr x k) x) m) k) (bvand (bvxor (bvlshr x k) x) m)))) ; bvand is a duplicate -(synth-fun f ((x (BitVec 32)) (m (BitVec 32)) (k (BitVec 32))) (BitVec 32) - ((Start (BitVec 32) ((bvand Start Start) +(synth-fun f ((x (_ BitVec 32)) (m (_ BitVec 32)) (k (_ BitVec 32))) (_ BitVec 32) + ((Start (_ BitVec 32))) + ((Start (_ BitVec 32) ((bvand Start Start) (bvsub Start Start) (bvxor Start Start) (bvor Start Start) @@ -23,10 +24,9 @@ k)))) -(declare-var x (BitVec 32)) -(declare-var m (BitVec 32)) -(declare-var k (BitVec 32)) +(declare-var x (_ BitVec 32)) +(declare-var m (_ BitVec 32)) +(declare-var k (_ BitVec 32)) (constraint (= (hd19 x m k) (f x m k))) (check-synth) - diff --git a/test/regress/regress1/sygus/issue3461.sy b/test/regress/regress1/sygus/issue3461.sy index 1f839c229..08b5738c1 100644 --- a/test/regress/regress1/sygus/issue3461.sy +++ b/test/regress/regress1/sygus/issue3461.sy @@ -1,15 +1,16 @@ ; EXPECT: unsat -; COMMAND-LINE: --sygus-out=status +; COMMAND-LINE: --lang=sygus2 --sygus-out=status (set-logic ALL_SUPPORTED) (declare-datatype Doc ((D (owner Int) (body Int)))) -(declare-datatype Policy - ((p (principal Int)) +(declare-datatype Policy + ((p (principal Int)) (por (left Policy) (right Policy)))) (synth-fun mkPolicy ((d Doc)) Policy + ((Start Policy) (Q Policy)) ((Start Policy (Q)) (Q Policy ((p 0) (p 1) (por Q Q)))) ) diff --git a/test/regress/regress1/sygus/max.sy b/test/regress/regress1/sygus/max.sy index 37ed848ef..f191d784f 100644 --- a/test/regress/regress1/sygus/max.sy +++ b/test/regress/regress1/sygus/max.sy @@ -1,8 +1,9 @@ ; EXPECT: unsat -; COMMAND-LINE: --cegqi-si=all --sygus-out=status +; COMMAND-LINE: --lang=sygus2 --cegqi-si=all --sygus-out=status (set-logic LIA) (synth-fun max ((x Int) (y Int)) Int + ((Start Int) (StartBool Bool)) ((Start Int (0 1 x y (+ Start Start) (- Start Start) @@ -12,6 +13,7 @@ (<= Start Start))))) ;(synth-fun min ((x Int) (y Int)) Int +; ((Start Int) (StartBool Bool)) ; ((Start Int ((Constant Int) (Variable Int) ; (+ Start Start) ; (- Start Start) diff --git a/test/regress/regress1/sygus/parity-si-rcons.sy b/test/regress/regress1/sygus/parity-si-rcons.sy index a836c9726..850cc6610 100644 --- a/test/regress/regress1/sygus/parity-si-rcons.sy +++ b/test/regress/regress1/sygus/parity-si-rcons.sy @@ -1,5 +1,5 @@ ; EXPECT: unsat -; COMMAND-LINE: --cegqi-si=all --cegqi-si-abort --decision=internal --cbqi-prereg-inst --cegqi-si-rcons=try --sygus-out=status +; COMMAND-LINE: --lang=sygus2 --cegqi-si=all --cegqi-si-abort --decision=internal --cbqi-prereg-inst --cegqi-si-rcons=try --sygus-out=status (set-logic BV) @@ -7,6 +7,7 @@ (xor (not (xor a b)) (not (xor c d)))) (synth-fun NAND ((a Bool) (b Bool) (c Bool) (d Bool)) Bool + ((Start Bool) (StartAnd Bool) (Vars Bool) (Constants Bool)) ((Start Bool ((not StartAnd) Vars Constants)) (StartAnd Bool ((and Start Start))) (Vars Bool (a b c d)) diff --git a/test/regress/regress1/sygus/re-concat.sy b/test/regress/regress1/sygus/re-concat.sy index 3449ed505..ac1172e33 100644 --- a/test/regress/regress1/sygus/re-concat.sy +++ b/test/regress/regress1/sygus/re-concat.sy @@ -1,13 +1,13 @@ ; EXPECT: unsat -; COMMAND-LINE: --sygus-out=status +; COMMAND-LINE: --lang=sygus2 --sygus-out=status (set-logic SLIA) -(synth-fun f () RegLan ( +(synth-fun f () RegLan ((Start RegLan) (Tokens String)) ( (Start RegLan ( - (str.to.re Tokens) + (str.to_re Tokens) (re.++ Start Start))) (Tokens String ("A" "B")) )) -(constraint (str.in.re "AB" f)) +(constraint (str.in_re "AB" f)) (check-synth) diff --git a/test/regress/regress1/sygus/simple-regexp.sy b/test/regress/regress1/sygus/simple-regexp.sy index b4c248de9..b7646725d 100644 --- a/test/regress/regress1/sygus/simple-regexp.sy +++ b/test/regress/regress1/sygus/simple-regexp.sy @@ -1,30 +1,32 @@ ; EXPECT: unsat -; COMMAND-LINE: --sygus-out=status +; COMMAND-LINE: --lang=sygus2 --sygus-out=status (set-logic SLIA) -(synth-fun P ((x String)) Bool ( -(Start Bool ( - (str.in.re StartStr StartRL) +(synth-fun P ((x String)) Bool + ((Start Bool) (StartStr String) (StartStrC String) (StartRL RegLan) (StartRLi RegLan)) ( + (Start Bool ( + (str.in_re StartStr StartRL) )) -(StartStr String ( - x - (str.++ StartStr StartStr) + (StartStr String ( + x + (str.++ StartStr StartStr) + )) + (StartStrC String ( + "A" "B" "" + (str.++ StartStrC StartStrC) + )) + (StartRL RegLan ( + (re.++ StartRLi StartRLi) + (re.inter StartRL StartRL) + (re.union StartRL StartRL) + (re.* StartRLi) + )) + (StartRLi RegLan ( + (str.to_re StartStrC) + (re.inter StartRLi StartRLi) + (re.union StartRLi StartRLi) + (re.++ StartRLi StartRLi) + (re.* StartRLi) )) -(StartStrC String ( - "A" "B" "" - (str.++ StartStrC StartStrC))) -(StartRL RegLan ( -(re.++ StartRLi StartRLi) -(re.inter StartRL StartRL) -(re.union StartRL StartRL) -(re.* StartRLi) -)) -(StartRLi RegLan ( -(str.to.re StartStrC) -(re.inter StartRLi StartRLi) -(re.union StartRLi StartRLi) -(re.++ StartRLi StartRLi) -(re.* StartRLi) -)) )) (constraint (P "AAAAA")) @@ -33,5 +35,5 @@ (constraint (not (P "AB"))) (constraint (not (P "B"))) -; (str.in.re x (re.* (str.to.re "A"))) is a solution +; (str.in_re x (re.* (str.to_re "A"))) is a solution (check-synth) diff --git a/test/regress/regress1/sygus/sygus-uf-ex.sy b/test/regress/regress1/sygus/sygus-uf-ex.sy index 66880eafa..7e1cd80b3 100644 --- a/test/regress/regress1/sygus/sygus-uf-ex.sy +++ b/test/regress/regress1/sygus/sygus-uf-ex.sy @@ -1,18 +1,24 @@ ; EXPECT: unsat -; COMMAND-LINE: --sygus-out=status --uf-ho -(set-logic UFLIA) -(declare-fun uf ( Int ) Int) +; COMMAND-LINE: --lang=sygus2 --sygus-out=status --uf-ho +(set-logic ALL) + +(declare-var uf (-> Int Int)) + (synth-fun f ((x Int) (y Int)) Bool -((Start Bool (true false - (<= IntExpr IntExpr ) - (= IntExpr IntExpr ) - (and Start Start ) - (or Start Start ) - (not Start ))) -(IntExpr Int (0 1 x y - (+ IntExpr IntExpr ) - (- IntExpr IntExpr ))))) + ((Start Bool) (IntExpr Int)) + ((Start Bool (true false + (<= IntExpr IntExpr) + (= IntExpr IntExpr) + (and Start Start) + (or Start Start) + (not Start ))) + (IntExpr Int (0 1 x y + (+ IntExpr IntExpr) + (- IntExpr IntExpr))))) + (declare-var x Int) + (constraint (f (uf x) (uf x))) (constraint (not (f 3 4))) + (check-synth) -- cgit v1.2.3 From a7f4f4fcf4d42f2c5b60bd62d3fd914f31202f64 Mon Sep 17 00:00:00 2001 From: Abdalrhman Mohamed <32971963+abdoo8080@users.noreply.github.com> Date: Sat, 28 Mar 2020 14:31:37 -0500 Subject: Change is-cons to (_ is cons) in Sygus benchmarks. (#4174) --- src/parser/smt2/smt2.cpp | 8 +++++++- src/parser/smt2/smt2.h | 2 ++ test/regress/regress1/sygus/dt-test-ns.sy | 4 ++-- test/regress/regress1/sygus/list-head-x.sy | 2 +- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/parser/smt2/smt2.cpp b/src/parser/smt2/smt2.cpp index 81ddae6d6..04e8be64b 100644 --- a/src/parser/smt2/smt2.cpp +++ b/src/parser/smt2/smt2.cpp @@ -330,7 +330,7 @@ api::Term Smt2::getExpressionForNameAndType(const std::string& name, bool Smt2::getTesterName(api::Term cons, std::string& name) { - if (v2_6() && strictModeEnabled()) + if ((v2_6() || sygus_v2()) && strictModeEnabled()) { // 2.6 or above uses indexed tester symbols, if we are in strict mode, // we do not automatically define is-cons for constructor cons. @@ -744,11 +744,17 @@ bool Smt2::sygus() const return ilang == language::input::LANG_SYGUS || ilang == language::input::LANG_SYGUS_V2; } + bool Smt2::sygus_v1() const { return getLanguage() == language::input::LANG_SYGUS; } +bool Smt2::sygus_v2() const +{ + return getLanguage() == language::input::LANG_SYGUS_V2; +} + void Smt2::setInfo(const std::string& flag, const SExpr& sexpr) { // TODO: ??? } diff --git a/src/parser/smt2/smt2.h b/src/parser/smt2/smt2.h index 0400c680f..35d088601 100644 --- a/src/parser/smt2/smt2.h +++ b/src/parser/smt2/smt2.h @@ -277,6 +277,8 @@ class Smt2 : public Parser bool sygus() const; /** Are we using the sygus version 1.0 format? */ bool sygus_v1() const; + /** Are we using the sygus version 2.0 format? */ + bool sygus_v2() const; /** * Returns true if the language that we are parsing (SMT-LIB version >=2.5 diff --git a/test/regress/regress1/sygus/dt-test-ns.sy b/test/regress/regress1/sygus/dt-test-ns.sy index 3d078cc25..90fa57827 100644 --- a/test/regress/regress1/sygus/dt-test-ns.sy +++ b/test/regress/regress1/sygus/dt-test-ns.sy @@ -1,6 +1,6 @@ ; EXPECT: unsat ; COMMAND-LINE: --lang=sygus2 --cegqi-si=all --sygus-out=status -(set-logic LIA) +(set-logic DTLIA) (declare-datatypes ((List 0)) (((cons (head Int) (tail List)) (nil)))) @@ -8,6 +8,6 @@ (declare-var x Int) -(constraint (is-cons (f x))) +(constraint ((_ is cons) (f x))) (constraint (and (= (head (f x)) x) (= (head (f x)) (+ 5 (head (tail (f x))))))) (check-synth) diff --git a/test/regress/regress1/sygus/list-head-x.sy b/test/regress/regress1/sygus/list-head-x.sy index 83ac8290d..ae2bcc00e 100644 --- a/test/regress/regress1/sygus/list-head-x.sy +++ b/test/regress/regress1/sygus/list-head-x.sy @@ -8,6 +8,6 @@ (declare-var x Int) -(constraint (is-cons (f x))) +(constraint ((_ is cons) (f x))) (constraint (= (head (f x)) (+ x 7))) (check-synth) -- cgit v1.2.3 From 01b257084a0a8ee70bff32e011704330d1544c01 Mon Sep 17 00:00:00 2001 From: Andrew Reynolds Date: Sat, 28 Mar 2020 19:50:35 -0500 Subject: Enumeration for String rewrites (#4173) In preparation for string proof infrastructure. This introduces an enumeration type to track string rewrites. It also makes inference printing more consistent. --- src/CMakeLists.txt | 2 + src/theory/strings/infer_info.cpp | 24 +-- src/theory/strings/infer_info.h | 2 +- src/theory/strings/rewrites.cpp | 185 +++++++++++++++++++++ src/theory/strings/rewrites.h | 202 ++++++++++++++++++++++ src/theory/strings/sequences_rewriter.cpp | 267 +++++++++++++++--------------- src/theory/strings/sequences_rewriter.h | 5 +- src/theory/strings/strings_rewriter.cpp | 26 +-- 8 files changed, 552 insertions(+), 161 deletions(-) create mode 100644 src/theory/strings/rewrites.cpp create mode 100644 src/theory/strings/rewrites.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 760c3fba7..2ea305bc7 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -684,6 +684,8 @@ libcvc4_add_sources( theory/strings/regexp_operation.h theory/strings/regexp_solver.cpp theory/strings/regexp_solver.h + theory/strings/rewrites.cpp + theory/strings/rewrites.h theory/strings/sequences_rewriter.cpp theory/strings/sequences_rewriter.h theory/strings/sequences_stats.cpp diff --git a/src/theory/strings/infer_info.cpp b/src/theory/strings/infer_info.cpp index cdf764aa8..3b4a6b857 100644 --- a/src/theory/strings/infer_info.cpp +++ b/src/theory/strings/infer_info.cpp @@ -22,18 +22,18 @@ const char* toString(Inference i) { switch (i) { - case Inference::N_ENDPOINT_EMP: return "N_EndpointEmp"; - case Inference::N_UNIFY: return "N_Unify"; - case Inference::N_ENDPOINT_EQ: return "N_EndpointEq"; - case Inference::N_CONST: return "N_Const"; - case Inference::INFER_EMP: return "Infer-Emp"; - case Inference::SSPLIT_CST_PROP: return "S-Split(CST-P)-prop"; - case Inference::SSPLIT_VAR_PROP: return "S-Split(VAR)-prop"; - case Inference::LEN_SPLIT: return "Len-Split(Len)"; - case Inference::LEN_SPLIT_EMP: return "Len-Split(Emp)"; - case Inference::SSPLIT_CST: return "S-Split(CST-P)"; - case Inference::SSPLIT_VAR: return "S-Split(VAR)"; - case Inference::FLOOP: return "F-Loop"; + case Inference::N_ENDPOINT_EMP: return "N_ENDPOINT_EMP"; + case Inference::N_UNIFY: return "N_UNIFY"; + case Inference::N_ENDPOINT_EQ: return "N_ENDPOINT_EQ"; + case Inference::N_CONST: return "N_CONST"; + case Inference::INFER_EMP: return "INFER_EMP"; + case Inference::SSPLIT_CST_PROP: return "SSPLIT_CST_PROP"; + case Inference::SSPLIT_VAR_PROP: return "SSPLIT_VAR_PROP"; + case Inference::LEN_SPLIT: return "LEN_SPLIT"; + case Inference::LEN_SPLIT_EMP: return "LEN_SPLIT_EMP"; + case Inference::SSPLIT_CST: return "SSPLIT_CST"; + case Inference::SSPLIT_VAR: return "SSPLIT_VAR"; + case Inference::FLOOP: return "FLOOP"; default: return "?"; } } diff --git a/src/theory/strings/infer_info.h b/src/theory/strings/infer_info.h index cfabe5c51..e13a5a2ff 100644 --- a/src/theory/strings/infer_info.h +++ b/src/theory/strings/infer_info.h @@ -196,4 +196,4 @@ class InferInfo } // namespace theory } // namespace CVC4 -#endif /* CVC4__THEORY__STRINGS__THEORY_STRINGS_H */ +#endif /* CVC4__THEORY__STRINGS__INFER_INFO_H */ diff --git a/src/theory/strings/rewrites.cpp b/src/theory/strings/rewrites.cpp new file mode 100644 index 000000000..5bc62b6e4 --- /dev/null +++ b/src/theory/strings/rewrites.cpp @@ -0,0 +1,185 @@ +/********************* */ +/*! \file rewrites.cpp + ** \verbatim + ** Top contributors (to current version): + ** Andrew Reynolds + ** 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 inference information utility. + **/ + +#include "theory/strings/rewrites.h" + +#include + +namespace CVC4 { +namespace theory { +namespace strings { + +const char* toString(Rewrite r) +{ + switch (r) + { + case Rewrite::CTN_COMPONENT: return "CTN_COMPONENT"; + case Rewrite::CTN_CONCAT_CHAR: return "CTN_CONCAT_CHAR"; + case Rewrite::CTN_CONST: return "CTN_CONST"; + case Rewrite::CTN_EQ: return "CTN_EQ"; + case Rewrite::CTN_LEN_INEQ: return "CTN_LEN_INEQ"; + case Rewrite::CTN_LEN_INEQ_NSTRICT: return "CTN_LEN_INEQ_NSTRICT"; + case Rewrite::CTN_LHS_EMPTYSTR: return "CTN_LHS_EMPTYSTR"; + case Rewrite::CTN_MSET_NSS: return "CTN_MSET_NSS"; + case Rewrite::CTN_NCONST_CTN_CONCAT: return "CTN_NCONST_CTN_CONCAT"; + case Rewrite::CTN_REPL: return "CTN_REPL"; + case Rewrite::CTN_REPL_CHAR: return "CTN_REPL_CHAR"; + case Rewrite::CTN_REPL_CNSTS_TO_CTN: return "CTN_REPL_CNSTS_TO_CTN"; + case Rewrite::CTN_REPL_EMPTY: return "CTN_REPL_EMPTY"; + case Rewrite::CTN_REPL_LEN_ONE_TO_CTN: return "CTN_REPL_LEN_ONE_TO_CTN"; + case Rewrite::CTN_REPL_SELF: return "CTN_REPL_SELF"; + case Rewrite::CTN_REPL_SIMP_REPL: return "CTN_REPL_SIMP_REPL"; + case Rewrite::CTN_REPL_TO_CTN: return "CTN_REPL_TO_CTN"; + case Rewrite::CTN_REPL_TO_CTN_DISJ: return "CTN_REPL_TO_CTN_DISJ"; + case Rewrite::CTN_RHS_EMPTYSTR: return "CTN_RHS_EMPTYSTR"; + case Rewrite::CTN_RPL_NON_CTN: return "CTN_RPL_NON_CTN"; + case Rewrite::CTN_SPLIT: return "CTN_SPLIT"; + case Rewrite::CTN_SPLIT_ONES: return "CTN_SPLIT_ONES"; + case Rewrite::CTN_STRIP_ENDPT: return "CTN_STRIP_ENDPT"; + case Rewrite::CTN_SUBSTR: return "CTN_SUBSTR"; + case Rewrite::EQ_LEN_DEQ: return "EQ_LEN_DEQ"; + case Rewrite::EQ_NCTN: return "EQ_NCTN"; + case Rewrite::EQ_NFIX: return "EQ_NFIX"; + case Rewrite::FROM_CODE_EVAL: return "FROM_CODE_EVAL"; + case Rewrite::IDOF_DEF_CTN: return "IDOF_DEF_CTN"; + case Rewrite::IDOF_EMP_IDOF: return "IDOF_EMP_IDOF"; + case Rewrite::IDOF_EQ_CST_START: return "IDOF_EQ_CST_START"; + case Rewrite::IDOF_EQ_NORM: return "IDOF_EQ_NORM"; + case Rewrite::IDOF_EQ_NSTART: return "IDOF_EQ_NSTART"; + case Rewrite::IDOF_FIND: return "IDOF_FIND"; + case Rewrite::IDOF_LEN: return "IDOF_LEN"; + case Rewrite::IDOF_MAX: return "IDOF_MAX"; + case Rewrite::IDOF_NCTN: return "IDOF_NCTN"; + case Rewrite::IDOF_NEG: return "IDOF_NEG"; + case Rewrite::IDOF_NFIND: return "IDOF_NFIND"; + case Rewrite::IDOF_NORM_PREFIX: return "IDOF_NORM_PREFIX"; + case Rewrite::IDOF_PULL_ENDPT: return "IDOF_PULL_ENDPT"; + case Rewrite::IDOF_STRIP_CNST_ENDPTS: return "IDOF_STRIP_CNST_ENDPTS"; + case Rewrite::IDOF_STRIP_SYM_LEN: return "IDOF_STRIP_SYM_LEN"; + case Rewrite::ITOS_EVAL: return "ITOS_EVAL"; + case Rewrite::RE_AND_EMPTY: return "RE_AND_EMPTY"; + case Rewrite::RE_ANDOR_FLATTEN: return "RE_ANDOR_FLATTEN"; + case Rewrite::RE_CHAR_IN_STR_STAR: return "RE_CHAR_IN_STR_STAR"; + case Rewrite::RE_CONCAT: return "RE_CONCAT"; + case Rewrite::RE_CONCAT_FLATTEN: return "RE_CONCAT_FLATTEN"; + case Rewrite::RE_CONCAT_OPT: return "RE_CONCAT_OPT"; + case Rewrite::RE_CONCAT_PURE_ALLCHAR: return "RE_CONCAT_PURE_ALLCHAR"; + case Rewrite::RE_CONCAT_TO_CONTAINS: return "RE_CONCAT_TO_CONTAINS"; + case Rewrite::RE_EMPTY_IN_STR_STAR: return "RE_EMPTY_IN_STR_STAR"; + case Rewrite::RE_IN_DIST_CHAR_STAR: return "RE_IN_DIST_CHAR_STAR"; + case Rewrite::RE_IN_SIGMA_STAR: return "RE_IN_SIGMA_STAR"; + case Rewrite::RE_LOOP: return "RE_LOOP"; + case Rewrite::RE_LOOP_STAR: return "RE_LOOP_STAR"; + case Rewrite::RE_OR_ALL: return "RE_OR_ALL"; + case Rewrite::RE_SIMPLE_CONSUME: return "RE_SIMPLE_CONSUME"; + case Rewrite::RE_STAR_EMPTY: return "RE_STAR_EMPTY"; + case Rewrite::RE_STAR_EMPTY_STRING: return "RE_STAR_EMPTY_STRING"; + case Rewrite::RE_STAR_NESTED_STAR: return "RE_STAR_NESTED_STAR"; + case Rewrite::RE_STAR_UNION: return "RE_STAR_UNION"; + case Rewrite::REPL_CHAR_NCONTRIB_FIND: return "REPL_CHAR_NCONTRIB_FIND"; + case Rewrite::REPL_DUAL_REPL_ITE: return "REPL_DUAL_REPL_ITE"; + case Rewrite::REPL_REPL_SHORT_CIRCUIT: return "REPL_REPL_SHORT_CIRCUIT"; + case Rewrite::REPL_REPL2_INV: return "REPL_REPL2_INV"; + case Rewrite::REPL_REPL2_INV_ID: return "REPL_REPL2_INV_ID"; + case Rewrite::REPL_REPL3_INV: return "REPL_REPL3_INV"; + case Rewrite::REPL_REPL3_INV_ID: return "REPL_REPL3_INV_ID"; + case Rewrite::REPL_SUBST_IDX: return "REPL_SUBST_IDX"; + case Rewrite::REPLALL_CONST: return "REPLALL_CONST"; + case Rewrite::REPLALL_EMPTY_FIND: return "REPLALL_EMPTY_FIND"; + case Rewrite::RPL_CCTN: return "RPL_CCTN"; + case Rewrite::RPL_CCTN_RPL: return "RPL_CCTN_RPL"; + case Rewrite::RPL_CNTS_SUBSTS: return "RPL_CNTS_SUBSTS"; + case Rewrite::RPL_CONST_FIND: return "RPL_CONST_FIND"; + case Rewrite::RPL_CONST_NFIND: return "RPL_CONST_NFIND"; + case Rewrite::RPL_EMP_CNTS_SUBSTS: return "RPL_EMP_CNTS_SUBSTS"; + case Rewrite::RPL_ID: return "RPL_ID"; + case Rewrite::RPL_NCTN: return "RPL_NCTN"; + case Rewrite::RPL_PULL_ENDPT: return "RPL_PULL_ENDPT"; + case Rewrite::RPL_REPLACE: return "RPL_REPLACE"; + case Rewrite::RPL_RPL_EMPTY: return "RPL_RPL_EMPTY"; + case Rewrite::RPL_RPL_LEN_ID: return "RPL_RPL_LEN_ID"; + case Rewrite::RPL_X_Y_X_SIMP: return "RPL_X_Y_X_SIMP"; + case Rewrite::SPLIT_EQ: return "SPLIT_EQ"; + case Rewrite::SPLIT_EQ_STRIP_L: return "SPLIT_EQ_STRIP_L"; + case Rewrite::SPLIT_EQ_STRIP_R: return "SPLIT_EQ_STRIP_R"; + case Rewrite::SS_COMBINE: return "SS_COMBINE"; + case Rewrite::SS_CONST_END_OOB: return "SS_CONST_END_OOB"; + case Rewrite::SS_CONST_LEN_MAX_OOB: return "SS_CONST_LEN_MAX_OOB"; + case Rewrite::SS_CONST_LEN_NON_POS: return "SS_CONST_LEN_NON_POS"; + case Rewrite::SS_CONST_SS: return "SS_CONST_SS"; + case Rewrite::SS_CONST_START_MAX_OOB: return "SS_CONST_START_MAX_OOB"; + case Rewrite::SS_CONST_START_NEG: return "SS_CONST_START_NEG"; + case Rewrite::SS_CONST_START_OOB: return "SS_CONST_START_OOB"; + case Rewrite::SS_EMPTYSTR: return "SS_EMPTYSTR"; + case Rewrite::SS_END_PT_NORM: return "SS_END_PT_NORM"; + case Rewrite::SS_GEQ_ZERO_START_ENTAILS_EMP_S: + return "SS_GEQ_ZERO_START_ENTAILS_EMP_S"; + case Rewrite::SS_LEN_INCLUDE: return "SS_LEN_INCLUDE"; + case Rewrite::SS_LEN_NON_POS: return "SS_LEN_NON_POS"; + case Rewrite::SS_LEN_ONE_Z_Z: return "SS_LEN_ONE_Z_Z"; + case Rewrite::SS_NON_ZERO_LEN_ENTAILS_OOB: + return "SS_NON_ZERO_LEN_ENTAILS_OOB"; + case Rewrite::SS_START_ENTAILS_ZERO_LEN: return "SS_START_ENTAILS_ZERO_LEN"; + case Rewrite::SS_START_GEQ_LEN: return "SS_START_GEQ_LEN"; + case Rewrite::SS_START_NEG: return "SS_START_NEG"; + case Rewrite::SS_STRIP_END_PT: return "SS_STRIP_END_PT"; + case Rewrite::SS_STRIP_START_PT: return "SS_STRIP_START_PT"; + case Rewrite::STOI_CONCAT_NONNUM: return "STOI_CONCAT_NONNUM"; + case Rewrite::STOI_EVAL: return "STOI_EVAL"; + case Rewrite::STR_CONV_CONST: return "STR_CONV_CONST"; + case Rewrite::STR_CONV_IDEM: return "STR_CONV_IDEM"; + case Rewrite::STR_CONV_ITOS: return "STR_CONV_ITOS"; + case Rewrite::STR_CONV_MINSCOPE_CONCAT: return "STR_CONV_MINSCOPE_CONCAT"; + case Rewrite::STR_EMP_REPL_EMP: return "STR_EMP_REPL_EMP"; + case Rewrite::STR_EMP_REPL_EMP_R: return "STR_EMP_REPL_EMP_R"; + case Rewrite::STR_EMP_REPL_X_Y_X: return "STR_EMP_REPL_X_Y_X"; + case Rewrite::STR_EMP_SUBSTR_ELIM: return "STR_EMP_SUBSTR_ELIM"; + case Rewrite::STR_EMP_SUBSTR_LEQ_LEN: return "STR_EMP_SUBSTR_LEQ_LEN"; + case Rewrite::STR_EMP_SUBSTR_LEQ_Z: return "STR_EMP_SUBSTR_LEQ_Z"; + case Rewrite::STR_EQ_CONJ_LEN_ENTAIL: return "STR_EQ_CONJ_LEN_ENTAIL"; + case Rewrite::STR_EQ_CONST_NHOMOG: return "STR_EQ_CONST_NHOMOG"; + case Rewrite::STR_EQ_HOMOG_CONST: return "STR_EQ_HOMOG_CONST"; + case Rewrite::STR_EQ_REPL_EMP: return "STR_EQ_REPL_EMP"; + case Rewrite::STR_EQ_REPL_NOT_CTN: return "STR_EQ_REPL_NOT_CTN"; + case Rewrite::STR_EQ_REPL_TO_DIS: return "STR_EQ_REPL_TO_DIS"; + case Rewrite::STR_EQ_REPL_TO_EQ: return "STR_EQ_REPL_TO_EQ"; + case Rewrite::STR_EQ_UNIFY: return "STR_EQ_UNIFY"; + case Rewrite::STR_LEQ_CPREFIX: return "STR_LEQ_CPREFIX"; + case Rewrite::STR_LEQ_EMPTY: return "STR_LEQ_EMPTY"; + case Rewrite::STR_LEQ_EVAL: return "STR_LEQ_EVAL"; + case Rewrite::STR_LEQ_ID: return "STR_LEQ_ID"; + case Rewrite::STR_REV_CONST: return "STR_REV_CONST"; + case Rewrite::STR_REV_IDEM: return "STR_REV_IDEM"; + case Rewrite::STR_REV_MINSCOPE_CONCAT: return "STR_REV_MINSCOPE_CONCAT"; + case Rewrite::SUBSTR_REPL_SWAP: return "SUBSTR_REPL_SWAP"; + case Rewrite::SUF_PREFIX_CONST: return "SUF_PREFIX_CONST"; + case Rewrite::SUF_PREFIX_CTN: return "SUF_PREFIX_CTN"; + case Rewrite::SUF_PREFIX_EMPTY: return "SUF_PREFIX_EMPTY"; + case Rewrite::SUF_PREFIX_EMPTY_CONST: return "SUF_PREFIX_EMPTY_CONST"; + case Rewrite::SUF_PREFIX_EQ: return "SUF_PREFIX_EQ"; + case Rewrite::SUF_PREFIX_TO_EQS: return "SUF_PREFIX_TO_EQS"; + case Rewrite::TO_CODE_EVAL: return "TO_CODE_EVAL"; + default: return "?"; + } +} + +std::ostream& operator<<(std::ostream& out, Rewrite r) +{ + out << toString(r); + return out; +} + +} // namespace strings +} // namespace theory +} // namespace CVC4 diff --git a/src/theory/strings/rewrites.h b/src/theory/strings/rewrites.h new file mode 100644 index 000000000..91d52197c --- /dev/null +++ b/src/theory/strings/rewrites.h @@ -0,0 +1,202 @@ +/********************* */ +/*! \file rewrites.h + ** \verbatim + ** Top contributors (to current version): + ** Andrew Reynolds + ** 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 Type for rewrites for strings. + **/ + +#include "cvc4_private.h" + +#ifndef CVC4__THEORY__STRINGS__REWRITES_H +#define CVC4__THEORY__STRINGS__REWRITES_H + +#include + +namespace CVC4 { +namespace theory { +namespace strings { + +/** Types of rewrites used by strings + * + * This rewrites are documented where they are used in the rewriter. + */ +enum class Rewrite : uint32_t +{ + CTN_COMPONENT, + CTN_CONCAT_CHAR, + CTN_CONST, + CTN_EQ, + CTN_LEN_INEQ, + CTN_LEN_INEQ_NSTRICT, + CTN_LHS_EMPTYSTR, + CTN_MSET_NSS, + CTN_NCONST_CTN_CONCAT, + CTN_REPL, + CTN_REPL_CHAR, + CTN_REPL_CNSTS_TO_CTN, + CTN_REPL_EMPTY, + CTN_REPL_LEN_ONE_TO_CTN, + CTN_REPL_SELF, + CTN_REPL_SIMP_REPL, + CTN_REPL_TO_CTN, + CTN_REPL_TO_CTN_DISJ, + CTN_RHS_EMPTYSTR, + CTN_RPL_NON_CTN, + CTN_SPLIT, + CTN_SPLIT_ONES, + CTN_STRIP_ENDPT, + CTN_SUBSTR, + EQ_LEN_DEQ, + EQ_NCTN, + EQ_NFIX, + FROM_CODE_EVAL, + IDOF_DEF_CTN, + IDOF_EMP_IDOF, + IDOF_EQ_CST_START, + IDOF_EQ_NORM, + IDOF_EQ_NSTART, + IDOF_FIND, + IDOF_LEN, + IDOF_MAX, + IDOF_NCTN, + IDOF_NEG, + IDOF_NFIND, + IDOF_NORM_PREFIX, + IDOF_PULL_ENDPT, + IDOF_STRIP_CNST_ENDPTS, + IDOF_STRIP_SYM_LEN, + ITOS_EVAL, + RE_AND_EMPTY, + RE_ANDOR_FLATTEN, + RE_CHAR_IN_STR_STAR, + RE_CONCAT, + RE_CONCAT_FLATTEN, + RE_CONCAT_OPT, + RE_CONCAT_PURE_ALLCHAR, + RE_CONCAT_TO_CONTAINS, + RE_EMPTY_IN_STR_STAR, + RE_IN_DIST_CHAR_STAR, + RE_IN_SIGMA_STAR, + RE_LOOP, + RE_LOOP_STAR, + RE_OR_ALL, + RE_SIMPLE_CONSUME, + RE_STAR_EMPTY, + RE_STAR_EMPTY_STRING, + RE_STAR_NESTED_STAR, + RE_STAR_UNION, + REPL_CHAR_NCONTRIB_FIND, + REPL_DUAL_REPL_ITE, + REPL_REPL_SHORT_CIRCUIT, + REPL_REPL2_INV, + REPL_REPL2_INV_ID, + REPL_REPL3_INV, + REPL_REPL3_INV_ID, + REPL_SUBST_IDX, + REPLALL_CONST, + REPLALL_EMPTY_FIND, + RPL_CCTN, + RPL_CCTN_RPL, + RPL_CNTS_SUBSTS, + RPL_CONST_FIND, + RPL_CONST_NFIND, + RPL_EMP_CNTS_SUBSTS, + RPL_ID, + RPL_NCTN, + RPL_PULL_ENDPT, + RPL_REPLACE, + RPL_RPL_EMPTY, + RPL_RPL_LEN_ID, + RPL_X_Y_X_SIMP, + SPLIT_EQ, + SPLIT_EQ_STRIP_L, + SPLIT_EQ_STRIP_R, + SS_COMBINE, + SS_CONST_END_OOB, + SS_CONST_LEN_MAX_OOB, + SS_CONST_LEN_NON_POS, + SS_CONST_SS, + SS_CONST_START_MAX_OOB, + SS_CONST_START_NEG, + SS_CONST_START_OOB, + SS_EMPTYSTR, + SS_END_PT_NORM, + SS_GEQ_ZERO_START_ENTAILS_EMP_S, + SS_LEN_INCLUDE, + SS_LEN_NON_POS, + SS_LEN_ONE_Z_Z, + SS_NON_ZERO_LEN_ENTAILS_OOB, + SS_START_ENTAILS_ZERO_LEN, + SS_START_GEQ_LEN, + SS_START_NEG, + SS_STRIP_END_PT, + SS_STRIP_START_PT, + STOI_CONCAT_NONNUM, + STOI_EVAL, + STR_CONV_CONST, + STR_CONV_IDEM, + STR_CONV_ITOS, + STR_CONV_MINSCOPE_CONCAT, + STR_EMP_REPL_EMP, + STR_EMP_REPL_EMP_R, + STR_EMP_REPL_X_Y_X, + STR_EMP_SUBSTR_ELIM, + STR_EMP_SUBSTR_LEQ_LEN, + STR_EMP_SUBSTR_LEQ_Z, + STR_EQ_CONJ_LEN_ENTAIL, + STR_EQ_CONST_NHOMOG, + STR_EQ_HOMOG_CONST, + STR_EQ_REPL_EMP, + STR_EQ_REPL_NOT_CTN, + STR_EQ_REPL_TO_DIS, + STR_EQ_REPL_TO_EQ, + STR_EQ_UNIFY, + STR_LEQ_CPREFIX, + STR_LEQ_EMPTY, + STR_LEQ_EVAL, + STR_LEQ_ID, + STR_REV_CONST, + STR_REV_IDEM, + STR_REV_MINSCOPE_CONCAT, + SUBSTR_REPL_SWAP, + SUF_PREFIX_CONST, + SUF_PREFIX_CTN, + SUF_PREFIX_EMPTY, + SUF_PREFIX_EMPTY_CONST, + SUF_PREFIX_EQ, + SUF_PREFIX_TO_EQS, + TO_CODE_EVAL +}; + +/** + * Converts an rewrite to a string. Note: This function is also used in + * `safe_print()`. Changing this functions name or signature will result in + * `safe_print()` printing "" instead of the proper strings for + * the enum values. + * + * @param r The rewrite + * @return The name of the rewrite + */ +const char* toString(Rewrite r); + +/** + * Writes an rewrite name to a stream. + * + * @param out The stream to write to + * @param r The rewrite to write to the stream + * @return The stream + */ +std::ostream& operator<<(std::ostream& out, Rewrite r); + +} // namespace strings +} // namespace theory +} // namespace CVC4 + +#endif /* CVC4__THEORY__STRINGS__REWRITES_H */ diff --git a/src/theory/strings/sequences_rewriter.cpp b/src/theory/strings/sequences_rewriter.cpp index b0940b7e1..861d99135 100644 --- a/src/theory/strings/sequences_rewriter.cpp +++ b/src/theory/strings/sequences_rewriter.cpp @@ -328,7 +328,7 @@ Node SequencesRewriter::rewriteEquality(Node node) { if (!ctn.getConst()) { - return returnRewrite(node, ctn, "eq-nctn"); + return returnRewrite(node, ctn, Rewrite::EQ_NCTN); } else { @@ -347,7 +347,7 @@ Node SequencesRewriter::rewriteEquality(Node node) len_eq = Rewriter::rewrite(len_eq); if (len_eq.isConst() && !len_eq.getConst()) { - return returnRewrite(node, len_eq, "eq-len-deq"); + return returnRewrite(node, len_eq, Rewrite::EQ_LEN_DEQ); } std::vector c[2]; @@ -375,7 +375,7 @@ Node SequencesRewriter::rewriteEquality(Node node) if (!isSameFix) { Node ret = NodeManager::currentNM()->mkConst(false); - return returnRewrite(node, ret, "eq-nfix"); + return returnRewrite(node, ret, Rewrite::EQ_NFIX); } } if (c[0][index1] != c[1][index2]) @@ -465,7 +465,7 @@ Node SequencesRewriter::rewriteStrEqualityExt(Node node) Node s1 = utils::mkConcat(c[0], stype); Node s2 = utils::mkConcat(c[1], stype); new_ret = s1.eqNode(s2); - node = returnRewrite(node, new_ret, "str-eq-unify"); + node = returnRewrite(node, new_ret, Rewrite::STR_EQ_UNIFY); } // ------- homogeneous constants @@ -505,7 +505,7 @@ Node SequencesRewriter::rewriteStrEqualityExt(Node node) // this conflict just in case. new_ret = nm->mkConst(false); return returnRewrite( - node, new_ret, "string-eq-const-conflict-non-homog"); + node, new_ret, Rewrite::STR_EQ_CONST_NHOMOG); } numHChars[j]++; } @@ -540,7 +540,7 @@ Node SequencesRewriter::rewriteStrEqualityExt(Node node) // "AA" = y ++ x ---> "AA" = x ++ y if x < y // "AAA" = y ++ "A" ++ z ---> "AA" = y ++ z new_ret = lhs.eqNode(ss); - node = returnRewrite(node, new_ret, "str-eq-homog-const"); + node = returnRewrite(node, new_ret, Rewrite::STR_EQ_HOMOG_CONST); } } } @@ -558,7 +558,7 @@ Node SequencesRewriter::rewriteStrEqualityExt(Node node) if (ne[0] == ne[2]) { Node ret = nm->mkNode(EQUAL, ne[0], empty); - return returnRewrite(node, ret, "str-emp-repl-x-y-x"); + return returnRewrite(node, ret, Rewrite::STR_EMP_REPL_X_Y_X); } // (= "" (str.replace x y "A")) ---> (and (= x "") (not (= y ""))) @@ -568,14 +568,14 @@ Node SequencesRewriter::rewriteStrEqualityExt(Node node) nm->mkNode(AND, nm->mkNode(EQUAL, ne[0], empty), nm->mkNode(NOT, nm->mkNode(EQUAL, ne[1], empty))); - return returnRewrite(node, ret, "str-emp-repl-emp"); + return returnRewrite(node, ret, Rewrite::STR_EMP_REPL_EMP); } // (= "" (str.replace x "A" "")) ---> (str.prefix x "A") if (checkEntailLengthOne(ne[1]) && ne[2] == empty) { Node ret = nm->mkNode(STRING_PREFIX, ne[0], ne[1]); - return returnRewrite(node, ret, "str-emp-repl-emp"); + return returnRewrite(node, ret, Rewrite::STR_EMP_REPL_EMP); } } else if (ne.getKind() == STRING_SUBSTR) @@ -588,20 +588,20 @@ Node SequencesRewriter::rewriteStrEqualityExt(Node node) if (ne[1] == zero) { Node ret = nm->mkNode(EQUAL, ne[0], empty); - return returnRewrite(node, ret, "str-emp-substr-leq-len"); + return returnRewrite(node, ret, Rewrite::STR_EMP_SUBSTR_LEQ_LEN); } // (= "" (str.substr x n m)) ---> (<= (str.len x) n) // if n >= 0 and m > 0 Node ret = nm->mkNode(LEQ, nm->mkNode(STRING_LENGTH, ne[0]), ne[1]); - return returnRewrite(node, ret, "str-emp-substr-leq-len"); + return returnRewrite(node, ret, Rewrite::STR_EMP_SUBSTR_LEQ_LEN); } // (= "" (str.substr "A" 0 z)) ---> (<= z 0) if (checkEntailNonEmpty(ne[0]) && ne[1] == zero) { Node ret = nm->mkNode(LEQ, ne[2], zero); - return returnRewrite(node, ret, "str-emp-substr-leq-z"); + return returnRewrite(node, ret, Rewrite::STR_EMP_SUBSTR_LEQ_Z); } } } @@ -620,14 +620,14 @@ Node SequencesRewriter::rewriteStrEqualityExt(Node node) { Node ret = nm->mkNode( EQUAL, empty, nm->mkNode(STRING_STRREPL, x, repl[2], repl[1])); - return returnRewrite(node, ret, "str-eq-repl-emp"); + return returnRewrite(node, ret, Rewrite::STR_EQ_REPL_EMP); } // (= x (str.replace y x y)) ---> (= x y) if (repl[0] == repl[2] && x == repl[1]) { Node ret = nm->mkNode(EQUAL, x, repl[0]); - return returnRewrite(node, ret, "str-eq-repl-to-eq"); + return returnRewrite(node, ret, Rewrite::STR_EQ_REPL_TO_EQ); } // (= x (str.replace x "A" "B")) ---> (not (str.contains x "A")) @@ -637,7 +637,7 @@ Node SequencesRewriter::rewriteStrEqualityExt(Node node) if (eq.isConst() && !eq.getConst()) { Node ret = nm->mkNode(NOT, nm->mkNode(STRING_STRCTN, x, repl[1])); - return returnRewrite(node, ret, "str-eq-repl-not-ctn"); + return returnRewrite(node, ret, Rewrite::STR_EQ_REPL_NOT_CTN); } } @@ -652,7 +652,7 @@ Node SequencesRewriter::rewriteStrEqualityExt(Node node) Node ret = nm->mkNode(OR, nm->mkNode(EQUAL, repl[0], repl[1]), nm->mkNode(EQUAL, repl[0], repl[2])); - return returnRewrite(node, ret, "str-eq-repl-to-dis"); + return returnRewrite(node, ret, Rewrite::STR_EQ_REPL_TO_DIS); } } } @@ -673,7 +673,7 @@ Node SequencesRewriter::rewriteStrEqualityExt(Node node) new_ret = inferEqsFromContains(node[i], node[1 - i]); if (!new_ret.isNull()) { - return returnRewrite(node, new_ret, "str-eq-conj-len-entail"); + return returnRewrite(node, new_ret, Rewrite::STR_EQ_CONJ_LEN_ENTAIL); } } } @@ -715,7 +715,7 @@ Node SequencesRewriter::rewriteStrEqualityExt(Node node) pfx0.eqNode(pfx1), utils::mkConcat(sfxv0, stype) .eqNode(utils::mkConcat(sfxv1, stype))); - return returnRewrite(node, ret, "split-eq"); + return returnRewrite(node, ret, Rewrite::SPLIT_EQ); } else if (checkEntailArith(lenPfx1, lenPfx0, true)) { @@ -735,7 +735,7 @@ Node SequencesRewriter::rewriteStrEqualityExt(Node node) pfx0.eqNode(utils::mkConcat(rpfxv1, stype)), utils::mkConcat(sfxv0, stype) .eqNode(utils::mkConcat(pfxv1, stype))); - return returnRewrite(node, ret, "split-eq-strip-r"); + return returnRewrite(node, ret, Rewrite::SPLIT_EQ_STRIP_R); } // If the prefix of the right-hand side is (strictly) longer than @@ -762,7 +762,7 @@ Node SequencesRewriter::rewriteStrEqualityExt(Node node) utils::mkConcat(rpfxv0, stype).eqNode(pfx1), utils::mkConcat(pfxv0, stype) .eqNode(utils::mkConcat(sfxv1, stype))); - return returnRewrite(node, ret, "split-eq-strip-l"); + return returnRewrite(node, ret, Rewrite::SPLIT_EQ_STRIP_L); } // If the prefix of the left-hand side is (strictly) longer than @@ -961,7 +961,7 @@ Node SequencesRewriter::rewriteConcatRegExp(TNode node) { retNode = vec.size() == 1 ? vec[0] : nm->mkNode(REGEXP_CONCAT, vec); } - return returnRewrite(node, retNode, "re.concat-flatten"); + return returnRewrite(node, retNode, Rewrite::RE_CONCAT_FLATTEN); } Trace("strings-rewrite-debug") << "Strings::rewriteConcatRegExp start " << node << std::endl; @@ -1039,7 +1039,7 @@ Node SequencesRewriter::rewriteConcatRegExp(TNode node) { // handles all cases where consecutive re constants are combined or // dropped as described in the loop above. - return returnRewrite(node, retNode, "re.concat"); + return returnRewrite(node, retNode, Rewrite::RE_CONCAT); } // flipping adjacent star arguments @@ -1056,7 +1056,7 @@ Node SequencesRewriter::rewriteConcatRegExp(TNode node) if (changed) { retNode = utils::mkConcat(cvec, rtype); - return returnRewrite(node, retNode, "re.concat.opt"); + return returnRewrite(node, retNode, Rewrite::RE_CONCAT_OPT); } return node; } @@ -1069,19 +1069,19 @@ Node SequencesRewriter::rewriteStarRegExp(TNode node) if (node[0].getKind() == REGEXP_STAR) { // ((R)*)* ---> R* - return returnRewrite(node, node[0], "re-star-nested-star"); + return returnRewrite(node, node[0], Rewrite::RE_STAR_NESTED_STAR); } else if (node[0].getKind() == STRING_TO_REGEXP && node[0][0].isConst() && Word::isEmpty(node[0][0])) { // ("")* ---> "" - return returnRewrite(node, node[0], "re-star-empty-string"); + return returnRewrite(node, node[0], Rewrite::RE_STAR_EMPTY_STRING); } else if (node[0].getKind() == REGEXP_EMPTY) { // (empty)* ---> "" retNode = nm->mkNode(STRING_TO_REGEXP, nm->mkConst(String(""))); - return returnRewrite(node, retNode, "re-star-empty"); + return returnRewrite(node, retNode, Rewrite::RE_STAR_EMPTY); } else if (node[0].getKind() == REGEXP_UNION) { @@ -1110,7 +1110,7 @@ Node SequencesRewriter::rewriteStarRegExp(TNode node) retNode = nm->mkNode(REGEXP_STAR, retNode); // simplification of union beneath star based on loop above // for example, ( "" | "a" )* ---> ("a")* - return returnRewrite(node, retNode, "re-star-union"); + return returnRewrite(node, retNode, Rewrite::RE_STAR_UNION); } } } @@ -1140,7 +1140,7 @@ Node SequencesRewriter::rewriteAndOrRegExp(TNode node) { if (nk == REGEXP_INTER) { - return returnRewrite(node, ni, "re.and-empty"); + return returnRewrite(node, ni, Rewrite::RE_AND_EMPTY); } // otherwise, can ignore } @@ -1148,7 +1148,7 @@ Node SequencesRewriter::rewriteAndOrRegExp(TNode node) { if (nk == REGEXP_UNION) { - return returnRewrite(node, ni, "re.or-all"); + return returnRewrite(node, ni, Rewrite::RE_OR_ALL); } // otherwise, can ignore } @@ -1178,7 +1178,7 @@ Node SequencesRewriter::rewriteAndOrRegExp(TNode node) if (retNode != node) { // flattening and removing children, based on loop above - return returnRewrite(node, retNode, "re.andor-flatten"); + return returnRewrite(node, retNode, Rewrite::RE_ANDOR_FLATTEN); } return node; } @@ -1190,7 +1190,7 @@ Node SequencesRewriter::rewriteLoopRegExp(TNode node) Node r = node[0]; if (r.getKind() == REGEXP_STAR) { - return returnRewrite(node, r, "re.loop-star"); + return returnRewrite(node, r, Rewrite::RE_LOOP_STAR); } TNode n1 = node[1]; NodeManager* nm = NodeManager::currentNM(); @@ -1252,7 +1252,7 @@ Node SequencesRewriter::rewriteLoopRegExp(TNode node) << std::endl; if (retNode != node) { - return returnRewrite(node, retNode, "re.loop"); + return returnRewrite(node, retNode, Rewrite::RE_LOOP); } return node; } @@ -1556,7 +1556,7 @@ Node SequencesRewriter::rewriteMembership(TNode node) { retNode = nm->mkConst(true); // e.g. (str.in.re "" (re.* (str.to.re x))) ----> true - return returnRewrite(node, retNode, "re-empty-in-str-star"); + return returnRewrite(node, retNode, Rewrite::RE_EMPTY_IN_STR_STAR); } else if (s.size() == 1) { @@ -1564,7 +1564,7 @@ Node SequencesRewriter::rewriteMembership(TNode node) { retNode = r[0][0].eqNode(x); // e.g. (str.in.re "A" (re.* (str.to.re x))) ----> "A" = x - return returnRewrite(node, retNode, "re-char-in-str-star"); + return returnRewrite(node, retNode, Rewrite::RE_CHAR_IN_STR_STAR); } } } @@ -1585,14 +1585,14 @@ Node SequencesRewriter::rewriteMembership(TNode node) nb << nm->mkNode(STRING_IN_REGEXP, xc, r); } return returnRewrite( - node, nb.constructNode(), "re-in-dist-char-star"); + node, nb.constructNode(), Rewrite::RE_IN_DIST_CHAR_STAR); } } } if (r[0].getKind() == kind::REGEXP_SIGMA) { retNode = NodeManager::currentNM()->mkConst(true); - return returnRewrite(node, retNode, "re-in-sigma-star"); + return returnRewrite(node, retNode, Rewrite::RE_IN_SIGMA_STAR); } } else if (r.getKind() == kind::REGEXP_CONCAT) @@ -1642,14 +1642,14 @@ Node SequencesRewriter::rewriteMembership(TNode node) Node num = nm->mkConst(Rational(allSigmaMinSize)); Node lenx = nm->mkNode(STRING_LENGTH, x); retNode = nm->mkNode(allSigmaStrict ? EQUAL : GEQ, lenx, num); - return returnRewrite(node, retNode, "re-concat-pure-allchar"); + return returnRewrite(node, retNode, Rewrite::RE_CONCAT_PURE_ALLCHAR); } else if (allSigmaMinSize == 0 && nchildren >= 3 && constIdx != 0 && constIdx != nchildren - 1) { // x in re.++(_*, "abc", _*) ---> str.contains(x, "abc") retNode = nm->mkNode(STRING_STRCTN, x, constStr); - return returnRewrite(node, retNode, "re-concat-to-contains"); + return returnRewrite(node, retNode, Rewrite::RE_CONCAT_TO_CONTAINS); } } } @@ -1773,7 +1773,7 @@ Node SequencesRewriter::rewriteMembership(TNode node) } Trace("regexp-ext-rewrite") << "Regexp : rewrite : " << node << " -> " << retNode << std::endl; - return returnRewrite(node, retNode, "re-simple-consume"); + return returnRewrite(node, retNode, Rewrite::RE_SIMPLE_CONSUME); } } } @@ -2003,7 +2003,7 @@ Node SequencesRewriter::rewriteSubstr(Node node) if (Word::isEmpty(node[0])) { Node ret = node[0]; - return returnRewrite(node, ret, "ss-emptystr"); + return returnRewrite(node, ret, Rewrite::SS_EMPTYSTR); } // rewriting for constant arguments if (node[1].isConst() && node[2].isConst()) @@ -2016,13 +2016,13 @@ Node SequencesRewriter::rewriteSubstr(Node node) // start beyond the maximum size of strings // thus, it must be beyond the end point of this string Node ret = Word::mkEmptyWord(node.getType()); - return returnRewrite(node, ret, "ss-const-start-max-oob"); + return returnRewrite(node, ret, Rewrite::SS_CONST_START_MAX_OOB); } else if (node[1].getConst().sgn() < 0) { // start before the beginning of the string Node ret = Word::mkEmptyWord(node.getType()); - return returnRewrite(node, ret, "ss-const-start-neg"); + return returnRewrite(node, ret, Rewrite::SS_CONST_START_NEG); } else { @@ -2031,7 +2031,7 @@ Node SequencesRewriter::rewriteSubstr(Node node) { // start beyond the end of the string Node ret = Word::mkEmptyWord(node.getType()); - return returnRewrite(node, ret, "ss-const-start-oob"); + return returnRewrite(node, ret, Rewrite::SS_CONST_START_OOB); } } if (node[2].getConst() > rMaxInt) @@ -2039,12 +2039,12 @@ Node SequencesRewriter::rewriteSubstr(Node node) // take up to the end of the string size_t lenS = Word::getLength(s); Node ret = Word::suffix(s, lenS - start); - return returnRewrite(node, ret, "ss-const-len-max-oob"); + return returnRewrite(node, ret, Rewrite::SS_CONST_LEN_MAX_OOB); } else if (node[2].getConst().sgn() <= 0) { Node ret = Word::mkEmptyWord(node.getType()); - return returnRewrite(node, ret, "ss-const-len-non-pos"); + return returnRewrite(node, ret, Rewrite::SS_CONST_LEN_NON_POS); } else { @@ -2055,13 +2055,13 @@ Node SequencesRewriter::rewriteSubstr(Node node) // take up to the end of the string size_t lenS = Word::getLength(s); Node ret = Word::suffix(s, lenS - start); - return returnRewrite(node, ret, "ss-const-end-oob"); + return returnRewrite(node, ret, Rewrite::SS_CONST_END_OOB); } else { // compute the substr using the constant string Node ret = Word::substr(s, start, len); - return returnRewrite(node, ret, "ss-const-ss"); + return returnRewrite(node, ret, Rewrite::SS_CONST_SS); } } } @@ -2072,12 +2072,12 @@ Node SequencesRewriter::rewriteSubstr(Node node) if (checkEntailArith(zero, node[1], true)) { Node ret = Word::mkEmptyWord(node.getType()); - return returnRewrite(node, ret, "ss-start-neg"); + return returnRewrite(node, ret, Rewrite::SS_START_NEG); } else if (checkEntailArith(zero, node[2])) { Node ret = Word::mkEmptyWord(node.getType()); - return returnRewrite(node, ret, "ss-len-non-pos"); + return returnRewrite(node, ret, Rewrite::SS_LEN_NON_POS); } if (node[0].getKind() == STRING_SUBSTR) @@ -2103,7 +2103,7 @@ Node SequencesRewriter::rewriteSubstr(Node node) if (checkEntailArith(node[1], node[0][2])) { Node ret = Word::mkEmptyWord(node.getType()); - return returnRewrite(node, ret, "ss-start-geq-len"); + return returnRewrite(node, ret, Rewrite::SS_START_GEQ_LEN); } } else if (node[0].getKind() == STRING_STRREPL) @@ -2121,7 +2121,7 @@ Node SequencesRewriter::rewriteSubstr(Node node) nm->mkNode(kind::STRING_SUBSTR, node[0][0], node[1], node[2]), node[0][1], node[0][2]); - return returnRewrite(node, ret, "substr-repl-swap"); + return returnRewrite(node, ret, Rewrite::SUBSTR_REPL_SWAP); } } } @@ -2143,7 +2143,7 @@ Node SequencesRewriter::rewriteSubstr(Node node) kind::STRING_SUBSTR, utils::mkConcat(n1, stype), node[1], curr)); } Node ret = utils::mkConcat(childrenr, stype); - return returnRewrite(node, ret, "ss-len-include"); + return returnRewrite(node, ret, Rewrite::SS_LEN_INCLUDE); } } @@ -2171,7 +2171,7 @@ Node SequencesRewriter::rewriteSubstr(Node node) { // end point beyond end point of string, map to tot_len Node ret = nm->mkNode(kind::STRING_SUBSTR, node[0], node[1], tot_len); - return returnRewrite(node, ret, "ss-end-pt-norm"); + return returnRewrite(node, ret, Rewrite::SS_END_PT_NORM); } else { @@ -2186,7 +2186,7 @@ Node SequencesRewriter::rewriteSubstr(Node node) if (checkEntailArithWithAssumption(n1_lt_tot_len, zero, node[2], false)) { Node ret = Word::mkEmptyWord(node.getType()); - return returnRewrite(node, ret, "ss-start-entails-zero-len"); + return returnRewrite(node, ret, Rewrite::SS_START_ENTAILS_ZERO_LEN); } // (str.substr s x y) --> "" if 0 < y |= x >= str.len(s) @@ -2195,7 +2195,7 @@ Node SequencesRewriter::rewriteSubstr(Node node) if (checkEntailArithWithAssumption(non_zero_len, node[1], tot_len, false)) { Node ret = Word::mkEmptyWord(node.getType()); - return returnRewrite(node, ret, "ss-non-zero-len-entails-oob"); + return returnRewrite(node, ret, Rewrite::SS_NON_ZERO_LEN_ENTAILS_OOB); } // (str.substr s x y) --> "" if x >= 0 |= 0 >= str.len(s) @@ -2204,14 +2204,15 @@ Node SequencesRewriter::rewriteSubstr(Node node) if (checkEntailArithWithAssumption(geq_zero_start, zero, tot_len, false)) { Node ret = Word::mkEmptyWord(node.getType()); - return returnRewrite(node, ret, "ss-geq-zero-start-entails-emp-s"); + return returnRewrite( + node, ret, Rewrite::SS_GEQ_ZERO_START_ENTAILS_EMP_S); } // (str.substr s x x) ---> "" if (str.len s) <= 1 if (node[1] == node[2] && checkEntailLengthOne(node[0])) { Node ret = Word::mkEmptyWord(node.getType()); - return returnRewrite(node, ret, "ss-len-one-z-z"); + return returnRewrite(node, ret, Rewrite::SS_LEN_ONE_Z_Z); } } if (!curr.isNull()) @@ -2225,7 +2226,7 @@ Node SequencesRewriter::rewriteSubstr(Node node) { Node ret = nm->mkNode( kind::STRING_SUBSTR, utils::mkConcat(n1, stype), curr, node[2]); - return returnRewrite(node, ret, "ss-strip-start-pt"); + return returnRewrite(node, ret, Rewrite::SS_STRIP_START_PT); } else { @@ -2233,7 +2234,7 @@ Node SequencesRewriter::rewriteSubstr(Node node) utils::mkConcat(n1, stype), node[1], node[2]); - return returnRewrite(node, ret, "ss-strip-end-pt"); + return returnRewrite(node, ret, Rewrite::SS_STRIP_END_PT); } } } @@ -2273,7 +2274,7 @@ Node SequencesRewriter::rewriteSubstr(Node node) Node new_start = nm->mkNode(kind::PLUS, start_inner, start_outer); Node ret = nm->mkNode(kind::STRING_SUBSTR, node[0][0], new_start, new_len); - return returnRewrite(node, ret, "ss-combine"); + return returnRewrite(node, ret, Rewrite::SS_COMBINE); } } } @@ -2289,7 +2290,7 @@ Node SequencesRewriter::rewriteContains(Node node) if (node[0] == node[1]) { Node ret = NodeManager::currentNM()->mkConst(true); - return returnRewrite(node, ret, "ctn-eq"); + return returnRewrite(node, ret, Rewrite::CTN_EQ); } if (node[0].isConst()) { @@ -2297,7 +2298,7 @@ Node SequencesRewriter::rewriteContains(Node node) if (node[1].isConst()) { Node ret = nm->mkConst(Word::find(node[0], node[1]) != std::string::npos); - return returnRewrite(node, ret, "ctn-const"); + return returnRewrite(node, ret, Rewrite::CTN_CONST); } else { @@ -2312,7 +2313,7 @@ Node SequencesRewriter::rewriteContains(Node node) // uses this function, hence we want to conclude false if possible. // len(x)>0 => contains( "", x ) ---> false Node ret = NodeManager::currentNM()->mkConst(false); - return returnRewrite(node, ret, "ctn-lhs-emptystr"); + return returnRewrite(node, ret, Rewrite::CTN_LHS_EMPTYSTR); } } else if (checkEntailLengthOne(t)) @@ -2331,7 +2332,7 @@ Node SequencesRewriter::rewriteContains(Node node) // t = "" v t = "A" v t = "B" v t = "C" v t = "a" v t = "b" v t = "c" // if len(t) <= 1 Node ret = nb; - return returnRewrite(node, ret, "ctn-split"); + return returnRewrite(node, ret, Rewrite::CTN_SPLIT); } else if (node[1].getKind() == kind::STRING_CONCAT) { @@ -2339,7 +2340,7 @@ Node SequencesRewriter::rewriteContains(Node node) if (!canConstantContainConcat(node[0], node[1], firstc, lastc)) { Node ret = NodeManager::currentNM()->mkConst(false); - return returnRewrite(node, ret, "ctn-nconst-ctn-concat"); + return returnRewrite(node, ret, Rewrite::CTN_NCONST_CTN_CONCAT); } } } @@ -2351,7 +2352,7 @@ Node SequencesRewriter::rewriteContains(Node node) { // contains( x, "" ) ---> true Node ret = NodeManager::currentNM()->mkConst(true); - return returnRewrite(node, ret, "ctn-rhs-emptystr"); + return returnRewrite(node, ret, Rewrite::CTN_RHS_EMPTYSTR); } else if (len == 1) { @@ -2370,7 +2371,7 @@ Node SequencesRewriter::rewriteContains(Node node) Node ret = nb.constructNode(); // str.contains( x ++ y, "A" ) ---> // str.contains( x, "A" ) OR str.contains( y, "A" ) - return returnRewrite(node, ret, "ctn-concat-char"); + return returnRewrite(node, ret, Rewrite::CTN_CONCAT_CHAR); } else if (node[0].getKind() == STRING_STRREPL) { @@ -2387,7 +2388,7 @@ Node SequencesRewriter::rewriteContains(Node node) // str.contains( str.replace( x, y, z ), "A" ) ---> // str.contains( x, "A" ) OR // ( str.contains( x, y ) AND str.contains( z, "A" ) ) - return returnRewrite(node, ret, "ctn-repl-char"); + return returnRewrite(node, ret, Rewrite::CTN_REPL_CHAR); } } } @@ -2403,7 +2404,7 @@ Node SequencesRewriter::rewriteContains(Node node) if (componentContains(nc1, nc2, nc1rb, nc1re) != -1) { Node ret = NodeManager::currentNM()->mkConst(true); - return returnRewrite(node, ret, "ctn-component"); + return returnRewrite(node, ret, Rewrite::CTN_COMPONENT); } TypeNode stype = node[0].getType(); @@ -2414,7 +2415,7 @@ Node SequencesRewriter::rewriteContains(Node node) { Node ret = NodeManager::currentNM()->mkNode( kind::STRING_STRCTN, utils::mkConcat(nc1, stype), node[1]); - return returnRewrite(node, ret, "ctn-strip-endpt"); + return returnRewrite(node, ret, Rewrite::CTN_STRIP_ENDPT); } for (const Node& n : nc2) @@ -2435,7 +2436,7 @@ Node SequencesRewriter::rewriteContains(Node node) if (!ctnConst2.isNull() && !ctnConst2.getConst()) { Node res = nm->mkConst(false); - return returnRewrite(node, res, "ctn-rpl-non-ctn"); + return returnRewrite(node, res, Rewrite::CTN_RPL_NON_CTN); } } @@ -2467,7 +2468,7 @@ Node SequencesRewriter::rewriteContains(Node node) { ret = nm->mkNode(kind::EQUAL, node[0], node[1]); } - return returnRewrite(node, ret, "ctn-repl-self"); + return returnRewrite(node, ret, Rewrite::CTN_REPL_SELF); } } } @@ -2479,7 +2480,7 @@ Node SequencesRewriter::rewriteContains(Node node) { // len( n2 ) > len( n1 ) => contains( n1, n2 ) ---> false Node ret = NodeManager::currentNM()->mkConst(false); - return returnRewrite(node, ret, "ctn-len-ineq"); + return returnRewrite(node, ret, Rewrite::CTN_LEN_INEQ); } // multi-set reasoning @@ -2489,14 +2490,14 @@ Node SequencesRewriter::rewriteContains(Node node) if (checkEntailMultisetSubset(node[0], node[1])) { Node ret = nm->mkConst(false); - return returnRewrite(node, ret, "ctn-mset-nss"); + return returnRewrite(node, ret, Rewrite::CTN_MSET_NSS); } if (checkEntailArith(len_n2, len_n1, false)) { // len( n2 ) >= len( n1 ) => contains( n1, n2 ) ---> n1 = n2 Node ret = node[0].eqNode(node[1]); - return returnRewrite(node, ret, "ctn-len-ineq-nstrict"); + return returnRewrite(node, ret, Rewrite::CTN_LEN_INEQ_NSTRICT); } // splitting @@ -2534,7 +2535,7 @@ Node SequencesRewriter::rewriteContains(Node node) NodeManager::currentNM()->mkNode(kind::STRING_STRCTN, utils::mkConcat(spl[1], stype), node[1])); - return returnRewrite(node, ret, "ctn-split"); + return returnRewrite(node, ret, Rewrite::CTN_SPLIT); } } } @@ -2549,7 +2550,7 @@ Node SequencesRewriter::rewriteContains(Node node) if (node[0][2] == nm->mkNode(kind::STRING_LENGTH, node[1])) { Node ret = nm->mkNode(kind::EQUAL, node[0], node[1]); - return returnRewrite(node, ret, "ctn-substr"); + return returnRewrite(node, ret, Rewrite::CTN_SUBSTR); } } else if (node[0].getKind() == kind::STRING_STRREPL) @@ -2562,7 +2563,7 @@ Node SequencesRewriter::rewriteContains(Node node) // (str.contains (str.replace x c1 c2) c3) ---> (str.contains x c3) // if there is no overlap between c1 and c3 and none between c2 and c3 Node ret = nm->mkNode(STRING_STRCTN, node[0][0], node[1]); - return returnRewrite(node, ret, "ctn-repl-cnsts-to-ctn"); + return returnRewrite(node, ret, Rewrite::CTN_REPL_CNSTS_TO_CTN); } } @@ -2572,7 +2573,7 @@ Node SequencesRewriter::rewriteContains(Node node) if (node[0][1] == node[1]) { Node ret = nm->mkNode(kind::STRING_STRCTN, node[0][0], node[1]); - return returnRewrite(node, ret, "ctn-repl-to-ctn"); + return returnRewrite(node, ret, Rewrite::CTN_REPL_TO_CTN); } // (str.contains (str.replace x y x) z) ---> (str.contains x z) @@ -2580,7 +2581,7 @@ Node SequencesRewriter::rewriteContains(Node node) if (checkEntailLengthOne(node[1])) { Node ret = nm->mkNode(kind::STRING_STRCTN, node[0][0], node[1]); - return returnRewrite(node, ret, "ctn-repl-len-one-to-ctn"); + return returnRewrite(node, ret, Rewrite::CTN_REPL_LEN_ONE_TO_CTN); } } @@ -2591,7 +2592,7 @@ Node SequencesRewriter::rewriteContains(Node node) Node ret = nm->mkNode(OR, nm->mkNode(STRING_STRCTN, node[0][0], node[0][1]), nm->mkNode(STRING_STRCTN, node[0][0], node[0][2])); - return returnRewrite(node, ret, "ctn-repl-to-ctn-disj"); + return returnRewrite(node, ret, Rewrite::CTN_REPL_TO_CTN_DISJ); } // (str.contains (str.replace x y z) w) ---> @@ -2607,7 +2608,7 @@ Node SequencesRewriter::rewriteContains(Node node) kind::STRING_STRCTN, nm->mkNode(kind::STRING_STRREPL, node[0][0], node[0][1], empty), node[1]); - return returnRewrite(node, ret, "ctn-repl-simp-repl"); + return returnRewrite(node, ret, Rewrite::CTN_REPL_SIMP_REPL); } } } @@ -2619,7 +2620,7 @@ Node SequencesRewriter::rewriteContains(Node node) if (node[0] == node[1][1] && node[1][0] == node[1][2]) { Node ret = nm->mkNode(kind::STRING_STRCTN, node[0], node[1][0]); - return returnRewrite(node, ret, "ctn-repl"); + return returnRewrite(node, ret, Rewrite::CTN_REPL); } // (str.contains x (str.replace "" x y)) ---> @@ -2632,7 +2633,7 @@ Node SequencesRewriter::rewriteContains(Node node) if (node[0] == node[1][1] && node[1][0] == emp) { Node ret = nm->mkNode(kind::EQUAL, emp, node[1]); - return returnRewrite(node, ret, "ctn-repl-empty"); + return returnRewrite(node, ret, Rewrite::CTN_REPL_EMPTY); } } @@ -2649,7 +2650,7 @@ Node SequencesRewriter::rewriteIndexof(Node node) { // z<0 implies str.indexof( x, y, z ) --> -1 Node negone = nm->mkConst(Rational(-1)); - return returnRewrite(node, negone, "idof-neg"); + return returnRewrite(node, negone, Rewrite::IDOF_NEG); } // the string type @@ -2667,7 +2668,7 @@ Node SequencesRewriter::rewriteIndexof(Node node) // in our implementation, that accessing a position greater than // rMaxInt is guaranteed to be out of bounds. Node negone = nm->mkConst(Rational(-1)); - return returnRewrite(node, negone, "idof-max"); + return returnRewrite(node, negone, Rewrite::IDOF_MAX); } Assert(node[2].getConst().sgn() >= 0); Node s = children0[0]; @@ -2678,12 +2679,12 @@ Node SequencesRewriter::rewriteIndexof(Node node) if (ret != std::string::npos) { Node retv = nm->mkConst(Rational(static_cast(ret))); - return returnRewrite(node, retv, "idof-find"); + return returnRewrite(node, retv, Rewrite::IDOF_FIND); } else if (children0.size() == 1) { Node negone = nm->mkConst(Rational(-1)); - return returnRewrite(node, negone, "idof-nfind"); + return returnRewrite(node, negone, Rewrite::IDOF_NFIND); } } @@ -2695,21 +2696,21 @@ Node SequencesRewriter::rewriteIndexof(Node node) { // indexof( x, x, 0 ) --> 0 Node zero = nm->mkConst(Rational(0)); - return returnRewrite(node, zero, "idof-eq-cst-start"); + return returnRewrite(node, zero, Rewrite::IDOF_EQ_CST_START); } } if (checkEntailArith(node[2], true)) { // y>0 implies indexof( x, x, y ) --> -1 Node negone = nm->mkConst(Rational(-1)); - return returnRewrite(node, negone, "idof-eq-nstart"); + return returnRewrite(node, negone, Rewrite::IDOF_EQ_NSTART); } Node emp = nm->mkConst(CVC4::String("")); if (node[0] != emp) { // indexof( x, x, z ) ---> indexof( "", "", z ) Node ret = nm->mkNode(STRING_STRIDOF, emp, emp, node[2]); - return returnRewrite(node, ret, "idof-eq-norm"); + return returnRewrite(node, ret, Rewrite::IDOF_EQ_NORM); } } @@ -2724,7 +2725,7 @@ Node SequencesRewriter::rewriteIndexof(Node node) if (checkEntailArith(len0, node[2]) && checkEntailArith(node[2])) { // len(x)>=z ^ z >=0 implies indexof( x, "", z ) ---> z - return returnRewrite(node, node[2], "idof-emp-idof"); + return returnRewrite(node, node[2], Rewrite::IDOF_EMP_IDOF); } } } @@ -2733,7 +2734,7 @@ Node SequencesRewriter::rewriteIndexof(Node node) { // len(x)-z < len(y) implies indexof( x, y, z ) ----> -1 Node negone = nm->mkConst(Rational(-1)); - return returnRewrite(node, negone, "idof-len"); + return returnRewrite(node, negone, Rewrite::IDOF_LEN); } Node fstr = node[0]; @@ -2765,7 +2766,7 @@ Node SequencesRewriter::rewriteIndexof(Node node) // str.indexof(str.++(x,y,z),y,0) ---> str.indexof(str.++(x,y),y,0) Node nn = utils::mkConcat(children0, stype); Node ret = nm->mkNode(kind::STRING_STRIDOF, nn, node[1], node[2]); - return returnRewrite(node, ret, "idof-def-ctn"); + return returnRewrite(node, ret, Rewrite::IDOF_DEF_CTN); } // Strip components from the beginning that are guaranteed not to match @@ -2780,7 +2781,7 @@ Node SequencesRewriter::rewriteIndexof(Node node) utils::mkConcat(children0, stype), node[1], node[2])); - return returnRewrite(node, ret, "idof-strip-cnst-endpts"); + return returnRewrite(node, ret, Rewrite::IDOF_STRIP_CNST_ENDPTS); } } @@ -2799,14 +2800,14 @@ Node SequencesRewriter::rewriteIndexof(Node node) nm->mkNode(kind::PLUS, nm->mkNode(kind::MINUS, node[2], new_len), nm->mkNode(kind::STRING_STRIDOF, nn, node[1], new_len)); - return returnRewrite(node, ret, "idof-strip-sym-len"); + return returnRewrite(node, ret, Rewrite::IDOF_STRIP_SYM_LEN); } } else { // str.contains( x, y ) --> false implies str.indexof(x,y,z) --> -1 Node negone = nm->mkConst(Rational(-1)); - return returnRewrite(node, negone, "idof-nctn"); + return returnRewrite(node, negone, Rewrite::IDOF_NCTN); } } else @@ -2830,7 +2831,7 @@ Node SequencesRewriter::rewriteIndexof(Node node) children.insert(children.end(), children0.begin(), children0.end()); Node nn = utils::mkConcat(children, stype); Node res = nm->mkNode(kind::STRING_STRIDOF, nn, node[1], node[2]); - return returnRewrite(node, res, "idof-norm-prefix"); + return returnRewrite(node, res, Rewrite::IDOF_NORM_PREFIX); } } } @@ -2845,7 +2846,7 @@ Node SequencesRewriter::rewriteIndexof(Node node) ret = nm->mkNode(STRING_STRIDOF, ret, node[1], node[2]); // For example: // str.indexof( str.++( x, "A" ), "B", 0 ) ---> str.indexof( x, "B", 0 ) - return returnRewrite(node, ret, "rpl-pull-endpt"); + return returnRewrite(node, ret, Rewrite::RPL_PULL_ENDPT); } } @@ -2861,7 +2862,7 @@ Node SequencesRewriter::rewriteReplace(Node node) if (node[1].isConst() && Word::isEmpty(node[1])) { Node ret = nm->mkNode(STRING_CONCAT, node[2], node[0]); - return returnRewrite(node, ret, "rpl-rpl-empty"); + return returnRewrite(node, ret, Rewrite::RPL_RPL_EMPTY); } // the string type TypeNode stype = node.getType(); @@ -2878,7 +2879,7 @@ Node SequencesRewriter::rewriteReplace(Node node) { if (children0.size() == 1) { - return returnRewrite(node, node[0], "rpl-const-nfind"); + return returnRewrite(node, node[0], Rewrite::RPL_CONST_NFIND); } } else @@ -2897,7 +2898,7 @@ Node SequencesRewriter::rewriteReplace(Node node) } children.insert(children.end(), children0.begin() + 1, children0.end()); Node ret = utils::mkConcat(children, stype); - return returnRewrite(node, ret, "rpl-const-find"); + return returnRewrite(node, ret, Rewrite::RPL_CONST_FIND); } } @@ -2916,7 +2917,7 @@ Node SequencesRewriter::rewriteReplace(Node node) Node l1 = NodeManager::currentNM()->mkNode(kind::STRING_LENGTH, node[1]); if (checkEntailArith(l1, l0)) { - return returnRewrite(node, node[0], "rpl-rpl-len-id"); + return returnRewrite(node, node[0], Rewrite::RPL_RPL_LEN_ID); } // (str.replace x y x) ---> (str.replace x (str.++ y1 ... yn) x) @@ -2938,7 +2939,7 @@ Node SequencesRewriter::rewriteReplace(Node node) if (node[1] != nn1) { Node ret = nm->mkNode(STRING_STRREPL, node[0], nn1, node[2]); - return returnRewrite(node, ret, "rpl-x-y-x-simp"); + return returnRewrite(node, ret, Rewrite::RPL_X_Y_X_SIMP); } } } @@ -2971,7 +2972,7 @@ Node SequencesRewriter::rewriteReplace(Node node) cres.push_back(node[2]); cres.insert(cres.end(), ce.begin(), ce.end()); Node ret = utils::mkConcat(cres, stype); - return returnRewrite(node, ret, "rpl-cctn-rpl"); + return returnRewrite(node, ret, Rewrite::RPL_CCTN_RPL); } else if (!ce.empty()) { @@ -2988,14 +2989,14 @@ Node SequencesRewriter::rewriteReplace(Node node) node[2])); scc.insert(scc.end(), ce.begin(), ce.end()); Node ret = utils::mkConcat(scc, stype); - return returnRewrite(node, ret, "rpl-cctn"); + return returnRewrite(node, ret, Rewrite::RPL_CCTN); } } } else { // ~contains( t, s ) => ( replace( t, s, r ) ----> t ) - return returnRewrite(node, node[0], "rpl-nctn"); + return returnRewrite(node, node[0], Rewrite::RPL_NCTN); } } else if (cmp_conr.getKind() == kind::EQUAL || cmp_conr.getKind() == kind::AND) @@ -3039,14 +3040,14 @@ Node SequencesRewriter::rewriteReplace(Node node) if (nn1 != node[1] || nn2 != node[2]) { Node res = nm->mkNode(kind::STRING_STRREPL, node[0], nn1, nn2); - return returnRewrite(node, res, "rpl-emp-cnts-substs"); + return returnRewrite(node, res, Rewrite::RPL_EMP_CNTS_SUBSTS); } } if (nn2 != node[2]) { Node res = nm->mkNode(kind::STRING_STRREPL, node[0], node[1], nn2); - return returnRewrite(node, res, "rpl-cnts-substs"); + return returnRewrite(node, res, Rewrite::RPL_CNTS_SUBSTS); } } } @@ -3072,7 +3073,7 @@ Node SequencesRewriter::rewriteReplace(Node node) node[2])); cc.insert(cc.end(), ce.begin(), ce.end()); Node ret = utils::mkConcat(cc, stype); - return returnRewrite(node, ret, "rpl-pull-endpt"); + return returnRewrite(node, ret, Rewrite::RPL_PULL_ENDPT); } } } @@ -3114,7 +3115,7 @@ Node SequencesRewriter::rewriteReplace(Node node) node[0], utils::mkConcat(children1, stype), node[2]); - return returnRewrite(node, res, "repl-subst-idx"); + return returnRewrite(node, res, Rewrite::REPL_SUBST_IDX); } } @@ -3162,7 +3163,7 @@ Node SequencesRewriter::rewriteReplace(Node node) nm->mkNode(kind::STRING_STRREPL, y, w, z), y, z); - return returnRewrite(node, ret, "repl-repl-short-circuit"); + return returnRewrite(node, ret, Rewrite::REPL_REPL_SHORT_CIRCUIT); } } } @@ -3175,7 +3176,7 @@ Node SequencesRewriter::rewriteReplace(Node node) if (node[1][0] == node[1][2] && node[1][0] == node[2]) { // str.replace( x, str.replace( x, y, x ), x ) ---> x - return returnRewrite(node, node[0], "repl-repl2-inv-id"); + return returnRewrite(node, node[0], Rewrite::REPL_REPL2_INV_ID); } bool dualReplIteSuccess = false; Node cmp_con2 = checkEntailContains(node[1][0], node[1][2]); @@ -3209,7 +3210,7 @@ Node SequencesRewriter::rewriteReplace(Node node) nm->mkNode(STRING_STRCTN, node[0], node[1][1]), node[0], node[2]); - return returnRewrite(node, res, "repl-dual-repl-ite"); + return returnRewrite(node, res, Rewrite::REPL_DUAL_REPL_ITE); } } @@ -3245,7 +3246,7 @@ Node SequencesRewriter::rewriteReplace(Node node) if (invSuccess) { Node res = nm->mkNode(kind::STRING_STRREPL, node[0], node[1][0], node[2]); - return returnRewrite(node, res, "repl-repl2-inv"); + return returnRewrite(node, res, Rewrite::REPL_REPL2_INV); } } if (node[2].getKind() == STRING_STRREPL) @@ -3259,7 +3260,7 @@ Node SequencesRewriter::rewriteReplace(Node node) { Node res = nm->mkNode(kind::STRING_STRREPL, node[0], node[1], node[2][0]); - return returnRewrite(node, res, "repl-repl3-inv"); + return returnRewrite(node, res, Rewrite::REPL_REPL3_INV); } } if (node[2][0] == node[1]) @@ -3279,7 +3280,7 @@ Node SequencesRewriter::rewriteReplace(Node node) } if (success) { - return returnRewrite(node, node[0], "repl-repl3-inv-id"); + return returnRewrite(node, node[0], Rewrite::REPL_REPL3_INV_ID); } } } @@ -3326,7 +3327,7 @@ Node SequencesRewriter::rewriteReplace(Node node) // second occurrence of x. Notice this is specific to single characters // due to complications with finds that span multiple components for // non-characters. - return returnRewrite(node, ret, "repl-char-ncontrib-find"); + return returnRewrite(node, ret, Rewrite::REPL_CHAR_NCONTRIB_FIND); } } @@ -3353,7 +3354,7 @@ Node SequencesRewriter::rewriteReplaceAll(Node node) Node t = node[1]; if (Word::isEmpty(s) || Word::isEmpty(t)) { - return returnRewrite(node, node[0], "replall-empty-find"); + return returnRewrite(node, node[0], Rewrite::REPLALL_EMPTY_FIND); } std::size_t sizeS = Word::getLength(s); std::size_t sizeT = Word::getLength(t); @@ -3378,7 +3379,7 @@ Node SequencesRewriter::rewriteReplaceAll(Node node) } while (curr != std::string::npos && curr < sizeS); // constant evaluation Node res = utils::mkConcat(children, stype); - return returnRewrite(node, res, "replall-const"); + return returnRewrite(node, res, Rewrite::REPLALL_CONST); } // rewrites that apply to both replace and replaceall @@ -3400,7 +3401,7 @@ Node SequencesRewriter::rewriteReplaceInternal(Node node) if (node[1] == node[2]) { - return returnRewrite(node, node[0], "rpl-id"); + return returnRewrite(node, node[0], Rewrite::RPL_ID); } if (node[0] == node[1]) @@ -3408,7 +3409,7 @@ Node SequencesRewriter::rewriteReplaceInternal(Node node) // only holds for replaceall if non-empty if (nk == STRING_STRREPL || checkEntailNonEmpty(node[1])) { - return returnRewrite(node, node[2], "rpl-replace"); + return returnRewrite(node, node[2], Rewrite::RPL_REPLACE); } } @@ -3425,7 +3426,7 @@ Node SequencesRewriter::rewriteStrReverse(Node node) std::vector nvec = node[0].getConst().getVec(); std::reverse(nvec.begin(), nvec.end()); Node retNode = nm->mkConst(String(nvec)); - return returnRewrite(node, retNode, "str-conv-const"); + return returnRewrite(node, retNode, Rewrite::STR_CONV_CONST); } else if (x.getKind() == STRING_CONCAT) { @@ -3437,13 +3438,13 @@ Node SequencesRewriter::rewriteStrReverse(Node node) std::reverse(children.begin(), children.end()); // rev( x1 ++ x2 ) --> rev( x2 ) ++ rev( x1 ) Node retNode = nm->mkNode(STRING_CONCAT, children); - return returnRewrite(node, retNode, "str-rev-minscope-concat"); + return returnRewrite(node, retNode, Rewrite::STR_REV_MINSCOPE_CONCAT); } else if (x.getKind() == STRING_REV) { // rev( rev( x ) ) --> x Node retNode = x[0]; - return returnRewrite(node, retNode, "str-rev-idem"); + return returnRewrite(node, retNode, Rewrite::STR_REV_IDEM); } return node; } @@ -3456,7 +3457,7 @@ Node SequencesRewriter::rewritePrefixSuffix(Node n) if (n[0] == n[1]) { Node ret = NodeManager::currentNM()->mkConst(true); - return returnRewrite(n, ret, "suf/prefix-eq"); + return returnRewrite(n, ret, Rewrite::SUF_PREFIX_EQ); } if (n[0].isConst()) { @@ -3464,7 +3465,7 @@ Node SequencesRewriter::rewritePrefixSuffix(Node n) if (t.isEmptyString()) { Node ret = NodeManager::currentNM()->mkConst(true); - return returnRewrite(n, ret, "suf/prefix-empty-const"); + return returnRewrite(n, ret, Rewrite::SUF_PREFIX_EMPTY_CONST); } } if (n[1].isConst()) @@ -3484,12 +3485,12 @@ Node SequencesRewriter::rewritePrefixSuffix(Node n) ret = NodeManager::currentNM()->mkConst(true); } } - return returnRewrite(n, ret, "suf/prefix-const"); + return returnRewrite(n, ret, Rewrite::SUF_PREFIX_CONST); } else if (lenS == 0) { Node ret = n[0].eqNode(n[1]); - return returnRewrite(n, ret, "suf/prefix-empty"); + return returnRewrite(n, ret, Rewrite::SUF_PREFIX_EMPTY); } else if (lenS == 1) { @@ -3497,7 +3498,7 @@ Node SequencesRewriter::rewritePrefixSuffix(Node n) // (str.contains "A" x ) Node ret = NodeManager::currentNM()->mkNode(kind::STRING_STRCTN, n[1], n[0]); - return returnRewrite(n, ret, "suf/prefix-ctn"); + return returnRewrite(n, ret, Rewrite::SUF_PREFIX_CTN); } } Node lens = NodeManager::currentNM()->mkNode(kind::STRING_LENGTH, n[0]); @@ -3517,7 +3518,7 @@ Node SequencesRewriter::rewritePrefixSuffix(Node n) Node eqs = inferEqsFromContains(n[1], n[0]); if (!eqs.isNull()) { - return returnRewrite(n, eqs, "suf/prefix-to-eqs"); + return returnRewrite(n, eqs, Rewrite::SUF_PREFIX_TO_EQS); } // general reduction to equality + substr @@ -5565,9 +5566,9 @@ std::pair > SequencesRewriter::collectEmptyEqs(Node x) allEmptyEqs, std::vector(emptyNodes.begin(), emptyNodes.end())); } -Node SequencesRewriter::returnRewrite(Node node, Node ret, const char* c) +Node SequencesRewriter::returnRewrite(Node node, Node ret, Rewrite r) { - Trace("strings-rewrite") << "Rewrite " << node << " to " << ret << " by " << c + Trace("strings-rewrite") << "Rewrite " << node << " to " << ret << " by " << r << "." << std::endl; NodeManager* nm = NodeManager::currentNM(); diff --git a/src/theory/strings/sequences_rewriter.h b/src/theory/strings/sequences_rewriter.h index 5aba4ab6f..a98ad97ac 100644 --- a/src/theory/strings/sequences_rewriter.h +++ b/src/theory/strings/sequences_rewriter.h @@ -23,6 +23,7 @@ #include #include "expr/attribute.h" +#include "theory/strings/rewrites.h" #include "theory/theory_rewriter.h" #include "theory/type_enumerator.h" @@ -149,7 +150,7 @@ class SequencesRewriter : public TheoryRewriter /** * Called when node rewrites to ret. * - * The string c indicates the justification for the rewrite, which is printed + * The rewrite r indicates the justification for the rewrite, which is printed * by this function for debugging. * * If node is not an equality and ret is an equality, this method applies @@ -157,7 +158,7 @@ class SequencesRewriter : public TheoryRewriter * additional rewrites on ret, after which we return the result of this call. * Otherwise, this method simply returns ret. */ - static Node returnRewrite(Node node, Node ret, const char* c); + static Node returnRewrite(Node node, Node ret, Rewrite r); public: RewriteResponse postRewrite(TNode node) override; diff --git a/src/theory/strings/strings_rewriter.cpp b/src/theory/strings/strings_rewriter.cpp index c7676d049..275c2e25e 100644 --- a/src/theory/strings/strings_rewriter.cpp +++ b/src/theory/strings/strings_rewriter.cpp @@ -41,7 +41,7 @@ Node StringsRewriter::rewriteStrToInt(Node node) { ret = nm->mkConst(Rational(-1)); } - return returnRewrite(node, ret, "stoi-eval"); + return returnRewrite(node, ret, Rewrite::STOI_EVAL); } else if (node[0].getKind() == STRING_CONCAT) { @@ -53,7 +53,7 @@ Node StringsRewriter::rewriteStrToInt(Node node) if (!t.isNumber()) { Node ret = nm->mkConst(Rational(-1)); - return returnRewrite(node, ret, "stoi-concat-nonnum"); + return returnRewrite(node, ret, Rewrite::STOI_CONCAT_NONNUM); } } } @@ -78,7 +78,7 @@ Node StringsRewriter::rewriteIntToStr(Node node) Assert(stmp[0] != '-'); ret = nm->mkConst(String(stmp)); } - return returnRewrite(node, ret, "itos-eval"); + return returnRewrite(node, ret, Rewrite::ITOS_EVAL); } return node; } @@ -114,7 +114,7 @@ Node StringsRewriter::rewriteStrConvert(Node node) nvec[i] = newChar; } Node retNode = nm->mkConst(String(nvec)); - return returnRewrite(node, retNode, "str-conv-const"); + return returnRewrite(node, retNode, Rewrite::STR_CONV_CONST); } else if (node[0].getKind() == STRING_CONCAT) { @@ -125,7 +125,7 @@ Node StringsRewriter::rewriteStrConvert(Node node) } // tolower( x1 ++ x2 ) --> tolower( x1 ) ++ tolower( x2 ) Node retNode = concatBuilder.constructNode(); - return returnRewrite(node, retNode, "str-conv-minscope-concat"); + return returnRewrite(node, retNode, Rewrite::STR_CONV_MINSCOPE_CONCAT); } else if (node[0].getKind() == STRING_TOLOWER || node[0].getKind() == STRING_TOUPPER) @@ -133,12 +133,12 @@ Node StringsRewriter::rewriteStrConvert(Node node) // tolower( tolower( x ) ) --> tolower( x ) // tolower( toupper( x ) ) --> tolower( x ) Node retNode = nm->mkNode(nk, node[0][0]); - return returnRewrite(node, retNode, "str-conv-idem"); + return returnRewrite(node, retNode, Rewrite::STR_CONV_IDEM); } else if (node[0].getKind() == STRING_ITOS) { // tolower( str.from.int( x ) ) --> str.from.int( x ) - return returnRewrite(node, node[0], "str-conv-itos"); + return returnRewrite(node, node[0], Rewrite::STR_CONV_ITOS); } return node; } @@ -150,14 +150,14 @@ Node StringsRewriter::rewriteStringLeq(Node n) if (n[0] == n[1]) { Node ret = nm->mkConst(true); - return returnRewrite(n, ret, "str-leq-id"); + return returnRewrite(n, ret, Rewrite::STR_LEQ_ID); } if (n[0].isConst() && n[1].isConst()) { String s = n[0].getConst(); String t = n[1].getConst(); Node ret = nm->mkConst(s.isLeq(t)); - return returnRewrite(n, ret, "str-leq-eval"); + return returnRewrite(n, ret, Rewrite::STR_LEQ_EVAL); } // empty strings for (unsigned i = 0; i < 2; i++) @@ -165,7 +165,7 @@ Node StringsRewriter::rewriteStringLeq(Node n) if (n[i].isConst() && n[i].getConst().isEmptyString()) { Node ret = i == 0 ? nm->mkConst(true) : n[0].eqNode(n[1]); - return returnRewrite(n, ret, "str-leq-empty"); + return returnRewrite(n, ret, Rewrite::STR_LEQ_EMPTY); } } @@ -189,7 +189,7 @@ Node StringsRewriter::rewriteStringLeq(Node n) if (!s.isLeq(t)) { Node ret = nm->mkConst(false); - return returnRewrite(n, ret, "str-leq-cprefix"); + return returnRewrite(n, ret, Rewrite::STR_LEQ_CPREFIX); } } return n; @@ -213,7 +213,7 @@ Node StringsRewriter::rewriteStringFromCode(Node n) { ret = nm->mkConst(String("")); } - return returnRewrite(n, ret, "from-code-eval"); + return returnRewrite(n, ret, Rewrite::FROM_CODE_EVAL); } return n; } @@ -236,7 +236,7 @@ Node StringsRewriter::rewriteStringToCode(Node n) { ret = nm->mkConst(Rational(-1)); } - return returnRewrite(n, ret, "to-code-eval"); + return returnRewrite(n, ret, Rewrite::TO_CODE_EVAL); } return n; } -- cgit v1.2.3 From 0060de329173c0b75c02778d003371f59cc11eff Mon Sep 17 00:00:00 2001 From: mudathirmahgoub Date: Mon, 30 Mar 2020 09:04:52 -0500 Subject: Frontend support for the choice operator (#4175) Added the operator choice to Smt2.g and Cvc.g. Removed the unused parameter hasBoundVars from TheoryModel::getModelValue --- src/parser/cvc/Cvc.g | 6 ++++-- src/parser/smt2/Smt2.g | 2 ++ src/theory/builtin/theory_builtin.cpp | 31 +++++++++++++++++++++++++++---- src/theory/builtin/theory_builtin.h | 30 ++++++++++++++++++------------ src/theory/theory_model.cpp | 8 ++++---- src/theory/theory_model.h | 3 +-- test/regress/CMakeLists.txt | 2 ++ test/regress/regress0/parser/choice.cvc | 10 ++++++++++ test/regress/regress0/parser/choice.smt2 | 10 ++++++++++ 9 files changed, 78 insertions(+), 24 deletions(-) create mode 100644 test/regress/regress0/parser/choice.cvc create mode 100644 test/regress/regress0/parser/choice.smt2 diff --git a/src/parser/cvc/Cvc.g b/src/parser/cvc/Cvc.g index 033389610..8b3b96cfd 100644 --- a/src/parser/cvc/Cvc.g +++ b/src/parser/cvc/Cvc.g @@ -114,6 +114,7 @@ tokens { FORALL_TOK = 'FORALL'; EXISTS_TOK = 'EXISTS'; + CHOICE_TOK = 'CHOICE'; PATTERN_TOK = 'PATTERN'; LAMBDA_TOK = 'LAMBDA'; @@ -343,7 +344,8 @@ int getOperatorPrecedence(int type) { case IMPLIES_TOK: return 30;// right-to-left case IFF_TOK: return 31; case FORALL_TOK: - case EXISTS_TOK: return 32; + case EXISTS_TOK: + case CHOICE_TOK: return 32; case ASSIGN_TOK: case IN_TOK: return 33; @@ -1465,7 +1467,7 @@ prefixFormula[CVC4::api::Term& f] api::Term ipl; } /* quantifiers */ - : ( FORALL_TOK { k = api::FORALL; } | EXISTS_TOK { k = api::EXISTS; } ) + : ( FORALL_TOK { k = api::FORALL; } | EXISTS_TOK { k = api::EXISTS; } | CHOICE_TOK { k = api::CHOICE; } ) { PARSER_STATE->pushScope(); } LPAREN boundVarDecl[ids,t] { for(std::vector::const_iterator i = ids.begin(); i != ids.end(); ++i) { diff --git a/src/parser/smt2/Smt2.g b/src/parser/smt2/Smt2.g index 0c42678aa..d0a73ce6a 100644 --- a/src/parser/smt2/Smt2.g +++ b/src/parser/smt2/Smt2.g @@ -2312,6 +2312,7 @@ quantOp[CVC4::api::Kind& kind] } : EXISTS_TOK { $kind = api::EXISTS; } | FORALL_TOK { $kind = api::FORALL; } + | CHOICE_TOK { $kind = api::CHOICE; } ; /** @@ -2682,6 +2683,7 @@ ATTRIBUTE_INST_LEVEL : ':quant-inst-max-level'; // operators (NOTE: theory symbols go here) EXISTS_TOK : 'exists'; FORALL_TOK : 'forall'; +CHOICE_TOK : { !PARSER_STATE->strictModeEnabled() }? 'choice'; EMP_TOK : { PARSER_STATE->isTheoryEnabled(theory::THEORY_SEP) }? 'emp'; TUPLE_CONST_TOK: { PARSER_STATE->isTheoryEnabled(theory::THEORY_DATATYPES) }? 'mkTuple'; diff --git a/src/theory/builtin/theory_builtin.cpp b/src/theory/builtin/theory_builtin.cpp index b819b883d..505aa503f 100644 --- a/src/theory/builtin/theory_builtin.cpp +++ b/src/theory/builtin/theory_builtin.cpp @@ -15,9 +15,10 @@ **/ #include "theory/builtin/theory_builtin.h" -#include "theory/valuation.h" + #include "expr/kind.h" #include "theory/theory_model.h" +#include "theory/valuation.h" using namespace std; @@ -25,6 +26,28 @@ namespace CVC4 { namespace theory { namespace builtin { -}/* CVC4::theory::builtin namespace */ -}/* CVC4::theory */ -}/* CVC4 namespace */ +TheoryBuiltin::TheoryBuiltin(context::Context* c, + context::UserContext* u, + OutputChannel& out, + Valuation valuation, + const LogicInfo& logicInfo) + : Theory(THEORY_BUILTIN, c, u, out, valuation, logicInfo) +{ +} + +std::string TheoryBuiltin::identify() const +{ + return std::string("TheoryBuiltin"); +} + +void TheoryBuiltin::finishInit() +{ + // choice nodes are not evaluated in getModelValue + TheoryModel* theoryModel = d_valuation.getModel(); + Assert(theoryModel != nullptr); + theoryModel->setUnevaluatedKind(kind::CHOICE); +} + +} // namespace builtin +} // namespace theory +} // namespace CVC4 diff --git a/src/theory/builtin/theory_builtin.h b/src/theory/builtin/theory_builtin.h index 8a7d1bf7b..6e99ef040 100644 --- a/src/theory/builtin/theory_builtin.h +++ b/src/theory/builtin/theory_builtin.h @@ -25,17 +25,23 @@ namespace CVC4 { namespace theory { namespace builtin { -class TheoryBuiltin : public Theory { -public: - TheoryBuiltin(context::Context* c, context::UserContext* u, - OutputChannel& out, Valuation valuation, - const LogicInfo& logicInfo) - : Theory(THEORY_BUILTIN, c, u, out, valuation, logicInfo) {} - std::string identify() const override { return std::string("TheoryBuiltin"); } -};/* class TheoryBuiltin */ - -}/* CVC4::theory::builtin namespace */ -}/* CVC4::theory namespace */ -}/* CVC4 namespace */ +class TheoryBuiltin : public Theory +{ + public: + TheoryBuiltin(context::Context* c, + context::UserContext* u, + OutputChannel& out, + Valuation valuation, + const LogicInfo& logicInfo); + + std::string identify() const override; + + /** finish initialization */ + void finishInit() override; +}; /* class TheoryBuiltin */ + +} // namespace builtin +} // namespace theory +} // namespace CVC4 #endif /* CVC4__THEORY__BUILTIN__THEORY_BUILTIN_H */ diff --git a/src/theory/theory_model.cpp b/src/theory/theory_model.cpp index 7bfb0e8f3..dae7261e5 100644 --- a/src/theory/theory_model.cpp +++ b/src/theory/theory_model.cpp @@ -147,7 +147,7 @@ Node TheoryModel::getValue(TNode n) const Node nn = d_substitutions.apply(n); Debug("model-getvalue-debug") << "[model-getvalue] getValue : substitute " << n << " to " << nn << std::endl; //get value in model - nn = getModelValue(nn, false); + nn = getModelValue(nn); if (nn.isNull()) return nn; if(options::condenseFunctionValues() || nn.getKind() != kind::LAMBDA) { //normalize @@ -193,7 +193,7 @@ Cardinality TheoryModel::getCardinality( Type t ) const{ } } -Node TheoryModel::getModelValue(TNode n, bool hasBoundVars) const +Node TheoryModel::getModelValue(TNode n) const { std::unordered_map::iterator it = d_modelCache.find(n); if (it != d_modelCache.end()) { @@ -220,7 +220,7 @@ Node TheoryModel::getModelValue(TNode n, bool hasBoundVars) const std::vector children; if (n.getKind() == APPLY_UF) { - Node op = getModelValue(n.getOperator(), hasBoundVars); + Node op = getModelValue(n.getOperator()); Debug("model-getvalue-debug") << " operator : " << op << std::endl; children.push_back(op); } @@ -231,7 +231,7 @@ Node TheoryModel::getModelValue(TNode n, bool hasBoundVars) const // evaluate the children for (unsigned i = 0, nchild = n.getNumChildren(); i < nchild; ++i) { - ret = getModelValue(n[i], hasBoundVars); + ret = getModelValue(n[i]); Debug("model-getvalue-debug") << " " << n << "[" << i << "] is " << ret << std::endl; children.push_back(ret); diff --git a/src/theory/theory_model.h b/src/theory/theory_model.h index d2ce63ac5..d984fbc6b 100644 --- a/src/theory/theory_model.h +++ b/src/theory/theory_model.h @@ -390,9 +390,8 @@ public: /** Get model value function. * * This function is a helper function for getValue. - * hasBoundVars is whether n may contain bound variables */ - Node getModelValue(TNode n, bool hasBoundVars = false) const; + Node getModelValue(TNode n) const; /** add term internal * * This will do any model-specific processing necessary for n, diff --git a/test/regress/CMakeLists.txt b/test/regress/CMakeLists.txt index a5acd62fb..d843eb5ed 100644 --- a/test/regress/CMakeLists.txt +++ b/test/regress/CMakeLists.txt @@ -610,6 +610,8 @@ set(regress_0_tests regress0/parser/as.smt2 regress0/parser/bv_arity_smt2.6.smt2 regress0/parser/bv_nat.smt2 + regress0/parser/choice.cvc + regress0/parser/choice.smt2 regress0/parser/constraint.smt2 regress0/parser/declarefun-emptyset-uf.smt2 regress0/parser/force_logic_set_logic.smt2 diff --git a/test/regress/regress0/parser/choice.cvc b/test/regress/regress0/parser/choice.cvc new file mode 100644 index 000000000..e0ebac051 --- /dev/null +++ b/test/regress/regress0/parser/choice.cvc @@ -0,0 +1,10 @@ +% EXPECT: sat + +a : INT; +b : INT; +c : INT; + +ASSERT (CHOICE(x: INT): x = a) = 1; +ASSERT (CHOICE(x: INT): x = b) = 2; + +CHECKSAT; \ No newline at end of file diff --git a/test/regress/regress0/parser/choice.smt2 b/test/regress/regress0/parser/choice.smt2 new file mode 100644 index 000000000..19763e222 --- /dev/null +++ b/test/regress/regress0/parser/choice.smt2 @@ -0,0 +1,10 @@ +(set-logic ALL) +(set-info :status sat) +(declare-fun a () Int) +(declare-fun b () Int) +(declare-fun c () Int) +(assert (= (choice ((x Int)) (= x a)) 1)) +(assert (= (choice ((x Int)) (= x b)) 2)) +;(assert (let ((x (choice ((x Int)) true))) (and (distinct a b x)(= x c)))) +(check-sat) + -- cgit v1.2.3 From 49d7b510a992a7a84594a11a4aeefcbd3fc8d257 Mon Sep 17 00:00:00 2001 From: Andrew Reynolds Date: Mon, 30 Mar 2020 10:46:45 -0500 Subject: Fix arguments to print callback (#4171) The method applyParseOp may modify the argument vector. In the case of the sygus V1 parser, this argument vector was then being used to set up a print callback, leading to incorrect printing and failures. Work towards having a working V1 -> V2 conversion for the release. FYI @abdoo8080 --- src/parser/smt2/smt2.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/parser/smt2/smt2.cpp b/src/parser/smt2/smt2.cpp index 04e8be64b..7ba882f24 100644 --- a/src/parser/smt2/smt2.cpp +++ b/src/parser/smt2/smt2.cpp @@ -1267,7 +1267,8 @@ void Smt2::mkSygusDatatype(api::DatatypeDecl& dt, api::Term lbvl = makeSygusBoundVarList(dt, i, ltypes, largs); // make the let_body - api::Term body = applyParseOp(ops[i], largs); + std::vector largsApply = largs; + api::Term body = applyParseOp(ops[i], largsApply); // replace by lambda ParseOp pLam; pLam.d_expr = d_solver->mkTerm(api::LAMBDA, lbvl, body); -- cgit v1.2.3 From 9e5b40af8b1c0e862814bd12b7667ec8ebebb367 Mon Sep 17 00:00:00 2001 From: Mathias Preiner Date: Mon, 30 Mar 2020 11:08:52 -0700 Subject: Add coverage badge. (#4187) Coverage information is updated nightly. --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index b22882d57..53398e00b 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,9 @@ [![Build Status]( https://travis-ci.org/CVC4/CVC4.svg?branch=master)]( https://travis-ci.org/CVC4/CVC4) +[![Coverage]( + https://img.shields.io/endpoint?url=https://cvc4.cs.stanford.edu/downloads/builds/coverage/nightly-coverage.json)]( + https://cvc4.cs.stanford.edu/downloads/builds/coverage) CVC4 =============================================================================== -- cgit v1.2.3 From 3ff70d61c111b70d5bf770669b0aa3f1d47a502e Mon Sep 17 00:00:00 2001 From: Andrew Reynolds Date: Mon, 30 Mar 2020 14:52:55 -0500 Subject: Remove ref skolem datatype option (#4185) Fixes #4180, fixes CVC4/cvc4-projects#133, fixes CVC4/cvc4-projects#134. --- src/options/datatypes_options.toml | 9 --------- src/theory/datatypes/theory_datatypes.cpp | 29 +++++------------------------ 2 files changed, 5 insertions(+), 33 deletions(-) diff --git a/src/options/datatypes_options.toml b/src/options/datatypes_options.toml index 82e833506..ac371efeb 100644 --- a/src/options/datatypes_options.toml +++ b/src/options/datatypes_options.toml @@ -31,15 +31,6 @@ header = "options/datatypes_options.h" read_only = true help = "do binary splits for datatype constructor types" -[[option]] - name = "dtRefIntro" - category = "regular" - long = "dt-ref-sk-intro" - type = "bool" - default = "false" - read_only = true - help = "introduce reference skolems for shorter explanations" - [[option]] name = "cdtBisimilar" category = "regular" diff --git a/src/theory/datatypes/theory_datatypes.cpp b/src/theory/datatypes/theory_datatypes.cpp index d202648f4..7fbe5bc68 100644 --- a/src/theory/datatypes/theory_datatypes.cpp +++ b/src/theory/datatypes/theory_datatypes.cpp @@ -1306,15 +1306,7 @@ void TheoryDatatypes::collapseSelector( Node s, Node c ) { Trace("dt-collapse-sel") << "collapse selector : " << s << " " << c << std::endl; Node r; bool wrong = false; - Node use_s; - Node eq_exp; - if( options::dtRefIntro() ){ - eq_exp = d_true; - use_s = getTermSkolemFor( c ); - }else{ - eq_exp = c.eqNode( s[0] ); - use_s = s; - } + Node eq_exp = c.eqNode(s[0]); if( s.getKind()==kind::APPLY_SELECTOR_TOTAL ){ Node selector = s.getOperator(); size_t constructorIndex = utils::indexOf(c.getOperator()); @@ -1322,14 +1314,7 @@ void TheoryDatatypes::collapseSelector( Node s, Node c ) { const DTypeConstructor& dtc = dt[constructorIndex]; int selectorIndex = dtc.getSelectorIndexInternal(selector); wrong = selectorIndex<0; - - //if( wrong ){ - // return; - //} r = NodeManager::currentNM()->mkNode( kind::APPLY_SELECTOR_TOTAL, s.getOperator(), c ); - if( options::dtRefIntro() ){ - use_s = NodeManager::currentNM()->mkNode( kind::APPLY_SELECTOR_TOTAL, s.getOperator(), use_s ); - } } if( !r.isNull() ){ Node rr = Rewriter::rewrite( r ); @@ -1341,14 +1326,10 @@ void TheoryDatatypes::collapseSelector( Node s, Node c ) { std::map< Node, Node > visited; rrs = removeUninterpretedConstants( rr, visited ); } - if( use_s!=rrs ){ - Node eq = use_s.eqNode( rrs ); - Node peq; - if( options::dtRefIntro() ){ - peq = d_true; - }else{ - peq = c.eqNode(s[0]); - } + if (s != rrs) + { + Node eq = s.eqNode(rrs); + Node peq = c.eqNode(s[0]); Trace("datatypes-infer") << "DtInfer : collapse sel"; //Trace("datatypes-infer") << ( wrong ? " wrong" : ""); Trace("datatypes-infer") << " : " << eq << " by " << peq << std::endl; -- cgit v1.2.3 From 6838885b1250e0abbb1b8d56e6b400a5d7f3ca95 Mon Sep 17 00:00:00 2001 From: Andrew Reynolds Date: Mon, 30 Mar 2020 16:29:23 -0500 Subject: Support indexed operators re.loop and re.^ (#4167) Towards support for the strings standard. This modifies our interface so that we accept the SMT-LIB standard versions of re.loop and re.^. This means re.loop no longer accepts 3 arguments but 1 (with 2 indices). This means we no longer accept re.loop with only a lower bound and no upper bound on the number of repetitions. Also fixes #4161. --- src/CMakeLists.txt | 1 + src/api/cvc4cpp.cpp | 12 ++++ src/api/cvc4cppkind.h | 38 +++++++++--- src/bindings/java/CMakeLists.txt | 2 + src/cvc4.i | 1 + src/parser/cvc/Cvc.g | 7 ++- src/parser/smt2/smt2.cpp | 3 +- src/theory/strings/kinds | 20 +++++- src/theory/strings/regexp_operation.cpp | 38 +++++------- src/theory/strings/sequences_rewriter.cpp | 74 +++++++++------------- src/theory/strings/theory_strings_utils.cpp | 18 ++++++ src/theory/strings/theory_strings_utils.h | 8 +++ src/util/CMakeLists.txt | 2 + src/util/regexp.cpp | 57 +++++++++++++++++ src/util/regexp.h | 75 +++++++++++++++++++++++ src/util/regexp.i | 12 ++++ test/regress/CMakeLists.txt | 1 + test/regress/regress0/strings/bug002.smt2 | 2 +- test/regress/regress0/strings/loop-wrong-sem.smt2 | 4 ++ test/regress/regress1/strings/bug686dd.smt2 | 4 +- test/regress/regress1/strings/pierre150331.smt2 | 2 +- test/regress/regress1/strings/reloop.smt2 | 8 +-- test/regress/regress2/strings/range-perf.smt2 | 2 +- 23 files changed, 303 insertions(+), 88 deletions(-) create mode 100644 src/util/regexp.cpp create mode 100644 src/util/regexp.h create mode 100644 src/util/regexp.i create mode 100644 test/regress/regress0/strings/loop-wrong-sem.smt2 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 2ea305bc7..912df8b82 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -966,6 +966,7 @@ install(FILES util/proof.h util/rational_cln_imp.h util/rational_gmp_imp.h + util/regexp.h util/resource_manager.h util/result.h util/sexpr.h diff --git a/src/api/cvc4cpp.cpp b/src/api/cvc4cpp.cpp index f563e83f5..fa727088e 100644 --- a/src/api/cvc4cpp.cpp +++ b/src/api/cvc4cpp.cpp @@ -277,6 +277,7 @@ const static std::unordered_map s_kinds{ {REGEXP_PLUS, CVC4::Kind::REGEXP_PLUS}, {REGEXP_OPT, CVC4::Kind::REGEXP_OPT}, {REGEXP_RANGE, CVC4::Kind::REGEXP_RANGE}, + {REGEXP_REPEAT, CVC4::Kind::REGEXP_REPEAT}, {REGEXP_LOOP, CVC4::Kind::REGEXP_LOOP}, {REGEXP_EMPTY, CVC4::Kind::REGEXP_EMPTY}, {REGEXP_SIGMA, CVC4::Kind::REGEXP_SIGMA}, @@ -544,6 +545,7 @@ const static std::unordered_map {CVC4::Kind::REGEXP_PLUS, REGEXP_PLUS}, {CVC4::Kind::REGEXP_OPT, REGEXP_OPT}, {CVC4::Kind::REGEXP_RANGE, REGEXP_RANGE}, + {CVC4::Kind::REGEXP_REPEAT, REGEXP_REPEAT}, {CVC4::Kind::REGEXP_LOOP, REGEXP_LOOP}, {CVC4::Kind::REGEXP_EMPTY, REGEXP_EMPTY}, {CVC4::Kind::REGEXP_SIGMA, REGEXP_SIGMA}, @@ -3490,6 +3492,11 @@ Op Solver::mkOp(Kind kind, uint32_t arg) const kind, *mkValHelper(CVC4::TupleUpdate(arg)).d_expr.get()); break; + case REGEXP_REPEAT: + res = Op(kind, + *mkValHelper(CVC4::RegExpRepeat(arg)) + .d_expr.get()); + break; default: CVC4_API_KIND_CHECK_EXPECTED(false, kind) << "operator kind with uint32_t argument"; @@ -3550,6 +3557,11 @@ Op Solver::mkOp(Kind kind, uint32_t arg1, uint32_t arg2) const CVC4::FloatingPointToFPGeneric(arg1, arg2)) .d_expr.get()); break; + case REGEXP_LOOP: + res = Op(kind, + *mkValHelper(CVC4::RegExpLoop(arg1, arg2)) + .d_expr.get()); + break; default: CVC4_API_KIND_CHECK_EXPECTED(false, kind) << "operator kind with two uint32_t arguments"; diff --git a/src/api/cvc4cppkind.h b/src/api/cvc4cppkind.h index d399ad616..f8e1fb90c 100644 --- a/src/api/cvc4cppkind.h +++ b/src/api/cvc4cppkind.h @@ -2175,15 +2175,37 @@ enum CVC4_PUBLIC Kind : int32_t */ REGEXP_RANGE, /** - * Regexp loop. - * Parameters: 2 (3) - * -[1]: Term of sort RegExp - * -[2]: Lower bound for the number of repetitions of the first argument - * -[3]: Upper bound for the number of repetitions of the first argument + * Operator for regular expression repeat. + * Parameters: 1 + * -[1]: The number of repetitions * Create with: - * mkTerm(Kind kind, Term child1, Term child2) - * mkTerm(Kind kind, Term child1, Term child2, Term child3) - * mkTerm(Kind kind, const std::vector& children) + * mkOp(Kind kind, uint32_t param) + * + * Apply regular expression loop. + * Parameters: 2 + * -[1]: Op of kind REGEXP_REPEAT + * -[2]: Term of regular expression sort + * Create with: + * mkTerm(Op op, Term child) + * mkTerm(Op op, const std::vector& children) + */ + REGEXP_REPEAT, + /** + * Operator for regular expression loop, from lower bound to upper bound + * number of repetitions. + * Parameters: 2 + * -[1]: The lower bound + * -[2]: The upper bound + * Create with: + * mkOp(Kind kind, uint32_t param, uint32_t param) + * + * Apply regular expression loop. + * Parameters: 2 + * -[1]: Op of kind REGEXP_LOOP + * -[2]: Term of regular expression sort + * Create with: + * mkTerm(Op op, Term child) + * mkTerm(Op op, const std::vector& children) */ REGEXP_LOOP, /** diff --git a/src/bindings/java/CMakeLists.txt b/src/bindings/java/CMakeLists.txt index 344387ed9..c6034d0aa 100644 --- a/src/bindings/java/CMakeLists.txt +++ b/src/bindings/java/CMakeLists.txt @@ -112,6 +112,8 @@ set(gen_java_files ${CMAKE_CURRENT_BINARY_DIR}/RecordUpdate.java ${CMAKE_CURRENT_BINARY_DIR}/RecordUpdateHashFunction.java ${CMAKE_CURRENT_BINARY_DIR}/RecoverableModalException.java + ${CMAKE_CURRENT_BINARY_DIR}/RegExpLoop.java + ${CMAKE_CURRENT_BINARY_DIR}/RegExpRepeat.java ${CMAKE_CURRENT_BINARY_DIR}/RegExpType.java ${CMAKE_CURRENT_BINARY_DIR}/Result.java ${CMAKE_CURRENT_BINARY_DIR}/RoundingMode.java diff --git a/src/cvc4.i b/src/cvc4.i index 9dcff7f8e..42713ce40 100644 --- a/src/cvc4.i +++ b/src/cvc4.i @@ -274,6 +274,7 @@ std::set CVC4::JavaInputStreamAdapter::s_adapters; %include "util/cardinality.i" %include "util/hash.i" %include "util/proof.i" +%include "util/regexp.i" %include "util/result.i" %include "util/sexpr.i" %include "util/statistics.i" diff --git a/src/parser/cvc/Cvc.g b/src/parser/cvc/Cvc.g index 8b3b96cfd..7b3e1c4de 100644 --- a/src/parser/cvc/Cvc.g +++ b/src/parser/cvc/Cvc.g @@ -2074,8 +2074,11 @@ stringTerm[CVC4::api::Term& f] { f = MK_TERM(CVC4::api::REGEXP_OPT, f); } | REGEXP_RANGE_TOK LPAREN formula[f] COMMA formula[f2] RPAREN { f = MK_TERM(CVC4::api::REGEXP_RANGE, f, f2); } - | REGEXP_LOOP_TOK LPAREN formula[f] COMMA formula[f2] COMMA formula[f3] RPAREN - { f = MK_TERM(CVC4::api::REGEXP_LOOP, f, f2, f3); } + | REGEXP_LOOP_TOK LPAREN formula[f] COMMA lo=numeral COMMA hi=numeral RPAREN + { + api::Op lop = SOLVER->mkOp(CVC4::api::REGEXP_LOOP, lo, hi); + f = MK_TERM(lop, f); + } | REGEXP_COMPLEMENT_TOK LPAREN formula[f] RPAREN { f = MK_TERM(CVC4::api::REGEXP_COMPLEMENT, f); } | REGEXP_EMPTY_TOK diff --git a/src/parser/smt2/smt2.cpp b/src/parser/smt2/smt2.cpp index 7ba882f24..3233ee7e8 100644 --- a/src/parser/smt2/smt2.cpp +++ b/src/parser/smt2/smt2.cpp @@ -193,8 +193,9 @@ void Smt2::addStringOperators() { addOperator(api::REGEXP_STAR, "re.*"); addOperator(api::REGEXP_PLUS, "re.+"); addOperator(api::REGEXP_OPT, "re.opt"); + addIndexedOperator(api::REGEXP_REPEAT, api::REGEXP_REPEAT, "re.^"); + addIndexedOperator(api::REGEXP_LOOP, api::REGEXP_LOOP, "re.loop"); addOperator(api::REGEXP_RANGE, "re.range"); - addOperator(api::REGEXP_LOOP, "re.loop"); addOperator(api::REGEXP_COMPLEMENT, "re.comp"); addOperator(api::REGEXP_DIFF, "re.diff"); addOperator(api::STRING_LT, "str.<"); diff --git a/src/theory/strings/kinds b/src/theory/strings/kinds index 3f7abdb7c..06f05a8af 100644 --- a/src/theory/strings/kinds +++ b/src/theory/strings/kinds @@ -68,12 +68,25 @@ operator REGEXP_STAR 1 "regexp *" operator REGEXP_PLUS 1 "regexp +" operator REGEXP_OPT 1 "regexp ?" operator REGEXP_RANGE 2 "regexp range" -operator REGEXP_LOOP 2:3 "regexp loop" operator REGEXP_COMPLEMENT 1 "regexp complement" operator REGEXP_EMPTY 0 "regexp empty" operator REGEXP_SIGMA 0 "regexp all characters" +constant REGEXP_REPEAT_OP \ + ::CVC4::RegExpRepeat \ + ::CVC4::RegExpRepeatHashFunction \ + "util/regexp.h" \ + "operator for regular expression repeat; payload is an instance of the CVC4::RegExpRepeat class" +parameterized REGEXP_REPEAT REGEXP_REPEAT_OP 1 "regular expression repeat; first parameter is a REGEXP_REPEAT_OP, second is a regular expression term" + +constant REGEXP_LOOP_OP \ + ::CVC4::RegExpLoop \ + ::CVC4::RegExpLoopHashFunction \ + "util/regexp.h" \ + "operator for regular expression loop; payload is an instance of the CVC4::RegExpLoop class" +parameterized REGEXP_LOOP REGEXP_LOOP_OP 1 "regular expression loop; first parameter is a REGEXP_LOOP_OP, second is a regular expression term" + #internal operator REGEXP_RV 1 "regexp rv (internal use only)" typerule REGEXP_RV "SimpleTypeRule" @@ -88,7 +101,10 @@ typerule REGEXP_STAR "SimpleTypeRule" typerule REGEXP_PLUS "SimpleTypeRule" typerule REGEXP_OPT "SimpleTypeRule" typerule REGEXP_RANGE ::CVC4::theory::strings::RegExpRangeTypeRule -typerule REGEXP_LOOP "SimpleTypeRule>" +typerule REGEXP_REPEAT_OP "SimpleTypeRule" +typerule REGEXP_REPEAT "SimpleTypeRule" +typerule REGEXP_LOOP_OP "SimpleTypeRule" +typerule REGEXP_LOOP "SimpleTypeRule" typerule REGEXP_COMPLEMENT "SimpleTypeRule" typerule STRING_TO_REGEXP "SimpleTypeRule" typerule STRING_IN_REGEXP "SimpleTypeRule" diff --git a/src/theory/strings/regexp_operation.cpp b/src/theory/strings/regexp_operation.cpp index 9a2091eac..6453e1909 100644 --- a/src/theory/strings/regexp_operation.cpp +++ b/src/theory/strings/regexp_operation.cpp @@ -93,15 +93,7 @@ RegExpConstType RegExpOpr::getRegExpConstType(Node r) { d_constCache[cur] = RE_C_UNKNOWN; visit.push_back(cur); - if (ck == REGEXP_LOOP) - { - // only add the first child of loop - visit.push_back(cur[0]); - } - else - { - visit.insert(visit.end(), cur.begin(), cur.end()); - } + visit.insert(visit.end(), cur.begin(), cur.end()); } } else if (it->second == RE_C_UNKNOWN) @@ -260,7 +252,9 @@ int RegExpOpr::delta( Node r, Node &exp ) { break; } case kind::REGEXP_LOOP: { - if(r[1] == d_zero) { + uint32_t lo = utils::getLoopMinOccurrences(r); + if (lo == 0) + { ret = 1; } else { ret = delta(r[0], exp); @@ -501,18 +495,18 @@ int RegExpOpr::derivativeS( Node r, CVC4::String c, Node &retNode ) { break; } case kind::REGEXP_LOOP: { - if(r[1] == r[2] && r[1] == d_zero) { + uint32_t l = utils::getLoopMinOccurrences(r); + uint32_t u = utils::getLoopMaxOccurrences(r); + if (l == u && l == 0) + { ret = 2; //retNode = d_emptyRegexp; } else { Node dc; ret = derivativeS(r[0], c, dc); if(dc==d_emptyRegexp) { - unsigned l = r[1].getConst().getNumerator().toUnsignedInt(); - unsigned u = r[2].getConst().getNumerator().toUnsignedInt(); - Node r2 = NodeManager::currentNM()->mkNode(kind::REGEXP_LOOP, r[0], - NodeManager::currentNM()->mkConst(CVC4::Rational(l==0? 0 : (l-1))), - NodeManager::currentNM()->mkConst(CVC4::Rational(u-1))); + Node lop = nm->mkConst(RegExpLoop(l == 0 ? 0 : (l - 1), u - 1)); + Node r2 = nm->mkNode(REGEXP_LOOP, lop, r[0]); retNode = dc==d_emptySingleton? r2 : NodeManager::currentNM()->mkNode( kind::REGEXP_CONCAT, dc, r2 ); } else { retNode = d_emptyRegexp; @@ -686,16 +680,16 @@ Node RegExpOpr::derivativeSingle( Node r, CVC4::String c ) { break; } case kind::REGEXP_LOOP: { - if(r[1] == r[2] && r[1] == d_zero) { + uint32_t l = utils::getLoopMinOccurrences(r); + uint32_t u = utils::getLoopMaxOccurrences(r); + if (l == u || l == 0) + { retNode = d_emptyRegexp; } else { Node dc = derivativeSingle(r[0], c); if(dc != d_emptyRegexp) { - unsigned l = r[1].getConst().getNumerator().toUnsignedInt(); - unsigned u = r[2].getConst().getNumerator().toUnsignedInt(); - Node r2 = NodeManager::currentNM()->mkNode(kind::REGEXP_LOOP, r[0], - NodeManager::currentNM()->mkConst(CVC4::Rational(l==0? 0 : (l-1))), - NodeManager::currentNM()->mkConst(CVC4::Rational(u-1))); + Node lop = nm->mkConst(RegExpLoop(l == 0 ? 0 : (l - 1), u - 1)); + Node r2 = nm->mkNode(REGEXP_LOOP, lop, r[0]); retNode = dc==d_emptySingleton? r2 : NodeManager::currentNM()->mkNode( kind::REGEXP_CONCAT, dc, r2 ); } else { retNode = d_emptyRegexp; diff --git a/src/theory/strings/sequences_rewriter.cpp b/src/theory/strings/sequences_rewriter.cpp index 861d99135..a86c9599f 100644 --- a/src/theory/strings/sequences_rewriter.cpp +++ b/src/theory/strings/sequences_rewriter.cpp @@ -1192,61 +1192,40 @@ Node SequencesRewriter::rewriteLoopRegExp(TNode node) { return returnRewrite(node, r, Rewrite::RE_LOOP_STAR); } - TNode n1 = node[1]; NodeManager* nm = NodeManager::currentNM(); CVC4::Rational rMaxInt(String::maxSize()); - AlwaysAssert(n1.isConst()) << "re.loop contains non-constant integer (1)."; - AlwaysAssert(n1.getConst().sgn() >= 0) - << "Negative integer in string REGEXP_LOOP (1)"; - Assert(n1.getConst() <= rMaxInt) - << "Exceeded UINT32_MAX in string REGEXP_LOOP (1)"; - uint32_t l = n1.getConst().getNumerator().toUnsignedInt(); + uint32_t l = utils::getLoopMinOccurrences(node); std::vector vec_nodes; for (unsigned i = 0; i < l; i++) { vec_nodes.push_back(r); } - if (node.getNumChildren() == 3) + Node n = + vec_nodes.size() == 0 + ? nm->mkNode(STRING_TO_REGEXP, nm->mkConst(String(""))) + : vec_nodes.size() == 1 ? r : nm->mkNode(REGEXP_CONCAT, vec_nodes); + uint32_t u = utils::getLoopMaxOccurrences(node); + if (u < l) { - TNode n2 = Rewriter::rewrite(node[2]); - Node n = - vec_nodes.size() == 0 - ? nm->mkNode(STRING_TO_REGEXP, nm->mkConst(String(""))) - : vec_nodes.size() == 1 ? r : nm->mkNode(REGEXP_CONCAT, vec_nodes); - AlwaysAssert(n2.isConst()) << "re.loop contains non-constant integer (2)."; - AlwaysAssert(n2.getConst().sgn() >= 0) - << "Negative integer in string REGEXP_LOOP (2)"; - Assert(n2.getConst() <= rMaxInt) - << "Exceeded UINT32_MAX in string REGEXP_LOOP (2)"; - uint32_t u = n2.getConst().getNumerator().toUnsignedInt(); - if (u <= l) - { - retNode = n; - } - else - { - std::vector vec2; - vec2.push_back(n); - TypeNode rtype = nm->regExpType(); - for (unsigned j = l; j < u; j++) - { - vec_nodes.push_back(r); - n = utils::mkConcat(vec_nodes, rtype); - vec2.push_back(n); - } - retNode = nm->mkNode(REGEXP_UNION, vec2); - } + std::vector nvec; + retNode = nm->mkNode(REGEXP_EMPTY, nvec); + } + else if (u == l) + { + retNode = n; } else { - Node rest = nm->mkNode(REGEXP_STAR, r); - retNode = vec_nodes.size() == 0 - ? rest - : vec_nodes.size() == 1 - ? nm->mkNode(REGEXP_CONCAT, r, rest) - : nm->mkNode(REGEXP_CONCAT, - nm->mkNode(REGEXP_CONCAT, vec_nodes), - rest); + std::vector vec2; + vec2.push_back(n); + TypeNode rtype = nm->regExpType(); + for (uint32_t j = l; j < u; j++) + { + vec_nodes.push_back(r); + n = utils::mkConcat(vec_nodes, rtype); + vec2.push_back(n); + } + retNode = nm->mkNode(REGEXP_UNION, vec2); } Trace("strings-lp") << "Strings::lp " << node << " => " << retNode << std::endl; @@ -1963,6 +1942,13 @@ RewriteResponse SequencesRewriter::postRewrite(TNode node) { retNode = rewriteLoopRegExp(node); } + else if (nk == REGEXP_REPEAT) + { + // ((_ re.^ n) R) --> ((_ re.loop n n) R) + unsigned r = utils::getRepeatAmount(node); + Node lop = nm->mkConst(RegExpLoop(r, r)); + retNode = nm->mkNode(REGEXP_LOOP, lop, node[0]); + } Trace("strings-postrewrite") << "Strings::postRewrite returning " << retNode << std::endl; diff --git a/src/theory/strings/theory_strings_utils.cpp b/src/theory/strings/theory_strings_utils.cpp index a3f6f4255..3b4c757f2 100644 --- a/src/theory/strings/theory_strings_utils.cpp +++ b/src/theory/strings/theory_strings_utils.cpp @@ -293,6 +293,24 @@ TypeNode getOwnerStringType(Node n) return tn; } +unsigned getRepeatAmount(TNode node) +{ + Assert(node.getKind() == REGEXP_REPEAT); + return node.getOperator().getConst().d_repeatAmount; +} + +unsigned getLoopMaxOccurrences(TNode node) +{ + Assert(node.getKind() == REGEXP_LOOP); + return node.getOperator().getConst().d_loopMaxOcc; +} + +unsigned getLoopMinOccurrences(TNode node) +{ + Assert(node.getKind() == REGEXP_LOOP); + return node.getOperator().getConst().d_loopMinOcc; +} + } // namespace utils } // namespace strings } // namespace theory diff --git a/src/theory/strings/theory_strings_utils.h b/src/theory/strings/theory_strings_utils.h index 578c224df..846d3d563 100644 --- a/src/theory/strings/theory_strings_utils.h +++ b/src/theory/strings/theory_strings_utils.h @@ -153,6 +153,14 @@ bool isStringKind(Kind k); */ TypeNode getOwnerStringType(Node n); +/* Get the number of repetitions for a regexp repeat node */ +unsigned getRepeatAmount(TNode node); + +/* Get the maximum occurrences of given regexp loop node. */ +unsigned getLoopMaxOccurrences(TNode node); +/* Get the minimum occurrences of given regexp loop node. */ +unsigned getLoopMinOccurrences(TNode node); + } // namespace utils } // namespace strings } // namespace theory diff --git a/src/util/CMakeLists.txt b/src/util/CMakeLists.txt index 6895dc01a..eba5fb8c9 100644 --- a/src/util/CMakeLists.txt +++ b/src/util/CMakeLists.txt @@ -29,6 +29,8 @@ libcvc4_add_sources( resource_manager.h result.cpp result.h + regexp.cpp + regexp.h safe_print.cpp safe_print.h sampler.cpp diff --git a/src/util/regexp.cpp b/src/util/regexp.cpp new file mode 100644 index 000000000..ca6547134 --- /dev/null +++ b/src/util/regexp.cpp @@ -0,0 +1,57 @@ +/********************* */ +/*! \file regexp.cpp + ** \verbatim + ** Top contributors (to current version): + ** Andrew Reynolds + ** 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 data structures for regular expression operators. + **/ + +#include "util/regexp.h" + +#include + +namespace CVC4 { + +RegExpRepeat::RegExpRepeat(uint32_t repeatAmount) : d_repeatAmount(repeatAmount) {} + +bool RegExpRepeat::operator==(const RegExpRepeat& r) const +{ + return d_repeatAmount == r.d_repeatAmount; +} + +RegExpLoop::RegExpLoop(uint32_t l, uint32_t h) : d_loopMinOcc(l), d_loopMaxOcc(h) {} + +bool RegExpLoop::operator==(const RegExpLoop& r) const +{ + return d_loopMinOcc == r.d_loopMinOcc + && d_loopMaxOcc == r.d_loopMaxOcc; +} + +size_t RegExpRepeatHashFunction::operator()(const RegExpRepeat& r) const +{ + return r.d_repeatAmount; +} + +size_t RegExpLoopHashFunction::operator()(const RegExpLoop& r) const +{ + return r.d_loopMinOcc + r.d_loopMaxOcc; +} + +std::ostream& operator<<(std::ostream& os, const RegExpRepeat& r) +{ + return os << r.d_repeatAmount; +} + +std::ostream& operator<<(std::ostream& os, const RegExpLoop& r) +{ + return os << "[" << r.d_loopMinOcc << ".." << r.d_loopMaxOcc << "]"; +} + +} // namespace CVC4 + diff --git a/src/util/regexp.h b/src/util/regexp.h new file mode 100644 index 000000000..aaba9e4db --- /dev/null +++ b/src/util/regexp.h @@ -0,0 +1,75 @@ +/********************* */ +/*! \file regexp.h + ** \verbatim + ** Top contributors (to current version): + ** Andrew Reynolds + ** 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 Data structures for regular expression operators. + **/ + +#include "cvc4_public.h" + +#ifndef CVC4__UTIL__REGEXP_H +#define CVC4__UTIL__REGEXP_H + +#include +#include + +namespace CVC4 { + +struct CVC4_PUBLIC RegExpRepeat +{ + RegExpRepeat(uint32_t repeatAmount); + + bool operator==(const RegExpRepeat& r) const; + /** The amount of repetitions of the regular expression */ + uint32_t d_repeatAmount; +}; + +struct CVC4_PUBLIC RegExpLoop +{ + RegExpLoop(uint32_t l, uint32_t h); + + bool operator==(const RegExpLoop& r) const; + /** The minimum number of repetitions of the regular expression */ + uint32_t d_loopMinOcc; + /** The maximum number of repetitions of the regular expression */ + uint32_t d_loopMaxOcc; +}; + +/* ----------------------------------------------------------------------- + ** Hash Function structs + * ----------------------------------------------------------------------- */ + +/* + * Hash function for the RegExpRepeat constants. + */ +struct CVC4_PUBLIC RegExpRepeatHashFunction +{ + size_t operator()(const RegExpRepeat& r) const; +}; + +/** + * Hash function for the RegExpLoop objects. + */ +struct CVC4_PUBLIC RegExpLoopHashFunction +{ + size_t operator()(const RegExpLoop& r) const; +}; + +/* ----------------------------------------------------------------------- + ** Output stream + * ----------------------------------------------------------------------- */ + +std::ostream& operator<<(std::ostream& os, const RegExpRepeat& bv) CVC4_PUBLIC; + +std::ostream& operator<<(std::ostream& os, const RegExpLoop& bv) CVC4_PUBLIC; + +} // namespace CVC4 + +#endif /* CVC4__UTIL__REGEXP_H */ diff --git a/src/util/regexp.i b/src/util/regexp.i new file mode 100644 index 000000000..775e778f7 --- /dev/null +++ b/src/util/regexp.i @@ -0,0 +1,12 @@ +%{ +#include "util/regexp.h" +%} + +%rename(equals) CVC4::RegExpRepeat::operator==(const RegExpRepeat&) const; + +%rename(equals) CVC4::RegExpLoop::operator==(const RegExpLoop&) const; + +%ignore CVC4::operator<<(std::ostream&, const RegExpRepeat&); +%ignore CVC4::operator<<(std::ostream&, const RegExpLoop&); + +%include "util/regexp.h" diff --git a/test/regress/CMakeLists.txt b/test/regress/CMakeLists.txt index d843eb5ed..b9835d919 100644 --- a/test/regress/CMakeLists.txt +++ b/test/regress/CMakeLists.txt @@ -942,6 +942,7 @@ set(regress_0_tests regress0/strings/large-model.smt2 regress0/strings/leadingzero001.smt2 regress0/strings/loop001.smt2 + regress0/strings/loop-wrong-sem.smt2 regress0/strings/model001.smt2 regress0/strings/model-code-point.smt2 regress0/strings/model-friendly.smt2 diff --git a/test/regress/regress0/strings/bug002.smt2 b/test/regress/regress0/strings/bug002.smt2 index fd60089fd..5bf21ebb9 100644 --- a/test/regress/regress0/strings/bug002.smt2 +++ b/test/regress/regress0/strings/bug002.smt2 @@ -4,7 +4,7 @@ (set-info :status sat) ; regex = [\*-,\t\*-\|](.{6,}()?)+ -(define-fun strinre ((?s String)) Bool (str.in.re ?s (re.union re.nostr (re.++ (str.to.re "") (str.to.re "") (re.union re.nostr (re.range "*" ",") (str.to.re "\t") (re.range "*" "|") ) (re.+ (re.union re.nostr (re.++ (str.to.re "") (str.to.re "") (re.loop re.allchar 6 ) (re.opt (re.union re.nostr (re.++ (str.to.re "") (str.to.re "") ) ) ) ) ) ) ) ) ) ) +(define-fun strinre ((?s String)) Bool (str.in.re ?s (re.union re.nostr (re.++ (str.to.re "") (str.to.re "") (re.union re.nostr (re.range "*" ",") (str.to.re "\t") (re.range "*" "|") ) (re.+ (re.union re.nostr (re.++ (str.to.re "") (str.to.re "") ((_ re.^ 6) re.allchar) (re.opt (re.union re.nostr (re.++ (str.to.re "") (str.to.re "") ) ) ) ) ) ) ) ) ) ) (assert (not (strinre "6O\1\127\n?"))) (check-sat) diff --git a/test/regress/regress0/strings/loop-wrong-sem.smt2 b/test/regress/regress0/strings/loop-wrong-sem.smt2 new file mode 100644 index 000000000..d0dd3fcb2 --- /dev/null +++ b/test/regress/regress0/strings/loop-wrong-sem.smt2 @@ -0,0 +1,4 @@ +(set-logic ALL) +(set-info :status unsat) +(assert (str.in.re "" ((_ re.loop 1 0) (str.to.re "")))) +(check-sat) diff --git a/test/regress/regress1/strings/bug686dd.smt2 b/test/regress/regress1/strings/bug686dd.smt2 index 0cb9fac26..b5c9457ff 100644 --- a/test/regress/regress1/strings/bug686dd.smt2 +++ b/test/regress/regress1/strings/bug686dd.smt2 @@ -8,7 +8,7 @@ (declare-fun root6 () T) (assert (and -(str.in.re root5 (re.loop (re.range "0" "9") 4 4) ) -(str.in.re (TCb root6) (re.loop (re.range "0" "9") 4 4) ) +(str.in.re root5 ((_ re.loop 4 4) (re.range "0" "9")) ) +(str.in.re (TCb root6) ((_ re.loop 4 4) (re.range "0" "9")) ) ) ) (check-sat) diff --git a/test/regress/regress1/strings/pierre150331.smt2 b/test/regress/regress1/strings/pierre150331.smt2 index add60d534..e04db8e9a 100644 --- a/test/regress/regress1/strings/pierre150331.smt2 +++ b/test/regress/regress1/strings/pierre150331.smt2 @@ -6,7 +6,7 @@ (define-fun stringEval ((?s String)) Bool (str.in.re ?s (re.union (str.to.re "H") -(re.++ (re.loop (str.to.re "{") 2 2 ) (re.loop (re.union re.nostr (re.range "" "]") (re.range "" "^") ) 2 4 ) ) ) ) ) +(re.++ ((_ re.loop 2 2) (str.to.re "{") ) ((_ re.loop 2 4) (re.union re.nostr (re.range "" "]") (re.range "" "^") ) ) ) ) ) ) (declare-fun s0() String) (declare-fun s1() String) (declare-fun s2() String) diff --git a/test/regress/regress1/strings/reloop.smt2 b/test/regress/regress1/strings/reloop.smt2 index 22537b957..6230d1656 100644 --- a/test/regress/regress1/strings/reloop.smt2 +++ b/test/regress/regress1/strings/reloop.smt2 @@ -8,11 +8,11 @@ (declare-fun z () String) (declare-fun w () String) -(assert (str.in.re x (re.loop (str.to.re "a") 5))) -(assert (str.in.re y (re.loop (str.to.re "b") 2 5))) -(assert (str.in.re z (re.loop (str.to.re "c") 5))) +(assert (str.in.re x ((_ re.^ 5) (str.to.re "a")))) +(assert (str.in.re y ((_ re.loop 2 5) (str.to.re "b")))) +(assert (str.in.re z ((_ re.loop 5 15) (str.to.re "c")))) (assert (> (str.len z) 7)) -(assert (str.in.re w (re.loop (str.to.re "b") 2 7))) +(assert (str.in.re w ((_ re.loop 2 7) (str.to.re "b")))) (assert (> (str.len w) 2)) (assert (< (str.len w) 5)) diff --git a/test/regress/regress2/strings/range-perf.smt2 b/test/regress/regress2/strings/range-perf.smt2 index 62ec10711..33960e124 100644 --- a/test/regress/regress2/strings/range-perf.smt2 +++ b/test/regress/regress2/strings/range-perf.smt2 @@ -2,6 +2,6 @@ ; EXPECT: sat (set-logic QF_SLIA) (declare-const x String) -(assert (str.in.re x (re.loop (re.range "0" "9") 12 12))) +(assert (str.in.re x ((_ re.loop 12 12) (re.range "0" "9")))) (assert (str.in.re x (re.++ (re.* re.allchar) (str.to.re "01") (re.* re.allchar)))) (check-sat) -- cgit v1.2.3 From bc4055d4543f3b697ade38b810f7ac3cf02dc3c8 Mon Sep 17 00:00:00 2001 From: Andrew Reynolds Date: Mon, 30 Mar 2020 18:15:07 -0500 Subject: Rewrites for all remaining return statements in strings rewriter (#4178) Towards proofs for string rewrites. All return statements all now associated with an enum value. An indentation in a block of code changed in rewriteMembership. --- src/theory/strings/rewrites.cpp | 29 +++ src/theory/strings/rewrites.h | 31 ++- src/theory/strings/sequences_rewriter.cpp | 406 +++++++++++++++++------------- src/theory/strings/sequences_rewriter.h | 43 ++++ src/theory/strings/strings_rewriter.cpp | 22 ++ src/theory/strings/strings_rewriter.h | 16 ++ src/util/regexp.cpp | 15 +- 7 files changed, 378 insertions(+), 184 deletions(-) diff --git a/src/theory/strings/rewrites.cpp b/src/theory/strings/rewrites.cpp index 5bc62b6e4..f1a818bf3 100644 --- a/src/theory/strings/rewrites.cpp +++ b/src/theory/strings/rewrites.cpp @@ -170,6 +170,35 @@ const char* toString(Rewrite r) case Rewrite::SUF_PREFIX_EQ: return "SUF_PREFIX_EQ"; case Rewrite::SUF_PREFIX_TO_EQS: return "SUF_PREFIX_TO_EQS"; case Rewrite::TO_CODE_EVAL: return "TO_CODE_EVAL"; + case Rewrite::EQ_REFL: return "EQ_REFL"; + case Rewrite::EQ_CONST_FALSE: return "EQ_CONST_FALSE"; + case Rewrite::EQ_SYM: return "EQ_SYM"; + case Rewrite::CONCAT_NORM: return "CONCAT_NORM"; + case Rewrite::IS_DIGIT_ELIM: return "IS_DIGIT_ELIM"; + case Rewrite::RE_CONCAT_EMPTY: return "RE_CONCAT_EMPTY"; + case Rewrite::RE_CONSUME_CCONF: return "RE_CONSUME_CCONF"; + case Rewrite::RE_CONSUME_S: return "RE_CONSUME_S"; + case Rewrite::RE_CONSUME_S_CCONF: return "RE_CONSUME_S_CCONF"; + case Rewrite::RE_CONSUME_S_FULL: return "RE_CONSUME_S_FULL"; + case Rewrite::RE_IN_EMPTY: return "RE_IN_EMPTY"; + case Rewrite::RE_IN_SIGMA: return "RE_IN_SIGMA"; + case Rewrite::RE_IN_EVAL: return "RE_IN_EVAL"; + case Rewrite::RE_IN_COMPLEMENT: return "RE_IN_COMPLEMENT"; + case Rewrite::RE_IN_RANGE: return "RE_IN_RANGE"; + case Rewrite::RE_IN_CSTRING: return "RE_IN_CSTRING"; + case Rewrite::RE_IN_ANDOR: return "RE_IN_ANDOR"; + case Rewrite::RE_REPEAT_ELIM: return "RE_REPEAT_ELIM"; + case Rewrite::SUF_PREFIX_ELIM: return "SUF_PREFIX_ELIM"; + case Rewrite::STR_LT_ELIM: return "STR_LT_ELIM"; + case Rewrite::RE_RANGE_SINGLE: return "RE_RANGE_SINGLE"; + case Rewrite::RE_OPT_ELIM: return "RE_OPT_ELIM"; + case Rewrite::RE_PLUS_ELIM: return "RE_PLUS_ELIM"; + case Rewrite::RE_DIFF_ELIM: return "RE_DIFF_ELIM"; + case Rewrite::LEN_EVAL: return "LEN_EVAL"; + case Rewrite::LEN_CONCAT: return "LEN_CONCAT"; + case Rewrite::LEN_REPL_INV: return "LEN_REPL_INV"; + case Rewrite::LEN_CONV_INV: return "LEN_CONV_INV"; + case Rewrite::CHARAT_ELIM: return "CHARAT_ELIM"; default: return "?"; } } diff --git a/src/theory/strings/rewrites.h b/src/theory/strings/rewrites.h index 91d52197c..cfa8c8448 100644 --- a/src/theory/strings/rewrites.h +++ b/src/theory/strings/rewrites.h @@ -172,7 +172,36 @@ enum class Rewrite : uint32_t SUF_PREFIX_EMPTY_CONST, SUF_PREFIX_EQ, SUF_PREFIX_TO_EQS, - TO_CODE_EVAL + TO_CODE_EVAL, + EQ_REFL, + EQ_CONST_FALSE, + EQ_SYM, + CONCAT_NORM, + IS_DIGIT_ELIM, + RE_CONCAT_EMPTY, + RE_CONSUME_CCONF, + RE_CONSUME_S, + RE_CONSUME_S_CCONF, + RE_CONSUME_S_FULL, + RE_IN_EMPTY, + RE_IN_SIGMA, + RE_IN_EVAL, + RE_IN_COMPLEMENT, + RE_IN_RANGE, + RE_IN_CSTRING, + RE_IN_ANDOR, + RE_REPEAT_ELIM, + SUF_PREFIX_ELIM, + STR_LT_ELIM, + RE_RANGE_SINGLE, + RE_OPT_ELIM, + RE_PLUS_ELIM, + RE_DIFF_ELIM, + LEN_EVAL, + LEN_CONCAT, + LEN_REPL_INV, + LEN_CONV_INV, + CHARAT_ELIM }; /** diff --git a/src/theory/strings/sequences_rewriter.cpp b/src/theory/strings/sequences_rewriter.cpp index a86c9599f..d7ee459c7 100644 --- a/src/theory/strings/sequences_rewriter.cpp +++ b/src/theory/strings/sequences_rewriter.cpp @@ -310,11 +310,13 @@ Node SequencesRewriter::rewriteEquality(Node node) Assert(node.getKind() == kind::EQUAL); if (node[0] == node[1]) { - return NodeManager::currentNM()->mkConst(true); + Node ret = NodeManager::currentNM()->mkConst(true); + return returnRewrite(node, ret, Rewrite::EQ_REFL); } else if (node[0].isConst() && node[1].isConst()) { - return NodeManager::currentNM()->mkConst(false); + Node ret = NodeManager::currentNM()->mkConst(false); + return returnRewrite(node, ret, Rewrite::EQ_CONST_FALSE); } // ( ~contains( s, t ) V ~contains( t, s ) ) => ( s == t ---> false ) @@ -388,7 +390,8 @@ Node SequencesRewriter::rewriteEquality(Node node) // standard ordering if (node[0] > node[1]) { - return NodeManager::currentNM()->mkNode(kind::EQUAL, node[1], node[0]); + Node ret = NodeManager::currentNM()->mkNode(kind::EQUAL, node[1], node[0]); + return returnRewrite(node, ret, Rewrite::EQ_SYM); } return node; } @@ -790,6 +793,59 @@ Node SequencesRewriter::rewriteArithEqualityExt(Node node) return node; } +Node SequencesRewriter::rewriteLength(Node node) +{ + Assert(node.getKind() == STRING_LENGTH); + NodeManager* nm = NodeManager::currentNM(); + Kind nk0 = node[0].getKind(); + if (node[0].isConst()) + { + Node retNode = nm->mkConst(Rational(Word::getLength(node[0]))); + return returnRewrite(node, retNode, Rewrite::LEN_EVAL); + } + else if (nk0 == kind::STRING_CONCAT) + { + Node tmpNode = node[0]; + if (tmpNode.getKind() == kind::STRING_CONCAT) + { + std::vector node_vec; + for (unsigned int i = 0; i < tmpNode.getNumChildren(); ++i) + { + if (tmpNode[i].isConst()) + { + node_vec.push_back( + nm->mkConst(Rational(Word::getLength(tmpNode[i])))); + } + else + { + node_vec.push_back(NodeManager::currentNM()->mkNode( + kind::STRING_LENGTH, tmpNode[i])); + } + } + Node retNode = NodeManager::currentNM()->mkNode(kind::PLUS, node_vec); + return returnRewrite(node, retNode, Rewrite::LEN_CONCAT); + } + } + else if (nk0 == STRING_STRREPL || nk0 == STRING_STRREPLALL) + { + Node len1 = Rewriter::rewrite(nm->mkNode(STRING_LENGTH, node[0][1])); + Node len2 = Rewriter::rewrite(nm->mkNode(STRING_LENGTH, node[0][2])); + if (len1 == len2) + { + // len( y ) == len( z ) => len( str.replace( x, y, z ) ) ---> len( x ) + Node retNode = nm->mkNode(STRING_LENGTH, node[0][0]); + return returnRewrite(node, retNode, Rewrite::LEN_REPL_INV); + } + } + else if (nk0 == STRING_TOLOWER || nk0 == STRING_TOUPPER || nk0 == STRING_REV) + { + // len( f( x ) ) == len( x ) where f is tolower, toupper, or rev. + Node retNode = nm->mkNode(STRING_LENGTH, node[0][0]); + return returnRewrite(node, retNode, Rewrite::LEN_CONV_INV); + } + return node; +} + // TODO (#1180) add rewrite // str.++( str.substr( x, n1, n2 ), str.substr( x, n1+n2, n3 ) ) ---> // str.substr( x, n1, n2+n3 ) @@ -799,7 +855,6 @@ Node SequencesRewriter::rewriteConcat(Node node) Trace("strings-rewrite-debug") << "Strings::rewriteConcat start " << node << std::endl; NodeManager* nm = NodeManager::currentNM(); - Node retNode = node; std::vector node_vec; Node preNode = Node::null(); for (Node tmpNode : node) @@ -890,10 +945,14 @@ Node SequencesRewriter::rewriteConcat(Node node) std::sort(node_vec.begin() + lastIdx, node_vec.end()); TypeNode tn = node.getType(); - retNode = utils::mkConcat(node_vec, tn); + Node retNode = utils::mkConcat(node_vec, tn); Trace("strings-rewrite-debug") << "Strings::rewriteConcat end " << retNode << std::endl; - return retNode; + if (retNode != node) + { + return returnRewrite(node, retNode, Rewrite::CONCAT_NORM); + } + return node; } Node SequencesRewriter::rewriteConcatRegExp(TNode node) @@ -940,7 +999,8 @@ Node SequencesRewriter::rewriteConcatRegExp(TNode node) { // re.++( ..., empty, ... ) ---> empty std::vector nvec; - return nm->mkNode(REGEXP_EMPTY, nvec); + Node ret = nm->mkNode(REGEXP_EMPTY, nvec); + return returnRewrite(node, ret, Rewrite::RE_CONCAT_EMPTY); } else { @@ -1236,6 +1296,59 @@ Node SequencesRewriter::rewriteLoopRegExp(TNode node) return node; } +Node SequencesRewriter::rewriteRepeatRegExp(TNode node) +{ + Assert(node.getKind() == REGEXP_REPEAT); + NodeManager* nm = NodeManager::currentNM(); + // ((_ re.^ n) R) --> ((_ re.loop n n) R) + unsigned r = utils::getRepeatAmount(node); + Node lop = nm->mkConst(RegExpLoop(r, r)); + Node retNode = nm->mkNode(REGEXP_LOOP, lop, node[0]); + return returnRewrite(node, retNode, Rewrite::RE_REPEAT_ELIM); +} + +Node SequencesRewriter::rewriteOptionRegExp(TNode node) +{ + Assert(node.getKind() == REGEXP_OPT); + NodeManager* nm = NodeManager::currentNM(); + Node retNode = + nm->mkNode(REGEXP_UNION, + nm->mkNode(STRING_TO_REGEXP, nm->mkConst(String(""))), + node[0]); + return returnRewrite(node, retNode, Rewrite::RE_OPT_ELIM); +} + +Node SequencesRewriter::rewritePlusRegExp(TNode node) +{ + Assert(node.getKind() == REGEXP_PLUS); + NodeManager* nm = NodeManager::currentNM(); + Node retNode = + nm->mkNode(REGEXP_CONCAT, node[0], nm->mkNode(REGEXP_STAR, node[0])); + return returnRewrite(node, retNode, Rewrite::RE_PLUS_ELIM); +} + +Node SequencesRewriter::rewriteDifferenceRegExp(TNode node) +{ + Assert(node.getKind() == REGEXP_DIFF); + NodeManager* nm = NodeManager::currentNM(); + Node retNode = + nm->mkNode(REGEXP_INTER, node[0], nm->mkNode(REGEXP_COMPLEMENT, node[1])); + return returnRewrite(node, retNode, Rewrite::RE_DIFF_ELIM); +} + +Node SequencesRewriter::rewriteRangeRegExp(TNode node) +{ + Assert(node.getKind() == REGEXP_RANGE); + if (node[0] == node[1]) + { + NodeManager* nm = NodeManager::currentNM(); + Node retNode = nm->mkNode(STRING_TO_REGEXP, node[0]); + // re.range( "A", "A" ) ---> str.to_re( "A" ) + return returnRewrite(node, retNode, Rewrite::RE_RANGE_SINGLE); + } + return node; +} + bool SequencesRewriter::isConstRegExp(TNode t) { if (t.getKind() == kind::STRING_TO_REGEXP) @@ -1503,7 +1616,6 @@ bool SequencesRewriter::testConstStringInRegExp(CVC4::String& s, Node SequencesRewriter::rewriteMembership(TNode node) { NodeManager* nm = NodeManager::currentNM(); - Node retNode = node; Node x = node[0]; Node r = node[1]; @@ -1512,19 +1624,22 @@ Node SequencesRewriter::rewriteMembership(TNode node) if(r.getKind() == kind::REGEXP_EMPTY) { - retNode = NodeManager::currentNM()->mkConst( false ); + Node retNode = NodeManager::currentNM()->mkConst(false); + return returnRewrite(node, retNode, Rewrite::RE_IN_EMPTY); } else if (x.isConst() && isConstRegExp(r)) { // test whether x in node[1] CVC4::String s = x.getConst(); - retNode = + Node retNode = NodeManager::currentNM()->mkConst(testConstStringInRegExp(s, 0, r)); + return returnRewrite(node, retNode, Rewrite::RE_IN_EVAL); } else if (r.getKind() == kind::REGEXP_SIGMA) { Node one = nm->mkConst(Rational(1)); - retNode = one.eqNode(nm->mkNode(STRING_LENGTH, x)); + Node retNode = one.eqNode(nm->mkNode(STRING_LENGTH, x)); + return returnRewrite(node, retNode, Rewrite::RE_IN_SIGMA); } else if (r.getKind() == kind::REGEXP_STAR) { @@ -1533,7 +1648,7 @@ Node SequencesRewriter::rewriteMembership(TNode node) String s = x.getConst(); if (s.size() == 0) { - retNode = nm->mkConst(true); + Node retNode = nm->mkConst(true); // e.g. (str.in.re "" (re.* (str.to.re x))) ----> true return returnRewrite(node, retNode, Rewrite::RE_EMPTY_IN_STR_STAR); } @@ -1541,7 +1656,7 @@ Node SequencesRewriter::rewriteMembership(TNode node) { if (r[0].getKind() == STRING_TO_REGEXP) { - retNode = r[0][0].eqNode(x); + Node retNode = r[0][0].eqNode(x); // e.g. (str.in.re "A" (re.* (str.to.re x))) ----> "A" = x return returnRewrite(node, retNode, Rewrite::RE_CHAR_IN_STR_STAR); } @@ -1570,7 +1685,7 @@ Node SequencesRewriter::rewriteMembership(TNode node) } if (r[0].getKind() == kind::REGEXP_SIGMA) { - retNode = NodeManager::currentNM()->mkConst(true); + Node retNode = NodeManager::currentNM()->mkConst(true); return returnRewrite(node, retNode, Rewrite::RE_IN_SIGMA_STAR); } } @@ -1620,14 +1735,14 @@ Node SequencesRewriter::rewriteMembership(TNode node) // x in re.++(_*, _, _) ---> str.len(x) >= 2 Node num = nm->mkConst(Rational(allSigmaMinSize)); Node lenx = nm->mkNode(STRING_LENGTH, x); - retNode = nm->mkNode(allSigmaStrict ? EQUAL : GEQ, lenx, num); + Node retNode = nm->mkNode(allSigmaStrict ? EQUAL : GEQ, lenx, num); return returnRewrite(node, retNode, Rewrite::RE_CONCAT_PURE_ALLCHAR); } else if (allSigmaMinSize == 0 && nchildren >= 3 && constIdx != 0 && constIdx != nchildren - 1) { // x in re.++(_*, "abc", _*) ---> str.contains(x, "abc") - retNode = nm->mkNode(STRING_STRCTN, x, constStr); + Node retNode = nm->mkNode(STRING_STRCTN, x, constStr); return returnRewrite(node, retNode, Rewrite::RE_CONCAT_TO_CONTAINS); } } @@ -1641,132 +1756,125 @@ Node SequencesRewriter::rewriteMembership(TNode node) mvec.push_back( NodeManager::currentNM()->mkNode(kind::STRING_IN_REGEXP, x, r[i])); } - retNode = NodeManager::currentNM()->mkNode( + Node retNode = NodeManager::currentNM()->mkNode( r.getKind() == kind::REGEXP_INTER ? kind::AND : kind::OR, mvec); + return returnRewrite(node, retNode, Rewrite::RE_IN_ANDOR); } else if (r.getKind() == kind::STRING_TO_REGEXP) { - retNode = x.eqNode(r[0]); + Node retNode = x.eqNode(r[0]); + return returnRewrite(node, retNode, Rewrite::RE_IN_CSTRING); } else if (r.getKind() == REGEXP_RANGE) { // x in re.range( char_i, char_j ) ---> i <= str.code(x) <= j Node xcode = nm->mkNode(STRING_TO_CODE, x); - retNode = + Node retNode = nm->mkNode(AND, nm->mkNode(LEQ, nm->mkNode(STRING_TO_CODE, r[0]), xcode), nm->mkNode(LEQ, xcode, nm->mkNode(STRING_TO_CODE, r[1]))); + return returnRewrite(node, retNode, Rewrite::RE_IN_RANGE); } else if (r.getKind() == REGEXP_COMPLEMENT) { - retNode = nm->mkNode(STRING_IN_REGEXP, x, r[0]).negate(); - } - else if (x != node[0] || r != node[1]) - { - retNode = NodeManager::currentNM()->mkNode(kind::STRING_IN_REGEXP, x, r); + Node retNode = nm->mkNode(STRING_IN_REGEXP, x, r[0]).negate(); + return returnRewrite(node, retNode, Rewrite::RE_IN_COMPLEMENT); } // do simple consumes - if (retNode == node) + Node retNode = node; + if (r.getKind() == kind::REGEXP_STAR) { - if (r.getKind() == kind::REGEXP_STAR) + for (unsigned dir = 0; dir <= 1; dir++) { - for (unsigned dir = 0; dir <= 1; dir++) + std::vector mchildren; + utils::getConcat(x, mchildren); + bool success = true; + while (success) { - std::vector mchildren; - utils::getConcat(x, mchildren); - bool success = true; - while (success) + success = false; + std::vector children; + utils::getConcat(r[0], children); + Node scn = simpleRegexpConsume(mchildren, children, dir); + if (!scn.isNull()) { - success = false; - std::vector children; - utils::getConcat(r[0], children); - Node scn = simpleRegexpConsume(mchildren, children, dir); - if (!scn.isNull()) + Trace("regexp-ext-rewrite") + << "Regexp star : const conflict : " << node << std::endl; + return returnRewrite(node, scn, Rewrite::RE_CONSUME_S_CCONF); + } + else if (children.empty()) + { + // fully consumed one copy of the STAR + if (mchildren.empty()) { Trace("regexp-ext-rewrite") - << "Regexp star : const conflict : " << node << std::endl; - return scn; + << "Regexp star : full consume : " << node << std::endl; + Node ret = NodeManager::currentNM()->mkConst(true); + return returnRewrite(node, ret, Rewrite::RE_CONSUME_S_FULL); } - else if (children.empty()) + else { - // fully consumed one copy of the STAR - if (mchildren.empty()) - { - Trace("regexp-ext-rewrite") - << "Regexp star : full consume : " << node << std::endl; - return NodeManager::currentNM()->mkConst(true); - } - else - { - retNode = nm->mkNode(STRING_IN_REGEXP, - utils::mkConcat(mchildren, stype), - r); - success = true; - } + retNode = nm->mkNode( + STRING_IN_REGEXP, utils::mkConcat(mchildren, stype), r); + success = true; } } - if (retNode != node) - { - Trace("regexp-ext-rewrite") << "Regexp star : rewrite " << node - << " -> " << retNode << std::endl; - break; - } + } + if (retNode != node) + { + Trace("regexp-ext-rewrite") << "Regexp star : rewrite " << node + << " -> " << retNode << std::endl; + return returnRewrite(node, retNode, Rewrite::RE_CONSUME_S); } } - else - { - std::vector children; - utils::getConcat(r, children); - std::vector mchildren; - utils::getConcat(x, mchildren); - unsigned prevSize = children.size() + mchildren.size(); - Node scn = simpleRegexpConsume(mchildren, children); - if (!scn.isNull()) + } + else + { + std::vector children; + utils::getConcat(r, children); + std::vector mchildren; + utils::getConcat(x, mchildren); + unsigned prevSize = children.size() + mchildren.size(); + Node scn = simpleRegexpConsume(mchildren, children); + if (!scn.isNull()) + { + Trace("regexp-ext-rewrite") + << "Regexp : const conflict : " << node << std::endl; + return returnRewrite(node, scn, Rewrite::RE_CONSUME_CCONF); + } + else if ((children.size() + mchildren.size()) != prevSize) + { + // Given a membership (str.++ x1 ... xn) in (re.++ r1 ... rm), + // above, we strip components to construct an equivalent membership: + // (str.++ xi .. xj) in (re.++ rk ... rl). + Node xn = utils::mkConcat(mchildren, stype); + Node emptyStr = nm->mkConst(String("")); + if (children.empty()) { - Trace("regexp-ext-rewrite") - << "Regexp : const conflict : " << node << std::endl; - return scn; + // If we stripped all components on the right, then the left is + // equal to the empty string. + // e.g. (str.++ "a" x) in (re.++ (str.to.re "a")) ---> (= x "") + retNode = xn.eqNode(emptyStr); } else { - if ((children.size() + mchildren.size()) != prevSize) - { - // Given a membership (str.++ x1 ... xn) in (re.++ r1 ... rm), - // above, we strip components to construct an equivalent membership: - // (str.++ xi .. xj) in (re.++ rk ... rl). - Node xn = utils::mkConcat(mchildren, stype); - Node emptyStr = nm->mkConst(String("")); - if (children.empty()) - { - // If we stripped all components on the right, then the left is - // equal to the empty string. - // e.g. (str.++ "a" x) in (re.++ (str.to.re "a")) ---> (= x "") - retNode = xn.eqNode(emptyStr); - } - else - { - // otherwise, construct the updated regular expression - retNode = nm->mkNode( - STRING_IN_REGEXP, xn, utils::mkConcat(children, rtype)); - } - Trace("regexp-ext-rewrite") << "Regexp : rewrite : " << node << " -> " - << retNode << std::endl; - return returnRewrite(node, retNode, Rewrite::RE_SIMPLE_CONSUME); - } + // otherwise, construct the updated regular expression + retNode = + nm->mkNode(STRING_IN_REGEXP, xn, utils::mkConcat(children, rtype)); } + Trace("regexp-ext-rewrite") + << "Regexp : rewrite : " << node << " -> " << retNode << std::endl; + return returnRewrite(node, retNode, Rewrite::RE_SIMPLE_CONSUME); } } - return retNode; + return node; } RewriteResponse SequencesRewriter::postRewrite(TNode node) { Trace("strings-postrewrite") << "Strings::postRewrite start " << node << std::endl; - NodeManager* nm = NodeManager::currentNM(); Node retNode = node; - Node orig = retNode; Kind nk = node.getKind(); if (nk == kind::STRING_CONCAT) { @@ -1778,59 +1886,11 @@ RewriteResponse SequencesRewriter::postRewrite(TNode node) } else if (nk == kind::STRING_LENGTH) { - Kind nk0 = node[0].getKind(); - if (node[0].isConst()) - { - retNode = nm->mkConst(Rational(Word::getLength(node[0]))); - } - else if (nk0 == kind::STRING_CONCAT) - { - Node tmpNode = node[0]; - if (tmpNode.isConst()) - { - retNode = nm->mkConst(Rational(Word::getLength(tmpNode))); - } - else if (tmpNode.getKind() == kind::STRING_CONCAT) - { - std::vector node_vec; - for (unsigned int i = 0; i < tmpNode.getNumChildren(); ++i) - { - if (tmpNode[i].isConst()) - { - node_vec.push_back( - nm->mkConst(Rational(Word::getLength(tmpNode[i])))); - } - else - { - node_vec.push_back(NodeManager::currentNM()->mkNode( - kind::STRING_LENGTH, tmpNode[i])); - } - } - retNode = NodeManager::currentNM()->mkNode(kind::PLUS, node_vec); - } - } - else if (nk0 == STRING_STRREPL || nk0 == STRING_STRREPLALL) - { - Node len1 = Rewriter::rewrite(nm->mkNode(STRING_LENGTH, node[0][1])); - Node len2 = Rewriter::rewrite(nm->mkNode(STRING_LENGTH, node[0][2])); - if (len1 == len2) - { - // len( y ) == len( z ) => len( str.replace( x, y, z ) ) ---> len( x ) - retNode = nm->mkNode(STRING_LENGTH, node[0][0]); - } - } - else if (nk0 == STRING_TOLOWER || nk0 == STRING_TOUPPER - || nk0 == STRING_REV) - { - // len( f( x ) ) == len( x ) where f is tolower, toupper, or rev. - retNode = nm->mkNode(STRING_LENGTH, node[0][0]); - } + retNode = rewriteLength(node); } else if (nk == kind::STRING_CHARAT) { - Node one = NodeManager::currentNM()->mkConst(Rational(1)); - retNode = NodeManager::currentNM()->mkNode( - kind::STRING_SUBSTR, node[0], node[1], one); + retNode = rewriteCharAt(node); } else if (nk == kind::STRING_SUBSTR) { @@ -1842,10 +1902,7 @@ RewriteResponse SequencesRewriter::postRewrite(TNode node) } else if (nk == kind::STRING_LT) { - // eliminate s < t ---> s != t AND s <= t - retNode = nm->mkNode(AND, - node[0].eqNode(node[1]).negate(), - nm->mkNode(STRING_LEQ, node[0], node[1])); + retNode = StringsRewriter::rewriteStringLt(node); } else if (nk == kind::STRING_LEQ) { @@ -1877,11 +1934,7 @@ RewriteResponse SequencesRewriter::postRewrite(TNode node) } else if (nk == STRING_IS_DIGIT) { - // eliminate str.is_digit(s) ----> 48 <= str.to_code(s) <= 57 - Node t = nm->mkNode(STRING_TO_CODE, node[0]); - retNode = nm->mkNode(AND, - nm->mkNode(LEQ, nm->mkConst(Rational(48)), t), - nm->mkNode(LEQ, t, nm->mkConst(Rational(57)))); + retNode = StringsRewriter::rewriteStringIsDigit(node); } else if (nk == kind::STRING_ITOS) { @@ -1913,8 +1966,7 @@ RewriteResponse SequencesRewriter::postRewrite(TNode node) } else if (nk == REGEXP_DIFF) { - retNode = nm->mkNode( - REGEXP_INTER, node[0], nm->mkNode(REGEXP_COMPLEMENT, node[1])); + retNode = rewriteDifferenceRegExp(node); } else if (nk == REGEXP_STAR) { @@ -1922,21 +1974,15 @@ RewriteResponse SequencesRewriter::postRewrite(TNode node) } else if (nk == REGEXP_PLUS) { - retNode = - nm->mkNode(REGEXP_CONCAT, node[0], nm->mkNode(REGEXP_STAR, node[0])); + retNode = rewritePlusRegExp(node); } else if (nk == REGEXP_OPT) { - retNode = nm->mkNode(REGEXP_UNION, - nm->mkNode(STRING_TO_REGEXP, nm->mkConst(String(""))), - node[0]); + retNode = rewriteOptionRegExp(node); } else if (nk == REGEXP_RANGE) { - if (node[0] == node[1]) - { - retNode = nm->mkNode(STRING_TO_REGEXP, node[0]); - } + retNode = rewriteRangeRegExp(node); } else if (nk == REGEXP_LOOP) { @@ -1944,21 +1990,18 @@ RewriteResponse SequencesRewriter::postRewrite(TNode node) } else if (nk == REGEXP_REPEAT) { - // ((_ re.^ n) R) --> ((_ re.loop n n) R) - unsigned r = utils::getRepeatAmount(node); - Node lop = nm->mkConst(RegExpLoop(r, r)); - retNode = nm->mkNode(REGEXP_LOOP, lop, node[0]); + retNode = rewriteRepeatRegExp(node); } Trace("strings-postrewrite") << "Strings::postRewrite returning " << retNode << std::endl; - if (orig != retNode) + if (node != retNode) { Trace("strings-rewrite-debug") - << "Strings: post-rewrite " << orig << " to " << retNode << std::endl; + << "Strings: post-rewrite " << node << " to " << retNode << std::endl; + return RewriteResponse(REWRITE_AGAIN_FULL, retNode); } - return RewriteResponse(orig == retNode ? REWRITE_DONE : REWRITE_AGAIN_FULL, - retNode); + return RewriteResponse(REWRITE_DONE, retNode); } bool SequencesRewriter::hasEpsilonNode(TNode node) @@ -1979,6 +2022,15 @@ RewriteResponse SequencesRewriter::preRewrite(TNode node) return RewriteResponse(REWRITE_DONE, node); } +Node SequencesRewriter::rewriteCharAt(Node node) +{ + Assert(node.getKind() == STRING_CHARAT); + NodeManager* nm = NodeManager::currentNM(); + Node one = nm->mkConst(Rational(1)); + Node retNode = nm->mkNode(STRING_SUBSTR, node[0], node[1], one); + return returnRewrite(node, retNode, Rewrite::CHARAT_ELIM); +} + Node SequencesRewriter::rewriteSubstr(Node node) { Assert(node.getKind() == kind::STRING_SUBSTR); @@ -3511,7 +3563,7 @@ Node SequencesRewriter::rewritePrefixSuffix(Node n) Node retNode = n[0].eqNode( NodeManager::currentNM()->mkNode(kind::STRING_SUBSTR, n[1], val, lens)); - return retNode; + return returnRewrite(n, retNode, Rewrite::SUF_PREFIX_ELIM); } Node SequencesRewriter::splitConstant(Node a, Node b, int& index, bool isRev) diff --git a/src/theory/strings/sequences_rewriter.h b/src/theory/strings/sequences_rewriter.h index a98ad97ac..afdd2c0e1 100644 --- a/src/theory/strings/sequences_rewriter.h +++ b/src/theory/strings/sequences_rewriter.h @@ -121,6 +121,37 @@ class SequencesRewriter : public TheoryRewriter * Returns the rewritten form of node. */ static Node rewriteLoopRegExp(TNode node); + /** rewrite regular expression repeat + * + * This is the entry point for post-rewriting applications of re.repeat. + * Returns the rewritten form of node. + */ + static Node rewriteRepeatRegExp(TNode node); + /** rewrite regular expression option + * + * This is the entry point for post-rewriting applications of re.opt. + * Returns the rewritten form of node. + */ + static Node rewriteOptionRegExp(TNode node); + /** rewrite regular expression plus + * + * This is the entry point for post-rewriting applications of re.+. + * Returns the rewritten form of node. + */ + static Node rewritePlusRegExp(TNode node); + /** rewrite regular expression difference + * + * This is the entry point for post-rewriting applications of re.diff. + * Returns the rewritten form of node. + */ + static Node rewriteDifferenceRegExp(TNode node); + /** rewrite regular expression range + * + * This is the entry point for post-rewriting applications of re.range. + * Returns the rewritten form of node. + */ + static Node rewriteRangeRegExp(TNode node); + /** rewrite regular expression membership * * This is the entry point for post-rewriting applications of str.in.re @@ -182,12 +213,24 @@ class SequencesRewriter : public TheoryRewriter * necessarily one of { s = t, t = s, true, false }. */ static Node rewriteEqualityExt(Node node); + /** rewrite string length + * This is the entry point for post-rewriting terms node of the form + * str.len( t ) + * Returns the rewritten form of node. + */ + static Node rewriteLength(Node node); /** rewrite concat * This is the entry point for post-rewriting terms node of the form * str.++( t1, .., tn ) * Returns the rewritten form of node. */ static Node rewriteConcat(Node node); + /** rewrite character at + * This is the entry point for post-rewriting terms node of the form + * str.charat( s, i1 ) + * Returns the rewritten form of node. + */ + static Node rewriteCharAt(Node node); /** rewrite substr * This is the entry point for post-rewriting terms node of the form * str.substr( s, i1, i2 ) diff --git a/src/theory/strings/strings_rewriter.cpp b/src/theory/strings/strings_rewriter.cpp index 275c2e25e..28ed14095 100644 --- a/src/theory/strings/strings_rewriter.cpp +++ b/src/theory/strings/strings_rewriter.cpp @@ -143,6 +143,16 @@ Node StringsRewriter::rewriteStrConvert(Node node) return node; } +Node StringsRewriter::rewriteStringLt(Node n) +{ + Assert(n.getKind() == kind::STRING_LT); + NodeManager* nm = NodeManager::currentNM(); + // eliminate s < t ---> s != t AND s <= t + Node retNode = nm->mkNode( + AND, n[0].eqNode(n[1]).negate(), nm->mkNode(STRING_LEQ, n[0], n[1])); + return returnRewrite(n, retNode, Rewrite::STR_LT_ELIM); +} + Node StringsRewriter::rewriteStringLeq(Node n) { Assert(n.getKind() == kind::STRING_LEQ); @@ -241,6 +251,18 @@ Node StringsRewriter::rewriteStringToCode(Node n) return n; } +Node StringsRewriter::rewriteStringIsDigit(Node n) +{ + Assert(n.getKind() == kind::STRING_IS_DIGIT); + NodeManager* nm = NodeManager::currentNM(); + // eliminate str.is_digit(s) ----> 48 <= str.to_code(s) <= 57 + Node t = nm->mkNode(STRING_TO_CODE, n[0]); + Node retNode = nm->mkNode(AND, + nm->mkNode(LEQ, nm->mkConst(Rational(48)), t), + nm->mkNode(LEQ, t, nm->mkConst(Rational(57)))); + return returnRewrite(n, retNode, Rewrite::IS_DIGIT_ELIM); +} + } // namespace strings } // namespace theory } // namespace CVC4 diff --git a/src/theory/strings/strings_rewriter.h b/src/theory/strings/strings_rewriter.h index e6a6b0693..0c5b0b2f8 100644 --- a/src/theory/strings/strings_rewriter.h +++ b/src/theory/strings/strings_rewriter.h @@ -56,6 +56,14 @@ class StringsRewriter : public SequencesRewriter */ static Node rewriteStrConvert(Node n); + /** rewrite string less than + * + * This is the entry point for post-rewriting terms n of the form + * str.<( t, s ) + * Returns the rewritten form of n. + */ + static Node rewriteStringLt(Node n); + /** rewrite string less than or equal * * This is the entry point for post-rewriting terms n of the form @@ -79,6 +87,14 @@ class StringsRewriter : public SequencesRewriter * Returns the rewritten form of n. */ static Node rewriteStringToCode(Node n); + + /** rewrite is digit + * + * This is the entry point for post-rewriting terms n of the form + * str.is_digit( t ) + * Returns the rewritten form of n. + */ + static Node rewriteStringIsDigit(Node n); }; } // namespace strings diff --git a/src/util/regexp.cpp b/src/util/regexp.cpp index ca6547134..7051b251f 100644 --- a/src/util/regexp.cpp +++ b/src/util/regexp.cpp @@ -18,21 +18,25 @@ namespace CVC4 { -RegExpRepeat::RegExpRepeat(uint32_t repeatAmount) : d_repeatAmount(repeatAmount) {} +RegExpRepeat::RegExpRepeat(uint32_t repeatAmount) : d_repeatAmount(repeatAmount) +{ +} bool RegExpRepeat::operator==(const RegExpRepeat& r) const { return d_repeatAmount == r.d_repeatAmount; } -RegExpLoop::RegExpLoop(uint32_t l, uint32_t h) : d_loopMinOcc(l), d_loopMaxOcc(h) {} +RegExpLoop::RegExpLoop(uint32_t l, uint32_t h) + : d_loopMinOcc(l), d_loopMaxOcc(h) +{ +} bool RegExpLoop::operator==(const RegExpLoop& r) const { - return d_loopMinOcc == r.d_loopMinOcc - && d_loopMaxOcc == r.d_loopMaxOcc; + return d_loopMinOcc == r.d_loopMinOcc && d_loopMaxOcc == r.d_loopMaxOcc; } - + size_t RegExpRepeatHashFunction::operator()(const RegExpRepeat& r) const { return r.d_repeatAmount; @@ -54,4 +58,3 @@ std::ostream& operator<<(std::ostream& os, const RegExpLoop& r) } } // namespace CVC4 - -- cgit v1.2.3 From 501894d709c19aebcaed1bd43e506501a8bbd69b Mon Sep 17 00:00:00 2001 From: Andrew Reynolds Date: Tue, 31 Mar 2020 08:39:21 -0500 Subject: Fixing regressions (#4189) An option was recently deleted, forgot to disable it from a regression. Fixes a failure in regress1. --- test/regress/regress2/sygus/issue4022-conjecture-gen.smt2 | 1 - 1 file changed, 1 deletion(-) diff --git a/test/regress/regress2/sygus/issue4022-conjecture-gen.smt2 b/test/regress/regress2/sygus/issue4022-conjecture-gen.smt2 index 9c3fe7ac5..471cc519b 100644 --- a/test/regress/regress2/sygus/issue4022-conjecture-gen.smt2 +++ b/test/regress/regress2/sygus/issue4022-conjecture-gen.smt2 @@ -2,7 +2,6 @@ (set-option :conjecture-filter-model true) (set-option :conjecture-gen true) (set-option :conjecture-no-filter true) -(set-option :dt-ref-sk-intro true) (set-option :quant-ind true) (set-option :sygus-inference true) (set-info :status sat) -- cgit v1.2.3 From 5726447e3864c7d2289b458b2d2c5f31b5933a81 Mon Sep 17 00:00:00 2001 From: Andrew Reynolds Date: Tue, 31 Mar 2020 10:07:29 -0500 Subject: Convert more uses of string-specific functions (#4158) Towards theory of sequences. --- src/theory/strings/sequences_rewriter.cpp | 87 +++++++++++------------- src/theory/strings/sequences_rewriter.h | 9 +-- src/theory/strings/theory_strings_preprocess.cpp | 25 ++++--- src/theory/strings/theory_strings_preprocess.h | 1 - src/theory/strings/word.cpp | 23 +++++++ src/theory/strings/word.h | 7 ++ 6 files changed, 91 insertions(+), 61 deletions(-) diff --git a/src/theory/strings/sequences_rewriter.cpp b/src/theory/strings/sequences_rewriter.cpp index d7ee459c7..be1e13459 100644 --- a/src/theory/strings/sequences_rewriter.cpp +++ b/src/theory/strings/sequences_rewriter.cpp @@ -479,13 +479,12 @@ Node SequencesRewriter::rewriteStrEqualityExt(Node node) { Assert(cn.isConst()); Assert(Word::getLength(cn) == 1); - unsigned hchar = cn.getConst().front(); // The operands of the concat on each side of the equality without // constant strings std::vector trimmed[2]; - // Counts the number of `hchar`s on each side - size_t numHChars[2] = {0, 0}; + // Counts the number of `cn`s on each side + size_t numCns[2] = {0, 0}; for (size_t j = 0; j < 2; j++) { // Sort the operands of the concats on both sides of the equality @@ -496,12 +495,12 @@ Node SequencesRewriter::rewriteStrEqualityExt(Node node) { if (cc.isConst()) { - // Count the number of `hchar`s in the string constant and make - // sure that all chars are `hchar`s - std::vector veccc = cc.getConst().getVec(); - for (size_t k = 0, size = veccc.size(); k < size; k++) + // Count the number of `cn`s in the string constant and make + // sure that all chars are `cn`s + std::vector veccc = Word::getChars(cc); + for (const Node& cv : veccc) { - if (veccc[k] != hchar) + if (cv != cn) { // This conflict case should mostly should be taken care of by // multiset reasoning in the strings rewriter, but we recognize @@ -510,7 +509,7 @@ Node SequencesRewriter::rewriteStrEqualityExt(Node node) return returnRewrite( node, new_ret, Rewrite::STR_EQ_CONST_NHOMOG); } - numHChars[j]++; + numCns[j]++; } } else @@ -520,18 +519,18 @@ Node SequencesRewriter::rewriteStrEqualityExt(Node node) } } - // We have to remove the same number of `hchar`s from both sides, so the - // side with less `hchar`s determines how many we can remove - size_t trimmedConst = std::min(numHChars[0], numHChars[1]); + // We have to remove the same number of `cn`s from both sides, so the + // side with less `cn`s determines how many we can remove + size_t trimmedConst = std::min(numCns[0], numCns[1]); for (size_t j = 0; j < 2; j++) { - size_t diff = numHChars[j] - trimmedConst; + size_t diff = numCns[j] - trimmedConst; if (diff != 0) { - // Add a constant string to the side with more `hchar`s to restore - // the difference in number of `hchar`s - std::vector vec(diff, hchar); - trimmed[j].push_back(nm->mkConst(String(vec))); + // Add a constant string to the side with more `cn`s to restore + // the difference in number of `cn`s + std::vector vec(diff, cn); + trimmed[j].push_back(Word::mkWord(vec)); } } @@ -2332,7 +2331,6 @@ Node SequencesRewriter::rewriteContains(Node node) } if (node[0].isConst()) { - CVC4::String s = node[0].getConst(); if (node[1].isConst()) { Node ret = nm->mkConst(Word::find(node[0], node[1]) != std::string::npos); @@ -2356,14 +2354,13 @@ Node SequencesRewriter::rewriteContains(Node node) } else if (checkEntailLengthOne(t)) { - const std::vector& vec = s.getVec(); - + std::vector vec = Word::getChars(node[0]); + Node emp = Word::mkEmptyWord(t.getType()); NodeBuilder<> nb(OR); - nb << nm->mkConst(String("")).eqNode(t); - for (unsigned c : vec) + nb << emp.eqNode(t); + for (const Node& c : vec) { - std::vector sv = {c}; - nb << nm->mkConst(String(sv)).eqNode(t); + nb << c.eqNode(t); } // str.contains("ABCabc", t) ---> @@ -4232,7 +4229,7 @@ bool SequencesRewriter::stripConstantEndpoints(std::vector& n1, return changed; } -Node SequencesRewriter::canonicalStrForSymbolicLength(Node len) +Node SequencesRewriter::canonicalStrForSymbolicLength(Node len, TypeNode stype) { NodeManager* nm = NodeManager::currentNM(); @@ -4243,7 +4240,15 @@ Node SequencesRewriter::canonicalStrForSymbolicLength(Node len) Rational ratLen = len.getConst(); Assert(ratLen.getDenominator() == 1); Integer intLen = ratLen.getNumerator(); - res = nm->mkConst(String(std::string(intLen.getUnsignedInt(), 'A'))); + uint32_t u = intLen.getUnsignedInt(); + if (stype.isString()) + { + res = nm->mkConst(String(std::string(u, 'A'))); + } + else + { + Unimplemented() << "canonicalStrForSymbolicLength for non-string"; + } } else if (len.getKind() == kind::PLUS) { @@ -4251,7 +4256,7 @@ Node SequencesRewriter::canonicalStrForSymbolicLength(Node len) NodeBuilder<> concatBuilder(kind::STRING_CONCAT); for (const auto& n : len) { - Node sn = canonicalStrForSymbolicLength(n); + Node sn = canonicalStrForSymbolicLength(n, stype); if (sn.isNull()) { return Node::null(); @@ -4270,7 +4275,7 @@ Node SequencesRewriter::canonicalStrForSymbolicLength(Node len) Assert(ratReps.getDenominator() == 1); Integer intReps = ratReps.getNumerator(); - Node nRep = canonicalStrForSymbolicLength(len[1]); + Node nRep = canonicalStrForSymbolicLength(len[1], stype); std::vector nRepChildren; utils::getConcat(nRep, nRepChildren); NodeBuilder<> concatBuilder(kind::STRING_CONCAT); @@ -4292,7 +4297,7 @@ Node SequencesRewriter::lengthPreserveRewrite(Node n) { NodeManager* nm = NodeManager::currentNM(); Node len = Rewriter::rewrite(nm->mkNode(kind::STRING_LENGTH, n)); - Node res = canonicalStrForSymbolicLength(len); + Node res = canonicalStrForSymbolicLength(len, n.getType()); return res.isNull() ? n : res; } @@ -4869,8 +4874,6 @@ void SequencesRewriter::getArithApproximations(Node a, bool SequencesRewriter::checkEntailMultisetSubset(Node a, Node b) { - NodeManager* nm = NodeManager::currentNM(); - std::vector avec; utils::getConcat(getMultisetApproximation(a), avec); std::vector bvec; @@ -4913,14 +4916,9 @@ bool SequencesRewriter::checkEntailMultisetSubset(Node a, Node b) { Node cn = ncp.first; Assert(cn.isConst()); - std::vector cc_vec; - const std::vector& cvec = cn.getConst().getVec(); - for (unsigned i = 0, size = cvec.size(); i < size; i++) + std::vector cnChars = Word::getChars(cn); + for (const Node& ch : cnChars) { - // make the character - cc_vec.clear(); - cc_vec.insert(cc_vec.end(), cvec.begin() + i, cvec.begin() + i + 1); - Node ch = nm->mkConst(String(cc_vec)); count_const[j][ch] += ncp.second; if (std::find(chars.begin(), chars.end(), ch) == chars.end()) { @@ -4955,19 +4953,17 @@ bool SequencesRewriter::checkEntailMultisetSubset(Node a, Node b) Node SequencesRewriter::checkEntailHomogeneousString(Node a) { - NodeManager* nm = NodeManager::currentNM(); - std::vector avec; utils::getConcat(getMultisetApproximation(a), avec); bool cValid = false; - unsigned c = 0; + Node c; for (const Node& ac : avec) { if (ac.isConst()) { - std::vector acv = ac.getConst().getVec(); - for (unsigned cc : acv) + std::vector acv = Word::getChars(ac); + for (const Node& cc : acv) { if (!cValid) { @@ -4990,11 +4986,10 @@ Node SequencesRewriter::checkEntailHomogeneousString(Node a) if (!cValid) { - return nm->mkConst(String("")); + return Word::mkEmptyWord(a.getType()); } - std::vector cv = {c}; - return nm->mkConst(String(cv)); + return c; } Node SequencesRewriter::getMultisetApproximation(Node a) diff --git a/src/theory/strings/sequences_rewriter.h b/src/theory/strings/sequences_rewriter.h index afdd2c0e1..0e5cd5705 100644 --- a/src/theory/strings/sequences_rewriter.h +++ b/src/theory/strings/sequences_rewriter.h @@ -512,11 +512,12 @@ class SequencesRewriter : public TheoryRewriter int dir = 0); /** - * Given a symbolic length n, returns the canonical string for that length. - * For example if n is constant, this function returns a string consisting of - * "A" repeated n times. Returns the null node if no such string exists. + * Given a symbolic length n, returns the canonical string (of type stype) + * for that length. For example if n is constant, this function returns a + * string consisting of "A" repeated n times. Returns the null node if no such + * string exists. */ - static Node canonicalStrForSymbolicLength(Node n); + static Node canonicalStrForSymbolicLength(Node n, TypeNode stype); /** length preserving rewrite * diff --git a/src/theory/strings/theory_strings_preprocess.cpp b/src/theory/strings/theory_strings_preprocess.cpp index b35c4a921..7777b9bd7 100644 --- a/src/theory/strings/theory_strings_preprocess.cpp +++ b/src/theory/strings/theory_strings_preprocess.cpp @@ -23,6 +23,7 @@ #include "proof/proof_manager.h" #include "smt/logic_exception.h" #include "theory/strings/sequences_rewriter.h" +#include "theory/strings/word.h" using namespace CVC4; using namespace CVC4::kind; @@ -40,7 +41,6 @@ StringsPreprocess::StringsPreprocess(SkolemCache* sc, d_zero = NodeManager::currentNM()->mkConst(Rational(0)); d_one = NodeManager::currentNM()->mkConst(Rational(1)); d_neg_one = NodeManager::currentNM()->mkConst(Rational(-1)); - d_empty_str = NodeManager::currentNM()->mkConst(String("")); } StringsPreprocess::~StringsPreprocess(){ @@ -70,11 +70,13 @@ Node StringsPreprocess::simplify( Node t, std::vector< Node > &new_nodes ) { Node c3 = nm->mkNode(GT, m, d_zero); Node cond = nm->mkNode(AND, c1, c2, c3); - Node sk1 = n == d_zero ? d_empty_str + Node emp = Word::mkEmptyWord(t.getType()); + + Node sk1 = n == d_zero ? emp : d_sc->mkSkolemCached( s, n, SkolemCache::SK_PREFIX, "sspre"); Node sk2 = SequencesRewriter::checkEntailArith(t12, lt0) - ? d_empty_str + ? emp : d_sc->mkSkolemCached( s, t12, SkolemCache::SK_SUFFIX_REM, "sssufr"); Node b11 = s.eqNode(nm->mkNode(STRING_CONCAT, sk1, skt, sk2)); @@ -91,7 +93,7 @@ Node StringsPreprocess::simplify( Node t, std::vector< Node > &new_nodes ) { Node b14 = nm->mkNode(LEQ, nm->mkNode(STRING_LENGTH, skt), m); Node b1 = nm->mkNode(AND, b11, b12, b13, b14); - Node b2 = skt.eqNode(d_empty_str); + Node b2 = skt.eqNode(emp); Node lemma = nm->mkNode(ITE, cond, b1, b2); // assert: @@ -151,7 +153,8 @@ Node StringsPreprocess::simplify( Node t, std::vector< Node > &new_nodes ) { Node cc1 = skk.eqNode(negone); // y = "" - Node cond2 = y.eqNode(d_empty_str); + Node emp = Word::mkEmptyWord(x.getType()); + Node cond2 = y.eqNode(emp); // skk = n Node cc2 = skk.eqNode(t[2]); @@ -239,8 +242,8 @@ Node StringsPreprocess::simplify( Node t, std::vector< Node > &new_nodes ) { Node nonneg = nm->mkNode(GEQ, n, d_zero); - lem = nm->mkNode( - ITE, nonneg, nm->mkNode(AND, conc), itost.eqNode(d_empty_str)); + Node emp = Word::mkEmptyWord(t.getType()); + lem = nm->mkNode(ITE, nonneg, nm->mkNode(AND, conc), itost.eqNode(emp)); new_nodes.push_back(lem); // assert: // IF n>=0 @@ -277,7 +280,8 @@ Node StringsPreprocess::simplify( Node t, std::vector< Node > &new_nodes ) { Node lem = stoit.eqNode(d_neg_one); conc1.push_back(lem); - Node sEmpty = s.eqNode(d_empty_str); + Node emp = Word::mkEmptyWord(s.getType()); + Node sEmpty = s.eqNode(emp); Node k = nm->mkSkolem("k", nm->integerType()); Node kc1 = nm->mkNode(GEQ, k, d_zero); Node kc2 = nm->mkNode(LT, k, lens); @@ -478,8 +482,9 @@ Node StringsPreprocess::simplify( Node t, std::vector< Node > &new_nodes ) { // the index to begin searching in x for y after the i^th occurrence of y in // x, and Us( i ) is the result of processing the remainder after processing // the i^th occurrence of y in x. - Node assert = nm->mkNode( - ITE, y.eqNode(d_empty_str), rpaw.eqNode(x), nm->mkNode(AND, lem)); + Node emp = Word::mkEmptyWord(t.getType()); + Node assert = + nm->mkNode(ITE, y.eqNode(emp), rpaw.eqNode(x), nm->mkNode(AND, lem)); new_nodes.push_back(assert); // Thus, replaceall( x, y, z ) = rpaw diff --git a/src/theory/strings/theory_strings_preprocess.h b/src/theory/strings/theory_strings_preprocess.h index 155b9014c..fb6404aa6 100644 --- a/src/theory/strings/theory_strings_preprocess.h +++ b/src/theory/strings/theory_strings_preprocess.h @@ -72,7 +72,6 @@ class StringsPreprocess { Node d_zero; Node d_one; Node d_neg_one; - Node d_empty_str; /** pointer to the skolem cache used by this class */ SkolemCache* d_sc; /** Reference to the statistics for the theory of strings/sequences. */ diff --git a/src/theory/strings/word.cpp b/src/theory/strings/word.cpp index 0faeffd99..b42cf3160 100644 --- a/src/theory/strings/word.cpp +++ b/src/theory/strings/word.cpp @@ -76,6 +76,29 @@ size_t Word::getLength(TNode x) return 0; } +std::vector Word::getChars(TNode x) +{ + Kind k = x.getKind(); + if (k == CONST_STRING) + { + std::vector ret; + NodeManager* nm = NodeManager::currentNM(); + std::vector ccVec; + const std::vector& cvec = x.getConst().getVec(); + for (unsigned chVal : cvec) + { + ccVec.clear(); + ccVec.push_back(chVal); + Node ch = nm->mkConst(String(ccVec)); + ret.push_back(ch); + } + return ret; + } + Unimplemented(); + std::vector ret; + return ret; +} + bool Word::isEmpty(TNode x) { return getLength(x) == 0; } bool Word::strncmp(TNode x, TNode y, std::size_t n) diff --git a/src/theory/strings/word.h b/src/theory/strings/word.h index 7b813a0b2..8e6e7876e 100644 --- a/src/theory/strings/word.h +++ b/src/theory/strings/word.h @@ -42,6 +42,13 @@ class Word /** Return the length of word x */ static size_t getLength(TNode x); + /** Get characters + * + * Given word x, this returns the vector of words of length one whose + * concatenation is equivalent to x. + */ + static std::vector getChars(TNode x); + /** Return true if x is empty */ static bool isEmpty(TNode x); -- cgit v1.2.3 From 5272f5d02f109b7dbfdb5088a1efbf7d13b64487 Mon Sep 17 00:00:00 2001 From: Andrew Reynolds Date: Tue, 31 Mar 2020 11:49:02 -0500 Subject: Fix strange bound regression (#4192) Several things have happened with this regression lately, in chronological order: (1) Instantiations involving bounded set quantifiers were changed to use choice to represent symbolic instantiations, (2) fmf-bound was decoupled from finite-model-find (the latter is not enabled when the former is), (3) choice was set to be an "unevaluated" kind (in 0060de3). After (1) and (2), for the regression test/regress/regress1/fmf/fmf-strange-bounds.smt2, CVC4 was answering "sat" correctly but internally there was a source of incompleteness. In particular, a choice term was being generated in an instantiation that was later incorrectly evaluated, thus allowing CVC4 to skip an instantiation it shouldn't have. The recent commit of (3) resolved this issue, making it so that choice is not an evaluated kind. This meant the benchmark went "sat" -> "unknown". This PR fixes this issue by enabling --finite-model-find, which is now necessary to answer "sat". It also adds a further test quantifier that was used in debugging this issue. Fixes regress1. --- test/regress/regress1/fmf/fmf-strange-bounds.smt2 | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/test/regress/regress1/fmf/fmf-strange-bounds.smt2 b/test/regress/regress1/fmf/fmf-strange-bounds.smt2 index 7812c2431..bdbca4bd0 100644 --- a/test/regress/regress1/fmf/fmf-strange-bounds.smt2 +++ b/test/regress/regress1/fmf/fmf-strange-bounds.smt2 @@ -1,4 +1,4 @@ -; COMMAND-LINE: --fmf-bound +; COMMAND-LINE: --fmf-bound --finite-model-find ; EXPECT: sat (set-logic ALL) (set-info :status sat) @@ -23,6 +23,12 @@ (=> (and (<= 0 y) (<= y (h z))) (P x y z)))))) +(assert (forall ((x Int) (y Int) (z U)) (=> +(or (= x 5) (= x 6)) +(=> (and (<= 0 y) (<= y x)) +(P x y z))))) + + (declare-fun Q (U Int) Bool) (declare-const a U) -- cgit v1.2.3 From 63f887783e003546bf8de4501774a79dbcf8d4b0 Mon Sep 17 00:00:00 2001 From: Andrew Reynolds Date: Tue, 31 Mar 2020 14:27:04 -0500 Subject: Remove replay and use-theory options and idl (#4186) Towards disentangling Options / NodeManager / SmtEngine. This PR removes options --use-theory=NAME and --replay/--replay-log. Both of these options are highly complex, unused, and lead to complications when implementing the way options and our build system work. The first is motivated by making TheoryEngine use an "alternate" theory, which appears to e.g. make it so that TheoryIdl could entirely replace TheoryArith. I believe this is too heavy handed of a solution: there should a consistent TheoryArith class, and options should be used to enable/disable alternate modules within it. The second attempts to replay low level decisions from the SAT solver. It is documented as not working (in 1.0). I do not believe this is worth salvaging. It also removes the solver in src/theory/idl, which cannot be enabled after this commit. --- CMakeLists.txt | 6 - cmake/ConfigCompetition.cmake | 2 - cmake/ConfigDebug.cmake | 2 - cmake/ConfigProduction.cmake | 2 - cmake/ConfigTesting.cmake | 2 - configure.sh | 7 - src/CMakeLists.txt | 12 +- src/base/configuration.cpp | 4 - src/base/configuration.h | 2 - src/base/configuration_private.h | 6 - src/bindings/java/CMakeLists.txt | 1 - src/cvc4.i | 1 - src/expr/CMakeLists.txt | 1 - src/expr/expr_stream.h | 45 ------- src/expr/expr_stream.i | 5 - src/expr/metakind_template.h | 14 -- src/expr/mkmetakind | 16 --- src/main/command_executor.cpp | 9 +- src/main/command_executor.h | 7 - src/main/driver_unified.cpp | 29 ----- src/options/CMakeLists.txt | 1 - src/options/idl_options.toml | 11 -- src/options/options.h | 33 ----- src/options/options_handler.cpp | 32 ----- src/options/options_handler.h | 6 - src/options/options_public_functions.cpp | 4 - src/options/options_template.cpp | 15 --- src/options/smt_options.toml | 21 --- src/options/theory_options.toml | 11 -- src/parser/cvc/Cvc.g | 2 - src/parser/parser.h | 55 -------- src/prop/minisat/core/Solver.cc | 13 -- src/prop/prop_engine.cpp | 20 +-- src/prop/prop_engine.h | 5 +- src/prop/theory_proxy.cpp | 35 +---- src/prop/theory_proxy.h | 20 +-- src/smt/managed_ostreams.cpp | 27 ---- src/smt/managed_ostreams.h | 21 --- src/smt/smt_engine.cpp | 76 +---------- src/smt/smt_engine.h | 11 -- src/theory/idl/idl_assertion.cpp | 213 ------------------------------- src/theory/idl/idl_assertion.h | 91 ------------- src/theory/idl/idl_assertion_db.cpp | 59 --------- src/theory/idl/idl_assertion_db.h | 86 ------------- src/theory/idl/idl_model.cpp | 74 ----------- src/theory/idl/idl_model.h | 84 ------------ src/theory/idl/kinds | 8 -- src/theory/idl/theory_idl.cpp | 156 ---------------------- src/theory/idl/theory_idl.h | 63 --------- src/theory/mktheorytraits | 6 - src/theory/theory_engine.cpp | 13 -- src/theory/theory_engine.h | 7 - 52 files changed, 13 insertions(+), 1439 deletions(-) delete mode 100644 src/expr/expr_stream.h delete mode 100644 src/expr/expr_stream.i delete mode 100644 src/options/idl_options.toml delete mode 100644 src/theory/idl/idl_assertion.cpp delete mode 100644 src/theory/idl/idl_assertion.h delete mode 100644 src/theory/idl/idl_assertion_db.cpp delete mode 100644 src/theory/idl/idl_assertion_db.h delete mode 100644 src/theory/idl/idl_model.cpp delete mode 100644 src/theory/idl/idl_model.h delete mode 100644 src/theory/idl/kinds delete mode 100644 src/theory/idl/theory_idl.cpp delete mode 100644 src/theory/idl/theory_idl.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 945f71d36..c535890e1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -121,7 +121,6 @@ cvc4_option(ENABLE_DEBUG_SYMBOLS "Enable debug symbols") cvc4_option(ENABLE_DUMPING "Enable dumping") cvc4_option(ENABLE_MUZZLE "Suppress ALL non-result output") cvc4_option(ENABLE_PROOFS "Enable proof support") -cvc4_option(ENABLE_REPLAY "Enable the replay feature") cvc4_option(ENABLE_STATISTICS "Enable statistics") cvc4_option(ENABLE_TRACING "Enable tracing") cvc4_option(ENABLE_UNIT_TESTING "Enable unit testing") @@ -429,10 +428,6 @@ if(ENABLE_PROOFS) add_definitions(-DCVC4_PROOF) endif() -if(ENABLE_REPLAY) - add_definitions(-DCVC4_REPLAY) -endif() - if(ENABLE_TRACING) add_definitions(-DCVC4_TRACING) endif() @@ -608,7 +603,6 @@ message("") print_config("Dumping :" ENABLE_DUMPING) print_config("Muzzle :" ENABLE_MUZZLE) print_config("Proofs :" ENABLE_PROOFS) -print_config("Replay :" ENABLE_REPLAY) print_config("Statistics :" ENABLE_STATISTICS) print_config("Tracing :" ENABLE_TRACING) message("") diff --git a/cmake/ConfigCompetition.cmake b/cmake/ConfigCompetition.cmake index 6bd846d0c..e18d2b2f1 100644 --- a/cmake/ConfigCompetition.cmake +++ b/cmake/ConfigCompetition.cmake @@ -8,8 +8,6 @@ set(OPTIMIZATION_LEVEL 9) cvc4_set_option(ENABLE_DEBUG_SYMBOLS OFF) # enable_statistics=no cvc4_set_option(ENABLE_STATISTICS OFF) -# enable_replay=no -cvc4_set_option(ENABLE_REPLAY OFF) # enable_assertions=no cvc4_set_option(ENABLE_ASSERTIONS OFF) # enable_proof=no diff --git a/cmake/ConfigDebug.cmake b/cmake/ConfigDebug.cmake index 31b142ffc..1ee78a602 100644 --- a/cmake/ConfigDebug.cmake +++ b/cmake/ConfigDebug.cmake @@ -7,8 +7,6 @@ add_c_cxx_flag("-Og") cvc4_set_option(ENABLE_DEBUG_SYMBOLS ON) # enable_statistics=yes cvc4_set_option(ENABLE_STATISTICS ON) -# enable_replay=yes -cvc4_set_option(ENABLE_REPLAY ON) # enable_assertions=yes cvc4_set_option(ENABLE_ASSERTIONS ON) # enable_proof=yes diff --git a/cmake/ConfigProduction.cmake b/cmake/ConfigProduction.cmake index 49e338abf..503f5d58f 100644 --- a/cmake/ConfigProduction.cmake +++ b/cmake/ConfigProduction.cmake @@ -4,8 +4,6 @@ set(OPTIMIZATION_LEVEL 3) cvc4_set_option(ENABLE_DEBUG_SYMBOLS OFF) # enable_statistics=yes cvc4_set_option(ENABLE_STATISTICS ON) -# enable_replay=no -cvc4_set_option(ENABLE_REPLAY OFF) # enable_assertions=no cvc4_set_option(ENABLE_ASSERTIONS OFF) # enable_proof=yes diff --git a/cmake/ConfigTesting.cmake b/cmake/ConfigTesting.cmake index 40366495d..cdc9e3af8 100644 --- a/cmake/ConfigTesting.cmake +++ b/cmake/ConfigTesting.cmake @@ -4,8 +4,6 @@ set(OPTIMIZATION_LEVEL 2) cvc4_set_option(ENABLE_DEBUG_SYMBOLS ON) # enable_statistics=yes cvc4_set_option(ENABLE_STATISTICS ON) -# enable_replay=yes -cvc4_set_option(ENABLE_REPLAY ON) # enable_assertions=yes cvc4_set_option(ENABLE_ASSERTIONS ON) # enable_proof=yes diff --git a/configure.sh b/configure.sh index ae9b275aa..bd95e38ed 100755 --- a/configure.sh +++ b/configure.sh @@ -34,7 +34,6 @@ The following flags enable optional features (disable with --no-