From ba659fb1b8bc2f110616ec113892f63f210a5ebb Mon Sep 17 00:00:00 2001 From: Andrew Reynolds Date: Sat, 30 May 2020 13:10:54 -0500 Subject: Add the sequence type (#4539) This adds basic infrastructure for the sequence type, including its type rules, type enumerator and extensions to type functions. The next step will be to finalize the support in TheoryStrings for sequences and then add front end support for sequences in the API/parsers. --- src/bindings/java/CMakeLists.txt | 1 + src/expr/node_manager.cpp | 11 ++++ src/expr/node_manager.h | 5 +- src/expr/type.cpp | 16 +++++ src/expr/type.h | 25 ++++++-- src/expr/type_node.cpp | 12 +++- src/expr/type_node.h | 10 ++++ src/theory/strings/kinds | 20 +++++++ src/theory/strings/rewrites.cpp | 1 + src/theory/strings/rewrites.h | 3 +- src/theory/strings/sequences_rewriter.cpp | 17 ++++++ src/theory/strings/sequences_rewriter.h | 6 ++ src/theory/strings/theory_strings_type_rules.h | 50 ++++++++++++++++ src/theory/strings/type_enumerator.cpp | 83 ++++++++++++++++++++++++++ src/theory/strings/type_enumerator.h | 43 +++++++++++++ 15 files changed, 296 insertions(+), 7 deletions(-) (limited to 'src') diff --git a/src/bindings/java/CMakeLists.txt b/src/bindings/java/CMakeLists.txt index 2cecf15e9..4e1b96af9 100644 --- a/src/bindings/java/CMakeLists.txt +++ b/src/bindings/java/CMakeLists.txt @@ -138,6 +138,7 @@ set(gen_java_files ${CMAKE_CURRENT_BINARY_DIR}/SWIGTYPE_p_std__vectorT_std__pairT_CVC4__Expr_CVC4__Expr_t_t.java ${CMAKE_CURRENT_BINARY_DIR}/SWIGTYPE_p_std__vectorT_std__vectorT_std__string_t_t.java ${CMAKE_CURRENT_BINARY_DIR}/SelectorType.java + ${CMAKE_CURRENT_BINARY_DIR}/SequenceType.java ${CMAKE_CURRENT_BINARY_DIR}/SetType.java ${CMAKE_CURRENT_BINARY_DIR}/SmtEngine.java ${CMAKE_CURRENT_BINARY_DIR}/SortConstructorType.java diff --git a/src/expr/node_manager.cpp b/src/expr/node_manager.cpp index feec9b782..7bc98f65d 100644 --- a/src/expr/node_manager.cpp +++ b/src/expr/node_manager.cpp @@ -495,6 +495,17 @@ Node NodeManager::mkSkolem(const std::string& prefix, const TypeNode& type, cons return n; } +TypeNode NodeManager::mkSequenceType(TypeNode elementType) +{ + CheckArgument( + !elementType.isNull(), elementType, "unexpected NULL element type"); + CheckArgument(elementType.isFirstClass(), + elementType, + "cannot store types that are not first-class in sequences. Try " + "option --uf-ho."); + return mkTypeNode(kind::SEQUENCE_TYPE, elementType); +} + TypeNode NodeManager::mkConstructorType(const DatatypeConstructor& constructor, TypeNode range) { vector sorts; diff --git a/src/expr/node_manager.h b/src/expr/node_manager.h index aea49d979..45d3d5b7d 100644 --- a/src/expr/node_manager.h +++ b/src/expr/node_manager.h @@ -875,9 +875,12 @@ public: /** Make the type of arrays with the given parameterization */ inline TypeNode mkArrayType(TypeNode indexType, TypeNode constituentType); - /** Make the type of arrays with the given parameterization */ + /** Make the type of set with the given parameterization */ inline TypeNode mkSetType(TypeNode elementType); + /** Make the type of sequences with the given parameterization */ + TypeNode mkSequenceType(TypeNode elementType); + /** Make a type representing a constructor with the given parameterization */ TypeNode mkConstructorType(const DatatypeConstructor& constructor, TypeNode range); /** diff --git a/src/expr/type.cpp b/src/expr/type.cpp index 031dcb3f0..2067beef5 100644 --- a/src/expr/type.cpp +++ b/src/expr/type.cpp @@ -353,6 +353,12 @@ bool Type::isSet() const { return d_typeNode->isSet(); } +bool Type::isSequence() const +{ + NodeManagerScope nms(d_nodeManager); + return d_typeNode->isSequence(); +} + /** Is this a sort kind */ bool Type::isSort() const { NodeManagerScope nms(d_nodeManager); @@ -516,6 +522,11 @@ SetType::SetType(const Type& t) : Type(t) PrettyCheckArgument(isNull() || isSet(), this); } +SequenceType::SequenceType(const Type& t) : Type(t) +{ + PrettyCheckArgument(isNull() || isSequence(), this); +} + SortType::SortType(const Type& t) : Type(t) { PrettyCheckArgument(isNull() || isSort(), this); @@ -550,6 +561,11 @@ Type SetType::getElementType() const { return makeType(d_typeNode->getSetElementType()); } +Type SequenceType::getElementType() const +{ + return makeType(d_typeNode->getSequenceElementType()); +} + DatatypeType ConstructorType::getRangeType() const { return DatatypeType(makeType(d_typeNode->getConstructorRangeType())); } diff --git a/src/expr/type.h b/src/expr/type.h index 529c40930..0cdf55626 100644 --- a/src/expr/type.h +++ b/src/expr/type.h @@ -373,7 +373,13 @@ protected: */ bool isSet() const; - /** + /** + * Is this a Sequence type? + * @return true if the type is a Sequence type + */ + bool isSequence() const; + + /** * Is this a datatype type? * @return true if the type is a datatype type */ @@ -515,15 +521,26 @@ class CVC4_PUBLIC ArrayType : public Type { Type getConstituentType() const; };/* class ArrayType */ -/** Class encapsulating an set type. */ +/** Class encapsulating a set type. */ class CVC4_PUBLIC SetType : public Type { public: /** Construct from the base type */ SetType(const Type& type = Type()); - /** Get the index type */ + /** Get the element type */ + Type getElementType() const; +}; /* class SetType */ + +/** Class encapsulating a sequence type. */ +class CVC4_PUBLIC SequenceType : public Type +{ + public: + /** Construct from the base type */ + SequenceType(const Type& type = Type()); + + /** Get the element type */ Type getElementType() const; -};/* class SetType */ +}; /* class SetType */ /** Class encapsulating a user-defined sort. */ class CVC4_PUBLIC SortType : public Type { diff --git a/src/expr/type_node.cpp b/src/expr/type_node.cpp index 110db6162..e191be0c2 100644 --- a/src/expr/type_node.cpp +++ b/src/expr/type_node.cpp @@ -122,7 +122,7 @@ bool TypeNode::isFiniteInternal(bool usortFinite) { ret = true; } - else if (isString() || isRegExp() || isReal()) + else if (isString() || isRegExp() || isSequence() || isReal()) { ret = false; } @@ -245,6 +245,10 @@ bool TypeNode::isClosedEnumerable() { ret = getSetElementType().isClosedEnumerable(); } + else if (isSequence()) + { + ret = getSequenceElementType().isClosedEnumerable(); + } else if (isDatatype()) { // avoid infinite loops: initially set to true @@ -353,6 +357,12 @@ bool TypeNode::isComparableTo(TypeNode t) const { return false; } +TypeNode TypeNode::getSequenceElementType() const +{ + Assert(isSequence()); + return (*this)[0]; +} + TypeNode TypeNode::getBaseType() const { TypeNode realt = NodeManager::currentNM()->realType(); if (isSubtypeOf(realt)) { diff --git a/src/expr/type_node.h b/src/expr/type_node.h index 70392fb01..c9771bd3d 100644 --- a/src/expr/type_node.h +++ b/src/expr/type_node.h @@ -518,6 +518,9 @@ public: /** Is this a Set type? */ bool isSet() const; + /** Is this a Sequence type? */ + bool isSequence() const; + /** Get the index type (for array types) */ TypeNode getArrayIndexType() const; @@ -536,6 +539,8 @@ public: /** Get the element type (for set types) */ TypeNode getSetElementType() const; + /** Get the element type (for sequence types) */ + TypeNode getSequenceElementType() const; /** * Is this a function type? Function-like things (e.g. datatype * selectors) that aren't actually functions are NOT considered @@ -964,6 +969,11 @@ inline bool TypeNode::isSet() const { return getKind() == kind::SET_TYPE; } +inline bool TypeNode::isSequence() const +{ + return getKind() == kind::SEQUENCE_TYPE; +} + inline TypeNode TypeNode::getSetElementType() const { Assert(isSet()); return (*this)[0]; diff --git a/src/theory/strings/kinds b/src/theory/strings/kinds index 27ffe5d26..800847ffe 100644 --- a/src/theory/strings/kinds +++ b/src/theory/strings/kinds @@ -58,12 +58,27 @@ constant CONST_STRING \ "util/string.h" \ "a string of characters" +# the type +operator SEQUENCE_TYPE 1 "seuence type, takes as parameter the type of the elements" +cardinality SEQUENCE_TYPE \ + "::CVC4::theory::strings::SequenceProperties::computeCardinality(%TYPE%)" \ + "theory/strings/theory_strings_type_rules.h" +well-founded SEQUENCE_TYPE \ + "::CVC4::theory::strings::SequenceProperties::isWellFounded(%TYPE%)" \ + "::CVC4::theory::strings::SequenceProperties::mkGroundTerm(%TYPE%)" \ + "theory/strings/theory_strings_type_rules.h" +enumerator SEQUENCE_TYPE \ + "::CVC4::theory::strings::SequenceEnumerator" \ + "theory/strings/type_enumerator.h" + constant CONST_SEQUENCE \ ::CVC4::ExprSequence \ ::CVC4::ExprSequenceHashFunction \ "expr/expr_sequence.h" \ "a sequence of characters" +operator SEQ_UNIT 1 "a sequence of length one" + # equal equal / less than / output operator STRING_TO_REGEXP 1 "convert string to regexp" operator REGEXP_CONCAT 2: "regexp concat" @@ -144,4 +159,9 @@ typerule STRING_FROM_CODE "SimpleTypeRule" typerule STRING_TOUPPER "SimpleTypeRule" typerule STRING_TOLOWER "SimpleTypeRule" +### sequence specific operators + +typerule CONST_SEQUENCE ::CVC4::theory::strings::ConstSequenceTypeRule +typerule SEQ_UNIT ::CVC4::theory::strings::SeqUnitTypeRule + endtheory diff --git a/src/theory/strings/rewrites.cpp b/src/theory/strings/rewrites.cpp index 2953a2b3c..a4055c4f9 100644 --- a/src/theory/strings/rewrites.cpp +++ b/src/theory/strings/rewrites.cpp @@ -200,6 +200,7 @@ const char* toString(Rewrite r) 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"; + case Rewrite::SEQ_UNIT_EVAL: return "SEQ_UNIT_EVAL"; default: return "?"; } } diff --git a/src/theory/strings/rewrites.h b/src/theory/strings/rewrites.h index 7a315ebd3..96a3b65fd 100644 --- a/src/theory/strings/rewrites.h +++ b/src/theory/strings/rewrites.h @@ -202,7 +202,8 @@ enum class Rewrite : uint32_t LEN_CONCAT, LEN_REPL_INV, LEN_CONV_INV, - CHARAT_ELIM + CHARAT_ELIM, + SEQ_UNIT_EVAL }; /** diff --git a/src/theory/strings/sequences_rewriter.cpp b/src/theory/strings/sequences_rewriter.cpp index 2d2ec0af0..55d58b860 100644 --- a/src/theory/strings/sequences_rewriter.cpp +++ b/src/theory/strings/sequences_rewriter.cpp @@ -1461,6 +1461,10 @@ RewriteResponse SequencesRewriter::postRewrite(TNode node) { retNode = rewriteRepeatRegExp(node); } + else if (nk == SEQ_UNIT) + { + retNode = rewriteSeqUnit(node); + } Trace("sequences-postrewrite") << "Strings::SequencesRewriter::postRewrite returning " << retNode @@ -3095,6 +3099,19 @@ Node SequencesRewriter::canonicalStrForSymbolicLength(Node len, TypeNode stype) return res; } +Node SequencesRewriter::rewriteSeqUnit(Node node) +{ + NodeManager* nm = NodeManager::currentNM(); + if (node[0].isConst()) + { + std::vector seq; + seq.push_back(node[0].toExpr()); + TypeNode stype = nm->mkSequenceType(node[0].getType()); + Node ret = nm->mkConst(ExprSequence(stype.toType(), seq)); + return returnRewrite(node, ret, Rewrite::SEQ_UNIT_EVAL); + } + return node; +} Node SequencesRewriter::returnRewrite(Node node, Node ret, Rewrite r) { diff --git a/src/theory/strings/sequences_rewriter.h b/src/theory/strings/sequences_rewriter.h index 56b74f536..490dd8b3c 100644 --- a/src/theory/strings/sequences_rewriter.h +++ b/src/theory/strings/sequences_rewriter.h @@ -224,6 +224,12 @@ class SequencesRewriter : public TheoryRewriter * Returns the rewritten form of node. */ Node rewriteStringToCode(Node node); + /** rewrite seq.unit + * This is the entry point for post-rewriting terms n of the form + * seq.unit( t ) + * Returns the rewritten form of node. + */ + Node rewriteSeqUnit(Node node); /** length preserving rewrite * diff --git a/src/theory/strings/theory_strings_type_rules.h b/src/theory/strings/theory_strings_type_rules.h index 93a32f26e..3229e6631 100644 --- a/src/theory/strings/theory_strings_type_rules.h +++ b/src/theory/strings/theory_strings_type_rules.h @@ -20,6 +20,9 @@ #ifndef CVC4__THEORY__STRINGS__THEORY_STRINGS_TYPE_RULES_H #define CVC4__THEORY__STRINGS__THEORY_STRINGS_TYPE_RULES_H +#include "expr/expr_sequence.h" +#include "expr/sequence.h" + namespace CVC4 { namespace theory { namespace strings { @@ -318,6 +321,53 @@ public: } }; +class ConstSequenceTypeRule +{ + public: + static TypeNode computeType(NodeManager* nodeManager, + TNode n, + bool check) + { + Assert(n.getKind() == kind::CONST_SEQUENCE); + return n.getConst().getSequence().getType(); + } +}; + +class SeqUnitTypeRule +{ + public: + static TypeNode computeType(NodeManager* nodeManager, + TNode n, + bool check) + { + return nodeManager->mkSequenceType(n[0].getType(check)); + } +}; + +/** Properties of the sequence type */ +struct SequenceProperties +{ + static Cardinality computeCardinality(TypeNode type) + { + Assert(type.getKind() == kind::SEQUENCE_TYPE); + return Cardinality::INTEGERS; + } + /** A sequence is well-founded if its element type is */ + static bool isWellFounded(TypeNode type) + { + return type[0].isWellFounded(); + } + /** Make ground term for sequence type (return the empty sequence) */ + static Node mkGroundTerm(TypeNode type) + { + Assert(type.isSequence()); + // empty sequence + std::vector seq; + return NodeManager::currentNM()->mkConst( + ExprSequence(SequenceType(type.toType()), seq)); + } +}; /* struct SequenceProperties */ + }/* CVC4::theory::strings namespace */ }/* CVC4::theory namespace */ }/* CVC4 namespace */ diff --git a/src/theory/strings/type_enumerator.cpp b/src/theory/strings/type_enumerator.cpp index d24206860..c25363e65 100644 --- a/src/theory/strings/type_enumerator.cpp +++ b/src/theory/strings/type_enumerator.cpp @@ -158,6 +158,67 @@ void StringEnumLen::mkCurr() d_curr = makeStandardModelConstant(d_witer->getData(), d_cardinality); } +SeqEnumLen::SeqEnumLen(TypeNode tn, + TypeEnumeratorProperties* tep, + uint32_t startLength) + : SEnumLen(tn, startLength) +{ + d_elementEnumerator.reset( + new TypeEnumerator(d_type.getSequenceElementType(), tep)); + mkCurr(); +} + +SeqEnumLen::SeqEnumLen(TypeNode tn, + TypeEnumeratorProperties* tep, + uint32_t startLength, + uint32_t endLength) + : SEnumLen(tn, startLength, endLength) +{ + d_elementEnumerator.reset( + new TypeEnumerator(d_type.getSequenceElementType(), tep)); + mkCurr(); +} + +SeqEnumLen::SeqEnumLen(const SeqEnumLen& wenum) + : SEnumLen(wenum), + d_elementEnumerator(new TypeEnumerator(*wenum.d_elementEnumerator)), + d_elementDomain(wenum.d_elementDomain) +{ +} + +bool SeqEnumLen::increment() +{ + if (!d_elementEnumerator->isFinished()) + { + // yet to establish domain + Assert(d_elementEnumerator != nullptr); + d_elementDomain.push_back((**d_elementEnumerator).toExpr()); + ++(*d_elementEnumerator); + } + // the current cardinality is the domain size of the element + if (!d_witer->increment(d_elementDomain.size())) + { + Assert(d_elementEnumerator->isFinished()); + d_curr = Node::null(); + return false; + } + mkCurr(); + return true; +} + +void SeqEnumLen::mkCurr() +{ + std::vector seq; + const std::vector& data = d_witer->getData(); + for (unsigned i : data) + { + seq.push_back(d_elementDomain[i]); + } + // make sequence from seq + d_curr = + NodeManager::currentNM()->mkConst(ExprSequence(d_type.toType(), seq)); +} + StringEnumerator::StringEnumerator(TypeNode type, TypeEnumeratorProperties* tep) : TypeEnumeratorBase(type), d_wenum(0, utils::getAlphabetCardinality()) @@ -182,6 +243,28 @@ StringEnumerator& StringEnumerator::operator++() bool StringEnumerator::isFinished() { return d_wenum.isFinished(); } +SequenceEnumerator::SequenceEnumerator(TypeNode type, + TypeEnumeratorProperties* tep) + : TypeEnumeratorBase(type), d_wenum(type, tep, 0) +{ +} + +SequenceEnumerator::SequenceEnumerator(const SequenceEnumerator& enumerator) + : TypeEnumeratorBase(enumerator.getType()), + d_wenum(enumerator.d_wenum) +{ +} + +Node SequenceEnumerator::operator*() { return d_wenum.getCurrent(); } + +SequenceEnumerator& SequenceEnumerator::operator++() +{ + d_wenum.increment(); + return *this; +} + +bool SequenceEnumerator::isFinished() { return d_wenum.isFinished(); } + } // namespace strings } // namespace theory } // namespace CVC4 diff --git a/src/theory/strings/type_enumerator.h b/src/theory/strings/type_enumerator.h index b379ce5c3..c82892624 100644 --- a/src/theory/strings/type_enumerator.h +++ b/src/theory/strings/type_enumerator.h @@ -136,6 +136,34 @@ class StringEnumLen : public SEnumLen void mkCurr(); }; +/** + * Enumerates sequence values for a given length. + */ +class SeqEnumLen : public SEnumLen +{ + public: + /** For sequences */ + SeqEnumLen(TypeNode tn, TypeEnumeratorProperties* tep, uint32_t startLength); + SeqEnumLen(TypeNode tn, + TypeEnumeratorProperties* tep, + uint32_t startLength, + uint32_t endLength); + /** copy constructor */ + SeqEnumLen(const SeqEnumLen& wenum); + /** destructor */ + ~SeqEnumLen() {} + /** increment */ + bool increment() override; + + private: + /** an enumerator for the elements' type */ + std::unique_ptr d_elementEnumerator; + /** The domain */ + std::vector d_elementDomain; + /** Make the current term from d_data */ + void mkCurr(); +}; + class StringEnumerator : public TypeEnumeratorBase { public: @@ -154,6 +182,21 @@ class StringEnumerator : public TypeEnumeratorBase StringEnumLen d_wenum; }; /* class StringEnumerator */ +class SequenceEnumerator : public TypeEnumeratorBase +{ + public: + SequenceEnumerator(TypeNode type, TypeEnumeratorProperties* tep = nullptr); + SequenceEnumerator(const SequenceEnumerator& enumerator); + ~SequenceEnumerator() {} + Node operator*() override; + SequenceEnumerator& operator++() override; + bool isFinished() override; + + private: + /** underlying sequence enumerator */ + SeqEnumLen d_wenum; +}; /* class SequenceEnumerator */ + }/* CVC4::theory::strings namespace */ }/* CVC4::theory namespace */ }/* CVC4 namespace */ -- cgit v1.2.3 From 6e4a5a8c865469aebec9d070c8c1976fed68914a Mon Sep 17 00:00:00 2001 From: Andres Noetzli Date: Sun, 31 May 2020 09:13:39 -0700 Subject: Do not cache operator eliminations in arith (#4542) Fixes #4525. The actual problem in the issue is not that the unsat core is satisfiable but that our unsat core check is not working as intended. Our unsat core check uses the same `ExprManager` as the main `SmtEngine` and thus also shares the same attributes for nodes. However, since we have a different `SmtEngine` instance for the check, we also have different instances of `TheoryArith`. This leads to the check failing due to the following: - Our only input assertion is `(> (cot 0.0) (/ 1 0)))`. - `TheoryArith::expandDefinition()` gets called on `(> (cot 0.0) (/ 1 0))` but nothing happens. - `TheoryArith::expandDefinition()` gets called on `(/ 1 0)`, which gets expanded as expected but no attribute is set because it happens in a simple `TheoryArith::eliminateOperators()` call. - `TheoryArith::expandDefinition()` on `(cot (/ 0 1))` first expands to `(/ 1 0)` (not cached) and then we expand it recursively to the expected term and cache `(/ 1 0) ---> (ite (= 0 0) (divByZero 1.0) (/ 1 0))`. Up to this point, things are suboptimal but there are no correctness issues. The problem starts when we do the same process in the unsat core check: - Our only input assertion is again `(> (cot 0.0) (/ 1 0)))`. - `TheoryArith::expandDefinition()` gets called on `(> (cot 0.0) (/ 1 0))` but nothing happens. - `TheoryArith::expandDefinition()` gets called on `(/ 1 0)`, which gets expanded as expected but no attribute is set or checked because it happens in a simple `TheoryArith::eliminateOperators()` call. Note that the skolem introduced here for the division-by-zero case is different from the skolem introduced above because this is in a different `TheoryArith` instance that does not know the existing skolems. - `TheoryArith::expandDefinition()` on `(cot (/ 0 1))` first expands to `(/ 1 0)` (not cached) and then we use the cache from our solving call to expand it `(/ 1 0) ---> (ite (= 0 0) (divByZero 1.0) (/ 1 0))`. Note that `divByZero` here is the skolem from the main solver. As a result, the preprocessed assertions mix skolems from the main `SmtEngine` with the `SmtEngine` of the unsat core check, making the constraints satisfiable. To solve this problem, this commit removes the caching-by-attribute mechanism. The reason for removing it is that it is currently ineffective (since most eliminations do not seem to be cached) and there are caches at other levels, e.g. when expanding definitions. If we deem the operator elimination to be a bottleneck, we can introduce a similar mechanism at the level of `TheoryArith`. --- src/theory/arith/theory_arith_private.cpp | 22 ++++------------------ test/regress/CMakeLists.txt | 1 + test/regress/regress0/arith/issue4525.smt2 | 4 ++++ 3 files changed, 9 insertions(+), 18 deletions(-) create mode 100644 test/regress/regress0/arith/issue4525.smt2 (limited to 'src') diff --git a/src/theory/arith/theory_arith_private.cpp b/src/theory/arith/theory_arith_private.cpp index 09b6d742a..638f5250e 100644 --- a/src/theory/arith/theory_arith_private.cpp +++ b/src/theory/arith/theory_arith_private.cpp @@ -1112,14 +1112,8 @@ void TheoryArithPrivate::checkNonLinearLogic(Node term) } } -struct ArithElimOpAttributeId -{ -}; -typedef expr::Attribute ArithElimOpAttribute; - Node TheoryArithPrivate::eliminateOperatorsRec(Node n) { - ArithElimOpAttribute aeoa; Trace("arith-elim") << "Begin elim: " << n << std::endl; NodeManager* nm = NodeManager::currentNM(); std::unordered_map visited; @@ -1138,18 +1132,11 @@ Node TheoryArithPrivate::eliminateOperatorsRec(Node n) } else if (it == visited.end()) { - if (cur.hasAttribute(aeoa)) - { - visited[cur] = cur.getAttribute(aeoa); - } - else + visited[cur] = Node::null(); + visit.push_back(cur); + for (const Node& cn : cur) { - visited[cur] = Node::null(); - visit.push_back(cur); - for (const Node& cn : cur) - { - visit.push_back(cn); - } + visit.push_back(cn); } } else if (it->second.isNull()) @@ -1180,7 +1167,6 @@ Node TheoryArithPrivate::eliminateOperatorsRec(Node n) // are defined in terms of other non-standard operators. ret = eliminateOperatorsRec(retElim); } - cur.setAttribute(aeoa, ret); visited[cur] = ret; } } while (!visit.empty()); diff --git a/test/regress/CMakeLists.txt b/test/regress/CMakeLists.txt index fbf249e7f..38c60e0e3 100644 --- a/test/regress/CMakeLists.txt +++ b/test/regress/CMakeLists.txt @@ -37,6 +37,7 @@ set(regress_0_tests regress0/arith/issue3413.smt2 regress0/arith/issue3480.smt2 regress0/arith/issue3683.smt2 + regress0/arith/issue4525.smt2 regress0/arith/ite-lift.smt2 regress0/arith/leq.01.smtv1.smt2 regress0/arith/miplib.cvc diff --git a/test/regress/regress0/arith/issue4525.smt2 b/test/regress/regress0/arith/issue4525.smt2 new file mode 100644 index 000000000..ae7e00990 --- /dev/null +++ b/test/regress/regress0/arith/issue4525.smt2 @@ -0,0 +1,4 @@ +(set-logic QF_NRAT) +(assert (> (cot 0.0) (/ 1 0))) +(set-info :status unsat) +(check-sat) -- cgit v1.2.3 From 30673d6ce9a5a1444b33fb11367914df0399e824 Mon Sep 17 00:00:00 2001 From: Andrew Reynolds Date: Mon, 1 Jun 2020 09:14:23 -0500 Subject: Incorporate sequences into the word interface (#4543) Also renames a function mkWord -> mkWordFlatten. --- src/theory/strings/sequences_rewriter.cpp | 6 +- src/theory/strings/word.cpp | 138 ++++++++++++++++++++++++++- src/theory/strings/word.h | 4 +- test/unit/theory/theory_strings_word_white.h | 4 +- 4 files changed, 143 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/src/theory/strings/sequences_rewriter.cpp b/src/theory/strings/sequences_rewriter.cpp index 55d58b860..4f74d7c15 100644 --- a/src/theory/strings/sequences_rewriter.cpp +++ b/src/theory/strings/sequences_rewriter.cpp @@ -264,7 +264,7 @@ Node SequencesRewriter::rewriteStrEqualityExt(Node node) // 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)); + trimmed[j].push_back(Word::mkWordFlatten(vec)); } } @@ -602,7 +602,7 @@ Node SequencesRewriter::rewriteConcat(Node node) std::vector wvec; wvec.push_back(preNode); wvec.push_back(tmpNode[0]); - preNode = Word::mkWord(wvec); + preNode = Word::mkWordFlatten(wvec); node_vec.push_back(preNode); } else @@ -644,7 +644,7 @@ Node SequencesRewriter::rewriteConcat(Node node) std::vector vec; vec.push_back(preNode); vec.push_back(tmpNode); - preNode = Word::mkWord(vec); + preNode = Word::mkWordFlatten(vec); } } } diff --git a/src/theory/strings/word.cpp b/src/theory/strings/word.cpp index e9ab2652e..3f6a9de32 100644 --- a/src/theory/strings/word.cpp +++ b/src/theory/strings/word.cpp @@ -14,6 +14,7 @@ #include "theory/strings/word.h" +#include "expr/sequence.h" #include "util/string.h" using namespace CVC4::kind; @@ -28,23 +29,28 @@ Node Word::mkEmptyWord(TypeNode tn) { return mkEmptyWord(CONST_STRING); } + else if (tn.isSequence()) + { + std::vector seq; + return NodeManager::currentNM()->mkConst( + ExprSequence(tn.getSequenceElementType().toType(), seq)); + } Unimplemented(); return Node::null(); } Node Word::mkEmptyWord(Kind k) { - NodeManager* nm = NodeManager::currentNM(); if (k == CONST_STRING) { std::vector vec; - return nm->mkConst(String(vec)); + return NodeManager::currentNM()->mkConst(String(vec)); } Unimplemented(); return Node::null(); } -Node Word::mkWord(const std::vector& xs) +Node Word::mkWordFlatten(const std::vector& xs) { Assert(!xs.empty()); NodeManager* nm = NodeManager::currentNM(); @@ -61,6 +67,22 @@ Node Word::mkWord(const std::vector& xs) } return nm->mkConst(String(vec)); } + else if (k == CONST_SEQUENCE) + { + std::vector seq; + TypeNode tn = xs[0].getType(); + for (TNode x : xs) + { + Assert(x.getType() == tn); + const Sequence& sx = x.getConst().getSequence(); + const std::vector& vecc = sx.getVec(); + for (const Node& c : vecc) + { + seq.push_back(c.toExpr()); + } + } + return NodeManager::currentNM()->mkConst(ExprSequence(tn.toType(), seq)); + } Unimplemented(); return Node::null(); } @@ -72,6 +94,10 @@ size_t Word::getLength(TNode x) { return x.getConst().size(); } + else if (k == CONST_SEQUENCE) + { + return x.getConst().getSequence().size(); + } Unimplemented(); return 0; } @@ -111,6 +137,13 @@ bool Word::strncmp(TNode x, TNode y, std::size_t n) String sy = y.getConst(); return sx.strncmp(sy, n); } + else if (k == CONST_SEQUENCE) + { + Assert(y.getKind() == CONST_SEQUENCE); + const Sequence& sx = x.getConst().getSequence(); + const Sequence& sy = y.getConst().getSequence(); + return sx.strncmp(sy, n); + } Unimplemented(); return false; } @@ -125,6 +158,13 @@ bool Word::rstrncmp(TNode x, TNode y, std::size_t n) String sy = y.getConst(); return sx.rstrncmp(sy, n); } + else if (k == CONST_SEQUENCE) + { + Assert(y.getKind() == CONST_SEQUENCE); + const Sequence& sx = x.getConst().getSequence(); + const Sequence& sy = y.getConst().getSequence(); + return sx.rstrncmp(sy, n); + } Unimplemented(); return false; } @@ -139,6 +179,13 @@ std::size_t Word::find(TNode x, TNode y, std::size_t start) String sy = y.getConst(); return sx.find(sy, start); } + else if (k == CONST_SEQUENCE) + { + Assert(y.getKind() == CONST_SEQUENCE); + const Sequence& sx = x.getConst().getSequence(); + const Sequence& sy = y.getConst().getSequence(); + return sx.find(sy, start); + } Unimplemented(); return 0; } @@ -153,6 +200,13 @@ std::size_t Word::rfind(TNode x, TNode y, std::size_t start) String sy = y.getConst(); return sx.rfind(sy, start); } + else if (k == CONST_SEQUENCE) + { + Assert(y.getKind() == CONST_SEQUENCE); + const Sequence& sx = x.getConst().getSequence(); + const Sequence& sy = y.getConst().getSequence(); + return sx.rfind(sy, start); + } Unimplemented(); return 0; } @@ -167,6 +221,13 @@ bool Word::hasPrefix(TNode x, TNode y) String sy = y.getConst(); return sx.hasPrefix(sy); } + else if (k == CONST_SEQUENCE) + { + Assert(y.getKind() == CONST_SEQUENCE); + const Sequence& sx = x.getConst().getSequence(); + const Sequence& sy = y.getConst().getSequence(); + return sx.hasPrefix(sy); + } Unimplemented(); return false; } @@ -181,6 +242,13 @@ bool Word::hasSuffix(TNode x, TNode y) String sy = y.getConst(); return sx.hasSuffix(sy); } + else if (k == CONST_SEQUENCE) + { + Assert(y.getKind() == CONST_SEQUENCE); + const Sequence& sx = x.getConst().getSequence(); + const Sequence& sy = y.getConst().getSequence(); + return sx.hasSuffix(sy); + } Unimplemented(); return false; } @@ -198,6 +266,16 @@ Node Word::replace(TNode x, TNode y, TNode t) String st = t.getConst(); return nm->mkConst(String(sx.replace(sy, st))); } + else if (k == CONST_SEQUENCE) + { + Assert(y.getKind() == CONST_SEQUENCE); + Assert(t.getKind() == CONST_SEQUENCE); + const Sequence& sx = x.getConst().getSequence(); + const Sequence& sy = y.getConst().getSequence(); + const Sequence& st = t.getConst().getSequence(); + Sequence res = sx.replace(sy, st); + return nm->mkConst(res.toExprSequence()); + } Unimplemented(); return Node::null(); } @@ -210,6 +288,12 @@ Node Word::substr(TNode x, std::size_t i) String sx = x.getConst(); return nm->mkConst(String(sx.substr(i))); } + else if (k == CONST_SEQUENCE) + { + const Sequence& sx = x.getConst().getSequence(); + Sequence res = sx.substr(i); + return nm->mkConst(res.toExprSequence()); + } Unimplemented(); return Node::null(); } @@ -222,6 +306,12 @@ Node Word::substr(TNode x, std::size_t i, std::size_t j) String sx = x.getConst(); return nm->mkConst(String(sx.substr(i, j))); } + else if (k == CONST_SEQUENCE) + { + const Sequence& sx = x.getConst().getSequence(); + Sequence res = sx.substr(i, j); + return nm->mkConst(res.toExprSequence()); + } Unimplemented(); return Node::null(); } @@ -237,6 +327,12 @@ Node Word::suffix(TNode x, std::size_t i) String sx = x.getConst(); return nm->mkConst(String(sx.suffix(i))); } + else if (k == CONST_SEQUENCE) + { + const Sequence& sx = x.getConst().getSequence(); + Sequence res = sx.suffix(i); + return nm->mkConst(res.toExprSequence()); + } Unimplemented(); return Node::null(); } @@ -251,6 +347,13 @@ bool Word::noOverlapWith(TNode x, TNode y) String sy = y.getConst(); return sx.noOverlapWith(sy); } + else if (k == CONST_SEQUENCE) + { + Assert(y.getKind() == CONST_SEQUENCE); + const Sequence& sx = x.getConst().getSequence(); + const Sequence& sy = y.getConst().getSequence(); + return sx.noOverlapWith(sy); + } Unimplemented(); return false; } @@ -265,6 +368,13 @@ std::size_t Word::overlap(TNode x, TNode y) String sy = y.getConst(); return sx.overlap(sy); } + else if (k == CONST_SEQUENCE) + { + Assert(y.getKind() == CONST_SEQUENCE); + const Sequence& sx = x.getConst().getSequence(); + const Sequence& sy = y.getConst().getSequence(); + return sx.overlap(sy); + } Unimplemented(); return 0; } @@ -279,10 +389,32 @@ std::size_t Word::roverlap(TNode x, TNode y) String sy = y.getConst(); return sx.roverlap(sy); } + else if (k == CONST_SEQUENCE) + { + Assert(y.getKind() == CONST_SEQUENCE); + const Sequence& sx = x.getConst().getSequence(); + const Sequence& sy = y.getConst().getSequence(); + return sx.roverlap(sy); + } Unimplemented(); return 0; } +bool Word::isRepeated(TNode x) +{ + Kind k = x.getKind(); + if (k == CONST_STRING) + { + return x.getConst().isRepeated(); + } + else if (k == CONST_SEQUENCE) + { + return x.getConst().getSequence().isRepeated(); + } + Unimplemented(); + return false; +} + Node Word::splitConstant(TNode x, TNode y, size_t& index, bool isRev) { Assert(x.isConst() && y.isConst()); diff --git a/src/theory/strings/word.h b/src/theory/strings/word.h index b84ea6874..ad1d9bc74 100644 --- a/src/theory/strings/word.h +++ b/src/theory/strings/word.h @@ -37,7 +37,7 @@ class Word static Node mkEmptyWord(Kind k); /** make word from constants in (non-empty) vector vec */ - static Node mkWord(const std::vector& xs); + static Node mkWordFlatten(const std::vector& xs); /** Return the length of word x */ static size_t getLength(TNode x); @@ -139,6 +139,8 @@ class Word * Notice that x.overlap(y) = y.roverlap(x) */ static std::size_t roverlap(TNode x, TNode y); + /** Return true if word x is a repetition of the same character */ + static bool isRepeated(TNode x); /** Split constant * * This returns the suffix remainder (resp. prefix remainder when isRev is diff --git a/test/unit/theory/theory_strings_word_white.h b/test/unit/theory/theory_strings_word_white.h index d84df7836..6b594d904 100644 --- a/test/unit/theory/theory_strings_word_white.h +++ b/test/unit/theory/theory_strings_word_white.h @@ -67,10 +67,10 @@ class TheoryStringsWordWhite : public CxxTest::TestSuite std::vector vec; vec.push_back(abc); - Node abcMk = Word::mkWord(vec); + Node abcMk = Word::mkWordFlatten(vec); TS_ASSERT_EQUALS(abc, abcMk); vec.push_back(a); - Node abcaMk = Word::mkWord(vec); + Node abcaMk = Word::mkWordFlatten(vec); TS_ASSERT_EQUALS(abca, abcaMk); TS_ASSERT(Word::getLength(empty) == 0); -- cgit v1.2.3 From 7c2045123b177334cc47b24266225d6b38599bf5 Mon Sep 17 00:00:00 2001 From: Andres Noetzli Date: Mon, 1 Jun 2020 09:41:16 -0700 Subject: Do not parse ->/lambda unless --uf-ho enabled (#4544) Fixes #4477. Logic ALL includes higher-order but we currently do not support solving higher-order problems unless --uf-ho is enabled. This commit changes the condition under which we parse -> and lambda to only enabled parsing of those symbols if the logic allows higher-order constraints and --uf-ho is enabled. --- src/parser/smt2/Smt2.g | 4 ++-- src/parser/smt2/smt2.cpp | 6 ++++++ src/parser/smt2/smt2.h | 7 +++++++ test/regress/CMakeLists.txt | 1 + test/regress/regress0/ho/issue4477.smt2 | 11 +++++++++++ 5 files changed, 27 insertions(+), 2 deletions(-) create mode 100644 test/regress/regress0/ho/issue4477.smt2 (limited to 'src') diff --git a/src/parser/smt2/Smt2.g b/src/parser/smt2/Smt2.g index d591c29de..95f4b1a67 100644 --- a/src/parser/smt2/Smt2.g +++ b/src/parser/smt2/Smt2.g @@ -2630,8 +2630,8 @@ CHAR_TOK : { PARSER_STATE->isTheoryEnabled(theory::THEORY_STRINGS) }? 'char'; TUPLE_CONST_TOK: { PARSER_STATE->isTheoryEnabled(theory::THEORY_DATATYPES) }? 'mkTuple'; TUPLE_SEL_TOK: { PARSER_STATE->isTheoryEnabled(theory::THEORY_DATATYPES) }? 'tupSel'; -HO_ARROW_TOK : { PARSER_STATE->getLogic().isHigherOrder() }? '->'; -HO_LAMBDA_TOK : { PARSER_STATE->getLogic().isHigherOrder() }? 'lambda'; +HO_ARROW_TOK : { PARSER_STATE->isHoEnabled() }? '->'; +HO_LAMBDA_TOK : { PARSER_STATE->isHoEnabled() }? 'lambda'; /** * A sequence of printable ASCII characters (except backslash) that starts diff --git a/src/parser/smt2/smt2.cpp b/src/parser/smt2/smt2.cpp index 91260d1db..9ca2194f4 100644 --- a/src/parser/smt2/smt2.cpp +++ b/src/parser/smt2/smt2.cpp @@ -315,6 +315,12 @@ bool Smt2::isTheoryEnabled(theory::TheoryId theory) const return d_logic.isTheoryEnabled(theory); } +bool Smt2::isHoEnabled() const +{ + return getLogic().isHigherOrder() + && d_solver->getExprManager()->getOptions().getUfHo(); +} + bool Smt2::logicIsSet() { return d_logicSet; } diff --git a/src/parser/smt2/smt2.h b/src/parser/smt2/smt2.h index 35d088601..af1e36795 100644 --- a/src/parser/smt2/smt2.h +++ b/src/parser/smt2/smt2.h @@ -98,6 +98,13 @@ class Smt2 : public Parser bool isTheoryEnabled(theory::TheoryId theory) const; + /** + * Checks if higher-order support is enabled. + * + * @return true if higher-order support is enabled, false otherwise + */ + bool isHoEnabled() const; + bool logicIsSet() override; /** diff --git a/test/regress/CMakeLists.txt b/test/regress/CMakeLists.txt index 38c60e0e3..10a1b6ba0 100644 --- a/test/regress/CMakeLists.txt +++ b/test/regress/CMakeLists.txt @@ -539,6 +539,7 @@ set(regress_0_tests regress0/ho/ho-matching-nested-app.smt2 regress0/ho/ho-std-fmf.smt2 regress0/ho/hoa0008.smt2 + regress0/ho/issue4477.smt2 regress0/ho/ite-apply-eq.smt2 regress0/ho/lambda-equality-non-canon.smt2 regress0/ho/match-middle.smt2 diff --git a/test/regress/regress0/ho/issue4477.smt2 b/test/regress/regress0/ho/issue4477.smt2 new file mode 100644 index 000000000..7162d260c --- /dev/null +++ b/test/regress/regress0/ho/issue4477.smt2 @@ -0,0 +1,11 @@ +; REQUIRES: no-competition +; SCRUBBER: grep -o "Symbol '->' not declared" +; EXPECT: Symbol '->' not declared +; EXIT: 1 +(set-logic ALL) +(declare-sort s 0) +(declare-fun a () s) +(declare-fun b () s) +(declare-fun c (s) s) +(assert (forall ((d (-> s s))) (distinct (d a) (c a) b))) +(check-sat) -- cgit v1.2.3 From a6c8c9a293eca7cd753368d7f23f9978deb2b2d5 Mon Sep 17 00:00:00 2001 From: Andres Noetzli Date: Mon, 1 Jun 2020 10:14:56 -0700 Subject: Set theoryof-mode after theory widening (#4545) Fixes #4367. We set the theoryof-mode depending on whether sharing is enabled or not. However, we were checking whether sharing was enabled before theory widening, leading to unexpected results. This commit moves the check after widening the theories. For the benchmark in the issue, setting the theoryof-mode before theory widening lead to problems because of the following: The main solver checks the condition for enabling term-based theoryof-mode, decides not to do it because sharing is not enabled Main solver adds UF to the logic Main solver does a check-sat all Unsat core check runs, sees both UF and NRA enabled, and switches to term-based mode Main solver gets to second check-sat call, now the theoryof-mode is suddenly changed, which leads to problems in the equality engine This commit fixes the issue in this particular instance but it is important to note that it does not address the issue of the subsolver changing settings in general. This can only really be solved by separating the options from the ExprManager/NodeManager and having separate options for each SmtEngine/Solver instance. --- src/smt/set_defaults.cpp | 24 ++++++++++++------------ test/regress/CMakeLists.txt | 1 + test/regress/regress0/arith/issue3480.smt2 | 2 +- test/regress/regress0/arith/issue4367.smt2 | 11 +++++++++++ 4 files changed, 25 insertions(+), 13 deletions(-) create mode 100644 test/regress/regress0/arith/issue4367.smt2 (limited to 'src') diff --git a/src/smt/set_defaults.cpp b/src/smt/set_defaults.cpp index e06363883..867ac8a4a 100644 --- a/src/smt/set_defaults.cpp +++ b/src/smt/set_defaults.cpp @@ -538,18 +538,6 @@ void setDefaults(SmtEngine& smte, LogicInfo& logic) 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); - } - } - ///////////////////////////////////////////////////////////////////////////// // Theory widening // @@ -609,6 +597,18 @@ void setDefaults(SmtEngine& smte, LogicInfo& logic) } ///////////////////////////////////////////////////////////////////////////// + // 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); + } + } + // by default, symmetry breaker is on only for non-incremental QF_UF if (!options::ufSymmetryBreaker.wasSetByUser()) { diff --git a/test/regress/CMakeLists.txt b/test/regress/CMakeLists.txt index 10a1b6ba0..0f1b090d4 100644 --- a/test/regress/CMakeLists.txt +++ b/test/regress/CMakeLists.txt @@ -37,6 +37,7 @@ set(regress_0_tests regress0/arith/issue3413.smt2 regress0/arith/issue3480.smt2 regress0/arith/issue3683.smt2 + regress0/arith/issue4367.smt2 regress0/arith/issue4525.smt2 regress0/arith/ite-lift.smt2 regress0/arith/leq.01.smtv1.smt2 diff --git a/test/regress/regress0/arith/issue3480.smt2 b/test/regress/regress0/arith/issue3480.smt2 index 7609ad3e7..74ce8d32b 100644 --- a/test/regress/regress0/arith/issue3480.smt2 +++ b/test/regress/regress0/arith/issue3480.smt2 @@ -1,4 +1,4 @@ -; COMMAND-LINE: --quiet +; COMMAND-LINE: --theoryof-mode=type --quiet (set-logic QF_NIA) (declare-fun a () Int) (declare-fun b () Int) diff --git a/test/regress/regress0/arith/issue4367.smt2 b/test/regress/regress0/arith/issue4367.smt2 new file mode 100644 index 000000000..abe5b09fd --- /dev/null +++ b/test/regress/regress0/arith/issue4367.smt2 @@ -0,0 +1,11 @@ +; COMMAND-LINE: --incremental --check-unsat-cores +; EXPECT: unsat +; EXPECT: unsat +(set-logic NRA) +(declare-const r0 Real) +(assert (! (forall ((q0 Bool) (q1 Real)) (= (* r0 r0) r0 r0)) :named IP_2)) +(assert (! (not (forall ((q2 Real)) (not (<= 55.033442 r0 55.033442 q2)))) :named IP_5)) +(push 1) +(check-sat) +(pop 1) +(check-sat) -- cgit v1.2.3 From 4ac66d3aee2a0571c169e4ce2d6049ea311462ce Mon Sep 17 00:00:00 2001 From: Andrew Reynolds Date: Mon, 1 Jun 2020 14:31:48 -0500 Subject: Move non-linear files to src/theory/arith/nl (#4548) Also makes CVC4::theory::arith::nl namespace. This includes some formatting changes. --- src/CMakeLists.txt | 28 +- src/theory/arith/nl/nl_constraint.cpp | 125 ++ src/theory/arith/nl/nl_constraint.h | 88 ++ src/theory/arith/nl/nl_lemma_utils.cpp | 65 + src/theory/arith/nl/nl_lemma_utils.h | 107 ++ src/theory/arith/nl/nl_model.cpp | 1349 +++++++++++++++++++++ src/theory/arith/nl/nl_model.h | 335 ++++++ src/theory/arith/nl/nl_monomial.cpp | 336 ++++++ src/theory/arith/nl/nl_monomial.h | 149 +++ src/theory/arith/nl/nl_solver.cpp | 1587 +++++++++++++++++++++++++ src/theory/arith/nl/nl_solver.h | 370 ++++++ src/theory/arith/nl/nonlinear_extension.cpp | 841 +++++++++++++ src/theory/arith/nl/nonlinear_extension.h | 341 ++++++ src/theory/arith/nl/transcendental_solver.cpp | 1477 +++++++++++++++++++++++ src/theory/arith/nl/transcendental_solver.h | 430 +++++++ src/theory/arith/nl_constraint.cpp | 123 -- src/theory/arith/nl_constraint.h | 86 -- src/theory/arith/nl_lemma_utils.cpp | 63 - src/theory/arith/nl_lemma_utils.h | 105 -- src/theory/arith/nl_model.cpp | 1347 --------------------- src/theory/arith/nl_model.h | 333 ------ src/theory/arith/nl_monomial.cpp | 334 ------ src/theory/arith/nl_monomial.h | 147 --- src/theory/arith/nl_solver.cpp | 1585 ------------------------ src/theory/arith/nl_solver.h | 368 ------ src/theory/arith/nonlinear_extension.cpp | 815 ------------- src/theory/arith/nonlinear_extension.h | 335 ------ src/theory/arith/theory_arith_private.cpp | 4 +- src/theory/arith/theory_arith_private.h | 4 +- src/theory/arith/transcendental_solver.cpp | 1475 ----------------------- src/theory/arith/transcendental_solver.h | 428 ------- 31 files changed, 7619 insertions(+), 7561 deletions(-) create mode 100644 src/theory/arith/nl/nl_constraint.cpp create mode 100644 src/theory/arith/nl/nl_constraint.h create mode 100644 src/theory/arith/nl/nl_lemma_utils.cpp create mode 100644 src/theory/arith/nl/nl_lemma_utils.h create mode 100644 src/theory/arith/nl/nl_model.cpp create mode 100644 src/theory/arith/nl/nl_model.h create mode 100644 src/theory/arith/nl/nl_monomial.cpp create mode 100644 src/theory/arith/nl/nl_monomial.h create mode 100644 src/theory/arith/nl/nl_solver.cpp create mode 100644 src/theory/arith/nl/nl_solver.h create mode 100644 src/theory/arith/nl/nonlinear_extension.cpp create mode 100644 src/theory/arith/nl/nonlinear_extension.h create mode 100644 src/theory/arith/nl/transcendental_solver.cpp create mode 100644 src/theory/arith/nl/transcendental_solver.h delete mode 100644 src/theory/arith/nl_constraint.cpp delete mode 100644 src/theory/arith/nl_constraint.h delete mode 100644 src/theory/arith/nl_lemma_utils.cpp delete mode 100644 src/theory/arith/nl_lemma_utils.h delete mode 100644 src/theory/arith/nl_model.cpp delete mode 100644 src/theory/arith/nl_model.h delete mode 100644 src/theory/arith/nl_monomial.cpp delete mode 100644 src/theory/arith/nl_monomial.h delete mode 100644 src/theory/arith/nl_solver.cpp delete mode 100644 src/theory/arith/nl_solver.h delete mode 100644 src/theory/arith/nonlinear_extension.cpp delete mode 100644 src/theory/arith/nonlinear_extension.h delete mode 100644 src/theory/arith/transcendental_solver.cpp delete mode 100644 src/theory/arith/transcendental_solver.h (limited to 'src') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e9be4dd3e..20e110b2b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -300,18 +300,20 @@ libcvc4_add_sources( theory/arith/linear_equality.h theory/arith/matrix.cpp theory/arith/matrix.h - theory/arith/nl_constraint.cpp - theory/arith/nl_constraint.h - theory/arith/nl_lemma_utils.cpp - theory/arith/nl_lemma_utils.h - theory/arith/nl_model.cpp - theory/arith/nl_model.h - theory/arith/nl_monomial.cpp - theory/arith/nl_monomial.h - theory/arith/nl_solver.cpp - theory/arith/nl_solver.h - theory/arith/nonlinear_extension.cpp - theory/arith/nonlinear_extension.h + theory/arith/nl/nl_constraint.cpp + theory/arith/nl/nl_constraint.h + theory/arith/nl/nl_lemma_utils.cpp + theory/arith/nl/nl_lemma_utils.h + theory/arith/nl/nl_model.cpp + theory/arith/nl/nl_model.h + theory/arith/nl/nl_monomial.cpp + theory/arith/nl/nl_monomial.h + theory/arith/nl/nl_solver.cpp + theory/arith/nl/nl_solver.h + theory/arith/nl/nonlinear_extension.cpp + theory/arith/nl/nonlinear_extension.h + theory/arith/nl/transcendental_solver.cpp + theory/arith/nl/transcendental_solver.h theory/arith/normal_form.cpp theory/arith/normal_form.h theory/arith/partial_model.cpp @@ -332,8 +334,6 @@ 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/nl/nl_constraint.cpp b/src/theory/arith/nl/nl_constraint.cpp new file mode 100644 index 000000000..c4c4dfbe7 --- /dev/null +++ b/src/theory/arith/nl/nl_constraint.cpp @@ -0,0 +1,125 @@ +/********************* */ +/*! \file nl_constraint.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 non-linear constraints + **/ + +#include "theory/arith/nl/nl_constraint.h" + +#include "theory/arith/arith_msum.h" +#include "theory/arith/arith_utilities.h" + +using namespace CVC4::kind; + +namespace CVC4 { +namespace theory { +namespace arith { +namespace nl { + +ConstraintDb::ConstraintDb(MonomialDb& mdb) : d_mdb(mdb) {} + +void ConstraintDb::registerConstraint(Node atom) +{ + if (std::find(d_constraints.begin(), d_constraints.end(), atom) + != d_constraints.end()) + { + return; + } + d_constraints.push_back(atom); + Trace("nl-ext-debug") << "Register constraint : " << atom << std::endl; + std::map msum; + if (ArithMSum::getMonomialSumLit(atom, msum)) + { + Trace("nl-ext-debug") << "got monomial sum: " << std::endl; + if (Trace.isOn("nl-ext-debug")) + { + ArithMSum::debugPrintMonomialSum(msum, "nl-ext-debug"); + } + unsigned max_degree = 0; + std::vector all_m; + std::vector max_deg_m; + for (std::map::iterator itm = msum.begin(); itm != msum.end(); + ++itm) + { + if (!itm->first.isNull()) + { + all_m.push_back(itm->first); + d_mdb.registerMonomial(itm->first); + Trace("nl-ext-debug2") + << "...process monomial " << itm->first << std::endl; + unsigned d = d_mdb.getDegree(itm->first); + if (d > max_degree) + { + max_degree = d; + max_deg_m.clear(); + } + if (d >= max_degree) + { + max_deg_m.push_back(itm->first); + } + } + } + // isolate for each maximal degree monomial + for (unsigned i = 0; i < all_m.size(); i++) + { + Node m = all_m[i]; + Node rhs, coeff; + int res = ArithMSum::isolate(m, msum, coeff, rhs, atom.getKind()); + if (res != 0) + { + Kind type = atom.getKind(); + if (res == -1) + { + type = reverseRelationKind(type); + } + Trace("nl-ext-constraint") << "Constraint : " << atom << " <=> "; + if (!coeff.isNull()) + { + Trace("nl-ext-constraint") << coeff << " * "; + } + Trace("nl-ext-constraint") + << m << " " << type << " " << rhs << std::endl; + ConstraintInfo& ci = d_c_info[atom][m]; + ci.d_rhs = rhs; + ci.d_coeff = coeff; + ci.d_type = type; + } + } + for (unsigned i = 0; i < max_deg_m.size(); i++) + { + Node m = max_deg_m[i]; + d_c_info_maxm[atom][m] = true; + } + } + else + { + Trace("nl-ext-debug") << "...failed to get monomial sum." << std::endl; + } +} + +const std::map >& +ConstraintDb::getConstraints() +{ + return d_c_info; +} + +bool ConstraintDb::isMaximal(Node atom, Node x) const +{ + std::map >::const_iterator itcm = + d_c_info_maxm.find(atom); + Assert(itcm != d_c_info_maxm.end()); + return itcm->second.find(x) != itcm->second.end(); +} + +} // namespace nl +} // namespace arith +} // namespace theory +} // namespace CVC4 diff --git a/src/theory/arith/nl/nl_constraint.h b/src/theory/arith/nl/nl_constraint.h new file mode 100644 index 000000000..e86ac4b66 --- /dev/null +++ b/src/theory/arith/nl/nl_constraint.h @@ -0,0 +1,88 @@ +/********************* */ +/*! \file nl_constraint.h + ** \verbatim + ** Top contributors (to current version): + ** Andrew Reynolds, Tim King + ** 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 Utilities for non-linear constraints + **/ + +#ifndef CVC4__THEORY__ARITH__NL__NL_CONSTRAINT_H +#define CVC4__THEORY__ARITH__NL__NL_CONSTRAINT_H + +#include +#include + +#include "expr/kind.h" +#include "expr/node.h" +#include "theory/arith/nl/nl_monomial.h" + +namespace CVC4 { +namespace theory { +namespace arith { +namespace nl { + +/** constraint information + * + * The struct ( d_rhs, d_coeff, d_type ) represents that a literal is of the + * form (d_coeff * x) d_rhs. + */ +struct ConstraintInfo +{ + public: + /** The term on the right hand side of the constraint */ + Node d_rhs; + /** The coefficent */ + Node d_coeff; + /** The type (relation) of the constraint */ + Kind d_type; +}; /* struct ConstraintInfo */ + +/** A database for constraints */ +class ConstraintDb +{ + public: + ConstraintDb(MonomialDb& mdb); + ~ConstraintDb() {} + /** register constraint + * + * This ensures that atom is in the domain of the constraints maintained by + * this database. + */ + void registerConstraint(Node atom); + /** get constraints + * + * Returns a map m such that whenever + * m[lit][x] = ( r, coeff, k ), then + * ( lit <=> (coeff * x) r ) + */ + const std::map >& getConstraints(); + /** Returns true if m is of maximal degree in atom + * + * For example, for atom x^2 + x*y + y >=0, the monomials x^2 and x*y + * are of maximal degree (2). + */ + bool isMaximal(Node atom, Node m) const; + + private: + /** Reference to a monomial database */ + MonomialDb& d_mdb; + /** List of all constraints */ + std::vector d_constraints; + /** Is maximal degree */ + std::map > d_c_info_maxm; + /** Constraint information */ + std::map > d_c_info; +}; + +} // namespace nl +} // namespace arith +} // namespace theory +} // namespace CVC4 + +#endif /* CVC4__THEORY__ARITH__NL_SOLVER_H */ diff --git a/src/theory/arith/nl/nl_lemma_utils.cpp b/src/theory/arith/nl/nl_lemma_utils.cpp new file mode 100644 index 000000000..ca34d91a9 --- /dev/null +++ b/src/theory/arith/nl/nl_lemma_utils.cpp @@ -0,0 +1,65 @@ +/********************* */ +/*! \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/nl_lemma_utils.h" + +#include "theory/arith/nl/nl_model.h" + +namespace CVC4 { +namespace theory { +namespace arith { +namespace nl { + +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 nl +} // namespace arith +} // namespace theory +} // namespace CVC4 diff --git a/src/theory/arith/nl/nl_lemma_utils.h b/src/theory/arith/nl/nl_lemma_utils.h new file mode 100644 index 000000000..64a4deb17 --- /dev/null +++ b/src/theory/arith/nl/nl_lemma_utils.h @@ -0,0 +1,107 @@ +/********************* */ +/*! \file nl_lemma_utils.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 Utilities for processing lemmas from the non-linear solver + **/ + +#ifndef CVC4__THEORY__ARITH__NL__NL_LEMMA_UTILS_H +#define CVC4__THEORY__ARITH__NL__NL_LEMMA_UTILS_H + +#include +#include +#include "expr/node.h" + +namespace CVC4 { +namespace theory { +namespace arith { +namespace nl { + +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 + * includes: + * - A set of secant points to record (for transcendental secant plane + * inferences). + */ +struct NlLemmaSideEffect +{ + NlLemmaSideEffect() {} + ~NlLemmaSideEffect() {} + /** secant points to add + * + * A member (tf, d, c) in this vector indicates that point c should be added + * to the list of secant points for an application of a transcendental + * function tf for Taylor degree d. This is used for incremental linearization + * for underapproximation (resp. overapproximations) of convex (resp. + * concave) regions of transcendental functions. For details, see + * Cimatti et al., CADE 2017. + */ + 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(const std::map& m) : d_mdegree(m) {} + /** pointer to the non-linear extension */ + const 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 nl +} // namespace arith +} // namespace theory +} // namespace CVC4 + +#endif /* CVC4__THEORY__ARITH__NL_LEMMA_UTILS_H */ diff --git a/src/theory/arith/nl/nl_model.cpp b/src/theory/arith/nl/nl_model.cpp new file mode 100644 index 000000000..d5df96bd8 --- /dev/null +++ b/src/theory/arith/nl/nl_model.cpp @@ -0,0 +1,1349 @@ +/********************* */ +/*! \file nl_model.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 Model object for the non-linear extension class + **/ + +#include "theory/arith/nl/nl_model.h" + +#include "expr/node_algorithm.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 { +namespace nl { + +NlModel::NlModel(context::Context* c) : d_used_approx(false) +{ + d_true = NodeManager::currentNM()->mkConst(true); + d_false = NodeManager::currentNM()->mkConst(false); + d_zero = NodeManager::currentNM()->mkConst(Rational(0)); + d_one = NodeManager::currentNM()->mkConst(Rational(1)); + d_two = NodeManager::currentNM()->mkConst(Rational(2)); +} + +NlModel::~NlModel() {} + +void NlModel::reset(TheoryModel* m, std::map& arithModel) +{ + d_model = m; + d_mv[0].clear(); + d_mv[1].clear(); + d_arithVal.clear(); + // process arithModel + std::map::iterator it; + for (const std::pair& m2 : arithModel) + { + d_arithVal[m2.first] = m2.second; + } +} + +void NlModel::resetCheck() +{ + d_used_approx = false; + d_check_model_solved.clear(); + d_check_model_bounds.clear(); + d_check_model_vars.clear(); + d_check_model_subs.clear(); +} + +Node NlModel::computeConcreteModelValue(Node n) +{ + return computeModelValue(n, true); +} + +Node NlModel::computeAbstractModelValue(Node n) +{ + return computeModelValue(n, false); +} + +Node NlModel::computeModelValue(Node n, bool isConcrete) +{ + unsigned index = isConcrete ? 0 : 1; + std::map::iterator it = d_mv[index].find(n); + if (it != d_mv[index].end()) + { + return it->second; + } + Trace("nl-ext-mv-debug") << "computeModelValue " << n << ", index=" << index + << std::endl; + Node ret; + Kind nk = n.getKind(); + if (n.isConst()) + { + ret = n; + } + else if (!isConcrete && hasTerm(n)) + { + // use model value for abstraction + ret = getRepresentative(n); + } + else if (n.getNumChildren() == 0) + { + // we are interested in the exact value of PI, which cannot be computed. + // hence, we return PI itself when asked for the concrete value. + if (nk == PI) + { + ret = n; + } + else + { + ret = getValueInternal(n); + } + } + else + { + // otherwise, compute true value + TheoryId ctid = theory::kindToTheoryId(nk); + if (ctid != THEORY_ARITH && ctid != THEORY_BOOL && ctid != THEORY_BUILTIN) + { + // we directly look up terms not belonging to arithmetic + ret = getValueInternal(n); + } + else + { + std::vector children; + if (n.getMetaKind() == metakind::PARAMETERIZED) + { + children.push_back(n.getOperator()); + } + for (unsigned i = 0, nchild = n.getNumChildren(); i < nchild; i++) + { + Node mc = computeModelValue(n[i], isConcrete); + children.push_back(mc); + } + ret = NodeManager::currentNM()->mkNode(nk, children); + ret = Rewriter::rewrite(ret); + } + } + Trace("nl-ext-mv-debug") << "computed " << (index == 0 ? "M" : "M_A") << "[" + << n << "] = " << ret << std::endl; + d_mv[index][n] = ret; + return ret; +} + +bool NlModel::hasTerm(Node n) const +{ + return d_arithVal.find(n) != d_arithVal.end(); +} + +Node NlModel::getRepresentative(Node n) const +{ + if (n.isConst()) + { + return n; + } + std::map::const_iterator it = d_arithVal.find(n); + if (it != d_arithVal.end()) + { + AlwaysAssert(it->second.isConst()); + return it->second; + } + return d_model->getRepresentative(n); +} + +Node NlModel::getValueInternal(Node n) const +{ + if (n.isConst()) + { + return n; + } + std::map::const_iterator it = d_arithVal.find(n); + if (it != d_arithVal.end()) + { + AlwaysAssert(it->second.isConst()); + return it->second; + } + // It is unconstrained in the model, return 0. + return d_zero; +} + +int NlModel::compare(Node i, Node j, bool isConcrete, bool isAbsolute) +{ + Node ci = computeModelValue(i, isConcrete); + Node cj = computeModelValue(j, isConcrete); + if (ci.isConst()) + { + if (cj.isConst()) + { + return compareValue(ci, cj, isAbsolute); + } + return 1; + } + return cj.isConst() ? -1 : 0; +} + +int NlModel::compareValue(Node i, Node j, bool isAbsolute) const +{ + Assert(i.isConst() && j.isConst()); + int ret; + if (i == j) + { + ret = 0; + } + else if (!isAbsolute) + { + ret = i.getConst() < j.getConst() ? 1 : -1; + } + else + { + ret = (i.getConst().abs() == j.getConst().abs() + ? 0 + : (i.getConst().abs() < j.getConst().abs() + ? 1 + : -1)); + } + return ret; +} + +bool NlModel::checkModel(const std::vector& assertions, + const std::vector& false_asserts, + unsigned d, + std::vector& lemmas, + std::vector& gs) +{ + Trace("nl-ext-cm-debug") << " solve for equalities..." << std::endl; + for (const Node& atom : false_asserts) + { + // see if it corresponds to a univariate polynomial equation of degree two + if (atom.getKind() == EQUAL) + { + if (!solveEqualitySimple(atom, d, lemmas)) + { + // no chance we will satisfy this equality + Trace("nl-ext-cm") << "...check-model : failed to solve equality : " + << atom << std::endl; + } + } + } + + // all remaining variables are constrained to their exact model values + Trace("nl-ext-cm-debug") << " set exact bounds for remaining variables..." + << std::endl; + std::unordered_set visited; + std::vector visit; + TNode cur; + for (const Node& a : assertions) + { + visit.push_back(a); + do + { + cur = visit.back(); + visit.pop_back(); + if (visited.find(cur) == visited.end()) + { + visited.insert(cur); + if (cur.getType().isReal() && !cur.isConst()) + { + Kind k = cur.getKind(); + if (k != MULT && k != PLUS && k != NONLINEAR_MULT + && !isTranscendentalKind(k)) + { + // if we have not set an approximate bound for it + if (!hasCheckModelAssignment(cur)) + { + // set its exact model value in the substitution + Node curv = computeConcreteModelValue(cur); + Trace("nl-ext-cm") + << "check-model-bound : exact : " << cur << " = "; + printRationalApprox("nl-ext-cm", curv); + Trace("nl-ext-cm") << std::endl; + bool ret = addCheckModelSubstitution(cur, curv); + AlwaysAssert(ret); + } + } + } + for (const Node& cn : cur) + { + visit.push_back(cn); + } + } + } while (!visit.empty()); + } + + Trace("nl-ext-cm-debug") << " check assertions..." << std::endl; + std::vector check_assertions; + for (const Node& a : assertions) + { + // don't have to check tautological literals + if (d_tautology.find(a) != d_tautology.end()) + { + continue; + } + if (d_check_model_solved.find(a) == d_check_model_solved.end()) + { + Node av = a; + // apply the substitution to a + if (!d_check_model_vars.empty()) + { + av = arithSubstitute(av, d_check_model_vars, d_check_model_subs); + av = Rewriter::rewrite(av); + } + // simple check literal + if (!simpleCheckModelLit(av)) + { + Trace("nl-ext-cm") << "...check-model : assertion failed : " << a + << std::endl; + check_assertions.push_back(av); + Trace("nl-ext-cm-debug") + << "...check-model : failed assertion, value : " << av << std::endl; + } + } + } + + if (!check_assertions.empty()) + { + Trace("nl-ext-cm") << "...simple check failed." << std::endl; + // TODO (#1450) check model for general case + return false; + } + Trace("nl-ext-cm") << "...simple check succeeded!" << std::endl; + + // must assert and re-check if produce models is true + if (options::produceModels()) + { + NodeManager* nm = NodeManager::currentNM(); + // model guard whose semantics is "the model we constructed holds" + Node mg = nm->mkSkolem("model", nm->booleanType()); + gs.push_back(mg); + // assert the constructed model as assertions + for (const std::pair> cb : + d_check_model_bounds) + { + Node l = cb.second.first; + Node u = cb.second.second; + Node v = cb.first; + Node pred = nm->mkNode(AND, nm->mkNode(GEQ, v, l), nm->mkNode(GEQ, u, v)); + pred = nm->mkNode(OR, mg.negate(), pred); + lemmas.push_back(pred); + } + } + return true; +} + +bool NlModel::addCheckModelSubstitution(TNode v, TNode s) +{ + // should not substitute the same variable twice + Trace("nl-ext-model") << "* check model substitution : " << v << " -> " << s + << std::endl; + // should not set exact bound more than once + if (std::find(d_check_model_vars.begin(), d_check_model_vars.end(), v) + != d_check_model_vars.end()) + { + Trace("nl-ext-model") << "...ERROR: already has value." << std::endl; + // this should never happen since substitutions should be applied eagerly + Assert(false); + return false; + } + // if we previously had an approximate bound, the exact bound should be in its + // range + std::map>::iterator itb = + d_check_model_bounds.find(v); + if (itb != d_check_model_bounds.end()) + { + if (s.getConst() >= itb->second.first.getConst() + || s.getConst() <= itb->second.second.getConst()) + { + Trace("nl-ext-model") + << "...ERROR: already has bound which is out of range." << std::endl; + return false; + } + } + std::vector varsTmp; + varsTmp.push_back(v); + std::vector subsTmp; + subsTmp.push_back(s); + for (unsigned i = 0, size = d_check_model_subs.size(); i < size; i++) + { + Node ms = d_check_model_subs[i]; + Node mss = arithSubstitute(ms, varsTmp, subsTmp); + if (mss != ms) + { + mss = Rewriter::rewrite(mss); + } + d_check_model_subs[i] = mss; + } + d_check_model_vars.push_back(v); + d_check_model_subs.push_back(s); + return true; +} + +bool NlModel::addCheckModelBound(TNode v, TNode l, TNode u) +{ + Trace("nl-ext-model") << "* check model bound : " << v << " -> [" << l << " " + << u << "]" << std::endl; + if (l == u) + { + // bound is exact, can add as substitution + return addCheckModelSubstitution(v, l); + } + // should not set a bound for a value that is exact + if (std::find(d_check_model_vars.begin(), d_check_model_vars.end(), v) + != d_check_model_vars.end()) + { + Trace("nl-ext-model") + << "...ERROR: setting bound for variable that already has exact value." + << std::endl; + Assert(false); + return false; + } + Assert(l.isConst()); + Assert(u.isConst()); + Assert(l.getConst() <= u.getConst()); + d_check_model_bounds[v] = std::pair(l, u); + if (Trace.isOn("nl-ext-cm")) + { + Trace("nl-ext-cm") << "check-model-bound : approximate : "; + printRationalApprox("nl-ext-cm", l); + Trace("nl-ext-cm") << " <= " << v << " <= "; + printRationalApprox("nl-ext-cm", u); + Trace("nl-ext-cm") << std::endl; + } + return true; +} + +bool NlModel::hasCheckModelAssignment(Node v) const +{ + if (d_check_model_bounds.find(v) != d_check_model_bounds.end()) + { + return true; + } + return std::find(d_check_model_vars.begin(), d_check_model_vars.end(), v) + != d_check_model_vars.end(); +} + +void NlModel::setUsedApproximate() { d_used_approx = true; } + +bool NlModel::usedApproximate() const { return d_used_approx; } + +void NlModel::addTautology(Node n) +{ + // ensure rewritten + n = Rewriter::rewrite(n); + std::unordered_set visited; + std::vector visit; + TNode cur; + visit.push_back(n); + do + { + cur = visit.back(); + visit.pop_back(); + if (visited.find(cur) == visited.end()) + { + visited.insert(cur); + if (cur.getKind() == AND) + { + // children of AND are also implied + for (const Node& cn : cur) + { + visit.push_back(cn); + } + } + else + { + // is this an arithmetic literal? + Node atom = cur.getKind() == NOT ? cur[0] : cur; + if ((atom.getKind() == EQUAL && atom[0].getType().isReal()) + || atom.getKind() == LEQ) + { + // Add to tautological literals if it does not contain + // non-linear multiplication. We cannot consider literals + // with non-linear multiplication to be tautological since this + // model object is responsible for checking whether they hold. + // (TODO, cvc4-projects #113: revisit this). + if (!expr::hasSubtermKind(NONLINEAR_MULT, atom)) + { + Trace("nl-taut") << "Tautological literal: " << atom << std::endl; + d_tautology.insert(cur); + } + } + } + } + } while (!visit.empty()); +} + +bool NlModel::solveEqualitySimple(Node eq, + unsigned d, + std::vector& lemmas) +{ + Node seq = eq; + if (!d_check_model_vars.empty()) + { + seq = arithSubstitute(eq, d_check_model_vars, d_check_model_subs); + seq = Rewriter::rewrite(seq); + if (seq.isConst()) + { + if (seq.getConst()) + { + d_check_model_solved[eq] = Node::null(); + return true; + } + return false; + } + } + Trace("nl-ext-cms") << "simple solve equality " << seq << "..." << std::endl; + Assert(seq.getKind() == EQUAL); + std::map msum; + if (!ArithMSum::getMonomialSumLit(seq, msum)) + { + Trace("nl-ext-cms") << "...fail, could not determine monomial sum." + << std::endl; + return false; + } + bool is_valid = true; + // the variable we will solve a quadratic equation for + Node var; + Node a = d_zero; + Node b = d_zero; + Node c = d_zero; + NodeManager* nm = NodeManager::currentNM(); + // the list of variables that occur as a monomial in msum, and whose value + // is so far unconstrained in the model. + std::unordered_set unc_vars; + // the list of variables that occur as a factor in a monomial, and whose + // value is so far unconstrained in the model. + std::unordered_set unc_vars_factor; + for (std::pair& m : msum) + { + Node v = m.first; + Node coeff = m.second.isNull() ? d_one : m.second; + if (v.isNull()) + { + c = coeff; + } + else if (v.getKind() == NONLINEAR_MULT) + { + if (v.getNumChildren() == 2 && v[0].isVar() && v[0] == v[1] + && (var.isNull() || var == v[0])) + { + // may solve quadratic + a = coeff; + var = v[0]; + } + else + { + is_valid = false; + Trace("nl-ext-cms-debug") + << "...invalid due to non-linear monomial " << v << std::endl; + // may wish to set an exact bound for a factor and repeat + for (const Node& vc : v) + { + unc_vars_factor.insert(vc); + } + } + } + else if (!v.isVar() || (!var.isNull() && var != v)) + { + Trace("nl-ext-cms-debug") + << "...invalid due to factor " << v << std::endl; + // cannot solve multivariate + if (is_valid) + { + is_valid = false; + // if b is non-zero, then var is also an unconstrained variable + if (b != d_zero) + { + unc_vars.insert(var); + unc_vars_factor.insert(var); + } + } + // if v is unconstrained, we may turn this equality into a substitution + unc_vars.insert(v); + unc_vars_factor.insert(v); + } + else + { + // set the variable to solve for + b = coeff; + var = v; + } + } + if (!is_valid) + { + // see if we can solve for a variable? + for (const Node& uv : unc_vars) + { + Trace("nl-ext-cm-debug") << "check subs var : " << uv << std::endl; + // cannot already have a bound + if (uv.isVar() && !hasCheckModelAssignment(uv)) + { + Node slv; + Node veqc; + if (ArithMSum::isolate(uv, msum, veqc, slv, EQUAL) != 0) + { + Assert(!slv.isNull()); + // Currently do not support substitution-with-coefficients. + // We also ensure types are correct here, which avoids substituting + // a term of non-integer type for a variable of integer type. + if (veqc.isNull() && !expr::hasSubterm(slv, uv) + && slv.getType().isSubtypeOf(uv.getType())) + { + Trace("nl-ext-cm") + << "check-model-subs : " << uv << " -> " << slv << std::endl; + bool ret = addCheckModelSubstitution(uv, slv); + if (ret) + { + Trace("nl-ext-cms") << "...success, model substitution " << uv + << " -> " << slv << std::endl; + d_check_model_solved[eq] = uv; + } + return ret; + } + } + } + } + // see if we can assign a variable to a constant + for (const Node& uvf : unc_vars_factor) + { + Trace("nl-ext-cm-debug") << "check set var : " << uvf << std::endl; + // cannot already have a bound + if (uvf.isVar() && !hasCheckModelAssignment(uvf)) + { + Node uvfv = computeConcreteModelValue(uvf); + Trace("nl-ext-cm") << "check-model-bound : exact : " << uvf << " = "; + printRationalApprox("nl-ext-cm", uvfv); + Trace("nl-ext-cm") << std::endl; + bool ret = addCheckModelSubstitution(uvf, uvfv); + // recurse + return ret ? solveEqualitySimple(eq, d, lemmas) : false; + } + } + Trace("nl-ext-cms") << "...fail due to constrained invalid terms." + << std::endl; + return false; + } + else if (var.isNull() || var.getType().isInteger()) + { + // cannot solve quadratic equations for integer variables + Trace("nl-ext-cms") << "...fail due to variable to solve for." << std::endl; + return false; + } + + // we are linear, it is simple + if (a == d_zero) + { + if (b == d_zero) + { + Trace("nl-ext-cms") << "...fail due to zero a/b." << std::endl; + Assert(false); + return false; + } + Node val = nm->mkConst(-c.getConst() / b.getConst()); + Trace("nl-ext-cm") << "check-model-bound : exact : " << var << " = "; + printRationalApprox("nl-ext-cm", val); + Trace("nl-ext-cm") << std::endl; + bool ret = addCheckModelSubstitution(var, val); + if (ret) + { + Trace("nl-ext-cms") << "...success, solved linear." << std::endl; + d_check_model_solved[eq] = var; + } + return ret; + } + Trace("nl-ext-quad") << "Solve quadratic : " << seq << std::endl; + Trace("nl-ext-quad") << " a : " << a << std::endl; + Trace("nl-ext-quad") << " b : " << b << std::endl; + Trace("nl-ext-quad") << " c : " << c << std::endl; + Node two_a = nm->mkNode(MULT, d_two, a); + two_a = Rewriter::rewrite(two_a); + Node sqrt_val = nm->mkNode( + MINUS, nm->mkNode(MULT, b, b), nm->mkNode(MULT, d_two, two_a, c)); + sqrt_val = Rewriter::rewrite(sqrt_val); + Trace("nl-ext-quad") << "Will approximate sqrt " << sqrt_val << std::endl; + Assert(sqrt_val.isConst()); + // if it is negative, then we are in conflict + if (sqrt_val.getConst().sgn() == -1) + { + Node conf = seq.negate(); + Trace("nl-ext-lemma") << "NlModel::Lemma : quadratic no root : " << conf + << std::endl; + lemmas.push_back(conf); + Trace("nl-ext-cms") << "...fail due to negative discriminant." << std::endl; + return false; + } + if (hasCheckModelAssignment(var)) + { + Trace("nl-ext-cms") << "...fail due to bounds on variable to solve for." + << std::endl; + // two quadratic equations for same variable, give up + return false; + } + // approximate the square root of sqrt_val + Node l, u; + if (!getApproximateSqrt(sqrt_val, l, u, 15 + d)) + { + Trace("nl-ext-cms") << "...fail, could not approximate sqrt." << std::endl; + return false; + } + d_used_approx = true; + Trace("nl-ext-quad") << "...got " << l << " <= sqrt(" << sqrt_val + << ") <= " << u << std::endl; + Node negb = nm->mkConst(-b.getConst()); + Node coeffa = nm->mkConst(Rational(1) / two_a.getConst()); + // two possible bound regions + Node bounds[2][2]; + Node diff_bound[2]; + Node m_var = computeConcreteModelValue(var); + Assert(m_var.isConst()); + for (unsigned r = 0; r < 2; r++) + { + for (unsigned b2 = 0; b2 < 2; b2++) + { + Node val = b2 == 0 ? l : u; + // (-b +- approx_sqrt( b^2 - 4ac ))/2a + Node approx = nm->mkNode( + MULT, coeffa, nm->mkNode(r == 0 ? MINUS : PLUS, negb, val)); + approx = Rewriter::rewrite(approx); + bounds[r][b2] = approx; + Assert(approx.isConst()); + } + if (bounds[r][0].getConst() > bounds[r][1].getConst()) + { + // ensure bound is (lower, upper) + Node tmp = bounds[r][0]; + bounds[r][0] = bounds[r][1]; + bounds[r][1] = tmp; + } + Node diff = + nm->mkNode(MINUS, + m_var, + nm->mkNode(MULT, + nm->mkConst(Rational(1) / Rational(2)), + nm->mkNode(PLUS, bounds[r][0], bounds[r][1]))); + Trace("nl-ext-cm-debug") << "Bound option #" << r << " : "; + printRationalApprox("nl-ext-cm-debug", bounds[r][0]); + Trace("nl-ext-cm-debug") << "..."; + printRationalApprox("nl-ext-cm-debug", bounds[r][1]); + Trace("nl-ext-cm-debug") << std::endl; + diff = Rewriter::rewrite(diff); + Assert(diff.isConst()); + diff = nm->mkConst(diff.getConst().abs()); + diff_bound[r] = diff; + Trace("nl-ext-cm-debug") << "...diff from model value ("; + printRationalApprox("nl-ext-cm-debug", m_var); + Trace("nl-ext-cm-debug") << ") is "; + printRationalApprox("nl-ext-cm-debug", diff_bound[r]); + Trace("nl-ext-cm-debug") << std::endl; + } + // take the one that var is closer to in the model + Node cmp = nm->mkNode(GEQ, diff_bound[0], diff_bound[1]); + cmp = Rewriter::rewrite(cmp); + Assert(cmp.isConst()); + unsigned r_use_index = cmp == d_true ? 1 : 0; + Trace("nl-ext-cm") << "check-model-bound : approximate (sqrt) : "; + printRationalApprox("nl-ext-cm", bounds[r_use_index][0]); + Trace("nl-ext-cm") << " <= " << var << " <= "; + printRationalApprox("nl-ext-cm", bounds[r_use_index][1]); + Trace("nl-ext-cm") << std::endl; + bool ret = + addCheckModelBound(var, bounds[r_use_index][0], bounds[r_use_index][1]); + if (ret) + { + d_check_model_solved[eq] = var; + Trace("nl-ext-cms") << "...success, solved quadratic." << std::endl; + } + return ret; +} + +bool NlModel::simpleCheckModelLit(Node lit) +{ + Trace("nl-ext-cms") << "*** Simple check-model lit for " << lit << "..." + << std::endl; + if (lit.isConst()) + { + Trace("nl-ext-cms") << " return constant." << std::endl; + return lit.getConst(); + } + NodeManager* nm = NodeManager::currentNM(); + bool pol = lit.getKind() != kind::NOT; + Node atom = lit.getKind() == kind::NOT ? lit[0] : lit; + + if (atom.getKind() == EQUAL) + { + // x = a is ( x >= a ^ x <= a ) + for (unsigned i = 0; i < 2; i++) + { + Node lit2 = nm->mkNode(GEQ, atom[i], atom[1 - i]); + if (!pol) + { + lit2 = lit2.negate(); + } + lit2 = Rewriter::rewrite(lit2); + bool success = simpleCheckModelLit(lit2); + if (success != pol) + { + // false != true -> one conjunct of equality is false, we fail + // true != false -> one disjunct of disequality is true, we succeed + return success; + } + } + // both checks passed and polarity is true, or both checks failed and + // polarity is false + return pol; + } + else if (atom.getKind() != GEQ) + { + Trace("nl-ext-cms") << " failed due to unknown literal." << std::endl; + return false; + } + // get the monomial sum + std::map msum; + if (!ArithMSum::getMonomialSumLit(atom, msum)) + { + Trace("nl-ext-cms") << " failed due to get msum." << std::endl; + return false; + } + // simple interval analysis + if (simpleCheckModelMsum(msum, pol)) + { + return true; + } + // can also try reasoning about univariate quadratic equations + Trace("nl-ext-cms-debug") + << "* Try univariate quadratic analysis..." << std::endl; + std::vector vs_invalid; + std::unordered_set vs; + std::map v_a; + std::map v_b; + // get coefficients... + for (std::pair& m : msum) + { + Node v = m.first; + if (!v.isNull()) + { + if (v.isVar()) + { + v_b[v] = m.second.isNull() ? d_one : m.second; + vs.insert(v); + } + else if (v.getKind() == NONLINEAR_MULT && v.getNumChildren() == 2 + && v[0] == v[1] && v[0].isVar()) + { + v_a[v[0]] = m.second.isNull() ? d_one : m.second; + vs.insert(v[0]); + } + else + { + vs_invalid.push_back(v); + } + } + } + // solve the valid variables... + Node invalid_vsum = vs_invalid.empty() ? d_zero + : (vs_invalid.size() == 1 + ? vs_invalid[0] + : nm->mkNode(PLUS, vs_invalid)); + // substitution to try + std::vector qvars; + std::vector qsubs; + for (const Node& v : vs) + { + // is it a valid variable? + std::map>::iterator bit = + d_check_model_bounds.find(v); + if (!expr::hasSubterm(invalid_vsum, v) && bit != d_check_model_bounds.end()) + { + std::map::iterator it = v_a.find(v); + if (it != v_a.end()) + { + Node a = it->second; + Assert(a.isConst()); + int asgn = a.getConst().sgn(); + Assert(asgn != 0); + Node t = nm->mkNode(MULT, a, v, v); + Node b = d_zero; + it = v_b.find(v); + if (it != v_b.end()) + { + b = it->second; + t = nm->mkNode(PLUS, t, nm->mkNode(MULT, b, v)); + } + t = Rewriter::rewrite(t); + Trace("nl-ext-cms-debug") << "Trying to find min/max for quadratic " + << t << "..." << std::endl; + Trace("nl-ext-cms-debug") << " a = " << a << std::endl; + Trace("nl-ext-cms-debug") << " b = " << b << std::endl; + // find maximal/minimal value on the interval + Node apex = nm->mkNode( + DIVISION, nm->mkNode(UMINUS, b), nm->mkNode(MULT, d_two, a)); + apex = Rewriter::rewrite(apex); + Assert(apex.isConst()); + // for lower, upper, whether we are greater than the apex + bool cmp[2]; + Node boundn[2]; + for (unsigned r = 0; r < 2; r++) + { + boundn[r] = r == 0 ? bit->second.first : bit->second.second; + Node cmpn = nm->mkNode(GT, boundn[r], apex); + cmpn = Rewriter::rewrite(cmpn); + Assert(cmpn.isConst()); + cmp[r] = cmpn.getConst(); + } + Trace("nl-ext-cms-debug") << " apex " << apex << std::endl; + Trace("nl-ext-cms-debug") + << " lower " << boundn[0] << ", cmp: " << cmp[0] << std::endl; + Trace("nl-ext-cms-debug") + << " upper " << boundn[1] << ", cmp: " << cmp[1] << std::endl; + Assert(boundn[0].getConst() + <= boundn[1].getConst()); + Node s; + qvars.push_back(v); + if (cmp[0] != cmp[1]) + { + Assert(!cmp[0] && cmp[1]); + // does the sign match the bound? + if ((asgn == 1) == pol) + { + // the apex is the max/min value + s = apex; + Trace("nl-ext-cms-debug") << " ...set to apex." << std::endl; + } + else + { + // it is one of the endpoints, plug in and compare + Node tcmpn[2]; + for (unsigned r = 0; r < 2; r++) + { + qsubs.push_back(boundn[r]); + Node ts = arithSubstitute(t, qvars, qsubs); + tcmpn[r] = Rewriter::rewrite(ts); + qsubs.pop_back(); + } + Node tcmp = nm->mkNode(LT, tcmpn[0], tcmpn[1]); + Trace("nl-ext-cms-debug") + << " ...both sides of apex, compare " << tcmp << std::endl; + tcmp = Rewriter::rewrite(tcmp); + Assert(tcmp.isConst()); + unsigned bindex_use = (tcmp.getConst() == pol) ? 1 : 0; + Trace("nl-ext-cms-debug") + << " ...set to " << (bindex_use == 1 ? "upper" : "lower") + << std::endl; + s = boundn[bindex_use]; + } + } + else + { + // both to one side of the apex + // we figure out which bound to use (lower or upper) based on + // three factors: + // (1) whether a's sign is positive, + // (2) whether we are greater than the apex of the parabola, + // (3) the polarity of the constraint, i.e. >= or <=. + // there are 8 cases of these factors, which we test here. + unsigned bindex_use = (((asgn == 1) == cmp[0]) == pol) ? 0 : 1; + Trace("nl-ext-cms-debug") + << " ...set to " << (bindex_use == 1 ? "upper" : "lower") + << std::endl; + s = boundn[bindex_use]; + } + Assert(!s.isNull()); + qsubs.push_back(s); + Trace("nl-ext-cms") << "* set bound based on quadratic : " << v + << " -> " << s << std::endl; + } + } + } + if (!qvars.empty()) + { + Assert(qvars.size() == qsubs.size()); + Node slit = arithSubstitute(lit, qvars, qsubs); + slit = Rewriter::rewrite(slit); + return simpleCheckModelLit(slit); + } + return false; +} + +bool NlModel::simpleCheckModelMsum(const std::map& msum, bool pol) +{ + Trace("nl-ext-cms-debug") << "* Try simple interval analysis..." << std::endl; + NodeManager* nm = NodeManager::currentNM(); + // map from transcendental functions to whether they were set to lower + // bound + bool simpleSuccess = true; + std::map set_bound; + std::vector sum_bound; + for (const std::pair& m : msum) + { + Node v = m.first; + if (v.isNull()) + { + sum_bound.push_back(m.second.isNull() ? d_one : m.second); + } + else + { + Trace("nl-ext-cms-debug") << "- monomial : " << v << std::endl; + // --- whether we should set a lower bound for this monomial + bool set_lower = + (m.second.isNull() || m.second.getConst().sgn() == 1) + == pol; + Trace("nl-ext-cms-debug") + << "set bound to " << (set_lower ? "lower" : "upper") << std::endl; + + // --- Collect variables and factors in v + std::vector vars; + std::vector factors; + if (v.getKind() == NONLINEAR_MULT) + { + unsigned last_start = 0; + for (unsigned i = 0, nchildren = v.getNumChildren(); i < nchildren; i++) + { + // are we at the end? + if (i + 1 == nchildren || v[i + 1] != v[i]) + { + unsigned vfact = 1 + (i - last_start); + last_start = (i + 1); + vars.push_back(v[i]); + factors.push_back(vfact); + } + } + } + else + { + vars.push_back(v); + factors.push_back(1); + } + + // --- Get the lower and upper bounds and sign information. + // Whether we have an (odd) number of negative factors in vars, apart + // from the variable at choose_index. + bool has_neg_factor = false; + int choose_index = -1; + std::vector ls; + std::vector us; + // the relevant sign information for variables with odd exponents: + // 1: both signs of the interval of this variable are positive, + // -1: both signs of the interval of this variable are negative. + std::vector signs; + Trace("nl-ext-cms-debug") << "get sign information..." << std::endl; + for (unsigned i = 0, size = vars.size(); i < size; i++) + { + Node vc = vars[i]; + unsigned vcfact = factors[i]; + if (Trace.isOn("nl-ext-cms-debug")) + { + Trace("nl-ext-cms-debug") << "-- " << vc; + if (vcfact > 1) + { + Trace("nl-ext-cms-debug") << "^" << vcfact; + } + Trace("nl-ext-cms-debug") << " "; + } + std::map>::iterator bit = + d_check_model_bounds.find(vc); + // if there is a model bound for this term + if (bit != d_check_model_bounds.end()) + { + Node l = bit->second.first; + Node u = bit->second.second; + ls.push_back(l); + us.push_back(u); + int vsign = 0; + if (vcfact % 2 == 1) + { + vsign = 1; + int lsgn = l.getConst().sgn(); + int usgn = u.getConst().sgn(); + Trace("nl-ext-cms-debug") + << "bound_sign(" << lsgn << "," << usgn << ") "; + if (lsgn == -1) + { + if (usgn < 1) + { + // must have a negative factor + has_neg_factor = !has_neg_factor; + vsign = -1; + } + else if (choose_index == -1) + { + // set the choose index to this + choose_index = i; + vsign = 0; + } + else + { + // ambiguous, can't determine the bound + Trace("nl-ext-cms") + << " failed due to ambiguious monomial." << std::endl; + return false; + } + } + } + Trace("nl-ext-cms-debug") << " -> " << vsign << std::endl; + signs.push_back(vsign); + } + else + { + Trace("nl-ext-cms-debug") << std::endl; + Trace("nl-ext-cms") + << " failed due to unknown bound for " << vc << std::endl; + // should either assign a model bound or eliminate the variable + // via substitution + Assert(false); + return false; + } + } + // whether we will try to minimize/maximize (-1/1) the absolute value + int setAbs = (set_lower == has_neg_factor) ? 1 : -1; + Trace("nl-ext-cms-debug") + << "set absolute value to " << (setAbs == 1 ? "maximal" : "minimal") + << std::endl; + + std::vector vbs; + Trace("nl-ext-cms-debug") << "set bounds..." << std::endl; + for (unsigned i = 0, size = vars.size(); i < size; i++) + { + Node vc = vars[i]; + unsigned vcfact = factors[i]; + Node l = ls[i]; + Node u = us[i]; + bool vc_set_lower; + int vcsign = signs[i]; + Trace("nl-ext-cms-debug") + << "Bounds for " << vc << " : " << l << ", " << u + << ", sign : " << vcsign << ", factor : " << vcfact << std::endl; + if (l == u) + { + // by convention, always say it is lower if they are the same + vc_set_lower = true; + Trace("nl-ext-cms-debug") + << "..." << vc << " equal bound, set to lower" << std::endl; + } + else + { + if (vcfact % 2 == 0) + { + // minimize or maximize its absolute value + Rational la = l.getConst().abs(); + Rational ua = u.getConst().abs(); + if (la == ua) + { + // by convention, always say it is lower if abs are the same + vc_set_lower = true; + Trace("nl-ext-cms-debug") + << "..." << vc << " equal abs, set to lower" << std::endl; + } + else + { + vc_set_lower = (la > ua) == (setAbs == 1); + } + } + else if (signs[i] == 0) + { + // we choose this index to match the overall set_lower + vc_set_lower = set_lower; + } + else + { + vc_set_lower = (signs[i] != setAbs); + } + Trace("nl-ext-cms-debug") + << "..." << vc << " set to " << (vc_set_lower ? "lower" : "upper") + << std::endl; + } + // check whether this is a conflicting bound + std::map::iterator itsb = set_bound.find(vc); + if (itsb == set_bound.end()) + { + set_bound[vc] = vc_set_lower; + } + else if (itsb->second != vc_set_lower) + { + Trace("nl-ext-cms") + << " failed due to conflicting bound for " << vc << std::endl; + return false; + } + // must over/under approximate based on vc_set_lower, computed above + Node vb = vc_set_lower ? l : u; + for (unsigned i2 = 0; i2 < vcfact; i2++) + { + vbs.push_back(vb); + } + } + if (!simpleSuccess) + { + break; + } + Node vbound = vbs.size() == 1 ? vbs[0] : nm->mkNode(MULT, vbs); + sum_bound.push_back(ArithMSum::mkCoeffTerm(m.second, vbound)); + } + } + // if the exact bound was computed via simple analysis above + // make the bound + Node bound; + if (sum_bound.size() > 1) + { + bound = nm->mkNode(kind::PLUS, sum_bound); + } + else if (sum_bound.size() == 1) + { + bound = sum_bound[0]; + } + else + { + bound = d_zero; + } + // make the comparison + Node comp = nm->mkNode(kind::GEQ, bound, d_zero); + if (!pol) + { + comp = comp.negate(); + } + Trace("nl-ext-cms") << " comparison is : " << comp << std::endl; + comp = Rewriter::rewrite(comp); + Assert(comp.isConst()); + Trace("nl-ext-cms") << " returned : " << comp << std::endl; + return comp == d_true; +} + +bool NlModel::getApproximateSqrt(Node c, Node& l, Node& u, unsigned iter) const +{ + Assert(c.isConst()); + if (c == d_one || c == d_zero) + { + l = c; + u = c; + return true; + } + Rational rc = c.getConst(); + + Rational rl = rc < Rational(1) ? rc : Rational(1); + Rational ru = rc < Rational(1) ? Rational(1) : rc; + unsigned count = 0; + Rational half = Rational(1) / Rational(2); + while (count < iter) + { + Rational curr = half * (rl + ru); + Rational curr_sq = curr * curr; + if (curr_sq == rc) + { + rl = curr; + ru = curr; + break; + } + else if (curr_sq < rc) + { + rl = curr; + } + else + { + ru = curr; + } + count++; + } + + NodeManager* nm = NodeManager::currentNM(); + l = nm->mkConst(rl); + u = nm->mkConst(ru); + return true; +} + +void NlModel::printModelValue(const char* c, Node n, unsigned prec) const +{ + if (Trace.isOn(c)) + { + Trace(c) << " " << n << " -> "; + for (int i = 1; i >= 0; --i) + { + std::map::const_iterator it = d_mv[i].find(n); + Assert(it != d_mv[i].end()); + if (it->second.isConst()) + { + printRationalApprox(c, it->second, prec); + } + else + { + Trace(c) << "?"; + } + Trace(c) << (i == 1 ? " [actual: " : " ]"); + } + Trace(c) << std::endl; + } +} + +void NlModel::getModelValueRepair( + std::map& arithModel, + std::map>& approximations) +{ + Trace("nl-model") << "NlModel::getModelValueRepair:" << std::endl; + + // Record the approximations we used. This code calls the + // recordApproximation method of the model, which overrides the model + // values for variables that we solved for, using techniques specific to + // this class. + NodeManager* nm = NodeManager::currentNM(); + for (const std::pair>& cb : + d_check_model_bounds) + { + Node l = cb.second.first; + Node u = cb.second.second; + Node pred; + Node v = cb.first; + if (l != u) + { + pred = nm->mkNode(AND, nm->mkNode(GEQ, v, l), nm->mkNode(GEQ, u, v)); + Trace("nl-model") << v << " approximated as " << pred << std::endl; + Node witness; + if (options::modelWitnessValue()) + { + // witness is the midpoint + witness = nm->mkNode( + MULT, nm->mkConst(Rational(1, 2)), nm->mkNode(PLUS, l, u)); + witness = Rewriter::rewrite(witness); + Trace("nl-model") << v << " witness is " << witness << std::endl; + } + approximations[v] = std::pair(pred, witness); + } + else + { + // overwrite + arithModel[v] = l; + Trace("nl-model") << v << " exact approximation is " << l << std::endl; + } + } + // Also record the exact values we used. An exact value can be seen as a + // special kind approximation of the form (witness x. x = exact_value). + // Notice that the above term gets rewritten such that the choice function + // is eliminated. + for (size_t i = 0, num = d_check_model_vars.size(); i < num; i++) + { + Node v = d_check_model_vars[i]; + Node s = d_check_model_subs[i]; + // overwrite + arithModel[v] = s; + Trace("nl-model") << v << " solved is " << s << std::endl; + } + + // multiplication terms should not be given values; their values are + // implied by the monomials that they consist of + std::vector amErase; + for (const std::pair& am : arithModel) + { + if (am.first.getKind() == NONLINEAR_MULT) + { + amErase.push_back(am.first); + } + } + for (const Node& ae : amErase) + { + arithModel.erase(ae); + } +} + +} // namespace nl +} // namespace arith +} // namespace theory +} // namespace CVC4 diff --git a/src/theory/arith/nl/nl_model.h b/src/theory/arith/nl/nl_model.h new file mode 100644 index 000000000..61193fc12 --- /dev/null +++ b/src/theory/arith/nl/nl_model.h @@ -0,0 +1,335 @@ +/********************* */ +/*! \file nl_model.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 Model object for the non-linear extension class + **/ + +#ifndef CVC4__THEORY__ARITH__NL__NL_MODEL_H +#define CVC4__THEORY__ARITH__NL__NL_MODEL_H + +#include +#include +#include + +#include "context/cdo.h" +#include "context/context.h" +#include "expr/kind.h" +#include "expr/node.h" +#include "theory/theory_model.h" + +namespace CVC4 { +namespace theory { +namespace arith { +namespace nl { + +class NonlinearExtension; + +/** Non-linear model finder + * + * This class is responsible for all queries related to the (candidate) model + * that is being processed by the non-linear arithmetic solver. It further + * implements techniques for finding modifications to the current candidate + * model in the case it can determine that a model exists. These include + * techniques based on solving (quadratic) equations and bound analysis. + */ +class NlModel +{ + friend class NonlinearExtension; + + public: + NlModel(context::Context* c); + ~NlModel(); + /** reset + * + * This method is called once at the beginning of a last call effort check, + * where m is the model of the theory of arithmetic. This method resets the + * cache of computed model values. + */ + void reset(TheoryModel* m, std::map& arithModel); + /** reset check + * + * This method is called when the non-linear arithmetic solver restarts + * its computation of lemmas and models during a last call effort check. + */ + void resetCheck(); + /** compute model value + * + * This computes model values for terms based on two semantics, a "concrete" + * semantics and an "abstract" semantics. + * + * if isConcrete is true, this means compute the value of n based on its + * children recursively. (we call this its "concrete" value) + * if isConcrete is false, this means lookup the value of n in the model. + * (we call this its "abstract" value) + * In other words, !isConcrete treats multiplication terms and transcendental + * function applications as variables, whereas isConcrete computes their + * actual values based on the semantics of multiplication. This is a key + * distinction used in the model-based refinement scheme in Cimatti et al. + * TACAS 2017. + * + * For example, if M( a ) = 2, M( b ) = 3, M( a*b ) = 5, i.e. the variable + * for a*b has been assigned a value 5 by the linear solver, then : + * + * computeModelValue( a*b, true ) = + * computeModelValue( a, true )*computeModelValue( b, true ) = 2*3 = 6 + * whereas: + * computeModelValue( a*b, false ) = 5 + */ + Node computeConcreteModelValue(Node n); + Node computeAbstractModelValue(Node n); + Node computeModelValue(Node n, bool isConcrete); + + /** Compare arithmetic terms i and j based an ordering. + * + * This returns: + * -1 if i < j, 1 if i > j, or 0 if i == j + * + * If isConcrete is true, we consider the concrete model values of i and j, + * otherwise, we consider their abstract model values. For definitions of + * concrete vs abstract model values, see NlModel::computeModelValue. + * + * If isAbsolute is true, we compare the absolute value of thee above + * values. + */ + int compare(Node i, Node j, bool isConcrete, bool isAbsolute); + /** Compare arithmetic terms i and j based an ordering. + * + * This returns: + * -1 if i < j, 1 if i > j, or 0 if i == j + * + * If isAbsolute is true, we compare the absolute value of i and j + */ + int compareValue(Node i, Node j, bool isAbsolute) const; + + //------------------------------ recording model substitutions and bounds + /** add check model substitution + * + * Adds the model substitution v -> s. This applies the substitution + * { v -> s } to each term in d_check_model_subs and adds v,s to + * d_check_model_vars and d_check_model_subs respectively. + * If this method returns false, then the substitution v -> s is inconsistent + * with the current substitution and bounds. + */ + bool addCheckModelSubstitution(TNode v, TNode s); + /** add check model bound + * + * Adds the bound x -> < l, u > to the map above, and records the + * approximation ( x, l <= x <= u ) in the model. This method returns false + * if the bound is inconsistent with the current model substitution or + * bounds. + */ + bool addCheckModelBound(TNode v, TNode l, TNode u); + /** has check model assignment + * + * Have we assigned v in the current checkModel(...) call? + * + * This method returns true if variable v is in the domain of + * d_check_model_bounds or if it occurs in d_check_model_vars. + */ + bool hasCheckModelAssignment(Node v) const; + /** Check model + * + * Checks the current model based on solving for equalities, and using error + * bounds on the Taylor approximation. + * + * If this function returns true, then all assertions in the input argument + * "assertions" are satisfied for all interpretations of variables within + * their computed bounds (as stored in d_check_model_bounds). + * + * For details, see Section 3 of Cimatti et al CADE 2017 under the heading + * "Detecting Satisfiable Formulas". + * + * d is a degree indicating how precise our computations are. + */ + bool checkModel(const std::vector& assertions, + const std::vector& false_asserts, + unsigned d, + std::vector& lemmas, + std::vector& gs); + /** + * Set that we have used an approximation during this check. This flag is + * reset on a call to resetCheck. It is set when we use reasoning that + * is limited by a degree of precision we are using. In other words, if we + * used an approximation, then we maybe could still establish a lemma or + * determine the input is SAT if we increased our precision. + */ + void setUsedApproximate(); + /** Did we use an approximation during this check? */ + bool usedApproximate() const; + /** Set tautology + * + * This states that formula n is a tautology (satisfied in all models). + * We call this on internally generated lemmas. This method computes a + * set of literals that are implied by n, that are hence tautological + * as well, such as: + * l_pi <= real.pi <= u_pi (pi approximations) + * sin(x) = -1*sin(-x) + * where these literals are internally generated for the purposes + * of guiding the models of the linear solver. + * + * TODO (cvc4-projects #113: would be helpful if we could do this even + * more aggressively by ignoring all internally generated literals. + * + * Tautological literals do not need be checked during checkModel. + */ + void addTautology(Node n); + //------------------------------ end recording model substitutions and bounds + + /** print model value, for debugging. + * + * This prints both the abstract and concrete model values for arithmetic + * term n on Trace c with precision prec. + */ + void printModelValue(const char* c, Node n, unsigned prec = 5) const; + /** get model value repair + * + * This gets mappings that indicate how to repair the model generated by the + * linear arithmetic solver. This method should be called after a successful + * call to checkModel above. + * + * The mapping arithModel is updated by this method to map arithmetic terms v + * to their (exact) value that was computed during checkModel; the mapping + * approximations is updated to store approximate values in the form of a + * pair (P, w), where P is a predicate that describes the possible values of + * v and w is a witness point that satisfies this predicate. + */ + void getModelValueRepair( + std::map& arithModel, + std::map>& approximations); + + private: + /** The current model */ + TheoryModel* d_model; + /** Get the model value of n from the model object above */ + Node getValueInternal(Node n) const; + /** Does the equality engine of the model have term n? */ + bool hasTerm(Node n) const; + /** Get the representative of n in the model */ + Node getRepresentative(Node n) const; + + //---------------------------check model + /** solve equality simple + * + * This method is used during checkModel(...). It takes as input an + * equality eq. If it returns true, then eq is correct-by-construction based + * on the information stored in our model representation (see + * d_check_model_vars, d_check_model_subs, d_check_model_bounds), and eq + * is added to d_check_model_solved. The equality eq may involve any + * number of variables, and monomials of arbitrary degree. If this method + * returns false, then we did not show that the equality was true in the + * model. This method uses incomplete techniques based on interval + * analysis and quadratic equation solving. + * + * If it can be shown that the equality must be false in the current + * model, then we may add a lemma to lemmas explaining why this is the case. + * For instance, if eq reduces to a univariate quadratic equation with no + * root, we send a conflict clause of the form a*x^2 + b*x + c != 0. + */ + bool solveEqualitySimple(Node eq, unsigned d, std::vector& lemmas); + + /** simple check model for transcendental functions for literal + * + * This method returns true if literal is true for all interpretations of + * transcendental functions within their error bounds (as stored + * in d_check_model_bounds). This is determined by a simple under/over + * approximation of the value of sum of (linear) monomials. For example, + * if we determine that .8 < sin( 1 ) < .9, this function will return + * true for literals like: + * 2.0*sin( 1 ) > 1.5 + * -1.0*sin( 1 ) < -0.79 + * -1.0*sin( 1 ) > -0.91 + * sin( 1 )*sin( 1 ) + sin( 1 ) > 0.0 + * It will return false for literals like: + * sin( 1 ) > 0.85 + * It will also return false for literals like: + * -0.3*sin( 1 )*sin( 2 ) + sin( 2 ) > .7 + * sin( sin( 1 ) ) > .5 + * since the bounds on these terms cannot quickly be determined. + */ + bool simpleCheckModelLit(Node lit); + bool simpleCheckModelMsum(const std::map& msum, bool pol); + //---------------------------end check model + /** get approximate sqrt + * + * This approximates the square root of positive constant c. If this method + * returns true, then l and u are updated to constants such that + * l <= sqrt( c ) <= u + * The argument iter is the number of iterations in the binary search to + * perform. By default, this is set to 15, which is usually enough to be + * precise in the majority of simple cases, whereas not prohibitively + * expensive to compute. + */ + bool getApproximateSqrt(Node c, Node& l, Node& u, unsigned iter = 15) const; + + /** commonly used terms */ + Node d_zero; + Node d_one; + Node d_two; + Node d_true; + Node d_false; + Node d_null; + /** + * The values that the arithmetic theory solver assigned in the model. This + * corresponds to exactly the set of equalities that TheoryArith is currently + * sending to TheoryModel during collectModelInfo. + */ + std::map d_arithVal; + /** cache of model values + * + * Stores the the concrete/abstract model values. This is a cache of the + * computeModelValue method. + */ + std::map d_mv[2]; + /** + * A substitution from variables that appear in assertions to a solved form + * term. These vectors are ordered in the form: + * x_1 -> t_1 ... x_n -> t_n + * where x_i is not in the free variables of t_j for j>=i. + */ + std::vector d_check_model_vars; + std::vector d_check_model_subs; + /** lower and upper bounds for check model + * + * For each term t in the domain of this map, if this stores the pair + * (c_l, c_u) then the model M is such that c_l <= M( t ) <= c_u. + * + * We add terms whose value is approximated in the model to this map, which + * includes: + * (1) applications of transcendental functions, whose value is approximated + * by the Taylor series, + * (2) variables we have solved quadratic equations for, whose value + * involves approximations of square roots. + */ + std::map> d_check_model_bounds; + /** + * The map from literals that our model construction solved, to the variable + * that was solved for. Examples of such literals are: + * (1) Equalities x = t, which we turned into a model substitution x -> t, + * where x not in FV( t ), and + * (2) Equalities a*x*x + b*x + c = 0, which we turned into a model bound + * -b+s*sqrt(b*b-4*a*c)/2a - E <= x <= -b+s*sqrt(b*b-4*a*c)/2a + E. + * + * These literals are exempt from check-model, since they are satisfied by + * definition of our model construction. + */ + std::unordered_map d_check_model_solved; + /** did we use an approximation on this call to last-call effort? */ + bool d_used_approx; + /** the set of all tautological literals */ + std::unordered_set d_tautology; +}; /* class NlModel */ + +} // namespace nl +} // namespace arith +} // namespace theory +} // namespace CVC4 + +#endif /* CVC4__THEORY__ARITH__NONLINEAR_EXTENSION_H */ diff --git a/src/theory/arith/nl/nl_monomial.cpp b/src/theory/arith/nl/nl_monomial.cpp new file mode 100644 index 000000000..e8e7aceba --- /dev/null +++ b/src/theory/arith/nl/nl_monomial.cpp @@ -0,0 +1,336 @@ +/********************* */ +/*! \file nl_monomial.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 monomials + **/ + +#include "theory/arith/nl/nl_monomial.h" + +#include "theory/arith/arith_utilities.h" +#include "theory/arith/nl/nl_lemma_utils.h" +#include "theory/rewriter.h" + +using namespace CVC4::kind; + +namespace CVC4 { +namespace theory { +namespace arith { +namespace nl { + +// Returns a[key] if key is in a or value otherwise. +unsigned getCountWithDefault(const NodeMultiset& a, Node key, unsigned value) +{ + NodeMultiset::const_iterator it = a.find(key); + return (it == a.end()) ? value : it->second; +} +// Given two multisets return the multiset difference a \ b. +NodeMultiset diffMultiset(const NodeMultiset& a, const NodeMultiset& b) +{ + NodeMultiset difference; + for (NodeMultiset::const_iterator it_a = a.begin(); it_a != a.end(); ++it_a) + { + Node key = it_a->first; + const unsigned a_value = it_a->second; + const unsigned b_value = getCountWithDefault(b, key, 0); + if (a_value > b_value) + { + difference[key] = a_value - b_value; + } + } + return difference; +} + +// Return a vector containing a[key] repetitions of key in a multiset a. +std::vector ExpandMultiset(const NodeMultiset& a) +{ + std::vector expansion; + for (NodeMultiset::const_iterator it_a = a.begin(); it_a != a.end(); ++it_a) + { + expansion.insert(expansion.end(), it_a->second, it_a->first); + } + return expansion; +} + +// status 0 : n equal, -1 : n superset, 1 : n subset +void MonomialIndex::addTerm(Node n, + const std::vector& reps, + MonomialDb* nla, + int status, + unsigned argIndex) +{ + if (status == 0) + { + if (argIndex == reps.size()) + { + d_monos.push_back(n); + } + else + { + d_data[reps[argIndex]].addTerm(n, reps, nla, status, argIndex + 1); + } + } + for (std::map::iterator it = d_data.begin(); + it != d_data.end(); + ++it) + { + if (status != 0 || argIndex == reps.size() || it->first != reps[argIndex]) + { + // if we do not contain this variable, then if we were a superset, + // fail (-2), otherwise we are subset. if we do contain this + // variable, then if we were equal, we are superset since variables + // are ordered, otherwise we remain the same. + int new_status = + std::find(reps.begin(), reps.end(), it->first) == reps.end() + ? (status >= 0 ? 1 : -2) + : (status == 0 ? -1 : status); + if (new_status != -2) + { + it->second.addTerm(n, reps, nla, new_status, argIndex); + } + } + } + // compare for subsets + for (unsigned i = 0; i < d_monos.size(); i++) + { + Node m = d_monos[i]; + if (m != n) + { + // we are superset if we are equal and haven't traversed all variables + int cstatus = status == 0 ? (argIndex == reps.size() ? 0 : -1) : status; + Trace("nl-ext-mindex-debug") << " compare " << n << " and " << m + << ", status = " << cstatus << std::endl; + if (cstatus <= 0 && nla->isMonomialSubset(m, n)) + { + nla->registerMonomialSubset(m, n); + Trace("nl-ext-mindex-debug") << "...success" << std::endl; + } + else if (cstatus >= 0 && nla->isMonomialSubset(n, m)) + { + nla->registerMonomialSubset(n, m); + Trace("nl-ext-mindex-debug") << "...success (rev)" << std::endl; + } + } + } +} + +MonomialDb::MonomialDb() +{ + d_one = NodeManager::currentNM()->mkConst(Rational(1)); +} + +void MonomialDb::registerMonomial(Node n) +{ + if (std::find(d_monomials.begin(), d_monomials.end(), n) != d_monomials.end()) + { + return; + } + d_monomials.push_back(n); + Trace("nl-ext-debug") << "Register monomial : " << n << std::endl; + Kind k = n.getKind(); + if (k == NONLINEAR_MULT) + { + // get exponent count + unsigned nchild = n.getNumChildren(); + for (unsigned i = 0; i < nchild; i++) + { + d_m_exp[n][n[i]]++; + if (i == 0 || n[i] != n[i - 1]) + { + d_m_vlist[n].push_back(n[i]); + } + } + d_m_degree[n] = nchild; + } + else if (n == d_one) + { + d_m_exp[n].clear(); + d_m_vlist[n].clear(); + d_m_degree[n] = 0; + } + else + { + Assert(k != PLUS && k != MULT); + d_m_exp[n][n] = 1; + d_m_vlist[n].push_back(n); + d_m_degree[n] = 1; + } + std::sort(d_m_vlist[n].begin(), d_m_vlist[n].end()); + Trace("nl-ext-mindex") << "Add monomial to index : " << n << std::endl; + d_m_index.addTerm(n, d_m_vlist[n], this); +} + +void MonomialDb::registerMonomialSubset(Node a, Node b) +{ + Assert(isMonomialSubset(a, b)); + + const NodeMultiset& a_exponent_map = getMonomialExponentMap(a); + const NodeMultiset& b_exponent_map = getMonomialExponentMap(b); + + std::vector diff_children = + ExpandMultiset(diffMultiset(b_exponent_map, a_exponent_map)); + Assert(!diff_children.empty()); + + d_m_contain_parent[a].push_back(b); + d_m_contain_children[b].push_back(a); + + Node mult_term = safeConstructNary(MULT, diff_children); + Node nlmult_term = safeConstructNary(NONLINEAR_MULT, diff_children); + d_m_contain_mult[a][b] = mult_term; + d_m_contain_umult[a][b] = nlmult_term; + Trace("nl-ext-mindex") << "..." << a << " is a subset of " << b + << ", difference is " << mult_term << std::endl; +} + +bool MonomialDb::isMonomialSubset(Node am, Node bm) const +{ + const NodeMultiset& a = getMonomialExponentMap(am); + const NodeMultiset& b = getMonomialExponentMap(bm); + for (NodeMultiset::const_iterator it_a = a.begin(); it_a != a.end(); ++it_a) + { + Node key = it_a->first; + const unsigned a_value = it_a->second; + const unsigned b_value = getCountWithDefault(b, key, 0); + if (a_value > b_value) + { + return false; + } + } + return true; +} + +const NodeMultiset& MonomialDb::getMonomialExponentMap(Node monomial) const +{ + MonomialExponentMap::const_iterator it = d_m_exp.find(monomial); + Assert(it != d_m_exp.end()); + return it->second; +} + +unsigned MonomialDb::getExponent(Node monomial, Node v) const +{ + MonomialExponentMap::const_iterator it = d_m_exp.find(monomial); + if (it == d_m_exp.end()) + { + return 0; + } + std::map::const_iterator itv = it->second.find(v); + if (itv == it->second.end()) + { + return 0; + } + return itv->second; +} + +const std::vector& MonomialDb::getVariableList(Node monomial) const +{ + std::map >::const_iterator itvl = + d_m_vlist.find(monomial); + Assert(itvl != d_m_vlist.end()); + return itvl->second; +} + +unsigned MonomialDb::getDegree(Node monomial) const +{ + std::map::const_iterator it = d_m_degree.find(monomial); + Assert(it != d_m_degree.end()); + return it->second; +} + +void MonomialDb::sortByDegree(std::vector& ms) const +{ + SortNonlinearDegree snlad(d_m_degree); + std::sort(ms.begin(), ms.end(), snlad); +} + +void MonomialDb::sortVariablesByModel(std::vector& ms, NlModel& m) +{ + SortNlModel smv; + smv.d_nlm = &m; + smv.d_isConcrete = false; + smv.d_isAbsolute = true; + smv.d_reverse_order = true; + for (const Node& msc : ms) + { + std::sort(d_m_vlist[msc].begin(), d_m_vlist[msc].end(), smv); + } +} + +const std::map >& MonomialDb::getContainsChildrenMap() +{ + return d_m_contain_children; +} + +const std::map >& MonomialDb::getContainsParentMap() +{ + return d_m_contain_parent; +} + +Node MonomialDb::getContainsDiff(Node a, Node b) const +{ + std::map >::const_iterator it = + d_m_contain_mult.find(a); + if (it == d_m_contain_umult.end()) + { + return Node::null(); + } + std::map::const_iterator it2 = it->second.find(b); + if (it2 == it->second.end()) + { + return Node::null(); + } + return it2->second; +} + +Node MonomialDb::getContainsDiffNl(Node a, Node b) const +{ + std::map >::const_iterator it = + d_m_contain_umult.find(a); + if (it == d_m_contain_umult.end()) + { + return Node::null(); + } + std::map::const_iterator it2 = it->second.find(b); + if (it2 == it->second.end()) + { + return Node::null(); + } + return it2->second; +} + +Node MonomialDb::mkMonomialRemFactor(Node n, + const NodeMultiset& n_exp_rem) const +{ + std::vector children; + const NodeMultiset& exponent_map = getMonomialExponentMap(n); + for (NodeMultiset::const_iterator itme2 = exponent_map.begin(); + itme2 != exponent_map.end(); + ++itme2) + { + Node v = itme2->first; + unsigned inc = itme2->second; + Trace("nl-ext-mono-factor") + << "..." << inc << " factors of " << v << std::endl; + unsigned count_in_n_exp_rem = getCountWithDefault(n_exp_rem, v, 0); + Assert(count_in_n_exp_rem <= inc); + inc -= count_in_n_exp_rem; + Trace("nl-ext-mono-factor") + << "......rem, now " << inc << " factors of " << v << std::endl; + children.insert(children.end(), inc, v); + } + Node ret = safeConstructNary(MULT, children); + ret = Rewriter::rewrite(ret); + Trace("nl-ext-mono-factor") << "...return : " << ret << std::endl; + return ret; +} + +} // namespace nl +} // namespace arith +} // namespace theory +} // namespace CVC4 diff --git a/src/theory/arith/nl/nl_monomial.h b/src/theory/arith/nl/nl_monomial.h new file mode 100644 index 000000000..b226730ac --- /dev/null +++ b/src/theory/arith/nl/nl_monomial.h @@ -0,0 +1,149 @@ +/********************* */ +/*! \file nl_monomial.h + ** \verbatim + ** Top contributors (to current version): + ** Andrew Reynolds, Tim King + ** 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 Utilities for monomials + **/ + +#ifndef CVC4__THEORY__ARITH__NL__NL_MONOMIAL_H +#define CVC4__THEORY__ARITH__NL__NL_MONOMIAL_H + +#include +#include + +#include "expr/node.h" + +namespace CVC4 { +namespace theory { +namespace arith { +namespace nl { + +class MonomialDb; +class NlModel; + +typedef std::map NodeMultiset; +typedef std::map MonomialExponentMap; + +/** An index data structure for node multisets (monomials) */ +class MonomialIndex +{ + public: + /** + * Add term to this trie. The argument status indicates what the status + * of n is with respect to the current node in the trie, where: + * 0 : n is equal, -1 : n is superset, 1 : n is subset + * of the node described by the current path in the trie. + */ + void addTerm(Node n, + const std::vector& reps, + MonomialDb* nla, + int status = 0, + unsigned argIndex = 0); + + private: + /** The children of this node */ + std::map d_data; + /** The monomials at this node */ + std::vector d_monos; +}; /* class MonomialIndex */ + +/** Context-independent database for monomial information */ +class MonomialDb +{ + public: + MonomialDb(); + ~MonomialDb() {} + /** register monomial */ + void registerMonomial(Node n); + /** + * Register monomial subset. This method is called when we infer that b is + * a subset of monomial a, e.g. x*y^2 is a subset of x^3*y^2*z. + */ + void registerMonomialSubset(Node a, Node b); + /** + * returns true if the multiset containing the + * factors of monomial a is a subset of the multiset + * containing the factors of monomial b. + */ + bool isMonomialSubset(Node a, Node b) const; + /** Returns the NodeMultiset for a registered monomial. */ + const NodeMultiset& getMonomialExponentMap(Node monomial) const; + /** Returns the exponent of variable v in the given monomial */ + unsigned getExponent(Node monomial, Node v) const; + /** Get the list of unique variables is the monomial */ + const std::vector& getVariableList(Node monomial) const; + /** Get degree of monomial, e.g. the degree of x^2*y^2 = 4 */ + unsigned getDegree(Node monomial) const; + /** Sort monomials in ms by their degree + * + * Updates ms so that degree(ms[i]) <= degree(ms[j]) for i <= j. + */ + void sortByDegree(std::vector& ms) const; + /** Sort the variable lists based on model values + * + * This updates the variable lists of monomials in ms based on the absolute + * value of their current model values in m. + * + * In other words, for each i, getVariableList(ms[i]) returns + * v1, ..., vn where |m(v1)| <= ... <= |m(vn)| after this method is invoked. + */ + void sortVariablesByModel(std::vector& ms, NlModel& m); + /** Get monomial contains children map + * + * This maps monomials to other monomials that are contained in them, e.g. + * x^2 * y may map to { x, x^2, y } if these three terms exist have been + * registered to this class. + */ + const std::map >& getContainsChildrenMap(); + /** Get monomial contains parent map, reverse of above */ + const std::map >& getContainsParentMap(); + /** + * Get contains difference. Return the difference of a and b or null if it + * does not exist. In other words, this returns a term equivalent to a/b + * that does not contain division. + */ + Node getContainsDiff(Node a, Node b) const; + /** + * Get contains difference non-linear. Same as above, but stores terms of kind + * NONLINEAR_MULT instead of MULT. + */ + Node getContainsDiffNl(Node a, Node b) const; + /** Make monomial remainder factor */ + Node mkMonomialRemFactor(Node n, const NodeMultiset& n_exp_rem) const; + + private: + /** commonly used terms */ + Node d_one; + /** list of all monomials */ + std::vector d_monomials; + /** Map from monomials to var^index. */ + MonomialExponentMap d_m_exp; + /** + * Mapping from monomials to the list of variables that occur in it. For + * example, x*x*y*z -> { x, y, z }. + */ + std::map > d_m_vlist; + /** Degree information */ + std::map d_m_degree; + /** monomial index, by sorted variables */ + MonomialIndex d_m_index; + /** containment ordering */ + std::map > d_m_contain_children; + std::map > d_m_contain_parent; + std::map > d_m_contain_mult; + std::map > d_m_contain_umult; +}; + +} // namespace nl +} // namespace arith +} // namespace theory +} // namespace CVC4 + +#endif /* CVC4__THEORY__ARITH__NL_MONOMIAL_H */ diff --git a/src/theory/arith/nl/nl_solver.cpp b/src/theory/arith/nl/nl_solver.cpp new file mode 100644 index 000000000..12cb02c70 --- /dev/null +++ b/src/theory/arith/nl/nl_solver.cpp @@ -0,0 +1,1587 @@ +/********************* */ +/*! \file nl_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 non-linear solver + **/ + +#include "theory/arith/nl/nl_solver.h" + +#include "options/arith_options.h" +#include "theory/arith/arith_msum.h" +#include "theory/arith/arith_utilities.h" +#include "theory/arith/theory_arith.h" +#include "theory/theory_model.h" + +using namespace CVC4::kind; + +namespace CVC4 { +namespace theory { +namespace arith { +namespace nl { + +void debugPrintBound(const char* c, Node coeff, Node x, Kind type, Node rhs) +{ + Node t = ArithMSum::mkCoeffTerm(coeff, x); + Trace(c) << t << " " << type << " " << rhs; +} + +bool hasNewMonomials(Node n, const std::vector& existing) +{ + std::set visited; + + std::vector worklist; + worklist.push_back(n); + while (!worklist.empty()) + { + Node current = worklist.back(); + worklist.pop_back(); + if (visited.find(current) == visited.end()) + { + visited.insert(current); + if (current.getKind() == NONLINEAR_MULT) + { + if (std::find(existing.begin(), existing.end(), current) + == existing.end()) + { + return true; + } + } + else + { + worklist.insert(worklist.end(), current.begin(), current.end()); + } + } + } + return false; +} + +NlSolver::NlSolver(TheoryArith& containing, NlModel& model) + : d_containing(containing), + d_model(model), + d_cdb(d_mdb), + d_zero_split(containing.getUserContext()) +{ + 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_order_points.push_back(d_neg_one); + d_order_points.push_back(d_zero); + d_order_points.push_back(d_one); +} + +NlSolver::~NlSolver() {} + +void NlSolver::initLastCall(const std::vector& assertions, + const std::vector& false_asserts, + const std::vector& xts) +{ + d_ms_vars.clear(); + d_ms_proc.clear(); + d_ms.clear(); + d_mterms.clear(); + d_m_nconst_factor.clear(); + d_tplane_refine.clear(); + d_ci.clear(); + d_ci_exp.clear(); + d_ci_max.clear(); + + Trace("nl-ext-mv") << "Extended terms : " << std::endl; + // for computing congruence + std::map argTrie; + for (unsigned i = 0, xsize = xts.size(); i < xsize; i++) + { + Node a = xts[i]; + d_model.computeConcreteModelValue(a); + d_model.computeAbstractModelValue(a); + d_model.printModelValue("nl-ext-mv", a); + Kind ak = a.getKind(); + if (ak == NONLINEAR_MULT) + { + d_ms.push_back(a); + + // context-independent registration + d_mdb.registerMonomial(a); + + const std::vector& varList = d_mdb.getVariableList(a); + for (const Node& v : varList) + { + if (std::find(d_ms_vars.begin(), d_ms_vars.end(), v) == d_ms_vars.end()) + { + d_ms_vars.push_back(v); + } + Node mvk = d_model.computeAbstractModelValue(v); + if (!mvk.isConst()) + { + d_m_nconst_factor[a] = true; + } + } + // mark processed if has a "one" factor (will look at reduced monomial)? + } + } + + // register constants + d_mdb.registerMonomial(d_one); + for (unsigned j = 0; j < d_order_points.size(); j++) + { + Node c = d_order_points[j]; + d_model.computeConcreteModelValue(c); + d_model.computeAbstractModelValue(c); + } + + // register variables + Trace("nl-ext-mv") << "Variables in monomials : " << std::endl; + for (unsigned i = 0; i < d_ms_vars.size(); i++) + { + Node v = d_ms_vars[i]; + d_mdb.registerMonomial(v); + d_model.computeConcreteModelValue(v); + d_model.computeAbstractModelValue(v); + d_model.printModelValue("nl-ext-mv", v); + } + + Trace("nl-ext") << "We have " << d_ms.size() << " monomials." << std::endl; +} + +void NlSolver::setMonomialFactor(Node a, Node b, const NodeMultiset& common) +{ + // Could not tell if this was being inserted intentionally or not. + std::map& mono_diff_a = d_mono_diff[a]; + if (mono_diff_a.find(b) == mono_diff_a.end()) + { + Trace("nl-ext-mono-factor") + << "Set monomial factor for " << a << "/" << b << std::endl; + mono_diff_a[b] = d_mdb.mkMonomialRemFactor(a, common); + } +} + +std::vector NlSolver::checkSplitZero() +{ + std::vector lemmas; + for (unsigned i = 0; i < d_ms_vars.size(); i++) + { + Node v = d_ms_vars[i]; + if (d_zero_split.insert(v)) + { + Node eq = v.eqNode(d_zero); + eq = Rewriter::rewrite(eq); + Node literal = d_containing.getValuation().ensureLiteral(eq); + d_containing.getOutputChannel().requirePhase(literal, true); + lemmas.push_back(literal.orNode(literal.negate())); + } + } + return lemmas; +} + +void NlSolver::assignOrderIds(std::vector& vars, + NodeMultiset& order, + bool isConcrete, + bool isAbsolute) +{ + SortNlModel smv; + smv.d_nlm = &d_model; + smv.d_isConcrete = isConcrete; + smv.d_isAbsolute = isAbsolute; + smv.d_reverse_order = false; + std::sort(vars.begin(), vars.end(), smv); + + order.clear(); + // assign ordering id's + unsigned counter = 0; + unsigned order_index = isConcrete ? 0 : 1; + Node prev; + for (unsigned j = 0; j < vars.size(); j++) + { + Node x = vars[j]; + Node v = d_model.computeModelValue(x, isConcrete); + if (!v.isConst()) + { + Trace("nl-ext-mvo") << "..do not assign order to " << x << " : " << v + << std::endl; + // don't assign for non-constant values (transcendental function apps) + break; + } + Trace("nl-ext-mvo") << " order " << x << " : " << v << std::endl; + if (v != prev) + { + // builtin points + bool success; + do + { + success = false; + if (order_index < d_order_points.size()) + { + Node vv = d_model.computeModelValue(d_order_points[order_index], + isConcrete); + if (d_model.compareValue(v, vv, isAbsolute) <= 0) + { + counter++; + Trace("nl-ext-mvo") << "O[" << d_order_points[order_index] + << "] = " << counter << std::endl; + order[d_order_points[order_index]] = counter; + prev = vv; + order_index++; + success = true; + } + } + } while (success); + } + if (prev.isNull() || d_model.compareValue(v, prev, isAbsolute) != 0) + { + counter++; + } + Trace("nl-ext-mvo") << "O[" << x << "] = " << counter << std::endl; + order[x] = counter; + prev = v; + } + while (order_index < d_order_points.size()) + { + counter++; + Trace("nl-ext-mvo") << "O[" << d_order_points[order_index] + << "] = " << counter << std::endl; + order[d_order_points[order_index]] = counter; + order_index++; + } +} + +// show a <> 0 by inequalities between variables in monomial a w.r.t 0 +int NlSolver::compareSign(Node oa, + Node a, + unsigned a_index, + int status, + std::vector& exp, + std::vector& lem) +{ + Trace("nl-ext-debug") << "Process " << a << " at index " << a_index + << ", status is " << status << std::endl; + NodeManager* nm = NodeManager::currentNM(); + Node mvaoa = d_model.computeAbstractModelValue(oa); + const std::vector& vla = d_mdb.getVariableList(a); + if (a_index == vla.size()) + { + if (mvaoa.getConst().sgn() != status) + { + Node lemma = + safeConstructNary(AND, exp).impNode(mkLit(oa, d_zero, status * 2)); + lem.push_back(lemma); + } + return status; + } + Assert(a_index < vla.size()); + Node av = vla[a_index]; + unsigned aexp = d_mdb.getExponent(a, av); + // take current sign in model + Node mvaav = d_model.computeAbstractModelValue(av); + int sgn = mvaav.getConst().sgn(); + Trace("nl-ext-debug") << "Process var " << av << "^" << aexp + << ", model sign = " << sgn << std::endl; + if (sgn == 0) + { + if (mvaoa.getConst().sgn() != 0) + { + Node lemma = av.eqNode(d_zero).impNode(oa.eqNode(d_zero)); + lem.push_back(lemma); + } + return 0; + } + if (aexp % 2 == 0) + { + exp.push_back(av.eqNode(d_zero).negate()); + return compareSign(oa, a, a_index + 1, status, exp, lem); + } + exp.push_back(nm->mkNode(sgn == 1 ? GT : LT, av, d_zero)); + return compareSign(oa, a, a_index + 1, status * sgn, exp, lem); +} + +bool NlSolver::compareMonomial( + Node oa, + Node a, + NodeMultiset& a_exp_proc, + Node ob, + Node b, + NodeMultiset& b_exp_proc, + std::vector& exp, + std::vector& lem, + std::map > >& cmp_infers) +{ + Trace("nl-ext-comp-debug") + << "Check |" << a << "| >= |" << b << "|" << std::endl; + unsigned pexp_size = exp.size(); + if (compareMonomial( + oa, a, 0, a_exp_proc, ob, b, 0, b_exp_proc, 0, exp, lem, cmp_infers)) + { + return true; + } + exp.resize(pexp_size); + Trace("nl-ext-comp-debug") + << "Check |" << b << "| >= |" << a << "|" << std::endl; + if (compareMonomial( + ob, b, 0, b_exp_proc, oa, a, 0, a_exp_proc, 0, exp, lem, cmp_infers)) + { + return true; + } + return false; +} + +Node NlSolver::mkLit(Node a, Node b, int status, bool isAbsolute) +{ + if (status == 0) + { + Node a_eq_b = a.eqNode(b); + if (!isAbsolute) + { + return a_eq_b; + } + Node negate_b = NodeManager::currentNM()->mkNode(UMINUS, b); + return a_eq_b.orNode(a.eqNode(negate_b)); + } + else if (status < 0) + { + return mkLit(b, a, -status); + } + Assert(status == 1 || status == 2); + NodeManager* nm = NodeManager::currentNM(); + Kind greater_op = status == 1 ? GEQ : GT; + if (!isAbsolute) + { + return nm->mkNode(greater_op, a, b); + } + // return nm->mkNode( greater_op, mkAbs( a ), mkAbs( b ) ); + Node zero = mkRationalNode(0); + Node a_is_nonnegative = nm->mkNode(GEQ, a, zero); + Node b_is_nonnegative = nm->mkNode(GEQ, b, zero); + Node negate_a = nm->mkNode(UMINUS, a); + Node negate_b = nm->mkNode(UMINUS, b); + return a_is_nonnegative.iteNode( + b_is_nonnegative.iteNode(nm->mkNode(greater_op, a, b), + nm->mkNode(greater_op, a, negate_b)), + b_is_nonnegative.iteNode(nm->mkNode(greater_op, negate_a, b), + nm->mkNode(greater_op, negate_a, negate_b))); +} + +bool NlSolver::cmp_holds(Node x, + Node y, + std::map >& cmp_infers, + std::vector& exp, + std::map& visited) +{ + if (x == y) + { + return true; + } + else if (visited.find(x) != visited.end()) + { + return false; + } + visited[x] = true; + std::map >::iterator it = cmp_infers.find(x); + if (it != cmp_infers.end()) + { + for (std::map::iterator itc = it->second.begin(); + itc != it->second.end(); + ++itc) + { + exp.push_back(itc->second); + if (cmp_holds(itc->first, y, cmp_infers, exp, visited)) + { + return true; + } + exp.pop_back(); + } + } + return false; +} + +// trying to show a ( >, = ) b by inequalities between variables in +// monomials a,b +bool NlSolver::compareMonomial( + Node oa, + Node a, + unsigned a_index, + NodeMultiset& a_exp_proc, + Node ob, + Node b, + unsigned b_index, + NodeMultiset& b_exp_proc, + int status, + std::vector& exp, + std::vector& lem, + std::map > >& cmp_infers) +{ + Trace("nl-ext-comp-debug") + << "compareMonomial " << oa << " and " << ob << ", indices = " << a_index + << " " << b_index << std::endl; + Assert(status == 0 || status == 2); + const std::vector& vla = d_mdb.getVariableList(a); + const std::vector& vlb = d_mdb.getVariableList(b); + if (a_index == vla.size() && b_index == vlb.size()) + { + // finished, compare absolute value of abstract model values + int modelStatus = d_model.compare(oa, ob, false, true) * -2; + Trace("nl-ext-comp") << "...finished comparison with " << oa << " <" + << status << "> " << ob + << ", model status = " << modelStatus << std::endl; + if (status != modelStatus) + { + Trace("nl-ext-comp-infer") + << "infer : " << oa << " <" << status << "> " << ob << std::endl; + if (status == 2) + { + // must state that all variables are non-zero + for (unsigned j = 0; j < vla.size(); j++) + { + exp.push_back(vla[j].eqNode(d_zero).negate()); + } + } + NodeManager* nm = NodeManager::currentNM(); + Node clem = nm->mkNode( + IMPLIES, safeConstructNary(AND, exp), mkLit(oa, ob, status, true)); + Trace("nl-ext-comp-lemma") << "comparison lemma : " << clem << std::endl; + lem.push_back(clem); + cmp_infers[status][oa][ob] = clem; + } + return true; + } + // get a/b variable information + Node av; + unsigned aexp = 0; + unsigned avo = 0; + if (a_index < vla.size()) + { + av = vla[a_index]; + unsigned aexpTotal = d_mdb.getExponent(a, av); + Assert(a_exp_proc[av] <= aexpTotal); + aexp = aexpTotal - a_exp_proc[av]; + if (aexp == 0) + { + return compareMonomial(oa, + a, + a_index + 1, + a_exp_proc, + ob, + b, + b_index, + b_exp_proc, + status, + exp, + lem, + cmp_infers); + } + Assert(d_order_vars.find(av) != d_order_vars.end()); + avo = d_order_vars[av]; + } + Node bv; + unsigned bexp = 0; + unsigned bvo = 0; + if (b_index < vlb.size()) + { + bv = vlb[b_index]; + unsigned bexpTotal = d_mdb.getExponent(b, bv); + Assert(b_exp_proc[bv] <= bexpTotal); + bexp = bexpTotal - b_exp_proc[bv]; + if (bexp == 0) + { + return compareMonomial(oa, + a, + a_index, + a_exp_proc, + ob, + b, + b_index + 1, + b_exp_proc, + status, + exp, + lem, + cmp_infers); + } + Assert(d_order_vars.find(bv) != d_order_vars.end()); + bvo = d_order_vars[bv]; + } + // get "one" information + Assert(d_order_vars.find(d_one) != d_order_vars.end()); + unsigned ovo = d_order_vars[d_one]; + Trace("nl-ext-comp-debug") << "....vars : " << av << "^" << aexp << " " << bv + << "^" << bexp << std::endl; + + //--- cases + if (av.isNull()) + { + if (bvo <= ovo) + { + Trace("nl-ext-comp-debug") << "...take leading " << bv << std::endl; + // can multiply b by <=1 + exp.push_back(mkLit(d_one, bv, bvo == ovo ? 0 : 2, true)); + return compareMonomial(oa, + a, + a_index, + a_exp_proc, + ob, + b, + b_index + 1, + b_exp_proc, + bvo == ovo ? status : 2, + exp, + lem, + cmp_infers); + } + Trace("nl-ext-comp-debug") + << "...failure, unmatched |b|>1 component." << std::endl; + return false; + } + else if (bv.isNull()) + { + if (avo >= ovo) + { + Trace("nl-ext-comp-debug") << "...take leading " << av << std::endl; + // can multiply a by >=1 + exp.push_back(mkLit(av, d_one, avo == ovo ? 0 : 2, true)); + return compareMonomial(oa, + a, + a_index + 1, + a_exp_proc, + ob, + b, + b_index, + b_exp_proc, + avo == ovo ? status : 2, + exp, + lem, + cmp_infers); + } + Trace("nl-ext-comp-debug") + << "...failure, unmatched |a|<1 component." << std::endl; + return false; + } + Assert(!av.isNull() && !bv.isNull()); + if (avo >= bvo) + { + if (bvo < ovo && avo >= ovo) + { + Trace("nl-ext-comp-debug") << "...take leading " << av << std::endl; + // do avo>=1 instead + exp.push_back(mkLit(av, d_one, avo == ovo ? 0 : 2, true)); + return compareMonomial(oa, + a, + a_index + 1, + a_exp_proc, + ob, + b, + b_index, + b_exp_proc, + avo == ovo ? status : 2, + exp, + lem, + cmp_infers); + } + unsigned min_exp = aexp > bexp ? bexp : aexp; + a_exp_proc[av] += min_exp; + b_exp_proc[bv] += min_exp; + Trace("nl-ext-comp-debug") << "...take leading " << min_exp << " from " + << av << " and " << bv << std::endl; + exp.push_back(mkLit(av, bv, avo == bvo ? 0 : 2, true)); + bool ret = compareMonomial(oa, + a, + a_index, + a_exp_proc, + ob, + b, + b_index, + b_exp_proc, + avo == bvo ? status : 2, + exp, + lem, + cmp_infers); + a_exp_proc[av] -= min_exp; + b_exp_proc[bv] -= min_exp; + return ret; + } + if (bvo <= ovo) + { + Trace("nl-ext-comp-debug") << "...take leading " << bv << std::endl; + // try multiply b <= 1 + exp.push_back(mkLit(d_one, bv, bvo == ovo ? 0 : 2, true)); + return compareMonomial(oa, + a, + a_index, + a_exp_proc, + ob, + b, + b_index + 1, + b_exp_proc, + bvo == ovo ? status : 2, + exp, + lem, + cmp_infers); + } + Trace("nl-ext-comp-debug") + << "...failure, leading |b|>|a|>1 component." << std::endl; + return false; +} + +std::vector NlSolver::checkMonomialSign() +{ + std::vector lemmas; + std::map signs; + Trace("nl-ext") << "Get monomial sign lemmas..." << std::endl; + for (unsigned j = 0; j < d_ms.size(); j++) + { + Node a = d_ms[j]; + if (d_ms_proc.find(a) == d_ms_proc.end()) + { + std::vector exp; + if (Trace.isOn("nl-ext-debug")) + { + Node cmva = d_model.computeConcreteModelValue(a); + Trace("nl-ext-debug") + << " process " << a << ", mv=" << cmva << "..." << std::endl; + } + if (d_m_nconst_factor.find(a) == d_m_nconst_factor.end()) + { + signs[a] = compareSign(a, a, 0, 1, exp, lemmas); + if (signs[a] == 0) + { + d_ms_proc[a] = true; + Trace("nl-ext-debug") + << "...mark " << a << " reduced since its value is 0." + << std::endl; + } + } + else + { + Trace("nl-ext-debug") + << "...can't conclude sign lemma for " << a + << " since model value of a factor is non-constant." << std::endl; + } + } + } + return lemmas; +} + +std::vector NlSolver::checkMonomialMagnitude(unsigned c) +{ + // ensure information is setup + if (c == 0) + { + // sort by absolute values of abstract model values + assignOrderIds(d_ms_vars, d_order_vars, false, true); + + // sort individual variable lists + Trace("nl-ext-proc") << "Assign order var lists..." << std::endl; + d_mdb.sortVariablesByModel(d_ms, d_model); + } + + unsigned r = 1; + std::vector lemmas; + // if (x,y,L) in cmp_infers, then x > y inferred as conclusion of L + // in lemmas + std::map > > cmp_infers; + Trace("nl-ext") << "Get monomial comparison lemmas (order=" << r + << ", compare=" << c << ")..." << std::endl; + for (unsigned j = 0; j < d_ms.size(); j++) + { + Node a = d_ms[j]; + if (d_ms_proc.find(a) == d_ms_proc.end() + && d_m_nconst_factor.find(a) == d_m_nconst_factor.end()) + { + if (c == 0) + { + // compare magnitude against 1 + std::vector exp; + NodeMultiset a_exp_proc; + NodeMultiset b_exp_proc; + compareMonomial(a, + a, + a_exp_proc, + d_one, + d_one, + b_exp_proc, + exp, + lemmas, + cmp_infers); + } + else + { + const NodeMultiset& mea = d_mdb.getMonomialExponentMap(a); + if (c == 1) + { + // could compare not just against containing variables? + // compare magnitude against variables + for (unsigned k = 0; k < d_ms_vars.size(); k++) + { + Node v = d_ms_vars[k]; + Node mvcv = d_model.computeConcreteModelValue(v); + if (mvcv.isConst()) + { + std::vector exp; + NodeMultiset a_exp_proc; + NodeMultiset b_exp_proc; + if (mea.find(v) != mea.end()) + { + a_exp_proc[v] = 1; + b_exp_proc[v] = 1; + setMonomialFactor(a, v, a_exp_proc); + setMonomialFactor(v, a, b_exp_proc); + compareMonomial(a, + a, + a_exp_proc, + v, + v, + b_exp_proc, + exp, + lemmas, + cmp_infers); + } + } + } + } + else + { + // compare magnitude against other non-linear monomials + for (unsigned k = (j + 1); k < d_ms.size(); k++) + { + Node b = d_ms[k]; + //(signs[a]==signs[b])==(r==0) + if (d_ms_proc.find(b) == d_ms_proc.end() + && d_m_nconst_factor.find(b) == d_m_nconst_factor.end()) + { + const NodeMultiset& meb = d_mdb.getMonomialExponentMap(b); + + std::vector exp; + // take common factors of monomials, set minimum of + // common exponents as processed + NodeMultiset a_exp_proc; + NodeMultiset b_exp_proc; + for (NodeMultiset::const_iterator itmea2 = mea.begin(); + itmea2 != mea.end(); + ++itmea2) + { + NodeMultiset::const_iterator itmeb2 = meb.find(itmea2->first); + if (itmeb2 != meb.end()) + { + unsigned min_exp = itmea2->second > itmeb2->second + ? itmeb2->second + : itmea2->second; + a_exp_proc[itmea2->first] = min_exp; + b_exp_proc[itmea2->first] = min_exp; + Trace("nl-ext-comp") << "Common exponent : " << itmea2->first + << " : " << min_exp << std::endl; + } + } + if (!a_exp_proc.empty()) + { + setMonomialFactor(a, b, a_exp_proc); + setMonomialFactor(b, a, b_exp_proc); + } + /* + if( !a_exp_proc.empty() ){ + //reduction based on common exponents a > 0 => ( a * b + <> a * c <=> b <> c ), a < 0 => ( a * b <> a * c <=> b + !<> c ) ? }else{ compareMonomial( a, a, a_exp_proc, b, + b, b_exp_proc, exp, lemmas ); + } + */ + compareMonomial( + a, a, a_exp_proc, b, b, b_exp_proc, exp, lemmas, cmp_infers); + } + } + } + } + } + } + // remove redundant lemmas, e.g. if a > b, b > c, a > c were + // inferred, discard lemma with conclusion a > c + Trace("nl-ext-comp") << "Compute redundancies for " << lemmas.size() + << " lemmas." << std::endl; + // naive + std::vector r_lemmas; + for (std::map > >::iterator itb = + cmp_infers.begin(); + itb != cmp_infers.end(); + ++itb) + { + for (std::map >::iterator itc = + itb->second.begin(); + itc != itb->second.end(); + ++itc) + { + for (std::map::iterator itc2 = itc->second.begin(); + itc2 != itc->second.end(); + ++itc2) + { + std::map visited; + for (std::map::iterator itc3 = itc->second.begin(); + itc3 != itc->second.end(); + ++itc3) + { + if (itc3->first != itc2->first) + { + std::vector exp; + if (cmp_holds(itc3->first, itc2->first, itb->second, exp, visited)) + { + r_lemmas.push_back(itc2->second); + Trace("nl-ext-comp") + << "...inference of " << itc->first << " > " << itc2->first + << " was redundant." << std::endl; + break; + } + } + } + } + } + } + std::vector nr_lemmas; + for (unsigned i = 0; i < lemmas.size(); i++) + { + if (std::find(r_lemmas.begin(), r_lemmas.end(), lemmas[i]) + == r_lemmas.end()) + { + nr_lemmas.push_back(lemmas[i]); + } + } + // could only take maximal lower/minimial lower bounds? + + Trace("nl-ext-comp") << nr_lemmas.size() << " / " << lemmas.size() + << " were non-redundant." << std::endl; + return nr_lemmas; +} + +std::vector NlSolver::checkTangentPlanes() +{ + std::vector lemmas; + Trace("nl-ext") << "Get monomial tangent plane lemmas..." << std::endl; + NodeManager* nm = NodeManager::currentNM(); + const std::map >& ccMap = + d_mdb.getContainsChildrenMap(); + unsigned kstart = d_ms_vars.size(); + for (unsigned k = kstart; k < d_mterms.size(); k++) + { + Node t = d_mterms[k]; + // if this term requires a refinement + if (d_tplane_refine.find(t) == d_tplane_refine.end()) + { + continue; + } + Trace("nl-ext-tplanes") + << "Look at monomial requiring refinement : " << t << std::endl; + // get a decomposition + std::map >::const_iterator it = ccMap.find(t); + if (it == ccMap.end()) + { + continue; + } + std::map > dproc; + for (unsigned j = 0; j < it->second.size(); j++) + { + Node tc = it->second[j]; + if (tc != d_one) + { + Node tc_diff = d_mdb.getContainsDiffNl(tc, t); + Assert(!tc_diff.isNull()); + Node a = tc < tc_diff ? tc : tc_diff; + Node b = tc < tc_diff ? tc_diff : tc; + if (dproc[a].find(b) == dproc[a].end()) + { + dproc[a][b] = true; + Trace("nl-ext-tplanes") + << " decomposable into : " << a << " * " << b << std::endl; + Node a_v_c = d_model.computeAbstractModelValue(a); + Node b_v_c = d_model.computeAbstractModelValue(b); + // points we will add tangent planes for + std::vector pts[2]; + pts[0].push_back(a_v_c); + pts[1].push_back(b_v_c); + // if previously refined + bool prevRefine = d_tangent_val_bound[0][a].find(b) + != d_tangent_val_bound[0][a].end(); + // a_min, a_max, b_min, b_max + for (unsigned p = 0; p < 4; p++) + { + Node curr_v = p <= 1 ? a_v_c : b_v_c; + if (prevRefine) + { + Node pt_v = d_tangent_val_bound[p][a][b]; + Assert(!pt_v.isNull()); + if (curr_v != pt_v) + { + Node do_extend = + nm->mkNode((p == 1 || p == 3) ? GT : LT, curr_v, pt_v); + do_extend = Rewriter::rewrite(do_extend); + if (do_extend == d_true) + { + for (unsigned q = 0; q < 2; q++) + { + pts[p <= 1 ? 0 : 1].push_back(curr_v); + pts[p <= 1 ? 1 : 0].push_back( + d_tangent_val_bound[p <= 1 ? 2 + q : q][a][b]); + } + } + } + } + else + { + d_tangent_val_bound[p][a][b] = curr_v; + } + } + + for (unsigned p = 0; p < pts[0].size(); p++) + { + Node a_v = pts[0][p]; + Node b_v = pts[1][p]; + + // tangent plane + Node tplane = nm->mkNode( + MINUS, + nm->mkNode( + PLUS, nm->mkNode(MULT, b_v, a), nm->mkNode(MULT, a_v, b)), + nm->mkNode(MULT, a_v, b_v)); + for (unsigned d = 0; d < 4; d++) + { + Node aa = nm->mkNode(d == 0 || d == 3 ? GEQ : LEQ, a, a_v); + Node ab = nm->mkNode(d == 1 || d == 3 ? GEQ : LEQ, b, b_v); + Node conc = nm->mkNode(d <= 1 ? LEQ : GEQ, t, tplane); + Node tlem = nm->mkNode(OR, aa.negate(), ab.negate(), conc); + Trace("nl-ext-tplanes") + << "Tangent plane lemma : " << tlem << std::endl; + lemmas.push_back(tlem); + } + + // tangent plane reverse implication + + // t <= tplane -> ( (a <= a_v ^ b >= b_v) v + // (a >= a_v ^ b <= b_v) ). + // in clause form, the above becomes + // t <= tplane -> a <= a_v v b <= b_v. + // t <= tplane -> b >= b_v v a >= a_v. + Node a_leq_av = nm->mkNode(LEQ, a, a_v); + Node b_leq_bv = nm->mkNode(LEQ, b, b_v); + Node a_geq_av = nm->mkNode(GEQ, a, a_v); + Node b_geq_bv = nm->mkNode(GEQ, b, b_v); + + Node t_leq_tplane = nm->mkNode(LEQ, t, tplane); + Node a_leq_av_or_b_leq_bv = nm->mkNode(OR, a_leq_av, b_leq_bv); + Node b_geq_bv_or_a_geq_av = nm->mkNode(OR, b_geq_bv, a_geq_av); + Node ub_reverse1 = + nm->mkNode(OR, t_leq_tplane.negate(), a_leq_av_or_b_leq_bv); + Trace("nl-ext-tplanes") + << "Tangent plane lemma (reverse) : " << ub_reverse1 + << std::endl; + lemmas.push_back(ub_reverse1); + Node ub_reverse2 = + nm->mkNode(OR, t_leq_tplane.negate(), b_geq_bv_or_a_geq_av); + Trace("nl-ext-tplanes") + << "Tangent plane lemma (reverse) : " << ub_reverse2 + << std::endl; + lemmas.push_back(ub_reverse2); + + // t >= tplane -> ( (a <= a_v ^ b <= b_v) v + // (a >= a_v ^ b >= b_v) ). + // in clause form, the above becomes + // t >= tplane -> a <= a_v v b >= b_v. + // t >= tplane -> b >= b_v v a <= a_v + Node t_geq_tplane = nm->mkNode(GEQ, t, tplane); + Node a_leq_av_or_b_geq_bv = nm->mkNode(OR, a_leq_av, b_geq_bv); + Node a_geq_av_or_b_leq_bv = nm->mkNode(OR, a_geq_av, b_leq_bv); + Node lb_reverse1 = + nm->mkNode(OR, t_geq_tplane.negate(), a_leq_av_or_b_geq_bv); + Trace("nl-ext-tplanes") + << "Tangent plane lemma (reverse) : " << lb_reverse1 + << std::endl; + lemmas.push_back(lb_reverse1); + Node lb_reverse2 = + nm->mkNode(OR, t_geq_tplane.negate(), a_geq_av_or_b_leq_bv); + Trace("nl-ext-tplanes") + << "Tangent plane lemma (reverse) : " << lb_reverse2 + << std::endl; + lemmas.push_back(lb_reverse2); + } + } + } + } + } + Trace("nl-ext") << "...trying " << lemmas.size() << " tangent plane lemmas..." + << std::endl; + return lemmas; +} + +std::vector NlSolver::checkMonomialInferBounds( + std::vector& nt_lemmas, + const std::vector& asserts, + const std::vector& false_asserts) +{ + // sort monomials by degree + Trace("nl-ext-proc") << "Sort monomials by degree..." << std::endl; + d_mdb.sortByDegree(d_ms); + // all monomials + d_mterms.insert(d_mterms.end(), d_ms_vars.begin(), d_ms_vars.end()); + d_mterms.insert(d_mterms.end(), d_ms.begin(), d_ms.end()); + + const std::map >& cim = + d_cdb.getConstraints(); + + std::vector lemmas; + NodeManager* nm = NodeManager::currentNM(); + // register constraints + Trace("nl-ext-debug") << "Register bound constraints..." << std::endl; + for (const Node& lit : asserts) + { + bool polarity = lit.getKind() != NOT; + Node atom = lit.getKind() == NOT ? lit[0] : lit; + d_cdb.registerConstraint(atom); + bool is_false_lit = + std::find(false_asserts.begin(), false_asserts.end(), lit) + != false_asserts.end(); + // add information about bounds to variables + std::map >::const_iterator itc = + cim.find(atom); + if (itc == cim.end()) + { + continue; + } + for (const std::pair& itcc : itc->second) + { + Node x = itcc.first; + Node coeff = itcc.second.d_coeff; + Node rhs = itcc.second.d_rhs; + Kind type = itcc.second.d_type; + Node exp = lit; + if (!polarity) + { + // reverse + if (type == EQUAL) + { + // we will take the strict inequality in the direction of the + // model + Node lhs = ArithMSum::mkCoeffTerm(coeff, x); + Node query = nm->mkNode(GT, lhs, rhs); + Node query_mv = d_model.computeAbstractModelValue(query); + if (query_mv == d_true) + { + exp = query; + type = GT; + } + else + { + Assert(query_mv == d_false); + exp = nm->mkNode(LT, lhs, rhs); + type = LT; + } + } + else + { + type = negateKind(type); + } + } + // add to status if maximal degree + d_ci_max[x][coeff][rhs] = d_cdb.isMaximal(atom, x); + if (Trace.isOn("nl-ext-bound-debug2")) + { + Node t = ArithMSum::mkCoeffTerm(coeff, x); + Trace("nl-ext-bound-debug2") << "Add Bound: " << t << " " << type << " " + << rhs << " by " << exp << std::endl; + } + bool updated = true; + std::map::iterator its = d_ci[x][coeff].find(rhs); + if (its == d_ci[x][coeff].end()) + { + d_ci[x][coeff][rhs] = type; + d_ci_exp[x][coeff][rhs] = exp; + } + else if (type != its->second) + { + Trace("nl-ext-bound-debug2") + << "Joining kinds : " << type << " " << its->second << std::endl; + Kind jk = joinKinds(type, its->second); + if (jk == UNDEFINED_KIND) + { + updated = false; + } + else if (jk != its->second) + { + if (jk == type) + { + d_ci[x][coeff][rhs] = type; + d_ci_exp[x][coeff][rhs] = exp; + } + else + { + d_ci[x][coeff][rhs] = jk; + d_ci_exp[x][coeff][rhs] = + nm->mkNode(AND, d_ci_exp[x][coeff][rhs], exp); + } + } + else + { + updated = false; + } + } + if (Trace.isOn("nl-ext-bound")) + { + if (updated) + { + Trace("nl-ext-bound") << "Bound: "; + debugPrintBound("nl-ext-bound", coeff, x, d_ci[x][coeff][rhs], rhs); + Trace("nl-ext-bound") << " by " << d_ci_exp[x][coeff][rhs]; + if (d_ci_max[x][coeff][rhs]) + { + Trace("nl-ext-bound") << ", is max degree"; + } + Trace("nl-ext-bound") << std::endl; + } + } + // compute if bound is not satisfied, and store what is required + // for a possible refinement + if (options::nlExtTangentPlanes()) + { + if (is_false_lit) + { + d_tplane_refine.insert(x); + } + } + } + } + // reflexive constraints + Node null_coeff; + for (unsigned j = 0; j < d_mterms.size(); j++) + { + Node n = d_mterms[j]; + d_ci[n][null_coeff][n] = EQUAL; + d_ci_exp[n][null_coeff][n] = d_true; + d_ci_max[n][null_coeff][n] = false; + } + + Trace("nl-ext") << "Get inferred bound lemmas..." << std::endl; + const std::map >& cpMap = + d_mdb.getContainsParentMap(); + for (unsigned k = 0; k < d_mterms.size(); k++) + { + Node x = d_mterms[k]; + Trace("nl-ext-bound-debug") + << "Process bounds for " << x << " : " << std::endl; + std::map >::const_iterator itm = cpMap.find(x); + if (itm == cpMap.end()) + { + Trace("nl-ext-bound-debug") << "...has no parent monomials." << std::endl; + continue; + } + Trace("nl-ext-bound-debug") + << "...has " << itm->second.size() << " parent monomials." << std::endl; + // check derived bounds + std::map > >::iterator itc = + d_ci.find(x); + if (itc == d_ci.end()) + { + continue; + } + for (std::map >::iterator itcc = + itc->second.begin(); + itcc != itc->second.end(); + ++itcc) + { + Node coeff = itcc->first; + Node t = ArithMSum::mkCoeffTerm(coeff, x); + for (std::map::iterator itcr = itcc->second.begin(); + itcr != itcc->second.end(); + ++itcr) + { + Node rhs = itcr->first; + // only consider this bound if maximal degree + if (!d_ci_max[x][coeff][rhs]) + { + continue; + } + Kind type = itcr->second; + for (unsigned j = 0; j < itm->second.size(); j++) + { + Node y = itm->second[j]; + Node mult = d_mdb.getContainsDiff(x, y); + // x t => m*x t where y = m*x + // get the sign of mult + Node mmv = d_model.computeConcreteModelValue(mult); + Trace("nl-ext-bound-debug2") + << "Model value of " << mult << " is " << mmv << std::endl; + if (!mmv.isConst()) + { + Trace("nl-ext-bound-debug") + << " ...coefficient " << mult + << " is non-constant (probably transcendental)." << std::endl; + continue; + } + int mmv_sign = mmv.getConst().sgn(); + Trace("nl-ext-bound-debug2") + << " sign of " << mmv << " is " << mmv_sign << std::endl; + if (mmv_sign == 0) + { + Trace("nl-ext-bound-debug") + << " ...coefficient " << mult << " is zero." << std::endl; + continue; + } + Trace("nl-ext-bound-debug") + << " from " << x << " * " << mult << " = " << y << " and " << t + << " " << type << " " << rhs << ", infer : " << std::endl; + Kind infer_type = mmv_sign == -1 ? reverseRelationKind(type) : type; + Node infer_lhs = nm->mkNode(MULT, mult, t); + Node infer_rhs = nm->mkNode(MULT, mult, rhs); + Node infer = nm->mkNode(infer_type, infer_lhs, infer_rhs); + Trace("nl-ext-bound-debug") << " " << infer << std::endl; + infer = Rewriter::rewrite(infer); + Trace("nl-ext-bound-debug2") + << " ...rewritten : " << infer << std::endl; + // check whether it is false in model for abstraction + Node infer_mv = d_model.computeAbstractModelValue(infer); + Trace("nl-ext-bound-debug") + << " ...infer model value is " << infer_mv << std::endl; + if (infer_mv == d_false) + { + Node exp = + nm->mkNode(AND, + nm->mkNode(mmv_sign == 1 ? GT : LT, mult, d_zero), + d_ci_exp[x][coeff][rhs]); + Node iblem = nm->mkNode(IMPLIES, exp, infer); + Node pr_iblem = iblem; + iblem = Rewriter::rewrite(iblem); + bool introNewTerms = hasNewMonomials(iblem, d_ms); + Trace("nl-ext-bound-lemma") + << "*** Bound inference lemma : " << iblem + << " (pre-rewrite : " << pr_iblem << ")" << std::endl; + // Trace("nl-ext-bound-lemma") << " intro new + // monomials = " << introNewTerms << std::endl; + if (!introNewTerms) + { + lemmas.push_back(iblem); + } + else + { + nt_lemmas.push_back(iblem); + } + } + } + } + } + } + return lemmas; +} + +std::vector NlSolver::checkFactoring( + const std::vector& asserts, const std::vector& false_asserts) +{ + std::vector lemmas; + NodeManager* nm = NodeManager::currentNM(); + Trace("nl-ext") << "Get factoring lemmas..." << std::endl; + for (const Node& lit : asserts) + { + bool polarity = lit.getKind() != NOT; + Node atom = lit.getKind() == NOT ? lit[0] : lit; + Node litv = d_model.computeConcreteModelValue(lit); + bool considerLit = false; + // Only consider literals that are in false_asserts. + considerLit = std::find(false_asserts.begin(), false_asserts.end(), lit) + != false_asserts.end(); + + if (considerLit) + { + std::map msum; + if (ArithMSum::getMonomialSumLit(atom, msum)) + { + Trace("nl-ext-factor") << "Factoring for literal " << lit + << ", monomial sum is : " << std::endl; + if (Trace.isOn("nl-ext-factor")) + { + ArithMSum::debugPrintMonomialSum(msum, "nl-ext-factor"); + } + std::map > factor_to_mono; + std::map > factor_to_mono_orig; + for (std::map::iterator itm = msum.begin(); + itm != msum.end(); + ++itm) + { + if (!itm->first.isNull()) + { + if (itm->first.getKind() == NONLINEAR_MULT) + { + std::vector children; + for (unsigned i = 0; i < itm->first.getNumChildren(); i++) + { + children.push_back(itm->first[i]); + } + std::map processed; + for (unsigned i = 0; i < itm->first.getNumChildren(); i++) + { + if (processed.find(itm->first[i]) == processed.end()) + { + processed[itm->first[i]] = true; + children[i] = d_one; + if (!itm->second.isNull()) + { + children.push_back(itm->second); + } + Node val = nm->mkNode(MULT, children); + if (!itm->second.isNull()) + { + children.pop_back(); + } + children[i] = itm->first[i]; + val = Rewriter::rewrite(val); + factor_to_mono[itm->first[i]].push_back(val); + factor_to_mono_orig[itm->first[i]].push_back(itm->first); + } + } + } + } + } + for (std::map >::iterator itf = + factor_to_mono.begin(); + itf != factor_to_mono.end(); + ++itf) + { + Node x = itf->first; + if (itf->second.size() == 1) + { + std::map::iterator itm = msum.find(x); + if (itm != msum.end()) + { + itf->second.push_back(itm->second.isNull() ? d_one : itm->second); + factor_to_mono_orig[x].push_back(x); + } + } + if (itf->second.size() <= 1) + { + continue; + } + Node sum = nm->mkNode(PLUS, itf->second); + sum = Rewriter::rewrite(sum); + Trace("nl-ext-factor") + << "* Factored sum for " << x << " : " << sum << std::endl; + Node kf = getFactorSkolem(sum, lemmas); + std::vector poly; + poly.push_back(nm->mkNode(MULT, x, kf)); + std::map >::iterator itfo = + factor_to_mono_orig.find(x); + Assert(itfo != factor_to_mono_orig.end()); + for (std::map::iterator itm = msum.begin(); + itm != msum.end(); + ++itm) + { + if (std::find(itfo->second.begin(), itfo->second.end(), itm->first) + == itfo->second.end()) + { + poly.push_back(ArithMSum::mkCoeffTerm( + itm->second, itm->first.isNull() ? d_one : itm->first)); + } + } + Node polyn = poly.size() == 1 ? poly[0] : nm->mkNode(PLUS, poly); + Trace("nl-ext-factor") + << "...factored polynomial : " << polyn << std::endl; + Node conc_lit = nm->mkNode(atom.getKind(), polyn, d_zero); + conc_lit = Rewriter::rewrite(conc_lit); + if (!polarity) + { + conc_lit = conc_lit.negate(); + } + + std::vector lemma_disj; + lemma_disj.push_back(lit.negate()); + lemma_disj.push_back(conc_lit); + Node flem = nm->mkNode(OR, lemma_disj); + Trace("nl-ext-factor") << "...lemma is " << flem << std::endl; + lemmas.push_back(flem); + } + } + } + } + return lemmas; +} + +Node NlSolver::getFactorSkolem(Node n, std::vector& lemmas) +{ + std::map::iterator itf = d_factor_skolem.find(n); + if (itf == d_factor_skolem.end()) + { + NodeManager* nm = NodeManager::currentNM(); + Node k = nm->mkSkolem("kf", n.getType()); + Node k_eq = Rewriter::rewrite(k.eqNode(n)); + lemmas.push_back(k_eq); + d_factor_skolem[n] = k; + return k; + } + return itf->second; +} + +std::vector NlSolver::checkMonomialInferResBounds() +{ + std::vector lemmas; + NodeManager* nm = NodeManager::currentNM(); + Trace("nl-ext") << "Get monomial resolution inferred bound lemmas..." + << std::endl; + size_t nmterms = d_mterms.size(); + for (unsigned j = 0; j < nmterms; j++) + { + Node a = d_mterms[j]; + std::map > >::iterator itca = + d_ci.find(a); + if (itca == d_ci.end()) + { + continue; + } + for (unsigned k = (j + 1); k < nmterms; k++) + { + Node b = d_mterms[k]; + std::map > >::iterator itcb = + d_ci.find(b); + if (itcb == d_ci.end()) + { + continue; + } + Trace("nl-ext-rbound-debug") << "resolution inferences : compare " << a + << " and " << b << std::endl; + // if they have common factors + std::map::iterator ita = d_mono_diff[a].find(b); + if (ita == d_mono_diff[a].end()) + { + continue; + } + Trace("nl-ext-rbound") << "Get resolution inferences for [a] " << a + << " vs [b] " << b << std::endl; + std::map::iterator itb = d_mono_diff[b].find(a); + Assert(itb != d_mono_diff[b].end()); + Node mv_a = d_model.computeAbstractModelValue(ita->second); + Assert(mv_a.isConst()); + int mv_a_sgn = mv_a.getConst().sgn(); + if (mv_a_sgn == 0) + { + // we don't compare monomials whose current model value is zero + continue; + } + Node mv_b = d_model.computeAbstractModelValue(itb->second); + Assert(mv_b.isConst()); + int mv_b_sgn = mv_b.getConst().sgn(); + if (mv_b_sgn == 0) + { + // we don't compare monomials whose current model value is zero + continue; + } + Trace("nl-ext-rbound") << " [a] factor is " << ita->second + << ", sign in model = " << mv_a_sgn << std::endl; + Trace("nl-ext-rbound") << " [b] factor is " << itb->second + << ", sign in model = " << mv_b_sgn << std::endl; + + std::vector exp; + // bounds of a + for (std::map >::iterator itcac = + itca->second.begin(); + itcac != itca->second.end(); + ++itcac) + { + Node coeff_a = itcac->first; + for (std::map::iterator itcar = itcac->second.begin(); + itcar != itcac->second.end(); + ++itcar) + { + Node rhs_a = itcar->first; + Node rhs_a_res_base = nm->mkNode(MULT, itb->second, rhs_a); + rhs_a_res_base = Rewriter::rewrite(rhs_a_res_base); + if (hasNewMonomials(rhs_a_res_base, d_ms)) + { + continue; + } + Kind type_a = itcar->second; + exp.push_back(d_ci_exp[a][coeff_a][rhs_a]); + + // bounds of b + for (std::map >::iterator itcbc = + itcb->second.begin(); + itcbc != itcb->second.end(); + ++itcbc) + { + Node coeff_b = itcbc->first; + Node rhs_a_res = ArithMSum::mkCoeffTerm(coeff_b, rhs_a_res_base); + for (std::map::iterator itcbr = itcbc->second.begin(); + itcbr != itcbc->second.end(); + ++itcbr) + { + Node rhs_b = itcbr->first; + Node rhs_b_res = nm->mkNode(MULT, ita->second, rhs_b); + rhs_b_res = ArithMSum::mkCoeffTerm(coeff_a, rhs_b_res); + rhs_b_res = Rewriter::rewrite(rhs_b_res); + if (hasNewMonomials(rhs_b_res, d_ms)) + { + continue; + } + Kind type_b = itcbr->second; + exp.push_back(d_ci_exp[b][coeff_b][rhs_b]); + if (Trace.isOn("nl-ext-rbound")) + { + Trace("nl-ext-rbound") << "* try bounds : "; + debugPrintBound("nl-ext-rbound", coeff_a, a, type_a, rhs_a); + Trace("nl-ext-rbound") << std::endl; + Trace("nl-ext-rbound") << " "; + debugPrintBound("nl-ext-rbound", coeff_b, b, type_b, rhs_b); + Trace("nl-ext-rbound") << std::endl; + } + Kind types[2]; + for (unsigned r = 0; r < 2; r++) + { + Node pivot_factor = r == 0 ? itb->second : ita->second; + int pivot_factor_sign = r == 0 ? mv_b_sgn : mv_a_sgn; + types[r] = r == 0 ? type_a : type_b; + if (pivot_factor_sign == (r == 0 ? 1 : -1)) + { + types[r] = reverseRelationKind(types[r]); + } + if (pivot_factor_sign == 1) + { + exp.push_back(nm->mkNode(GT, pivot_factor, d_zero)); + } + else + { + exp.push_back(nm->mkNode(LT, pivot_factor, d_zero)); + } + } + Kind jk = transKinds(types[0], types[1]); + Trace("nl-ext-rbound-debug") + << "trans kind : " << types[0] << " + " << types[1] << " = " + << jk << std::endl; + if (jk != UNDEFINED_KIND) + { + Node conc = nm->mkNode(jk, rhs_a_res, rhs_b_res); + Node conc_mv = d_model.computeAbstractModelValue(conc); + if (conc_mv == d_false) + { + Node rblem = nm->mkNode(IMPLIES, nm->mkNode(AND, exp), conc); + Trace("nl-ext-rbound-lemma-debug") + << "Resolution bound lemma " + "(pre-rewrite) " + ": " + << rblem << std::endl; + rblem = Rewriter::rewrite(rblem); + Trace("nl-ext-rbound-lemma") + << "Resolution bound lemma : " << rblem << std::endl; + lemmas.push_back(rblem); + } + } + exp.pop_back(); + exp.pop_back(); + exp.pop_back(); + } + } + exp.pop_back(); + } + } + } + } + return lemmas; +} + +} // namespace nl +} // namespace arith +} // namespace theory +} // namespace CVC4 diff --git a/src/theory/arith/nl/nl_solver.h b/src/theory/arith/nl/nl_solver.h new file mode 100644 index 000000000..35c51569b --- /dev/null +++ b/src/theory/arith/nl/nl_solver.h @@ -0,0 +1,370 @@ +/********************* */ +/*! \file nl_solver.h + ** \verbatim + ** Top contributors (to current version): + ** Andrew Reynolds, Tim King + ** 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 Solver for standard non-linear constraints + **/ + +#ifndef CVC4__THEORY__ARITH__NL_SOLVER_H +#define CVC4__THEORY__ARITH__NL_SOLVER_H + +#include +#include +#include +#include + +#include "context/cdhashset.h" +#include "context/cdinsert_hashmap.h" +#include "context/cdlist.h" +#include "context/cdqueue.h" +#include "context/context.h" +#include "expr/kind.h" +#include "expr/node.h" +#include "theory/arith/nl/nl_constraint.h" +#include "theory/arith/nl/nl_lemma_utils.h" +#include "theory/arith/nl/nl_model.h" +#include "theory/arith/nl/nl_monomial.h" +#include "theory/arith/theory_arith.h" + +namespace CVC4 { +namespace theory { +namespace arith { +namespace nl { + +typedef std::map NodeMultiset; + +/** Non-linear solver class + * + * This class implements model-based refinement schemes + * for non-linear arithmetic, described in: + * + * - "Invariant Checking of NRA Transition Systems + * via Incremental Reduction to LRA with EUF" by + * Cimatti et al., TACAS 2017. + * + * - Section 5 of "Desiging Theory Solvers with + * Extensions" by Reynolds et al., FroCoS 2017. + */ +class NlSolver +{ + typedef std::map MonomialExponentMap; + typedef context::CDHashSet NodeSet; + + public: + NlSolver(TheoryArith& containing, NlModel& model); + ~NlSolver(); + + /** init last call + * + * This is called at the beginning of last call effort check, where + * assertions are the set of assertions belonging to arithmetic, + * false_asserts is the subset of assertions that are false in the current + * model, and xts is the set of extended function terms that are active in + * the current context. + */ + void initLastCall(const std::vector& assertions, + const std::vector& false_asserts, + const std::vector& xts); + //-------------------------------------------- lemma schemas + /** check split zero + * + * Returns a set of theory lemmas of the form + * t = 0 V t != 0 + * where t is a term that exists in the current context. + */ + std::vector checkSplitZero(); + + /** check monomial sign + * + * Returns a set of valid theory lemmas, based on a + * lemma schema which ensures that non-linear monomials + * respect sign information based on their facts. + * For more details, see Section 5 of "Design Theory + * Solvers with Extensions" by Reynolds et al., FroCoS 2017, + * Figure 5, this is the schema "Sign". + * + * Examples: + * + * x > 0 ^ y > 0 => x*y > 0 + * x < 0 => x*y*y < 0 + * x = 0 => x*y*z = 0 + */ + std::vector checkMonomialSign(); + + /** check monomial magnitude + * + * Returns a set of valid theory lemmas, based on a + * lemma schema which ensures that comparisons between + * non-linear monomials respect the magnitude of their + * factors. + * For more details, see Section 5 of "Design Theory + * Solvers with Extensions" by Reynolds et al., FroCoS 2017, + * Figure 5, this is the schema "Magnitude". + * + * Examples: + * + * |x|>|y| => |x*z|>|y*z| + * |x|>|y| ^ |z|>|w| ^ |x|>=1 => |x*x*z*u|>|y*w| + * + * Argument c indicates the class of inferences to perform for the + * (non-linear) monomials in the vector d_ms. 0 : compare non-linear monomials + * against 1, 1 : compare non-linear monomials against variables, 2 : compare + * non-linear monomials against other non-linear monomials. + */ + std::vector checkMonomialMagnitude(unsigned c); + + /** check monomial inferred bounds + * + * Returns a set of valid theory lemmas, based on a + * lemma schema that infers new constraints about existing + * terms based on mulitplying both sides of an existing + * constraint by a term. + * For more details, see Section 5 of "Design Theory + * Solvers with Extensions" by Reynolds et al., FroCoS 2017, + * Figure 5, this is the schema "Multiply". + * + * Examples: + * + * x > 0 ^ (y > z + w) => x*y > x*(z+w) + * x < 0 ^ (y > z + w) => x*y < x*(z+w) + * ...where (y > z + w) and x*y are a constraint and term + * that occur in the current context. + */ + std::vector checkMonomialInferBounds( + std::vector& nt_lemmas, + const std::vector& asserts, + const std::vector& false_asserts); + + /** check factoring + * + * Returns a set of valid theory lemmas, based on a + * lemma schema that states a relationship betwen monomials + * with common factors that occur in the same constraint. + * + * Examples: + * + * x*z+y*z > t => ( k = x + y ^ k*z > t ) + * ...where k is fresh and x*z + y*z > t is a + * constraint that occurs in the current context. + */ + std::vector checkFactoring(const std::vector& asserts, + const std::vector& false_asserts); + + /** check monomial infer resolution bounds + * + * Returns a set of valid theory lemmas, based on a + * lemma schema which "resolves" upper bounds + * of one inequality with lower bounds for another. + * This schema is not enabled by default, and can + * be enabled by --nl-ext-rbound. + * + * Examples: + * + * ( y>=0 ^ s <= x*z ^ x*y <= t ) => y*s <= z*t + * ...where s <= x*z and x*y <= t are constraints + * that occur in the current context. + */ + std::vector checkMonomialInferResBounds(); + + /** check tangent planes + * + * Returns a set of valid theory lemmas, based on an + * "incremental linearization" of non-linear monomials. + * This linearization is accomplished by adding constraints + * corresponding to "tangent planes" at the current + * model value of each non-linear monomial. In particular + * consider the definition for constants a,b : + * T_{a,b}( x*y ) = b*x + a*y - a*b. + * The lemmas added by this function are of the form : + * ( ( x>a ^ yb) ) => x*y < T_{a,b}( x*y ) + * ( ( x>a ^ y>b) ^ (x x*y > T_{a,b}( x*y ) + * It is inspired by "Invariant Checking of NRA Transition + * Systems via Incremental Reduction to LRA with EUF" by + * Cimatti et al., TACAS 2017. + * This schema is not terminating in general. + * It is not enabled by default, and can + * be enabled by --nl-ext-tplanes. + * + * Examples: + * + * ( ( x>2 ^ y>5) ^ (x<2 ^ y<5) ) => x*y > 5*x + 2*y - 10 + * ( ( x>2 ^ y<5) ^ (x<2 ^ y>5) ) => x*y < 5*x + 2*y - 10 + */ + std::vector checkTangentPlanes(); + + //-------------------------------------------- end lemma schemas + private: + // The theory of arithmetic containing this extension. + TheoryArith& d_containing; + /** 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_two; + Node d_true; + Node d_false; + /** Context-independent database of monomial information */ + MonomialDb d_mdb; + /** Context-independent database of constraint information */ + ConstraintDb d_cdb; + + // ( x*y, x*z, y ) for each pair of monomials ( x*y, x*z ) with common factors + std::map > d_mono_diff; + + /** cache of terms t for which we have added the lemma ( t = 0 V t != 0 ). */ + NodeSet d_zero_split; + + // ordering, stores variables and 0,1,-1 + std::map d_order_vars; + std::vector d_order_points; + + // information about monomials + std::vector d_ms; + std::vector d_ms_vars; + std::map d_ms_proc; + std::vector d_mterms; + + // list of monomials with factors whose model value is non-constant in model + // e.g. y*cos( x ) + std::map d_m_nconst_factor; + /** the set of monomials we should apply tangent planes to */ + std::unordered_set d_tplane_refine; + /** maps nodes to their factor skolems */ + std::map d_factor_skolem; + /** tangent plane bounds */ + std::map > d_tangent_val_bound[4]; + // term -> coeff -> rhs -> ( status, exp, b ), + // where we have that : exp => ( coeff * term rhs ) + // b is true if degree( term ) >= degree( rhs ) + std::map > > d_ci; + std::map > > d_ci_exp; + std::map > > d_ci_max; + + /** Make literal */ + static Node mkLit(Node a, Node b, int status, bool isAbsolute = false); + /** register monomial */ + void setMonomialFactor(Node a, Node b, const NodeMultiset& common); + /** assign order ids */ + void assignOrderIds(std::vector& vars, + NodeMultiset& d_order, + bool isConcrete, + bool isAbsolute); + + /** Check whether we have already inferred a relationship between monomials + * x and y based on the information in cmp_infers. This computes the + * transitive closure of the relation stored in cmp_infers. + */ + bool cmp_holds(Node x, + Node y, + std::map >& cmp_infers, + std::vector& exp, + std::map& visited); + /** In the following functions, status states a relationship + * between two arithmetic terms, where: + * 0 : equal + * 1 : greater than or equal + * 2 : greater than + * -X : (greater -> less) + * TODO (#1287) make this an enum? + */ + /** compute the sign of a. + * + * Calls to this function are such that : + * exp => ( oa = a ^ a 0 ) + * + * This function iterates over the factors of a, + * where a_index is the index of the factor in a + * we are currently looking at. + * + * This function returns a status, which indicates + * a's relationship to 0. + * We add lemmas to lem of the form given by the + * lemma schema checkSign(...). + */ + int compareSign(Node oa, + Node a, + unsigned a_index, + int status, + std::vector& exp, + std::vector& lem); + /** compare monomials a and b + * + * Initially, a call to this function is such that : + * exp => ( oa = a ^ ob = b ) + * + * This function returns true if we can infer a valid + * arithmetic lemma of the form : + * P => abs( a ) >= abs( b ) + * where P is true and abs( a ) >= abs( b ) is false in the + * current model. + * + * This function is implemented by "processing" factors + * of monomials a and b until an inference of the above + * form can be made. For example, if : + * a = x*x*y and b = z*w + * Assuming we are trying to show abs( a ) >= abs( c ), + * then if abs( M( x ) ) >= abs( M( z ) ) where M is the current model, + * then we can add abs( x ) >= abs( z ) to our explanation, and + * mark one factor of x as processed in a, and + * one factor of z as processed in b. The number of processed factors of a + * and b are stored in a_exp_proc and b_exp_proc respectively. + * + * cmp_infers stores information that is helpful + * in discarding redundant inferences. For example, + * we do not want to infer abs( x ) >= abs( z ) if + * we have already inferred abs( x ) >= abs( y ) and + * abs( y ) >= abs( z ). + * It stores entries of the form (status,t1,t2)->F, + * which indicates that we constructed a lemma F that + * showed t1 t2. + * + * We add lemmas to lem of the form given by the + * lemma schema checkMagnitude(...). + */ + bool compareMonomial( + Node oa, + Node a, + NodeMultiset& a_exp_proc, + Node ob, + Node b, + NodeMultiset& b_exp_proc, + std::vector& exp, + std::vector& lem, + std::map > >& cmp_infers); + /** helper function for above + * + * The difference is the inputs a_index and b_index, which are the indices of + * children (factors) in monomials a and b which we are currently looking at. + */ + bool compareMonomial( + Node oa, + Node a, + unsigned a_index, + NodeMultiset& a_exp_proc, + Node ob, + Node b, + unsigned b_index, + NodeMultiset& b_exp_proc, + int status, + std::vector& exp, + std::vector& lem, + std::map > >& cmp_infers); + /** Get factor skolem for n, add resulting lemmas to lemmas */ + Node getFactorSkolem(Node n, std::vector& lemmas); +}; /* class NlSolver */ + +} // namespace nl +} // namespace arith +} // namespace theory +} // namespace CVC4 + +#endif /* CVC4__THEORY__ARITH__NL_SOLVER_H */ diff --git a/src/theory/arith/nl/nonlinear_extension.cpp b/src/theory/arith/nl/nonlinear_extension.cpp new file mode 100644 index 000000000..4b2d2fd37 --- /dev/null +++ b/src/theory/arith/nl/nonlinear_extension.cpp @@ -0,0 +1,841 @@ +/********************* */ +/*! \file nonlinear_extension.cpp + ** \verbatim + ** Top contributors (to current version): + ** Andrew Reynolds, Tim King, Aina Niemetz + ** 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 "theory/arith/nl/nonlinear_extension.h" + +#include "options/arith_options.h" +#include "theory/arith/arith_utilities.h" +#include "theory/arith/theory_arith.h" +#include "theory/ext_theory.h" +#include "theory/theory_model.h" + +using namespace CVC4::kind; + +namespace CVC4 { +namespace theory { +namespace arith { +namespace nl { + +NonlinearExtension::NonlinearExtension(TheoryArith& containing, + eq::EqualityEngine* ee) + : d_lemmas(containing.getUserContext()), + d_containing(containing), + d_ee(ee), + d_needsLastCall(false), + d_model(containing.getSatContext()), + d_trSlv(d_model), + d_nlSlv(containing, d_model), + d_builtModel(containing.getSatContext(), false) +{ + d_true = NodeManager::currentNM()->mkConst(true); + d_zero = NodeManager::currentNM()->mkConst(Rational(0)); + d_one = NodeManager::currentNM()->mkConst(Rational(1)); + d_neg_one = NodeManager::currentNM()->mkConst(Rational(-1)); +} + +NonlinearExtension::~NonlinearExtension() {} + +bool NonlinearExtension::getCurrentSubstitution( + int effort, + const std::vector& vars, + std::vector& subs, + std::map>& exp) +{ + // get the constant equivalence classes + std::map> rep_to_subs_index; + + bool retVal = false; + for (unsigned i = 0; i < vars.size(); i++) + { + Node n = vars[i]; + if (d_ee->hasTerm(n)) + { + Node nr = d_ee->getRepresentative(n); + if (nr.isConst()) + { + subs.push_back(nr); + Trace("nl-subs") << "Basic substitution : " << n << " -> " << nr + << std::endl; + exp[n].push_back(n.eqNode(nr)); + retVal = true; + } + else + { + rep_to_subs_index[nr].push_back(i); + subs.push_back(n); + } + } + else + { + subs.push_back(n); + } + } + + // return true if the substitution is non-trivial + return retVal; +} + +std::pair NonlinearExtension::isExtfReduced( + int effort, Node n, Node on, const std::vector& exp) const +{ + if (n != d_zero) + { + Kind k = n.getKind(); + return std::make_pair(k != NONLINEAR_MULT && !isTranscendentalKind(k), + Node::null()); + } + Assert(n == d_zero); + if (on.getKind() == NONLINEAR_MULT) + { + Trace("nl-ext-zero-exp") + << "Infer zero : " << on << " == " << n << std::endl; + // minimize explanation if a substitution+rewrite results in zero + const std::set vars(on.begin(), on.end()); + + for (unsigned i = 0, size = exp.size(); i < size; i++) + { + Trace("nl-ext-zero-exp") + << " exp[" << i << "] = " << exp[i] << std::endl; + std::vector eqs; + if (exp[i].getKind() == EQUAL) + { + eqs.push_back(exp[i]); + } + else if (exp[i].getKind() == AND) + { + for (const Node& ec : exp[i]) + { + if (ec.getKind() == EQUAL) + { + eqs.push_back(ec); + } + } + } + + for (unsigned j = 0; j < eqs.size(); j++) + { + for (unsigned r = 0; r < 2; r++) + { + if (eqs[j][r] == d_zero && vars.find(eqs[j][1 - r]) != vars.end()) + { + Trace("nl-ext-zero-exp") + << "...single exp : " << eqs[j] << std::endl; + return std::make_pair(true, eqs[j]); + } + } + } + } + } + return std::make_pair(true, Node::null()); +} + +void NonlinearExtension::sendLemmas(const std::vector& out, + bool preprocess, + std::map& lemSE) +{ + std::map::iterator its; + for (const Node& lem : out) + { + Trace("nl-ext-lemma") << "NonlinearExtension::Lemma : " << lem << std::endl; + d_containing.getOutputChannel().lemma(lem, false, preprocess); + // process the side effect + its = lemSE.find(lem); + if (its != lemSE.end()) + { + processSideEffect(its->second); + } + // add to cache if not preprocess + if (!preprocess) + { + d_lemmas.insert(lem); + } + // also indicate this is a tautology + d_model.addTautology(lem); + } +} + +void NonlinearExtension::processSideEffect(const NlLemmaSideEffect& se) +{ + d_trSlv.processSideEffect(se); +} + +unsigned NonlinearExtension::filterLemma(Node lem, std::vector& out) +{ + Trace("nl-ext-lemma-debug") + << "NonlinearExtension::Lemma pre-rewrite : " << lem << std::endl; + lem = Rewriter::rewrite(lem); + if (d_lemmas.find(lem) != d_lemmas.end() + || std::find(out.begin(), out.end(), lem) != out.end()) + { + Trace("nl-ext-lemma-debug") + << "NonlinearExtension::Lemma duplicate : " << lem << std::endl; + return 0; + } + out.push_back(lem); + return 1; +} + +unsigned NonlinearExtension::filterLemmas(std::vector& lemmas, + std::vector& out) +{ + if (options::nlExtEntailConflicts()) + { + // check if any are entailed to be false + for (const Node& lem : lemmas) + { + Node ch_lemma = lem.negate(); + ch_lemma = Rewriter::rewrite(ch_lemma); + Trace("nl-ext-et-debug") + << "Check entailment of " << ch_lemma << "..." << std::endl; + std::pair et = d_containing.getValuation().entailmentCheck( + options::TheoryOfMode::THEORY_OF_TYPE_BASED, ch_lemma); + Trace("nl-ext-et-debug") << "entailment test result : " << et.first << " " + << et.second << std::endl; + if (et.first) + { + Trace("nl-ext-et") << "*** Lemma entailed to be in conflict : " << lem + << std::endl; + // return just this lemma + if (filterLemma(lem, out) > 0) + { + lemmas.clear(); + return 1; + } + } + } + } + + unsigned sum = 0; + for (const Node& lem : lemmas) + { + sum += filterLemma(lem, out); + } + lemmas.clear(); + return sum; +} + +void NonlinearExtension::getAssertions(std::vector& assertions) +{ + Trace("nl-ext") << "Getting assertions..." << std::endl; + NodeManager* nm = NodeManager::currentNM(); + // get the assertions + std::map init_bounds[2]; + std::map init_bounds_lit[2]; + unsigned nassertions = 0; + std::unordered_set init_assertions; + for (Theory::assertions_iterator it = d_containing.facts_begin(); + it != d_containing.facts_end(); + ++it) + { + nassertions++; + const Assertion& assertion = *it; + Node lit = assertion.d_assertion; + init_assertions.insert(lit); + // check for concrete bounds + bool pol = lit.getKind() != NOT; + Node atom_orig = lit.getKind() == NOT ? lit[0] : lit; + + std::vector atoms; + if (atom_orig.getKind() == EQUAL) + { + if (pol) + { + // t = s is ( t >= s ^ t <= s ) + for (unsigned i = 0; i < 2; i++) + { + Node atom_new = nm->mkNode(GEQ, atom_orig[i], atom_orig[1 - i]); + atom_new = Rewriter::rewrite(atom_new); + atoms.push_back(atom_new); + } + } + } + else + { + atoms.push_back(atom_orig); + } + + for (const Node& atom : atoms) + { + // non-strict bounds only + if (atom.getKind() == GEQ || (!pol && atom.getKind() == GT)) + { + Node p = atom[0]; + Assert(atom[1].isConst()); + Rational bound = atom[1].getConst(); + if (!pol) + { + if (atom[0].getType().isInteger()) + { + // ~( p >= c ) ---> ( p <= c-1 ) + bound = bound - Rational(1); + } + } + unsigned bindex = pol ? 0 : 1; + bool setBound = true; + std::map::iterator itb = init_bounds[bindex].find(p); + if (itb != init_bounds[bindex].end()) + { + if (itb->second == bound) + { + setBound = atom_orig.getKind() == EQUAL; + } + else + { + setBound = pol ? itb->second < bound : itb->second > bound; + } + if (setBound) + { + // the bound is subsumed + init_assertions.erase(init_bounds_lit[bindex][p]); + } + } + if (setBound) + { + Trace("nl-ext-init") << (pol ? "Lower" : "Upper") << " bound for " + << p << " : " << bound << std::endl; + init_bounds[bindex][p] = bound; + init_bounds_lit[bindex][p] = lit; + } + } + } + } + // for each bound that is the same, ensure we've inferred the equality + for (std::pair& ib : init_bounds[0]) + { + Node p = ib.first; + Node lit1 = init_bounds_lit[0][p]; + if (lit1.getKind() != EQUAL) + { + std::map::iterator itb = init_bounds[1].find(p); + if (itb != init_bounds[1].end()) + { + if (ib.second == itb->second) + { + Node eq = p.eqNode(nm->mkConst(ib.second)); + eq = Rewriter::rewrite(eq); + Node lit2 = init_bounds_lit[1][p]; + Assert(lit2.getKind() != EQUAL); + // use the equality instead, thus these are redundant + init_assertions.erase(lit1); + init_assertions.erase(lit2); + init_assertions.insert(eq); + } + } + } + } + + for (const Node& a : init_assertions) + { + assertions.push_back(a); + } + Trace("nl-ext") << "...keep " << assertions.size() << " / " << nassertions + << " assertions." << std::endl; +} + +std::vector NonlinearExtension::checkModelEval( + const std::vector& assertions) +{ + std::vector false_asserts; + for (size_t i = 0; i < assertions.size(); ++i) + { + Node lit = assertions[i]; + Node atom = lit.getKind() == NOT ? lit[0] : lit; + Node litv = d_model.computeConcreteModelValue(lit); + Trace("nl-ext-mv-assert") << "M[[ " << lit << " ]] -> " << litv; + if (litv != d_true) + { + Trace("nl-ext-mv-assert") << " [model-false]" << std::endl; + false_asserts.push_back(lit); + } + else + { + Trace("nl-ext-mv-assert") << std::endl; + } + } + return false_asserts; +} + +bool NonlinearExtension::checkModel(const std::vector& assertions, + const std::vector& false_asserts, + std::vector& lemmas, + std::vector& gs) +{ + Trace("nl-ext-cm") << "--- check-model ---" << std::endl; + + // get the presubstitution + Trace("nl-ext-cm-debug") << " apply pre-substitution..." << std::endl; + std::vector passertions = assertions; + + // preprocess the assertions with the trancendental solver + if (!d_trSlv.preprocessAssertionsCheckModel(passertions)) + { + return false; + } + + Trace("nl-ext-cm") << "-----" << std::endl; + unsigned tdegree = d_trSlv.getTaylorDegree(); + bool ret = + d_model.checkModel(passertions, false_asserts, tdegree, lemmas, gs); + return ret; +} + +int NonlinearExtension::checkLastCall(const std::vector& assertions, + const std::vector& false_asserts, + const std::vector& xts, + std::vector& lems, + std::vector& lemsPp, + std::vector& wlems, + std::map& lemSE) +{ + // initialize the non-linear solver + d_nlSlv.initLastCall(assertions, false_asserts, xts); + // 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() || !lemsPp.empty()) + { + Trace("nl-ext") << " ...finished with " << lems.size() + << " new lemmas during registration." << std::endl; + return lems.size() + lemsPp.size(); + } + + //----------------------------------- possibly split on zero + if (options::nlExtSplitZero()) + { + Trace("nl-ext") << "Get zero split lemmas..." << std::endl; + lemmas = d_nlSlv.checkSplitZero(); + filterLemmas(lemmas, lems); + if (!lems.empty()) + { + Trace("nl-ext") << " ...finished with " << lems.size() << " new lemmas." + << std::endl; + return lems.size(); + } + } + + //-----------------------------------initial lemmas for transcendental + //functions + lemmas = d_trSlv.checkTranscendentalInitialRefine(); + filterLemmas(lemmas, lems); + if (!lems.empty()) + { + Trace("nl-ext") << " ...finished with " << lems.size() << " new lemmas." + << std::endl; + return lems.size(); + } + + //-----------------------------------lemmas based on sign (comparison to zero) + lemmas = d_nlSlv.checkMonomialSign(); + filterLemmas(lemmas, lems); + if (!lems.empty()) + { + Trace("nl-ext") << " ...finished with " << lems.size() << " new lemmas." + << std::endl; + return lems.size(); + } + + //-----------------------------------monotonicity of transdental functions + lemmas = d_trSlv.checkTranscendentalMonotonic(); + filterLemmas(lemmas, lems); + if (!lems.empty()) + { + Trace("nl-ext") << " ...finished with " << lems.size() << " new lemmas." + << std::endl; + return lems.size(); + } + + //-----------------------------------lemmas based on magnitude of non-zero + //monomials + for (unsigned c = 0; c < 3; c++) + { + // c is effort level + lemmas = d_nlSlv.checkMonomialMagnitude(c); + unsigned nlem = lemmas.size(); + filterLemmas(lemmas, lems); + if (!lems.empty()) + { + Trace("nl-ext") << " ...finished with " << lems.size() + << " new lemmas (out of possible " << nlem << ")." + << std::endl; + return lems.size(); + } + } + + //-----------------------------------inferred bounds lemmas + // e.g. x >= t => y*x >= y*t + std::vector nt_lemmas; + lemmas = + d_nlSlv.checkMonomialInferBounds(nt_lemmas, assertions, false_asserts); + // Trace("nl-ext") << "Bound lemmas : " << lemmas.size() << ", " << + // nt_lemmas.size() << std::endl; prioritize lemmas that do not + // introduce new monomials + filterLemmas(lemmas, lems); + + if (options::nlExtTangentPlanes() && options::nlExtTangentPlanesInterleave()) + { + lemmas = d_nlSlv.checkTangentPlanes(); + filterLemmas(lemmas, lems); + } + + if (!lems.empty()) + { + Trace("nl-ext") << " ...finished with " << lems.size() << " new lemmas." + << std::endl; + return lems.size(); + } + + // from inferred bound inferences : now do ones that introduce new terms + filterLemmas(nt_lemmas, lems); + if (!lems.empty()) + { + Trace("nl-ext") << " ...finished with " << lems.size() + << " new (monomial-introducing) lemmas." << std::endl; + return lems.size(); + } + + //------------------------------------factoring lemmas + // x*y + x*z >= t => exists k. k = y + z ^ x*k >= t + if (options::nlExtFactor()) + { + lemmas = d_nlSlv.checkFactoring(assertions, false_asserts); + filterLemmas(lemmas, lems); + if (!lems.empty()) + { + Trace("nl-ext") << " ...finished with " << lems.size() << " new lemmas." + << std::endl; + return lems.size(); + } + } + + //------------------------------------resolution bound inferences + // e.g. ( y>=0 ^ s <= x*z ^ x*y <= t ) => y*s <= z*t + if (options::nlExtResBound()) + { + lemmas = d_nlSlv.checkMonomialInferResBounds(); + filterLemmas(lemmas, lems); + if (!lems.empty()) + { + Trace("nl-ext") << " ...finished with " << lems.size() << " new lemmas." + << std::endl; + return lems.size(); + } + } + + //------------------------------------tangent planes + if (options::nlExtTangentPlanes() && !options::nlExtTangentPlanesInterleave()) + { + lemmas = d_nlSlv.checkTangentPlanes(); + filterLemmas(lemmas, wlems); + } + if (options::nlExtTfTangentPlanes()) + { + lemmas = d_trSlv.checkTranscendentalTangentPlanes(lemSE); + filterLemmas(lemmas, wlems); + } + Trace("nl-ext") << " ...finished with " << wlems.size() << " waiting lemmas." + << std::endl; + + return 0; +} + +void NonlinearExtension::check(Theory::Effort e) +{ + Trace("nl-ext") << std::endl; + Trace("nl-ext") << "NonlinearExtension::check, effort = " << e + << ", built model = " << d_builtModel.get() << std::endl; + if (e == Theory::EFFORT_FULL) + { + d_containing.getExtTheory()->clearCache(); + d_needsLastCall = true; + if (options::nlExtRewrites()) + { + std::vector nred; + if (!d_containing.getExtTheory()->doInferences(0, nred)) + { + Trace("nl-ext") << "...sent no lemmas, # extf to reduce = " + << nred.size() << std::endl; + if (nred.empty()) + { + d_needsLastCall = false; + } + } + else + { + Trace("nl-ext") << "...sent lemmas." << std::endl; + } + } + } + else + { + // If we computed lemmas during collectModelInfo, send them now. + if (!d_cmiLemmas.empty() || !d_cmiLemmasPp.empty()) + { + sendLemmas(d_cmiLemmas, false, d_cmiLemmasSE); + sendLemmas(d_cmiLemmasPp, true, d_cmiLemmasSE); + return; + } + // Otherwise, we will answer SAT. The values that we approximated are + // recorded as approximations here. + TheoryModel* tm = d_containing.getValuation().getModel(); + for (std::pair>& a : d_approximations) + { + if (a.second.second.isNull()) + { + tm->recordApproximation(a.first, a.second.first); + } + else + { + tm->recordApproximation(a.first, a.second.first, a.second.second); + } + } + } +} + +bool NonlinearExtension::modelBasedRefinement( + std::vector& mlems, + std::vector& mlemsPp, + std::map& lemSE) +{ + // get the assertions + std::vector assertions; + getAssertions(assertions); + + Trace("nl-ext-mv-assert") + << "Getting model values... check for [model-false]" << std::endl; + // get the assertions that are false in the model + const std::vector false_asserts = checkModelEval(assertions); + + // get the extended terms belonging to this theory + std::vector xts; + d_containing.getExtTheory()->getTerms(xts); + + if (Trace.isOn("nl-ext-debug")) + { + Trace("nl-ext-debug") << " processing NonlinearExtension::check : " + << std::endl; + Trace("nl-ext-debug") << " " << false_asserts.size() + << " false assertions" << std::endl; + Trace("nl-ext-debug") << " " << xts.size() + << " extended terms: " << std::endl; + Trace("nl-ext-debug") << " "; + for (unsigned j = 0; j < xts.size(); j++) + { + Trace("nl-ext-debug") << xts[j] << " "; + } + Trace("nl-ext-debug") << std::endl; + } + + // compute whether shared terms have correct values + unsigned num_shared_wrong_value = 0; + std::vector shared_term_value_splits; + // must ensure that shared terms are equal to their concrete value + Trace("nl-ext-mv") << "Shared terms : " << std::endl; + for (context::CDList::const_iterator its = + d_containing.shared_terms_begin(); + its != d_containing.shared_terms_end(); + ++its) + { + TNode shared_term = *its; + // compute its value in the model, and its evaluation in the model + Node stv0 = d_model.computeConcreteModelValue(shared_term); + Node stv1 = d_model.computeAbstractModelValue(shared_term); + d_model.printModelValue("nl-ext-mv", shared_term); + if (stv0 != stv1) + { + num_shared_wrong_value++; + Trace("nl-ext-mv") << "Bad shared term value : " << shared_term + << std::endl; + if (shared_term != stv0) + { + // split on the value, this is non-terminating in general, TODO : + // improve this + Node eq = shared_term.eqNode(stv0); + shared_term_value_splits.push_back(eq); + } + else + { + // this can happen for transcendental functions + // the problem is that we cannot evaluate transcendental functions + // (they don't have a rewriter that returns constants) + // thus, the actual value in their model can be themselves, hence we + // have no reference point to rule out the current model. In this + // case, we may set incomplete below. + } + } + } + Trace("nl-ext-debug") << " " << num_shared_wrong_value + << " shared terms with wrong model value." << std::endl; + bool needsRecheck; + do + { + d_model.resetCheck(); + needsRecheck = false; + // complete_status: + // 1 : we may answer SAT, -1 : we may not answer SAT, 0 : unknown + int complete_status = 1; + // lemmas that should be sent later + std::vector wlems; + // We require a check either if an assertion is false or a shared term has + // a wrong value + if (!false_asserts.empty() || num_shared_wrong_value > 0) + { + complete_status = num_shared_wrong_value > 0 ? -1 : 0; + checkLastCall( + assertions, false_asserts, xts, mlems, mlemsPp, wlems, lemSE); + if (!mlems.empty() || !mlemsPp.empty()) + { + return true; + } + } + Trace("nl-ext") << "Finished check with status : " << complete_status + << std::endl; + + // if we did not add a lemma during check and there is a chance for SAT + if (complete_status == 0) + { + Trace("nl-ext") + << "Check model based on bounds for irrational-valued functions..." + << std::endl; + // check the model based on simple solving of equalities and using + // error bounds on the Taylor approximation of transcendental functions. + std::vector lemmas; + std::vector gs; + if (checkModel(assertions, false_asserts, lemmas, gs)) + { + complete_status = 1; + } + for (const Node& mg : gs) + { + Node mgr = Rewriter::rewrite(mg); + mgr = d_containing.getValuation().ensureLiteral(mgr); + d_containing.getOutputChannel().requirePhase(mgr, true); + d_builtModel = true; + } + filterLemmas(lemmas, mlems); + if (!mlems.empty()) + { + return true; + } + } + + // if we have not concluded SAT + if (complete_status != 1) + { + // flush the waiting lemmas + if (!wlems.empty()) + { + mlems.insert(mlems.end(), wlems.begin(), wlems.end()); + Trace("nl-ext") << "...added " << wlems.size() << " waiting lemmas." + << std::endl; + return true; + } + // resort to splitting on shared terms with their model value + // if we did not add any lemmas + if (num_shared_wrong_value > 0) + { + complete_status = -1; + if (!shared_term_value_splits.empty()) + { + std::vector stvLemmas; + for (const Node& eq : shared_term_value_splits) + { + Node req = Rewriter::rewrite(eq); + Node literal = d_containing.getValuation().ensureLiteral(req); + d_containing.getOutputChannel().requirePhase(literal, true); + Trace("nl-ext-debug") << "Split on : " << literal << std::endl; + Node split = literal.orNode(literal.negate()); + filterLemma(split, stvLemmas); + } + if (!stvLemmas.empty()) + { + mlems.insert(mlems.end(), stvLemmas.begin(), stvLemmas.end()); + Trace("nl-ext") << "...added " << stvLemmas.size() + << " shared term value split lemmas." << std::endl; + return true; + } + } + else + { + // this can happen if we are trying to do theory combination with + // trancendental functions + // since their model value cannot even be computed exactly + } + } + + // we are incomplete + if (options::nlExtIncPrecision() && d_model.usedApproximate()) + { + 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_trSlv.getTaylorDegree() << std::endl; + } + else + { + Trace("nl-ext") << "...failed to send lemma in " + "NonLinearExtension, set incomplete" + << std::endl; + d_containing.getOutputChannel().setIncomplete(); + } + } + } while (needsRecheck); + + // did not add lemmas + return false; +} + +void NonlinearExtension::interceptModel(std::map& arithModel) +{ + if (!needsCheckLastEffort()) + { + // no non-linear constraints, we are done + return; + } + Trace("nl-ext") << "NonlinearExtension::interceptModel begin" << std::endl; + d_model.reset(d_containing.getValuation().getModel(), arithModel); + // run a last call effort check + d_cmiLemmas.clear(); + d_cmiLemmasPp.clear(); + d_cmiLemmasSE.clear(); + if (!d_builtModel.get()) + { + Trace("nl-ext") << "interceptModel: do model-based refinement" << std::endl; + modelBasedRefinement(d_cmiLemmas, d_cmiLemmasPp, d_cmiLemmasSE); + } + if (d_builtModel.get()) + { + Trace("nl-ext") << "interceptModel: do model repair" << std::endl; + d_approximations.clear(); + // modify the model values + d_model.getModelValueRepair(arithModel, d_approximations); + } +} + +void NonlinearExtension::presolve() +{ + Trace("nl-ext") << "NonlinearExtension::presolve" << std::endl; +} + +} // namespace nl +} // namespace arith +} // namespace theory +} // namespace CVC4 diff --git a/src/theory/arith/nl/nonlinear_extension.h b/src/theory/arith/nl/nonlinear_extension.h new file mode 100644 index 000000000..855310daa --- /dev/null +++ b/src/theory/arith/nl/nonlinear_extension.h @@ -0,0 +1,341 @@ +/********************* */ +/*! \file nonlinear_extension.h + ** \verbatim + ** Top contributors (to current version): + ** Andrew Reynolds, Tim King + ** 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 Extensions for incomplete handling of nonlinear multiplication. + ** + ** Extensions to the theory of arithmetic incomplete handling of nonlinear + ** multiplication via axiom instantiations. + **/ + +#ifndef CVC4__THEORY__ARITH__NL__NONLINEAR_EXTENSION_H +#define CVC4__THEORY__ARITH__NL__NONLINEAR_EXTENSION_H + +#include +#include +#include + +#include "context/cdlist.h" +#include "expr/kind.h" +#include "expr/node.h" +#include "theory/arith/nl/nl_lemma_utils.h" +#include "theory/arith/nl/nl_model.h" +#include "theory/arith/nl/nl_solver.h" +#include "theory/arith/nl/transcendental_solver.h" +#include "theory/arith/theory_arith.h" +#include "theory/uf/equality_engine.h" + +namespace CVC4 { +namespace theory { +namespace arith { +namespace nl { + +/** Non-linear extension class + * + * This class implements model-based refinement schemes + * for non-linear arithmetic, described in: + * + * - "Invariant Checking of NRA Transition Systems + * via Incremental Reduction to LRA with EUF" by + * Cimatti et al., TACAS 2017. + * + * - Section 5 of "Desiging Theory Solvers with + * Extensions" by Reynolds et al., FroCoS 2017. + * + * - "Satisfiability Modulo Transcendental + * Functions via Incremental Linearization" by Cimatti + * et al., CADE 2017. + * + * It's main functionality is a check(...) method, + * which is called by TheoryArithPrivate either: + * (1) at full effort with no conflicts or lemmas emitted, or + * (2) at last call effort. + * In this method, this class calls d_out->lemma(...) + * for valid arithmetic theory lemmas, based on the current set of assertions, + * where d_out is the output channel of TheoryArith. + */ +class NonlinearExtension +{ + typedef context::CDHashSet NodeSet; + + public: + NonlinearExtension(TheoryArith& containing, eq::EqualityEngine* ee); + ~NonlinearExtension(); + /** Get current substitution + * + * This function and the one below are + * used for context-dependent + * simplification, see Section 3.1 of + * "Designing Theory Solvers with Extensions" + * by Reynolds et al. FroCoS 2017. + * + * effort : an identifier indicating the stage where + * we are performing context-dependent simplification, + * vars : a set of arithmetic variables. + * + * This function populates subs and exp, such that for 0 <= i < vars.size(): + * ( exp[vars[i]] ) => vars[i] = subs[i] + * where exp[vars[i]] is a set of assertions + * that hold in the current context. We call { vars -> subs } a "derivable + * substituion" (see Reynolds et al. FroCoS 2017). + */ + bool getCurrentSubstitution(int effort, + const std::vector& vars, + std::vector& subs, + std::map>& exp); + /** Is the term n in reduced form? + * + * Used for context-dependent simplification. + * + * effort : an identifier indicating the stage where + * we are performing context-dependent simplification, + * on : the original term that we reduced to n, + * exp : an explanation such that ( exp => on = n ). + * + * We return a pair ( b, exp' ) such that + * if b is true, then: + * n is in reduced form + * if exp' is non-null, then ( exp' => on = n ) + * The second part of the pair is used for constructing + * minimal explanations for context-dependent simplifications. + */ + std::pair isExtfReduced(int effort, + Node n, + Node on, + const std::vector& exp) const; + /** Check at effort level e. + * + * This call may result in (possibly multiple) calls to d_out->lemma(...) + * where d_out is the output channel of TheoryArith. + * + * If e is FULL, then we add lemmas based on context-depedent + * simplification (see Reynolds et al FroCoS 2017). + * + * If e is LAST_CALL, we add lemmas based on model-based refinement + * (see additionally Cimatti et al., TACAS 2017). The lemmas added at this + * effort may be computed during a call to interceptModel as described below. + */ + void check(Theory::Effort e); + /** intercept model + * + * This method is called during TheoryArith::collectModelInfo, which is + * invoked after the linear arithmetic solver passes a full effort check + * with no lemmas. + * + * The argument arithModel is a map of the form { v1 -> c1, ..., vn -> cn } + * which represents the linear arithmetic theory solver's contribution to the + * current candidate model. That is, its collectModelInfo method is requesting + * that equalities v1 = c1, ..., vn = cn be added to the current model, where + * v1, ..., vn are arithmetic variables and c1, ..., cn are constants. Notice + * arithmetic variables may be real-valued terms belonging to other theories, + * or abstractions of applications of multiplication (kind NONLINEAR_MULT). + * + * This method requests that the non-linear solver inspect this model and + * do any number of the following: + * (1) Construct lemmas based on a model-based refinement procedure inspired + * by Cimatti et al., TACAS 2017., + * (2) In the case that the nonlinear solver finds that the current + * constraints are satisfiable, it may "repair" the values in the argument + * arithModel so that it satisfies certain nonlinear constraints. This may + * involve e.g. solving for variables in nonlinear equations. + * + * Notice that in the former case, the lemmas it constructs are not sent out + * immediately. Instead, they are put in temporary vectors d_cmiLemmas + * and d_cmiLemmasPp, which are then sent out (if necessary) when a last call + * effort check is issued to this class. + */ + void interceptModel(std::map& arithModel); + /** Does this class need a call to check(...) at last call effort? */ + bool needsCheckLastEffort() const { return d_needsLastCall; } + /** presolve + * + * This function is called during TheoryArith's presolve command. + * In this function, we send lemmas we accumulated during preprocessing, + * for instance, definitional lemmas from expandDefinitions are sent out + * on the output channel of TheoryArith in this function. + */ + void presolve(); + + private: + /** Model-based refinement + * + * This is the main entry point of this class for generating lemmas on the + * output channel of the theory of arithmetic. + * + * It is currently run at last call effort. It applies lemma schemas + * described in Reynolds et al. FroCoS 2017 that are based on ruling out + * the current candidate model. + * + * This function returns true if a lemma was added to the vector lems/lemsPp. + * Otherwise, it returns false. In the latter case, the model object d_model + * may have information regarding how to construct a model, in the case that + * we determined the problem is satisfiable. + * + * The argument lemSE is the "side effect" of the lemmas in mlems and mlemsPp + * (for details, see checkLastCall). + */ + bool modelBasedRefinement(std::vector& mlems, + std::vector& mlemsPp, + std::map& lemSE); + + /** check last call + * + * Check assertions for consistency in the effort LAST_CALL with a subset of + * the assertions, false_asserts, that evaluate to false in the current model. + * + * xts : the list of (non-reduced) extended terms in the current context. + * + * This method adds lemmas to arguments lems, lemsPp, and wlems, each of + * which are intended to be sent out on the output channel of TheoryArith + * under certain conditions. + * + * If the set lems or lemsPp is non-empty, then no further processing is + * necessary. The last call effort check should terminate and these + * lemmas should be sent. The set lemsPp is distinguished from lems since + * the preprocess flag on the lemma(...) call should be set to true. + * + * The "waiting" lemmas wlems contain lemmas that should be sent on the + * output channel as a last resort. In other words, only if we are not + * able to establish SAT via a call to checkModel(...) should wlems be + * considered. This set typically contains tangent plane lemmas. + * + * The argument lemSE is the "side effect" of the lemmas from the previous + * three calls. If a lemma is mapping to a side effect, it should be + * processed via a call to processSideEffect(...) immediately after the + * lemma is sent (if it is indeed sent on this call to check). + */ + int checkLastCall(const std::vector& assertions, + const std::vector& false_asserts, + const std::vector& xts, + std::vector& lems, + std::vector& lemsPp, + std::vector& wlems, + std::map& lemSE); + + /** get assertions + * + * Let M be the set of assertions known by THEORY_ARITH. This function adds a + * set of literals M' to assertions such that M' and M are equivalent. + * + * Examples of how M' differs with M: + * (1) M' may not include t < c (in M) if t < c' is in M' for c' < c, where + * c and c' are constants, + * (2) M' may contain t = c if both t >= c and t <= c are in M. + */ + void getAssertions(std::vector& assertions); + /** check model + * + * Returns the subset of assertions whose concrete values we cannot show are + * true in the current model. Notice that we typically cannot compute concrete + * values for assertions involving transcendental functions. Any assertion + * whose model value cannot be computed is included in the return value of + * this function. + */ + std::vector checkModelEval(const std::vector& assertions); + + //---------------------------check model + /** Check model + * + * Checks the current model based on solving for equalities, and using error + * bounds on the Taylor approximation. + * + * If this function returns true, then all assertions in the input argument + * "assertions" are satisfied for all interpretations of variables within + * their computed bounds (as stored in d_check_model_bounds). + * + * For details, see Section 3 of Cimatti et al CADE 2017 under the heading + * "Detecting Satisfiable Formulas". + * + * The arguments lemmas and gs store the lemmas and guard literals to be sent + * out on the output channel of TheoryArith as lemmas and calls to + * ensureLiteral respectively. + */ + bool checkModel(const std::vector& assertions, + const std::vector& false_asserts, + std::vector& lemmas, + std::vector& gs); + //---------------------------end check model + + /** Is n entailed with polarity pol in the current context? */ + bool isEntailed(Node n, bool pol); + + /** + * Potentially adds lemmas to the set out and clears lemmas. Returns + * the number of lemmas added to out. We do not add lemmas that have already + * been sent on the output channel of TheoryArith. + */ + unsigned filterLemmas(std::vector& lemmas, std::vector& out); + /** singleton version of above */ + unsigned filterLemma(Node lem, std::vector& out); + + /** + * Send lemmas in out on the output channel of theory of arithmetic. + */ + void sendLemmas(const std::vector& out, + bool preprocess, + std::map& lemSE); + /** Process side effect se */ + void processSideEffect(const NlLemmaSideEffect& se); + + /** cache of all lemmas sent on the output channel (user-context-dependent) */ + NodeSet d_lemmas; + /** commonly used terms */ + Node d_zero; + Node d_one; + Node d_neg_one; + Node d_true; + // The theory of arithmetic containing this extension. + TheoryArith& d_containing; + // pointer to used equality engine + eq::EqualityEngine* d_ee; + // needs last call effort + bool d_needsLastCall; + /** The non-linear model object + * + * This class is responsible for computing model values for arithmetic terms + * 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 nonlinear extension object + * + * This is the subsolver responsible for running the procedure for + * constraints involving nonlinear mulitplication. + */ + NlSolver d_nlSlv; + /** + * The lemmas we computed during collectModelInfo. We store two vectors of + * lemmas to be sent out on the output channel of TheoryArith. The first + * is not preprocessed, the second is. + */ + std::vector d_cmiLemmas; + std::vector d_cmiLemmasPp; + /** the side effects of the above lemmas */ + std::map d_cmiLemmasSE; + /** + * The approximations computed during collectModelInfo. For details, see + * NlModel::getModelValueRepair. + */ + std::map> d_approximations; + /** have we successfully built the model in this SAT context? */ + context::CDO d_builtModel; +}; /* class NonlinearExtension */ + +} // namespace nl +} // namespace arith +} // namespace theory +} // namespace CVC4 + +#endif /* CVC4__THEORY__ARITH__NONLINEAR_EXTENSION_H */ diff --git a/src/theory/arith/nl/transcendental_solver.cpp b/src/theory/arith/nl/transcendental_solver.cpp new file mode 100644 index 000000000..3e10f6397 --- /dev/null +++ b/src/theory/arith/nl/transcendental_solver.cpp @@ -0,0 +1,1477 @@ +/********************* */ +/*! \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/nl/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 { +namespace nl { + +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 nl +} // namespace arith +} // namespace theory +} // namespace CVC4 diff --git a/src/theory/arith/nl/transcendental_solver.h b/src/theory/arith/nl/transcendental_solver.h new file mode 100644 index 000000000..5cd57d8fa --- /dev/null +++ b/src/theory/arith/nl/transcendental_solver.h @@ -0,0 +1,430 @@ +/********************* */ +/*! \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__NL__TRANSCENDENTAL_SOLVER_H +#define CVC4__THEORY__ARITH__NL__TRANSCENDENTAL_SOLVER_H + +#include +#include +#include +#include + +#include "expr/node.h" +#include "theory/arith/nl/nl_lemma_utils.h" +#include "theory/arith/nl/nl_model.h" + +namespace CVC4 { +namespace theory { +namespace arith { +namespace nl { + +/** 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 + * + * This is called at the beginning of last call effort check, where + * assertions are the set of assertions belonging to arithmetic, + * false_asserts is the subset of assertions that are false in the current + * model, and xts is the set of extended function terms that are active in + * the current context. + * + * This call may add lemmas to lems/lemsPp based on registering term + * information (for example, purification of sine terms). + */ + 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 nl +} // namespace arith +} // namespace theory +} // namespace CVC4 + +#endif /* CVC4__THEORY__ARITH__TRANSCENDENTAL_SOLVER_H */ diff --git a/src/theory/arith/nl_constraint.cpp b/src/theory/arith/nl_constraint.cpp deleted file mode 100644 index 8fb4535ea..000000000 --- a/src/theory/arith/nl_constraint.cpp +++ /dev/null @@ -1,123 +0,0 @@ -/********************* */ -/*! \file nl_constraint.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 non-linear constraints - **/ - -#include "theory/arith/nl_constraint.h" - -#include "theory/arith/arith_msum.h" -#include "theory/arith/arith_utilities.h" - -using namespace CVC4::kind; - -namespace CVC4 { -namespace theory { -namespace arith { - -ConstraintDb::ConstraintDb(MonomialDb& mdb) : d_mdb(mdb) {} - -void ConstraintDb::registerConstraint(Node atom) -{ - if (std::find(d_constraints.begin(), d_constraints.end(), atom) - != d_constraints.end()) - { - return; - } - d_constraints.push_back(atom); - Trace("nl-ext-debug") << "Register constraint : " << atom << std::endl; - std::map msum; - if (ArithMSum::getMonomialSumLit(atom, msum)) - { - Trace("nl-ext-debug") << "got monomial sum: " << std::endl; - if (Trace.isOn("nl-ext-debug")) - { - ArithMSum::debugPrintMonomialSum(msum, "nl-ext-debug"); - } - unsigned max_degree = 0; - std::vector all_m; - std::vector max_deg_m; - for (std::map::iterator itm = msum.begin(); itm != msum.end(); - ++itm) - { - if (!itm->first.isNull()) - { - all_m.push_back(itm->first); - d_mdb.registerMonomial(itm->first); - Trace("nl-ext-debug2") - << "...process monomial " << itm->first << std::endl; - unsigned d = d_mdb.getDegree(itm->first); - if (d > max_degree) - { - max_degree = d; - max_deg_m.clear(); - } - if (d >= max_degree) - { - max_deg_m.push_back(itm->first); - } - } - } - // isolate for each maximal degree monomial - for (unsigned i = 0; i < all_m.size(); i++) - { - Node m = all_m[i]; - Node rhs, coeff; - int res = ArithMSum::isolate(m, msum, coeff, rhs, atom.getKind()); - if (res != 0) - { - Kind type = atom.getKind(); - if (res == -1) - { - type = reverseRelationKind(type); - } - Trace("nl-ext-constraint") << "Constraint : " << atom << " <=> "; - if (!coeff.isNull()) - { - Trace("nl-ext-constraint") << coeff << " * "; - } - Trace("nl-ext-constraint") - << m << " " << type << " " << rhs << std::endl; - ConstraintInfo& ci = d_c_info[atom][m]; - ci.d_rhs = rhs; - ci.d_coeff = coeff; - ci.d_type = type; - } - } - for (unsigned i = 0; i < max_deg_m.size(); i++) - { - Node m = max_deg_m[i]; - d_c_info_maxm[atom][m] = true; - } - } - else - { - Trace("nl-ext-debug") << "...failed to get monomial sum." << std::endl; - } -} - -const std::map >& -ConstraintDb::getConstraints() -{ - return d_c_info; -} - -bool ConstraintDb::isMaximal(Node atom, Node x) const -{ - std::map >::const_iterator itcm = - d_c_info_maxm.find(atom); - Assert(itcm != d_c_info_maxm.end()); - return itcm->second.find(x) != itcm->second.end(); -} - -} // namespace arith -} // namespace theory -} // namespace CVC4 diff --git a/src/theory/arith/nl_constraint.h b/src/theory/arith/nl_constraint.h deleted file mode 100644 index faa3cc812..000000000 --- a/src/theory/arith/nl_constraint.h +++ /dev/null @@ -1,86 +0,0 @@ -/********************* */ -/*! \file nl_constraint.h - ** \verbatim - ** Top contributors (to current version): - ** Andrew Reynolds, Tim King - ** 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 Utilities for non-linear constraints - **/ - -#ifndef CVC4__THEORY__ARITH__NL_CONSTRAINT_H -#define CVC4__THEORY__ARITH__NL_CONSTRAINT_H - -#include -#include - -#include "expr/kind.h" -#include "expr/node.h" -#include "theory/arith/nl_monomial.h" - -namespace CVC4 { -namespace theory { -namespace arith { - -/** constraint information - * - * The struct ( d_rhs, d_coeff, d_type ) represents that a literal is of the - * form (d_coeff * x) d_rhs. - */ -struct ConstraintInfo -{ - public: - /** The term on the right hand side of the constraint */ - Node d_rhs; - /** The coefficent */ - Node d_coeff; - /** The type (relation) of the constraint */ - Kind d_type; -}; /* struct ConstraintInfo */ - -/** A database for constraints */ -class ConstraintDb -{ - public: - ConstraintDb(MonomialDb& mdb); - ~ConstraintDb() {} - /** register constraint - * - * This ensures that atom is in the domain of the constraints maintained by - * this database. - */ - void registerConstraint(Node atom); - /** get constraints - * - * Returns a map m such that whenever - * m[lit][x] = ( r, coeff, k ), then - * ( lit <=> (coeff * x) r ) - */ - const std::map >& getConstraints(); - /** Returns true if m is of maximal degree in atom - * - * For example, for atom x^2 + x*y + y >=0, the monomials x^2 and x*y - * are of maximal degree (2). - */ - bool isMaximal(Node atom, Node m) const; - - private: - /** Reference to a monomial database */ - MonomialDb& d_mdb; - /** List of all constraints */ - std::vector d_constraints; - /** Is maximal degree */ - std::map > d_c_info_maxm; - /** Constraint information */ - std::map > d_c_info; -}; - -} // namespace arith -} // namespace theory -} // namespace CVC4 - -#endif /* CVC4__THEORY__ARITH__NL_SOLVER_H */ diff --git a/src/theory/arith/nl_lemma_utils.cpp b/src/theory/arith/nl_lemma_utils.cpp deleted file mode 100644 index e43a77b06..000000000 --- a/src/theory/arith/nl_lemma_utils.cpp +++ /dev/null @@ -1,63 +0,0 @@ -/********************* */ -/*! \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 deleted file mode 100644 index bd729dad9..000000000 --- a/src/theory/arith/nl_lemma_utils.h +++ /dev/null @@ -1,105 +0,0 @@ -/********************* */ -/*! \file nl_lemma_utils.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 Utilities for processing lemmas from the non-linear solver - **/ - -#ifndef CVC4__THEORY__ARITH__NL_LEMMA_UTILS_H -#define CVC4__THEORY__ARITH__NL_LEMMA_UTILS_H - -#include -#include -#include "expr/node.h" - -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 - * includes: - * - A set of secant points to record (for transcendental secant plane - * inferences). - */ -struct NlLemmaSideEffect -{ - NlLemmaSideEffect() {} - ~NlLemmaSideEffect() {} - /** secant points to add - * - * A member (tf, d, c) in this vector indicates that point c should be added - * to the list of secant points for an application of a transcendental - * function tf for Taylor degree d. This is used for incremental linearization - * for underapproximation (resp. overapproximations) of convex (resp. - * concave) regions of transcendental functions. For details, see - * Cimatti et al., CADE 2017. - */ - 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(const std::map& m) : d_mdegree(m) {} - /** pointer to the non-linear extension */ - const 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 - -#endif /* CVC4__THEORY__ARITH__NL_LEMMA_UTILS_H */ diff --git a/src/theory/arith/nl_model.cpp b/src/theory/arith/nl_model.cpp deleted file mode 100644 index 0d47c8874..000000000 --- a/src/theory/arith/nl_model.cpp +++ /dev/null @@ -1,1347 +0,0 @@ -/********************* */ -/*! \file nl_model.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 Model object for the non-linear extension class - **/ - -#include "theory/arith/nl_model.h" - -#include "expr/node_algorithm.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 { - -NlModel::NlModel(context::Context* c) : d_used_approx(false) -{ - d_true = NodeManager::currentNM()->mkConst(true); - d_false = NodeManager::currentNM()->mkConst(false); - d_zero = NodeManager::currentNM()->mkConst(Rational(0)); - d_one = NodeManager::currentNM()->mkConst(Rational(1)); - d_two = NodeManager::currentNM()->mkConst(Rational(2)); -} - -NlModel::~NlModel() {} - -void NlModel::reset(TheoryModel* m, std::map& arithModel) -{ - d_model = m; - d_mv[0].clear(); - d_mv[1].clear(); - d_arithVal.clear(); - // process arithModel - std::map::iterator it; - for (const std::pair& m2 : arithModel) - { - d_arithVal[m2.first] = m2.second; - } -} - -void NlModel::resetCheck() -{ - d_used_approx = false; - d_check_model_solved.clear(); - d_check_model_bounds.clear(); - d_check_model_vars.clear(); - d_check_model_subs.clear(); -} - -Node NlModel::computeConcreteModelValue(Node n) -{ - return computeModelValue(n, true); -} - -Node NlModel::computeAbstractModelValue(Node n) -{ - return computeModelValue(n, false); -} - -Node NlModel::computeModelValue(Node n, bool isConcrete) -{ - unsigned index = isConcrete ? 0 : 1; - std::map::iterator it = d_mv[index].find(n); - if (it != d_mv[index].end()) - { - return it->second; - } - Trace("nl-ext-mv-debug") << "computeModelValue " << n << ", index=" << index - << std::endl; - Node ret; - Kind nk = n.getKind(); - if (n.isConst()) - { - ret = n; - } - else if (!isConcrete && hasTerm(n)) - { - // use model value for abstraction - ret = getRepresentative(n); - } - else if (n.getNumChildren() == 0) - { - // we are interested in the exact value of PI, which cannot be computed. - // hence, we return PI itself when asked for the concrete value. - if (nk == PI) - { - ret = n; - } - else - { - ret = getValueInternal(n); - } - } - else - { - // otherwise, compute true value - TheoryId ctid = theory::kindToTheoryId(nk); - if (ctid != THEORY_ARITH && ctid != THEORY_BOOL && ctid != THEORY_BUILTIN) - { - // we directly look up terms not belonging to arithmetic - ret = getValueInternal(n); - } - else - { - std::vector children; - if (n.getMetaKind() == metakind::PARAMETERIZED) - { - children.push_back(n.getOperator()); - } - for (unsigned i = 0, nchild = n.getNumChildren(); i < nchild; i++) - { - Node mc = computeModelValue(n[i], isConcrete); - children.push_back(mc); - } - ret = NodeManager::currentNM()->mkNode(nk, children); - ret = Rewriter::rewrite(ret); - } - } - Trace("nl-ext-mv-debug") << "computed " << (index == 0 ? "M" : "M_A") << "[" - << n << "] = " << ret << std::endl; - d_mv[index][n] = ret; - return ret; -} - -bool NlModel::hasTerm(Node n) const -{ - return d_arithVal.find(n) != d_arithVal.end(); -} - -Node NlModel::getRepresentative(Node n) const -{ - if (n.isConst()) - { - return n; - } - std::map::const_iterator it = d_arithVal.find(n); - if (it != d_arithVal.end()) - { - AlwaysAssert(it->second.isConst()); - return it->second; - } - return d_model->getRepresentative(n); -} - -Node NlModel::getValueInternal(Node n) const -{ - if (n.isConst()) - { - return n; - } - std::map::const_iterator it = d_arithVal.find(n); - if (it != d_arithVal.end()) - { - AlwaysAssert(it->second.isConst()); - return it->second; - } - // It is unconstrained in the model, return 0. - return d_zero; -} - -int NlModel::compare(Node i, Node j, bool isConcrete, bool isAbsolute) -{ - Node ci = computeModelValue(i, isConcrete); - Node cj = computeModelValue(j, isConcrete); - if (ci.isConst()) - { - if (cj.isConst()) - { - return compareValue(ci, cj, isAbsolute); - } - return 1; - } - return cj.isConst() ? -1 : 0; -} - -int NlModel::compareValue(Node i, Node j, bool isAbsolute) const -{ - Assert(i.isConst() && j.isConst()); - int ret; - if (i == j) - { - ret = 0; - } - else if (!isAbsolute) - { - ret = i.getConst() < j.getConst() ? 1 : -1; - } - else - { - ret = (i.getConst().abs() == j.getConst().abs() - ? 0 - : (i.getConst().abs() < j.getConst().abs() - ? 1 - : -1)); - } - return ret; -} - -bool NlModel::checkModel(const std::vector& assertions, - const std::vector& false_asserts, - unsigned d, - std::vector& lemmas, - std::vector& gs) -{ - Trace("nl-ext-cm-debug") << " solve for equalities..." << std::endl; - for (const Node& atom : false_asserts) - { - // see if it corresponds to a univariate polynomial equation of degree two - if (atom.getKind() == EQUAL) - { - if (!solveEqualitySimple(atom, d, lemmas)) - { - // no chance we will satisfy this equality - Trace("nl-ext-cm") << "...check-model : failed to solve equality : " - << atom << std::endl; - } - } - } - - // all remaining variables are constrained to their exact model values - Trace("nl-ext-cm-debug") << " set exact bounds for remaining variables..." - << std::endl; - std::unordered_set visited; - std::vector visit; - TNode cur; - for (const Node& a : assertions) - { - visit.push_back(a); - do - { - cur = visit.back(); - visit.pop_back(); - if (visited.find(cur) == visited.end()) - { - visited.insert(cur); - if (cur.getType().isReal() && !cur.isConst()) - { - Kind k = cur.getKind(); - if (k != MULT && k != PLUS && k != NONLINEAR_MULT - && !isTranscendentalKind(k)) - { - // if we have not set an approximate bound for it - if (!hasCheckModelAssignment(cur)) - { - // set its exact model value in the substitution - Node curv = computeConcreteModelValue(cur); - Trace("nl-ext-cm") - << "check-model-bound : exact : " << cur << " = "; - printRationalApprox("nl-ext-cm", curv); - Trace("nl-ext-cm") << std::endl; - bool ret = addCheckModelSubstitution(cur, curv); - AlwaysAssert(ret); - } - } - } - for (const Node& cn : cur) - { - visit.push_back(cn); - } - } - } while (!visit.empty()); - } - - Trace("nl-ext-cm-debug") << " check assertions..." << std::endl; - std::vector check_assertions; - for (const Node& a : assertions) - { - // don't have to check tautological literals - if (d_tautology.find(a) != d_tautology.end()) - { - continue; - } - if (d_check_model_solved.find(a) == d_check_model_solved.end()) - { - Node av = a; - // apply the substitution to a - if (!d_check_model_vars.empty()) - { - av = arithSubstitute(av, d_check_model_vars, d_check_model_subs); - av = Rewriter::rewrite(av); - } - // simple check literal - if (!simpleCheckModelLit(av)) - { - Trace("nl-ext-cm") << "...check-model : assertion failed : " << a - << std::endl; - check_assertions.push_back(av); - Trace("nl-ext-cm-debug") - << "...check-model : failed assertion, value : " << av << std::endl; - } - } - } - - if (!check_assertions.empty()) - { - Trace("nl-ext-cm") << "...simple check failed." << std::endl; - // TODO (#1450) check model for general case - return false; - } - Trace("nl-ext-cm") << "...simple check succeeded!" << std::endl; - - // must assert and re-check if produce models is true - if (options::produceModels()) - { - NodeManager* nm = NodeManager::currentNM(); - // model guard whose semantics is "the model we constructed holds" - Node mg = nm->mkSkolem("model", nm->booleanType()); - gs.push_back(mg); - // assert the constructed model as assertions - for (const std::pair > cb : - d_check_model_bounds) - { - Node l = cb.second.first; - Node u = cb.second.second; - Node v = cb.first; - Node pred = nm->mkNode(AND, nm->mkNode(GEQ, v, l), nm->mkNode(GEQ, u, v)); - pred = nm->mkNode(OR, mg.negate(), pred); - lemmas.push_back(pred); - } - } - return true; -} - -bool NlModel::addCheckModelSubstitution(TNode v, TNode s) -{ - // should not substitute the same variable twice - Trace("nl-ext-model") << "* check model substitution : " << v << " -> " << s - << std::endl; - // should not set exact bound more than once - if (std::find(d_check_model_vars.begin(), d_check_model_vars.end(), v) - != d_check_model_vars.end()) - { - Trace("nl-ext-model") << "...ERROR: already has value." << std::endl; - // this should never happen since substitutions should be applied eagerly - Assert(false); - return false; - } - // if we previously had an approximate bound, the exact bound should be in its - // range - std::map >::iterator itb = - d_check_model_bounds.find(v); - if (itb != d_check_model_bounds.end()) - { - if (s.getConst() >= itb->second.first.getConst() - || s.getConst() <= itb->second.second.getConst()) - { - Trace("nl-ext-model") - << "...ERROR: already has bound which is out of range." << std::endl; - return false; - } - } - std::vector varsTmp; - varsTmp.push_back(v); - std::vector subsTmp; - subsTmp.push_back(s); - for (unsigned i = 0, size = d_check_model_subs.size(); i < size; i++) - { - Node ms = d_check_model_subs[i]; - Node mss = arithSubstitute(ms, varsTmp, subsTmp); - if (mss != ms) - { - mss = Rewriter::rewrite(mss); - } - d_check_model_subs[i] = mss; - } - d_check_model_vars.push_back(v); - d_check_model_subs.push_back(s); - return true; -} - -bool NlModel::addCheckModelBound(TNode v, TNode l, TNode u) -{ - Trace("nl-ext-model") << "* check model bound : " << v << " -> [" << l << " " - << u << "]" << std::endl; - if (l == u) - { - // bound is exact, can add as substitution - return addCheckModelSubstitution(v, l); - } - // should not set a bound for a value that is exact - if (std::find(d_check_model_vars.begin(), d_check_model_vars.end(), v) - != d_check_model_vars.end()) - { - Trace("nl-ext-model") - << "...ERROR: setting bound for variable that already has exact value." - << std::endl; - Assert(false); - return false; - } - Assert(l.isConst()); - Assert(u.isConst()); - Assert(l.getConst() <= u.getConst()); - d_check_model_bounds[v] = std::pair(l, u); - if (Trace.isOn("nl-ext-cm")) - { - Trace("nl-ext-cm") << "check-model-bound : approximate : "; - printRationalApprox("nl-ext-cm", l); - Trace("nl-ext-cm") << " <= " << v << " <= "; - printRationalApprox("nl-ext-cm", u); - Trace("nl-ext-cm") << std::endl; - } - return true; -} - -bool NlModel::hasCheckModelAssignment(Node v) const -{ - if (d_check_model_bounds.find(v) != d_check_model_bounds.end()) - { - return true; - } - return std::find(d_check_model_vars.begin(), d_check_model_vars.end(), v) - != d_check_model_vars.end(); -} - -void NlModel::setUsedApproximate() { d_used_approx = true; } - -bool NlModel::usedApproximate() const { return d_used_approx; } - -void NlModel::addTautology(Node n) -{ - // ensure rewritten - n = Rewriter::rewrite(n); - std::unordered_set visited; - std::vector visit; - TNode cur; - visit.push_back(n); - do - { - cur = visit.back(); - visit.pop_back(); - if (visited.find(cur) == visited.end()) - { - visited.insert(cur); - if (cur.getKind() == AND) - { - // children of AND are also implied - for (const Node& cn : cur) - { - visit.push_back(cn); - } - } - else - { - // is this an arithmetic literal? - Node atom = cur.getKind() == NOT ? cur[0] : cur; - if ((atom.getKind() == EQUAL && atom[0].getType().isReal()) - || atom.getKind() == LEQ) - { - // Add to tautological literals if it does not contain - // non-linear multiplication. We cannot consider literals - // with non-linear multiplication to be tautological since this - // model object is responsible for checking whether they hold. - // (TODO, cvc4-projects #113: revisit this). - if (!expr::hasSubtermKind(NONLINEAR_MULT, atom)) - { - Trace("nl-taut") << "Tautological literal: " << atom << std::endl; - d_tautology.insert(cur); - } - } - } - } - } while (!visit.empty()); -} - -bool NlModel::solveEqualitySimple(Node eq, - unsigned d, - std::vector& lemmas) -{ - Node seq = eq; - if (!d_check_model_vars.empty()) - { - seq = arithSubstitute(eq, d_check_model_vars, d_check_model_subs); - seq = Rewriter::rewrite(seq); - if (seq.isConst()) - { - if (seq.getConst()) - { - d_check_model_solved[eq] = Node::null(); - return true; - } - return false; - } - } - Trace("nl-ext-cms") << "simple solve equality " << seq << "..." << std::endl; - Assert(seq.getKind() == EQUAL); - std::map msum; - if (!ArithMSum::getMonomialSumLit(seq, msum)) - { - Trace("nl-ext-cms") << "...fail, could not determine monomial sum." - << std::endl; - return false; - } - bool is_valid = true; - // the variable we will solve a quadratic equation for - Node var; - Node a = d_zero; - Node b = d_zero; - Node c = d_zero; - NodeManager* nm = NodeManager::currentNM(); - // the list of variables that occur as a monomial in msum, and whose value - // is so far unconstrained in the model. - std::unordered_set unc_vars; - // the list of variables that occur as a factor in a monomial, and whose - // value is so far unconstrained in the model. - std::unordered_set unc_vars_factor; - for (std::pair& m : msum) - { - Node v = m.first; - Node coeff = m.second.isNull() ? d_one : m.second; - if (v.isNull()) - { - c = coeff; - } - else if (v.getKind() == NONLINEAR_MULT) - { - if (v.getNumChildren() == 2 && v[0].isVar() && v[0] == v[1] - && (var.isNull() || var == v[0])) - { - // may solve quadratic - a = coeff; - var = v[0]; - } - else - { - is_valid = false; - Trace("nl-ext-cms-debug") - << "...invalid due to non-linear monomial " << v << std::endl; - // may wish to set an exact bound for a factor and repeat - for (const Node& vc : v) - { - unc_vars_factor.insert(vc); - } - } - } - else if (!v.isVar() || (!var.isNull() && var != v)) - { - Trace("nl-ext-cms-debug") - << "...invalid due to factor " << v << std::endl; - // cannot solve multivariate - if (is_valid) - { - is_valid = false; - // if b is non-zero, then var is also an unconstrained variable - if (b != d_zero) - { - unc_vars.insert(var); - unc_vars_factor.insert(var); - } - } - // if v is unconstrained, we may turn this equality into a substitution - unc_vars.insert(v); - unc_vars_factor.insert(v); - } - else - { - // set the variable to solve for - b = coeff; - var = v; - } - } - if (!is_valid) - { - // see if we can solve for a variable? - for (const Node& uv : unc_vars) - { - Trace("nl-ext-cm-debug") << "check subs var : " << uv << std::endl; - // cannot already have a bound - if (uv.isVar() && !hasCheckModelAssignment(uv)) - { - Node slv; - Node veqc; - if (ArithMSum::isolate(uv, msum, veqc, slv, EQUAL) != 0) - { - Assert(!slv.isNull()); - // Currently do not support substitution-with-coefficients. - // We also ensure types are correct here, which avoids substituting - // a term of non-integer type for a variable of integer type. - if (veqc.isNull() && !expr::hasSubterm(slv, uv) - && slv.getType().isSubtypeOf(uv.getType())) - { - Trace("nl-ext-cm") - << "check-model-subs : " << uv << " -> " << slv << std::endl; - bool ret = addCheckModelSubstitution(uv, slv); - if (ret) - { - Trace("nl-ext-cms") << "...success, model substitution " << uv - << " -> " << slv << std::endl; - d_check_model_solved[eq] = uv; - } - return ret; - } - } - } - } - // see if we can assign a variable to a constant - for (const Node& uvf : unc_vars_factor) - { - Trace("nl-ext-cm-debug") << "check set var : " << uvf << std::endl; - // cannot already have a bound - if (uvf.isVar() && !hasCheckModelAssignment(uvf)) - { - Node uvfv = computeConcreteModelValue(uvf); - Trace("nl-ext-cm") << "check-model-bound : exact : " << uvf << " = "; - printRationalApprox("nl-ext-cm", uvfv); - Trace("nl-ext-cm") << std::endl; - bool ret = addCheckModelSubstitution(uvf, uvfv); - // recurse - return ret ? solveEqualitySimple(eq, d, lemmas) : false; - } - } - Trace("nl-ext-cms") << "...fail due to constrained invalid terms." - << std::endl; - return false; - } - else if (var.isNull() || var.getType().isInteger()) - { - // cannot solve quadratic equations for integer variables - Trace("nl-ext-cms") << "...fail due to variable to solve for." << std::endl; - return false; - } - - // we are linear, it is simple - if (a == d_zero) - { - if (b == d_zero) - { - Trace("nl-ext-cms") << "...fail due to zero a/b." << std::endl; - Assert(false); - return false; - } - Node val = nm->mkConst(-c.getConst() / b.getConst()); - Trace("nl-ext-cm") << "check-model-bound : exact : " << var << " = "; - printRationalApprox("nl-ext-cm", val); - Trace("nl-ext-cm") << std::endl; - bool ret = addCheckModelSubstitution(var, val); - if (ret) - { - Trace("nl-ext-cms") << "...success, solved linear." << std::endl; - d_check_model_solved[eq] = var; - } - return ret; - } - Trace("nl-ext-quad") << "Solve quadratic : " << seq << std::endl; - Trace("nl-ext-quad") << " a : " << a << std::endl; - Trace("nl-ext-quad") << " b : " << b << std::endl; - Trace("nl-ext-quad") << " c : " << c << std::endl; - Node two_a = nm->mkNode(MULT, d_two, a); - two_a = Rewriter::rewrite(two_a); - Node sqrt_val = nm->mkNode( - MINUS, nm->mkNode(MULT, b, b), nm->mkNode(MULT, d_two, two_a, c)); - sqrt_val = Rewriter::rewrite(sqrt_val); - Trace("nl-ext-quad") << "Will approximate sqrt " << sqrt_val << std::endl; - Assert(sqrt_val.isConst()); - // if it is negative, then we are in conflict - if (sqrt_val.getConst().sgn() == -1) - { - Node conf = seq.negate(); - Trace("nl-ext-lemma") << "NlModel::Lemma : quadratic no root : " << conf - << std::endl; - lemmas.push_back(conf); - Trace("nl-ext-cms") << "...fail due to negative discriminant." << std::endl; - return false; - } - if (hasCheckModelAssignment(var)) - { - Trace("nl-ext-cms") << "...fail due to bounds on variable to solve for." - << std::endl; - // two quadratic equations for same variable, give up - return false; - } - // approximate the square root of sqrt_val - Node l, u; - if (!getApproximateSqrt(sqrt_val, l, u, 15 + d)) - { - Trace("nl-ext-cms") << "...fail, could not approximate sqrt." << std::endl; - return false; - } - d_used_approx = true; - Trace("nl-ext-quad") << "...got " << l << " <= sqrt(" << sqrt_val - << ") <= " << u << std::endl; - Node negb = nm->mkConst(-b.getConst()); - Node coeffa = nm->mkConst(Rational(1) / two_a.getConst()); - // two possible bound regions - Node bounds[2][2]; - Node diff_bound[2]; - Node m_var = computeConcreteModelValue(var); - Assert(m_var.isConst()); - for (unsigned r = 0; r < 2; r++) - { - for (unsigned b2 = 0; b2 < 2; b2++) - { - Node val = b2 == 0 ? l : u; - // (-b +- approx_sqrt( b^2 - 4ac ))/2a - Node approx = nm->mkNode( - MULT, coeffa, nm->mkNode(r == 0 ? MINUS : PLUS, negb, val)); - approx = Rewriter::rewrite(approx); - bounds[r][b2] = approx; - Assert(approx.isConst()); - } - if (bounds[r][0].getConst() > bounds[r][1].getConst()) - { - // ensure bound is (lower, upper) - Node tmp = bounds[r][0]; - bounds[r][0] = bounds[r][1]; - bounds[r][1] = tmp; - } - Node diff = - nm->mkNode(MINUS, - m_var, - nm->mkNode(MULT, - nm->mkConst(Rational(1) / Rational(2)), - nm->mkNode(PLUS, bounds[r][0], bounds[r][1]))); - Trace("nl-ext-cm-debug") << "Bound option #" << r << " : "; - printRationalApprox("nl-ext-cm-debug", bounds[r][0]); - Trace("nl-ext-cm-debug") << "..."; - printRationalApprox("nl-ext-cm-debug", bounds[r][1]); - Trace("nl-ext-cm-debug") << std::endl; - diff = Rewriter::rewrite(diff); - Assert(diff.isConst()); - diff = nm->mkConst(diff.getConst().abs()); - diff_bound[r] = diff; - Trace("nl-ext-cm-debug") << "...diff from model value ("; - printRationalApprox("nl-ext-cm-debug", m_var); - Trace("nl-ext-cm-debug") << ") is "; - printRationalApprox("nl-ext-cm-debug", diff_bound[r]); - Trace("nl-ext-cm-debug") << std::endl; - } - // take the one that var is closer to in the model - Node cmp = nm->mkNode(GEQ, diff_bound[0], diff_bound[1]); - cmp = Rewriter::rewrite(cmp); - Assert(cmp.isConst()); - unsigned r_use_index = cmp == d_true ? 1 : 0; - Trace("nl-ext-cm") << "check-model-bound : approximate (sqrt) : "; - printRationalApprox("nl-ext-cm", bounds[r_use_index][0]); - Trace("nl-ext-cm") << " <= " << var << " <= "; - printRationalApprox("nl-ext-cm", bounds[r_use_index][1]); - Trace("nl-ext-cm") << std::endl; - bool ret = - addCheckModelBound(var, bounds[r_use_index][0], bounds[r_use_index][1]); - if (ret) - { - d_check_model_solved[eq] = var; - Trace("nl-ext-cms") << "...success, solved quadratic." << std::endl; - } - return ret; -} - -bool NlModel::simpleCheckModelLit(Node lit) -{ - Trace("nl-ext-cms") << "*** Simple check-model lit for " << lit << "..." - << std::endl; - if (lit.isConst()) - { - Trace("nl-ext-cms") << " return constant." << std::endl; - return lit.getConst(); - } - NodeManager* nm = NodeManager::currentNM(); - bool pol = lit.getKind() != kind::NOT; - Node atom = lit.getKind() == kind::NOT ? lit[0] : lit; - - if (atom.getKind() == EQUAL) - { - // x = a is ( x >= a ^ x <= a ) - for (unsigned i = 0; i < 2; i++) - { - Node lit2 = nm->mkNode(GEQ, atom[i], atom[1 - i]); - if (!pol) - { - lit2 = lit2.negate(); - } - lit2 = Rewriter::rewrite(lit2); - bool success = simpleCheckModelLit(lit2); - if (success != pol) - { - // false != true -> one conjunct of equality is false, we fail - // true != false -> one disjunct of disequality is true, we succeed - return success; - } - } - // both checks passed and polarity is true, or both checks failed and - // polarity is false - return pol; - } - else if (atom.getKind() != GEQ) - { - Trace("nl-ext-cms") << " failed due to unknown literal." << std::endl; - return false; - } - // get the monomial sum - std::map msum; - if (!ArithMSum::getMonomialSumLit(atom, msum)) - { - Trace("nl-ext-cms") << " failed due to get msum." << std::endl; - return false; - } - // simple interval analysis - if (simpleCheckModelMsum(msum, pol)) - { - return true; - } - // can also try reasoning about univariate quadratic equations - Trace("nl-ext-cms-debug") - << "* Try univariate quadratic analysis..." << std::endl; - std::vector vs_invalid; - std::unordered_set vs; - std::map v_a; - std::map v_b; - // get coefficients... - for (std::pair& m : msum) - { - Node v = m.first; - if (!v.isNull()) - { - if (v.isVar()) - { - v_b[v] = m.second.isNull() ? d_one : m.second; - vs.insert(v); - } - else if (v.getKind() == NONLINEAR_MULT && v.getNumChildren() == 2 - && v[0] == v[1] && v[0].isVar()) - { - v_a[v[0]] = m.second.isNull() ? d_one : m.second; - vs.insert(v[0]); - } - else - { - vs_invalid.push_back(v); - } - } - } - // solve the valid variables... - Node invalid_vsum = vs_invalid.empty() ? d_zero - : (vs_invalid.size() == 1 - ? vs_invalid[0] - : nm->mkNode(PLUS, vs_invalid)); - // substitution to try - std::vector qvars; - std::vector qsubs; - for (const Node& v : vs) - { - // is it a valid variable? - std::map >::iterator bit = - d_check_model_bounds.find(v); - if (!expr::hasSubterm(invalid_vsum, v) && bit != d_check_model_bounds.end()) - { - std::map::iterator it = v_a.find(v); - if (it != v_a.end()) - { - Node a = it->second; - Assert(a.isConst()); - int asgn = a.getConst().sgn(); - Assert(asgn != 0); - Node t = nm->mkNode(MULT, a, v, v); - Node b = d_zero; - it = v_b.find(v); - if (it != v_b.end()) - { - b = it->second; - t = nm->mkNode(PLUS, t, nm->mkNode(MULT, b, v)); - } - t = Rewriter::rewrite(t); - Trace("nl-ext-cms-debug") << "Trying to find min/max for quadratic " - << t << "..." << std::endl; - Trace("nl-ext-cms-debug") << " a = " << a << std::endl; - Trace("nl-ext-cms-debug") << " b = " << b << std::endl; - // find maximal/minimal value on the interval - Node apex = nm->mkNode( - DIVISION, nm->mkNode(UMINUS, b), nm->mkNode(MULT, d_two, a)); - apex = Rewriter::rewrite(apex); - Assert(apex.isConst()); - // for lower, upper, whether we are greater than the apex - bool cmp[2]; - Node boundn[2]; - for (unsigned r = 0; r < 2; r++) - { - boundn[r] = r == 0 ? bit->second.first : bit->second.second; - Node cmpn = nm->mkNode(GT, boundn[r], apex); - cmpn = Rewriter::rewrite(cmpn); - Assert(cmpn.isConst()); - cmp[r] = cmpn.getConst(); - } - Trace("nl-ext-cms-debug") << " apex " << apex << std::endl; - Trace("nl-ext-cms-debug") - << " lower " << boundn[0] << ", cmp: " << cmp[0] << std::endl; - Trace("nl-ext-cms-debug") - << " upper " << boundn[1] << ", cmp: " << cmp[1] << std::endl; - Assert(boundn[0].getConst() - <= boundn[1].getConst()); - Node s; - qvars.push_back(v); - if (cmp[0] != cmp[1]) - { - Assert(!cmp[0] && cmp[1]); - // does the sign match the bound? - if ((asgn == 1) == pol) - { - // the apex is the max/min value - s = apex; - Trace("nl-ext-cms-debug") << " ...set to apex." << std::endl; - } - else - { - // it is one of the endpoints, plug in and compare - Node tcmpn[2]; - for (unsigned r = 0; r < 2; r++) - { - qsubs.push_back(boundn[r]); - Node ts = arithSubstitute(t, qvars, qsubs); - tcmpn[r] = Rewriter::rewrite(ts); - qsubs.pop_back(); - } - Node tcmp = nm->mkNode(LT, tcmpn[0], tcmpn[1]); - Trace("nl-ext-cms-debug") - << " ...both sides of apex, compare " << tcmp << std::endl; - tcmp = Rewriter::rewrite(tcmp); - Assert(tcmp.isConst()); - unsigned bindex_use = (tcmp.getConst() == pol) ? 1 : 0; - Trace("nl-ext-cms-debug") - << " ...set to " << (bindex_use == 1 ? "upper" : "lower") - << std::endl; - s = boundn[bindex_use]; - } - } - else - { - // both to one side of the apex - // we figure out which bound to use (lower or upper) based on - // three factors: - // (1) whether a's sign is positive, - // (2) whether we are greater than the apex of the parabola, - // (3) the polarity of the constraint, i.e. >= or <=. - // there are 8 cases of these factors, which we test here. - unsigned bindex_use = (((asgn == 1) == cmp[0]) == pol) ? 0 : 1; - Trace("nl-ext-cms-debug") - << " ...set to " << (bindex_use == 1 ? "upper" : "lower") - << std::endl; - s = boundn[bindex_use]; - } - Assert(!s.isNull()); - qsubs.push_back(s); - Trace("nl-ext-cms") << "* set bound based on quadratic : " << v - << " -> " << s << std::endl; - } - } - } - if (!qvars.empty()) - { - Assert(qvars.size() == qsubs.size()); - Node slit = arithSubstitute(lit, qvars, qsubs); - slit = Rewriter::rewrite(slit); - return simpleCheckModelLit(slit); - } - return false; -} - -bool NlModel::simpleCheckModelMsum(const std::map& msum, bool pol) -{ - Trace("nl-ext-cms-debug") << "* Try simple interval analysis..." << std::endl; - NodeManager* nm = NodeManager::currentNM(); - // map from transcendental functions to whether they were set to lower - // bound - bool simpleSuccess = true; - std::map set_bound; - std::vector sum_bound; - for (const std::pair& m : msum) - { - Node v = m.first; - if (v.isNull()) - { - sum_bound.push_back(m.second.isNull() ? d_one : m.second); - } - else - { - Trace("nl-ext-cms-debug") << "- monomial : " << v << std::endl; - // --- whether we should set a lower bound for this monomial - bool set_lower = - (m.second.isNull() || m.second.getConst().sgn() == 1) - == pol; - Trace("nl-ext-cms-debug") - << "set bound to " << (set_lower ? "lower" : "upper") << std::endl; - - // --- Collect variables and factors in v - std::vector vars; - std::vector factors; - if (v.getKind() == NONLINEAR_MULT) - { - unsigned last_start = 0; - for (unsigned i = 0, nchildren = v.getNumChildren(); i < nchildren; i++) - { - // are we at the end? - if (i + 1 == nchildren || v[i + 1] != v[i]) - { - unsigned vfact = 1 + (i - last_start); - last_start = (i + 1); - vars.push_back(v[i]); - factors.push_back(vfact); - } - } - } - else - { - vars.push_back(v); - factors.push_back(1); - } - - // --- Get the lower and upper bounds and sign information. - // Whether we have an (odd) number of negative factors in vars, apart - // from the variable at choose_index. - bool has_neg_factor = false; - int choose_index = -1; - std::vector ls; - std::vector us; - // the relevant sign information for variables with odd exponents: - // 1: both signs of the interval of this variable are positive, - // -1: both signs of the interval of this variable are negative. - std::vector signs; - Trace("nl-ext-cms-debug") << "get sign information..." << std::endl; - for (unsigned i = 0, size = vars.size(); i < size; i++) - { - Node vc = vars[i]; - unsigned vcfact = factors[i]; - if (Trace.isOn("nl-ext-cms-debug")) - { - Trace("nl-ext-cms-debug") << "-- " << vc; - if (vcfact > 1) - { - Trace("nl-ext-cms-debug") << "^" << vcfact; - } - Trace("nl-ext-cms-debug") << " "; - } - std::map >::iterator bit = - d_check_model_bounds.find(vc); - // if there is a model bound for this term - if (bit != d_check_model_bounds.end()) - { - Node l = bit->second.first; - Node u = bit->second.second; - ls.push_back(l); - us.push_back(u); - int vsign = 0; - if (vcfact % 2 == 1) - { - vsign = 1; - int lsgn = l.getConst().sgn(); - int usgn = u.getConst().sgn(); - Trace("nl-ext-cms-debug") - << "bound_sign(" << lsgn << "," << usgn << ") "; - if (lsgn == -1) - { - if (usgn < 1) - { - // must have a negative factor - has_neg_factor = !has_neg_factor; - vsign = -1; - } - else if (choose_index == -1) - { - // set the choose index to this - choose_index = i; - vsign = 0; - } - else - { - // ambiguous, can't determine the bound - Trace("nl-ext-cms") - << " failed due to ambiguious monomial." << std::endl; - return false; - } - } - } - Trace("nl-ext-cms-debug") << " -> " << vsign << std::endl; - signs.push_back(vsign); - } - else - { - Trace("nl-ext-cms-debug") << std::endl; - Trace("nl-ext-cms") - << " failed due to unknown bound for " << vc << std::endl; - // should either assign a model bound or eliminate the variable - // via substitution - Assert(false); - return false; - } - } - // whether we will try to minimize/maximize (-1/1) the absolute value - int setAbs = (set_lower == has_neg_factor) ? 1 : -1; - Trace("nl-ext-cms-debug") - << "set absolute value to " << (setAbs == 1 ? "maximal" : "minimal") - << std::endl; - - std::vector vbs; - Trace("nl-ext-cms-debug") << "set bounds..." << std::endl; - for (unsigned i = 0, size = vars.size(); i < size; i++) - { - Node vc = vars[i]; - unsigned vcfact = factors[i]; - Node l = ls[i]; - Node u = us[i]; - bool vc_set_lower; - int vcsign = signs[i]; - Trace("nl-ext-cms-debug") - << "Bounds for " << vc << " : " << l << ", " << u - << ", sign : " << vcsign << ", factor : " << vcfact << std::endl; - if (l == u) - { - // by convention, always say it is lower if they are the same - vc_set_lower = true; - Trace("nl-ext-cms-debug") - << "..." << vc << " equal bound, set to lower" << std::endl; - } - else - { - if (vcfact % 2 == 0) - { - // minimize or maximize its absolute value - Rational la = l.getConst().abs(); - Rational ua = u.getConst().abs(); - if (la == ua) - { - // by convention, always say it is lower if abs are the same - vc_set_lower = true; - Trace("nl-ext-cms-debug") - << "..." << vc << " equal abs, set to lower" << std::endl; - } - else - { - vc_set_lower = (la > ua) == (setAbs == 1); - } - } - else if (signs[i] == 0) - { - // we choose this index to match the overall set_lower - vc_set_lower = set_lower; - } - else - { - vc_set_lower = (signs[i] != setAbs); - } - Trace("nl-ext-cms-debug") - << "..." << vc << " set to " << (vc_set_lower ? "lower" : "upper") - << std::endl; - } - // check whether this is a conflicting bound - std::map::iterator itsb = set_bound.find(vc); - if (itsb == set_bound.end()) - { - set_bound[vc] = vc_set_lower; - } - else if (itsb->second != vc_set_lower) - { - Trace("nl-ext-cms") - << " failed due to conflicting bound for " << vc << std::endl; - return false; - } - // must over/under approximate based on vc_set_lower, computed above - Node vb = vc_set_lower ? l : u; - for (unsigned i2 = 0; i2 < vcfact; i2++) - { - vbs.push_back(vb); - } - } - if (!simpleSuccess) - { - break; - } - Node vbound = vbs.size() == 1 ? vbs[0] : nm->mkNode(MULT, vbs); - sum_bound.push_back(ArithMSum::mkCoeffTerm(m.second, vbound)); - } - } - // if the exact bound was computed via simple analysis above - // make the bound - Node bound; - if (sum_bound.size() > 1) - { - bound = nm->mkNode(kind::PLUS, sum_bound); - } - else if (sum_bound.size() == 1) - { - bound = sum_bound[0]; - } - else - { - bound = d_zero; - } - // make the comparison - Node comp = nm->mkNode(kind::GEQ, bound, d_zero); - if (!pol) - { - comp = comp.negate(); - } - Trace("nl-ext-cms") << " comparison is : " << comp << std::endl; - comp = Rewriter::rewrite(comp); - Assert(comp.isConst()); - Trace("nl-ext-cms") << " returned : " << comp << std::endl; - return comp == d_true; -} - -bool NlModel::getApproximateSqrt(Node c, Node& l, Node& u, unsigned iter) const -{ - Assert(c.isConst()); - if (c == d_one || c == d_zero) - { - l = c; - u = c; - return true; - } - Rational rc = c.getConst(); - - Rational rl = rc < Rational(1) ? rc : Rational(1); - Rational ru = rc < Rational(1) ? Rational(1) : rc; - unsigned count = 0; - Rational half = Rational(1) / Rational(2); - while (count < iter) - { - Rational curr = half * (rl + ru); - Rational curr_sq = curr * curr; - if (curr_sq == rc) - { - rl = curr; - ru = curr; - break; - } - else if (curr_sq < rc) - { - rl = curr; - } - else - { - ru = curr; - } - count++; - } - - NodeManager* nm = NodeManager::currentNM(); - l = nm->mkConst(rl); - u = nm->mkConst(ru); - return true; -} - -void NlModel::printModelValue(const char* c, Node n, unsigned prec) const -{ - if (Trace.isOn(c)) - { - Trace(c) << " " << n << " -> "; - for (int i = 1; i >= 0; --i) - { - std::map::const_iterator it = d_mv[i].find(n); - Assert(it != d_mv[i].end()); - if (it->second.isConst()) - { - printRationalApprox(c, it->second, prec); - } - else - { - Trace(c) << "?"; - } - Trace(c) << (i == 1 ? " [actual: " : " ]"); - } - Trace(c) << std::endl; - } -} - -void NlModel::getModelValueRepair( - std::map& arithModel, - std::map>& approximations) -{ - Trace("nl-model") << "NlModel::getModelValueRepair:" << std::endl; - - // Record the approximations we used. This code calls the - // recordApproximation method of the model, which overrides the model - // values for variables that we solved for, using techniques specific to - // this class. - NodeManager* nm = NodeManager::currentNM(); - for (const std::pair >& cb : - d_check_model_bounds) - { - Node l = cb.second.first; - Node u = cb.second.second; - Node pred; - Node v = cb.first; - if (l != u) - { - pred = nm->mkNode(AND, nm->mkNode(GEQ, v, l), nm->mkNode(GEQ, u, v)); - Trace("nl-model") << v << " approximated as " << pred << std::endl; - Node witness; - if (options::modelWitnessValue()) - { - // witness is the midpoint - witness = nm->mkNode( - MULT, nm->mkConst(Rational(1, 2)), nm->mkNode(PLUS, l, u)); - witness = Rewriter::rewrite(witness); - Trace("nl-model") << v << " witness is " << witness << std::endl; - } - approximations[v] = std::pair(pred, witness); - } - else - { - // overwrite - arithModel[v] = l; - Trace("nl-model") << v << " exact approximation is " << l << std::endl; - } - } - // Also record the exact values we used. An exact value can be seen as a - // special kind approximation of the form (witness x. x = exact_value). - // Notice that the above term gets rewritten such that the choice function - // is eliminated. - for (size_t i = 0, num = d_check_model_vars.size(); i < num; i++) - { - Node v = d_check_model_vars[i]; - Node s = d_check_model_subs[i]; - // overwrite - arithModel[v] = s; - Trace("nl-model") << v << " solved is " << s << std::endl; - } - - // multiplication terms should not be given values; their values are - // implied by the monomials that they consist of - std::vector amErase; - for (const std::pair& am : arithModel) - { - if (am.first.getKind() == NONLINEAR_MULT) - { - amErase.push_back(am.first); - } - } - for (const Node& ae : amErase) - { - arithModel.erase(ae); - } -} - -} // namespace arith -} // namespace theory -} // namespace CVC4 diff --git a/src/theory/arith/nl_model.h b/src/theory/arith/nl_model.h deleted file mode 100644 index 5129a7a32..000000000 --- a/src/theory/arith/nl_model.h +++ /dev/null @@ -1,333 +0,0 @@ -/********************* */ -/*! \file nl_model.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 Model object for the non-linear extension class - **/ - -#ifndef CVC4__THEORY__ARITH__NL_MODEL_H -#define CVC4__THEORY__ARITH__NL_MODEL_H - -#include -#include -#include - -#include "context/cdo.h" -#include "context/context.h" -#include "expr/kind.h" -#include "expr/node.h" -#include "theory/theory_model.h" - -namespace CVC4 { -namespace theory { -namespace arith { - -class NonlinearExtension; - -/** Non-linear model finder - * - * This class is responsible for all queries related to the (candidate) model - * that is being processed by the non-linear arithmetic solver. It further - * implements techniques for finding modifications to the current candidate - * model in the case it can determine that a model exists. These include - * techniques based on solving (quadratic) equations and bound analysis. - */ -class NlModel -{ - friend class NonlinearExtension; - - public: - NlModel(context::Context* c); - ~NlModel(); - /** reset - * - * This method is called once at the beginning of a last call effort check, - * where m is the model of the theory of arithmetic. This method resets the - * cache of computed model values. - */ - void reset(TheoryModel* m, std::map& arithModel); - /** reset check - * - * This method is called when the non-linear arithmetic solver restarts - * its computation of lemmas and models during a last call effort check. - */ - void resetCheck(); - /** compute model value - * - * This computes model values for terms based on two semantics, a "concrete" - * semantics and an "abstract" semantics. - * - * if isConcrete is true, this means compute the value of n based on its - * children recursively. (we call this its "concrete" value) - * if isConcrete is false, this means lookup the value of n in the model. - * (we call this its "abstract" value) - * In other words, !isConcrete treats multiplication terms and transcendental - * function applications as variables, whereas isConcrete computes their - * actual values based on the semantics of multiplication. This is a key - * distinction used in the model-based refinement scheme in Cimatti et al. - * TACAS 2017. - * - * For example, if M( a ) = 2, M( b ) = 3, M( a*b ) = 5, i.e. the variable - * for a*b has been assigned a value 5 by the linear solver, then : - * - * computeModelValue( a*b, true ) = - * computeModelValue( a, true )*computeModelValue( b, true ) = 2*3 = 6 - * whereas: - * computeModelValue( a*b, false ) = 5 - */ - Node computeConcreteModelValue(Node n); - Node computeAbstractModelValue(Node n); - Node computeModelValue(Node n, bool isConcrete); - - /** Compare arithmetic terms i and j based an ordering. - * - * This returns: - * -1 if i < j, 1 if i > j, or 0 if i == j - * - * If isConcrete is true, we consider the concrete model values of i and j, - * otherwise, we consider their abstract model values. For definitions of - * concrete vs abstract model values, see NlModel::computeModelValue. - * - * If isAbsolute is true, we compare the absolute value of thee above - * values. - */ - int compare(Node i, Node j, bool isConcrete, bool isAbsolute); - /** Compare arithmetic terms i and j based an ordering. - * - * This returns: - * -1 if i < j, 1 if i > j, or 0 if i == j - * - * If isAbsolute is true, we compare the absolute value of i and j - */ - int compareValue(Node i, Node j, bool isAbsolute) const; - - //------------------------------ recording model substitutions and bounds - /** add check model substitution - * - * Adds the model substitution v -> s. This applies the substitution - * { v -> s } to each term in d_check_model_subs and adds v,s to - * d_check_model_vars and d_check_model_subs respectively. - * If this method returns false, then the substitution v -> s is inconsistent - * with the current substitution and bounds. - */ - bool addCheckModelSubstitution(TNode v, TNode s); - /** add check model bound - * - * Adds the bound x -> < l, u > to the map above, and records the - * approximation ( x, l <= x <= u ) in the model. This method returns false - * if the bound is inconsistent with the current model substitution or - * bounds. - */ - bool addCheckModelBound(TNode v, TNode l, TNode u); - /** has check model assignment - * - * Have we assigned v in the current checkModel(...) call? - * - * This method returns true if variable v is in the domain of - * d_check_model_bounds or if it occurs in d_check_model_vars. - */ - bool hasCheckModelAssignment(Node v) const; - /** Check model - * - * Checks the current model based on solving for equalities, and using error - * bounds on the Taylor approximation. - * - * If this function returns true, then all assertions in the input argument - * "assertions" are satisfied for all interpretations of variables within - * their computed bounds (as stored in d_check_model_bounds). - * - * For details, see Section 3 of Cimatti et al CADE 2017 under the heading - * "Detecting Satisfiable Formulas". - * - * d is a degree indicating how precise our computations are. - */ - bool checkModel(const std::vector& assertions, - const std::vector& false_asserts, - unsigned d, - std::vector& lemmas, - std::vector& gs); - /** - * Set that we have used an approximation during this check. This flag is - * reset on a call to resetCheck. It is set when we use reasoning that - * is limited by a degree of precision we are using. In other words, if we - * used an approximation, then we maybe could still establish a lemma or - * determine the input is SAT if we increased our precision. - */ - void setUsedApproximate(); - /** Did we use an approximation during this check? */ - bool usedApproximate() const; - /** Set tautology - * - * This states that formula n is a tautology (satisfied in all models). - * We call this on internally generated lemmas. This method computes a - * set of literals that are implied by n, that are hence tautological - * as well, such as: - * l_pi <= real.pi <= u_pi (pi approximations) - * sin(x) = -1*sin(-x) - * where these literals are internally generated for the purposes - * of guiding the models of the linear solver. - * - * TODO (cvc4-projects #113: would be helpful if we could do this even - * more aggressively by ignoring all internally generated literals. - * - * Tautological literals do not need be checked during checkModel. - */ - void addTautology(Node n); - //------------------------------ end recording model substitutions and bounds - - /** print model value, for debugging. - * - * This prints both the abstract and concrete model values for arithmetic - * term n on Trace c with precision prec. - */ - void printModelValue(const char* c, Node n, unsigned prec = 5) const; - /** get model value repair - * - * This gets mappings that indicate how to repair the model generated by the - * linear arithmetic solver. This method should be called after a successful - * call to checkModel above. - * - * The mapping arithModel is updated by this method to map arithmetic terms v - * to their (exact) value that was computed during checkModel; the mapping - * approximations is updated to store approximate values in the form of a - * pair (P, w), where P is a predicate that describes the possible values of - * v and w is a witness point that satisfies this predicate. - */ - void getModelValueRepair( - std::map& arithModel, - std::map>& approximations); - - private: - /** The current model */ - TheoryModel* d_model; - /** Get the model value of n from the model object above */ - Node getValueInternal(Node n) const; - /** Does the equality engine of the model have term n? */ - bool hasTerm(Node n) const; - /** Get the representative of n in the model */ - Node getRepresentative(Node n) const; - - //---------------------------check model - /** solve equality simple - * - * This method is used during checkModel(...). It takes as input an - * equality eq. If it returns true, then eq is correct-by-construction based - * on the information stored in our model representation (see - * d_check_model_vars, d_check_model_subs, d_check_model_bounds), and eq - * is added to d_check_model_solved. The equality eq may involve any - * number of variables, and monomials of arbitrary degree. If this method - * returns false, then we did not show that the equality was true in the - * model. This method uses incomplete techniques based on interval - * analysis and quadratic equation solving. - * - * If it can be shown that the equality must be false in the current - * model, then we may add a lemma to lemmas explaining why this is the case. - * For instance, if eq reduces to a univariate quadratic equation with no - * root, we send a conflict clause of the form a*x^2 + b*x + c != 0. - */ - bool solveEqualitySimple(Node eq, unsigned d, std::vector& lemmas); - - /** simple check model for transcendental functions for literal - * - * This method returns true if literal is true for all interpretations of - * transcendental functions within their error bounds (as stored - * in d_check_model_bounds). This is determined by a simple under/over - * approximation of the value of sum of (linear) monomials. For example, - * if we determine that .8 < sin( 1 ) < .9, this function will return - * true for literals like: - * 2.0*sin( 1 ) > 1.5 - * -1.0*sin( 1 ) < -0.79 - * -1.0*sin( 1 ) > -0.91 - * sin( 1 )*sin( 1 ) + sin( 1 ) > 0.0 - * It will return false for literals like: - * sin( 1 ) > 0.85 - * It will also return false for literals like: - * -0.3*sin( 1 )*sin( 2 ) + sin( 2 ) > .7 - * sin( sin( 1 ) ) > .5 - * since the bounds on these terms cannot quickly be determined. - */ - bool simpleCheckModelLit(Node lit); - bool simpleCheckModelMsum(const std::map& msum, bool pol); - //---------------------------end check model - /** get approximate sqrt - * - * This approximates the square root of positive constant c. If this method - * returns true, then l and u are updated to constants such that - * l <= sqrt( c ) <= u - * The argument iter is the number of iterations in the binary search to - * perform. By default, this is set to 15, which is usually enough to be - * precise in the majority of simple cases, whereas not prohibitively - * expensive to compute. - */ - bool getApproximateSqrt(Node c, Node& l, Node& u, unsigned iter = 15) const; - - /** commonly used terms */ - Node d_zero; - Node d_one; - Node d_two; - Node d_true; - Node d_false; - Node d_null; - /** - * The values that the arithmetic theory solver assigned in the model. This - * corresponds to exactly the set of equalities that TheoryArith is currently - * sending to TheoryModel during collectModelInfo. - */ - std::map d_arithVal; - /** cache of model values - * - * Stores the the concrete/abstract model values. This is a cache of the - * computeModelValue method. - */ - std::map d_mv[2]; - /** - * A substitution from variables that appear in assertions to a solved form - * term. These vectors are ordered in the form: - * x_1 -> t_1 ... x_n -> t_n - * where x_i is not in the free variables of t_j for j>=i. - */ - std::vector d_check_model_vars; - std::vector d_check_model_subs; - /** lower and upper bounds for check model - * - * For each term t in the domain of this map, if this stores the pair - * (c_l, c_u) then the model M is such that c_l <= M( t ) <= c_u. - * - * We add terms whose value is approximated in the model to this map, which - * includes: - * (1) applications of transcendental functions, whose value is approximated - * by the Taylor series, - * (2) variables we have solved quadratic equations for, whose value - * involves approximations of square roots. - */ - std::map > d_check_model_bounds; - /** - * The map from literals that our model construction solved, to the variable - * that was solved for. Examples of such literals are: - * (1) Equalities x = t, which we turned into a model substitution x -> t, - * where x not in FV( t ), and - * (2) Equalities a*x*x + b*x + c = 0, which we turned into a model bound - * -b+s*sqrt(b*b-4*a*c)/2a - E <= x <= -b+s*sqrt(b*b-4*a*c)/2a + E. - * - * These literals are exempt from check-model, since they are satisfied by - * definition of our model construction. - */ - std::unordered_map d_check_model_solved; - /** did we use an approximation on this call to last-call effort? */ - bool d_used_approx; - /** the set of all tautological literals */ - std::unordered_set d_tautology; -}; /* class NlModel */ - -} // namespace arith -} // namespace theory -} // namespace CVC4 - -#endif /* CVC4__THEORY__ARITH__NONLINEAR_EXTENSION_H */ diff --git a/src/theory/arith/nl_monomial.cpp b/src/theory/arith/nl_monomial.cpp deleted file mode 100644 index be472217d..000000000 --- a/src/theory/arith/nl_monomial.cpp +++ /dev/null @@ -1,334 +0,0 @@ -/********************* */ -/*! \file nl_monomial.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 monomials - **/ - -#include "theory/arith/nl_monomial.h" - -#include "theory/arith/arith_utilities.h" -#include "theory/arith/nl_lemma_utils.h" -#include "theory/rewriter.h" - -using namespace CVC4::kind; - -namespace CVC4 { -namespace theory { -namespace arith { - -// Returns a[key] if key is in a or value otherwise. -unsigned getCountWithDefault(const NodeMultiset& a, Node key, unsigned value) -{ - NodeMultiset::const_iterator it = a.find(key); - return (it == a.end()) ? value : it->second; -} -// Given two multisets return the multiset difference a \ b. -NodeMultiset diffMultiset(const NodeMultiset& a, const NodeMultiset& b) -{ - NodeMultiset difference; - for (NodeMultiset::const_iterator it_a = a.begin(); it_a != a.end(); ++it_a) - { - Node key = it_a->first; - const unsigned a_value = it_a->second; - const unsigned b_value = getCountWithDefault(b, key, 0); - if (a_value > b_value) - { - difference[key] = a_value - b_value; - } - } - return difference; -} - -// Return a vector containing a[key] repetitions of key in a multiset a. -std::vector ExpandMultiset(const NodeMultiset& a) -{ - std::vector expansion; - for (NodeMultiset::const_iterator it_a = a.begin(); it_a != a.end(); ++it_a) - { - expansion.insert(expansion.end(), it_a->second, it_a->first); - } - return expansion; -} - -// status 0 : n equal, -1 : n superset, 1 : n subset -void MonomialIndex::addTerm(Node n, - const std::vector& reps, - MonomialDb* nla, - int status, - unsigned argIndex) -{ - if (status == 0) - { - if (argIndex == reps.size()) - { - d_monos.push_back(n); - } - else - { - d_data[reps[argIndex]].addTerm(n, reps, nla, status, argIndex + 1); - } - } - for (std::map::iterator it = d_data.begin(); - it != d_data.end(); - ++it) - { - if (status != 0 || argIndex == reps.size() || it->first != reps[argIndex]) - { - // if we do not contain this variable, then if we were a superset, - // fail (-2), otherwise we are subset. if we do contain this - // variable, then if we were equal, we are superset since variables - // are ordered, otherwise we remain the same. - int new_status = - std::find(reps.begin(), reps.end(), it->first) == reps.end() - ? (status >= 0 ? 1 : -2) - : (status == 0 ? -1 : status); - if (new_status != -2) - { - it->second.addTerm(n, reps, nla, new_status, argIndex); - } - } - } - // compare for subsets - for (unsigned i = 0; i < d_monos.size(); i++) - { - Node m = d_monos[i]; - if (m != n) - { - // we are superset if we are equal and haven't traversed all variables - int cstatus = status == 0 ? (argIndex == reps.size() ? 0 : -1) : status; - Trace("nl-ext-mindex-debug") << " compare " << n << " and " << m - << ", status = " << cstatus << std::endl; - if (cstatus <= 0 && nla->isMonomialSubset(m, n)) - { - nla->registerMonomialSubset(m, n); - Trace("nl-ext-mindex-debug") << "...success" << std::endl; - } - else if (cstatus >= 0 && nla->isMonomialSubset(n, m)) - { - nla->registerMonomialSubset(n, m); - Trace("nl-ext-mindex-debug") << "...success (rev)" << std::endl; - } - } - } -} - -MonomialDb::MonomialDb() -{ - d_one = NodeManager::currentNM()->mkConst(Rational(1)); -} - -void MonomialDb::registerMonomial(Node n) -{ - if (std::find(d_monomials.begin(), d_monomials.end(), n) != d_monomials.end()) - { - return; - } - d_monomials.push_back(n); - Trace("nl-ext-debug") << "Register monomial : " << n << std::endl; - Kind k = n.getKind(); - if (k == NONLINEAR_MULT) - { - // get exponent count - unsigned nchild = n.getNumChildren(); - for (unsigned i = 0; i < nchild; i++) - { - d_m_exp[n][n[i]]++; - if (i == 0 || n[i] != n[i - 1]) - { - d_m_vlist[n].push_back(n[i]); - } - } - d_m_degree[n] = nchild; - } - else if (n == d_one) - { - d_m_exp[n].clear(); - d_m_vlist[n].clear(); - d_m_degree[n] = 0; - } - else - { - Assert(k != PLUS && k != MULT); - d_m_exp[n][n] = 1; - d_m_vlist[n].push_back(n); - d_m_degree[n] = 1; - } - std::sort(d_m_vlist[n].begin(), d_m_vlist[n].end()); - Trace("nl-ext-mindex") << "Add monomial to index : " << n << std::endl; - d_m_index.addTerm(n, d_m_vlist[n], this); -} - -void MonomialDb::registerMonomialSubset(Node a, Node b) -{ - Assert(isMonomialSubset(a, b)); - - const NodeMultiset& a_exponent_map = getMonomialExponentMap(a); - const NodeMultiset& b_exponent_map = getMonomialExponentMap(b); - - std::vector diff_children = - ExpandMultiset(diffMultiset(b_exponent_map, a_exponent_map)); - Assert(!diff_children.empty()); - - d_m_contain_parent[a].push_back(b); - d_m_contain_children[b].push_back(a); - - Node mult_term = safeConstructNary(MULT, diff_children); - Node nlmult_term = safeConstructNary(NONLINEAR_MULT, diff_children); - d_m_contain_mult[a][b] = mult_term; - d_m_contain_umult[a][b] = nlmult_term; - Trace("nl-ext-mindex") << "..." << a << " is a subset of " << b - << ", difference is " << mult_term << std::endl; -} - -bool MonomialDb::isMonomialSubset(Node am, Node bm) const -{ - const NodeMultiset& a = getMonomialExponentMap(am); - const NodeMultiset& b = getMonomialExponentMap(bm); - for (NodeMultiset::const_iterator it_a = a.begin(); it_a != a.end(); ++it_a) - { - Node key = it_a->first; - const unsigned a_value = it_a->second; - const unsigned b_value = getCountWithDefault(b, key, 0); - if (a_value > b_value) - { - return false; - } - } - return true; -} - -const NodeMultiset& MonomialDb::getMonomialExponentMap(Node monomial) const -{ - MonomialExponentMap::const_iterator it = d_m_exp.find(monomial); - Assert(it != d_m_exp.end()); - return it->second; -} - -unsigned MonomialDb::getExponent(Node monomial, Node v) const -{ - MonomialExponentMap::const_iterator it = d_m_exp.find(monomial); - if (it == d_m_exp.end()) - { - return 0; - } - std::map::const_iterator itv = it->second.find(v); - if (itv == it->second.end()) - { - return 0; - } - return itv->second; -} - -const std::vector& MonomialDb::getVariableList(Node monomial) const -{ - std::map >::const_iterator itvl = - d_m_vlist.find(monomial); - Assert(itvl != d_m_vlist.end()); - return itvl->second; -} - -unsigned MonomialDb::getDegree(Node monomial) const -{ - std::map::const_iterator it = d_m_degree.find(monomial); - Assert(it != d_m_degree.end()); - return it->second; -} - -void MonomialDb::sortByDegree(std::vector& ms) const -{ - SortNonlinearDegree snlad(d_m_degree); - std::sort(ms.begin(), ms.end(), snlad); -} - -void MonomialDb::sortVariablesByModel(std::vector& ms, NlModel& m) -{ - SortNlModel smv; - smv.d_nlm = &m; - smv.d_isConcrete = false; - smv.d_isAbsolute = true; - smv.d_reverse_order = true; - for (const Node& msc : ms) - { - std::sort(d_m_vlist[msc].begin(), d_m_vlist[msc].end(), smv); - } -} - -const std::map >& MonomialDb::getContainsChildrenMap() -{ - return d_m_contain_children; -} - -const std::map >& MonomialDb::getContainsParentMap() -{ - return d_m_contain_parent; -} - -Node MonomialDb::getContainsDiff(Node a, Node b) const -{ - std::map >::const_iterator it = - d_m_contain_mult.find(a); - if (it == d_m_contain_umult.end()) - { - return Node::null(); - } - std::map::const_iterator it2 = it->second.find(b); - if (it2 == it->second.end()) - { - return Node::null(); - } - return it2->second; -} - -Node MonomialDb::getContainsDiffNl(Node a, Node b) const -{ - std::map >::const_iterator it = - d_m_contain_umult.find(a); - if (it == d_m_contain_umult.end()) - { - return Node::null(); - } - std::map::const_iterator it2 = it->second.find(b); - if (it2 == it->second.end()) - { - return Node::null(); - } - return it2->second; -} - -Node MonomialDb::mkMonomialRemFactor(Node n, - const NodeMultiset& n_exp_rem) const -{ - std::vector children; - const NodeMultiset& exponent_map = getMonomialExponentMap(n); - for (NodeMultiset::const_iterator itme2 = exponent_map.begin(); - itme2 != exponent_map.end(); - ++itme2) - { - Node v = itme2->first; - unsigned inc = itme2->second; - Trace("nl-ext-mono-factor") - << "..." << inc << " factors of " << v << std::endl; - unsigned count_in_n_exp_rem = getCountWithDefault(n_exp_rem, v, 0); - Assert(count_in_n_exp_rem <= inc); - inc -= count_in_n_exp_rem; - Trace("nl-ext-mono-factor") - << "......rem, now " << inc << " factors of " << v << std::endl; - children.insert(children.end(), inc, v); - } - Node ret = safeConstructNary(MULT, children); - ret = Rewriter::rewrite(ret); - Trace("nl-ext-mono-factor") << "...return : " << ret << std::endl; - return ret; -} - -} // namespace arith -} // namespace theory -} // namespace CVC4 diff --git a/src/theory/arith/nl_monomial.h b/src/theory/arith/nl_monomial.h deleted file mode 100644 index 81665c4d9..000000000 --- a/src/theory/arith/nl_monomial.h +++ /dev/null @@ -1,147 +0,0 @@ -/********************* */ -/*! \file nl_monomial.h - ** \verbatim - ** Top contributors (to current version): - ** Andrew Reynolds, Tim King - ** 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 Utilities for monomials - **/ - -#ifndef CVC4__THEORY__ARITH__NL_MONOMIAL_H -#define CVC4__THEORY__ARITH__NL_MONOMIAL_H - -#include -#include - -#include "expr/node.h" - -namespace CVC4 { -namespace theory { -namespace arith { - -class MonomialDb; -class NlModel; - -typedef std::map NodeMultiset; -typedef std::map MonomialExponentMap; - -/** An index data structure for node multisets (monomials) */ -class MonomialIndex -{ - public: - /** - * Add term to this trie. The argument status indicates what the status - * of n is with respect to the current node in the trie, where: - * 0 : n is equal, -1 : n is superset, 1 : n is subset - * of the node described by the current path in the trie. - */ - void addTerm(Node n, - const std::vector& reps, - MonomialDb* nla, - int status = 0, - unsigned argIndex = 0); - - private: - /** The children of this node */ - std::map d_data; - /** The monomials at this node */ - std::vector d_monos; -}; /* class MonomialIndex */ - -/** Context-independent database for monomial information */ -class MonomialDb -{ - public: - MonomialDb(); - ~MonomialDb() {} - /** register monomial */ - void registerMonomial(Node n); - /** - * Register monomial subset. This method is called when we infer that b is - * a subset of monomial a, e.g. x*y^2 is a subset of x^3*y^2*z. - */ - void registerMonomialSubset(Node a, Node b); - /** - * returns true if the multiset containing the - * factors of monomial a is a subset of the multiset - * containing the factors of monomial b. - */ - bool isMonomialSubset(Node a, Node b) const; - /** Returns the NodeMultiset for a registered monomial. */ - const NodeMultiset& getMonomialExponentMap(Node monomial) const; - /** Returns the exponent of variable v in the given monomial */ - unsigned getExponent(Node monomial, Node v) const; - /** Get the list of unique variables is the monomial */ - const std::vector& getVariableList(Node monomial) const; - /** Get degree of monomial, e.g. the degree of x^2*y^2 = 4 */ - unsigned getDegree(Node monomial) const; - /** Sort monomials in ms by their degree - * - * Updates ms so that degree(ms[i]) <= degree(ms[j]) for i <= j. - */ - void sortByDegree(std::vector& ms) const; - /** Sort the variable lists based on model values - * - * This updates the variable lists of monomials in ms based on the absolute - * value of their current model values in m. - * - * In other words, for each i, getVariableList(ms[i]) returns - * v1, ..., vn where |m(v1)| <= ... <= |m(vn)| after this method is invoked. - */ - void sortVariablesByModel(std::vector& ms, NlModel& m); - /** Get monomial contains children map - * - * This maps monomials to other monomials that are contained in them, e.g. - * x^2 * y may map to { x, x^2, y } if these three terms exist have been - * registered to this class. - */ - const std::map >& getContainsChildrenMap(); - /** Get monomial contains parent map, reverse of above */ - const std::map >& getContainsParentMap(); - /** - * Get contains difference. Return the difference of a and b or null if it - * does not exist. In other words, this returns a term equivalent to a/b - * that does not contain division. - */ - Node getContainsDiff(Node a, Node b) const; - /** - * Get contains difference non-linear. Same as above, but stores terms of kind - * NONLINEAR_MULT instead of MULT. - */ - Node getContainsDiffNl(Node a, Node b) const; - /** Make monomial remainder factor */ - Node mkMonomialRemFactor(Node n, const NodeMultiset& n_exp_rem) const; - - private: - /** commonly used terms */ - Node d_one; - /** list of all monomials */ - std::vector d_monomials; - /** Map from monomials to var^index. */ - MonomialExponentMap d_m_exp; - /** - * Mapping from monomials to the list of variables that occur in it. For - * example, x*x*y*z -> { x, y, z }. - */ - std::map > d_m_vlist; - /** Degree information */ - std::map d_m_degree; - /** monomial index, by sorted variables */ - MonomialIndex d_m_index; - /** containment ordering */ - std::map > d_m_contain_children; - std::map > d_m_contain_parent; - std::map > d_m_contain_mult; - std::map > d_m_contain_umult; -}; - -} // namespace arith -} // namespace theory -} // namespace CVC4 - -#endif /* CVC4__THEORY__ARITH__NL_MONOMIAL_H */ diff --git a/src/theory/arith/nl_solver.cpp b/src/theory/arith/nl_solver.cpp deleted file mode 100644 index 893d3dbd7..000000000 --- a/src/theory/arith/nl_solver.cpp +++ /dev/null @@ -1,1585 +0,0 @@ -/********************* */ -/*! \file nl_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 non-linear solver - **/ - -#include "theory/arith/nl_solver.h" - -#include "options/arith_options.h" -#include "theory/arith/arith_msum.h" -#include "theory/arith/arith_utilities.h" -#include "theory/arith/theory_arith.h" -#include "theory/theory_model.h" - -using namespace CVC4::kind; - -namespace CVC4 { -namespace theory { -namespace arith { - -void debugPrintBound(const char* c, Node coeff, Node x, Kind type, Node rhs) -{ - Node t = ArithMSum::mkCoeffTerm(coeff, x); - Trace(c) << t << " " << type << " " << rhs; -} - -bool hasNewMonomials(Node n, const std::vector& existing) -{ - std::set visited; - - std::vector worklist; - worklist.push_back(n); - while (!worklist.empty()) - { - Node current = worklist.back(); - worklist.pop_back(); - if (visited.find(current) == visited.end()) - { - visited.insert(current); - if (current.getKind() == NONLINEAR_MULT) - { - if (std::find(existing.begin(), existing.end(), current) - == existing.end()) - { - return true; - } - } - else - { - worklist.insert(worklist.end(), current.begin(), current.end()); - } - } - } - return false; -} - -NlSolver::NlSolver(TheoryArith& containing, NlModel& model) - : d_containing(containing), - d_model(model), - d_cdb(d_mdb), - d_zero_split(containing.getUserContext()) -{ - 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_order_points.push_back(d_neg_one); - d_order_points.push_back(d_zero); - d_order_points.push_back(d_one); -} - -NlSolver::~NlSolver() {} - -void NlSolver::initLastCall(const std::vector& assertions, - const std::vector& false_asserts, - const std::vector& xts) -{ - d_ms_vars.clear(); - d_ms_proc.clear(); - d_ms.clear(); - d_mterms.clear(); - d_m_nconst_factor.clear(); - d_tplane_refine.clear(); - d_ci.clear(); - d_ci_exp.clear(); - d_ci_max.clear(); - - Trace("nl-ext-mv") << "Extended terms : " << std::endl; - // for computing congruence - std::map argTrie; - for (unsigned i = 0, xsize = xts.size(); i < xsize; i++) - { - Node a = xts[i]; - d_model.computeConcreteModelValue(a); - d_model.computeAbstractModelValue(a); - d_model.printModelValue("nl-ext-mv", a); - Kind ak = a.getKind(); - if (ak == NONLINEAR_MULT) - { - d_ms.push_back(a); - - // context-independent registration - d_mdb.registerMonomial(a); - - const std::vector& varList = d_mdb.getVariableList(a); - for (const Node& v : varList) - { - if (std::find(d_ms_vars.begin(), d_ms_vars.end(), v) == d_ms_vars.end()) - { - d_ms_vars.push_back(v); - } - Node mvk = d_model.computeAbstractModelValue(v); - if (!mvk.isConst()) - { - d_m_nconst_factor[a] = true; - } - } - // mark processed if has a "one" factor (will look at reduced monomial)? - } - } - - // register constants - d_mdb.registerMonomial(d_one); - for (unsigned j = 0; j < d_order_points.size(); j++) - { - Node c = d_order_points[j]; - d_model.computeConcreteModelValue(c); - d_model.computeAbstractModelValue(c); - } - - // register variables - Trace("nl-ext-mv") << "Variables in monomials : " << std::endl; - for (unsigned i = 0; i < d_ms_vars.size(); i++) - { - Node v = d_ms_vars[i]; - d_mdb.registerMonomial(v); - d_model.computeConcreteModelValue(v); - d_model.computeAbstractModelValue(v); - d_model.printModelValue("nl-ext-mv", v); - } - - Trace("nl-ext") << "We have " << d_ms.size() << " monomials." << std::endl; -} - -void NlSolver::setMonomialFactor(Node a, Node b, const NodeMultiset& common) -{ - // Could not tell if this was being inserted intentionally or not. - std::map& mono_diff_a = d_mono_diff[a]; - if (mono_diff_a.find(b) == mono_diff_a.end()) - { - Trace("nl-ext-mono-factor") - << "Set monomial factor for " << a << "/" << b << std::endl; - mono_diff_a[b] = d_mdb.mkMonomialRemFactor(a, common); - } -} - -std::vector NlSolver::checkSplitZero() -{ - std::vector lemmas; - for (unsigned i = 0; i < d_ms_vars.size(); i++) - { - Node v = d_ms_vars[i]; - if (d_zero_split.insert(v)) - { - Node eq = v.eqNode(d_zero); - eq = Rewriter::rewrite(eq); - Node literal = d_containing.getValuation().ensureLiteral(eq); - d_containing.getOutputChannel().requirePhase(literal, true); - lemmas.push_back(literal.orNode(literal.negate())); - } - } - return lemmas; -} - -void NlSolver::assignOrderIds(std::vector& vars, - NodeMultiset& order, - bool isConcrete, - bool isAbsolute) -{ - SortNlModel smv; - smv.d_nlm = &d_model; - smv.d_isConcrete = isConcrete; - smv.d_isAbsolute = isAbsolute; - smv.d_reverse_order = false; - std::sort(vars.begin(), vars.end(), smv); - - order.clear(); - // assign ordering id's - unsigned counter = 0; - unsigned order_index = isConcrete ? 0 : 1; - Node prev; - for (unsigned j = 0; j < vars.size(); j++) - { - Node x = vars[j]; - Node v = d_model.computeModelValue(x, isConcrete); - if (!v.isConst()) - { - Trace("nl-ext-mvo") << "..do not assign order to " << x << " : " << v - << std::endl; - // don't assign for non-constant values (transcendental function apps) - break; - } - Trace("nl-ext-mvo") << " order " << x << " : " << v << std::endl; - if (v != prev) - { - // builtin points - bool success; - do - { - success = false; - if (order_index < d_order_points.size()) - { - Node vv = d_model.computeModelValue(d_order_points[order_index], - isConcrete); - if (d_model.compareValue(v, vv, isAbsolute) <= 0) - { - counter++; - Trace("nl-ext-mvo") << "O[" << d_order_points[order_index] - << "] = " << counter << std::endl; - order[d_order_points[order_index]] = counter; - prev = vv; - order_index++; - success = true; - } - } - } while (success); - } - if (prev.isNull() || d_model.compareValue(v, prev, isAbsolute) != 0) - { - counter++; - } - Trace("nl-ext-mvo") << "O[" << x << "] = " << counter << std::endl; - order[x] = counter; - prev = v; - } - while (order_index < d_order_points.size()) - { - counter++; - Trace("nl-ext-mvo") << "O[" << d_order_points[order_index] - << "] = " << counter << std::endl; - order[d_order_points[order_index]] = counter; - order_index++; - } -} - -// show a <> 0 by inequalities between variables in monomial a w.r.t 0 -int NlSolver::compareSign(Node oa, - Node a, - unsigned a_index, - int status, - std::vector& exp, - std::vector& lem) -{ - Trace("nl-ext-debug") << "Process " << a << " at index " << a_index - << ", status is " << status << std::endl; - NodeManager* nm = NodeManager::currentNM(); - Node mvaoa = d_model.computeAbstractModelValue(oa); - const std::vector& vla = d_mdb.getVariableList(a); - if (a_index == vla.size()) - { - if (mvaoa.getConst().sgn() != status) - { - Node lemma = - safeConstructNary(AND, exp).impNode(mkLit(oa, d_zero, status * 2)); - lem.push_back(lemma); - } - return status; - } - Assert(a_index < vla.size()); - Node av = vla[a_index]; - unsigned aexp = d_mdb.getExponent(a, av); - // take current sign in model - Node mvaav = d_model.computeAbstractModelValue(av); - int sgn = mvaav.getConst().sgn(); - Trace("nl-ext-debug") << "Process var " << av << "^" << aexp - << ", model sign = " << sgn << std::endl; - if (sgn == 0) - { - if (mvaoa.getConst().sgn() != 0) - { - Node lemma = av.eqNode(d_zero).impNode(oa.eqNode(d_zero)); - lem.push_back(lemma); - } - return 0; - } - if (aexp % 2 == 0) - { - exp.push_back(av.eqNode(d_zero).negate()); - return compareSign(oa, a, a_index + 1, status, exp, lem); - } - exp.push_back(nm->mkNode(sgn == 1 ? GT : LT, av, d_zero)); - return compareSign(oa, a, a_index + 1, status * sgn, exp, lem); -} - -bool NlSolver::compareMonomial( - Node oa, - Node a, - NodeMultiset& a_exp_proc, - Node ob, - Node b, - NodeMultiset& b_exp_proc, - std::vector& exp, - std::vector& lem, - std::map > >& cmp_infers) -{ - Trace("nl-ext-comp-debug") - << "Check |" << a << "| >= |" << b << "|" << std::endl; - unsigned pexp_size = exp.size(); - if (compareMonomial( - oa, a, 0, a_exp_proc, ob, b, 0, b_exp_proc, 0, exp, lem, cmp_infers)) - { - return true; - } - exp.resize(pexp_size); - Trace("nl-ext-comp-debug") - << "Check |" << b << "| >= |" << a << "|" << std::endl; - if (compareMonomial( - ob, b, 0, b_exp_proc, oa, a, 0, a_exp_proc, 0, exp, lem, cmp_infers)) - { - return true; - } - return false; -} - -Node NlSolver::mkLit(Node a, Node b, int status, bool isAbsolute) -{ - if (status == 0) - { - Node a_eq_b = a.eqNode(b); - if (!isAbsolute) - { - return a_eq_b; - } - Node negate_b = NodeManager::currentNM()->mkNode(UMINUS, b); - return a_eq_b.orNode(a.eqNode(negate_b)); - } - else if (status < 0) - { - return mkLit(b, a, -status); - } - Assert(status == 1 || status == 2); - NodeManager* nm = NodeManager::currentNM(); - Kind greater_op = status == 1 ? GEQ : GT; - if (!isAbsolute) - { - return nm->mkNode(greater_op, a, b); - } - // return nm->mkNode( greater_op, mkAbs( a ), mkAbs( b ) ); - Node zero = mkRationalNode(0); - Node a_is_nonnegative = nm->mkNode(GEQ, a, zero); - Node b_is_nonnegative = nm->mkNode(GEQ, b, zero); - Node negate_a = nm->mkNode(UMINUS, a); - Node negate_b = nm->mkNode(UMINUS, b); - return a_is_nonnegative.iteNode( - b_is_nonnegative.iteNode(nm->mkNode(greater_op, a, b), - nm->mkNode(greater_op, a, negate_b)), - b_is_nonnegative.iteNode(nm->mkNode(greater_op, negate_a, b), - nm->mkNode(greater_op, negate_a, negate_b))); -} - -bool NlSolver::cmp_holds(Node x, - Node y, - std::map >& cmp_infers, - std::vector& exp, - std::map& visited) -{ - if (x == y) - { - return true; - } - else if (visited.find(x) != visited.end()) - { - return false; - } - visited[x] = true; - std::map >::iterator it = cmp_infers.find(x); - if (it != cmp_infers.end()) - { - for (std::map::iterator itc = it->second.begin(); - itc != it->second.end(); - ++itc) - { - exp.push_back(itc->second); - if (cmp_holds(itc->first, y, cmp_infers, exp, visited)) - { - return true; - } - exp.pop_back(); - } - } - return false; -} - -// trying to show a ( >, = ) b by inequalities between variables in -// monomials a,b -bool NlSolver::compareMonomial( - Node oa, - Node a, - unsigned a_index, - NodeMultiset& a_exp_proc, - Node ob, - Node b, - unsigned b_index, - NodeMultiset& b_exp_proc, - int status, - std::vector& exp, - std::vector& lem, - std::map > >& cmp_infers) -{ - Trace("nl-ext-comp-debug") - << "compareMonomial " << oa << " and " << ob << ", indices = " << a_index - << " " << b_index << std::endl; - Assert(status == 0 || status == 2); - const std::vector& vla = d_mdb.getVariableList(a); - const std::vector& vlb = d_mdb.getVariableList(b); - if (a_index == vla.size() && b_index == vlb.size()) - { - // finished, compare absolute value of abstract model values - int modelStatus = d_model.compare(oa, ob, false, true) * -2; - Trace("nl-ext-comp") << "...finished comparison with " << oa << " <" - << status << "> " << ob - << ", model status = " << modelStatus << std::endl; - if (status != modelStatus) - { - Trace("nl-ext-comp-infer") - << "infer : " << oa << " <" << status << "> " << ob << std::endl; - if (status == 2) - { - // must state that all variables are non-zero - for (unsigned j = 0; j < vla.size(); j++) - { - exp.push_back(vla[j].eqNode(d_zero).negate()); - } - } - NodeManager* nm = NodeManager::currentNM(); - Node clem = nm->mkNode( - IMPLIES, safeConstructNary(AND, exp), mkLit(oa, ob, status, true)); - Trace("nl-ext-comp-lemma") << "comparison lemma : " << clem << std::endl; - lem.push_back(clem); - cmp_infers[status][oa][ob] = clem; - } - return true; - } - // get a/b variable information - Node av; - unsigned aexp = 0; - unsigned avo = 0; - if (a_index < vla.size()) - { - av = vla[a_index]; - unsigned aexpTotal = d_mdb.getExponent(a, av); - Assert(a_exp_proc[av] <= aexpTotal); - aexp = aexpTotal - a_exp_proc[av]; - if (aexp == 0) - { - return compareMonomial(oa, - a, - a_index + 1, - a_exp_proc, - ob, - b, - b_index, - b_exp_proc, - status, - exp, - lem, - cmp_infers); - } - Assert(d_order_vars.find(av) != d_order_vars.end()); - avo = d_order_vars[av]; - } - Node bv; - unsigned bexp = 0; - unsigned bvo = 0; - if (b_index < vlb.size()) - { - bv = vlb[b_index]; - unsigned bexpTotal = d_mdb.getExponent(b, bv); - Assert(b_exp_proc[bv] <= bexpTotal); - bexp = bexpTotal - b_exp_proc[bv]; - if (bexp == 0) - { - return compareMonomial(oa, - a, - a_index, - a_exp_proc, - ob, - b, - b_index + 1, - b_exp_proc, - status, - exp, - lem, - cmp_infers); - } - Assert(d_order_vars.find(bv) != d_order_vars.end()); - bvo = d_order_vars[bv]; - } - // get "one" information - Assert(d_order_vars.find(d_one) != d_order_vars.end()); - unsigned ovo = d_order_vars[d_one]; - Trace("nl-ext-comp-debug") << "....vars : " << av << "^" << aexp << " " << bv - << "^" << bexp << std::endl; - - //--- cases - if (av.isNull()) - { - if (bvo <= ovo) - { - Trace("nl-ext-comp-debug") << "...take leading " << bv << std::endl; - // can multiply b by <=1 - exp.push_back(mkLit(d_one, bv, bvo == ovo ? 0 : 2, true)); - return compareMonomial(oa, - a, - a_index, - a_exp_proc, - ob, - b, - b_index + 1, - b_exp_proc, - bvo == ovo ? status : 2, - exp, - lem, - cmp_infers); - } - Trace("nl-ext-comp-debug") - << "...failure, unmatched |b|>1 component." << std::endl; - return false; - } - else if (bv.isNull()) - { - if (avo >= ovo) - { - Trace("nl-ext-comp-debug") << "...take leading " << av << std::endl; - // can multiply a by >=1 - exp.push_back(mkLit(av, d_one, avo == ovo ? 0 : 2, true)); - return compareMonomial(oa, - a, - a_index + 1, - a_exp_proc, - ob, - b, - b_index, - b_exp_proc, - avo == ovo ? status : 2, - exp, - lem, - cmp_infers); - } - Trace("nl-ext-comp-debug") - << "...failure, unmatched |a|<1 component." << std::endl; - return false; - } - Assert(!av.isNull() && !bv.isNull()); - if (avo >= bvo) - { - if (bvo < ovo && avo >= ovo) - { - Trace("nl-ext-comp-debug") << "...take leading " << av << std::endl; - // do avo>=1 instead - exp.push_back(mkLit(av, d_one, avo == ovo ? 0 : 2, true)); - return compareMonomial(oa, - a, - a_index + 1, - a_exp_proc, - ob, - b, - b_index, - b_exp_proc, - avo == ovo ? status : 2, - exp, - lem, - cmp_infers); - } - unsigned min_exp = aexp > bexp ? bexp : aexp; - a_exp_proc[av] += min_exp; - b_exp_proc[bv] += min_exp; - Trace("nl-ext-comp-debug") << "...take leading " << min_exp << " from " - << av << " and " << bv << std::endl; - exp.push_back(mkLit(av, bv, avo == bvo ? 0 : 2, true)); - bool ret = compareMonomial(oa, - a, - a_index, - a_exp_proc, - ob, - b, - b_index, - b_exp_proc, - avo == bvo ? status : 2, - exp, - lem, - cmp_infers); - a_exp_proc[av] -= min_exp; - b_exp_proc[bv] -= min_exp; - return ret; - } - if (bvo <= ovo) - { - Trace("nl-ext-comp-debug") << "...take leading " << bv << std::endl; - // try multiply b <= 1 - exp.push_back(mkLit(d_one, bv, bvo == ovo ? 0 : 2, true)); - return compareMonomial(oa, - a, - a_index, - a_exp_proc, - ob, - b, - b_index + 1, - b_exp_proc, - bvo == ovo ? status : 2, - exp, - lem, - cmp_infers); - } - Trace("nl-ext-comp-debug") - << "...failure, leading |b|>|a|>1 component." << std::endl; - return false; -} - -std::vector NlSolver::checkMonomialSign() -{ - std::vector lemmas; - std::map signs; - Trace("nl-ext") << "Get monomial sign lemmas..." << std::endl; - for (unsigned j = 0; j < d_ms.size(); j++) - { - Node a = d_ms[j]; - if (d_ms_proc.find(a) == d_ms_proc.end()) - { - std::vector exp; - if (Trace.isOn("nl-ext-debug")) - { - Node cmva = d_model.computeConcreteModelValue(a); - Trace("nl-ext-debug") - << " process " << a << ", mv=" << cmva << "..." << std::endl; - } - if (d_m_nconst_factor.find(a) == d_m_nconst_factor.end()) - { - signs[a] = compareSign(a, a, 0, 1, exp, lemmas); - if (signs[a] == 0) - { - d_ms_proc[a] = true; - Trace("nl-ext-debug") - << "...mark " << a << " reduced since its value is 0." - << std::endl; - } - } - else - { - Trace("nl-ext-debug") - << "...can't conclude sign lemma for " << a - << " since model value of a factor is non-constant." << std::endl; - } - } - } - return lemmas; -} - -std::vector NlSolver::checkMonomialMagnitude(unsigned c) -{ - // ensure information is setup - if (c == 0) - { - // sort by absolute values of abstract model values - assignOrderIds(d_ms_vars, d_order_vars, false, true); - - // sort individual variable lists - Trace("nl-ext-proc") << "Assign order var lists..." << std::endl; - d_mdb.sortVariablesByModel(d_ms, d_model); - } - - unsigned r = 1; - std::vector lemmas; - // if (x,y,L) in cmp_infers, then x > y inferred as conclusion of L - // in lemmas - std::map > > cmp_infers; - Trace("nl-ext") << "Get monomial comparison lemmas (order=" << r - << ", compare=" << c << ")..." << std::endl; - for (unsigned j = 0; j < d_ms.size(); j++) - { - Node a = d_ms[j]; - if (d_ms_proc.find(a) == d_ms_proc.end() - && d_m_nconst_factor.find(a) == d_m_nconst_factor.end()) - { - if (c == 0) - { - // compare magnitude against 1 - std::vector exp; - NodeMultiset a_exp_proc; - NodeMultiset b_exp_proc; - compareMonomial(a, - a, - a_exp_proc, - d_one, - d_one, - b_exp_proc, - exp, - lemmas, - cmp_infers); - } - else - { - const NodeMultiset& mea = d_mdb.getMonomialExponentMap(a); - if (c == 1) - { - // could compare not just against containing variables? - // compare magnitude against variables - for (unsigned k = 0; k < d_ms_vars.size(); k++) - { - Node v = d_ms_vars[k]; - Node mvcv = d_model.computeConcreteModelValue(v); - if (mvcv.isConst()) - { - std::vector exp; - NodeMultiset a_exp_proc; - NodeMultiset b_exp_proc; - if (mea.find(v) != mea.end()) - { - a_exp_proc[v] = 1; - b_exp_proc[v] = 1; - setMonomialFactor(a, v, a_exp_proc); - setMonomialFactor(v, a, b_exp_proc); - compareMonomial(a, - a, - a_exp_proc, - v, - v, - b_exp_proc, - exp, - lemmas, - cmp_infers); - } - } - } - } - else - { - // compare magnitude against other non-linear monomials - for (unsigned k = (j + 1); k < d_ms.size(); k++) - { - Node b = d_ms[k]; - //(signs[a]==signs[b])==(r==0) - if (d_ms_proc.find(b) == d_ms_proc.end() - && d_m_nconst_factor.find(b) == d_m_nconst_factor.end()) - { - const NodeMultiset& meb = d_mdb.getMonomialExponentMap(b); - - std::vector exp; - // take common factors of monomials, set minimum of - // common exponents as processed - NodeMultiset a_exp_proc; - NodeMultiset b_exp_proc; - for (NodeMultiset::const_iterator itmea2 = mea.begin(); - itmea2 != mea.end(); - ++itmea2) - { - NodeMultiset::const_iterator itmeb2 = meb.find(itmea2->first); - if (itmeb2 != meb.end()) - { - unsigned min_exp = itmea2->second > itmeb2->second - ? itmeb2->second - : itmea2->second; - a_exp_proc[itmea2->first] = min_exp; - b_exp_proc[itmea2->first] = min_exp; - Trace("nl-ext-comp") << "Common exponent : " << itmea2->first - << " : " << min_exp << std::endl; - } - } - if (!a_exp_proc.empty()) - { - setMonomialFactor(a, b, a_exp_proc); - setMonomialFactor(b, a, b_exp_proc); - } - /* - if( !a_exp_proc.empty() ){ - //reduction based on common exponents a > 0 => ( a * b - <> a * c <=> b <> c ), a < 0 => ( a * b <> a * c <=> b - !<> c ) ? }else{ compareMonomial( a, a, a_exp_proc, b, - b, b_exp_proc, exp, lemmas ); - } - */ - compareMonomial( - a, a, a_exp_proc, b, b, b_exp_proc, exp, lemmas, cmp_infers); - } - } - } - } - } - } - // remove redundant lemmas, e.g. if a > b, b > c, a > c were - // inferred, discard lemma with conclusion a > c - Trace("nl-ext-comp") << "Compute redundancies for " << lemmas.size() - << " lemmas." << std::endl; - // naive - std::vector r_lemmas; - for (std::map > >::iterator itb = - cmp_infers.begin(); - itb != cmp_infers.end(); - ++itb) - { - for (std::map >::iterator itc = - itb->second.begin(); - itc != itb->second.end(); - ++itc) - { - for (std::map::iterator itc2 = itc->second.begin(); - itc2 != itc->second.end(); - ++itc2) - { - std::map visited; - for (std::map::iterator itc3 = itc->second.begin(); - itc3 != itc->second.end(); - ++itc3) - { - if (itc3->first != itc2->first) - { - std::vector exp; - if (cmp_holds(itc3->first, itc2->first, itb->second, exp, visited)) - { - r_lemmas.push_back(itc2->second); - Trace("nl-ext-comp") - << "...inference of " << itc->first << " > " << itc2->first - << " was redundant." << std::endl; - break; - } - } - } - } - } - } - std::vector nr_lemmas; - for (unsigned i = 0; i < lemmas.size(); i++) - { - if (std::find(r_lemmas.begin(), r_lemmas.end(), lemmas[i]) - == r_lemmas.end()) - { - nr_lemmas.push_back(lemmas[i]); - } - } - // could only take maximal lower/minimial lower bounds? - - Trace("nl-ext-comp") << nr_lemmas.size() << " / " << lemmas.size() - << " were non-redundant." << std::endl; - return nr_lemmas; -} - -std::vector NlSolver::checkTangentPlanes() -{ - std::vector lemmas; - Trace("nl-ext") << "Get monomial tangent plane lemmas..." << std::endl; - NodeManager* nm = NodeManager::currentNM(); - const std::map >& ccMap = - d_mdb.getContainsChildrenMap(); - unsigned kstart = d_ms_vars.size(); - for (unsigned k = kstart; k < d_mterms.size(); k++) - { - Node t = d_mterms[k]; - // if this term requires a refinement - if (d_tplane_refine.find(t) == d_tplane_refine.end()) - { - continue; - } - Trace("nl-ext-tplanes") - << "Look at monomial requiring refinement : " << t << std::endl; - // get a decomposition - std::map >::const_iterator it = ccMap.find(t); - if (it == ccMap.end()) - { - continue; - } - std::map > dproc; - for (unsigned j = 0; j < it->second.size(); j++) - { - Node tc = it->second[j]; - if (tc != d_one) - { - Node tc_diff = d_mdb.getContainsDiffNl(tc, t); - Assert(!tc_diff.isNull()); - Node a = tc < tc_diff ? tc : tc_diff; - Node b = tc < tc_diff ? tc_diff : tc; - if (dproc[a].find(b) == dproc[a].end()) - { - dproc[a][b] = true; - Trace("nl-ext-tplanes") - << " decomposable into : " << a << " * " << b << std::endl; - Node a_v_c = d_model.computeAbstractModelValue(a); - Node b_v_c = d_model.computeAbstractModelValue(b); - // points we will add tangent planes for - std::vector pts[2]; - pts[0].push_back(a_v_c); - pts[1].push_back(b_v_c); - // if previously refined - bool prevRefine = d_tangent_val_bound[0][a].find(b) - != d_tangent_val_bound[0][a].end(); - // a_min, a_max, b_min, b_max - for (unsigned p = 0; p < 4; p++) - { - Node curr_v = p <= 1 ? a_v_c : b_v_c; - if (prevRefine) - { - Node pt_v = d_tangent_val_bound[p][a][b]; - Assert(!pt_v.isNull()); - if (curr_v != pt_v) - { - Node do_extend = - nm->mkNode((p == 1 || p == 3) ? GT : LT, curr_v, pt_v); - do_extend = Rewriter::rewrite(do_extend); - if (do_extend == d_true) - { - for (unsigned q = 0; q < 2; q++) - { - pts[p <= 1 ? 0 : 1].push_back(curr_v); - pts[p <= 1 ? 1 : 0].push_back( - d_tangent_val_bound[p <= 1 ? 2 + q : q][a][b]); - } - } - } - } - else - { - d_tangent_val_bound[p][a][b] = curr_v; - } - } - - for (unsigned p = 0; p < pts[0].size(); p++) - { - Node a_v = pts[0][p]; - Node b_v = pts[1][p]; - - // tangent plane - Node tplane = nm->mkNode( - MINUS, - nm->mkNode( - PLUS, nm->mkNode(MULT, b_v, a), nm->mkNode(MULT, a_v, b)), - nm->mkNode(MULT, a_v, b_v)); - for (unsigned d = 0; d < 4; d++) - { - Node aa = nm->mkNode(d == 0 || d == 3 ? GEQ : LEQ, a, a_v); - Node ab = nm->mkNode(d == 1 || d == 3 ? GEQ : LEQ, b, b_v); - Node conc = nm->mkNode(d <= 1 ? LEQ : GEQ, t, tplane); - Node tlem = nm->mkNode(OR, aa.negate(), ab.negate(), conc); - Trace("nl-ext-tplanes") - << "Tangent plane lemma : " << tlem << std::endl; - lemmas.push_back(tlem); - } - - // tangent plane reverse implication - - // t <= tplane -> ( (a <= a_v ^ b >= b_v) v - // (a >= a_v ^ b <= b_v) ). - // in clause form, the above becomes - // t <= tplane -> a <= a_v v b <= b_v. - // t <= tplane -> b >= b_v v a >= a_v. - Node a_leq_av = nm->mkNode(LEQ, a, a_v); - Node b_leq_bv = nm->mkNode(LEQ, b, b_v); - Node a_geq_av = nm->mkNode(GEQ, a, a_v); - Node b_geq_bv = nm->mkNode(GEQ, b, b_v); - - Node t_leq_tplane = nm->mkNode(LEQ, t, tplane); - Node a_leq_av_or_b_leq_bv = nm->mkNode(OR, a_leq_av, b_leq_bv); - Node b_geq_bv_or_a_geq_av = nm->mkNode(OR, b_geq_bv, a_geq_av); - Node ub_reverse1 = - nm->mkNode(OR, t_leq_tplane.negate(), a_leq_av_or_b_leq_bv); - Trace("nl-ext-tplanes") - << "Tangent plane lemma (reverse) : " << ub_reverse1 - << std::endl; - lemmas.push_back(ub_reverse1); - Node ub_reverse2 = - nm->mkNode(OR, t_leq_tplane.negate(), b_geq_bv_or_a_geq_av); - Trace("nl-ext-tplanes") - << "Tangent plane lemma (reverse) : " << ub_reverse2 - << std::endl; - lemmas.push_back(ub_reverse2); - - // t >= tplane -> ( (a <= a_v ^ b <= b_v) v - // (a >= a_v ^ b >= b_v) ). - // in clause form, the above becomes - // t >= tplane -> a <= a_v v b >= b_v. - // t >= tplane -> b >= b_v v a <= a_v - Node t_geq_tplane = nm->mkNode(GEQ, t, tplane); - Node a_leq_av_or_b_geq_bv = nm->mkNode(OR, a_leq_av, b_geq_bv); - Node a_geq_av_or_b_leq_bv = nm->mkNode(OR, a_geq_av, b_leq_bv); - Node lb_reverse1 = - nm->mkNode(OR, t_geq_tplane.negate(), a_leq_av_or_b_geq_bv); - Trace("nl-ext-tplanes") - << "Tangent plane lemma (reverse) : " << lb_reverse1 - << std::endl; - lemmas.push_back(lb_reverse1); - Node lb_reverse2 = - nm->mkNode(OR, t_geq_tplane.negate(), a_geq_av_or_b_leq_bv); - Trace("nl-ext-tplanes") - << "Tangent plane lemma (reverse) : " << lb_reverse2 - << std::endl; - lemmas.push_back(lb_reverse2); - } - } - } - } - } - Trace("nl-ext") << "...trying " << lemmas.size() << " tangent plane lemmas..." - << std::endl; - return lemmas; -} - -std::vector NlSolver::checkMonomialInferBounds( - std::vector& nt_lemmas, - const std::vector& asserts, - const std::vector& false_asserts) -{ - // sort monomials by degree - Trace("nl-ext-proc") << "Sort monomials by degree..." << std::endl; - d_mdb.sortByDegree(d_ms); - // all monomials - d_mterms.insert(d_mterms.end(), d_ms_vars.begin(), d_ms_vars.end()); - d_mterms.insert(d_mterms.end(), d_ms.begin(), d_ms.end()); - - const std::map >& cim = - d_cdb.getConstraints(); - - std::vector lemmas; - NodeManager* nm = NodeManager::currentNM(); - // register constraints - Trace("nl-ext-debug") << "Register bound constraints..." << std::endl; - for (const Node& lit : asserts) - { - bool polarity = lit.getKind() != NOT; - Node atom = lit.getKind() == NOT ? lit[0] : lit; - d_cdb.registerConstraint(atom); - bool is_false_lit = - std::find(false_asserts.begin(), false_asserts.end(), lit) - != false_asserts.end(); - // add information about bounds to variables - std::map >::const_iterator itc = - cim.find(atom); - if (itc == cim.end()) - { - continue; - } - for (const std::pair& itcc : itc->second) - { - Node x = itcc.first; - Node coeff = itcc.second.d_coeff; - Node rhs = itcc.second.d_rhs; - Kind type = itcc.second.d_type; - Node exp = lit; - if (!polarity) - { - // reverse - if (type == EQUAL) - { - // we will take the strict inequality in the direction of the - // model - Node lhs = ArithMSum::mkCoeffTerm(coeff, x); - Node query = nm->mkNode(GT, lhs, rhs); - Node query_mv = d_model.computeAbstractModelValue(query); - if (query_mv == d_true) - { - exp = query; - type = GT; - } - else - { - Assert(query_mv == d_false); - exp = nm->mkNode(LT, lhs, rhs); - type = LT; - } - } - else - { - type = negateKind(type); - } - } - // add to status if maximal degree - d_ci_max[x][coeff][rhs] = d_cdb.isMaximal(atom, x); - if (Trace.isOn("nl-ext-bound-debug2")) - { - Node t = ArithMSum::mkCoeffTerm(coeff, x); - Trace("nl-ext-bound-debug2") << "Add Bound: " << t << " " << type << " " - << rhs << " by " << exp << std::endl; - } - bool updated = true; - std::map::iterator its = d_ci[x][coeff].find(rhs); - if (its == d_ci[x][coeff].end()) - { - d_ci[x][coeff][rhs] = type; - d_ci_exp[x][coeff][rhs] = exp; - } - else if (type != its->second) - { - Trace("nl-ext-bound-debug2") - << "Joining kinds : " << type << " " << its->second << std::endl; - Kind jk = joinKinds(type, its->second); - if (jk == UNDEFINED_KIND) - { - updated = false; - } - else if (jk != its->second) - { - if (jk == type) - { - d_ci[x][coeff][rhs] = type; - d_ci_exp[x][coeff][rhs] = exp; - } - else - { - d_ci[x][coeff][rhs] = jk; - d_ci_exp[x][coeff][rhs] = - nm->mkNode(AND, d_ci_exp[x][coeff][rhs], exp); - } - } - else - { - updated = false; - } - } - if (Trace.isOn("nl-ext-bound")) - { - if (updated) - { - Trace("nl-ext-bound") << "Bound: "; - debugPrintBound("nl-ext-bound", coeff, x, d_ci[x][coeff][rhs], rhs); - Trace("nl-ext-bound") << " by " << d_ci_exp[x][coeff][rhs]; - if (d_ci_max[x][coeff][rhs]) - { - Trace("nl-ext-bound") << ", is max degree"; - } - Trace("nl-ext-bound") << std::endl; - } - } - // compute if bound is not satisfied, and store what is required - // for a possible refinement - if (options::nlExtTangentPlanes()) - { - if (is_false_lit) - { - d_tplane_refine.insert(x); - } - } - } - } - // reflexive constraints - Node null_coeff; - for (unsigned j = 0; j < d_mterms.size(); j++) - { - Node n = d_mterms[j]; - d_ci[n][null_coeff][n] = EQUAL; - d_ci_exp[n][null_coeff][n] = d_true; - d_ci_max[n][null_coeff][n] = false; - } - - Trace("nl-ext") << "Get inferred bound lemmas..." << std::endl; - const std::map >& cpMap = - d_mdb.getContainsParentMap(); - for (unsigned k = 0; k < d_mterms.size(); k++) - { - Node x = d_mterms[k]; - Trace("nl-ext-bound-debug") - << "Process bounds for " << x << " : " << std::endl; - std::map >::const_iterator itm = cpMap.find(x); - if (itm == cpMap.end()) - { - Trace("nl-ext-bound-debug") << "...has no parent monomials." << std::endl; - continue; - } - Trace("nl-ext-bound-debug") - << "...has " << itm->second.size() << " parent monomials." << std::endl; - // check derived bounds - std::map > >::iterator itc = - d_ci.find(x); - if (itc == d_ci.end()) - { - continue; - } - for (std::map >::iterator itcc = - itc->second.begin(); - itcc != itc->second.end(); - ++itcc) - { - Node coeff = itcc->first; - Node t = ArithMSum::mkCoeffTerm(coeff, x); - for (std::map::iterator itcr = itcc->second.begin(); - itcr != itcc->second.end(); - ++itcr) - { - Node rhs = itcr->first; - // only consider this bound if maximal degree - if (!d_ci_max[x][coeff][rhs]) - { - continue; - } - Kind type = itcr->second; - for (unsigned j = 0; j < itm->second.size(); j++) - { - Node y = itm->second[j]; - Node mult = d_mdb.getContainsDiff(x, y); - // x t => m*x t where y = m*x - // get the sign of mult - Node mmv = d_model.computeConcreteModelValue(mult); - Trace("nl-ext-bound-debug2") - << "Model value of " << mult << " is " << mmv << std::endl; - if (!mmv.isConst()) - { - Trace("nl-ext-bound-debug") - << " ...coefficient " << mult - << " is non-constant (probably transcendental)." << std::endl; - continue; - } - int mmv_sign = mmv.getConst().sgn(); - Trace("nl-ext-bound-debug2") - << " sign of " << mmv << " is " << mmv_sign << std::endl; - if (mmv_sign == 0) - { - Trace("nl-ext-bound-debug") - << " ...coefficient " << mult << " is zero." << std::endl; - continue; - } - Trace("nl-ext-bound-debug") - << " from " << x << " * " << mult << " = " << y << " and " << t - << " " << type << " " << rhs << ", infer : " << std::endl; - Kind infer_type = mmv_sign == -1 ? reverseRelationKind(type) : type; - Node infer_lhs = nm->mkNode(MULT, mult, t); - Node infer_rhs = nm->mkNode(MULT, mult, rhs); - Node infer = nm->mkNode(infer_type, infer_lhs, infer_rhs); - Trace("nl-ext-bound-debug") << " " << infer << std::endl; - infer = Rewriter::rewrite(infer); - Trace("nl-ext-bound-debug2") - << " ...rewritten : " << infer << std::endl; - // check whether it is false in model for abstraction - Node infer_mv = d_model.computeAbstractModelValue(infer); - Trace("nl-ext-bound-debug") - << " ...infer model value is " << infer_mv << std::endl; - if (infer_mv == d_false) - { - Node exp = - nm->mkNode(AND, - nm->mkNode(mmv_sign == 1 ? GT : LT, mult, d_zero), - d_ci_exp[x][coeff][rhs]); - Node iblem = nm->mkNode(IMPLIES, exp, infer); - Node pr_iblem = iblem; - iblem = Rewriter::rewrite(iblem); - bool introNewTerms = hasNewMonomials(iblem, d_ms); - Trace("nl-ext-bound-lemma") - << "*** Bound inference lemma : " << iblem - << " (pre-rewrite : " << pr_iblem << ")" << std::endl; - // Trace("nl-ext-bound-lemma") << " intro new - // monomials = " << introNewTerms << std::endl; - if (!introNewTerms) - { - lemmas.push_back(iblem); - } - else - { - nt_lemmas.push_back(iblem); - } - } - } - } - } - } - return lemmas; -} - -std::vector NlSolver::checkFactoring( - const std::vector& asserts, const std::vector& false_asserts) -{ - std::vector lemmas; - NodeManager* nm = NodeManager::currentNM(); - Trace("nl-ext") << "Get factoring lemmas..." << std::endl; - for (const Node& lit : asserts) - { - bool polarity = lit.getKind() != NOT; - Node atom = lit.getKind() == NOT ? lit[0] : lit; - Node litv = d_model.computeConcreteModelValue(lit); - bool considerLit = false; - // Only consider literals that are in false_asserts. - considerLit = std::find(false_asserts.begin(), false_asserts.end(), lit) - != false_asserts.end(); - - if (considerLit) - { - std::map msum; - if (ArithMSum::getMonomialSumLit(atom, msum)) - { - Trace("nl-ext-factor") << "Factoring for literal " << lit - << ", monomial sum is : " << std::endl; - if (Trace.isOn("nl-ext-factor")) - { - ArithMSum::debugPrintMonomialSum(msum, "nl-ext-factor"); - } - std::map > factor_to_mono; - std::map > factor_to_mono_orig; - for (std::map::iterator itm = msum.begin(); - itm != msum.end(); - ++itm) - { - if (!itm->first.isNull()) - { - if (itm->first.getKind() == NONLINEAR_MULT) - { - std::vector children; - for (unsigned i = 0; i < itm->first.getNumChildren(); i++) - { - children.push_back(itm->first[i]); - } - std::map processed; - for (unsigned i = 0; i < itm->first.getNumChildren(); i++) - { - if (processed.find(itm->first[i]) == processed.end()) - { - processed[itm->first[i]] = true; - children[i] = d_one; - if (!itm->second.isNull()) - { - children.push_back(itm->second); - } - Node val = nm->mkNode(MULT, children); - if (!itm->second.isNull()) - { - children.pop_back(); - } - children[i] = itm->first[i]; - val = Rewriter::rewrite(val); - factor_to_mono[itm->first[i]].push_back(val); - factor_to_mono_orig[itm->first[i]].push_back(itm->first); - } - } - } - } - } - for (std::map >::iterator itf = - factor_to_mono.begin(); - itf != factor_to_mono.end(); - ++itf) - { - Node x = itf->first; - if (itf->second.size() == 1) - { - std::map::iterator itm = msum.find(x); - if (itm != msum.end()) - { - itf->second.push_back(itm->second.isNull() ? d_one : itm->second); - factor_to_mono_orig[x].push_back(x); - } - } - if (itf->second.size() <= 1) - { - continue; - } - Node sum = nm->mkNode(PLUS, itf->second); - sum = Rewriter::rewrite(sum); - Trace("nl-ext-factor") - << "* Factored sum for " << x << " : " << sum << std::endl; - Node kf = getFactorSkolem(sum, lemmas); - std::vector poly; - poly.push_back(nm->mkNode(MULT, x, kf)); - std::map >::iterator itfo = - factor_to_mono_orig.find(x); - Assert(itfo != factor_to_mono_orig.end()); - for (std::map::iterator itm = msum.begin(); - itm != msum.end(); - ++itm) - { - if (std::find(itfo->second.begin(), itfo->second.end(), itm->first) - == itfo->second.end()) - { - poly.push_back(ArithMSum::mkCoeffTerm( - itm->second, itm->first.isNull() ? d_one : itm->first)); - } - } - Node polyn = poly.size() == 1 ? poly[0] : nm->mkNode(PLUS, poly); - Trace("nl-ext-factor") - << "...factored polynomial : " << polyn << std::endl; - Node conc_lit = nm->mkNode(atom.getKind(), polyn, d_zero); - conc_lit = Rewriter::rewrite(conc_lit); - if (!polarity) - { - conc_lit = conc_lit.negate(); - } - - std::vector lemma_disj; - lemma_disj.push_back(lit.negate()); - lemma_disj.push_back(conc_lit); - Node flem = nm->mkNode(OR, lemma_disj); - Trace("nl-ext-factor") << "...lemma is " << flem << std::endl; - lemmas.push_back(flem); - } - } - } - } - return lemmas; -} - -Node NlSolver::getFactorSkolem(Node n, std::vector& lemmas) -{ - std::map::iterator itf = d_factor_skolem.find(n); - if (itf == d_factor_skolem.end()) - { - NodeManager* nm = NodeManager::currentNM(); - Node k = nm->mkSkolem("kf", n.getType()); - Node k_eq = Rewriter::rewrite(k.eqNode(n)); - lemmas.push_back(k_eq); - d_factor_skolem[n] = k; - return k; - } - return itf->second; -} - -std::vector NlSolver::checkMonomialInferResBounds() -{ - std::vector lemmas; - NodeManager* nm = NodeManager::currentNM(); - Trace("nl-ext") << "Get monomial resolution inferred bound lemmas..." - << std::endl; - size_t nmterms = d_mterms.size(); - for (unsigned j = 0; j < nmterms; j++) - { - Node a = d_mterms[j]; - std::map > >::iterator itca = - d_ci.find(a); - if (itca == d_ci.end()) - { - continue; - } - for (unsigned k = (j + 1); k < nmterms; k++) - { - Node b = d_mterms[k]; - std::map > >::iterator itcb = - d_ci.find(b); - if (itcb == d_ci.end()) - { - continue; - } - Trace("nl-ext-rbound-debug") << "resolution inferences : compare " << a - << " and " << b << std::endl; - // if they have common factors - std::map::iterator ita = d_mono_diff[a].find(b); - if (ita == d_mono_diff[a].end()) - { - continue; - } - Trace("nl-ext-rbound") << "Get resolution inferences for [a] " << a - << " vs [b] " << b << std::endl; - std::map::iterator itb = d_mono_diff[b].find(a); - Assert(itb != d_mono_diff[b].end()); - Node mv_a = d_model.computeAbstractModelValue(ita->second); - Assert(mv_a.isConst()); - int mv_a_sgn = mv_a.getConst().sgn(); - if (mv_a_sgn == 0) - { - // we don't compare monomials whose current model value is zero - continue; - } - Node mv_b = d_model.computeAbstractModelValue(itb->second); - Assert(mv_b.isConst()); - int mv_b_sgn = mv_b.getConst().sgn(); - if (mv_b_sgn == 0) - { - // we don't compare monomials whose current model value is zero - continue; - } - Trace("nl-ext-rbound") << " [a] factor is " << ita->second - << ", sign in model = " << mv_a_sgn << std::endl; - Trace("nl-ext-rbound") << " [b] factor is " << itb->second - << ", sign in model = " << mv_b_sgn << std::endl; - - std::vector exp; - // bounds of a - for (std::map >::iterator itcac = - itca->second.begin(); - itcac != itca->second.end(); - ++itcac) - { - Node coeff_a = itcac->first; - for (std::map::iterator itcar = itcac->second.begin(); - itcar != itcac->second.end(); - ++itcar) - { - Node rhs_a = itcar->first; - Node rhs_a_res_base = nm->mkNode(MULT, itb->second, rhs_a); - rhs_a_res_base = Rewriter::rewrite(rhs_a_res_base); - if (hasNewMonomials(rhs_a_res_base, d_ms)) - { - continue; - } - Kind type_a = itcar->second; - exp.push_back(d_ci_exp[a][coeff_a][rhs_a]); - - // bounds of b - for (std::map >::iterator itcbc = - itcb->second.begin(); - itcbc != itcb->second.end(); - ++itcbc) - { - Node coeff_b = itcbc->first; - Node rhs_a_res = ArithMSum::mkCoeffTerm(coeff_b, rhs_a_res_base); - for (std::map::iterator itcbr = itcbc->second.begin(); - itcbr != itcbc->second.end(); - ++itcbr) - { - Node rhs_b = itcbr->first; - Node rhs_b_res = nm->mkNode(MULT, ita->second, rhs_b); - rhs_b_res = ArithMSum::mkCoeffTerm(coeff_a, rhs_b_res); - rhs_b_res = Rewriter::rewrite(rhs_b_res); - if (hasNewMonomials(rhs_b_res, d_ms)) - { - continue; - } - Kind type_b = itcbr->second; - exp.push_back(d_ci_exp[b][coeff_b][rhs_b]); - if (Trace.isOn("nl-ext-rbound")) - { - Trace("nl-ext-rbound") << "* try bounds : "; - debugPrintBound("nl-ext-rbound", coeff_a, a, type_a, rhs_a); - Trace("nl-ext-rbound") << std::endl; - Trace("nl-ext-rbound") << " "; - debugPrintBound("nl-ext-rbound", coeff_b, b, type_b, rhs_b); - Trace("nl-ext-rbound") << std::endl; - } - Kind types[2]; - for (unsigned r = 0; r < 2; r++) - { - Node pivot_factor = r == 0 ? itb->second : ita->second; - int pivot_factor_sign = r == 0 ? mv_b_sgn : mv_a_sgn; - types[r] = r == 0 ? type_a : type_b; - if (pivot_factor_sign == (r == 0 ? 1 : -1)) - { - types[r] = reverseRelationKind(types[r]); - } - if (pivot_factor_sign == 1) - { - exp.push_back(nm->mkNode(GT, pivot_factor, d_zero)); - } - else - { - exp.push_back(nm->mkNode(LT, pivot_factor, d_zero)); - } - } - Kind jk = transKinds(types[0], types[1]); - Trace("nl-ext-rbound-debug") - << "trans kind : " << types[0] << " + " << types[1] << " = " - << jk << std::endl; - if (jk != UNDEFINED_KIND) - { - Node conc = nm->mkNode(jk, rhs_a_res, rhs_b_res); - Node conc_mv = d_model.computeAbstractModelValue(conc); - if (conc_mv == d_false) - { - Node rblem = nm->mkNode(IMPLIES, nm->mkNode(AND, exp), conc); - Trace("nl-ext-rbound-lemma-debug") - << "Resolution bound lemma " - "(pre-rewrite) " - ": " - << rblem << std::endl; - rblem = Rewriter::rewrite(rblem); - Trace("nl-ext-rbound-lemma") - << "Resolution bound lemma : " << rblem << std::endl; - lemmas.push_back(rblem); - } - } - exp.pop_back(); - exp.pop_back(); - exp.pop_back(); - } - } - exp.pop_back(); - } - } - } - } - return lemmas; -} - -} // namespace arith -} // namespace theory -} // namespace CVC4 diff --git a/src/theory/arith/nl_solver.h b/src/theory/arith/nl_solver.h deleted file mode 100644 index 73ca97f00..000000000 --- a/src/theory/arith/nl_solver.h +++ /dev/null @@ -1,368 +0,0 @@ -/********************* */ -/*! \file nl_solver.h - ** \verbatim - ** Top contributors (to current version): - ** Andrew Reynolds, Tim King - ** 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 Solver for standard non-linear constraints - **/ - -#ifndef CVC4__THEORY__ARITH__NL_SOLVER_H -#define CVC4__THEORY__ARITH__NL_SOLVER_H - -#include -#include -#include -#include - -#include "context/cdhashset.h" -#include "context/cdinsert_hashmap.h" -#include "context/cdlist.h" -#include "context/cdqueue.h" -#include "context/context.h" -#include "expr/kind.h" -#include "expr/node.h" -#include "theory/arith/nl_constraint.h" -#include "theory/arith/nl_lemma_utils.h" -#include "theory/arith/nl_model.h" -#include "theory/arith/nl_monomial.h" -#include "theory/arith/theory_arith.h" - -namespace CVC4 { -namespace theory { -namespace arith { - -typedef std::map NodeMultiset; - -/** Non-linear solver class - * - * This class implements model-based refinement schemes - * for non-linear arithmetic, described in: - * - * - "Invariant Checking of NRA Transition Systems - * via Incremental Reduction to LRA with EUF" by - * Cimatti et al., TACAS 2017. - * - * - Section 5 of "Desiging Theory Solvers with - * Extensions" by Reynolds et al., FroCoS 2017. - */ -class NlSolver -{ - typedef std::map MonomialExponentMap; - typedef context::CDHashSet NodeSet; - - public: - NlSolver(TheoryArith& containing, NlModel& model); - ~NlSolver(); - - /** init last call - * - * This is called at the beginning of last call effort check, where - * assertions are the set of assertions belonging to arithmetic, - * false_asserts is the subset of assertions that are false in the current - * model, and xts is the set of extended function terms that are active in - * the current context. - */ - void initLastCall(const std::vector& assertions, - const std::vector& false_asserts, - const std::vector& xts); - //-------------------------------------------- lemma schemas - /** check split zero - * - * Returns a set of theory lemmas of the form - * t = 0 V t != 0 - * where t is a term that exists in the current context. - */ - std::vector checkSplitZero(); - - /** check monomial sign - * - * Returns a set of valid theory lemmas, based on a - * lemma schema which ensures that non-linear monomials - * respect sign information based on their facts. - * For more details, see Section 5 of "Design Theory - * Solvers with Extensions" by Reynolds et al., FroCoS 2017, - * Figure 5, this is the schema "Sign". - * - * Examples: - * - * x > 0 ^ y > 0 => x*y > 0 - * x < 0 => x*y*y < 0 - * x = 0 => x*y*z = 0 - */ - std::vector checkMonomialSign(); - - /** check monomial magnitude - * - * Returns a set of valid theory lemmas, based on a - * lemma schema which ensures that comparisons between - * non-linear monomials respect the magnitude of their - * factors. - * For more details, see Section 5 of "Design Theory - * Solvers with Extensions" by Reynolds et al., FroCoS 2017, - * Figure 5, this is the schema "Magnitude". - * - * Examples: - * - * |x|>|y| => |x*z|>|y*z| - * |x|>|y| ^ |z|>|w| ^ |x|>=1 => |x*x*z*u|>|y*w| - * - * Argument c indicates the class of inferences to perform for the - * (non-linear) monomials in the vector d_ms. 0 : compare non-linear monomials - * against 1, 1 : compare non-linear monomials against variables, 2 : compare - * non-linear monomials against other non-linear monomials. - */ - std::vector checkMonomialMagnitude(unsigned c); - - /** check monomial inferred bounds - * - * Returns a set of valid theory lemmas, based on a - * lemma schema that infers new constraints about existing - * terms based on mulitplying both sides of an existing - * constraint by a term. - * For more details, see Section 5 of "Design Theory - * Solvers with Extensions" by Reynolds et al., FroCoS 2017, - * Figure 5, this is the schema "Multiply". - * - * Examples: - * - * x > 0 ^ (y > z + w) => x*y > x*(z+w) - * x < 0 ^ (y > z + w) => x*y < x*(z+w) - * ...where (y > z + w) and x*y are a constraint and term - * that occur in the current context. - */ - std::vector checkMonomialInferBounds( - std::vector& nt_lemmas, - const std::vector& asserts, - const std::vector& false_asserts); - - /** check factoring - * - * Returns a set of valid theory lemmas, based on a - * lemma schema that states a relationship betwen monomials - * with common factors that occur in the same constraint. - * - * Examples: - * - * x*z+y*z > t => ( k = x + y ^ k*z > t ) - * ...where k is fresh and x*z + y*z > t is a - * constraint that occurs in the current context. - */ - std::vector checkFactoring(const std::vector& asserts, - const std::vector& false_asserts); - - /** check monomial infer resolution bounds - * - * Returns a set of valid theory lemmas, based on a - * lemma schema which "resolves" upper bounds - * of one inequality with lower bounds for another. - * This schema is not enabled by default, and can - * be enabled by --nl-ext-rbound. - * - * Examples: - * - * ( y>=0 ^ s <= x*z ^ x*y <= t ) => y*s <= z*t - * ...where s <= x*z and x*y <= t are constraints - * that occur in the current context. - */ - std::vector checkMonomialInferResBounds(); - - /** check tangent planes - * - * Returns a set of valid theory lemmas, based on an - * "incremental linearization" of non-linear monomials. - * This linearization is accomplished by adding constraints - * corresponding to "tangent planes" at the current - * model value of each non-linear monomial. In particular - * consider the definition for constants a,b : - * T_{a,b}( x*y ) = b*x + a*y - a*b. - * The lemmas added by this function are of the form : - * ( ( x>a ^ yb) ) => x*y < T_{a,b}( x*y ) - * ( ( x>a ^ y>b) ^ (x x*y > T_{a,b}( x*y ) - * It is inspired by "Invariant Checking of NRA Transition - * Systems via Incremental Reduction to LRA with EUF" by - * Cimatti et al., TACAS 2017. - * This schema is not terminating in general. - * It is not enabled by default, and can - * be enabled by --nl-ext-tplanes. - * - * Examples: - * - * ( ( x>2 ^ y>5) ^ (x<2 ^ y<5) ) => x*y > 5*x + 2*y - 10 - * ( ( x>2 ^ y<5) ^ (x<2 ^ y>5) ) => x*y < 5*x + 2*y - 10 - */ - std::vector checkTangentPlanes(); - - //-------------------------------------------- end lemma schemas - private: - // The theory of arithmetic containing this extension. - TheoryArith& d_containing; - /** 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_two; - Node d_true; - Node d_false; - /** Context-independent database of monomial information */ - MonomialDb d_mdb; - /** Context-independent database of constraint information */ - ConstraintDb d_cdb; - - // ( x*y, x*z, y ) for each pair of monomials ( x*y, x*z ) with common factors - std::map > d_mono_diff; - - /** cache of terms t for which we have added the lemma ( t = 0 V t != 0 ). */ - NodeSet d_zero_split; - - // ordering, stores variables and 0,1,-1 - std::map d_order_vars; - std::vector d_order_points; - - // information about monomials - std::vector d_ms; - std::vector d_ms_vars; - std::map d_ms_proc; - std::vector d_mterms; - - // list of monomials with factors whose model value is non-constant in model - // e.g. y*cos( x ) - std::map d_m_nconst_factor; - /** the set of monomials we should apply tangent planes to */ - std::unordered_set d_tplane_refine; - /** maps nodes to their factor skolems */ - std::map d_factor_skolem; - /** tangent plane bounds */ - std::map > d_tangent_val_bound[4]; - // term -> coeff -> rhs -> ( status, exp, b ), - // where we have that : exp => ( coeff * term rhs ) - // b is true if degree( term ) >= degree( rhs ) - std::map > > d_ci; - std::map > > d_ci_exp; - std::map > > d_ci_max; - - /** Make literal */ - static Node mkLit(Node a, Node b, int status, bool isAbsolute = false); - /** register monomial */ - void setMonomialFactor(Node a, Node b, const NodeMultiset& common); - /** assign order ids */ - void assignOrderIds(std::vector& vars, - NodeMultiset& d_order, - bool isConcrete, - bool isAbsolute); - - /** Check whether we have already inferred a relationship between monomials - * x and y based on the information in cmp_infers. This computes the - * transitive closure of the relation stored in cmp_infers. - */ - bool cmp_holds(Node x, - Node y, - std::map >& cmp_infers, - std::vector& exp, - std::map& visited); - /** In the following functions, status states a relationship - * between two arithmetic terms, where: - * 0 : equal - * 1 : greater than or equal - * 2 : greater than - * -X : (greater -> less) - * TODO (#1287) make this an enum? - */ - /** compute the sign of a. - * - * Calls to this function are such that : - * exp => ( oa = a ^ a 0 ) - * - * This function iterates over the factors of a, - * where a_index is the index of the factor in a - * we are currently looking at. - * - * This function returns a status, which indicates - * a's relationship to 0. - * We add lemmas to lem of the form given by the - * lemma schema checkSign(...). - */ - int compareSign(Node oa, - Node a, - unsigned a_index, - int status, - std::vector& exp, - std::vector& lem); - /** compare monomials a and b - * - * Initially, a call to this function is such that : - * exp => ( oa = a ^ ob = b ) - * - * This function returns true if we can infer a valid - * arithmetic lemma of the form : - * P => abs( a ) >= abs( b ) - * where P is true and abs( a ) >= abs( b ) is false in the - * current model. - * - * This function is implemented by "processing" factors - * of monomials a and b until an inference of the above - * form can be made. For example, if : - * a = x*x*y and b = z*w - * Assuming we are trying to show abs( a ) >= abs( c ), - * then if abs( M( x ) ) >= abs( M( z ) ) where M is the current model, - * then we can add abs( x ) >= abs( z ) to our explanation, and - * mark one factor of x as processed in a, and - * one factor of z as processed in b. The number of processed factors of a - * and b are stored in a_exp_proc and b_exp_proc respectively. - * - * cmp_infers stores information that is helpful - * in discarding redundant inferences. For example, - * we do not want to infer abs( x ) >= abs( z ) if - * we have already inferred abs( x ) >= abs( y ) and - * abs( y ) >= abs( z ). - * It stores entries of the form (status,t1,t2)->F, - * which indicates that we constructed a lemma F that - * showed t1 t2. - * - * We add lemmas to lem of the form given by the - * lemma schema checkMagnitude(...). - */ - bool compareMonomial( - Node oa, - Node a, - NodeMultiset& a_exp_proc, - Node ob, - Node b, - NodeMultiset& b_exp_proc, - std::vector& exp, - std::vector& lem, - std::map > >& cmp_infers); - /** helper function for above - * - * The difference is the inputs a_index and b_index, which are the indices of - * children (factors) in monomials a and b which we are currently looking at. - */ - bool compareMonomial( - Node oa, - Node a, - unsigned a_index, - NodeMultiset& a_exp_proc, - Node ob, - Node b, - unsigned b_index, - NodeMultiset& b_exp_proc, - int status, - std::vector& exp, - std::vector& lem, - std::map > >& cmp_infers); - /** Get factor skolem for n, add resulting lemmas to lemmas */ - Node getFactorSkolem(Node n, std::vector& lemmas); -}; /* class NlSolver */ - -} // namespace arith -} // namespace theory -} // namespace CVC4 - -#endif /* CVC4__THEORY__ARITH__NL_SOLVER_H */ diff --git a/src/theory/arith/nonlinear_extension.cpp b/src/theory/arith/nonlinear_extension.cpp deleted file mode 100644 index b638d8a59..000000000 --- a/src/theory/arith/nonlinear_extension.cpp +++ /dev/null @@ -1,815 +0,0 @@ -/********************* */ -/*! \file nonlinear_extension.cpp - ** \verbatim - ** Top contributors (to current version): - ** Andrew Reynolds, Tim King, Aina Niemetz - ** 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 "theory/arith/nonlinear_extension.h" - -#include "options/arith_options.h" -#include "theory/arith/arith_utilities.h" -#include "theory/arith/theory_arith.h" -#include "theory/ext_theory.h" -#include "theory/theory_model.h" - -using namespace CVC4::kind; - -namespace CVC4 { -namespace theory { -namespace arith { - -NonlinearExtension::NonlinearExtension(TheoryArith& containing, - eq::EqualityEngine* ee) - : d_lemmas(containing.getUserContext()), - d_containing(containing), - d_ee(ee), - d_needsLastCall(false), - d_model(containing.getSatContext()), - d_trSlv(d_model), - d_nlSlv(containing, d_model), - d_builtModel(containing.getSatContext(), false) -{ - d_true = NodeManager::currentNM()->mkConst(true); - d_zero = NodeManager::currentNM()->mkConst(Rational(0)); - d_one = NodeManager::currentNM()->mkConst(Rational(1)); - d_neg_one = NodeManager::currentNM()->mkConst(Rational(-1)); -} - -NonlinearExtension::~NonlinearExtension() {} - -bool NonlinearExtension::getCurrentSubstitution( - int effort, const std::vector& vars, std::vector& subs, - std::map >& exp) { - // get the constant equivalence classes - std::map > rep_to_subs_index; - - bool retVal = false; - for (unsigned i = 0; i < vars.size(); i++) { - Node n = vars[i]; - if (d_ee->hasTerm(n)) { - Node nr = d_ee->getRepresentative(n); - if (nr.isConst()) { - subs.push_back(nr); - Trace("nl-subs") << "Basic substitution : " << n << " -> " << nr - << std::endl; - exp[n].push_back(n.eqNode(nr)); - retVal = true; - } else { - rep_to_subs_index[nr].push_back(i); - subs.push_back(n); - } - } else { - subs.push_back(n); - } - } - - // return true if the substitution is non-trivial - return retVal; -} - -std::pair NonlinearExtension::isExtfReduced( - int effort, Node n, Node on, const std::vector& exp) const { - if (n != d_zero) { - Kind k = n.getKind(); - return std::make_pair(k != NONLINEAR_MULT && !isTranscendentalKind(k), - Node::null()); - } - Assert(n == d_zero); - if (on.getKind() == NONLINEAR_MULT) - { - Trace("nl-ext-zero-exp") << "Infer zero : " << on << " == " << n - << std::endl; - // minimize explanation if a substitution+rewrite results in zero - const std::set vars(on.begin(), on.end()); - - for (unsigned i = 0, size = exp.size(); i < size; i++) - { - Trace("nl-ext-zero-exp") << " exp[" << i << "] = " << exp[i] - << std::endl; - std::vector eqs; - if (exp[i].getKind() == EQUAL) - { - eqs.push_back(exp[i]); - } - else if (exp[i].getKind() == AND) - { - for (const Node& ec : exp[i]) - { - if (ec.getKind() == EQUAL) - { - eqs.push_back(ec); - } - } - } - - for (unsigned j = 0; j < eqs.size(); j++) - { - for (unsigned r = 0; r < 2; r++) - { - if (eqs[j][r] == d_zero && vars.find(eqs[j][1 - r]) != vars.end()) - { - Trace("nl-ext-zero-exp") << "...single exp : " << eqs[j] - << std::endl; - return std::make_pair(true, eqs[j]); - } - } - } - } - } - return std::make_pair(true, Node::null()); -} - -void NonlinearExtension::sendLemmas(const std::vector& out, - bool preprocess, - std::map& lemSE) -{ - std::map::iterator its; - for (const Node& lem : out) - { - Trace("nl-ext-lemma") << "NonlinearExtension::Lemma : " << lem << std::endl; - d_containing.getOutputChannel().lemma(lem, false, preprocess); - // process the side effect - its = lemSE.find(lem); - if (its != lemSE.end()) - { - processSideEffect(its->second); - } - // add to cache if not preprocess - if (!preprocess) - { - d_lemmas.insert(lem); - } - // also indicate this is a tautology - d_model.addTautology(lem); - } -} - -void NonlinearExtension::processSideEffect(const NlLemmaSideEffect& se) -{ - d_trSlv.processSideEffect(se); -} - -unsigned NonlinearExtension::filterLemma(Node lem, std::vector& out) -{ - Trace("nl-ext-lemma-debug") - << "NonlinearExtension::Lemma pre-rewrite : " << lem << std::endl; - lem = Rewriter::rewrite(lem); - if (d_lemmas.find(lem) != d_lemmas.end() - || std::find(out.begin(), out.end(), lem) != out.end()) - { - Trace("nl-ext-lemma-debug") - << "NonlinearExtension::Lemma duplicate : " << lem << std::endl; - return 0; - } - out.push_back(lem); - return 1; -} - -unsigned NonlinearExtension::filterLemmas(std::vector& lemmas, - std::vector& out) -{ - if (options::nlExtEntailConflicts()) - { - // check if any are entailed to be false - for (const Node& lem : lemmas) - { - Node ch_lemma = lem.negate(); - ch_lemma = Rewriter::rewrite(ch_lemma); - Trace("nl-ext-et-debug") - << "Check entailment of " << ch_lemma << "..." << std::endl; - std::pair et = d_containing.getValuation().entailmentCheck( - options::TheoryOfMode::THEORY_OF_TYPE_BASED, ch_lemma); - Trace("nl-ext-et-debug") << "entailment test result : " << et.first << " " - << et.second << std::endl; - if (et.first) - { - Trace("nl-ext-et") << "*** Lemma entailed to be in conflict : " << lem - << std::endl; - // return just this lemma - if (filterLemma(lem, out) > 0) - { - lemmas.clear(); - return 1; - } - } - } - } - - unsigned sum = 0; - for (const Node& lem : lemmas) - { - sum += filterLemma(lem, out); - } - lemmas.clear(); - return sum; -} - -void NonlinearExtension::getAssertions(std::vector& assertions) -{ - Trace("nl-ext") << "Getting assertions..." << std::endl; - NodeManager* nm = NodeManager::currentNM(); - // get the assertions - std::map init_bounds[2]; - std::map init_bounds_lit[2]; - unsigned nassertions = 0; - std::unordered_set init_assertions; - for (Theory::assertions_iterator it = d_containing.facts_begin(); - it != d_containing.facts_end(); - ++it) - { - nassertions++; - const Assertion& assertion = *it; - Node lit = assertion.d_assertion; - init_assertions.insert(lit); - // check for concrete bounds - bool pol = lit.getKind() != NOT; - Node atom_orig = lit.getKind() == NOT ? lit[0] : lit; - - std::vector atoms; - if (atom_orig.getKind() == EQUAL) - { - if (pol) - { - // t = s is ( t >= s ^ t <= s ) - for (unsigned i = 0; i < 2; i++) - { - Node atom_new = nm->mkNode(GEQ, atom_orig[i], atom_orig[1 - i]); - atom_new = Rewriter::rewrite(atom_new); - atoms.push_back(atom_new); - } - } - } - else - { - atoms.push_back(atom_orig); - } - - for (const Node& atom : atoms) - { - // non-strict bounds only - if (atom.getKind() == GEQ || (!pol && atom.getKind() == GT)) - { - Node p = atom[0]; - Assert(atom[1].isConst()); - Rational bound = atom[1].getConst(); - if (!pol) - { - if (atom[0].getType().isInteger()) - { - // ~( p >= c ) ---> ( p <= c-1 ) - bound = bound - Rational(1); - } - } - unsigned bindex = pol ? 0 : 1; - bool setBound = true; - std::map::iterator itb = init_bounds[bindex].find(p); - if (itb != init_bounds[bindex].end()) - { - if (itb->second == bound) - { - setBound = atom_orig.getKind() == EQUAL; - } - else - { - setBound = pol ? itb->second < bound : itb->second > bound; - } - if (setBound) - { - // the bound is subsumed - init_assertions.erase(init_bounds_lit[bindex][p]); - } - } - if (setBound) - { - Trace("nl-ext-init") << (pol ? "Lower" : "Upper") << " bound for " - << p << " : " << bound << std::endl; - init_bounds[bindex][p] = bound; - init_bounds_lit[bindex][p] = lit; - } - } - } - } - // for each bound that is the same, ensure we've inferred the equality - for (std::pair& ib : init_bounds[0]) - { - Node p = ib.first; - Node lit1 = init_bounds_lit[0][p]; - if (lit1.getKind() != EQUAL) - { - std::map::iterator itb = init_bounds[1].find(p); - if (itb != init_bounds[1].end()) - { - if (ib.second == itb->second) - { - Node eq = p.eqNode(nm->mkConst(ib.second)); - eq = Rewriter::rewrite(eq); - Node lit2 = init_bounds_lit[1][p]; - Assert(lit2.getKind() != EQUAL); - // use the equality instead, thus these are redundant - init_assertions.erase(lit1); - init_assertions.erase(lit2); - init_assertions.insert(eq); - } - } - } - } - - for (const Node& a : init_assertions) - { - assertions.push_back(a); - } - Trace("nl-ext") << "...keep " << assertions.size() << " / " << nassertions - << " assertions." << std::endl; -} - -std::vector NonlinearExtension::checkModelEval( - const std::vector& assertions) -{ - std::vector false_asserts; - for (size_t i = 0; i < assertions.size(); ++i) { - Node lit = assertions[i]; - Node atom = lit.getKind()==NOT ? lit[0] : lit; - Node litv = d_model.computeConcreteModelValue(lit); - Trace("nl-ext-mv-assert") << "M[[ " << lit << " ]] -> " << litv; - if (litv != d_true) - { - Trace("nl-ext-mv-assert") << " [model-false]" << std::endl; - false_asserts.push_back(lit); - } - else - { - Trace("nl-ext-mv-assert") << std::endl; - } - } - return false_asserts; -} - -bool NonlinearExtension::checkModel(const std::vector& assertions, - const std::vector& false_asserts, - std::vector& lemmas, - std::vector& gs) -{ - Trace("nl-ext-cm") << "--- check-model ---" << std::endl; - - // get the presubstitution - Trace("nl-ext-cm-debug") << " apply pre-substitution..." << std::endl; - std::vector passertions = assertions; - - // preprocess the assertions with the trancendental solver - if (!d_trSlv.preprocessAssertionsCheckModel(passertions)) - { - return false; - } - - Trace("nl-ext-cm") << "-----" << std::endl; - unsigned tdegree = d_trSlv.getTaylorDegree(); - bool ret = - d_model.checkModel(passertions, false_asserts, tdegree, lemmas, gs); - return ret; -} - -int NonlinearExtension::checkLastCall(const std::vector& assertions, - const std::vector& false_asserts, - const std::vector& xts, - std::vector& lems, - std::vector& lemsPp, - std::vector& wlems, - std::map& lemSE) -{ - // initialize the non-linear solver - d_nlSlv.initLastCall(assertions, false_asserts, xts); - // 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() || !lemsPp.empty()) - { - Trace("nl-ext") << " ...finished with " << lems.size() - << " new lemmas during registration." << std::endl; - return lems.size() + lemsPp.size(); - } - - //----------------------------------- possibly split on zero - if (options::nlExtSplitZero()) { - Trace("nl-ext") << "Get zero split lemmas..." << std::endl; - lemmas = d_nlSlv.checkSplitZero(); - filterLemmas(lemmas, lems); - if (!lems.empty()) - { - Trace("nl-ext") << " ...finished with " << lems.size() << " new lemmas." - << std::endl; - return lems.size(); - } - } - - //-----------------------------------initial lemmas for transcendental functions - lemmas = d_trSlv.checkTranscendentalInitialRefine(); - filterLemmas(lemmas, lems); - if (!lems.empty()) - { - Trace("nl-ext") << " ...finished with " << lems.size() << " new lemmas." - << std::endl; - return lems.size(); - } - - //-----------------------------------lemmas based on sign (comparison to zero) - lemmas = d_nlSlv.checkMonomialSign(); - filterLemmas(lemmas, lems); - if (!lems.empty()) - { - Trace("nl-ext") << " ...finished with " << lems.size() << " new lemmas." - << std::endl; - return lems.size(); - } - - //-----------------------------------monotonicity of transdental functions - lemmas = d_trSlv.checkTranscendentalMonotonic(); - filterLemmas(lemmas, lems); - if (!lems.empty()) - { - Trace("nl-ext") << " ...finished with " << lems.size() << " new lemmas." - << std::endl; - return lems.size(); - } - - //-----------------------------------lemmas based on magnitude of non-zero monomials - for (unsigned c = 0; c < 3; c++) { - // c is effort level - lemmas = d_nlSlv.checkMonomialMagnitude(c); - unsigned nlem = lemmas.size(); - filterLemmas(lemmas, lems); - if (!lems.empty()) - { - Trace("nl-ext") << " ...finished with " << lems.size() - << " new lemmas (out of possible " << nlem << ")." - << std::endl; - return lems.size(); - } - } - - //-----------------------------------inferred bounds lemmas - // e.g. x >= t => y*x >= y*t - std::vector< Node > nt_lemmas; - lemmas = - d_nlSlv.checkMonomialInferBounds(nt_lemmas, assertions, false_asserts); - // Trace("nl-ext") << "Bound lemmas : " << lemmas.size() << ", " << - // nt_lemmas.size() << std::endl; prioritize lemmas that do not - // introduce new monomials - filterLemmas(lemmas, lems); - - if (options::nlExtTangentPlanes() && options::nlExtTangentPlanesInterleave()) - { - lemmas = d_nlSlv.checkTangentPlanes(); - filterLemmas(lemmas, lems); - } - - if (!lems.empty()) - { - Trace("nl-ext") << " ...finished with " << lems.size() << " new lemmas." - << std::endl; - return lems.size(); - } - - // from inferred bound inferences : now do ones that introduce new terms - filterLemmas(nt_lemmas, lems); - if (!lems.empty()) - { - Trace("nl-ext") << " ...finished with " << lems.size() - << " new (monomial-introducing) lemmas." << std::endl; - return lems.size(); - } - - //------------------------------------factoring lemmas - // x*y + x*z >= t => exists k. k = y + z ^ x*k >= t - if( options::nlExtFactor() ){ - lemmas = d_nlSlv.checkFactoring(assertions, false_asserts); - filterLemmas(lemmas, lems); - if (!lems.empty()) - { - Trace("nl-ext") << " ...finished with " << lems.size() << " new lemmas." - << std::endl; - return lems.size(); - } - } - - //------------------------------------resolution bound inferences - // e.g. ( y>=0 ^ s <= x*z ^ x*y <= t ) => y*s <= z*t - if (options::nlExtResBound()) { - lemmas = d_nlSlv.checkMonomialInferResBounds(); - filterLemmas(lemmas, lems); - if (!lems.empty()) - { - Trace("nl-ext") << " ...finished with " << lems.size() << " new lemmas." - << std::endl; - return lems.size(); - } - } - - //------------------------------------tangent planes - if (options::nlExtTangentPlanes() && !options::nlExtTangentPlanesInterleave()) - { - lemmas = d_nlSlv.checkTangentPlanes(); - filterLemmas(lemmas, wlems); - } - if (options::nlExtTfTangentPlanes()) - { - lemmas = d_trSlv.checkTranscendentalTangentPlanes(lemSE); - filterLemmas(lemmas, wlems); - } - Trace("nl-ext") << " ...finished with " << wlems.size() << " waiting lemmas." - << std::endl; - - return 0; -} - -void NonlinearExtension::check(Theory::Effort e) { - Trace("nl-ext") << std::endl; - Trace("nl-ext") << "NonlinearExtension::check, effort = " << e - << ", built model = " << d_builtModel.get() << std::endl; - if (e == Theory::EFFORT_FULL) - { - d_containing.getExtTheory()->clearCache(); - d_needsLastCall = true; - if (options::nlExtRewrites()) { - std::vector nred; - if (!d_containing.getExtTheory()->doInferences(0, nred)) { - Trace("nl-ext") << "...sent no lemmas, # extf to reduce = " - << nred.size() << std::endl; - if (nred.empty()) { - d_needsLastCall = false; - } - } else { - Trace("nl-ext") << "...sent lemmas." << std::endl; - } - } - } - else - { - // If we computed lemmas during collectModelInfo, send them now. - if (!d_cmiLemmas.empty() || !d_cmiLemmasPp.empty()) - { - sendLemmas(d_cmiLemmas, false, d_cmiLemmasSE); - sendLemmas(d_cmiLemmasPp, true, d_cmiLemmasSE); - return; - } - // Otherwise, we will answer SAT. The values that we approximated are - // recorded as approximations here. - TheoryModel* tm = d_containing.getValuation().getModel(); - for (std::pair>& a : d_approximations) - { - if (a.second.second.isNull()) - { - tm->recordApproximation(a.first, a.second.first); - } - else - { - tm->recordApproximation(a.first, a.second.first, a.second.second); - } - } - } -} - -bool NonlinearExtension::modelBasedRefinement( - std::vector& mlems, - std::vector& mlemsPp, - std::map& lemSE) -{ - // get the assertions - std::vector assertions; - getAssertions(assertions); - - Trace("nl-ext-mv-assert") - << "Getting model values... check for [model-false]" << std::endl; - // get the assertions that are false in the model - const std::vector false_asserts = checkModelEval(assertions); - - // get the extended terms belonging to this theory - std::vector xts; - d_containing.getExtTheory()->getTerms(xts); - - if (Trace.isOn("nl-ext-debug")) - { - Trace("nl-ext-debug") << " processing NonlinearExtension::check : " - << std::endl; - Trace("nl-ext-debug") << " " << false_asserts.size() - << " false assertions" << std::endl; - Trace("nl-ext-debug") << " " << xts.size() - << " extended terms: " << std::endl; - Trace("nl-ext-debug") << " "; - for (unsigned j = 0; j < xts.size(); j++) - { - Trace("nl-ext-debug") << xts[j] << " "; - } - Trace("nl-ext-debug") << std::endl; - } - - // compute whether shared terms have correct values - unsigned num_shared_wrong_value = 0; - std::vector shared_term_value_splits; - // must ensure that shared terms are equal to their concrete value - Trace("nl-ext-mv") << "Shared terms : " << std::endl; - for (context::CDList::const_iterator its = - d_containing.shared_terms_begin(); - its != d_containing.shared_terms_end(); - ++its) - { - TNode shared_term = *its; - // compute its value in the model, and its evaluation in the model - Node stv0 = d_model.computeConcreteModelValue(shared_term); - Node stv1 = d_model.computeAbstractModelValue(shared_term); - d_model.printModelValue("nl-ext-mv", shared_term); - if (stv0 != stv1) - { - num_shared_wrong_value++; - Trace("nl-ext-mv") << "Bad shared term value : " << shared_term - << std::endl; - if (shared_term != stv0) - { - // split on the value, this is non-terminating in general, TODO : - // improve this - Node eq = shared_term.eqNode(stv0); - shared_term_value_splits.push_back(eq); - } - else - { - // this can happen for transcendental functions - // the problem is that we cannot evaluate transcendental functions - // (they don't have a rewriter that returns constants) - // thus, the actual value in their model can be themselves, hence we - // have no reference point to rule out the current model. In this - // case, we may set incomplete below. - } - } - } - Trace("nl-ext-debug") << " " << num_shared_wrong_value - << " shared terms with wrong model value." << std::endl; - bool needsRecheck; - do - { - d_model.resetCheck(); - needsRecheck = false; - // complete_status: - // 1 : we may answer SAT, -1 : we may not answer SAT, 0 : unknown - int complete_status = 1; - // lemmas that should be sent later - std::vector wlems; - // We require a check either if an assertion is false or a shared term has - // a wrong value - if (!false_asserts.empty() || num_shared_wrong_value > 0) - { - complete_status = num_shared_wrong_value > 0 ? -1 : 0; - checkLastCall( - assertions, false_asserts, xts, mlems, mlemsPp, wlems, lemSE); - if (!mlems.empty() || !mlemsPp.empty()) - { - return true; - } - } - Trace("nl-ext") << "Finished check with status : " << complete_status - << std::endl; - - // if we did not add a lemma during check and there is a chance for SAT - if (complete_status == 0) - { - Trace("nl-ext") - << "Check model based on bounds for irrational-valued functions..." - << std::endl; - // check the model based on simple solving of equalities and using - // error bounds on the Taylor approximation of transcendental functions. - std::vector lemmas; - std::vector gs; - if (checkModel(assertions, false_asserts, lemmas, gs)) - { - complete_status = 1; - } - for (const Node& mg : gs) - { - Node mgr = Rewriter::rewrite(mg); - mgr = d_containing.getValuation().ensureLiteral(mgr); - d_containing.getOutputChannel().requirePhase(mgr, true); - d_builtModel = true; - } - filterLemmas(lemmas, mlems); - if (!mlems.empty()) - { - return true; - } - } - - // if we have not concluded SAT - if (complete_status != 1) - { - // flush the waiting lemmas - if (!wlems.empty()) - { - mlems.insert(mlems.end(), wlems.begin(), wlems.end()); - Trace("nl-ext") << "...added " << wlems.size() << " waiting lemmas." - << std::endl; - return true; - } - // resort to splitting on shared terms with their model value - // if we did not add any lemmas - if (num_shared_wrong_value > 0) - { - complete_status = -1; - if (!shared_term_value_splits.empty()) - { - std::vector stvLemmas; - for (const Node& eq : shared_term_value_splits) - { - Node req = Rewriter::rewrite(eq); - Node literal = d_containing.getValuation().ensureLiteral(req); - d_containing.getOutputChannel().requirePhase(literal, true); - Trace("nl-ext-debug") << "Split on : " << literal << std::endl; - Node split = literal.orNode(literal.negate()); - filterLemma(split, stvLemmas); - } - if (!stvLemmas.empty()) - { - mlems.insert(mlems.end(), stvLemmas.begin(), stvLemmas.end()); - Trace("nl-ext") << "...added " << stvLemmas.size() - << " shared term value split lemmas." << std::endl; - return true; - } - } - else - { - // this can happen if we are trying to do theory combination with - // trancendental functions - // since their model value cannot even be computed exactly - } - } - - // we are incomplete - if (options::nlExtIncPrecision() && d_model.usedApproximate()) - { - 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_trSlv.getTaylorDegree() << std::endl; - } - else - { - Trace("nl-ext") << "...failed to send lemma in " - "NonLinearExtension, set incomplete" - << std::endl; - d_containing.getOutputChannel().setIncomplete(); - } - } - } while (needsRecheck); - - // did not add lemmas - return false; -} - -void NonlinearExtension::interceptModel(std::map& arithModel) -{ - if (!needsCheckLastEffort()) - { - // no non-linear constraints, we are done - return; - } - Trace("nl-ext") << "NonlinearExtension::interceptModel begin" << std::endl; - d_model.reset(d_containing.getValuation().getModel(), arithModel); - // run a last call effort check - d_cmiLemmas.clear(); - d_cmiLemmasPp.clear(); - d_cmiLemmasSE.clear(); - if (!d_builtModel.get()) - { - Trace("nl-ext") << "interceptModel: do model-based refinement" << std::endl; - modelBasedRefinement(d_cmiLemmas, d_cmiLemmasPp, d_cmiLemmasSE); - } - if (d_builtModel.get()) - { - Trace("nl-ext") << "interceptModel: do model repair" << std::endl; - d_approximations.clear(); - // modify the model values - d_model.getModelValueRepair(arithModel, d_approximations); - } -} - -void NonlinearExtension::presolve() -{ - Trace("nl-ext") << "NonlinearExtension::presolve" << std::endl; -} - - -} // namespace arith -} // namespace theory -} // namespace CVC4 diff --git a/src/theory/arith/nonlinear_extension.h b/src/theory/arith/nonlinear_extension.h deleted file mode 100644 index 5aa2070a6..000000000 --- a/src/theory/arith/nonlinear_extension.h +++ /dev/null @@ -1,335 +0,0 @@ -/********************* */ -/*! \file nonlinear_extension.h - ** \verbatim - ** Top contributors (to current version): - ** Andrew Reynolds, Tim King - ** 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 Extensions for incomplete handling of nonlinear multiplication. - ** - ** Extensions to the theory of arithmetic incomplete handling of nonlinear - ** multiplication via axiom instantiations. - **/ - -#ifndef CVC4__THEORY__ARITH__NONLINEAR_EXTENSION_H -#define CVC4__THEORY__ARITH__NONLINEAR_EXTENSION_H - -#include -#include -#include - -#include "context/cdlist.h" -#include "expr/kind.h" -#include "expr/node.h" -#include "theory/arith/nl_lemma_utils.h" -#include "theory/arith/nl_model.h" -#include "theory/arith/nl_solver.h" -#include "theory/arith/theory_arith.h" -#include "theory/arith/transcendental_solver.h" -#include "theory/uf/equality_engine.h" - -namespace CVC4 { -namespace theory { -namespace arith { - -/** Non-linear extension class - * - * This class implements model-based refinement schemes - * for non-linear arithmetic, described in: - * - * - "Invariant Checking of NRA Transition Systems - * via Incremental Reduction to LRA with EUF" by - * Cimatti et al., TACAS 2017. - * - * - Section 5 of "Desiging Theory Solvers with - * Extensions" by Reynolds et al., FroCoS 2017. - * - * - "Satisfiability Modulo Transcendental - * Functions via Incremental Linearization" by Cimatti - * et al., CADE 2017. - * - * It's main functionality is a check(...) method, - * which is called by TheoryArithPrivate either: - * (1) at full effort with no conflicts or lemmas emitted, or - * (2) at last call effort. - * In this method, this class calls d_out->lemma(...) - * for valid arithmetic theory lemmas, based on the current set of assertions, - * where d_out is the output channel of TheoryArith. - */ -class NonlinearExtension { - typedef context::CDHashSet NodeSet; - - public: - NonlinearExtension(TheoryArith& containing, eq::EqualityEngine* ee); - ~NonlinearExtension(); - /** Get current substitution - * - * This function and the one below are - * used for context-dependent - * simplification, see Section 3.1 of - * "Designing Theory Solvers with Extensions" - * by Reynolds et al. FroCoS 2017. - * - * effort : an identifier indicating the stage where - * we are performing context-dependent simplification, - * vars : a set of arithmetic variables. - * - * This function populates subs and exp, such that for 0 <= i < vars.size(): - * ( exp[vars[i]] ) => vars[i] = subs[i] - * where exp[vars[i]] is a set of assertions - * that hold in the current context. We call { vars -> subs } a "derivable - * substituion" (see Reynolds et al. FroCoS 2017). - */ - bool getCurrentSubstitution(int effort, const std::vector& vars, - std::vector& subs, - std::map >& exp); - /** Is the term n in reduced form? - * - * Used for context-dependent simplification. - * - * effort : an identifier indicating the stage where - * we are performing context-dependent simplification, - * on : the original term that we reduced to n, - * exp : an explanation such that ( exp => on = n ). - * - * We return a pair ( b, exp' ) such that - * if b is true, then: - * n is in reduced form - * if exp' is non-null, then ( exp' => on = n ) - * The second part of the pair is used for constructing - * minimal explanations for context-dependent simplifications. - */ - std::pair isExtfReduced(int effort, Node n, Node on, - const std::vector& exp) const; - /** Check at effort level e. - * - * This call may result in (possibly multiple) calls to d_out->lemma(...) - * where d_out is the output channel of TheoryArith. - * - * If e is FULL, then we add lemmas based on context-depedent - * simplification (see Reynolds et al FroCoS 2017). - * - * If e is LAST_CALL, we add lemmas based on model-based refinement - * (see additionally Cimatti et al., TACAS 2017). The lemmas added at this - * effort may be computed during a call to interceptModel as described below. - */ - void check(Theory::Effort e); - /** intercept model - * - * This method is called during TheoryArith::collectModelInfo, which is - * invoked after the linear arithmetic solver passes a full effort check - * with no lemmas. - * - * The argument arithModel is a map of the form { v1 -> c1, ..., vn -> cn } - * which represents the linear arithmetic theory solver's contribution to the - * current candidate model. That is, its collectModelInfo method is requesting - * that equalities v1 = c1, ..., vn = cn be added to the current model, where - * v1, ..., vn are arithmetic variables and c1, ..., cn are constants. Notice - * arithmetic variables may be real-valued terms belonging to other theories, - * or abstractions of applications of multiplication (kind NONLINEAR_MULT). - * - * This method requests that the non-linear solver inspect this model and - * do any number of the following: - * (1) Construct lemmas based on a model-based refinement procedure inspired - * by Cimatti et al., TACAS 2017., - * (2) In the case that the nonlinear solver finds that the current - * constraints are satisfiable, it may "repair" the values in the argument - * arithModel so that it satisfies certain nonlinear constraints. This may - * involve e.g. solving for variables in nonlinear equations. - * - * Notice that in the former case, the lemmas it constructs are not sent out - * immediately. Instead, they are put in temporary vectors d_cmiLemmas - * and d_cmiLemmasPp, which are then sent out (if necessary) when a last call - * effort check is issued to this class. - */ - void interceptModel(std::map& arithModel); - /** Does this class need a call to check(...) at last call effort? */ - bool needsCheckLastEffort() const { return d_needsLastCall; } - /** presolve - * - * This function is called during TheoryArith's presolve command. - * In this function, we send lemmas we accumulated during preprocessing, - * for instance, definitional lemmas from expandDefinitions are sent out - * on the output channel of TheoryArith in this function. - */ - void presolve(); - private: - /** Model-based refinement - * - * This is the main entry point of this class for generating lemmas on the - * output channel of the theory of arithmetic. - * - * It is currently run at last call effort. It applies lemma schemas - * described in Reynolds et al. FroCoS 2017 that are based on ruling out - * the current candidate model. - * - * This function returns true if a lemma was added to the vector lems/lemsPp. - * Otherwise, it returns false. In the latter case, the model object d_model - * may have information regarding how to construct a model, in the case that - * we determined the problem is satisfiable. - * - * The argument lemSE is the "side effect" of the lemmas in mlems and mlemsPp - * (for details, see checkLastCall). - */ - bool modelBasedRefinement(std::vector& mlems, - std::vector& mlemsPp, - std::map& lemSE); - - - /** check last call - * - * Check assertions for consistency in the effort LAST_CALL with a subset of - * the assertions, false_asserts, that evaluate to false in the current model. - * - * xts : the list of (non-reduced) extended terms in the current context. - * - * This method adds lemmas to arguments lems, lemsPp, and wlems, each of - * which are intended to be sent out on the output channel of TheoryArith - * under certain conditions. - * - * If the set lems or lemsPp is non-empty, then no further processing is - * necessary. The last call effort check should terminate and these - * lemmas should be sent. The set lemsPp is distinguished from lems since - * the preprocess flag on the lemma(...) call should be set to true. - * - * The "waiting" lemmas wlems contain lemmas that should be sent on the - * output channel as a last resort. In other words, only if we are not - * able to establish SAT via a call to checkModel(...) should wlems be - * considered. This set typically contains tangent plane lemmas. - * - * The argument lemSE is the "side effect" of the lemmas from the previous - * three calls. If a lemma is mapping to a side effect, it should be - * processed via a call to processSideEffect(...) immediately after the - * lemma is sent (if it is indeed sent on this call to check). - */ - int checkLastCall(const std::vector& assertions, - const std::vector& false_asserts, - const std::vector& xts, - std::vector& lems, - std::vector& lemsPp, - std::vector& wlems, - std::map& lemSE); - - /** get assertions - * - * Let M be the set of assertions known by THEORY_ARITH. This function adds a - * set of literals M' to assertions such that M' and M are equivalent. - * - * Examples of how M' differs with M: - * (1) M' may not include t < c (in M) if t < c' is in M' for c' < c, where - * c and c' are constants, - * (2) M' may contain t = c if both t >= c and t <= c are in M. - */ - void getAssertions(std::vector& assertions); - /** check model - * - * Returns the subset of assertions whose concrete values we cannot show are - * true in the current model. Notice that we typically cannot compute concrete - * values for assertions involving transcendental functions. Any assertion - * whose model value cannot be computed is included in the return value of - * this function. - */ - std::vector checkModelEval(const std::vector& assertions); - - //---------------------------check model - /** Check model - * - * Checks the current model based on solving for equalities, and using error - * bounds on the Taylor approximation. - * - * If this function returns true, then all assertions in the input argument - * "assertions" are satisfied for all interpretations of variables within - * their computed bounds (as stored in d_check_model_bounds). - * - * For details, see Section 3 of Cimatti et al CADE 2017 under the heading - * "Detecting Satisfiable Formulas". - * - * The arguments lemmas and gs store the lemmas and guard literals to be sent - * out on the output channel of TheoryArith as lemmas and calls to - * ensureLiteral respectively. - */ - bool checkModel(const std::vector& assertions, - const std::vector& false_asserts, - std::vector& lemmas, - std::vector& gs); - //---------------------------end check model - - /** Is n entailed with polarity pol in the current context? */ - bool isEntailed(Node n, bool pol); - - /** - * Potentially adds lemmas to the set out and clears lemmas. Returns - * the number of lemmas added to out. We do not add lemmas that have already - * been sent on the output channel of TheoryArith. - */ - unsigned filterLemmas(std::vector& lemmas, std::vector& out); - /** singleton version of above */ - unsigned filterLemma(Node lem, std::vector& out); - - /** - * Send lemmas in out on the output channel of theory of arithmetic. - */ - void sendLemmas(const std::vector& out, - bool preprocess, - std::map& lemSE); - /** Process side effect se */ - void processSideEffect(const NlLemmaSideEffect& se); - - /** cache of all lemmas sent on the output channel (user-context-dependent) */ - NodeSet d_lemmas; - /** commonly used terms */ - Node d_zero; - Node d_one; - Node d_neg_one; - Node d_true; - // The theory of arithmetic containing this extension. - TheoryArith& d_containing; - // pointer to used equality engine - eq::EqualityEngine* d_ee; - // needs last call effort - bool d_needsLastCall; - /** The non-linear model object - * - * This class is responsible for computing model values for arithmetic terms - * 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 nonlinear extension object - * - * This is the subsolver responsible for running the procedure for - * constraints involving nonlinear mulitplication. - */ - NlSolver d_nlSlv; - /** - * The lemmas we computed during collectModelInfo. We store two vectors of - * lemmas to be sent out on the output channel of TheoryArith. The first - * is not preprocessed, the second is. - */ - std::vector d_cmiLemmas; - std::vector d_cmiLemmasPp; - /** the side effects of the above lemmas */ - std::map d_cmiLemmasSE; - /** - * The approximations computed during collectModelInfo. For details, see - * NlModel::getModelValueRepair. - */ - std::map> d_approximations; - /** have we successfully built the model in this SAT context? */ - context::CDO d_builtModel; -}; /* class NonlinearExtension */ - -} // namespace arith -} // namespace theory -} // namespace CVC4 - -#endif /* CVC4__THEORY__ARITH__NONLINEAR_EXTENSION_H */ diff --git a/src/theory/arith/theory_arith_private.cpp b/src/theory/arith/theory_arith_private.cpp index 638f5250e..3ff3966cc 100644 --- a/src/theory/arith/theory_arith_private.cpp +++ b/src/theory/arith/theory_arith_private.cpp @@ -55,7 +55,7 @@ #include "theory/arith/dio_solver.h" #include "theory/arith/linear_equality.h" #include "theory/arith/matrix.h" -#include "theory/arith/nonlinear_extension.h" +#include "theory/arith/nl/nonlinear_extension.h" #include "theory/arith/normal_form.h" #include "theory/arith/partial_model.h" #include "theory/arith/simplex.h" @@ -162,7 +162,7 @@ TheoryArithPrivate::TheoryArithPrivate(TheoryArith& containing, d_nlin_inverse_skolem(u) { if( options::nlExt() ){ - d_nonlinearExtension = new NonlinearExtension( + d_nonlinearExtension = new nl::NonlinearExtension( containing, d_congruenceManager.getEqualityEngine()); } } diff --git a/src/theory/arith/theory_arith_private.h b/src/theory/arith/theory_arith_private.h index 8198dbcf1..822b38f69 100644 --- a/src/theory/arith/theory_arith_private.h +++ b/src/theory/arith/theory_arith_private.h @@ -83,7 +83,9 @@ namespace inferbounds { } class InferBoundsResult; +namespace nl { class NonlinearExtension; +} /** * Implementation of QF_LRA. @@ -372,7 +374,7 @@ private: AttemptSolutionSDP d_attemptSolSimplex; /** non-linear algebraic approach */ - NonlinearExtension * d_nonlinearExtension; + nl::NonlinearExtension* d_nonlinearExtension; bool solveRealRelaxation(Theory::Effort effortLevel); diff --git a/src/theory/arith/transcendental_solver.cpp b/src/theory/arith/transcendental_solver.cpp deleted file mode 100644 index 665accc0a..000000000 --- a/src/theory/arith/transcendental_solver.cpp +++ /dev/null @@ -1,1475 +0,0 @@ -/********************* */ -/*! \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 deleted file mode 100644 index 88de49af3..000000000 --- a/src/theory/arith/transcendental_solver.h +++ /dev/null @@ -1,428 +0,0 @@ -/********************* */ -/*! \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 - * - * This is called at the beginning of last call effort check, where - * assertions are the set of assertions belonging to arithmetic, - * false_asserts is the subset of assertions that are false in the current - * model, and xts is the set of extended function terms that are active in - * the current context. - * - * This call may add lemmas to lems/lemsPp based on registering term - * information (for example, purification of sine terms). - */ - 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 b826fc8ae95fc13c4e2be45e39961199392a4dda Mon Sep 17 00:00:00 2001 From: Andrew Reynolds Date: Mon, 1 Jun 2020 18:34:21 -0500 Subject: (proof-new) Proof step buffer utilities (#4533) This class is used for delaying proof node creation until it is necessary. --- src/expr/CMakeLists.txt | 2 + src/expr/proof_step_buffer.cpp | 109 +++++++++++++++++++++++++++++++++++++++++ src/expr/proof_step_buffer.h | 96 ++++++++++++++++++++++++++++++++++++ 3 files changed, 207 insertions(+) create mode 100644 src/expr/proof_step_buffer.cpp create mode 100644 src/expr/proof_step_buffer.h (limited to 'src') diff --git a/src/expr/CMakeLists.txt b/src/expr/CMakeLists.txt index 413041213..3d41b7a72 100644 --- a/src/expr/CMakeLists.txt +++ b/src/expr/CMakeLists.txt @@ -51,6 +51,8 @@ libcvc4_add_sources( proof_rule.h proof_skolem_cache.cpp proof_skolem_cache.h + proof_step_buffer.cpp + proof_step_buffer.h symbol_table.cpp symbol_table.h term_canonize.cpp diff --git a/src/expr/proof_step_buffer.cpp b/src/expr/proof_step_buffer.cpp new file mode 100644 index 000000000..800efa2c0 --- /dev/null +++ b/src/expr/proof_step_buffer.cpp @@ -0,0 +1,109 @@ +/********************* */ +/*! \file proof_step_buffer.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 proof step and proof step buffer utilities. + **/ + +#include "expr/proof_step_buffer.h" + +using namespace CVC4::kind; + +namespace CVC4 { + +ProofStep::ProofStep() : d_rule(PfRule::UNKNOWN) {} +ProofStep::ProofStep(PfRule r, + const std::vector& children, + const std::vector& args) + : d_rule(r), d_children(children), d_args(args) +{ +} +std::ostream& operator<<(std::ostream& out, ProofStep step) +{ + out << "(step " << step.d_rule; + for (const Node& c : step.d_children) + { + out << " " << c; + } + if (!step.d_args.empty()) + { + out << " :args"; + for (const Node& a : step.d_args) + { + out << " " << a; + } + } + out << ")"; + return out; +} + +ProofStepBuffer::ProofStepBuffer(ProofChecker* pc) : d_checker(pc) {} + +Node ProofStepBuffer::tryStep(PfRule id, + const std::vector& children, + const std::vector& args, + Node expected) +{ + if (d_checker == nullptr) + { + Assert(false) << "ProofStepBuffer::ProofStepBuffer: no proof checker."; + return Node::null(); + } + Node res = + d_checker->checkDebug(id, children, args, expected, "pf-step-buffer"); + if (!res.isNull()) + { + // add proof step + d_steps.push_back( + std::pair(res, ProofStep(id, children, args))); + } + return res; +} + +void ProofStepBuffer::addStep(PfRule id, + const std::vector& children, + const std::vector& args, + Node expected) +{ + d_steps.push_back( + std::pair(expected, ProofStep(id, children, args))); +} + +void ProofStepBuffer::addSteps(ProofStepBuffer& psb) +{ + const std::vector>& steps = psb.getSteps(); + for (const std::pair& step : steps) + { + addStep(step.second.d_rule, + step.second.d_children, + step.second.d_args, + step.first); + } +} + +void ProofStepBuffer::popStep() +{ + Assert(!d_steps.empty()); + if (!d_steps.empty()) + { + d_steps.pop_back(); + } +} + +size_t ProofStepBuffer::getNumSteps() const { return d_steps.size(); } + +const std::vector>& ProofStepBuffer::getSteps() const +{ + return d_steps; +} + +void ProofStepBuffer::clear() { d_steps.clear(); } + +} // namespace CVC4 diff --git a/src/expr/proof_step_buffer.h b/src/expr/proof_step_buffer.h new file mode 100644 index 000000000..005aee399 --- /dev/null +++ b/src/expr/proof_step_buffer.h @@ -0,0 +1,96 @@ +/********************* */ +/*! \file proof_step_buffer.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 Proof step and proof step buffer utilities. + **/ + +#include "cvc4_private.h" + +#ifndef CVC4__EXPR__PROOF_STEP_BUFFER_H +#define CVC4__EXPR__PROOF_STEP_BUFFER_H + +#include + +#include "expr/node.h" +#include "expr/proof_checker.h" +#include "expr/proof_rule.h" + +namespace CVC4 { + +/** + * Information for constructing a step in a CDProof. Notice that the conclusion + * of the proof step is intentionally not included in this data structure. + * Instead, it is intended that conclusions may be associated with proof steps + * based on e.g. the result of proof checking. + */ +class ProofStep +{ + public: + ProofStep(); + ProofStep(PfRule r, + const std::vector& children, + const std::vector& args); + /** The proof rule */ + PfRule d_rule; + /** The proof children */ + std::vector d_children; + /** The proof arguments */ + std::vector d_args; +}; +std::ostream& operator<<(std::ostream& out, ProofStep step); + +/** + * Class used to speculatively try and buffer a set of proof steps before + * sending them to a proof object. + */ +class ProofStepBuffer +{ + public: + ProofStepBuffer(ProofChecker* pc = nullptr); + ~ProofStepBuffer() {} + /** + * Returns the conclusion of the proof step, as determined by the proof + * checker of the given proof. If this is non-null, then the given step + * is added to the buffer maintained by this class. + * + * If expected is non-null, then this method returns null if the result of + * checking is not equal to expected. + */ + Node tryStep(PfRule id, + const std::vector& children, + const std::vector& args, + Node expected = Node::null()); + /** Same as above, without checking */ + void addStep(PfRule id, + const std::vector& children, + const std::vector& args, + Node expected); + /** Multi-step version */ + void addSteps(ProofStepBuffer& psb); + /** pop step */ + void popStep(); + /** Get num steps */ + size_t getNumSteps() const; + /** Get steps */ + const std::vector>& getSteps() const; + /** Clear */ + void clear(); + + private: + /** The proof checker*/ + ProofChecker* d_checker; + /** the queued proof steps */ + std::vector> d_steps; +}; + +} // namespace CVC4 + +#endif /* CVC4__EXPR__PROOF_STEP_BUFFER_H */ -- cgit v1.2.3 From 50edf184492d20f4acb7b8d82f3843f3146f77d5 Mon Sep 17 00:00:00 2001 From: Aina Niemetz Date: Tue, 2 Jun 2020 09:09:15 -0700 Subject: New C++ API: Keep reference to solver object in non-solver objects. (#4549) This is in preparation for adding guards to ensure that sort and term arguments belong to the same solver. --- src/api/cvc4cpp.cpp | 545 ++++++++++++++++++-------------- src/api/cvc4cpp.h | 111 +++++-- src/parser/cvc/Cvc.g | 43 ++- src/parser/parser.cpp | 55 ++-- src/parser/parser.h | 12 +- src/parser/smt2/Smt2.g | 18 +- src/parser/smt2/smt2.cpp | 13 +- src/parser/tptp/Tptp.g | 4 +- test/unit/api/op_black.h | 2 +- test/unit/api/solver_black.h | 6 +- test/unit/api/term_black.h | 14 +- test/unit/parser/parser_builder_black.h | 2 +- 12 files changed, 491 insertions(+), 334 deletions(-) (limited to 'src') diff --git a/src/api/cvc4cpp.cpp b/src/api/cvc4cpp.cpp index 1ea421c4b..0bfb9a325 100644 --- a/src/api/cvc4cpp.cpp +++ b/src/api/cvc4cpp.cpp @@ -702,6 +702,8 @@ class CVC4ApiExceptionStream CVC4_API_CHECK(isDefinedKind(kind)) \ << "Invalid kind '" << kindToString(kind) << "'"; +#define CVC4_API_SORT_CHECK_SOLVER(sort) + #define CVC4_API_KIND_CHECK_EXPECTED(cond, kind) \ CVC4_PREDICT_TRUE(cond) \ ? (void)0 \ @@ -823,9 +825,12 @@ std::ostream& operator<<(std::ostream& out, const Result& r) /* Sort */ /* -------------------------------------------------------------------------- */ -Sort::Sort(const CVC4::Type& t) : d_type(new CVC4::Type(t)) {} +Sort::Sort(const Solver* slv, const CVC4::Type& t) + : d_solver(slv), d_type(new CVC4::Type(t)) +{ +} -Sort::Sort() : d_type(new CVC4::Type()) {} +Sort::Sort() : d_solver(nullptr), d_type(new CVC4::Type()) {} Sort::~Sort() {} @@ -909,7 +914,7 @@ bool Sort::isComparableTo(Sort s) const Datatype Sort::getDatatype() const { CVC4_API_CHECK(isDatatype()) << "Expected datatype sort."; - return DatatypeType(*d_type).getDatatype(); + return Datatype(d_solver, DatatypeType(*d_type).getDatatype()); } Sort Sort::instantiate(const std::vector& params) const @@ -923,10 +928,10 @@ Sort Sort::instantiate(const std::vector& params) const } if (d_type->isDatatype()) { - return DatatypeType(*d_type).instantiate(tparams); + return Sort(d_solver, DatatypeType(*d_type).instantiate(tparams)); } Assert(d_type->isSortConstructor()); - return SortConstructorType(*d_type).instantiate(tparams); + return Sort(d_solver, SortConstructorType(*d_type).instantiate(tparams)); } std::string Sort::toString() const { return d_type->toString(); } @@ -947,13 +952,13 @@ std::vector Sort::getConstructorDomainSorts() const { CVC4_API_CHECK(isConstructor()) << "Not a function sort."; std::vector types = ConstructorType(*d_type).getArgTypes(); - return typeVectorToSorts(types); + return typeVectorToSorts(d_solver, types); } Sort Sort::getConstructorCodomainSort() const { CVC4_API_CHECK(isConstructor()) << "Not a function sort."; - return ConstructorType(*d_type).getRangeType(); + return Sort(d_solver, ConstructorType(*d_type).getRangeType()); } /* Function sort ------------------------------------------------------- */ @@ -968,13 +973,13 @@ std::vector Sort::getFunctionDomainSorts() const { CVC4_API_CHECK(isFunction()) << "Not a function sort."; std::vector types = FunctionType(*d_type).getArgTypes(); - return typeVectorToSorts(types); + return typeVectorToSorts(d_solver, types); } Sort Sort::getFunctionCodomainSort() const { CVC4_API_CHECK(isFunction()) << "Not a function sort."; - return FunctionType(*d_type).getRangeType(); + return Sort(d_solver, FunctionType(*d_type).getRangeType()); } /* Array sort ---------------------------------------------------------- */ @@ -982,13 +987,13 @@ Sort Sort::getFunctionCodomainSort() const Sort Sort::getArrayIndexSort() const { CVC4_API_CHECK(isArray()) << "Not an array sort."; - return ArrayType(*d_type).getIndexType(); + return Sort(d_solver, ArrayType(*d_type).getIndexType()); } Sort Sort::getArrayElementSort() const { CVC4_API_CHECK(isArray()) << "Not an array sort."; - return ArrayType(*d_type).getConstituentType(); + return Sort(d_solver, ArrayType(*d_type).getConstituentType()); } /* Set sort ------------------------------------------------------------ */ @@ -996,7 +1001,7 @@ Sort Sort::getArrayElementSort() const Sort Sort::getSetElementSort() const { CVC4_API_CHECK(isSet()) << "Not a set sort."; - return SetType(*d_type).getElementType(); + return Sort(d_solver, SetType(*d_type).getElementType()); } /* Uninterpreted sort -------------------------------------------------- */ @@ -1017,7 +1022,7 @@ std::vector Sort::getUninterpretedSortParamSorts() const { CVC4_API_CHECK(isUninterpretedSort()) << "Not an uninterpreted sort."; std::vector types = SortType(*d_type).getParamTypes(); - return typeVectorToSorts(types); + return typeVectorToSorts(d_solver, types); } /* Sort constructor sort ----------------------------------------------- */ @@ -1062,7 +1067,7 @@ std::vector Sort::getDatatypeParamSorts() const { CVC4_API_CHECK(isParametricDatatype()) << "Not a parametric datatype sort."; std::vector types = DatatypeType(*d_type).getParamTypes(); - return typeVectorToSorts(types); + return typeVectorToSorts(d_solver, types); } size_t Sort::getDatatypeArity() const @@ -1083,7 +1088,7 @@ std::vector Sort::getTupleSorts() const { CVC4_API_CHECK(isTuple()) << "Not a tuple sort."; std::vector types = DatatypeType(*d_type).getTupleTypes(); - return typeVectorToSorts(types); + return typeVectorToSorts(d_solver, types); } /* --------------------------------------------------------------------- */ @@ -1105,9 +1110,13 @@ size_t SortHashFunction::operator()(const Sort& s) const Op::Op() : d_kind(NULL_EXPR), d_expr(new CVC4::Expr()) {} -Op::Op(const Kind k) : d_kind(k), d_expr(new CVC4::Expr()) {} +Op::Op(const Solver* slv, const Kind k) + : d_solver(slv), d_kind(k), d_expr(new CVC4::Expr()) +{ +} -Op::Op(const Kind k, const CVC4::Expr& e) : d_kind(k), d_expr(new CVC4::Expr(e)) +Op::Op(const Solver* slv, const Kind k, const CVC4::Expr& e) + : d_solver(slv), d_kind(k), d_expr(new CVC4::Expr(e)) { } @@ -1323,19 +1332,20 @@ size_t OpHashFunction::operator()(const Op& t) const /* Term */ /* -------------------------------------------------------------------------- */ -Term::Term() : d_expr(new CVC4::Expr()) {} +Term::Term() : d_solver(nullptr), d_expr(new CVC4::Expr()) {} -Term::Term(const CVC4::Expr& e) : d_expr(new CVC4::Expr(e)) {} +Term::Term(const Solver* slv, const CVC4::Expr& e) + : d_solver(slv), d_expr(new CVC4::Expr(e)) +{ +} Term::~Term() {} -/* Helpers */ -/* -------------------------------------------------------------------------- */ - -/* Split out to avoid nested API calls (problematic with API tracing). */ -/* .......................................................................... */ - -bool Term::isNullHelper() const { return d_expr->isNull(); } +bool Term::isNullHelper() const +{ + /* Split out to avoid nested API calls (problematic with API tracing). */ + return d_expr->isNull(); +} bool Term::operator==(const Term& t) const { return *d_expr == *t.d_expr; } @@ -1371,12 +1381,12 @@ Term Term::operator[](size_t index) const if (index == 0) { // return the operator - return api::Term(d_expr->getOperator()); + return Term(d_solver, d_expr->getOperator()); } // otherwise we are looking up child at (index-1) index--; } - return api::Term((*d_expr)[index]); + return Term(d_solver, (*d_expr)[index]); } uint64_t Term::getId() const @@ -1394,7 +1404,7 @@ Kind Term::getKind() const Sort Term::getSort() const { CVC4_API_CHECK_NOT_NULL; - return Sort(d_expr->getType()); + return Sort(d_solver, d_expr->getType()); } Term Term::substitute(Term e, Term replacement) const @@ -1406,7 +1416,7 @@ Term Term::substitute(Term e, Term replacement) const << "Expected non-null term as replacement in substitute"; CVC4_API_CHECK(e.getSort().isComparableTo(replacement.getSort())) << "Expecting terms of comparable sort in substitute"; - return api::Term(d_expr->substitute(e.getExpr(), replacement.getExpr())); + return Term(d_solver, d_expr->substitute(e.getExpr(), replacement.getExpr())); } Term Term::substitute(const std::vector es, @@ -1424,8 +1434,9 @@ Term Term::substitute(const std::vector es, CVC4_API_CHECK(es[i].getSort().isComparableTo(replacements[i].getSort())) << "Expecting terms of comparable sort in substitute"; } - return api::Term(d_expr->substitute(termVectorToExprs(es), - termVectorToExprs(replacements))); + return Term(d_solver, + d_expr->substitute(termVectorToExprs(es), + termVectorToExprs(replacements))); } bool Term::hasOp() const @@ -1447,18 +1458,18 @@ Op Term::getOp() const // is one of the APPLY_* kinds if (isApplyKind(d_expr->getKind())) { - return Op(intToExtKind(d_expr->getKind())); + return Op(d_solver, intToExtKind(d_expr->getKind())); } else if (d_expr->isParameterized()) { // it's an indexed operator // so we should return the indexed op CVC4::Expr op = d_expr->getOperator(); - return Op(intToExtKind(d_expr->getKind()), op); + return Op(d_solver, intToExtKind(d_expr->getKind()), op); } else { - return Op(intToExtKind(d_expr->getKind())); + return Op(d_solver, intToExtKind(d_expr->getKind())); } } @@ -1475,9 +1486,9 @@ Term Term::notTerm() const CVC4_API_CHECK_NOT_NULL; try { - Term res = d_expr->notExpr(); - (void)res.d_expr->getType(true); /* kick off type checking */ - return res; + Expr res = d_expr->notExpr(); + (void)res.getType(true); /* kick off type checking */ + return Term(d_solver, res); } catch (const CVC4::TypeCheckingException& e) { @@ -1491,9 +1502,9 @@ Term Term::andTerm(const Term& t) const CVC4_API_ARG_CHECK_NOT_NULL(t); try { - Term res = d_expr->andExpr(*t.d_expr); - (void)res.d_expr->getType(true); /* kick off type checking */ - return res; + Expr res = d_expr->andExpr(*t.d_expr); + (void)res.getType(true); /* kick off type checking */ + return Term(d_solver, res); } catch (const CVC4::TypeCheckingException& e) { @@ -1507,9 +1518,9 @@ Term Term::orTerm(const Term& t) const CVC4_API_ARG_CHECK_NOT_NULL(t); try { - Term res = d_expr->orExpr(*t.d_expr); - (void)res.d_expr->getType(true); /* kick off type checking */ - return res; + Expr res = d_expr->orExpr(*t.d_expr); + (void)res.getType(true); /* kick off type checking */ + return Term(d_solver, res); } catch (const CVC4::TypeCheckingException& e) { @@ -1523,9 +1534,9 @@ Term Term::xorTerm(const Term& t) const CVC4_API_ARG_CHECK_NOT_NULL(t); try { - Term res = d_expr->xorExpr(*t.d_expr); - (void)res.d_expr->getType(true); /* kick off type checking */ - return res; + Expr res = d_expr->xorExpr(*t.d_expr); + (void)res.getType(true); /* kick off type checking */ + return Term(d_solver, res); } catch (const CVC4::TypeCheckingException& e) { @@ -1539,9 +1550,9 @@ Term Term::eqTerm(const Term& t) const CVC4_API_ARG_CHECK_NOT_NULL(t); try { - Term res = d_expr->eqExpr(*t.d_expr); - (void)res.d_expr->getType(true); /* kick off type checking */ - return res; + Expr res = d_expr->eqExpr(*t.d_expr); + (void)res.getType(true); /* kick off type checking */ + return Term(d_solver, res); } catch (const CVC4::TypeCheckingException& e) { @@ -1555,9 +1566,9 @@ Term Term::impTerm(const Term& t) const CVC4_API_ARG_CHECK_NOT_NULL(t); try { - Term res = d_expr->impExpr(*t.d_expr); - (void)res.d_expr->getType(true); /* kick off type checking */ - return res; + Expr res = d_expr->impExpr(*t.d_expr); + (void)res.getType(true); /* kick off type checking */ + return Term(d_solver, res); } catch (const CVC4::TypeCheckingException& e) { @@ -1572,9 +1583,9 @@ Term Term::iteTerm(const Term& then_t, const Term& else_t) const CVC4_API_ARG_CHECK_NOT_NULL(else_t); try { - Term res = d_expr->iteExpr(*then_t.d_expr, *else_t.d_expr); - (void)res.d_expr->getType(true); /* kick off type checking */ - return res; + Expr res = d_expr->iteExpr(*then_t.d_expr, *else_t.d_expr); + (void)res.getType(true); /* kick off type checking */ + return Term(d_solver, res); } catch (const CVC4::TypeCheckingException& e) { @@ -1584,11 +1595,15 @@ Term Term::iteTerm(const Term& then_t, const Term& else_t) const std::string Term::toString() const { return d_expr->toString(); } -Term::const_iterator::const_iterator() : d_orig_expr(nullptr), d_pos(0) {} +Term::const_iterator::const_iterator() + : d_solver(nullptr), d_orig_expr(nullptr), d_pos(0) +{ +} -Term::const_iterator::const_iterator(const std::shared_ptr& e, +Term::const_iterator::const_iterator(const Solver* slv, + const std::shared_ptr& e, uint32_t p) - : d_orig_expr(e), d_pos(p) + : d_solver(slv), d_orig_expr(e), d_pos(p) { } @@ -1647,7 +1662,7 @@ Term Term::const_iterator::operator*() const if (!d_pos && extra_child) { - return Term(d_orig_expr->getOperator()); + return Term(d_solver, d_orig_expr->getOperator()); } else { @@ -1658,13 +1673,13 @@ Term Term::const_iterator::operator*() const --idx; } Assert(idx >= 0); - return Term((*d_orig_expr)[idx]); + return Term(d_solver, (*d_orig_expr)[idx]); } } Term::const_iterator Term::begin() const { - return Term::const_iterator(d_expr, 0); + return Term::const_iterator(d_solver, d_expr, 0); } Term::const_iterator Term::end() const @@ -1681,7 +1696,7 @@ Term::const_iterator Term::end() const // one more child if this is a UF application (count the UF as a child) ++endpos; } - return Term::const_iterator(d_expr, endpos); + return Term::const_iterator(d_solver, d_expr, endpos); } // !!! This is only temporarily available until the parser is fully migrated @@ -1789,25 +1804,25 @@ std::ostream& operator<<(std::ostream& out, /* DatatypeDecl ------------------------------------------------------------- */ -DatatypeDecl::DatatypeDecl(const Solver* s, +DatatypeDecl::DatatypeDecl(const Solver* slv, const std::string& name, bool isCoDatatype) - : d_dtype(new CVC4::Datatype(s->getExprManager(), name, isCoDatatype)) + : d_dtype(new CVC4::Datatype(slv->getExprManager(), name, isCoDatatype)) { } -DatatypeDecl::DatatypeDecl(const Solver* s, +DatatypeDecl::DatatypeDecl(const Solver* slv, const std::string& name, Sort param, bool isCoDatatype) - : d_dtype(new CVC4::Datatype(s->getExprManager(), + : d_dtype(new CVC4::Datatype(slv->getExprManager(), name, std::vector{*param.d_type}, isCoDatatype)) { } -DatatypeDecl::DatatypeDecl(const Solver* s, +DatatypeDecl::DatatypeDecl(const Solver* slv, const std::string& name, const std::vector& params, bool isCoDatatype) @@ -1818,7 +1833,7 @@ DatatypeDecl::DatatypeDecl(const Solver* s, tparams.push_back(*p.d_type); } d_dtype = std::shared_ptr( - new CVC4::Datatype(s->getExprManager(), name, tparams, isCoDatatype)); + new CVC4::Datatype(slv->getExprManager(), name, tparams, isCoDatatype)); } bool DatatypeDecl::isNullHelper() const { return !d_dtype; } @@ -1875,8 +1890,9 @@ std::ostream& operator<<(std::ostream& out, const DatatypeDecl& dtdecl) DatatypeSelector::DatatypeSelector() { d_stor = nullptr; } -DatatypeSelector::DatatypeSelector(const CVC4::DatatypeConstructorArg& stor) - : d_stor(new CVC4::DatatypeConstructorArg(stor)) +DatatypeSelector::DatatypeSelector(const Solver* slv, + const CVC4::DatatypeConstructorArg& stor) + : d_solver(slv), d_stor(new CVC4::DatatypeConstructorArg(stor)) { CVC4_API_CHECK(d_stor->isResolved()) << "Expected resolved datatype selector"; } @@ -1887,13 +1903,13 @@ std::string DatatypeSelector::getName() const { return d_stor->getName(); } Term DatatypeSelector::getSelectorTerm() const { - Term sel = d_stor->getSelector(); + Term sel = Term(d_solver, d_stor->getSelector()); return sel; } Sort DatatypeSelector::getRangeSort() const { - return Sort(d_stor->getRangeType()); + return Sort(d_solver, d_stor->getRangeType()); } std::string DatatypeSelector::toString() const @@ -1919,10 +1935,13 @@ std::ostream& operator<<(std::ostream& out, const DatatypeSelector& stor) /* DatatypeConstructor ------------------------------------------------------ */ -DatatypeConstructor::DatatypeConstructor() { d_ctor = nullptr; } +DatatypeConstructor::DatatypeConstructor() : d_solver(nullptr), d_ctor(nullptr) +{ +} -DatatypeConstructor::DatatypeConstructor(const CVC4::DatatypeConstructor& ctor) - : d_ctor(new CVC4::DatatypeConstructor(ctor)) +DatatypeConstructor::DatatypeConstructor(const Solver* slv, + const CVC4::DatatypeConstructor& ctor) + : d_solver(slv), d_ctor(new CVC4::DatatypeConstructor(ctor)) { CVC4_API_CHECK(d_ctor->isResolved()) << "Expected resolved datatype constructor"; @@ -1934,13 +1953,13 @@ std::string DatatypeConstructor::getName() const { return d_ctor->getName(); } Term DatatypeConstructor::getConstructorTerm() const { - Term ctor = d_ctor->getConstructor(); + Term ctor = Term(d_solver, d_ctor->getConstructor()); return ctor; } Term DatatypeConstructor::getTesterTerm() const { - Term tst = d_ctor->getTester(); + Term tst = Term(d_solver, d_ctor->getTester()); return tst; } @@ -1951,7 +1970,7 @@ size_t DatatypeConstructor::getNumSelectors() const DatatypeSelector DatatypeConstructor::operator[](size_t index) const { - return (*d_ctor)[index]; + return DatatypeSelector(d_solver, (*d_ctor)[index]); } DatatypeSelector DatatypeConstructor::operator[](const std::string& name) const @@ -1972,36 +1991,41 @@ Term DatatypeConstructor::getSelectorTerm(const std::string& name) const DatatypeConstructor::const_iterator DatatypeConstructor::begin() const { - return DatatypeConstructor::const_iterator(*d_ctor, true); + return DatatypeConstructor::const_iterator(d_solver, *d_ctor, true); } DatatypeConstructor::const_iterator DatatypeConstructor::end() const { - return DatatypeConstructor::const_iterator(*d_ctor, false); + return DatatypeConstructor::const_iterator(d_solver, *d_ctor, false); } DatatypeConstructor::const_iterator::const_iterator( - const CVC4::DatatypeConstructor& ctor, bool begin) + const Solver* slv, const CVC4::DatatypeConstructor& ctor, bool begin) { + d_solver = slv; d_int_stors = ctor.getArgs(); + const std::vector* sels = static_cast*>( d_int_stors); for (const auto& s : *sels) { /* Can not use emplace_back here since constructor is private. */ - d_stors.push_back(DatatypeSelector(s)); + d_stors.push_back(DatatypeSelector(d_solver, s)); } d_idx = begin ? 0 : sels->size(); } -// Nullary constructor for Cython -DatatypeConstructor::const_iterator::const_iterator() {} +DatatypeConstructor::const_iterator::const_iterator() + : d_solver(nullptr), d_int_stors(nullptr), d_idx(0) +{ +} DatatypeConstructor::const_iterator& DatatypeConstructor::const_iterator::operator=( const DatatypeConstructor::const_iterator& it) { + d_solver = it.d_solver; d_int_stors = it.d_int_stors; d_stors = it.d_stors; d_idx = it.d_idx; @@ -2076,7 +2100,7 @@ DatatypeSelector DatatypeConstructor::getSelectorForName( } CVC4_API_CHECK(foundSel) << "No selector " << name << " for constructor " << getName() << " exists"; - return (*d_ctor)[index]; + return DatatypeSelector(d_solver, (*d_ctor)[index]); } std::ostream& operator<<(std::ostream& out, const DatatypeConstructor& ctor) @@ -2087,21 +2111,20 @@ std::ostream& operator<<(std::ostream& out, const DatatypeConstructor& ctor) /* Datatype ----------------------------------------------------------------- */ -Datatype::Datatype(const CVC4::Datatype& dtype) - : d_dtype(new CVC4::Datatype(dtype)) +Datatype::Datatype(const Solver* slv, const CVC4::Datatype& dtype) + : d_solver(slv), d_dtype(new CVC4::Datatype(dtype)) { CVC4_API_CHECK(d_dtype->isResolved()) << "Expected resolved datatype"; } -// Nullary constructor for Cython -Datatype::Datatype() {} +Datatype::Datatype() : d_solver(nullptr), d_dtype(nullptr) {} Datatype::~Datatype() {} DatatypeConstructor Datatype::operator[](size_t idx) const { CVC4_API_CHECK(idx < getNumConstructors()) << "Index out of bounds."; - return (*d_dtype)[idx]; + return DatatypeConstructor(d_solver, (*d_dtype)[idx]); } DatatypeConstructor Datatype::operator[](const std::string& name) const @@ -2141,12 +2164,12 @@ std::string Datatype::toString() const { return d_dtype->getName(); } Datatype::const_iterator Datatype::begin() const { - return Datatype::const_iterator(*d_dtype, true); + return Datatype::const_iterator(d_solver, *d_dtype, true); } Datatype::const_iterator Datatype::end() const { - return Datatype::const_iterator(*d_dtype, false); + return Datatype::const_iterator(d_solver, *d_dtype, false); } // !!! This is only temporarily available until the parser is fully migrated @@ -2169,28 +2192,33 @@ DatatypeConstructor Datatype::getConstructorForName( } CVC4_API_CHECK(foundCons) << "No constructor " << name << " for datatype " << getName() << " exists"; - return (*d_dtype)[index]; + return DatatypeConstructor(d_solver, (*d_dtype)[index]); } -Datatype::const_iterator::const_iterator(const CVC4::Datatype& dtype, +Datatype::const_iterator::const_iterator(const Solver* slv, + const CVC4::Datatype& dtype, bool begin) + : d_solver(slv), d_int_ctors(dtype.getConstructors()) { - d_int_ctors = dtype.getConstructors(); const std::vector* cons = static_cast*>(d_int_ctors); for (const auto& c : *cons) { /* Can not use emplace_back here since constructor is private. */ - d_ctors.push_back(DatatypeConstructor(c)); + d_ctors.push_back(DatatypeConstructor(d_solver, c)); } d_idx = begin ? 0 : cons->size(); } -Datatype::const_iterator::const_iterator() {} +Datatype::const_iterator::const_iterator() + : d_solver(nullptr), d_int_ctors(nullptr), d_idx(0) +{ +} Datatype::const_iterator& Datatype::const_iterator::operator=( const Datatype::const_iterator& it) { + d_solver = it.d_solver; d_int_ctors = it.d_int_ctors; d_ctors = it.d_ctors; d_idx = it.d_idx; @@ -2235,10 +2263,10 @@ bool Datatype::const_iterator::operator!=( /* -------------------------------------------------------------------------- */ /* Grammar */ /* -------------------------------------------------------------------------- */ -Grammar::Grammar(const Solver* s, +Grammar::Grammar(const Solver* slv, const std::vector& sygusVars, const std::vector& ntSymbols) - : d_s(s), + : d_solver(slv), d_sygusVars(sygusVars), d_ntSyms(ntSymbols), d_ntsToTerms(ntSymbols.size()), @@ -2326,8 +2354,9 @@ Sort Grammar::resolve() if (!d_sygusVars.empty()) { - bvl = d_s->getExprManager()->mkExpr(CVC4::kind::BOUND_VAR_LIST, - termVectorToExprs(d_sygusVars)); + bvl = Term(d_solver, + d_solver->getExprManager()->mkExpr( + CVC4::kind::BOUND_VAR_LIST, termVectorToExprs(d_sygusVars))); } std::unordered_map ntsToUnres(d_ntSyms.size()); @@ -2336,7 +2365,8 @@ Sort Grammar::resolve() { // make the unresolved type, used for referencing the final version of // the ntsymbol's datatype - ntsToUnres[ntsymbol] = d_s->getExprManager()->mkSort(ntsymbol.toString()); + ntsToUnres[ntsymbol] = + Sort(d_solver, d_solver->getExprManager()->mkSort(ntsymbol.toString())); } std::vector datatypes; @@ -2347,7 +2377,7 @@ Sort Grammar::resolve() for (const Term& ntSym : d_ntSyms) { // make the datatype, which encodes terms generated by this non-terminal - DatatypeDecl dtDecl(d_s, ntSym.toString()); + DatatypeDecl dtDecl(d_solver, ntSym.toString()); for (const Term& consTerm : d_ntsToTerms[ntSym]) { @@ -2356,7 +2386,8 @@ Sort Grammar::resolve() if (d_allowVars.find(ntSym) != d_allowVars.cend()) { - addSygusConstructorVariables(dtDecl, ntSym.d_expr->getType()); + addSygusConstructorVariables(dtDecl, + Sort(d_solver, ntSym.d_expr->getType())); } bool aci = d_allowConst.find(ntSym) != d_allowConst.end(); @@ -2375,11 +2406,11 @@ Sort Grammar::resolve() } std::vector datatypeTypes = - d_s->getExprManager()->mkMutualDatatypeTypes( + d_solver->getExprManager()->mkMutualDatatypeTypes( datatypes, unresTypes, ExprManager::DATATYPE_FLAG_PLACEHOLDER); // return is the first datatype - return datatypeTypes[0]; + return Sort(d_solver, datatypeTypes[0]); } void Grammar::addSygusConstructorTerm( @@ -2406,11 +2437,13 @@ void Grammar::addSygusConstructorTerm( *op.d_expr, termVectorToExprs(args)); if (!args.empty()) { - Term lbvl = d_s->getExprManager()->mkExpr(CVC4::kind::BOUND_VAR_LIST, - termVectorToExprs(args)); + Term lbvl = Term(d_solver, + d_solver->getExprManager()->mkExpr( + CVC4::kind::BOUND_VAR_LIST, termVectorToExprs(args))); // its operator is a lambda - op = d_s->getExprManager()->mkExpr(CVC4::kind::LAMBDA, - {*lbvl.d_expr, *op.d_expr}); + op = Term(d_solver, + d_solver->getExprManager()->mkExpr(CVC4::kind::LAMBDA, + {*lbvl.d_expr, *op.d_expr})); } dt.d_dtype->addSygusConstructor( *op.d_expr, ssCName.str(), sortVectorToTypes(cargs), spc); @@ -2426,7 +2459,9 @@ Term Grammar::purifySygusGTerm( ntsToUnres.find(term); if (itn != ntsToUnres.cend()) { - Term ret = d_s->getExprManager()->mkBoundVar(term.d_expr->getType()); + Term ret = + Term(d_solver, + d_solver->getExprManager()->mkBoundVar(term.d_expr->getType())); args.push_back(ret); cargs.push_back(itn->second); return ret; @@ -2435,7 +2470,8 @@ Term Grammar::purifySygusGTerm( bool childChanged = false; for (unsigned i = 0, nchild = term.d_expr->getNumChildren(); i < nchild; i++) { - Term ptermc = purifySygusGTerm((*term.d_expr)[i], args, cargs, ntsToUnres); + Term ptermc = purifySygusGTerm( + Term(d_solver, (*term.d_expr)[i]), args, cargs, ntsToUnres); pchildren.push_back(ptermc); childChanged = childChanged || *ptermc.d_expr != (*term.d_expr)[i]; } @@ -2444,22 +2480,22 @@ Term Grammar::purifySygusGTerm( return term; } - Term nret; + Expr nret; if (term.d_expr->isParameterized()) { // it's an indexed operator so we should provide the op - nret = d_s->getExprManager()->mkExpr(term.d_expr->getKind(), - term.d_expr->getOperator(), - termVectorToExprs(pchildren)); + nret = d_solver->getExprManager()->mkExpr(term.d_expr->getKind(), + term.d_expr->getOperator(), + termVectorToExprs(pchildren)); } else { - nret = d_s->getExprManager()->mkExpr(term.d_expr->getKind(), - termVectorToExprs(pchildren)); + nret = d_solver->getExprManager()->mkExpr(term.d_expr->getKind(), + termVectorToExprs(pchildren)); } - return nret; + return Term(d_solver, nret); } void Grammar::addSygusConstructorVariables(DatatypeDecl& dt, Sort sort) const @@ -2538,9 +2574,9 @@ Solver::~Solver() {} template Term Solver::mkValHelper(T t) const { - Term res = d_exprMgr->mkConst(t); - (void)res.d_expr->getType(true); /* kick off type checking */ - return res; + Expr res = d_exprMgr->mkConst(t); + (void)res.getType(true); /* kick off type checking */ + return Term(this, res); } Term Solver::mkRealFromStrHelper(const std::string& s) const @@ -2623,7 +2659,7 @@ Term Solver::mkTermFromKind(Kind kind) const kind == PI || kind == REGEXP_EMPTY || kind == REGEXP_SIGMA, kind) << "PI or REGEXP_EMPTY or REGEXP_SIGMA"; - Term res; + Expr res; if (kind == REGEXP_EMPTY || kind == REGEXP_SIGMA) { CVC4::Kind k = extToIntKind(kind); @@ -2635,8 +2671,8 @@ Term Solver::mkTermFromKind(Kind kind) const Assert(kind == PI); res = d_exprMgr->mkNullaryOperator(d_exprMgr->realType(), CVC4::kind::PI); } - (void)res.d_expr->getType(true); /* kick off type checking */ - return res; + (void)res.getType(true); /* kick off type checking */ + return Term(this, res); CVC4_API_SOLVER_TRY_CATCH_END; } @@ -2656,7 +2692,7 @@ Term Solver::mkTermHelper(Kind kind, const std::vector& children) const Assert(isDefinedIntKind(k)) << "Not a defined internal kind : " << k << " " << kind; - Term res; + Expr res; if (echildren.size() > 2) { if (kind == INTS_DIVISION || kind == XOR || kind == MINUS @@ -2701,8 +2737,8 @@ Term Solver::mkTermHelper(Kind kind, const std::vector& children) const res = d_exprMgr->mkExpr(k, echildren); } - (void)res.d_expr->getType(true); /* kick off type checking */ - return res; + (void)res.getType(true); /* kick off type checking */ + return Term(this, res); CVC4_API_SOLVER_TRY_CATCH_END; } @@ -2726,7 +2762,7 @@ std::vector Solver::mkDatatypeSortsInternal( std::vector retTypes; for (CVC4::DatatypeType t : dtypes) { - retTypes.push_back(Sort(t)); + retTypes.push_back(Sort(this, t)); } return retTypes; @@ -2786,49 +2822,49 @@ void Solver::checkMkTerm(Kind kind, uint32_t nchildren) const Sort Solver::getNullSort(void) const { CVC4_API_SOLVER_TRY_CATCH_BEGIN; - return Type(); + return Sort(this, Type()); CVC4_API_SOLVER_TRY_CATCH_END; } Sort Solver::getBooleanSort(void) const { CVC4_API_SOLVER_TRY_CATCH_BEGIN; - return d_exprMgr->booleanType(); + return Sort(this, d_exprMgr->booleanType()); CVC4_API_SOLVER_TRY_CATCH_END; } Sort Solver::getIntegerSort(void) const { CVC4_API_SOLVER_TRY_CATCH_BEGIN; - return d_exprMgr->integerType(); + return Sort(this, d_exprMgr->integerType()); CVC4_API_SOLVER_TRY_CATCH_END; } Sort Solver::getRealSort(void) const { CVC4_API_SOLVER_TRY_CATCH_BEGIN; - return d_exprMgr->realType(); + return Sort(this, d_exprMgr->realType()); CVC4_API_SOLVER_TRY_CATCH_END; } Sort Solver::getRegExpSort(void) const { CVC4_API_SOLVER_TRY_CATCH_BEGIN; - return d_exprMgr->regExpType(); + return Sort(this, d_exprMgr->regExpType()); CVC4_API_SOLVER_TRY_CATCH_END; } Sort Solver::getStringSort(void) const { CVC4_API_SOLVER_TRY_CATCH_BEGIN; - return d_exprMgr->stringType(); + return Sort(this, d_exprMgr->stringType()); CVC4_API_SOLVER_TRY_CATCH_END; } Sort Solver::getRoundingmodeSort(void) const { CVC4_API_SOLVER_TRY_CATCH_BEGIN; - return d_exprMgr->roundingModeType(); + return Sort(this, d_exprMgr->roundingModeType()); CVC4_API_SOLVER_TRY_CATCH_END; } @@ -2842,7 +2878,8 @@ Sort Solver::mkArraySort(Sort indexSort, Sort elemSort) const CVC4_API_ARG_CHECK_EXPECTED(!elemSort.isNull(), elemSort) << "non-null element sort"; - return d_exprMgr->mkArrayType(*indexSort.d_type, *elemSort.d_type); + return Sort(this, + d_exprMgr->mkArrayType(*indexSort.d_type, *elemSort.d_type)); CVC4_API_SOLVER_TRY_CATCH_END; } @@ -2852,7 +2889,7 @@ Sort Solver::mkBitVectorSort(uint32_t size) const CVC4_API_SOLVER_TRY_CATCH_BEGIN; CVC4_API_ARG_CHECK_EXPECTED(size > 0, size) << "size > 0"; - return d_exprMgr->mkBitVectorType(size); + return Sort(this, d_exprMgr->mkBitVectorType(size)); CVC4_API_SOLVER_TRY_CATCH_END; } @@ -2863,7 +2900,7 @@ Sort Solver::mkFloatingPointSort(uint32_t exp, uint32_t sig) const CVC4_API_ARG_CHECK_EXPECTED(exp > 0, exp) << "exponent size > 0"; CVC4_API_ARG_CHECK_EXPECTED(sig > 0, sig) << "significand size > 0"; - return d_exprMgr->mkFloatingPointType(exp, sig); + return Sort(this, d_exprMgr->mkFloatingPointType(exp, sig)); CVC4_API_SOLVER_TRY_CATCH_END; } @@ -2874,7 +2911,7 @@ Sort Solver::mkDatatypeSort(DatatypeDecl dtypedecl) const CVC4_API_ARG_CHECK_EXPECTED(dtypedecl.getNumConstructors() > 0, dtypedecl) << "a datatype declaration with at least one constructor"; - return d_exprMgr->mkDatatypeType(*dtypedecl.d_dtype); + return Sort(this, d_exprMgr->mkDatatypeType(*dtypedecl.d_dtype)); CVC4_API_SOLVER_TRY_CATCH_END; } @@ -2903,7 +2940,8 @@ Sort Solver::mkFunctionSort(Sort domain, Sort codomain) const << "first-class sort as codomain sort for function sort"; Assert(!codomain.isFunction()); /* A function sort is not first-class. */ - return d_exprMgr->mkFunctionType(*domain.d_type, *codomain.d_type); + return Sort(this, + d_exprMgr->mkFunctionType(*domain.d_type, *codomain.d_type)); CVC4_API_SOLVER_TRY_CATCH_END; } @@ -2929,7 +2967,7 @@ Sort Solver::mkFunctionSort(const std::vector& sorts, Sort codomain) const Assert(!codomain.isFunction()); /* A function sort is not first-class. */ std::vector argTypes = sortVectorToTypes(sorts); - return d_exprMgr->mkFunctionType(argTypes, *codomain.d_type); + return Sort(this, d_exprMgr->mkFunctionType(argTypes, *codomain.d_type)); CVC4_API_SOLVER_TRY_CATCH_END; } @@ -2937,7 +2975,8 @@ Sort Solver::mkFunctionSort(const std::vector& sorts, Sort codomain) const Sort Solver::mkParamSort(const std::string& symbol) const { CVC4_API_SOLVER_TRY_CATCH_BEGIN; - return d_exprMgr->mkSort(symbol, ExprManager::SORT_FLAG_PLACEHOLDER); + return Sort(this, + d_exprMgr->mkSort(symbol, ExprManager::SORT_FLAG_PLACEHOLDER)); CVC4_API_SOLVER_TRY_CATCH_END; } @@ -2957,7 +2996,7 @@ Sort Solver::mkPredicateSort(const std::vector& sorts) const } std::vector types = sortVectorToTypes(sorts); - return d_exprMgr->mkPredicateType(types); + return Sort(this, d_exprMgr->mkPredicateType(types)); CVC4_API_SOLVER_TRY_CATCH_END; } @@ -2977,7 +3016,7 @@ Sort Solver::mkRecordSort( f.emplace_back(p.first, *p.second.d_type); } - return d_exprMgr->mkRecordType(Record(f)); + return Sort(this, d_exprMgr->mkRecordType(Record(f))); CVC4_API_SOLVER_TRY_CATCH_END; } @@ -2988,7 +3027,7 @@ Sort Solver::mkSetSort(Sort elemSort) const CVC4_API_ARG_CHECK_EXPECTED(!elemSort.isNull(), elemSort) << "non-null element sort"; - return d_exprMgr->mkSetType(*elemSort.d_type); + return Sort(this, d_exprMgr->mkSetType(*elemSort.d_type)); CVC4_API_SOLVER_TRY_CATCH_END; } @@ -2996,7 +3035,7 @@ Sort Solver::mkSetSort(Sort elemSort) const Sort Solver::mkUninterpretedSort(const std::string& symbol) const { CVC4_API_SOLVER_TRY_CATCH_BEGIN; - return d_exprMgr->mkSort(symbol); + return Sort(this, d_exprMgr->mkSort(symbol)); CVC4_API_SOLVER_TRY_CATCH_END; } @@ -3006,7 +3045,7 @@ Sort Solver::mkSortConstructorSort(const std::string& symbol, CVC4_API_SOLVER_TRY_CATCH_BEGIN; CVC4_API_ARG_CHECK_EXPECTED(arity > 0, arity) << "an arity > 0"; - return d_exprMgr->mkSortConstructor(symbol, arity); + return Sort(this, d_exprMgr->mkSortConstructor(symbol, arity)); CVC4_API_SOLVER_TRY_CATCH_END; } @@ -3025,7 +3064,7 @@ Sort Solver::mkTupleSort(const std::vector& sorts) const } std::vector types = sortVectorToTypes(sorts); - return d_exprMgr->mkTupleType(types); + return Sort(this, d_exprMgr->mkTupleType(types)); CVC4_API_SOLVER_TRY_CATCH_END; } @@ -3036,21 +3075,21 @@ Sort Solver::mkTupleSort(const std::vector& sorts) const Term Solver::mkTrue(void) const { CVC4_API_SOLVER_TRY_CATCH_BEGIN; - return d_exprMgr->mkConst(true); + return Term(this, d_exprMgr->mkConst(true)); CVC4_API_SOLVER_TRY_CATCH_END; } Term Solver::mkFalse(void) const { CVC4_API_SOLVER_TRY_CATCH_BEGIN; - return d_exprMgr->mkConst(false); + return Term(this, d_exprMgr->mkConst(false)); CVC4_API_SOLVER_TRY_CATCH_END; } Term Solver::mkBoolean(bool val) const { CVC4_API_SOLVER_TRY_CATCH_BEGIN; - return d_exprMgr->mkConst(val); + return Term(this, d_exprMgr->mkConst(val)); CVC4_API_SOLVER_TRY_CATCH_END; } @@ -3058,10 +3097,10 @@ Term Solver::mkPi() const { CVC4_API_SOLVER_TRY_CATCH_BEGIN; - Term res = + Expr res = d_exprMgr->mkNullaryOperator(d_exprMgr->realType(), CVC4::kind::PI); - (void)res.d_expr->getType(true); /* kick off type checking */ - return res; + (void)res.getType(true); /* kick off type checking */ + return Term(this, res); CVC4_API_SOLVER_TRY_CATCH_END; } @@ -3143,10 +3182,10 @@ Term Solver::mkRegexpEmpty() const { CVC4_API_SOLVER_TRY_CATCH_BEGIN; - Term res = + Expr res = d_exprMgr->mkExpr(CVC4::kind::REGEXP_EMPTY, std::vector()); - (void)res.d_expr->getType(true); /* kick off type checking */ - return res; + (void)res.getType(true); /* kick off type checking */ + return Term(this, res); CVC4_API_SOLVER_TRY_CATCH_END; } @@ -3155,10 +3194,10 @@ Term Solver::mkRegexpSigma() const { CVC4_API_SOLVER_TRY_CATCH_BEGIN; - Term res = + Expr res = d_exprMgr->mkExpr(CVC4::kind::REGEXP_SIGMA, std::vector()); - (void)res.d_expr->getType(true); /* kick off type checking */ - return res; + (void)res.getType(true); /* kick off type checking */ + return Term(this, res); CVC4_API_SOLVER_TRY_CATCH_END; } @@ -3179,9 +3218,9 @@ Term Solver::mkSepNil(Sort sort) const CVC4_API_SOLVER_TRY_CATCH_BEGIN; CVC4_API_ARG_CHECK_EXPECTED(!sort.isNull(), sort) << "non-null sort"; - Term res = d_exprMgr->mkNullaryOperator(*sort.d_type, CVC4::kind::SEP_NIL); - (void)res.d_expr->getType(true); /* kick off type checking */ - return res; + Expr res = d_exprMgr->mkNullaryOperator(*sort.d_type, CVC4::kind::SEP_NIL); + (void)res.getType(true); /* kick off type checking */ + return Term(this, res); CVC4_API_SOLVER_TRY_CATCH_END; } @@ -3234,11 +3273,11 @@ Term Solver::mkUniverseSet(Sort sort) const CVC4_API_SOLVER_TRY_CATCH_BEGIN; CVC4_API_ARG_CHECK_EXPECTED(!sort.isNull(), sort) << "non-null sort"; - Term res = + Expr res = d_exprMgr->mkNullaryOperator(*sort.d_type, CVC4::kind::UNIVERSE_SET); // TODO(#2771): Reenable? - // (void)res.d_expr->getType(true); /* kick off type checking */ - return res; + // (void)res->getType(true); /* kick off type checking */ + return Term(this, res); CVC4_API_SOLVER_TRY_CATCH_END; } @@ -3381,7 +3420,7 @@ Term Solver::mkAbstractValue(const std::string& index) const CVC4::Integer idx(index, 10); CVC4_API_ARG_CHECK_EXPECTED(idx > 0, index) << "a string representing an integer > 0"; - return d_exprMgr->mkConst(CVC4::AbstractValue(idx)); + return Term(this, d_exprMgr->mkConst(CVC4::AbstractValue(idx))); // do not call getType(), for abstract values, type can not be computed // until it is substituted away CVC4_API_SOLVER_TRY_CATCH_END; @@ -3392,7 +3431,7 @@ Term Solver::mkAbstractValue(uint64_t index) const CVC4_API_SOLVER_TRY_CATCH_BEGIN; CVC4_API_ARG_CHECK_EXPECTED(index > 0, index) << "an integer > 0"; - return d_exprMgr->mkConst(CVC4::AbstractValue(Integer(index))); + return Term(this, d_exprMgr->mkConst(CVC4::AbstractValue(Integer(index)))); // do not call getType(), for abstract values, type can not be computed // until it is substituted away CVC4_API_SOLVER_TRY_CATCH_END; @@ -3427,10 +3466,10 @@ Term Solver::mkConst(Sort sort, const std::string& symbol) const CVC4_API_SOLVER_TRY_CATCH_BEGIN; CVC4_API_ARG_CHECK_EXPECTED(!sort.isNull(), sort) << "non-null sort"; - Term res = symbol.empty() ? d_exprMgr->mkVar(*sort.d_type) + Expr res = symbol.empty() ? d_exprMgr->mkVar(*sort.d_type) : d_exprMgr->mkVar(symbol, *sort.d_type); - (void)res.d_expr->getType(true); /* kick off type checking */ - return res; + (void)res.getType(true); /* kick off type checking */ + return Term(this, res); CVC4_API_SOLVER_TRY_CATCH_END; } @@ -3443,10 +3482,10 @@ Term Solver::mkVar(Sort sort, const std::string& symbol) const CVC4_API_SOLVER_TRY_CATCH_BEGIN; CVC4_API_ARG_CHECK_EXPECTED(!sort.isNull(), sort) << "non-null sort"; - Term res = symbol.empty() ? d_exprMgr->mkBoundVar(*sort.d_type) + Expr res = symbol.empty() ? d_exprMgr->mkBoundVar(*sort.d_type) : d_exprMgr->mkBoundVar(symbol, *sort.d_type); - (void)res.d_expr->getType(true); /* kick off type checking */ - return res; + (void)res.getType(true); /* kick off type checking */ + return Term(this, res); CVC4_API_SOLVER_TRY_CATCH_END; } @@ -3489,9 +3528,9 @@ Term Solver::mkTerm(Kind kind, Term child) const CVC4_API_ARG_CHECK_EXPECTED(!child.isNull(), child) << "non-null term"; checkMkTerm(kind, 1); - Term res = d_exprMgr->mkExpr(extToIntKind(kind), *child.d_expr); - (void)res.d_expr->getType(true); /* kick off type checking */ - return res; + Expr res = d_exprMgr->mkExpr(extToIntKind(kind), *child.d_expr); + (void)res.getType(true); /* kick off type checking */ + return Term(this, res); CVC4_API_SOLVER_TRY_CATCH_END; } @@ -3503,10 +3542,10 @@ Term Solver::mkTerm(Kind kind, Term child1, Term child2) const CVC4_API_ARG_CHECK_EXPECTED(!child2.isNull(), child2) << "non-null term"; checkMkTerm(kind, 2); - Term res = + Expr res = d_exprMgr->mkExpr(extToIntKind(kind), *child1.d_expr, *child2.d_expr); - (void)res.d_expr->getType(true); /* kick off type checking */ - return res; + (void)res.getType(true); /* kick off type checking */ + return Term(this, res); CVC4_API_SOLVER_TRY_CATCH_END; } @@ -3530,7 +3569,7 @@ Term Solver::mkTerm(Op op) const if (op.isIndexedHelper()) { const CVC4::Kind int_kind = extToIntKind(op.d_kind); - res = d_exprMgr->mkExpr(int_kind, *op.d_expr); + res = Term(this, d_exprMgr->mkExpr(int_kind, *op.d_expr)); } else { @@ -3549,7 +3588,7 @@ Term Solver::mkTerm(Op op, Term child) const CVC4_API_ARG_CHECK_EXPECTED(!child.isNull(), child) << "non-null term"; const CVC4::Kind int_kind = extToIntKind(op.d_kind); - Term res; + Expr res; if (op.isIndexedHelper()) { res = d_exprMgr->mkExpr(int_kind, *op.d_expr, *child.d_expr); @@ -3559,8 +3598,8 @@ Term Solver::mkTerm(Op op, Term child) const res = d_exprMgr->mkExpr(int_kind, *child.d_expr); } - (void)res.d_expr->getType(true); /* kick off type checking */ - return res; + (void)res.getType(true); /* kick off type checking */ + return Term(this, res); CVC4_API_SOLVER_TRY_CATCH_END; } @@ -3572,7 +3611,7 @@ Term Solver::mkTerm(Op op, Term child1, Term child2) const CVC4_API_ARG_CHECK_EXPECTED(!child2.isNull(), child2) << "non-null term"; const CVC4::Kind int_kind = extToIntKind(op.d_kind); - Term res; + Expr res; if (op.isIndexedHelper()) { res = @@ -3583,8 +3622,8 @@ Term Solver::mkTerm(Op op, Term child1, Term child2) const res = d_exprMgr->mkExpr(int_kind, *child1.d_expr, *child2.d_expr); } - (void)res.d_expr->getType(true); /* kick off type checking */ - return res; + (void)res.getType(true); /* kick off type checking */ + return Term(this, res); CVC4_API_SOLVER_TRY_CATCH_END; } @@ -3596,7 +3635,7 @@ Term Solver::mkTerm(Op op, Term child1, Term child2, Term child3) const CVC4_API_ARG_CHECK_EXPECTED(!child3.isNull(), child3) << "non-null term"; const CVC4::Kind int_kind = extToIntKind(op.d_kind); - Term res; + Expr res; if (op.isIndexedHelper()) { res = d_exprMgr->mkExpr( @@ -3608,8 +3647,8 @@ Term Solver::mkTerm(Op op, Term child1, Term child2, Term child3) const int_kind, *child1.d_expr, *child2.d_expr, *child3.d_expr); } - (void)res.d_expr->getType(true); /* kick off type checking */ - return res; + (void)res.getType(true); /* kick off type checking */ + return Term(this, res); CVC4_API_SOLVER_TRY_CATCH_END; } @@ -3626,7 +3665,7 @@ Term Solver::mkTerm(Op op, const std::vector& children) const const CVC4::Kind int_kind = extToIntKind(op.d_kind); std::vector echildren = termVectorToExprs(children); - Term res; + Expr res; if (op.isIndexedHelper()) { res = d_exprMgr->mkExpr(int_kind, *op.d_expr, echildren); @@ -3636,8 +3675,8 @@ Term Solver::mkTerm(Op op, const std::vector& children) const res = d_exprMgr->mkExpr(int_kind, echildren); } - (void)res.d_expr->getType(true); /* kick off type checking */ - return res; + (void)res.getType(true); /* kick off type checking */ + return Term(this, res); CVC4_API_SOLVER_TRY_CATCH_END; } @@ -3656,11 +3695,11 @@ Term Solver::mkTuple(const std::vector& sorts, Sort s = mkTupleSort(sorts); Datatype dt = s.getDatatype(); - Term res = d_exprMgr->mkExpr(extToIntKind(APPLY_CONSTRUCTOR), + Expr res = d_exprMgr->mkExpr(extToIntKind(APPLY_CONSTRUCTOR), *dt[0].getConstructorTerm().d_expr, args); - (void)res.d_expr->getType(true); /* kick off type checking */ - return res; + (void)res.getType(true); /* kick off type checking */ + return Term(this, res); CVC4_API_SOLVER_TRY_CATCH_END; } @@ -3673,7 +3712,7 @@ Op Solver::mkOp(Kind kind) const CVC4_API_SOLVER_TRY_CATCH_BEGIN; CVC4_API_CHECK(s_indexed_kinds.find(kind) == s_indexed_kinds.end()) << "Expected a kind for a non-indexed operator."; - return Op(kind); + return Op(this, kind); CVC4_API_SOLVER_TRY_CATCH_END } @@ -3687,6 +3726,7 @@ Op Solver::mkOp(Kind kind, const std::string& arg) const if (kind == RECORD_UPDATE) { res = Op( + this, kind, *mkValHelper(CVC4::RecordUpdate(arg)).d_expr.get()); } @@ -3697,7 +3737,8 @@ Op Solver::mkOp(Kind kind, const std::string& arg) const * as invalid. */ CVC4_API_ARG_CHECK_EXPECTED(arg != ".", arg) << "a string representing an integer, real or rational value."; - res = Op(kind, + res = Op(this, + kind, *mkValHelper(CVC4::Divisible(CVC4::Integer(arg))) .d_expr.get()); } @@ -3716,62 +3757,73 @@ Op Solver::mkOp(Kind kind, uint32_t arg) const { case DIVISIBLE: res = - Op(kind, + Op(this, + kind, *mkValHelper(CVC4::Divisible(arg)).d_expr.get()); break; case BITVECTOR_REPEAT: - res = Op(kind, + res = Op(this, + kind, *mkValHelper(CVC4::BitVectorRepeat(arg)) .d_expr.get()); break; case BITVECTOR_ZERO_EXTEND: - res = Op(kind, + res = Op(this, + kind, *mkValHelper( CVC4::BitVectorZeroExtend(arg)) .d_expr.get()); break; case BITVECTOR_SIGN_EXTEND: - res = Op(kind, + res = Op(this, + kind, *mkValHelper( CVC4::BitVectorSignExtend(arg)) .d_expr.get()); break; case BITVECTOR_ROTATE_LEFT: - res = Op(kind, + res = Op(this, + kind, *mkValHelper( CVC4::BitVectorRotateLeft(arg)) .d_expr.get()); break; case BITVECTOR_ROTATE_RIGHT: - res = Op(kind, + res = Op(this, + kind, *mkValHelper( CVC4::BitVectorRotateRight(arg)) .d_expr.get()); break; case INT_TO_BITVECTOR: - res = Op(kind, + res = Op(this, + kind, *mkValHelper(CVC4::IntToBitVector(arg)) .d_expr.get()); break; case FLOATINGPOINT_TO_UBV: res = Op( + this, kind, *mkValHelper(CVC4::FloatingPointToUBV(arg)) .d_expr.get()); break; case FLOATINGPOINT_TO_SBV: res = Op( + this, kind, *mkValHelper(CVC4::FloatingPointToSBV(arg)) .d_expr.get()); break; case TUPLE_UPDATE: res = Op( + this, kind, *mkValHelper(CVC4::TupleUpdate(arg)).d_expr.get()); break; case REGEXP_REPEAT: - res = Op(kind, + res = Op(this, + kind, *mkValHelper(CVC4::RegExpRepeat(arg)) .d_expr.get()); break; @@ -3794,49 +3846,57 @@ Op Solver::mkOp(Kind kind, uint32_t arg1, uint32_t arg2) const switch (kind) { case BITVECTOR_EXTRACT: - res = Op(kind, + res = Op(this, + kind, *mkValHelper( CVC4::BitVectorExtract(arg1, arg2)) .d_expr.get()); break; case FLOATINGPOINT_TO_FP_IEEE_BITVECTOR: - res = Op(kind, + res = Op(this, + kind, *mkValHelper( CVC4::FloatingPointToFPIEEEBitVector(arg1, arg2)) .d_expr.get()); break; case FLOATINGPOINT_TO_FP_FLOATINGPOINT: - res = Op(kind, + res = Op(this, + kind, *mkValHelper( CVC4::FloatingPointToFPFloatingPoint(arg1, arg2)) .d_expr.get()); break; case FLOATINGPOINT_TO_FP_REAL: - res = Op(kind, + res = Op(this, + kind, *mkValHelper( CVC4::FloatingPointToFPReal(arg1, arg2)) .d_expr.get()); break; case FLOATINGPOINT_TO_FP_SIGNED_BITVECTOR: - res = Op(kind, + res = Op(this, + kind, *mkValHelper( CVC4::FloatingPointToFPSignedBitVector(arg1, arg2)) .d_expr.get()); break; case FLOATINGPOINT_TO_FP_UNSIGNED_BITVECTOR: - res = Op(kind, + res = Op(this, + kind, *mkValHelper( CVC4::FloatingPointToFPUnsignedBitVector(arg1, arg2)) .d_expr.get()); break; case FLOATINGPOINT_TO_FP_GENERIC: - res = Op(kind, + res = Op(this, + kind, *mkValHelper( CVC4::FloatingPointToFPGeneric(arg1, arg2)) .d_expr.get()); break; case REGEXP_LOOP: - res = Op(kind, + res = Op(this, + kind, *mkValHelper(CVC4::RegExpLoop(arg1, arg2)) .d_expr.get()); break; @@ -3858,7 +3918,7 @@ Term Solver::simplify(const Term& t) CVC4_API_SOLVER_TRY_CATCH_BEGIN; CVC4_API_ARG_CHECK_NOT_NULL(t); - return d_smtEngine->simplify(*t.d_expr); + return Term(this, d_smtEngine->simplify(*t.d_expr)); CVC4_API_SOLVER_TRY_CATCH_END; } @@ -3961,7 +4021,7 @@ Sort Solver::declareDatatype( { dtdecl.addConstructor(ctor); } - return d_exprMgr->mkDatatypeType(*dtdecl.d_dtype); + return Sort(this, d_exprMgr->mkDatatypeType(*dtdecl.d_dtype)); } /** @@ -3986,7 +4046,7 @@ Term Solver::declareFun(const std::string& symbol, std::vector types = sortVectorToTypes(sorts); type = d_exprMgr->mkFunctionType(types, type); } - return d_exprMgr->mkVar(symbol, type); + return Term(this, d_exprMgr->mkVar(symbol, type)); } /** @@ -3994,8 +4054,8 @@ Term Solver::declareFun(const std::string& symbol, */ Sort Solver::declareSort(const std::string& symbol, uint32_t arity) const { - if (arity == 0) return d_exprMgr->mkSort(symbol); - return d_exprMgr->mkSortConstructor(symbol, arity); + if (arity == 0) return Sort(this, d_exprMgr->mkSort(symbol)); + return Sort(this, d_exprMgr->mkSortConstructor(symbol, arity)); } /** @@ -4037,7 +4097,7 @@ Term Solver::defineFun(const std::string& symbol, Expr fun = d_exprMgr->mkVar(symbol, type); std::vector ebound_vars = termVectorToExprs(bound_vars); d_smtEngine->defineFunction(fun, ebound_vars, *term.d_expr); - return fun; + return Term(this, fun); } Term Solver::defineFun(Term fun, @@ -4115,7 +4175,7 @@ Term Solver::defineFunRec(const std::string& symbol, Expr fun = d_exprMgr->mkVar(symbol, type); std::vector ebound_vars = termVectorToExprs(bound_vars); d_smtEngine->defineFunctionRec(fun, ebound_vars, *term.d_expr); - return fun; + return Term(this, fun); } Term Solver::defineFunRec(Term fun, @@ -4229,7 +4289,7 @@ std::vector Solver::getAssertions(void) const std::vector res; for (const Expr& e : assertions) { - res.push_back(Term(e)); + res.push_back(Term(this, e)); } return res; } @@ -4245,7 +4305,7 @@ std::vector> Solver::getAssignment(void) const std::vector> res; for (const auto& p : assignment) { - res.emplace_back(Term(p.first), Term(p.second)); + res.emplace_back(Term(this, p.first), Term(this, p.second)); } return res; } @@ -4284,7 +4344,7 @@ std::vector Solver::getUnsatAssumptions(void) const std::vector res; for (const Expr& e : uassumptions) { - res.push_back(Term(e)); + res.push_back(Term(this, e)); } return res; } @@ -4302,7 +4362,7 @@ std::vector Solver::getUnsatCore(void) const std::vector res; for (const Expr& e : core) { - res.push_back(Term(e)); + res.push_back(Term(this, e)); } return res; } @@ -4315,7 +4375,7 @@ Term Solver::getValue(Term term) const // CHECK: // NodeManager::fromExprManager(d_exprMgr) // == NodeManager::fromExprManager(expr.getExprManager()) - return d_smtEngine->getValue(*term.d_expr); + return Term(this, d_smtEngine->getValue(*term.d_expr)); } /** @@ -4331,7 +4391,7 @@ std::vector Solver::getValue(const std::vector& terms) const for (const Term& t : terms) { /* Can not use emplace_back here since constructor is private. */ - res.push_back(Term(d_smtEngine->getValue(*t.d_expr))); + res.push_back(Term(this, d_smtEngine->getValue(*t.d_expr))); } return res; } @@ -4471,7 +4531,8 @@ Term Solver::ensureTermSort(const Term& term, const Sort& sort) const // constructors. We do this cast using division with 1. This has the // advantage wrt using TO_REAL since (constant) division is always included // in the theory. - res = Term(d_exprMgr->mkExpr(extToIntKind(DIVISION), + res = Term(this, + d_exprMgr->mkExpr(extToIntKind(DIVISION), *res.d_expr, d_exprMgr->mkConst(CVC4::Rational(1)))); } @@ -4489,7 +4550,7 @@ Term Solver::mkSygusVar(Sort sort, const std::string& symbol) const d_smtEngine->declareSygusVar(symbol, res, *sort.d_type); - return res; + return Term(this, res); CVC4_API_SOLVER_TRY_CATCH_END; } @@ -4535,14 +4596,16 @@ Term Solver::synthFun(const std::string& symbol, Term Solver::synthInv(const std::string& symbol, const std::vector& boundVars) const { - return synthFunHelper(symbol, boundVars, d_exprMgr->booleanType(), true); + return synthFunHelper( + symbol, boundVars, Sort(this, d_exprMgr->booleanType()), true); } Term Solver::synthInv(const std::string& symbol, const std::vector& boundVars, Grammar& g) const { - return synthFunHelper(symbol, boundVars, d_exprMgr->booleanType(), true, &g); + return synthFunHelper( + symbol, boundVars, Sort(this, d_exprMgr->booleanType()), true, &g); } Term Solver::synthFunHelper(const std::string& symbol, @@ -4586,7 +4649,7 @@ Term Solver::synthFunHelper(const std::string& symbol, isInv, termVectorToExprs(boundVars)); - return fun; + return Term(this, fun); CVC4_API_SOLVER_TRY_CATCH_END; } @@ -4661,7 +4724,7 @@ Term Solver::getSynthSolution(Term term) const CVC4_API_CHECK(it != map.cend()) << "Synth solution not found for given term"; - return it->second; + return Term(this, it->second); } std::vector Solver::getSynthSolutions( @@ -4692,7 +4755,7 @@ std::vector Solver::getSynthSolutions( CVC4_API_CHECK(it != map.cend()) << "Synth solution not found for term at index " << i; - synthSolution.push_back(it->second); + synthSolution.push_back(Term(this, it->second)); } return synthSolution; @@ -4749,22 +4812,24 @@ std::set sortSetToTypes(const std::set& sorts) return types; } -std::vector exprVectorToTerms(const std::vector& exprs) +std::vector exprVectorToTerms(const Solver* slv, + const std::vector& exprs) { std::vector terms; for (size_t i = 0, esize = exprs.size(); i < esize; i++) { - terms.push_back(Term(exprs[i])); + terms.push_back(Term(slv, exprs[i])); } return terms; } -std::vector typeVectorToSorts(const std::vector& types) +std::vector typeVectorToSorts(const Solver* slv, + const std::vector& types) { std::vector sorts; for (size_t i = 0, tsize = types.size(); i < tsize; i++) { - sorts.push_back(Sort(types[i])); + sorts.push_back(Sort(slv, types[i])); } return sorts; } diff --git a/src/api/cvc4cpp.h b/src/api/cvc4cpp.h index 279453747..87be7b74c 100644 --- a/src/api/cvc4cpp.h +++ b/src/api/cvc4cpp.h @@ -49,6 +49,8 @@ class Result; namespace api { +class Solver; + /* -------------------------------------------------------------------------- */ /* Exception */ /* -------------------------------------------------------------------------- */ @@ -199,10 +201,11 @@ class CVC4_PUBLIC Sort // migrated to the new API. !!! /** * Constructor. + * @param slv the associated solver object * @param t the internal type that is to be wrapped by this sort * @return the Sort */ - Sort(const CVC4::Type& t); + Sort(const Solver* slv, const CVC4::Type& t); /** * Constructor. @@ -588,6 +591,11 @@ class CVC4_PUBLIC Sort */ bool isNullHelper() const; + /** + * The associated solver object. + */ + const Solver* d_solver; + /** * The interal type wrapped by this sort. * This is a shared_ptr rather than a unique_ptr to avoid overhead due to @@ -637,19 +645,21 @@ class CVC4_PUBLIC Op // migrated to the new API. !!! /** * Constructor for a single kind (non-indexed operator). + * @param slv the associated solver object * @param k the kind of this Op */ - Op(const Kind k); + Op(const Solver* slv, const Kind k); // !!! This constructor is only temporarily public until the parser is fully // migrated to the new API. !!! /** * Constructor. + * @param slv the associated solver object * @param k the kind of this Op * @param e the internal expression that is to be wrapped by this term * @return the Term */ - Op(const Kind k, const CVC4::Expr& e); + Op(const Solver* slv, const Kind k, const CVC4::Expr& e); /** * Destructor. @@ -726,6 +736,11 @@ class CVC4_PUBLIC Op */ bool isIndexedHelper() const; + /** + * The associated solver object. + */ + const Solver* d_solver; + /* The kind of this operator. */ Kind d_kind; @@ -758,10 +773,11 @@ class CVC4_PUBLIC Term // migrated to the new API. !!! /** * Constructor. + * @param slv the associated solver object * @param e the internal expression that is to be wrapped by this term * @return the Term */ - Term(const CVC4::Expr& e); + Term(const Solver* slv, const CVC4::Expr& e); /** * Constructor. @@ -955,10 +971,13 @@ class CVC4_PUBLIC Term /** * Constructor + * @param slv the associated solver object * @param e a shared pointer to the expression that we're iterating over * @param p the position of the iterator (e.g. which child it's on) */ - const_iterator(const std::shared_ptr& e, uint32_t p); + const_iterator(const Solver* slv, + const std::shared_ptr& e, + uint32_t p); /** * Copy constructor. @@ -1005,6 +1024,10 @@ class CVC4_PUBLIC Term Term operator*() const; private: + /** + * The associated solver object. + */ + const Solver* d_solver; /* The original expression to be iterated over */ std::shared_ptr d_orig_expr; /* Keeps track of the iteration position */ @@ -1025,6 +1048,12 @@ class CVC4_PUBLIC Term // to the new API. !!! CVC4::Expr getExpr(void) const; + protected: + /** + * The associated solver object. + */ + const Solver* d_solver; + private: /** * Helper for isNull checks. This prevents calling an API function with @@ -1228,24 +1257,24 @@ class CVC4_PUBLIC DatatypeDecl private: /** * Constructor. - * @param s the solver that created this datatype declaration + * @param slv the solver that created this datatype declaration * @param name the name of the datatype * @param isCoDatatype true if a codatatype is to be constructed * @return the DatatypeDecl */ - DatatypeDecl(const Solver* s, + DatatypeDecl(const Solver* slv, const std::string& name, bool isCoDatatype = false); /** * Constructor for parameterized datatype declaration. * Create sorts parameter with Solver::mkParamSort(). - * @param s the solver that created this datatype declaration + * @param slv the solver that created this datatype declaration * @param name the name of the datatype * @param param the sort parameter * @param isCoDatatype true if a codatatype is to be constructed */ - DatatypeDecl(const Solver* s, + DatatypeDecl(const Solver* slv, const std::string& name, Sort param, bool isCoDatatype = false); @@ -1253,12 +1282,12 @@ class CVC4_PUBLIC DatatypeDecl /** * Constructor for parameterized datatype declaration. * Create sorts parameter with Solver::mkParamSort(). - * @param s the solver that created this datatype declaration + * @param slv the solver that created this datatype declaration * @param name the name of the datatype * @param params a list of sort parameters * @param isCoDatatype true if a codatatype is to be constructed */ - DatatypeDecl(const Solver* s, + DatatypeDecl(const Solver* slv, const std::string& name, const std::vector& params, bool isCoDatatype = false); @@ -1292,10 +1321,11 @@ class CVC4_PUBLIC DatatypeSelector // migrated to the new API. !!! /** * Constructor. + * @param slv the associated solver object * @param stor the internal datatype selector to be wrapped * @return the DatatypeSelector */ - DatatypeSelector(const CVC4::DatatypeConstructorArg& stor); + DatatypeSelector(const Solver* slv, const CVC4::DatatypeConstructorArg& stor); /** * Destructor. @@ -1324,6 +1354,11 @@ class CVC4_PUBLIC DatatypeSelector CVC4::DatatypeConstructorArg getDatatypeConstructorArg(void) const; private: + /** + * The associated solver object. + */ + const Solver* d_solver; + /** * The internal datatype selector wrapped by this datatype selector. * This is a shared_ptr rather than a unique_ptr since CVC4::Datatype is @@ -1353,7 +1388,7 @@ class CVC4_PUBLIC DatatypeConstructor * @param ctor the internal datatype constructor to be wrapped * @return the DatatypeConstructor */ - DatatypeConstructor(const CVC4::DatatypeConstructor& ctor); + DatatypeConstructor(const Solver* slv, const CVC4::DatatypeConstructor& ctor); /** * Destructor. @@ -1466,16 +1501,27 @@ class CVC4_PUBLIC DatatypeConstructor private: /** * Constructor. + * @param slv the associated Solver object * @param ctor the internal datatype constructor to iterate over * @param true if this is a begin() iterator */ - const_iterator(const CVC4::DatatypeConstructor& ctor, bool begin); + const_iterator(const Solver* slv, + const CVC4::DatatypeConstructor& ctor, + bool begin); + + /** + * The associated solver object. + */ + const Solver* d_solver; + /* A pointer to the list of selectors of the internal datatype * constructor to iterate over. * This pointer is maintained for operators == and != only. */ const void* d_int_stors; + /* The list of datatype selector (wrappers) to iterate over. */ std::vector d_stors; + /* The current index of the iterator. */ size_t d_idx; }; @@ -1501,6 +1547,12 @@ class CVC4_PUBLIC DatatypeConstructor * @return the selector object for the name */ DatatypeSelector getSelectorForName(const std::string& name) const; + + /** + * The associated solver object. + */ + const Solver* d_solver; + /** * The internal datatype constructor wrapped by this datatype constructor. * This is a shared_ptr rather than a unique_ptr since CVC4::Datatype is @@ -1525,7 +1577,7 @@ class CVC4_PUBLIC Datatype * @param dtype the internal datatype to be wrapped * @return the Datatype */ - Datatype(const CVC4::Datatype& dtype); + Datatype(const Solver* slv, const CVC4::Datatype& dtype); // Nullary constructor for Cython Datatype(); @@ -1654,16 +1706,25 @@ class CVC4_PUBLIC Datatype private: /** * Constructor. + * @param slv the associated Solver object * @param dtype the internal datatype to iterate over * @param true if this is a begin() iterator */ - const_iterator(const CVC4::Datatype& dtype, bool begin); + const_iterator(const Solver* slv, const CVC4::Datatype& dtype, bool begin); + + /** + * The associated solver object. + */ + const Solver* d_solver; + /* A pointer to the list of constructors of the internal datatype * to iterate over. * This pointer is maintained for operators == and != only. */ const void* d_int_ctors; + /* The list of datatype constructor (wrappers) to iterate over. */ std::vector d_ctors; + /* The current index of the iterator. */ size_t d_idx; }; @@ -1689,6 +1750,12 @@ class CVC4_PUBLIC Datatype * @return the constructor object for the name */ DatatypeConstructor getConstructorForName(const std::string& name) const; + + /** + * The associated solver object. + */ + const Solver* d_solver; + /** * The internal datatype wrapped by this datatype. * This is a shared_ptr rather than a unique_ptr since CVC4::Datatype is @@ -1793,11 +1860,11 @@ class CVC4_PUBLIC Grammar private: /** * Constructor. - * @param s the solver that created this grammar + * @param slv the solver that created this grammar * @param sygusVars the input variables to synth-fun/synth-var * @param ntSymbols the non-terminals of this grammar */ - Grammar(const Solver* s, + Grammar(const Solver* slv, const std::vector& sygusVars, const std::vector& ntSymbols); @@ -1863,7 +1930,7 @@ class CVC4_PUBLIC Grammar void addSygusConstructorVariables(DatatypeDecl& dt, Sort sort) const; /** The solver that created this grammar. */ - const Solver* d_s; + const Solver* d_solver; /** Input variables to the corresponding function/invariant to synthesize.*/ std::vector d_sygusVars; /** The non-terminal symbols of this grammar. */ @@ -3142,9 +3209,11 @@ class CVC4_PUBLIC Solver // new API. !!! std::vector termVectorToExprs(const std::vector& terms); std::vector sortVectorToTypes(const std::vector& sorts); -std::vector exprVectorToTerms(const std::vector& terms); -std::vector typeVectorToSorts(const std::vector& sorts); std::set sortSetToTypes(const std::set& sorts); +std::vector exprVectorToTerms(const Solver* slv, + const std::vector& terms); +std::vector typeVectorToSorts(const Solver* slv, + const std::vector& sorts); } // namespace api diff --git a/src/parser/cvc/Cvc.g b/src/parser/cvc/Cvc.g index 8e4152e2e..fdb6a081c 100644 --- a/src/parser/cvc/Cvc.g +++ b/src/parser/cvc/Cvc.g @@ -1159,7 +1159,7 @@ declareVariables[std::unique_ptr* cmd, CVC4::api::Sort& t, PARSER_STATE->checkDeclaration(*i, CHECK_UNDECLARED, SYM_VARIABLE); api::Term func = PARSER_STATE->mkVar( *i, - t.getType(), + api::Sort(PARSER_STATE->getSolver(), t.getType()), ExprManager::VAR_FLAG_GLOBAL | ExprManager::VAR_FLAG_DEFINED); PARSER_STATE->defineVar(*i, f); Command* decl = @@ -1654,7 +1654,9 @@ tupleStore[CVC4::api::Term& f] } const Datatype & dt = ((DatatypeType)t.getType()).getDatatype(); f2 = SOLVER->mkTerm( - api::APPLY_SELECTOR, api::Term(dt[0][k].getSelector()), f); + api::APPLY_SELECTOR, + api::Term(PARSER_STATE->getSolver(), dt[0][k].getSelector()), + f); } ( ( arrayStore[f2] | DOT ( tupleStore[f2] @@ -1687,7 +1689,9 @@ recordStore[CVC4::api::Term& f] } const Datatype & dt = ((DatatypeType)t.getType()).getDatatype(); f2 = SOLVER->mkTerm( - api::APPLY_SELECTOR, api::Term(dt[0][id].getSelector()), f); + api::APPLY_SELECTOR, + api::Term(PARSER_STATE->getSolver(), dt[0][id].getSelector()), + f); } ( ( arrayStore[f2] | DOT ( tupleStore[f2] @@ -1831,7 +1835,10 @@ postfixTerm[CVC4::api::Term& f] PARSER_STATE->parseError(std::string("no such field `") + id + "' in record"); } const Datatype & dt = ((DatatypeType)type.getType()).getDatatype(); - f = SOLVER->mkTerm(api::APPLY_SELECTOR,api::Term(dt[0][id].getSelector()), f); + f = SOLVER->mkTerm( + api::APPLY_SELECTOR, + api::Term(PARSER_STATE->getSolver(), dt[0][id].getSelector()), + f); } | k=numeral { @@ -1846,7 +1853,10 @@ postfixTerm[CVC4::api::Term& f] PARSER_STATE->parseError(ss.str()); } const Datatype & dt = ((DatatypeType)type.getType()).getDatatype(); - f = SOLVER->mkTerm(api::APPLY_SELECTOR,api::Term(dt[0][k].getSelector()), f); + f = SOLVER->mkTerm( + api::APPLY_SELECTOR, + api::Term(PARSER_STATE->getSolver(), dt[0][k].getSelector()), + f); } ) )* @@ -1857,7 +1867,7 @@ postfixTerm[CVC4::api::Term& f] | ABS_TOK LPAREN formula[f] RPAREN { f = MK_TERM(CVC4::api::ABS, f); } | DIVISIBLE_TOK LPAREN formula[f] COMMA n=numeral RPAREN - { f = MK_TERM(SOLVER->mkOp(CVC4::api::DIVISIBLE,n), f); } + { f = MK_TERM(SOLVER->mkOp(CVC4::api::DIVISIBLE, n), f); } | DISTINCT_TOK LPAREN formula[f] { args.push_back(f); } ( COMMA formula[f] { args.push_back(f); } )* RPAREN @@ -1868,7 +1878,7 @@ postfixTerm[CVC4::api::Term& f] ) ( typeAscription[f, t] { - f = PARSER_STATE->applyTypeAscription(f,t).getExpr(); + f = PARSER_STATE->applyTypeAscription(f,t); } )? ; @@ -1885,8 +1895,9 @@ relationTerm[CVC4::api::Term& f] args.push_back(f); types.push_back(f.getSort()); api::Sort t = SOLVER->mkTupleSort(types); - const Datatype& dt = ((DatatypeType)t.getType()).getDatatype(); - args.insert( args.begin(), api::Term(dt[0].getConstructor()) ); + const Datatype& dt = Datatype(((DatatypeType)t.getType()).getDatatype()); + args.insert(args.begin(), + api::Term(PARSER_STATE->getSolver(), dt[0].getConstructor())); f = MK_TERM(api::APPLY_CONSTRUCTOR, args); } | IDEN_TOK LPAREN formula[f] RPAREN @@ -2136,7 +2147,9 @@ simpleTerm[CVC4::api::Term& f] } api::Sort dtype = SOLVER->mkTupleSort(types); const Datatype& dt = ((DatatypeType)dtype.getType()).getDatatype(); - args.insert( args.begin(), dt[0].getConstructor() ); + args.insert( + args.begin(), + api::Term(PARSER_STATE->getSolver(), dt[0].getConstructor())); f = MK_TERM(api::APPLY_CONSTRUCTOR, args); } } @@ -2146,7 +2159,9 @@ simpleTerm[CVC4::api::Term& f] { std::vector types; api::Sort dtype = SOLVER->mkTupleSort(types); const Datatype& dt = ((DatatypeType)dtype.getType()).getDatatype(); - f = MK_TERM(api::APPLY_CONSTRUCTOR, api::Term(dt[0].getConstructor())); } + f = MK_TERM(api::APPLY_CONSTRUCTOR, + api::Term(PARSER_STATE->getSolver(), dt[0].getConstructor())); + } /* empty record literal */ | PARENHASH HASHPAREN @@ -2154,7 +2169,8 @@ simpleTerm[CVC4::api::Term& f] api::Sort dtype = SOLVER->mkRecordSort( std::vector>()); const Datatype& dt = ((DatatypeType)dtype.getType()).getDatatype(); - f = MK_TERM(api::APPLY_CONSTRUCTOR, api::Term(dt[0].getConstructor())); + f = MK_TERM(api::APPLY_CONSTRUCTOR, + api::Term(PARSER_STATE->getSolver(), dt[0].getConstructor())); } /* empty set literal */ | LBRACE RBRACE @@ -2252,7 +2268,8 @@ simpleTerm[CVC4::api::Term& f] } api::Sort dtype = SOLVER->mkRecordSort(typeIds); const Datatype& dt = ((DatatypeType)dtype.getType()).getDatatype(); - args.insert( args.begin(), dt[0].getConstructor() ); + args.insert(args.begin(), + api::Term(PARSER_STATE->getSolver(), dt[0].getConstructor())); f = MK_TERM(api::APPLY_CONSTRUCTOR, args); } diff --git a/src/parser/parser.cpp b/src/parser/parser.cpp index c860d14c7..b24f9ae9d 100644 --- a/src/parser/parser.cpp +++ b/src/parser/parser.cpp @@ -82,14 +82,9 @@ api::Term Parser::getSymbol(const std::string& name, SymbolType type) { checkDeclaration(name, CHECK_DECLARED, type); assert(isDeclared(name, type)); - - if (type == SYM_VARIABLE) { - // Functions share var namespace - return d_symtab->lookup(name); - } - - assert(false); // Unhandled(type); - return Expr(); + assert(type == SYM_VARIABLE); + // Functions share var namespace + return api::Term(d_solver, d_symtab->lookup(name)); } api::Term Parser::getVariable(const std::string& name) @@ -166,7 +161,7 @@ api::Sort Parser::getSort(const std::string& name) { checkDeclaration(name, CHECK_DECLARED, SYM_SORT); assert(isDeclared(name, SYM_SORT)); - api::Sort t = api::Sort(d_symtab->lookupType(name)); + api::Sort t = api::Sort(d_solver, d_symtab->lookupType(name)); return t; } @@ -175,8 +170,8 @@ api::Sort Parser::getSort(const std::string& name, { checkDeclaration(name, CHECK_DECLARED, SYM_SORT); assert(isDeclared(name, SYM_SORT)); - api::Sort t = - api::Sort(d_symtab->lookupType(name, api::sortVectorToTypes(params))); + api::Sort t = api::Sort( + d_solver, d_symtab->lookupType(name, api::sortVectorToTypes(params))); return t; } @@ -237,7 +232,8 @@ std::vector Parser::bindBoundVars( std::vector vars; for (std::pair& i : sortedVarNames) { - vars.push_back(bindBoundVar(i.first, i.second.getType())); + vars.push_back( + bindBoundVar(i.first, api::Sort(d_solver, i.second.getType()))); } return vars; } @@ -251,7 +247,7 @@ api::Term Parser::mkAnonymousFunction(const std::string& prefix, } stringstream name; name << prefix << "_anon_" << ++d_anonymousFunctionCount; - return mkVar(name.str(), type.getType(), flags); + return mkVar(name.str(), api::Sort(d_solver, type.getType()), flags); } std::vector Parser::bindVars(const std::vector names, @@ -334,7 +330,8 @@ void Parser::defineParameterizedType(const std::string& name, api::Sort Parser::mkSort(const std::string& name, uint32_t flags) { Debug("parser") << "newSort(" << name << ")" << std::endl; - api::Sort type = d_solver->getExprManager()->mkSort(name, flags); + api::Sort type = + api::Sort(d_solver, d_solver->getExprManager()->mkSort(name, flags)); defineType( name, type, @@ -348,8 +345,9 @@ api::Sort Parser::mkSortConstructor(const std::string& name, { Debug("parser") << "newSortConstructor(" << name << ", " << arity << ")" << std::endl; - api::Sort type = - d_solver->getExprManager()->mkSortConstructor(name, arity, flags); + api::Sort type = api::Sort( + d_solver, + d_solver->getExprManager()->mkSortConstructor(name, arity, flags)); defineType( name, vector(arity), @@ -379,8 +377,10 @@ api::Sort Parser::mkUnresolvedTypeConstructor( { Debug("parser") << "newSortConstructor(P)(" << name << ", " << params.size() << ")" << std::endl; - api::Sort unresolved = d_solver->getExprManager()->mkSortConstructor( - name, params.size(), ExprManager::SORT_FLAG_PLACEHOLDER); + api::Sort unresolved = + api::Sort(d_solver, + d_solver->getExprManager()->mkSortConstructor( + name, params.size(), ExprManager::SORT_FLAG_PLACEHOLDER)); defineType(name, params, unresolved); api::Sort t = getSort(name, params); d_unresolved.insert(unresolved); @@ -588,11 +588,12 @@ api::Term Parser::applyTypeAscription(api::Term t, api::Sort s) Expr e = t.getExpr(); const DatatypeConstructor& dtc = Datatype::datatypeOf(e)[Datatype::indexOf(e)]; - t = api::Term(em->mkExpr( - kind::APPLY_TYPE_ASCRIPTION, - em->mkConst( - AscriptionType(dtc.getSpecializedConstructorType(s.getType()))), - e)); + t = api::Term( + d_solver, + em->mkExpr(kind::APPLY_TYPE_ASCRIPTION, + em->mkConst(AscriptionType( + dtc.getSpecializedConstructorType(s.getType()))), + e)); } // the type of t does not match the sort s by design (constructor type // vs datatype type), thus we use an alternative check here. @@ -624,7 +625,7 @@ api::Term Parser::mkVar(const std::string& name, uint32_t flags) { return api::Term( - d_solver->getExprManager()->mkVar(name, type.getType(), flags)); + d_solver, d_solver->getExprManager()->mkVar(name, type.getType(), flags)); } //!!!!!!!!!!! temporary @@ -892,16 +893,16 @@ std::vector Parser::processAdHocStringEsc(const std::string& s) return str; } -Expr Parser::mkStringConstant(const std::string& s) +api::Term Parser::mkStringConstant(const std::string& s) { ExprManager* em = d_solver->getExprManager(); if (language::isInputLang_smt2_6(em->getOptions().getInputLanguage())) { - return d_solver->mkString(s, true).getExpr(); + return api::Term(d_solver, 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(); + return api::Term(d_solver, d_solver->mkString(str).getExpr()); } } /* CVC4::parser namespace */ diff --git a/src/parser/parser.h b/src/parser/parser.h index 7941cfdd5..b5dc83902 100644 --- a/src/parser/parser.h +++ b/src/parser/parser.h @@ -808,7 +808,7 @@ public: inline SymbolTable* getSymbolTable() const { return d_symtab; } - + //------------------------ operator overloading /** is this function overloaded? */ bool isOverloadedFunction(api::Term fun) @@ -822,7 +822,8 @@ public: */ api::Term getOverloadedConstantForType(const std::string& name, api::Sort t) { - return d_symtab->getOverloadedConstantForType(name, t.getType()); + return api::Term(d_solver, + d_symtab->getOverloadedConstantForType(name, t.getType())); } /** @@ -833,8 +834,9 @@ public: api::Term getOverloadedFunctionForTypes(const std::string& name, std::vector& argTypes) { - return d_symtab->getOverloadedFunctionForTypes( - name, api::sortVectorToTypes(argTypes)); + return api::Term(d_solver, + d_symtab->getOverloadedFunctionForTypes( + name, api::sortVectorToTypes(argTypes))); } //------------------------ end operator overloading /** @@ -845,7 +847,7 @@ public: * SMT-LIB 2.6 or higher), or otherwise calling the solver to construct * the string. */ - Expr mkStringConstant(const std::string& s); + api::Term mkStringConstant(const std::string& s); private: /** ad-hoc string escaping diff --git a/src/parser/smt2/Smt2.g b/src/parser/smt2/Smt2.g index 95f4b1a67..081990a45 100644 --- a/src/parser/smt2/Smt2.g +++ b/src/parser/smt2/Smt2.g @@ -101,7 +101,7 @@ namespace CVC4 { struct myExpr : public CVC4::api::Term { myExpr() : CVC4::api::Term() {} myExpr(void*) : CVC4::api::Term() {} - myExpr(const Expr& e) : CVC4::api::Term(e) {} + myExpr(const Expr& e) : CVC4::api::Term(d_solver, e) {} myExpr(const myExpr& e) : CVC4::api::Term(e) {} };/* struct myExpr */ }/* CVC4::parser::smt2 namespace */ @@ -286,7 +286,7 @@ command [std::unique_ptr* cmd] { PARSER_STATE->popScope(); // Do NOT call mkSort, since that creates a new sort! // This name is not its own distinct sort, it's an alias. - PARSER_STATE->defineParameterizedType(name, sorts, t.getType()); + PARSER_STATE->defineParameterizedType(name, sorts, t); cmd->reset(new DefineTypeCommand( name, api::sortVectorToTypes(sorts), t.getType())); } @@ -800,7 +800,7 @@ sygusGrammarV1[CVC4::api::Sort & ret, PARSER_STATE->getUnresolvedSorts().clear(); - ret = datatypeTypes[0]; + ret = api::Sort(PARSER_STATE->getSolver(), datatypeTypes[0]); }; // SyGuS grammar term. @@ -893,7 +893,7 @@ sygusGTerm[CVC4::SygusGTerm& sgt, const std::string& fun] << "expression " << atomTerm << std::endl; std::stringstream ss; ss << atomTerm; - sgt.d_op.d_expr = atomTerm.getExpr(); + sgt.d_op.d_expr = atomTerm; sgt.d_name = ss.str(); sgt.d_gterm_type = SygusGTerm::gterm_op; } @@ -1791,8 +1791,10 @@ termNonVariable[CVC4::api::Term& expr, CVC4::api::Term& expr2] Expr ef = f.getExpr(); if (Datatype::datatypeOf(ef).isParametric()) { - type = Datatype::datatypeOf(ef)[Datatype::indexOf(ef)] - .getSpecializedConstructorType(expr.getSort().getType()); + type = api::Sort( + PARSER_STATE->getSolver(), + Datatype::datatypeOf(ef)[Datatype::indexOf(ef)] + .getSpecializedConstructorType(expr.getSort().getType())); } argTypes = type.getConstructorDomainSorts(); } @@ -1914,10 +1916,10 @@ termNonVariable[CVC4::api::Term& expr, CVC4::api::Term& expr2] sorts.emplace_back(arg.getSort()); terms.emplace_back(arg); } - expr = SOLVER->mkTuple(sorts, terms).getExpr(); + expr = SOLVER->mkTuple(sorts, terms); } | /* an atomic term (a term with no subterms) */ - termAtomic[atomTerm] { expr = atomTerm.getExpr(); } + termAtomic[atomTerm] { expr = atomTerm; } ; diff --git a/src/parser/smt2/smt2.cpp b/src/parser/smt2/smt2.cpp index 9ca2194f4..608e47a6b 100644 --- a/src/parser/smt2/smt2.cpp +++ b/src/parser/smt2/smt2.cpp @@ -1377,7 +1377,7 @@ void Smt2::mkSygusDatatype(api::DatatypeDecl& dt, if( std::find( types.begin(), types.end(), t )==types.end() ){ types.push_back( t ); //identity element - api::Sort bt = dt.getDatatype().getSygusType(); + api::Sort bt = api::Sort(d_solver, dt.getDatatype().getSygusType()); Debug("parser-sygus") << ": make identity function for " << bt << ", argument type " << t << std::endl; std::stringstream ss; @@ -1481,7 +1481,7 @@ api::Term Smt2::purifySygusGTerm(api::Term term, api::Term ret = d_solver->mkVar(term.getSort()); Trace("parser-sygus2-debug") << "...unresolved non-terminal, intro " << ret << std::endl; - args.push_back(ret.getExpr()); + args.push_back(api::Term(d_solver, ret.getExpr())); cargs.push_back(itn->second); return ret; } @@ -1571,8 +1571,7 @@ void Smt2::parseOpApplyTypeAscription(ParseOp& p, api::Sort type) Trace("parser-qid") << " " << p.d_expr.getKind() << " " << p.d_expr.getSort(); Trace("parser-qid") << std::endl; // otherwise, we process the type ascription - p.d_expr = - applyTypeAscription(api::Term(p.d_expr), api::Sort(type)).getExpr(); + p.d_expr = applyTypeAscription(p.d_expr, type); } api::Term Smt2::parseOpToExpr(ParseOp& p) @@ -1776,8 +1775,10 @@ api::Term Smt2::applyParseOp(ParseOp& p, std::vector& args) parseError(ss.str()); } const Datatype& dt = ((DatatypeType)t.getType()).getDatatype(); - api::Term ret = d_solver->mkTerm( - api::APPLY_SELECTOR, api::Term(dt[0][n].getSelector()), args[0]); + api::Term ret = + d_solver->mkTerm(api::APPLY_SELECTOR, + api::Term(d_solver, dt[0][n].getSelector()), + args[0]); Debug("parser") << "applyParseOp: return selector " << ret << std::endl; return ret; } diff --git a/src/parser/tptp/Tptp.g b/src/parser/tptp/Tptp.g index c2f4675b1..c1d60ca31 100644 --- a/src/parser/tptp/Tptp.g +++ b/src/parser/tptp/Tptp.g @@ -1441,7 +1441,7 @@ tffLetTermBinding[std::vector & bvlist, PARSER_STATE->checkLetBinding(bvlist, lhs, rhs, false); std::vector lchildren(++lhs.begin(), lhs.end()); rhs = MK_TERM(api::LAMBDA, MK_TERM(api::BOUND_VAR_LIST, lchildren), rhs); - lhs = api::Term(lhs.getExpr().getOperator()); + lhs = api::Term(PARSER_STATE->getSolver(), lhs.getExpr().getOperator()); } | LPAREN_TOK tffLetTermBinding[bvlist, lhs, rhs] RPAREN_TOK ; @@ -1463,7 +1463,7 @@ tffLetFormulaBinding[std::vector & bvlist, PARSER_STATE->checkLetBinding(bvlist, lhs, rhs, true); std::vector lchildren(++lhs.begin(), lhs.end()); rhs = MK_TERM(api::LAMBDA, MK_TERM(api::BOUND_VAR_LIST, lchildren), rhs); - lhs = api::Term(lhs.getExpr().getOperator()); + lhs = api::Term(PARSER_STATE->getSolver(), lhs.getExpr().getOperator()); } | LPAREN_TOK tffLetFormulaBinding[bvlist, lhs, rhs] RPAREN_TOK ; diff --git a/test/unit/api/op_black.h b/test/unit/api/op_black.h index e99e8daf2..27ca7bb88 100644 --- a/test/unit/api/op_black.h +++ b/test/unit/api/op_black.h @@ -52,7 +52,7 @@ void OpBlack::testIsNull() void OpBlack::testOpFromKind() { - Op plus(PLUS); + Op plus(&d_solver, PLUS); TS_ASSERT(!plus.isIndexed()); TS_ASSERT_THROWS(plus.getIndices(), CVC4ApiException&); diff --git a/test/unit/api/solver_black.h b/test/unit/api/solver_black.h index 07b5c5aec..90d4c10c1 100644 --- a/test/unit/api/solver_black.h +++ b/test/unit/api/solver_black.h @@ -978,13 +978,13 @@ void SolverBlack::testGetOp() Term listhead = d_solver->mkTerm(APPLY_SELECTOR, headTerm, listcons1); TS_ASSERT(listnil.hasOp()); - TS_ASSERT_EQUALS(listnil.getOp(), APPLY_CONSTRUCTOR); + TS_ASSERT_EQUALS(listnil.getOp(), Op(d_solver.get(), APPLY_CONSTRUCTOR)); TS_ASSERT(listcons1.hasOp()); - TS_ASSERT_EQUALS(listcons1.getOp(), APPLY_CONSTRUCTOR); + TS_ASSERT_EQUALS(listcons1.getOp(), Op(d_solver.get(), APPLY_CONSTRUCTOR)); TS_ASSERT(listhead.hasOp()); - TS_ASSERT_EQUALS(listhead.getOp(), APPLY_SELECTOR); + TS_ASSERT_EQUALS(listhead.getOp(), Op(d_solver.get(), APPLY_SELECTOR)); } void SolverBlack::testPush1() diff --git a/test/unit/api/term_black.h b/test/unit/api/term_black.h index 78d6ee5cc..d8f022201 100644 --- a/test/unit/api/term_black.h +++ b/test/unit/api/term_black.h @@ -175,10 +175,10 @@ void TermBlack::testGetOp() Term extb = d_solver.mkTerm(ext, b); TS_ASSERT(ab.hasOp()); - TS_ASSERT_EQUALS(ab.getOp(), Op(SELECT)); + TS_ASSERT_EQUALS(ab.getOp(), Op(&d_solver, SELECT)); TS_ASSERT(!ab.getOp().isIndexed()); // can compare directly to a Kind (will invoke Op constructor) - TS_ASSERT_EQUALS(ab.getOp(), SELECT); + TS_ASSERT_EQUALS(ab.getOp(), Op(&d_solver, SELECT)); TS_ASSERT(extb.hasOp()); TS_ASSERT(extb.getOp().isIndexed()); TS_ASSERT_EQUALS(extb.getOp(), ext); @@ -189,7 +189,7 @@ void TermBlack::testGetOp() TS_ASSERT(!f.hasOp()); TS_ASSERT_THROWS(f.getOp(), CVC4ApiException&); TS_ASSERT(fx.hasOp()); - TS_ASSERT_EQUALS(fx.getOp(), APPLY_UF); + TS_ASSERT_EQUALS(fx.getOp(), Op(&d_solver, APPLY_UF)); std::vector children(fx.begin(), fx.end()); // testing rebuild from op and children TS_ASSERT_EQUALS(fx, d_solver.mkTerm(fx.getOp(), children)); @@ -225,10 +225,10 @@ void TermBlack::testGetOp() TS_ASSERT(headTerm.hasOp()); TS_ASSERT(tailTerm.hasOp()); - TS_ASSERT_EQUALS(nilTerm.getOp(), APPLY_CONSTRUCTOR); - TS_ASSERT_EQUALS(consTerm.getOp(), APPLY_CONSTRUCTOR); - TS_ASSERT_EQUALS(headTerm.getOp(), APPLY_SELECTOR); - TS_ASSERT_EQUALS(tailTerm.getOp(), APPLY_SELECTOR); + TS_ASSERT_EQUALS(nilTerm.getOp(), Op(&d_solver, APPLY_CONSTRUCTOR)); + TS_ASSERT_EQUALS(consTerm.getOp(), Op(&d_solver, APPLY_CONSTRUCTOR)); + TS_ASSERT_EQUALS(headTerm.getOp(), Op(&d_solver, APPLY_SELECTOR)); + TS_ASSERT_EQUALS(tailTerm.getOp(), Op(&d_solver, APPLY_SELECTOR)); // Test rebuilding children.clear(); diff --git a/test/unit/parser/parser_builder_black.h b/test/unit/parser/parser_builder_black.h index 44bb9293b..2962439ff 100644 --- a/test/unit/parser/parser_builder_black.h +++ b/test/unit/parser/parser_builder_black.h @@ -111,7 +111,7 @@ class ParserBuilderBlack : public CxxTest::TestSuite TS_ASSERT(parser != NULL); api::Term e = parser->nextExpression(); - TS_ASSERT_EQUALS(e, d_solver->getExprManager()->mkConst(true)); + TS_ASSERT_EQUALS(e, d_solver->mkTrue()); e = parser->nextExpression(); TS_ASSERT(e.isNull()); -- cgit v1.2.3 From e4926117ce53433e59f4b1a86892ea43a01f709d Mon Sep 17 00:00:00 2001 From: Andrew Reynolds Date: Tue, 2 Jun 2020 12:22:17 -0500 Subject: Do not handle universal quantification on functions in model-based FMF (#4226) Fixes #4225, fixes CVC4/cvc4-projects#159, fixes CVC4/cvc4-projects#157, fixes #4289, fixes #4483. This makes it so that the main model-based instantiation algorithm is not applied to quantified formulas with universally quantified functions. Identation changed in a FMF function, this was refactored to conform to guidelines, and further cleaned. --- src/theory/quantifiers/fmf/full_model_check.cpp | 295 +++++++++++++--------- src/theory/quantifiers/fmf/full_model_check.h | 19 ++ test/regress/CMakeLists.txt | 1 + test/regress/regress1/fmf/issue4225-univ-fun.smt2 | 6 + 4 files changed, 203 insertions(+), 118 deletions(-) create mode 100644 test/regress/regress1/fmf/issue4225-univ-fun.smt2 (limited to 'src') diff --git a/src/theory/quantifiers/fmf/full_model_check.cpp b/src/theory/quantifiers/fmf/full_model_check.cpp index af3a94d96..91cacdc2e 100644 --- a/src/theory/quantifiers/fmf/full_model_check.cpp +++ b/src/theory/quantifiers/fmf/full_model_check.cpp @@ -592,139 +592,168 @@ void FullModelChecker::debugPrint(const char * tr, Node n, bool dispStar) { int FullModelChecker::doExhaustiveInstantiation( FirstOrderModel * fm, Node f, int effort ) { Trace("fmc") << "Full model check " << f << ", effort = " << effort << "..." << std::endl; + // register the quantifier + registerQuantifiedFormula(f); Assert(!d_qe->inConflict()); - if( optUseModel() ){ - FirstOrderModelFmc * fmfmc = fm->asFirstOrderModelFmc(); - if (effort==0) { - //register the quantifier - if (d_quant_cond.find(f)==d_quant_cond.end()) { - std::vector< TypeNode > types; - for(unsigned i=0; iasFirstOrderModelFmc(); + if (effort == 0) + { + if (options::mbqiMode() == options::MbqiMode::NONE) + { + // just exhaustive instantiate + Node c = mkCondDefault(fmfmc, f); + d_quant_models[f].addEntry(fmfmc, c, d_false); + if (!exhaustiveInstantiate(fmfmc, f, c, -1)) + { + return 0; + } + return 1; + } + // model check the quantifier + doCheck(fmfmc, f, d_quant_models[f], f[1]); + std::vector& mcond = d_quant_models[f].d_cond; + Trace("fmc") << "Definition for quantifier " << f << " is : " << std::endl; + Assert(!mcond.empty()); + d_quant_models[f].debugPrint("fmc", Node::null(), this); + Trace("fmc") << std::endl; + + // consider all entries going to non-true + Instantiate* instq = d_qe->getInstantiate(); + for (unsigned i = 0, msize = mcond.size(); i < msize; i++) + { + if (d_quant_models[f].d_value[i] == d_true) + { + // already satisfied + continue; + } + Trace("fmc-inst") << "Instantiate based on " << mcond[i] << "..." + << std::endl; + bool hasStar = false; + std::vector inst; + for (unsigned j = 0, nchild = mcond[i].getNumChildren(); j < nchild; j++) + { + if (fmfmc->isStar(mcond[i][j])) + { + hasStar = true; + inst.push_back(fmfmc->getModelBasisTerm(mcond[i][j].getType())); + } + else + { + inst.push_back(mcond[i][j]); } - TypeNode typ = NodeManager::currentNM()->mkFunctionType( types, NodeManager::currentNM()->booleanType() ); - Node op = NodeManager::currentNM()->mkSkolem( "qfmc", typ, "op created for full-model checking" ); - d_quant_cond[f] = op; } - - if (options::mbqiMode() == options::MbqiMode::NONE) + bool addInst = true; + if (hasStar) { - //just exhaustive instantiate - Node c = mkCondDefault( fmfmc, f ); - d_quant_models[f].addEntry( fmfmc, c, d_false ); - return exhaustiveInstantiate( fmfmc, f, c, -1); + // try obvious (specified by inst) + Node ev = d_quant_models[f].evaluate(fmfmc, inst); + if (ev == d_true) + { + addInst = false; + Trace("fmc-debug") + << "...do not instantiate, evaluation was " << ev << std::endl; + } } else { - //model check the quantifier - doCheck(fmfmc, f, d_quant_models[f], f[1]); - Trace("fmc") << "Definition for quantifier " << f << " is : " << std::endl; - Assert(!d_quant_models[f].d_cond.empty()); - d_quant_models[f].debugPrint("fmc", Node::null(), this); - Trace("fmc") << std::endl; - - //consider all entries going to non-true - for (unsigned i=0; i inst; - for (unsigned j=0; jisStar(d_quant_models[f].d_cond[i][j])) { - hasStar = true; - inst.push_back(fmfmc->getModelBasisTerm(d_quant_models[f].d_cond[i][j].getType())); - }else{ - inst.push_back(d_quant_models[f].d_cond[i][j]); - } - } - bool addInst = true; - if( hasStar ){ - //try obvious (specified by inst) - Node ev = d_quant_models[f].evaluate(fmfmc, inst); - if (ev==d_true) { - addInst = false; - Trace("fmc-debug") << "...do not instantiate, evaluation was " << ev << std::endl; - } - }else{ - //for debugging - if (Trace.isOn("fmc-test-inst")) { - Node ev = d_quant_models[f].evaluate(fmfmc, inst); - if( ev==d_true ){ - Message() << "WARNING: instantiation was true! " << f << " " - << d_quant_models[f].d_cond[i] << std::endl; - AlwaysAssert(false); - }else{ - Trace("fmc-test-inst") << "...instantiation evaluated to false." << std::endl; - } - } - } - if( addInst ){ - if( options::fmfBound() ){ - std::vector< Node > cond; - cond.push_back(d_quant_cond[f]); - cond.insert( cond.end(), inst.begin(), inst.end() ); - //need to do exhaustive instantiate algorithm to set things properly (should only add one instance) - Node c = mkCond( cond ); - unsigned prevInst = d_addedLemmas; - exhaustiveInstantiate( fmfmc, f, c, -1 ); - if( d_addedLemmas==prevInst ){ - d_star_insts[f].push_back(i); - } - }else{ - //just add the instance - d_triedLemmas++; - if (d_qe->getInstantiate()->addInstantiation(f, inst, true)) - { - Trace("fmc-debug-inst") << "** Added instantiation." << std::endl; - d_addedLemmas++; - if( d_qe->inConflict() || options::fmfOneInstPerRound() ){ - break; - } - }else{ - Trace("fmc-debug-inst") << "** Instantiation was duplicate." << std::endl; - //this can happen if evaluation is unknown, or if we are generalizing a star that already has a value - //if( !hasStar && d_quant_models[f].d_value[i]==d_false ){ - // Trace("fmc-warn") << "**** FMC warning: inconsistent duplicate instantiation." << std::endl; - //} - //this assertion can happen if two instantiations from this round are identical - // (0,1)->false (1,0)->false for forall xy. f( x, y ) = f( y, x ) - //Assert( hasStar || d_quant_models[f].d_value[i]!=d_false ); - //might try it next effort level - d_star_insts[f].push_back(i); - } - } - }else{ - Trace("fmc-debug-inst") << "** Instantiation was already true." << std::endl; - //might try it next effort level - d_star_insts[f].push_back(i); - } + // for debugging + if (Trace.isOn("fmc-test-inst")) + { + Node ev = d_quant_models[f].evaluate(fmfmc, inst); + if (ev == d_true) + { + Message() << "WARNING: instantiation was true! " << f << " " + << mcond[i] << std::endl; + AlwaysAssert(false); + } + else + { + Trace("fmc-test-inst") + << "...instantiation evaluated to false." << std::endl; } } } - }else{ - if (!d_star_insts[f].empty()) { - Trace("fmc-exh") << "Exhaustive instantiate " << f << std::endl; - Trace("fmc-exh") << "Definition was : " << std::endl; - d_quant_models[f].debugPrint("fmc-exh", Node::null(), this); - Trace("fmc-exh") << std::endl; - Def temp; - //simplify the exceptions? - for( int i=(d_star_insts[f].size()-1); i>=0; i--) { - //get witness for d_star_insts[f][i] - int j = d_star_insts[f][i]; - if( temp.addEntry(fmfmc, d_quant_models[f].d_cond[j], d_quant_models[f].d_value[j] ) ){ - if( !exhaustiveInstantiate(fmfmc, f, d_quant_models[f].d_cond[j], j ) ){ - //something went wrong, resort to exhaustive instantiation - return 0; - } - } + if (!addInst) + { + Trace("fmc-debug-inst") + << "** Instantiation was already true." << std::endl; + // might try it next effort level + d_star_insts[f].push_back(i); + continue; + } + if (options::fmfBound()) + { + std::vector cond; + cond.push_back(d_quant_cond[f]); + cond.insert(cond.end(), inst.begin(), inst.end()); + // need to do exhaustive instantiate algorithm to set things properly + // (should only add one instance) + Node c = mkCond(cond); + unsigned prevInst = d_addedLemmas; + exhaustiveInstantiate(fmfmc, f, c, -1); + if (d_addedLemmas == prevInst) + { + d_star_insts[f].push_back(i); } + continue; + } + // just add the instance + d_triedLemmas++; + if (instq->addInstantiation(f, inst, true)) + { + Trace("fmc-debug-inst") << "** Added instantiation." << std::endl; + d_addedLemmas++; + if (d_qe->inConflict() || options::fmfOneInstPerRound()) + { + break; + } + } + else + { + Trace("fmc-debug-inst") + << "** Instantiation was duplicate." << std::endl; + // might try it next effort level + d_star_insts[f].push_back(i); } } return 1; - }else{ - return 0; } + // Get the list of instantiation regions (described by "star entries" in the + // definition) that were not tried at the previous effort level. For each + // of these, we add one instantiation. + std::vector& mcond = d_quant_models[f].d_cond; + if (!d_star_insts[f].empty()) + { + if (Trace.isOn("fmc-exh")) + { + Trace("fmc-exh") << "Exhaustive instantiate " << f << std::endl; + Trace("fmc-exh") << "Definition was : " << std::endl; + d_quant_models[f].debugPrint("fmc-exh", Node::null(), this); + Trace("fmc-exh") << std::endl; + } + Def temp; + // simplify the exceptions? + for (int i = (d_star_insts[f].size() - 1); i >= 0; i--) + { + // get witness for d_star_insts[f][i] + int j = d_star_insts[f][i]; + if (temp.addEntry(fmfmc, mcond[j], d_quant_models[f].d_value[j])) + { + if (!exhaustiveInstantiate(fmfmc, f, mcond[j], j)) + { + // something went wrong, resort to exhaustive instantiation + return 0; + } + } + } + } + return 1; } /** Representative bound fmc entry @@ -1290,3 +1319,33 @@ Node FullModelChecker::getFunctionValue(FirstOrderModelFmc * fm, Node op, const bool FullModelChecker::useSimpleModels() { return options::fmfFmcSimple(); } + +void FullModelChecker::registerQuantifiedFormula(Node q) +{ + if (d_quant_cond.find(q) != d_quant_cond.end()) + { + return; + } + NodeManager* nm = NodeManager::currentNM(); + std::vector types; + for (const Node& v : q[0]) + { + TypeNode tn = v.getType(); + if (tn.isFunction()) + { + // we will not use model-based quantifier instantiation for q, since + // the model-based instantiation algorithm does not handle (universally + // quantified) functions + d_unhandledQuant.insert(q); + } + types.push_back(tn); + } + TypeNode typ = nm->mkFunctionType(types, nm->booleanType()); + Node op = nm->mkSkolem("qfmc", typ, "op for full-model checking"); + d_quant_cond[q] = op; +} + +bool FullModelChecker::isHandled(Node q) const +{ + return d_unhandledQuant.find(q) == d_unhandledQuant.end(); +} diff --git a/src/theory/quantifiers/fmf/full_model_check.h b/src/theory/quantifiers/fmf/full_model_check.h index 7dd1991f5..60de5d1eb 100644 --- a/src/theory/quantifiers/fmf/full_model_check.h +++ b/src/theory/quantifiers/fmf/full_model_check.h @@ -86,7 +86,16 @@ protected: Node d_false; std::map > d_rep_ids; std::map d_quant_models; + /** + * The predicate for the quantified formula. This is used to express + * conditions under which the quantified formula is false in the model. + * For example, for quantified formula (forall x:Int, y:U. P), this is + * a predicate of type (Int x U) -> Bool. + */ std::map d_quant_cond; + /** A set of quantified formulas that cannot be handled by model-based + * quantifier instantiation */ + std::unordered_set d_unhandledQuant; std::map< TypeNode, Node > d_array_cond; std::map< Node, Node > d_array_term_cond; std::map< Node, std::vector< int > > d_star_insts; @@ -155,6 +164,16 @@ public: bool processBuildModel(TheoryModel* m) override; bool useSimpleModels(); + + private: + /** + * Register quantified formula. + * This checks whether q can be handled by model-based instantiation and + * initializes the necessary information if so. + */ + void registerQuantifiedFormula(Node q); + /** Is quantified formula q handled by model-based instantiation? */ + bool isHandled(Node q) const; };/* class FullModelChecker */ }/* CVC4::theory::quantifiers::fmcheck namespace */ diff --git a/test/regress/CMakeLists.txt b/test/regress/CMakeLists.txt index 0f1b090d4..2b587c93a 100644 --- a/test/regress/CMakeLists.txt +++ b/test/regress/CMakeLists.txt @@ -1315,6 +1315,7 @@ set(regress_1_tests regress1/fmf/issue3626.smt2 regress1/fmf/issue3689.smt2 regress1/fmf/issue4068-si-qf.smt2 + regress1/fmf/issue4225-univ-fun.smt2 regress1/fmf/issue916-fmf-or.smt2 regress1/fmf/jasmin-cdt-crash.smt2 regress1/fmf/ko-bound-set.cvc diff --git a/test/regress/regress1/fmf/issue4225-univ-fun.smt2 b/test/regress/regress1/fmf/issue4225-univ-fun.smt2 new file mode 100644 index 000000000..9946a4567 --- /dev/null +++ b/test/regress/regress1/fmf/issue4225-univ-fun.smt2 @@ -0,0 +1,6 @@ +; COMMAND-LINE: --finite-model-find --uf-ho +; EXPECT: unknown +(set-logic ALL) +; this is not handled by fmf +(assert (forall ((a (-> Int Int)) (b Int)) (not (= (a b) 0)))) +(check-sat) -- cgit v1.2.3 From 6ae4eda75d5717543f7c847d4b2f58ccbbb611bf Mon Sep 17 00:00:00 2001 From: Andrew Reynolds Date: Tue, 2 Jun 2020 15:55:49 -0500 Subject: Fix scope issue with pulling ITEs in extended rewriter. (#4547) Fixes #4476. --- src/theory/quantifiers/extended_rewrite.cpp | 5 +++++ test/regress/CMakeLists.txt | 1 + test/regress/regress0/quantifiers/issue4476-ext-rew.smt2 | 5 +++++ 3 files changed, 11 insertions(+) create mode 100644 test/regress/regress0/quantifiers/issue4476-ext-rew.smt2 (limited to 'src') diff --git a/src/theory/quantifiers/extended_rewrite.cpp b/src/theory/quantifiers/extended_rewrite.cpp index 1f42c384f..8803a9df8 100644 --- a/src/theory/quantifiers/extended_rewrite.cpp +++ b/src/theory/quantifiers/extended_rewrite.cpp @@ -586,6 +586,11 @@ Node ExtendedRewriter::extendedRewriteAndOr(Node n) Node ExtendedRewriter::extendedRewritePullIte(Kind itek, Node n) { Assert(n.getKind() != ITE); + if (n.isClosure()) + { + // don't pull ITE out of quantifiers + return n; + } NodeManager* nm = NodeManager::currentNM(); TypeNode tn = n.getType(); std::vector children; diff --git a/test/regress/CMakeLists.txt b/test/regress/CMakeLists.txt index 2b587c93a..b66d4e973 100644 --- a/test/regress/CMakeLists.txt +++ b/test/regress/CMakeLists.txt @@ -740,6 +740,7 @@ set(regress_0_tests regress0/quantifiers/issue3655.smt2 regress0/quantifiers/issue4086-infs.smt2 regress0/quantifiers/issue4275-qcf-cegqi-rep.smt2 + regress0/quantifiers/issue4476-ext-rew.smt2 regress0/quantifiers/lra-triv-gn.smt2 regress0/quantifiers/macros-int-real.smt2 regress0/quantifiers/macros-real-arg.smt2 diff --git a/test/regress/regress0/quantifiers/issue4476-ext-rew.smt2 b/test/regress/regress0/quantifiers/issue4476-ext-rew.smt2 new file mode 100644 index 000000000..c54254e67 --- /dev/null +++ b/test/regress/regress0/quantifiers/issue4476-ext-rew.smt2 @@ -0,0 +1,5 @@ +(set-logic NRA) +(set-info :status sat) +(set-option :ext-rewrite-quant true) +(assert (exists ((a Real) (b Real)) (forall ((c Real)) (= (/ b (/ 1 c)) 0)))) +(check-sat) -- cgit v1.2.3 From 37d97be56ccba9c8da9b58fc5a7309ba2f4b1765 Mon Sep 17 00:00:00 2001 From: makaimann Date: Tue, 2 Jun 2020 15:18:15 -0700 Subject: Add hash Op, Sort and Term in Python bindings (#4498) --- src/api/python/cvc4.pxd | 12 ++++++++++++ src/api/python/cvc4.pxi | 18 ++++++++++++++++++ 2 files changed, 30 insertions(+) (limited to 'src') diff --git a/src/api/python/cvc4.pxd b/src/api/python/cvc4.pxd index d81d0c0bf..5bcc3c5c3 100644 --- a/src/api/python/cvc4.pxd +++ b/src/api/python/cvc4.pxd @@ -81,6 +81,10 @@ cdef extern from "api/cvc4cpp.h" namespace "CVC4::api": T getIndices[T]() except + string toString() except + + cdef cppclass OpHashFunction: + OpHashFunction() except + + size_t operator()(const Op & o) except + + cdef cppclass Result: # Note: don't even need constructor @@ -229,6 +233,10 @@ cdef extern from "api/cvc4cpp.h" namespace "CVC4::api": bint isUninterpretedSortParameterized() except + string toString() except + + cdef cppclass SortHashFunction: + SortHashFunction() except + + size_t operator()(const Sort & s) except + + cdef cppclass Term: Term() bint operator==(const Term&) except + @@ -255,6 +263,10 @@ cdef extern from "api/cvc4cpp.h" namespace "CVC4::api": const_iterator begin() except + const_iterator end() except + + cdef cppclass TermHashFunction: + TermHashFunction() except + + size_t operator()(const Term & t) except + + cdef extern from "api/cvc4cpp.h" namespace "CVC4::api::RoundingMode": cdef RoundingMode ROUND_NEAREST_TIES_TO_EVEN, diff --git a/src/api/python/cvc4.pxi b/src/api/python/cvc4.pxi index 1489b34a6..080f24c8b 100644 --- a/src/api/python/cvc4.pxi +++ b/src/api/python/cvc4.pxi @@ -15,11 +15,14 @@ from cvc4 cimport DatatypeSelector as c_DatatypeSelector from cvc4 cimport Result as c_Result from cvc4 cimport RoundingMode as c_RoundingMode from cvc4 cimport Op as c_Op +from cvc4 cimport OpHashFunction as c_OpHashFunction from cvc4 cimport Solver as c_Solver from cvc4 cimport Sort as c_Sort +from cvc4 cimport SortHashFunction as c_SortHashFunction from cvc4 cimport ROUND_NEAREST_TIES_TO_EVEN, ROUND_TOWARD_POSITIVE from cvc4 cimport ROUND_TOWARD_ZERO, ROUND_NEAREST_TIES_TO_AWAY from cvc4 cimport Term as c_Term +from cvc4 cimport TermHashFunction as c_TermHashFunction from cvc4kinds cimport Kind as c_Kind @@ -52,6 +55,12 @@ def expand_list_arg(num_req_args=0): #### Result class can have default because it's pure python +## Objects for hashing +cdef c_OpHashFunction cophash = c_OpHashFunction() +cdef c_SortHashFunction csorthash = c_SortHashFunction() +cdef c_TermHashFunction ctermhash = c_TermHashFunction() + + cdef class Datatype: cdef c_Datatype cd def __cinit__(self): @@ -188,6 +197,9 @@ cdef class Op: def __repr__(self): return self.cop.toString().decode() + def __hash__(self): + return cophash(self.cop) + def getKind(self): return kind( self.cop.getKind()) @@ -953,6 +965,9 @@ cdef class Sort: def __repr__(self): return self.csort.toString().decode() + def __hash__(self): + return csorthash(self.csort) + def isBoolean(self): return self.csort.isBoolean() @@ -1054,6 +1069,9 @@ cdef class Term: term.cterm = ci yield term + def __hash__(self): + return ctermhash(self.cterm) + def getKind(self): return kind( self.cterm.getKind()) -- cgit v1.2.3 From 6dd4efeea9fa0d9975fcffecd5af03bc081b68e7 Mon Sep 17 00:00:00 2001 From: makaimann Date: Tue, 2 Jun 2020 18:10:18 -0700 Subject: Add Term::substitute to Python bindings (#4499) --- src/api/python/cvc4.pxd | 1 + src/api/python/cvc4.pxi | 17 +++++++++++++++++ 2 files changed, 18 insertions(+) (limited to 'src') diff --git a/src/api/python/cvc4.pxd b/src/api/python/cvc4.pxd index 5bcc3c5c3..1c7071958 100644 --- a/src/api/python/cvc4.pxd +++ b/src/api/python/cvc4.pxd @@ -243,6 +243,7 @@ cdef extern from "api/cvc4cpp.h" namespace "CVC4::api": bint operator!=(const Term&) except + Kind getKind() except + Sort getSort() except + + Term substitute(const vector[Term] es, const vector[Term] & reps) except + bint hasOp() except + Op getOp() except + bint isNull() except + diff --git a/src/api/python/cvc4.pxi b/src/api/python/cvc4.pxi index 080f24c8b..fa5313f0e 100644 --- a/src/api/python/cvc4.pxi +++ b/src/api/python/cvc4.pxi @@ -1080,6 +1080,23 @@ cdef class Term: sort.csort = self.cterm.getSort() return sort + def substitute(self, list es, list replacements): + cdef vector[c_Term] ces + cdef vector[c_Term] creplacements + cdef Term term = Term() + + if len(es) != len(replacements): + raise RuntimeError("Expecting list inputs to substitute to " + "have the same length but got: " + "{} and {}".format(len(es), len(replacements))) + + for e, r in zip(es, replacements): + ces.push_back(( e).cterm) + creplacements.push_back(( r).cterm) + + term.cterm = self.cterm.substitute(ces, creplacements) + return term + def hasOp(self): return self.cterm.hasOp() -- cgit v1.2.3 From bbfb310eba34ae078ee2601c7e5ea2b56dbe1252 Mon Sep 17 00:00:00 2001 From: Andrew Reynolds Date: Tue, 2 Jun 2020 20:57:58 -0500 Subject: Use prenex normal form when using cegqi-nested-qe (#4522) Previously, we used a specialized variant of prenex normal form that allowed top level disjunctions. However, the method to put quantifiers into this form led to variable shadowing on some benchmarks in SMT-LIB LRA. This simplifies the code so that we use standard prenex normal form when cegqi-nested-qe is used and deletes the old variant (DISJ_NORMAL). --- src/options/quantifiers_options.toml | 3 - src/smt/set_defaults.cpp | 7 +- src/theory/quantifiers/quantifiers_rewriter.cpp | 228 +++++++++++------------- src/theory/quantifiers/quantifiers_rewriter.h | 21 ++- 4 files changed, 123 insertions(+), 136 deletions(-) (limited to 'src') diff --git a/src/options/quantifiers_options.toml b/src/options/quantifiers_options.toml index 1834c90c4..8ae6ea89a 100644 --- a/src/options/quantifiers_options.toml +++ b/src/options/quantifiers_options.toml @@ -46,9 +46,6 @@ header = "options/quantifiers_options.h" [[option.mode.SIMPLE]] name = "simple" help = "Do simple prenexing of same sign quantifiers." -[[option.mode.DISJ_NORMAL]] - name = "dnorm" - help = "Prenex to disjunctive prenex normal form." [[option.mode.NORMAL]] name = "norm" help = "Prenex to prenex normal form." diff --git a/src/smt/set_defaults.cpp b/src/smt/set_defaults.cpp index 867ac8a4a..bae7fbe68 100644 --- a/src/smt/set_defaults.cpp +++ b/src/smt/set_defaults.cpp @@ -1161,11 +1161,8 @@ void setDefaults(SmtEngine& smte, LogicInfo& logic) // prenexing if (options::cegqiNestedQE()) { - // only complete with prenex = disj_normal or normal - if (options::prenexQuant() <= options::PrenexQuantMode::DISJ_NORMAL) - { - options::prenexQuant.set(options::PrenexQuantMode::DISJ_NORMAL); - } + // only complete with prenex = normal + options::prenexQuant.set(options::PrenexQuantMode::NORMAL); } else if (options::globalNegate()) { diff --git a/src/theory/quantifiers/quantifiers_rewriter.cpp b/src/theory/quantifiers/quantifiers_rewriter.cpp index df86922bc..43c6e73bc 100644 --- a/src/theory/quantifiers/quantifiers_rewriter.cpp +++ b/src/theory/quantifiers/quantifiers_rewriter.cpp @@ -1342,15 +1342,19 @@ Node QuantifiersRewriter::computeVarElimination( Node body, std::vector< Node >& } Node QuantifiersRewriter::computePrenex( Node body, std::vector< Node >& args, std::vector< Node >& nargs, bool pol, bool prenexAgg ){ - if( body.getKind()==FORALL ){ + NodeManager* nm = NodeManager::currentNM(); + Kind k = body.getKind(); + if (k == FORALL) + { if( ( pol || prenexAgg ) && ( options::prenexQuantUser() || body.getNumChildren()==2 ) ){ std::vector< Node > terms; std::vector< Node > subs; //for doing prenexing of same-signed quantifiers //must rename each variable that already exists - for( unsigned i=0; imkBoundVar( body[0][i].getType() ) ); + for (const Node& v : body[0]) + { + terms.push_back(v); + subs.push_back(nm->mkBoundVar(v.getType())); } if( pol ){ args.insert( args.end(), subs.begin(), subs.end() ); @@ -1362,161 +1366,134 @@ Node QuantifiersRewriter::computePrenex( Node body, std::vector< Node >& args, s return newBody; } //must remove structure - }else if( prenexAgg && body.getKind()==kind::ITE && body.getType().isBoolean() ){ - Node nn = NodeManager::currentNM()->mkNode( kind::AND, - NodeManager::currentNM()->mkNode( kind::OR, body[0].notNode(), body[1] ), - NodeManager::currentNM()->mkNode( kind::OR, body[0], body[2] ) ); + } + else if (prenexAgg && k == ITE && body.getType().isBoolean()) + { + Node nn = nm->mkNode(AND, + nm->mkNode(OR, body[0].notNode(), body[1]), + nm->mkNode(OR, body[0], body[2])); return computePrenex( nn, args, nargs, pol, prenexAgg ); - }else if( prenexAgg && body.getKind()==kind::EQUAL && body[0].getType().isBoolean() ){ - Node nn = NodeManager::currentNM()->mkNode( kind::AND, - NodeManager::currentNM()->mkNode( kind::OR, body[0].notNode(), body[1] ), - NodeManager::currentNM()->mkNode( kind::OR, body[0], body[1].notNode() ) ); + } + else if (prenexAgg && k == EQUAL && body[0].getType().isBoolean()) + { + Node nn = nm->mkNode(AND, + nm->mkNode(OR, body[0].notNode(), body[1]), + nm->mkNode(OR, body[0], body[1].notNode())); return computePrenex( nn, args, nargs, pol, prenexAgg ); }else if( body.getType().isBoolean() ){ - Assert(body.getKind() != EXISTS); + Assert(k != EXISTS); bool childrenChanged = false; std::vector< Node > newChildren; - for( unsigned i=0; imkNode( body.getKind(), newChildren ); } + return nm->mkNode(k, newChildren); } } return body; } -Node QuantifiersRewriter::computePrenexAgg( Node n, bool topLevel, std::map< unsigned, std::map< Node, Node > >& visited ){ - unsigned tindex = topLevel ? 0 : 1; - std::map< Node, Node >::iterator itv = visited[tindex].find( n ); - if( itv!=visited[tindex].end() ){ +Node QuantifiersRewriter::computePrenexAgg(Node n, + std::map& visited) +{ + std::map< Node, Node >::iterator itv = visited.find( n ); + if( itv!=visited.end() ){ return itv->second; } - if (expr::hasClosure(n)) + if (!expr::hasClosure(n)) + { + // trivial + return n; + } + NodeManager* nm = NodeManager::currentNM(); + Node ret = n; + if (n.getKind() == NOT) + { + ret = computePrenexAgg(n[0], visited).negate(); + } + else if (n.getKind() == FORALL) { - Node ret = n; - if (topLevel - && options::prenexQuant() == options::PrenexQuantMode::DISJ_NORMAL - && (n.getKind() == AND || (n.getKind() == NOT && n[0].getKind() == OR))) + std::vector children; + children.push_back(computePrenexAgg(n[1], visited)); + std::vector args; + args.insert(args.end(), n[0].begin(), n[0].end()); + // for each child, strip top level quant + for (unsigned i = 0; i < children.size(); i++) { - std::vector< Node > children; - Node nc = n.getKind()==NOT ? n[0] : n; - for( unsigned i=0; imkNode( AND, children ); } - else if (n.getKind() == NOT) + // keep the pattern + std::vector iplc; + if (n.getNumChildren() == 3) { - ret = computePrenexAgg( n[0], false, visited ).negate(); + iplc.insert(iplc.end(), n[2].begin(), n[2].end()); } - else if (n.getKind() == FORALL) + Node nb = children.size() == 1 ? children[0] : nm->mkNode(OR, children); + ret = mkForall(args, nb, iplc, true); + } + else + { + std::vector args; + std::vector nargs; + Node nn = computePrenex(n, args, nargs, true, true); + if (n != nn) { - /* - Node nn = computePrenexAgg( n[1], false ); - if( nn!=n[1] ){ - if( n.getNumChildren()==2 ){ - return NodeManager::currentNM()->mkNode( FORALL, n[0], nn ); - }else{ - return NodeManager::currentNM()->mkNode( FORALL, n[0], nn, n[2] ); - } - } - */ - std::vector< Node > children; - if (n[1].getKind() == OR - && options::prenexQuant() == options::PrenexQuantMode::DISJ_NORMAL) + Node nnn = computePrenexAgg(nn, visited); + // merge prenex + if (nnn.getKind() == FORALL) { - for( unsigned i=0; i args; - for( unsigned i=0; i nargs; - //for each child, strip top level quant - for( unsigned i=0; i iplc; - if( n.getNumChildren()==3 ){ - for( unsigned i=0; imkNode( OR, children ); - ret = mkForall( args, nb, iplc, true ); + ret = nnn; } else { - std::vector< Node > args; - std::vector< Node > nargs; - Node nn = computePrenex( n, args, nargs, true, true ); - if( n!=nn ){ - Node nnn = computePrenexAgg( nn, false, visited ); - //merge prenex - if( nnn.getKind()==FORALL ){ - for( unsigned i=0; i& args, Node body, QAttributes& qa ) { @@ -1925,8 +1902,7 @@ Node QuantifiersRewriter::computeOperation(Node f, if( computeOption==COMPUTE_ELIM_SYMBOLS ){ n = computeElimSymbols( n ); }else if( computeOption==COMPUTE_MINISCOPING ){ - if (options::prenexQuant() == options::PrenexQuantMode::DISJ_NORMAL - || options::prenexQuant() == options::PrenexQuantMode::NORMAL) + if (options::prenexQuant() == options::PrenexQuantMode::NORMAL) { if( !qa.d_qid_num.isNull() ){ //already processed this, return self @@ -1957,8 +1933,7 @@ Node QuantifiersRewriter::computeOperation(Node f, } else if (computeOption == COMPUTE_PRENEX) { - if (options::prenexQuant() == options::PrenexQuantMode::DISJ_NORMAL - || options::prenexQuant() == options::PrenexQuantMode::NORMAL) + if (options::prenexQuant() == options::PrenexQuantMode::NORMAL) { //will rewrite at preprocess time return f; @@ -2091,12 +2066,11 @@ Node QuantifiersRewriter::preprocess( Node n, bool isInst ) { } } //pull all quantifiers globally - if (options::prenexQuant() == options::PrenexQuantMode::DISJ_NORMAL - || options::prenexQuant() == options::PrenexQuantMode::NORMAL) + if (options::prenexQuant() == options::PrenexQuantMode::NORMAL) { Trace("quantifiers-prenex") << "Prenexing : " << n << std::endl; - std::map< unsigned, std::map< Node, Node > > visited; - n = computePrenexAgg( n, true, visited ); + std::map visited; + n = computePrenexAgg(n, visited); n = Rewriter::rewrite( n ); Trace("quantifiers-prenex") << "Prenexing returned : " << n << std::endl; //Assert( isPrenexNormalForm( n ) ); diff --git a/src/theory/quantifiers/quantifiers_rewriter.h b/src/theory/quantifiers/quantifiers_rewriter.h index 2a3180e78..c8995ef4e 100644 --- a/src/theory/quantifiers/quantifiers_rewriter.h +++ b/src/theory/quantifiers/quantifiers_rewriter.h @@ -234,8 +234,27 @@ class QuantifiersRewriter : public TheoryRewriter static Node computeElimSymbols( Node body ); static Node computeMiniscoping( std::vector< Node >& args, Node body, QAttributes& qa ); static Node computeAggressiveMiniscoping( std::vector< Node >& args, Node body ); + /** + * This function removes top-level quantifiers from subformulas of body + * appearing with overall polarity pol. It adds quantified variables that + * appear in positive polarity positions into args, and those at negative + * polarity positions in nargs. + * + * If prenexAgg is true, we ensure that all top-level quantifiers are + * eliminated from subformulas. This means that we must expand ITE and + * Boolean equalities to ensure that quantifiers are at fixed polarities. + * + * For example, calling this function on: + * (or (forall ((x Int)) (P x z)) (not (forall ((y Int)) (Q y z)))) + * would return: + * (or (P x z) (not (Q y z))) + * and add {x} to args, and {y} to nargs. + */ static Node computePrenex( Node body, std::vector< Node >& args, std::vector< Node >& nargs, bool pol, bool prenexAgg ); - static Node computePrenexAgg( Node n, bool topLevel, std::map< unsigned, std::map< Node, Node > >& visited ); + /** + * Apply prenexing aggressively. Returns the prenex normal form of n. + */ + static Node computePrenexAgg(Node n, std::map& visited); static Node computeSplit( std::vector< Node >& args, Node body, QAttributes& qa ); private: static Node computeOperation(Node f, -- cgit v1.2.3 From 0a960638857ae4682162cf19b47801bc19dd94c3 Mon Sep 17 00:00:00 2001 From: Andrew Reynolds Date: Wed, 3 Jun 2020 08:08:04 -0500 Subject: Update CEGQI to use lemma status instead of forcing preprocess (#4551) This PR removes a hack in counterexample-guided quantifier instantiation. The issue is the CEGQI needs to know the form of certain lemmas, after theory preprocessing. CEGQI needs to know this since it must construct solutions (e.g. solved form of equalities) for free variables in these lemmas, which includes fresh variables in the lemma after postprocess like ITE skolems. Previously, CEGQI was hacked so that it applied the preprocessing eagerly so that it had full knowledge of the postprocessed lemma. This PR updates CEGQI so that it uses the returned LemmaStatus as the way of getting the lemma after postprocess. It also fixes the lemma status returned by TheoryEngine so that all lemmas not just the "main lemma" are returned as a conjunction. This PR is in preparation for major refactoring to theory preprocessing for the sake of proofs. --- .../quantifiers/cegqi/ceg_bv_instantiator.cpp | 14 ++--- src/theory/quantifiers/cegqi/ceg_bv_instantiator.h | 28 ++++----- src/theory/quantifiers/cegqi/ceg_instantiator.cpp | 73 ++++++++++++++-------- src/theory/quantifiers/cegqi/ceg_instantiator.h | 34 +++++----- .../quantifiers/cegqi/inst_strategy_cegqi.cpp | 17 ++--- src/theory/theory_engine.cpp | 9 ++- src/theory/theory_engine.h | 2 - 7 files changed, 100 insertions(+), 77 deletions(-) (limited to 'src') diff --git a/src/theory/quantifiers/cegqi/ceg_bv_instantiator.cpp b/src/theory/quantifiers/cegqi/ceg_bv_instantiator.cpp index f94fee66b..472dabf68 100644 --- a/src/theory/quantifiers/cegqi/ceg_bv_instantiator.cpp +++ b/src/theory/quantifiers/cegqi/ceg_bv_instantiator.cpp @@ -633,7 +633,7 @@ struct SortBvExtractInterval }; void BvInstantiatorPreprocess::registerCounterexampleLemma( - std::vector& lems, std::vector& ce_vars) + Node lem, std::vector& ceVars, std::vector& auxLems) { // new variables std::vector vars; @@ -647,12 +647,8 @@ void BvInstantiatorPreprocess::registerCounterexampleLemma( // map from terms to bitvector extracts applied to that term std::map > extract_map; std::unordered_set visited; - for (unsigned i = 0, size = lems.size(); i < size; i++) - { - Trace("cegqi-bv-pp-debug2") - << "Register ce lemma # " << i << " : " << lems[i] << std::endl; - collectExtracts(lems[i], extract_map, visited); - } + Trace("cegqi-bv-pp-debug2") << "Register ce lemma " << lem << std::endl; + collectExtracts(lem, extract_map, visited); for (std::pair >& es : extract_map) { // sort based on the extract start position @@ -721,10 +717,10 @@ void BvInstantiatorPreprocess::registerCounterexampleLemma( Trace("cegqi-bv-pp") << "Adding " << new_lems.size() << " lemmas..." << std::endl; - lems.insert(lems.end(), new_lems.begin(), new_lems.end()); + auxLems.insert(auxLems.end(), new_lems.begin(), new_lems.end()); Trace("cegqi-bv-pp") << "Adding " << vars.size() << " variables..." << std::endl; - ce_vars.insert(ce_vars.end(), vars.begin(), vars.end()); + ceVars.insert(ceVars.end(), vars.begin(), vars.end()); } } diff --git a/src/theory/quantifiers/cegqi/ceg_bv_instantiator.h b/src/theory/quantifiers/cegqi/ceg_bv_instantiator.h index 3ad45d5be..6f6c216f6 100644 --- a/src/theory/quantifiers/cegqi/ceg_bv_instantiator.h +++ b/src/theory/quantifiers/cegqi/ceg_bv_instantiator.h @@ -168,32 +168,32 @@ class BvInstantiatorPreprocess : public InstantiatorPreprocess ~BvInstantiatorPreprocess() override {} /** register counterexample lemma * - * This method modifies the contents of lems based on the extract terms - * it contains when the option --cbqi-bv-rm-extract is enabled. It introduces + * This method adds to auxLems based on the extract terms that lem + * contains when the option --cbqi-bv-rm-extract is enabled. It introduces * a dummy equality so that segments of terms t under extracts can be solved * independently. * - * For example: + * For example, if lem is: * P[ ((extract 7 4) t), ((extract 3 0) t)] - * becomes: - * P[((extract 7 4) t), ((extract 3 0) t)] ^ + * then we add: * t = concat( x74, x30 ) - * where x74 and x30 are fresh variables of type BV_4. + * to auxLems, where x74 and x30 are fresh variables of type BV_4, which are + * added to ceVars. * - * Another example: + * Another example, for: * P[ ((extract 7 3) t), ((extract 4 0) t)] - * becomes: - * P[((extract 7 4) t), ((extract 3 0) t)] ^ + * we add: * t = concat( x75, x44, x30 ) - * where x75, x44 and x30 are fresh variables of type BV_3, BV_1, and BV_4 - * respectively. + * to auxLems where x75, x44 and x30 are fresh variables of type BV_3, BV_1, + * and BV_4 respectively, which are added to ceVars. * - * Notice we leave the original conjecture alone. This is done for performance + * Notice we leave the original lem alone. This is done for performance * since the added equalities ensure we are able to construct the proper * solved forms for variables in t and for the intermediate variables above. */ - void registerCounterexampleLemma(std::vector& lems, - std::vector& ce_vars) override; + void registerCounterexampleLemma(Node lem, + std::vector& ceVars, + std::vector& auxLems) override; private: /** collect extracts diff --git a/src/theory/quantifiers/cegqi/ceg_instantiator.cpp b/src/theory/quantifiers/cegqi/ceg_instantiator.cpp index 186024219..95a4037fc 100644 --- a/src/theory/quantifiers/cegqi/ceg_instantiator.cpp +++ b/src/theory/quantifiers/cegqi/ceg_instantiator.cpp @@ -21,7 +21,6 @@ #include "expr/node_algorithm.h" #include "options/quantifiers_options.h" -#include "smt/term_formula_removal.h" #include "theory/arith/arith_msum.h" #include "theory/quantifiers/cegqi/inst_strategy_cegqi.h" #include "theory/quantifiers/first_order_model.h" @@ -1571,18 +1570,21 @@ void CegInstantiator::collectCeAtoms( Node n, std::map< Node, bool >& visited ) } } -void CegInstantiator::registerCounterexampleLemma( std::vector< Node >& lems, std::vector< Node >& ce_vars ) { +void CegInstantiator::registerCounterexampleLemma(Node lem, + std::vector& ceVars, + std::vector& auxLems) +{ Trace("cegqi-reg") << "Register counterexample lemma..." << std::endl; d_input_vars.clear(); - d_input_vars.insert(d_input_vars.end(), ce_vars.begin(), ce_vars.end()); + d_input_vars.insert(d_input_vars.end(), ceVars.begin(), ceVars.end()); //Assert( d_vars.empty() ); d_vars.clear(); registerTheoryId(THEORY_UF); - for (unsigned i = 0; i < ce_vars.size(); i++) + for (const Node& cv : ceVars) { - Trace("cegqi-reg") << " register input variable : " << ce_vars[i] << std::endl; - registerVariable(ce_vars[i]); + Trace("cegqi-reg") << " register input variable : " << cv << std::endl; + registerVariable(cv); } // preprocess with all relevant instantiator preprocessors @@ -1592,7 +1594,7 @@ void CegInstantiator::registerCounterexampleLemma( std::vector< Node >& lems, st pvars.insert(pvars.end(), d_vars.begin(), d_vars.end()); for (std::pair& p : d_tipp) { - p.second->registerCounterexampleLemma(lems, pvars); + p.second->registerCounterexampleLemma(lem, pvars, auxLems); } // must register variables generated by preprocessors Trace("cegqi-debug") << "Register variables from theory-specific methods " @@ -1600,28 +1602,43 @@ void CegInstantiator::registerCounterexampleLemma( std::vector< Node >& lems, st << std::endl; for (unsigned i = d_input_vars.size(), size = pvars.size(); i < size; ++i) { - Trace("cegqi-reg") << " register theory preprocess variable : " << pvars[i] - << std::endl; + Trace("cegqi-reg") << " register inst preprocess variable : " << pvars[i] + << std::endl; registerVariable(pvars[i]); } - //remove ITEs - IteSkolemMap iteSkolemMap; - d_qe->getTheoryEngine()->getTermFormulaRemover()->run(lems, iteSkolemMap); - for(IteSkolemMap::iterator i = iteSkolemMap.begin(); i != iteSkolemMap.end(); ++i) { - Trace("cegqi-reg") << " register aux variable : " << i->first << std::endl; - registerVariable(i->first); - } - for( unsigned i=0; igetTheoryEngine()->preprocess(rlem); - Trace("cegqi-debug") << "Counterexample lemma (post-rewrite) " << i << " : " << rlem << std::endl; - lems[i] = rlem; + // register variables that were introduced during TheoryEngine preprocessing + std::unordered_set ceSyms; + expr::getSymbols(lem, ceSyms); + std::unordered_set qSyms; + expr::getSymbols(d_quant, qSyms); + // all variables that are in counterexample lemma but not in quantified + // formula + for (const Node& ces : ceSyms) + { + if (qSyms.find(ces) != qSyms.end()) + { + // a free symbol of the quantified formula. + continue; + } + if (std::find(d_vars.begin(), d_vars.end(), ces) != d_vars.end()) + { + // already processed variable + continue; + } + if (ces.getType().isBoolean()) + { + // Boolean variables, including the counterexample literal, don't matter + // since they are always assigned a model value. + continue; + } + Trace("cegqi-reg") << " register theory preprocess variable : " << ces + << std::endl; + // register the variable, which was introduced by TheoryEngine's preprocess + // method, e.g. an ITE skolem. + registerVariable(ces); } + // determine variable order: must do Reals before Ints Trace("cegqi-debug") << "Determine variable order..." << std::endl; if (!d_vars.empty()) @@ -1673,8 +1690,10 @@ void CegInstantiator::registerCounterexampleLemma( std::vector< Node >& lems, st // the original body d_is_nested_quant = false; std::map< Node, bool > visited; - for( unsigned i=0; i& lems, - std::vector& ce_vars); + * @param lem contains the counterexample lemma of the quantified formula we + * are processing. The counterexample lemma is the formula { ~phi[e/x] } in + * Figure 1 of Reynolds et al. FMSD 2017. + * @param ce_vars contains the variables e. Notice these are variables of + * INST_CONSTANT kind, since we do not permit bound variables in assertions. + * This method may add additional variables to this vector if it decides there + * are additional auxiliary variables to solve for. + * @param auxLems : if this method decides that additional lemmas should be + * sent on the output channel, they are added to this vector, and sent out by + * the caller of this method. + */ + void registerCounterexampleLemma(Node lem, + std::vector& ce_vars, + std::vector& auxLems); //------------------------------interface for instantiators /** get quantifiers engine */ QuantifiersEngine* getQuantifiersEngine() { return d_qe; } @@ -829,8 +828,9 @@ class InstantiatorPreprocess * of counterexample lemmas, with the same contract as * CegInstantiation::registerCounterexampleLemma. */ - virtual void registerCounterexampleLemma(std::vector& lems, - std::vector& ce_vars) + virtual void registerCounterexampleLemma(Node lem, + std::vector& ceVars, + std::vector& auxLems) { } }; diff --git a/src/theory/quantifiers/cegqi/inst_strategy_cegqi.cpp b/src/theory/quantifiers/cegqi/inst_strategy_cegqi.cpp index 208eb0bf8..8693f97f4 100644 --- a/src/theory/quantifiers/cegqi/inst_strategy_cegqi.cpp +++ b/src/theory/quantifiers/cegqi/inst_strategy_cegqi.cpp @@ -593,15 +593,18 @@ void InstStrategyCegqi::registerCounterexampleLemma(Node q, Node lem) { ce_vars.push_back(tutil->getInstantiationConstant(q, i)); } - std::vector lems; - lems.push_back(lem); CegInstantiator* cinst = getInstantiator(q); - cinst->registerCounterexampleLemma(lems, ce_vars); - for (unsigned i = 0, size = lems.size(); i < size; i++) + LemmaStatus status = d_quantEngine->getOutputChannel().lemma(lem); + Node ppLem = status.getRewrittenLemma(); + Trace("cegqi-debug") << "Counterexample lemma (post-preprocess): " << ppLem + << std::endl; + std::vector auxLems; + cinst->registerCounterexampleLemma(ppLem, ce_vars, auxLems); + for (unsigned i = 0, size = auxLems.size(); i < size; i++) { - Trace("cegqi-debug") << "Counterexample lemma " << i << " : " << lems[i] - << std::endl; - d_quantEngine->addLemma(lems[i], false); + Trace("cegqi-debug") << "Auxiliary CE lemma " << i << " : " << auxLems[i] + << std::endl; + d_quantEngine->addLemma(auxLems[i], false); } } diff --git a/src/theory/theory_engine.cpp b/src/theory/theory_engine.cpp index 2c27c6054..71c144daa 100644 --- a/src/theory/theory_engine.cpp +++ b/src/theory/theory_engine.cpp @@ -1931,7 +1931,14 @@ theory::LemmaStatus TheoryEngine::lemma(TNode node, // Lemma analysis isn't online yet; this lemma may only live for this // user level. - return theory::LemmaStatus(additionalLemmas[0], d_userContext->getLevel()); + Node retLemma = additionalLemmas[0]; + if (additionalLemmas.size() > 1) + { + // the returned lemma is the conjunction of all additional lemmas. + retLemma = + NodeManager::currentNM()->mkNode(kind::AND, additionalLemmas.ref()); + } + return theory::LemmaStatus(retLemma, d_userContext->getLevel()); } void TheoryEngine::conflict(TNode conflict, TheoryId theoryId) { diff --git a/src/theory/theory_engine.h b/src/theory/theory_engine.h index 9c631ca60..233047321 100644 --- a/src/theory/theory_engine.h +++ b/src/theory/theory_engine.h @@ -890,8 +890,6 @@ public: theory::eq::EqualityEngine* getMasterEqualityEngine() { return d_masterEqualityEngine; } - RemoveTermFormulas* getTermFormulaRemover() { return &d_tform_remover; } - SortInference* getSortInference() { return &d_sortInfer; } /** Prints the assertions to the debug stream */ -- cgit v1.2.3 From 6c8702ab5eb08466bf954e202241df39de680081 Mon Sep 17 00:00:00 2001 From: Andrew Reynolds Date: Wed, 3 Jun 2020 08:47:46 -0500 Subject: Do not apply unconstrained simplification when quantifiers are present (#4532) Fixes #4437. This is a simpler fix that aborts the preprocessing pass when a quantifier is encountered. It also updates our smt2 parser to throw a logic exception when forall/exists is used in non-quantified logics. This is required to ensure that unconstrained simplification does not throw an exception to a user as a result of accidentally setting the wrong logic. --- src/options/smt_options.toml | 2 +- src/parser/smt2/Smt2.g | 8 +++++++- src/preprocessing/passes/unconstrained_simplifier.cpp | 9 +++++++++ src/preprocessing/passes/unconstrained_simplifier.h | 6 +++++- test/regress/CMakeLists.txt | 3 +-- test/regress/regress0/quantifiers/issue4437-unc-quant.smt2 | 7 +++++++ test/regress/regress0/unconstrained/4481.smt2 | 3 ++- test/regress/regress0/unconstrained/4484.smt2 | 8 -------- test/regress/regress0/unconstrained/4486.smt2 | 8 -------- test/regress/regress1/strings/norn-ab.smt2 | 2 +- 10 files changed, 33 insertions(+), 23 deletions(-) create mode 100644 test/regress/regress0/quantifiers/issue4437-unc-quant.smt2 delete mode 100644 test/regress/regress0/unconstrained/4484.smt2 delete mode 100644 test/regress/regress0/unconstrained/4486.smt2 (limited to 'src') diff --git a/src/options/smt_options.toml b/src/options/smt_options.toml index 08e6f317c..449c0c31e 100644 --- a/src/options/smt_options.toml +++ b/src/options/smt_options.toml @@ -346,7 +346,7 @@ header = "options/smt_options.h" long = "unconstrained-simp" type = "bool" default = "false" - help = "turn on unconstrained simplification (see Bruttomesso/Brummayer PhD thesis)" + help = "turn on unconstrained simplification (see Bruttomesso/Brummayer PhD thesis). Fully supported only in (subsets of) the logic QF_ABV." [[option]] name = "repeatSimp" diff --git a/src/parser/smt2/Smt2.g b/src/parser/smt2/Smt2.g index 081990a45..f29e03380 100644 --- a/src/parser/smt2/Smt2.g +++ b/src/parser/smt2/Smt2.g @@ -1692,7 +1692,13 @@ termNonVariable[CVC4::api::Term& expr, CVC4::api::Term& expr2] std::vector argTypes; } : LPAREN_TOK quantOp[kind] - { PARSER_STATE->pushScope(true); } + { + if (!PARSER_STATE->isTheoryEnabled(theory::THEORY_QUANTIFIERS)) + { + PARSER_STATE->parseError("Quantifier used in non-quantified logic."); + } + PARSER_STATE->pushScope(true); + } boundVarList[bvl] term[f, f2] RPAREN_TOK { diff --git a/src/preprocessing/passes/unconstrained_simplifier.cpp b/src/preprocessing/passes/unconstrained_simplifier.cpp index 5d544ae57..b74909824 100644 --- a/src/preprocessing/passes/unconstrained_simplifier.cpp +++ b/src/preprocessing/passes/unconstrained_simplifier.cpp @@ -91,6 +91,15 @@ void UnconstrainedSimplifier::visitAll(TNode assertion) d_unconstrained.insert(current); } } + else if (current.isClosure()) + { + // Throw an exception. This should never happen in practice unless the + // user specifically enabled unconstrained simplification in an illegal + // logic. + throw LogicException( + "Cannot use unconstrained simplification in this logic, due to " + "(possibly internally introduced) quantified formula."); + } else { for (TNode childNode : current) diff --git a/src/preprocessing/passes/unconstrained_simplifier.h b/src/preprocessing/passes/unconstrained_simplifier.h index ac4fd0a03..7fc13e17d 100644 --- a/src/preprocessing/passes/unconstrained_simplifier.h +++ b/src/preprocessing/passes/unconstrained_simplifier.h @@ -62,7 +62,11 @@ class UnconstrainedSimplifier : public PreprocessingPass theory::SubstitutionMap d_substitutions; const LogicInfo& d_logicInfo; - + /** + * Visit all subterms in assertion. This method throws a LogicException if + * there is a subterm that is unhandled by this preprocessing pass (e.g. a + * quantified formula). + */ void visitAll(TNode assertion); Node newUnconstrainedVar(TypeNode t, TNode var); void processUnconstrained(); diff --git a/test/regress/CMakeLists.txt b/test/regress/CMakeLists.txt index b66d4e973..801f38b29 100644 --- a/test/regress/CMakeLists.txt +++ b/test/regress/CMakeLists.txt @@ -740,6 +740,7 @@ set(regress_0_tests regress0/quantifiers/issue3655.smt2 regress0/quantifiers/issue4086-infs.smt2 regress0/quantifiers/issue4275-qcf-cegqi-rep.smt2 + regress0/quantifiers/issue4437-unc-quant.smt2 regress0/quantifiers/issue4476-ext-rew.smt2 regress0/quantifiers/lra-triv-gn.smt2 regress0/quantifiers/macros-int-real.smt2 @@ -1127,8 +1128,6 @@ set(regress_0_tests regress0/uflra/simple.03.cvc regress0/uflra/simple.04.cvc regress0/unconstrained/4481.smt2 - regress0/unconstrained/4484.smt2 - regress0/unconstrained/4486.smt2 regress0/unconstrained/arith.smt2 regress0/unconstrained/arith3.smt2 regress0/unconstrained/arith4.smt2 diff --git a/test/regress/regress0/quantifiers/issue4437-unc-quant.smt2 b/test/regress/regress0/quantifiers/issue4437-unc-quant.smt2 new file mode 100644 index 000000000..61f792999 --- /dev/null +++ b/test/regress/regress0/quantifiers/issue4437-unc-quant.smt2 @@ -0,0 +1,7 @@ +; EXPECT: (error "Parse Error: issue4437-unc-quant.smt2:6.15: Quantifier used in non-quantified logic.") +; EXIT: 1 +(set-logic QF_AUFBVLIA) +(declare-fun a () (_ BitVec 8)) +(declare-fun b () (_ BitVec 8)) +(assert (forall ((c (_ BitVec 8))) (= (bvashr c a) b))) +(check-sat) diff --git a/test/regress/regress0/unconstrained/4481.smt2 b/test/regress/regress0/unconstrained/4481.smt2 index 028607093..19179f4d7 100644 --- a/test/regress/regress0/unconstrained/4481.smt2 +++ b/test/regress/regress0/unconstrained/4481.smt2 @@ -1,5 +1,6 @@ ; COMMAND-LINE: --unconstrained-simp -; EXPECT: unsat +; EXPECT: (error "Cannot use unconstrained simplification in this logic, due to (possibly internally introduced) quantified formula.") +; EXIT: 1 (set-logic ALL) (set-info :status unsat) (declare-fun a () Int) diff --git a/test/regress/regress0/unconstrained/4484.smt2 b/test/regress/regress0/unconstrained/4484.smt2 deleted file mode 100644 index f2f11295b..000000000 --- a/test/regress/regress0/unconstrained/4484.smt2 +++ /dev/null @@ -1,8 +0,0 @@ -; COMMAND-LINE: --unconstrained-simp -; EXPECT: unsat -(set-logic QF_NIRA) -(set-info :status unsat) -(declare-fun a () Real) -(assert (= (to_int a) 2)) -(assert (= (to_int (/ a 2.0)) 2)) -(check-sat) diff --git a/test/regress/regress0/unconstrained/4486.smt2 b/test/regress/regress0/unconstrained/4486.smt2 deleted file mode 100644 index 01771ce66..000000000 --- a/test/regress/regress0/unconstrained/4486.smt2 +++ /dev/null @@ -1,8 +0,0 @@ -; COMMAND-LINE: --unconstrained-simp -; EXPECT: sat -(set-logic ALL) -(set-info :status sat) -(declare-fun x () Real) -(assert (is_int x)) -(assert (is_int (+ x 1))) -(check-sat) diff --git a/test/regress/regress1/strings/norn-ab.smt2 b/test/regress/regress1/strings/norn-ab.smt2 index 63a95bb78..6109a01dd 100644 --- a/test/regress/regress1/strings/norn-ab.smt2 +++ b/test/regress/regress1/strings/norn-ab.smt2 @@ -1,5 +1,5 @@ (set-info :smt-lib-version 2.6) -(set-logic QF_SLIA) +(set-logic SLIA) (set-info :status unsat) (set-option :strings-exp true) -- cgit v1.2.3 From f70e265cd4e7df46a1b3b7e3cc67fbf9b9b1b528 Mon Sep 17 00:00:00 2001 From: Andrew Reynolds Date: Wed, 3 Jun 2020 14:01:13 -0500 Subject: (proof-new) Add builtin proof checker (#4537) This adds the proof checker for TheoryBuiltin, which handles the core proof rules (SCOPE and ASSUME) and all proof rules corresponding to generic operations on Node objects. This includes proof rules for rewriting and substitution, which are added in this PR. --- src/CMakeLists.txt | 2 + src/expr/proof_rule.cpp | 6 + src/expr/proof_rule.h | 96 ++++++++++ src/theory/builtin/proof_checker.cpp | 359 +++++++++++++++++++++++++++++++++++ src/theory/builtin/proof_checker.h | 150 +++++++++++++++ 5 files changed, 613 insertions(+) create mode 100644 src/theory/builtin/proof_checker.cpp create mode 100644 src/theory/builtin/proof_checker.h (limited to 'src') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 20e110b2b..4fb1606bc 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -366,6 +366,8 @@ libcvc4_add_sources( theory/builtin/theory_builtin_rewriter.cpp theory/builtin/theory_builtin_rewriter.h theory/builtin/theory_builtin_type_rules.h + theory/builtin/proof_checker.cpp + theory/builtin/proof_checker.h theory/builtin/type_enumerator.cpp theory/builtin/type_enumerator.h theory/bv/abstraction.cpp diff --git a/src/expr/proof_rule.cpp b/src/expr/proof_rule.cpp index e555f5691..09ffc82bf 100644 --- a/src/expr/proof_rule.cpp +++ b/src/expr/proof_rule.cpp @@ -25,6 +25,12 @@ const char* toString(PfRule id) //================================================= Core rules case PfRule::ASSUME: return "ASSUME"; case PfRule::SCOPE: return "SCOPE"; + case PfRule::SUBS: return "SUBS"; + case PfRule::REWRITE: return "REWRITE"; + case PfRule::MACRO_SR_EQ_INTRO: return "MACRO_SR_EQ_INTRO"; + case PfRule::MACRO_SR_PRED_INTRO: return "MACRO_SR_PRED_INTRO"; + case PfRule::MACRO_SR_PRED_ELIM: return "MACRO_SR_PRED_ELIM"; + case PfRule::MACRO_SR_PRED_TRANSFORM: return "MACRO_SR_PRED_TRANSFORM"; //================================================= Unknown rule case PfRule::UNKNOWN: return "UNKNOWN"; diff --git a/src/expr/proof_rule.h b/src/expr/proof_rule.h index 0d03bb347..e0a626b69 100644 --- a/src/expr/proof_rule.h +++ b/src/expr/proof_rule.h @@ -72,6 +72,102 @@ enum class PfRule : uint32_t // proof with no free assumptions always concludes a valid formula. SCOPE, + //======================== Builtin theory (common node operations) + // ======== Substitution + // Children: (P1:F1, ..., Pn:Fn) + // Arguments: (t, (ids)?) + // --------------------------------------------------------------- + // Conclusion: (= t t*sigma{ids}(Fn)*...*sigma{ids}(F1)) + // where sigma{ids}(Fi) are substitutions, which notice are applied in + // reverse order. + // Notice that ids is a MethodId identifier, which determines how to convert + // the formulas F1, ..., Fn into substitutions. + SUBS, + // ======== Rewrite + // Children: none + // Arguments: (t, (idr)?) + // ---------------------------------------- + // Conclusion: (= t Rewriter{idr}(t)) + // where idr is a MethodId identifier, which determines the kind of rewriter + // to apply, e.g. Rewriter::rewrite. + REWRITE, + // ======== Substitution + Rewriting equality introduction + // + // In this rule, we provide a term t and conclude that it is equal to its + // rewritten form under a (proven) substitution. + // + // Children: (P1:F1, ..., Pn:Fn) + // Arguments: (t, (ids (idr)?)?) + // --------------------------------------------------------------- + // Conclusion: (= t t') + // where + // t' is + // toWitness(Rewriter{idr}(toSkolem(t)*sigma{ids}(Fn)*...*sigma{ids}(F1))) + // toSkolem(...) converts terms from witness form to Skolem form, + // toWitness(...) converts terms from Skolem form to witness form. + // + // Notice that: + // toSkolem(t')=Rewriter{idr}(toSkolem(t)*sigma{ids}(Fn)*...*sigma{ids}(F1)) + // In other words, from the point of view of Skolem forms, this rule + // transforms t to t' by standard substitution + rewriting. + // + // The argument ids and idr is optional and specify the identifier of the + // substitution and rewriter respectively to be used. For details, see + // theory/builtin/proof_checker.h. + MACRO_SR_EQ_INTRO, + // ======== Substitution + Rewriting predicate introduction + // + // In this rule, we provide a formula F and conclude it, under the condition + // that it rewrites to true under a proven substitution. + // + // Children: (P1:F1, ..., Pn:Fn) + // Arguments: (F, (ids (idr)?)?) + // --------------------------------------------------------------- + // Conclusion: F + // where + // Rewriter{idr}(F*sigma{ids}(Fn)*...*sigma{ids}(F1)) == true + // where ids and idr are method identifiers. + // + // Notice that we apply rewriting on the witness form of F, meaning that this + // rule may conclude an F whose Skolem form is justified by the definition of + // its (fresh) Skolem variables. Furthermore, notice that the rewriting and + // substitution is applied only within the side condition, meaning the + // rewritten form of the witness form of F does not escape this rule. + MACRO_SR_PRED_INTRO, + // ======== Substitution + Rewriting predicate elimination + // + // In this rule, if we have proven a formula F, then we may conclude its + // rewritten form under a proven substitution. + // + // Children: (P1:F, P2:F1, ..., P_{n+1}:Fn) + // Arguments: ((ids (idr)?)?) + // ---------------------------------------- + // Conclusion: F' + // where + // F' is + // toWitness(Rewriter{idr}(toSkolem(F)*sigma{ids}(Fn)*...*sigma{ids}(F1)). + // where ids and idr are method identifiers. + // + // We rewrite only on the Skolem form of F, similar to MACRO_SR_EQ_INTRO. + MACRO_SR_PRED_ELIM, + // ======== Substitution + Rewriting predicate transform + // + // In this rule, if we have proven a formula F, then we may provide a formula + // G and conclude it if F and G are equivalent after rewriting under a proven + // substitution. + // + // Children: (P1:F, P2:F1, ..., P_{n+1}:Fn) + // Arguments: (G, (ids (idr)?)?) + // ---------------------------------------- + // Conclusion: G + // where + // Rewriter{idr}(F*sigma{ids}(Fn)*...*sigma{ids}(F1)) == + // Rewriter{idr}(G*sigma{ids}(Fn)*...*sigma{ids}(F1)) + // + // Notice that we apply rewriting on the witness form of F and G, similar to + // MACRO_SR_PRED_INTRO. + MACRO_SR_PRED_TRANSFORM, + //================================================= Unknown rule UNKNOWN, }; diff --git a/src/theory/builtin/proof_checker.cpp b/src/theory/builtin/proof_checker.cpp new file mode 100644 index 000000000..8d2a1540c --- /dev/null +++ b/src/theory/builtin/proof_checker.cpp @@ -0,0 +1,359 @@ +/********************* */ +/*! \file proof_checker.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 equality proof checker + **/ + +#include "theory/builtin/proof_checker.h" + +#include "expr/proof_skolem_cache.h" +#include "theory/rewriter.h" +#include "theory/theory.h" + +using namespace CVC4::kind; + +namespace CVC4 { +namespace theory { + +const char* toString(MethodId id) +{ + switch (id) + { + case MethodId::RW_REWRITE: return "RW_REWRITE"; + case MethodId::RW_IDENTITY: return "RW_IDENTITY"; + case MethodId::SB_DEFAULT: return "SB_DEFAULT"; + case MethodId::SB_LITERAL: return "SB_LITERAL"; + case MethodId::SB_FORMULA: return "SB_FORMULA"; + default: return "MethodId::Unknown"; + }; +} + +std::ostream& operator<<(std::ostream& out, MethodId id) +{ + out << toString(id); + return out; +} + +Node mkMethodId(MethodId id) +{ + return NodeManager::currentNM()->mkConst(Rational(static_cast(id))); +} + +namespace builtin { + +void BuiltinProofRuleChecker::registerTo(ProofChecker* pc) +{ + pc->registerChecker(PfRule::ASSUME, this); + pc->registerChecker(PfRule::SCOPE, this); + pc->registerChecker(PfRule::SUBS, this); + pc->registerChecker(PfRule::REWRITE, this); + pc->registerChecker(PfRule::MACRO_SR_EQ_INTRO, this); + pc->registerChecker(PfRule::MACRO_SR_PRED_INTRO, this); + pc->registerChecker(PfRule::MACRO_SR_PRED_ELIM, this); + pc->registerChecker(PfRule::MACRO_SR_PRED_TRANSFORM, this); +} + +Node BuiltinProofRuleChecker::applyRewrite(Node n, MethodId idr) +{ + Node nk = ProofSkolemCache::getSkolemForm(n); + Node nkr = applyRewriteExternal(nk, idr); + return ProofSkolemCache::getWitnessForm(nkr); +} + +Node BuiltinProofRuleChecker::applySubstitution(Node n, Node exp, MethodId ids) +{ + if (exp.isNull() || exp.getKind() != EQUAL) + { + return Node::null(); + } + Node nk = ProofSkolemCache::getSkolemForm(n); + Node nks = applySubstitutionExternal(nk, exp, ids); + return ProofSkolemCache::getWitnessForm(nks); +} + +Node BuiltinProofRuleChecker::applySubstitution(Node n, + const std::vector& exp, + MethodId ids) +{ + Node nk = ProofSkolemCache::getSkolemForm(n); + Node nks = applySubstitutionExternal(nk, exp, ids); + return ProofSkolemCache::getWitnessForm(nks); +} + +Node BuiltinProofRuleChecker::applySubstitutionRewrite( + Node n, const std::vector& exp, MethodId ids, MethodId idr) +{ + Node nk = ProofSkolemCache::getSkolemForm(n); + Node nks = applySubstitutionExternal(nk, exp, ids); + Node nksr = applyRewriteExternal(nks, idr); + return ProofSkolemCache::getWitnessForm(nksr); +} + +Node BuiltinProofRuleChecker::applyRewriteExternal(Node n, MethodId idr) +{ + Trace("builtin-pfcheck-debug") + << "applyRewriteExternal (" << idr << "): " << n << std::endl; + if (idr == MethodId::RW_REWRITE) + { + return Rewriter::rewrite(n); + } + else if (idr == MethodId::RW_IDENTITY) + { + // does nothing + return n; + } + // unknown rewriter + Assert(false) + << "BuiltinProofRuleChecker::applyRewriteExternal: no rewriter for " + << idr << std::endl; + return n; +} + +Node BuiltinProofRuleChecker::applySubstitutionExternal(Node n, + Node exp, + MethodId ids) +{ + Assert(!exp.isNull()); + Node expk = ProofSkolemCache::getSkolemForm(exp); + TNode var, subs; + if (ids == MethodId::SB_DEFAULT) + { + if (expk.getKind() != EQUAL) + { + return Node::null(); + } + var = expk[0]; + subs = expk[1]; + } + else if (ids == MethodId::SB_LITERAL) + { + bool polarity = expk.getKind() != NOT; + var = polarity ? expk : expk[0]; + subs = NodeManager::currentNM()->mkConst(polarity); + } + else if (ids == MethodId::SB_FORMULA) + { + var = expk; + subs = NodeManager::currentNM()->mkConst(true); + } + else + { + Assert(false) << "BuiltinProofRuleChecker::applySubstitutionExternal: no " + "substitution for " + << ids << std::endl; + } + return n.substitute(var, subs); +} + +Node BuiltinProofRuleChecker::applySubstitutionExternal( + Node n, const std::vector& exp, MethodId ids) +{ + Node curr = n; + // apply substitution one at a time, in reverse order + for (size_t i = 0, nexp = exp.size(); i < nexp; i++) + { + if (exp[nexp - 1 - i].isNull()) + { + return Node::null(); + } + curr = applySubstitutionExternal(curr, exp[nexp - 1 - i], ids); + if (curr.isNull()) + { + break; + } + } + return curr; +} + +bool BuiltinProofRuleChecker::getMethodId(TNode n, MethodId& i) +{ + uint32_t index; + if (!getUInt32(n, index)) + { + return false; + } + i = static_cast(index); + return true; +} + +Node BuiltinProofRuleChecker::checkInternal(PfRule id, + const std::vector& children, + const std::vector& args) +{ + // compute what was proven + if (id == PfRule::ASSUME) + { + Assert(children.empty()); + Assert(args.size() == 1); + Assert(args[0].getType().isBoolean()); + return args[0]; + } + else if (id == PfRule::SCOPE) + { + Assert(children.size() == 1); + if (args.empty()) + { + // no antecedant + return children[0]; + } + Node ant = mkAnd(args); + // if the conclusion is false, its the negated antencedant only + if (children[0].isConst() && !children[0].getConst()) + { + return ant.notNode(); + } + return NodeManager::currentNM()->mkNode(IMPLIES, ant, children[0]); + } + else if (id == PfRule::SUBS) + { + Assert(children.size() > 0); + Assert(1 <= args.size() && args.size() <= 2); + MethodId ids = MethodId::SB_DEFAULT; + if (args.size() == 2 && !getMethodId(args[1], ids)) + { + return Node::null(); + } + std::vector exp; + for (size_t i = 0, nchild = children.size(); i < nchild; i++) + { + exp.push_back(children[i]); + } + Node res = applySubstitution(args[0], exp); + return args[0].eqNode(res); + } + else if (id == PfRule::REWRITE) + { + Assert(children.empty()); + Assert(1 <= args.size() && args.size() <= 2); + MethodId ids = MethodId::RW_REWRITE; + if (args.size() == 2 && !getMethodId(args[1], ids)) + { + return Node::null(); + } + Node res = applyRewrite(args[0]); + return args[0].eqNode(res); + } + else if (id == PfRule::MACRO_SR_EQ_INTRO) + { + Assert(1 <= args.size() && args.size() <= 3); + MethodId ids, idr; + if (!getMethodIds(args, ids, idr, 1)) + { + return Node::null(); + } + Node res = applySubstitutionRewrite(args[0], children, idr); + return args[0].eqNode(res); + } + else if (id == PfRule::MACRO_SR_PRED_INTRO) + { + Trace("builtin-pfcheck") << "Check " << id << " " << children.size() << " " + << args.size() << std::endl; + Assert(1 <= args.size() && args.size() <= 3); + MethodId ids, idr; + if (!getMethodIds(args, ids, idr, 1)) + { + return Node::null(); + } + Node res = applySubstitutionRewrite(args[0], children, ids, idr); + if (res.isNull()) + { + return Node::null(); + } + // **** NOTE: can rewrite the witness form here. This enables certain lemmas + // to be provable, e.g. (= k t) where k is a purification Skolem for t. + res = Rewriter::rewrite(res); + if (!res.isConst() || !res.getConst()) + { + Trace("builtin-pfcheck") + << "Failed to rewrite to true, res=" << res << std::endl; + return Node::null(); + } + return args[0]; + } + else if (id == PfRule::MACRO_SR_PRED_ELIM) + { + Trace("builtin-pfcheck") << "Check " << id << " " << children.size() << " " + << args.size() << std::endl; + Assert(children.size() >= 1); + Assert(args.size() <= 2); + std::vector exp; + exp.insert(exp.end(), children.begin() + 1, children.end()); + MethodId ids, idr; + if (!getMethodIds(args, ids, idr, 0)) + { + return Node::null(); + } + Node res1 = applySubstitutionRewrite(children[0], exp, ids, idr); + Trace("builtin-pfcheck") << "Returned " << res1 << std::endl; + return res1; + } + else if (id == PfRule::MACRO_SR_PRED_TRANSFORM) + { + Trace("builtin-pfcheck") << "Check " << id << " " << children.size() << " " + << args.size() << std::endl; + Assert(children.size() >= 1); + Assert(1 <= args.size() && args.size() <= 3); + Assert(args[0].getType().isBoolean()); + MethodId ids, idr; + if (!getMethodIds(args, ids, idr, 1)) + { + return Node::null(); + } + std::vector exp; + exp.insert(exp.end(), children.begin() + 1, children.end()); + Node res1 = applySubstitutionRewrite(children[0], exp, ids, idr); + Node res2 = applySubstitutionRewrite(args[0], exp, ids, idr); + // can rewrite the witness forms + res1 = Rewriter::rewrite(res1); + res2 = Rewriter::rewrite(res2); + if (res1.isNull() || res1 != res2) + { + Trace("builtin-pfcheck") << "Failed to match results" << std::endl; + Trace("builtin-pfcheck-debug") << res1 << " vs " << res2 << std::endl; + return Node::null(); + } + return args[0]; + } + // no rule + return Node::null(); +} + +bool BuiltinProofRuleChecker::getMethodIds(const std::vector& args, + MethodId& ids, + MethodId& idr, + size_t index) +{ + ids = MethodId::SB_DEFAULT; + idr = MethodId::RW_REWRITE; + if (args.size() > index) + { + if (!getMethodId(args[index], ids)) + { + Trace("builtin-pfcheck") + << "Failed to get id from " << args[index] << std::endl; + return false; + } + } + if (args.size() > index + 1) + { + if (!getMethodId(args[index + 1], idr)) + { + Trace("builtin-pfcheck") + << "Failed to get id from " << args[index + 1] << std::endl; + return false; + } + } + return true; +} + +} // namespace builtin +} // namespace theory +} // namespace CVC4 diff --git a/src/theory/builtin/proof_checker.h b/src/theory/builtin/proof_checker.h new file mode 100644 index 000000000..f4424b107 --- /dev/null +++ b/src/theory/builtin/proof_checker.h @@ -0,0 +1,150 @@ +/********************* */ +/*! \file proof_checker.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 Equality proof checker utility + **/ + +#include "cvc4_private.h" + +#ifndef CVC4__THEORY__BUILTIN__PROOF_CHECKER_H +#define CVC4__THEORY__BUILTIN__PROOF_CHECKER_H + +#include "expr/node.h" +#include "expr/proof_checker.h" +#include "expr/proof_node.h" + +namespace CVC4 { +namespace theory { + +/** + * Identifiers for rewriters and substitutions, which we abstractly + * classify as "methods". Methods have a unique identifier in the internal + * proof calculus implemented by the checker below. + * + * A "rewriter" is abstractly a method from Node to Node, where the output + * is semantically equivalent to the input. The identifiers below list + * various methods that have this contract. This identifier is used + * in a number of the builtin rules. + * + * A substitution is a method for turning a formula into a substitution. + */ +enum class MethodId : uint32_t +{ + //---------------------------- Rewriters + // Rewriter::rewrite(n) + RW_REWRITE, + // identity + RW_IDENTITY, + //---------------------------- Substitutions + // (= x y) is interpreted as x -> y, using Node::substitute + SB_DEFAULT, + // P, (not P) are interpreted as P -> true, P -> false using Node::substitute + SB_LITERAL, + // P is interpreted as P -> true using Node::substitute + SB_FORMULA, +}; +/** Converts a rewriter id to a string. */ +const char* toString(MethodId id); +/** Write a rewriter id to out */ +std::ostream& operator<<(std::ostream& out, MethodId id); +/** Make a method id node */ +Node mkMethodId(MethodId id); + +namespace builtin { + +/** A checker for builtin proofs */ +class BuiltinProofRuleChecker : public ProofRuleChecker +{ + public: + BuiltinProofRuleChecker() {} + ~BuiltinProofRuleChecker() {} + /** + * Apply rewrite on n (in witness form). This encapsulates the exact behavior + * of a REWRITE step in a proof. Rewriting is performed on the Skolem form of + * n. + * + * @param n The node (in witness form) to rewrite, + * @param idr The method identifier of the rewriter, by default RW_REWRITE + * specifying a call to Rewriter::rewrite. + * @return The rewritten form of n. + */ + static Node applyRewrite(Node n, MethodId idr = MethodId::RW_REWRITE); + /** + * Apply substitution on n (in witness form). This encapsulates the exact + * behavior of a SUBS step in a proof. Substitution is on the Skolem form of + * n. + * + * @param n The node (in witness form) to substitute, + * @param exp The (set of) equalities (in witness form) corresponding to the + * substitution + * @param ids The method identifier of the substitution, by default SB_DEFAULT + * specifying that lhs/rhs of equalities are interpreted as a substitution. + * @return The substituted form of n. + */ + static Node applySubstitution(Node n, + Node exp, + MethodId ids = MethodId::SB_DEFAULT); + static Node applySubstitution(Node n, + const std::vector& exp, + MethodId ids = MethodId::SB_DEFAULT); + /** Apply substitution + rewriting + * + * Combines the above two steps. + * + * @param n The node (in witness form) to substitute and rewrite, + * @param exp The (set of) equalities (in witness form) corresponding to the + * substitution + * @param ids The method identifier of the substitution. + * @param idr The method identifier of the rewriter. + * @return The substituted, rewritten form of n. + */ + static Node applySubstitutionRewrite(Node n, + const std::vector& exp, + MethodId ids = MethodId::SB_DEFAULT, + MethodId idr = MethodId::RW_REWRITE); + /** get a rewriter Id from a node, return false if we fail */ + static bool getMethodId(TNode n, MethodId& i); + + /** Register all rules owned by this rule checker into pc. */ + void registerTo(ProofChecker* pc) override; + + protected: + /** Return the conclusion of the given proof step, or null if it is invalid */ + Node checkInternal(PfRule id, + const std::vector& children, + const std::vector& args) override; + /** get method identifiers */ + bool getMethodIds(const std::vector& args, + MethodId& ids, + MethodId& idr, + size_t index); + /** + * Apply rewrite (on Skolem form). id is the identifier of the rewriter. + */ + static Node applyRewriteExternal(Node n, MethodId idr = MethodId::RW_REWRITE); + /** + * Apply substitution for n (on Skolem form), where exp is an equality + * (or set of equalities) in Witness form. Returns the result of + * n * sigma{ids}(exp), where sigma{ids} is a substitution based on method + * identifier ids. + */ + static Node applySubstitutionExternal(Node n, Node exp, MethodId ids); + /** Same as above, for a list of substitutions in exp */ + static Node applySubstitutionExternal(Node n, + const std::vector& exp, + MethodId ids); +}; + +} // namespace builtin +} // namespace theory +} // namespace CVC4 + +#endif /* CVC4__THEORY__BUILTIN__PROOF_CHECKER_H */ -- cgit v1.2.3 From b19d246d75be92a0189b9aaacc71426395b8c098 Mon Sep 17 00:00:00 2001 From: Haniel Barbosa Date: Wed, 3 Jun 2020 18:54:47 -0300 Subject: (proof-new) Adding rules and proof checker for Boolean reasoning (#4560) --- src/CMakeLists.txt | 2 + src/expr/proof_rule.cpp | 45 +++ src/expr/proof_rule.h | 260 +++++++++++++++- src/theory/booleans/proof_checker.cpp | 568 ++++++++++++++++++++++++++++++++++ src/theory/booleans/proof_checker.h | 49 +++ 5 files changed, 922 insertions(+), 2 deletions(-) create mode 100644 src/theory/booleans/proof_checker.cpp create mode 100644 src/theory/booleans/proof_checker.h (limited to 'src') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 4fb1606bc..97c352d5d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -355,6 +355,8 @@ libcvc4_add_sources( theory/atom_requests.h theory/booleans/circuit_propagator.cpp theory/booleans/circuit_propagator.h + theory/booleans/proof_checker.cpp + theory/booleans/proof_checker.h theory/booleans/theory_bool.cpp theory/booleans/theory_bool.h theory/booleans/theory_bool_rewriter.cpp diff --git a/src/expr/proof_rule.cpp b/src/expr/proof_rule.cpp index 09ffc82bf..0e73d8c10 100644 --- a/src/expr/proof_rule.cpp +++ b/src/expr/proof_rule.cpp @@ -32,6 +32,51 @@ const char* toString(PfRule id) case PfRule::MACRO_SR_PRED_ELIM: return "MACRO_SR_PRED_ELIM"; case PfRule::MACRO_SR_PRED_TRANSFORM: return "MACRO_SR_PRED_TRANSFORM"; + //================================================= Boolean rules + case PfRule::SPLIT: return "SPLIT"; + case PfRule::AND_ELIM: return "AND_ELIM"; + case PfRule::AND_INTRO: return "AND_INTRO"; + case PfRule::NOT_OR_ELIM: return "NOT_OR_ELIM"; + case PfRule::IMPLIES_ELIM: return "IMPLIES_ELIM"; + case PfRule::NOT_IMPLIES_ELIM1: return "NOT_IMPLIES_ELIM1"; + case PfRule::NOT_IMPLIES_ELIM2: return "NOT_IMPLIES_ELIM2"; + case PfRule::EQUIV_ELIM1: return "EQUIV_ELIM1"; + case PfRule::EQUIV_ELIM2: return "EQUIV_ELIM2"; + case PfRule::NOT_EQUIV_ELIM1: return "NOT_EQUIV_ELIM1"; + case PfRule::NOT_EQUIV_ELIM2: return "NOT_EQUIV_ELIM2"; + case PfRule::XOR_ELIM1: return "XOR_ELIM1"; + case PfRule::XOR_ELIM2: return "XOR_ELIM2"; + case PfRule::NOT_XOR_ELIM1: return "NOT_XOR_ELIM1"; + case PfRule::NOT_XOR_ELIM2: return "NOT_XOR_ELIM2"; + case PfRule::ITE_ELIM1: return "ITE_ELIM1"; + case PfRule::ITE_ELIM2: return "ITE_ELIM2"; + case PfRule::NOT_ITE_ELIM1: return "NOT_ITE_ELIM1"; + case PfRule::NOT_ITE_ELIM2: return "NOT_ITE_ELIM2"; + case PfRule::CONTRA: return "CONTRA"; + //================================================= De Morgan rules + case PfRule::NOT_AND: return "NOT_AND"; + //================================================= CNF rules + case PfRule::CNF_AND_POS: return "CNF_AND_POS"; + case PfRule::CNF_AND_NEG: return "CNF_AND_NEG"; + case PfRule::CNF_OR_POS: return "CNF_OR_POS"; + case PfRule::CNF_OR_NEG: return "CNF_OR_NEG"; + case PfRule::CNF_IMPLIES_POS: return "CNF_IMPLIES_POS"; + case PfRule::CNF_IMPLIES_NEG1: return "CNF_IMPLIES_NEG1"; + case PfRule::CNF_IMPLIES_NEG2: return "CNF_IMPLIES_NEG2"; + case PfRule::CNF_EQUIV_POS1: return "CNF_EQUIV_POS1"; + case PfRule::CNF_EQUIV_POS2: return "CNF_EQUIV_POS2"; + case PfRule::CNF_EQUIV_NEG1: return "CNF_EQUIV_NEG1"; + case PfRule::CNF_EQUIV_NEG2: return "CNF_EQUIV_NEG2"; + case PfRule::CNF_XOR_POS1: return "CNF_XOR_POS1"; + case PfRule::CNF_XOR_POS2: return "CNF_XOR_POS2"; + case PfRule::CNF_XOR_NEG1: return "CNF_XOR_NEG1"; + case PfRule::CNF_XOR_NEG2: return "CNF_XOR_NEG2"; + case PfRule::CNF_ITE_POS1: return "CNF_ITE_POS1"; + case PfRule::CNF_ITE_POS2: return "CNF_ITE_POS2"; + case PfRule::CNF_ITE_POS3: return "CNF_ITE_POS3"; + case PfRule::CNF_ITE_NEG1: return "CNF_ITE_NEG1"; + case PfRule::CNF_ITE_NEG2: return "CNF_ITE_NEG2"; + case PfRule::CNF_ITE_NEG3: return "CNF_ITE_NEG3"; //================================================= Unknown rule case PfRule::UNKNOWN: return "UNKNOWN"; default: return "?"; diff --git a/src/expr/proof_rule.h b/src/expr/proof_rule.h index e0a626b69..9dc1d017f 100644 --- a/src/expr/proof_rule.h +++ b/src/expr/proof_rule.h @@ -71,6 +71,262 @@ enum class PfRule : uint32_t // has the conclusion (=> F F) and has no free assumptions. More generally, a // proof with no free assumptions always concludes a valid formula. SCOPE, + //================================================= Boolean rules + // ======== Split + // Children: none + // Arguments: (F) + // --------------------- + // Conclusion: (or F (not F)) + SPLIT, + // ======== And elimination + // Children: (P:(and F1 ... Fn)) + // Arguments: (i) + // --------------------- + // Conclusion: (Fi) + AND_ELIM, + // ======== And introduction + // Children: (P1:F1 ... Pn:Fn)) + // Arguments: () + // --------------------- + // Conclusion: (and P1 ... Pn) + AND_INTRO, + // ======== Not Or elimination + // Children: (P:(not (or F1 ... Fn))) + // Arguments: (i) + // --------------------- + // Conclusion: (not Fi) + NOT_OR_ELIM, + // ======== Implication elimination + // Children: (P:(=> F1 F2)) + // Arguments: () + // --------------------- + // Conclusion: (or (not F1) F2) + IMPLIES_ELIM, + // ======== Not Implication elimination version 1 + // Children: (P:(not (=> F1 F2))) + // Arguments: () + // --------------------- + // Conclusion: (F1) + NOT_IMPLIES_ELIM1, + // ======== Not Implication elimination version 2 + // Children: (P:(not (=> F1 F2))) + // Arguments: () + // --------------------- + // Conclusion: (not F2) + NOT_IMPLIES_ELIM2, + // ======== Equivalence elimination version 1 + // Children: (P:(= F1 F2)) + // Arguments: () + // --------------------- + // Conclusion: (or (not F1) F2) + EQUIV_ELIM1, + // ======== Equivalence elimination version 2 + // Children: (P:(= F1 F2)) + // Arguments: () + // --------------------- + // Conclusion: (or F1 (not F2)) + EQUIV_ELIM2, + // ======== Not Equivalence elimination version 1 + // Children: (P:(not (= F1 F2))) + // Arguments: () + // --------------------- + // Conclusion: (or F1 F2) + NOT_EQUIV_ELIM1, + // ======== Not Equivalence elimination version 2 + // Children: (P:(not (= F1 F2))) + // Arguments: () + // --------------------- + // Conclusion: (or (not F1) (not F2)) + NOT_EQUIV_ELIM2, + // ======== XOR elimination version 1 + // Children: (P:(xor F1 F2))) + // Arguments: () + // --------------------- + // Conclusion: (or F1 F2) + XOR_ELIM1, + // ======== XOR elimination version 2 + // Children: (P:(xor F1 F2))) + // Arguments: () + // --------------------- + // Conclusion: (or (not F1) (not F2)) + XOR_ELIM2, + // ======== Not XOR elimination version 1 + // Children: (P:(not (xor F1 F2))) + // Arguments: () + // --------------------- + // Conclusion: (or F1 (not F2)) + NOT_XOR_ELIM1, + // ======== Not XOR elimination version 2 + // Children: (P:(not (xor F1 F2))) + // Arguments: () + // --------------------- + // Conclusion: (or (not F1) F2) + NOT_XOR_ELIM2, + // ======== ITE elimination version 1 + // Children: (P:(ite C F1 F2)) + // Arguments: () + // --------------------- + // Conclusion: (or (not C) F1) + ITE_ELIM1, + // ======== ITE elimination version 2 + // Children: (P:(ite C F1 F2)) + // Arguments: () + // --------------------- + // Conclusion: (or C F2) + ITE_ELIM2, + // ======== Not ITE elimination version 1 + // Children: (P:(not (ite C F1 F2))) + // Arguments: () + // --------------------- + // Conclusion: (or (not C) (not F1)) + NOT_ITE_ELIM1, + // ======== Not ITE elimination version 1 + // Children: (P:(not (ite C F1 F2))) + // Arguments: () + // --------------------- + // Conclusion: (or C (not F2)) + NOT_ITE_ELIM2, + // ======== Not ITE elimination version 1 + // Children: (P1:P P2:(not P)) + // Arguments: () + // --------------------- + // Conclusion: (false) + CONTRA, + + //================================================= De Morgan rules + // ======== Not And + // Children: (P:(not (and F1 ... Fn)) + // Arguments: () + // --------------------- + // Conclusion: (or (not F1) ... (not Fn)) + NOT_AND, + //================================================= CNF rules + // ======== CNF And Pos + // Children: () + // Arguments: ((and F1 ... Fn), i) + // --------------------- + // Conclusion: (or (not (and F1 ... Fn)) Fi) + CNF_AND_POS, + // ======== CNF And Neg + // Children: () + // Arguments: ((and F1 ... Fn)) + // --------------------- + // Conclusion: (or (and F1 ... Fn) (not F1) ... (not Fn)) + CNF_AND_NEG, + // ======== CNF Or Pos + // Children: () + // Arguments: ((or F1 ... Fn)) + // --------------------- + // Conclusion: (or (not (or F1 ... Fn)) F1 ... Fn) + CNF_OR_POS, + // ======== CNF Or Neg + // Children: () + // Arguments: ((or F1 ... Fn), i) + // --------------------- + // Conclusion: (or (or F1 ... Fn) (not Fi)) + CNF_OR_NEG, + // ======== CNF Implies Pos + // Children: () + // Arguments: ((implies F1 F2)) + // --------------------- + // Conclusion: (or (not (implies F1 F2)) (not F1) F2) + CNF_IMPLIES_POS, + // ======== CNF Implies Neg version 1 + // Children: () + // Arguments: ((implies F1 F2)) + // --------------------- + // Conclusion: (or (implies F1 F2) F1) + CNF_IMPLIES_NEG1, + // ======== CNF Implies Neg version 2 + // Children: () + // Arguments: ((implies F1 F2)) + // --------------------- + // Conclusion: (or (implies F1 F2) (not F2)) + CNF_IMPLIES_NEG2, + // ======== CNF Equiv Pos version 1 + // Children: () + // Arguments: ((= F1 F2)) + // --------------------- + // Conclusion: (or (not (= F1 F2)) (not F1) F2) + CNF_EQUIV_POS1, + // ======== CNF Equiv Pos version 2 + // Children: () + // Arguments: ((= F1 F2)) + // --------------------- + // Conclusion: (or (not (= F1 F2)) F1 (not F2)) + CNF_EQUIV_POS2, + // ======== CNF Equiv Neg version 1 + // Children: () + // Arguments: ((= F1 F2)) + // --------------------- + // Conclusion: (or (= F1 F2) F1 F2) + CNF_EQUIV_NEG1, + // ======== CNF Equiv Neg version 2 + // Children: () + // Arguments: ((= F1 F2)) + // --------------------- + // Conclusion: (or (= F1 F2) (not F1) (not F2)) + CNF_EQUIV_NEG2, + // ======== CNF Xor Pos version 1 + // Children: () + // Arguments: ((xor F1 F2)) + // --------------------- + // Conclusion: (or (not (xor F1 F2)) F1 F2) + CNF_XOR_POS1, + // ======== CNF Xor Pos version 2 + // Children: () + // Arguments: ((xor F1 F2)) + // --------------------- + // Conclusion: (or (not (xor F1 F2)) (not F1) (not F2)) + CNF_XOR_POS2, + // ======== CNF Xor Neg version 1 + // Children: () + // Arguments: ((xor F1 F2)) + // --------------------- + // Conclusion: (or (xor F1 F2) (not F1) F2) + CNF_XOR_NEG1, + // ======== CNF Xor Neg version 2 + // Children: () + // Arguments: ((xor F1 F2)) + // --------------------- + // Conclusion: (or (xor F1 F2) F1 (not F2)) + CNF_XOR_NEG2, + // ======== CNF ITE Pos version 1 + // Children: () + // Arguments: ((ite C F1 F2)) + // --------------------- + // Conclusion: (or (not (ite C F1 F2)) (not C) F1) + CNF_ITE_POS1, + // ======== CNF ITE Pos version 2 + // Children: () + // Arguments: ((ite C F1 F2)) + // --------------------- + // Conclusion: (or (not (ite C F1 F2)) C F2) + CNF_ITE_POS2, + // ======== CNF ITE Pos version 3 + // Children: () + // Arguments: ((ite C F1 F2)) + // --------------------- + // Conclusion: (or (not (ite C F1 F2)) F1 F2) + CNF_ITE_POS3, + // ======== CNF ITE Neg version 1 + // Children: () + // Arguments: ((ite C F1 F2)) + // --------------------- + // Conclusion: (or (ite C F1 F2) (not C) (not F1)) + CNF_ITE_NEG1, + // ======== CNF ITE Neg version 2 + // Children: () + // Arguments: ((ite C F1 F2)) + // --------------------- + // Conclusion: (or (ite C F1 F2) C (not F2)) + CNF_ITE_NEG2, + // ======== CNF ITE Neg version 3 + // Children: () + // Arguments: ((ite C F1 F2)) + // --------------------- + // Conclusion: (or (ite C F1 F2) (not F1) (not F2)) + CNF_ITE_NEG3, //======================== Builtin theory (common node operations) // ======== Substitution @@ -101,7 +357,7 @@ enum class PfRule : uint32_t // --------------------------------------------------------------- // Conclusion: (= t t') // where - // t' is + // t' is // toWitness(Rewriter{idr}(toSkolem(t)*sigma{ids}(Fn)*...*sigma{ids}(F1))) // toSkolem(...) converts terms from witness form to Skolem form, // toWitness(...) converts terms from Skolem form to witness form. @@ -167,7 +423,7 @@ enum class PfRule : uint32_t // Notice that we apply rewriting on the witness form of F and G, similar to // MACRO_SR_PRED_INTRO. MACRO_SR_PRED_TRANSFORM, - + //================================================= Unknown rule UNKNOWN, }; diff --git a/src/theory/booleans/proof_checker.cpp b/src/theory/booleans/proof_checker.cpp new file mode 100644 index 000000000..8fdbe9f6b --- /dev/null +++ b/src/theory/booleans/proof_checker.cpp @@ -0,0 +1,568 @@ +/********************* */ +/*! \file proof_checker.cpp + ** \verbatim + ** Top contributors (to current version): + ** Haniel Barbosa, 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 equality proof checker + **/ + +#include "theory/booleans/proof_checker.h" + +namespace CVC4 { +namespace theory { +namespace booleans { + +void BoolProofRuleChecker::registerTo(ProofChecker* pc) +{ + pc->registerChecker(PfRule::SPLIT, this); + pc->registerChecker(PfRule::AND_ELIM, this); + pc->registerChecker(PfRule::AND_INTRO, this); + pc->registerChecker(PfRule::NOT_OR_ELIM, this); + pc->registerChecker(PfRule::IMPLIES_ELIM, this); + pc->registerChecker(PfRule::NOT_IMPLIES_ELIM1, this); + pc->registerChecker(PfRule::NOT_IMPLIES_ELIM2, this); + pc->registerChecker(PfRule::EQUIV_ELIM1, this); + pc->registerChecker(PfRule::EQUIV_ELIM2, this); + pc->registerChecker(PfRule::NOT_EQUIV_ELIM1, this); + pc->registerChecker(PfRule::NOT_EQUIV_ELIM2, this); + pc->registerChecker(PfRule::XOR_ELIM1, this); + pc->registerChecker(PfRule::XOR_ELIM2, this); + pc->registerChecker(PfRule::NOT_XOR_ELIM1, this); + pc->registerChecker(PfRule::NOT_XOR_ELIM2, this); + pc->registerChecker(PfRule::ITE_ELIM1, this); + pc->registerChecker(PfRule::ITE_ELIM2, this); + pc->registerChecker(PfRule::NOT_ITE_ELIM1, this); + pc->registerChecker(PfRule::NOT_ITE_ELIM2, this); + pc->registerChecker(PfRule::NOT_AND, this); + pc->registerChecker(PfRule::CNF_AND_POS, this); + pc->registerChecker(PfRule::CNF_AND_NEG, this); + pc->registerChecker(PfRule::CNF_OR_POS, this); + pc->registerChecker(PfRule::CNF_OR_NEG, this); + pc->registerChecker(PfRule::CNF_IMPLIES_POS, this); + pc->registerChecker(PfRule::CNF_IMPLIES_NEG1, this); + pc->registerChecker(PfRule::CNF_IMPLIES_NEG2, this); + pc->registerChecker(PfRule::CNF_EQUIV_POS1, this); + pc->registerChecker(PfRule::CNF_EQUIV_POS2, this); + pc->registerChecker(PfRule::CNF_EQUIV_NEG1, this); + pc->registerChecker(PfRule::CNF_EQUIV_NEG2, this); + pc->registerChecker(PfRule::CNF_XOR_POS1, this); + pc->registerChecker(PfRule::CNF_XOR_POS2, this); + pc->registerChecker(PfRule::CNF_XOR_NEG1, this); + pc->registerChecker(PfRule::CNF_XOR_NEG2, this); + pc->registerChecker(PfRule::CNF_ITE_POS1, this); + pc->registerChecker(PfRule::CNF_ITE_POS2, this); + pc->registerChecker(PfRule::CNF_ITE_POS3, this); + pc->registerChecker(PfRule::CNF_ITE_NEG1, this); + pc->registerChecker(PfRule::CNF_ITE_NEG2, this); + pc->registerChecker(PfRule::CNF_ITE_NEG3, this); +} + +Node BoolProofRuleChecker::checkInternal(PfRule id, + const std::vector& children, + const std::vector& args) +{ + if (id == PfRule::SPLIT) + { + Assert(children.empty()); + Assert(args.size() == 1); + return NodeManager::currentNM()->mkNode( + kind::OR, args[0], args[0].notNode()); + } + // natural deduction rules + if (id == PfRule::AND_ELIM) + { + Assert(children.size() == 1); + Assert(args.size() == 1); + uint32_t i; + if (children[0].getKind() != kind::AND || !getUInt32(args[0], i)) + { + return Node::null(); + } + if (i >= children[0].getNumChildren()) + { + return Node::null(); + } + return children[0][i]; + } + if (id == PfRule::AND_INTRO) + { + Assert(children.size() >= 1); + return children.size() == 1 + ? children[0] + : NodeManager::currentNM()->mkNode(kind::AND, children); + } + if (id == PfRule::NOT_OR_ELIM) + { + Assert(children.size() == 1); + Assert(args.size() == 1); + uint32_t i; + if (children[0].getKind() != kind::NOT + || children[0][0].getKind() != kind::OR || !getUInt32(args[0], i)) + { + return Node::null(); + } + if (i >= children[0][0].getNumChildren()) + { + return Node::null(); + } + return children[0][0][i].notNode(); + } + if (id == PfRule::IMPLIES_ELIM) + { + Assert(children.size() == 1); + Assert(args.empty()); + if (children[0].getKind() != kind::IMPLIES) + { + return Node::null(); + } + return NodeManager::currentNM()->mkNode( + kind::OR, children[0][0].notNode(), children[0][1]); + } + if (id == PfRule::NOT_IMPLIES_ELIM1) + { + Assert(children.size() == 1); + Assert(args.empty()); + if (children[0].getKind() != kind::NOT + || children[0][0].getKind() != kind::IMPLIES) + { + return Node::null(); + } + return children[0][0][0]; + } + if (id == PfRule::NOT_IMPLIES_ELIM2) + { + Assert(children.size() == 1); + Assert(args.empty()); + if (children[0].getKind() != kind::NOT + || children[0][0].getKind() != kind::IMPLIES) + { + return Node::null(); + } + return children[0][0][1].notNode(); + } + if (id == PfRule::EQUIV_ELIM1) + { + Assert(children.size() == 1); + Assert(args.empty()); + if (children[0].getKind() != kind::EQUAL) + { + return Node::null(); + } + return NodeManager::currentNM()->mkNode( + kind::OR, children[0][0].notNode(), children[0][1]); + } + if (id == PfRule::EQUIV_ELIM2) + { + Assert(children.size() == 1); + Assert(args.empty()); + if (children[0].getKind() != kind::EQUAL) + { + return Node::null(); + } + return NodeManager::currentNM()->mkNode( + kind::OR, children[0][0], children[0][1].notNode()); + } + if (id == PfRule::NOT_EQUIV_ELIM1) + { + Assert(children.size() == 1); + Assert(args.empty()); + if (children[0].getKind() != kind::NOT + || children[0][0].getKind() != kind::EQUAL) + { + return Node::null(); + } + return NodeManager::currentNM()->mkNode( + kind::OR, children[0][0][0], children[0][0][1]); + } + if (id == PfRule::NOT_EQUIV_ELIM2) + { + Assert(children.size() == 1); + Assert(args.empty()); + if (children[0].getKind() != kind::NOT + || children[0][0].getKind() != kind::EQUAL) + { + return Node::null(); + } + return NodeManager::currentNM()->mkNode( + kind::OR, children[0][0][0].notNode(), children[0][0][1].notNode()); + } + if (id == PfRule::XOR_ELIM1) + { + Assert(children.size() == 1); + Assert(args.empty()); + if (children[0].getKind() != kind::XOR) + { + return Node::null(); + } + return NodeManager::currentNM()->mkNode( + kind::OR, children[0][0], children[0][1]); + } + if (id == PfRule::XOR_ELIM2) + { + Assert(children.size() == 1); + Assert(args.empty()); + if (children[0].getKind() != kind::XOR) + { + return Node::null(); + } + return NodeManager::currentNM()->mkNode( + kind::OR, children[0][0].notNode(), children[0][1].notNode()); + } + if (id == PfRule::NOT_XOR_ELIM1) + { + Assert(children.size() == 1); + Assert(args.empty()); + if (children[0].getKind() != kind::NOT + || children[0][0].getKind() != kind::XOR) + { + return Node::null(); + } + return NodeManager::currentNM()->mkNode( + kind::OR, children[0][0][0], children[0][0][1].notNode()); + } + if (id == PfRule::NOT_XOR_ELIM2) + { + Assert(children.size() == 1); + Assert(args.empty()); + if (children[0].getKind() != kind::NOT + || children[0][0].getKind() != kind::XOR) + { + return Node::null(); + } + return NodeManager::currentNM()->mkNode( + kind::OR, children[0][0][0].notNode(), children[0][0][1]); + } + if (id == PfRule::ITE_ELIM1) + { + Assert(children.size() == 1); + Assert(args.empty()); + if (children[0].getKind() != kind::ITE) + { + return Node::null(); + } + return NodeManager::currentNM()->mkNode( + kind::OR, children[0][0].notNode(), children[0][1]); + } + if (id == PfRule::ITE_ELIM2) + { + Assert(children.size() == 1); + Assert(args.empty()); + if (children[0].getKind() != kind::ITE) + { + return Node::null(); + } + return NodeManager::currentNM()->mkNode( + kind::OR, children[0][0], children[0][2]); + } + if (id == PfRule::NOT_ITE_ELIM1) + { + Assert(children.size() == 1); + Assert(args.empty()); + if (children[0].getKind() != kind::NOT + || children[0][0].getKind() != kind::ITE) + { + return Node::null(); + } + return NodeManager::currentNM()->mkNode( + kind::OR, children[0][0][0].notNode(), children[0][0][1].notNode()); + } + if (id == PfRule::NOT_ITE_ELIM2) + { + Assert(children.size() == 1); + Assert(args.empty()); + if (children[0].getKind() != kind::NOT + || children[0][0].getKind() != kind::ITE) + { + return Node::null(); + } + return NodeManager::currentNM()->mkNode( + kind::OR, children[0][0][0], children[0][0][2].notNode()); + } + // De Morgan + if (id == PfRule::NOT_AND) + { + Assert(children.size() == 1); + Assert(args.empty()); + if (children[0].getKind() != kind::NOT + || children[0][0].getKind() != kind::AND) + { + return Node::null(); + } + std::vector disjuncts; + for (unsigned i = 0, size = children[0][0].getNumChildren(); i < size; ++i) + { + disjuncts.push_back(children[0][0][i].notNode()); + } + return NodeManager::currentNM()->mkNode(kind::OR, disjuncts); + } + // valid clauses rules for Tseitin CNF transformation + if (id == PfRule::CNF_AND_POS) + { + Assert(children.empty()); + Assert(args.size() == 2); + uint32_t i; + if (args[0].getKind() != kind::AND || !getUInt32(args[1], i)) + { + return Node::null(); + } + if (i >= args[0].getNumChildren()) + { + return Node::null(); + } + return NodeManager::currentNM()->mkNode( + kind::OR, args[0].notNode(), args[0][i]); + } + if (id == PfRule::CNF_AND_NEG) + { + Assert(children.empty()); + Assert(args.size() == 1); + if (args[0].getKind() != kind::AND) + { + return Node::null(); + } + std::vector disjuncts{args[0]}; + for (unsigned i = 0, size = args[0].getNumChildren(); i < size; ++i) + { + disjuncts.push_back(args[0][i].notNode()); + } + return NodeManager::currentNM()->mkNode(kind::OR, disjuncts); + } + if (id == PfRule::CNF_OR_POS) + { + Assert(children.empty()); + Assert(args.size() == 1); + if (args[0].getKind() != kind::OR) + { + return Node::null(); + } + std::vector disjuncts{args[0].notNode()}; + for (unsigned i = 0, size = args[0].getNumChildren(); i < size; ++i) + { + disjuncts.push_back(args[0][i]); + } + return NodeManager::currentNM()->mkNode(kind::OR, disjuncts); + } + if (id == PfRule::CNF_OR_NEG) + { + Assert(children.empty()); + Assert(args.size() == 2); + uint32_t i; + if (args[0].getKind() != kind::OR || !getUInt32(args[1], i)) + { + return Node::null(); + } + if (i >= args[0].getNumChildren()) + { + return Node::null(); + } + return NodeManager::currentNM()->mkNode( + kind::OR, args[0], args[0][i].notNode()); + } + if (id == PfRule::CNF_IMPLIES_POS) + { + Assert(children.empty()); + Assert(args.size() == 1); + if (args[0].getKind() != kind::IMPLIES) + { + return Node::null(); + } + std::vector disjuncts{ + args[0].notNode(), args[0][0].notNode(), args[0][1]}; + return NodeManager::currentNM()->mkNode(kind::OR, disjuncts); + } + if (id == PfRule::CNF_IMPLIES_NEG1) + { + Assert(children.empty()); + Assert(args.size() == 1); + if (args[0].getKind() != kind::IMPLIES) + { + return Node::null(); + } + std::vector disjuncts{args[0], args[0][0]}; + return NodeManager::currentNM()->mkNode(kind::OR, disjuncts); + } + if (id == PfRule::CNF_IMPLIES_NEG2) + { + Assert(children.empty()); + Assert(args.size() == 1); + if (args[0].getKind() != kind::IMPLIES) + { + return Node::null(); + } + std::vector disjuncts{args[0], args[0][1].notNode()}; + return NodeManager::currentNM()->mkNode(kind::OR, disjuncts); + } + if (id == PfRule::CNF_EQUIV_POS1) + { + Assert(children.empty()); + Assert(args.size() == 1); + if (args[0].getKind() != kind::EQUAL) + { + return Node::null(); + } + std::vector disjuncts{ + args[0].notNode(), args[0][0].notNode(), args[0][1]}; + return NodeManager::currentNM()->mkNode(kind::OR, disjuncts); + } + if (id == PfRule::CNF_EQUIV_POS2) + { + Assert(children.empty()); + Assert(args.size() == 1); + if (args[0].getKind() != kind::EQUAL) + { + return Node::null(); + } + std::vector disjuncts{ + args[0].notNode(), args[0][0], args[0][1].notNode()}; + return NodeManager::currentNM()->mkNode(kind::OR, disjuncts); + } + if (id == PfRule::CNF_EQUIV_NEG1) + { + Assert(children.empty()); + Assert(args.size() == 1); + if (args[0].getKind() != kind::EQUAL) + { + return Node::null(); + } + std::vector disjuncts{args[0], args[0][0], args[0][1]}; + return NodeManager::currentNM()->mkNode(kind::OR, disjuncts); + } + if (id == PfRule::CNF_EQUIV_NEG2) + { + Assert(children.empty()); + Assert(args.size() == 1); + if (args[0].getKind() != kind::EQUAL) + { + return Node::null(); + } + std::vector disjuncts{ + args[0], args[0][0].notNode(), args[0][1].notNode()}; + return NodeManager::currentNM()->mkNode(kind::OR, disjuncts); + } + if (id == PfRule::CNF_XOR_POS1) + { + Assert(children.empty()); + Assert(args.size() == 1); + if (args[0].getKind() != kind::XOR) + { + return Node::null(); + } + std::vector disjuncts{args[0].notNode(), args[0][0], args[0][1]}; + return NodeManager::currentNM()->mkNode(kind::OR, disjuncts); + } + if (id == PfRule::CNF_XOR_POS2) + { + Assert(children.empty()); + Assert(args.size() == 1); + if (args[0].getKind() != kind::XOR) + { + return Node::null(); + } + std::vector disjuncts{ + args[0].notNode(), args[0][0].notNode(), args[0][1].notNode()}; + return NodeManager::currentNM()->mkNode(kind::OR, disjuncts); + } + if (id == PfRule::CNF_XOR_NEG1) + { + Assert(children.empty()); + Assert(args.size() == 1); + if (args[0].getKind() != kind::XOR) + { + return Node::null(); + } + std::vector disjuncts{args[0], args[0][0].notNode(), args[0][1]}; + return NodeManager::currentNM()->mkNode(kind::OR, disjuncts); + } + if (id == PfRule::CNF_XOR_NEG2) + { + Assert(children.empty()); + Assert(args.size() == 1); + if (args[0].getKind() != kind::XOR) + { + return Node::null(); + } + std::vector disjuncts{args[0], args[0][0], args[0][1].notNode()}; + return NodeManager::currentNM()->mkNode(kind::OR, disjuncts); + } + if (id == PfRule::CNF_ITE_POS1) + { + Assert(children.empty()); + Assert(args.size() == 1); + if (args[0].getKind() != kind::ITE) + { + return Node::null(); + } + std::vector disjuncts{ + args[0].notNode(), args[0][0].notNode(), args[0][1]}; + return NodeManager::currentNM()->mkNode(kind::OR, disjuncts); + } + if (id == PfRule::CNF_ITE_POS2) + { + Assert(children.empty()); + Assert(args.size() == 1); + if (args[0].getKind() != kind::ITE) + { + return Node::null(); + } + std::vector disjuncts{args[0].notNode(), args[0][0], args[0][2]}; + return NodeManager::currentNM()->mkNode(kind::OR, disjuncts); + } + if (id == PfRule::CNF_ITE_POS3) + { + Assert(children.empty()); + Assert(args.size() == 1); + if (args[0].getKind() != kind::ITE) + { + return Node::null(); + } + std::vector disjuncts{args[0].notNode(), args[0][1], args[0][2]}; + return NodeManager::currentNM()->mkNode(kind::OR, disjuncts); + } + if (id == PfRule::CNF_ITE_NEG1) + { + Assert(children.empty()); + Assert(args.size() == 1); + if (args[0].getKind() != kind::ITE) + { + return Node::null(); + } + std::vector disjuncts{ + args[0], args[0][0].notNode(), args[0][1].notNode()}; + return NodeManager::currentNM()->mkNode(kind::OR, disjuncts); + } + if (id == PfRule::CNF_ITE_NEG2) + { + Assert(children.empty()); + Assert(args.size() == 1); + if (args[0].getKind() != kind::ITE) + { + return Node::null(); + } + std::vector disjuncts{args[0], args[0][0], args[0][2].notNode()}; + return NodeManager::currentNM()->mkNode(kind::OR, disjuncts); + } + if (id == PfRule::CNF_ITE_NEG3) + { + Assert(children.empty()); + Assert(args.size() == 1); + if (args[0].getKind() != kind::ITE) + { + return Node::null(); + } + std::vector disjuncts{ + args[0], args[0][1].notNode(), args[0][2].notNode()}; + return NodeManager::currentNM()->mkNode(kind::OR, disjuncts); + } + // no rule + return Node::null(); +} + +} // namespace booleans +} // namespace theory +} // namespace CVC4 diff --git a/src/theory/booleans/proof_checker.h b/src/theory/booleans/proof_checker.h new file mode 100644 index 000000000..56414ff91 --- /dev/null +++ b/src/theory/booleans/proof_checker.h @@ -0,0 +1,49 @@ +/********************* */ +/*! \file proof_checker.h + ** \verbatim + ** Top contributors (to current version): + ** Haniel Barbosa, 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 Boolean proof checker utility + **/ + +#include "cvc4_private.h" + +#ifndef CVC4__THEORY__BOOLEANS__PROOF_CHECKER_H +#define CVC4__THEORY__BOOLEANS__PROOF_CHECKER_H + +#include "expr/node.h" +#include "expr/proof_checker.h" +#include "expr/proof_node.h" + +namespace CVC4 { +namespace theory { +namespace booleans { + +/** A checker for boolean reasoning in proofs */ +class BoolProofRuleChecker : public ProofRuleChecker +{ + public: + BoolProofRuleChecker() {} + ~BoolProofRuleChecker() {} + + /** Register all rules owned by this rule checker into pc. */ + void registerTo(ProofChecker* pc) override; + + protected: + /** Return the conclusion of the given proof step, or null if it is invalid */ + Node checkInternal(PfRule id, + const std::vector& children, + const std::vector& args) override; +}; + +} // namespace booleans +} // namespace theory +} // namespace CVC4 + +#endif /* CVC4__THEORY__BOOLEANS__PROOF_CHECKER_H */ -- cgit v1.2.3 From 418b0281e62a6b657da32f6504965269ad90c18b Mon Sep 17 00:00:00 2001 From: Haniel Barbosa Date: Wed, 3 Jun 2020 20:52:49 -0300 Subject: (proof-new) Adding rules and proof checker for EUF (#4559) --- src/CMakeLists.txt | 2 + src/expr/node_manager.cpp | 22 +++++ src/expr/node_manager.h | 14 ++++ src/expr/proof_rule.cpp | 10 ++- src/expr/proof_rule.h | 50 ++++++++++++ src/theory/uf/proof_checker.cpp | 172 ++++++++++++++++++++++++++++++++++++++++ src/theory/uf/proof_checker.h | 49 ++++++++++++ 7 files changed, 318 insertions(+), 1 deletion(-) create mode 100644 src/theory/uf/proof_checker.cpp create mode 100644 src/theory/uf/proof_checker.h (limited to 'src') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 97c352d5d..44b5dfeaf 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -754,6 +754,8 @@ libcvc4_add_sources( theory/uf/equality_engine.cpp theory/uf/equality_engine.h theory/uf/equality_engine_types.h + theory/uf/proof_checker.cpp + theory/uf/proof_checker.h theory/uf/ho_extension.cpp theory/uf/ho_extension.h theory/uf/symmetry_breaker.cpp diff --git a/src/expr/node_manager.cpp b/src/expr/node_manager.cpp index 7bc98f65d..427afd5af 100644 --- a/src/expr/node_manager.cpp +++ b/src/expr/node_manager.cpp @@ -865,4 +865,26 @@ void NodeManager::debugHook(int debugFlag){ // For debugging purposes only, DO NOT CHECK IN ANY CODE! } +Kind NodeManager::getKindForFunction(TNode fun) +{ + TypeNode tn = fun.getType(); + if (tn.isFunction()) + { + return kind::APPLY_UF; + } + else if (tn.isConstructor()) + { + return kind::APPLY_CONSTRUCTOR; + } + else if (tn.isSelector()) + { + return kind::APPLY_SELECTOR; + } + else if (tn.isTester()) + { + return kind::APPLY_TESTER; + } + return kind::UNDEFINED_KIND; +} + }/* CVC4 namespace */ diff --git a/src/expr/node_manager.h b/src/expr/node_manager.h index 45d3d5b7d..1fab328e9 100644 --- a/src/expr/node_manager.h +++ b/src/expr/node_manager.h @@ -444,6 +444,20 @@ public: /** Get a Kind from an operator expression */ static inline Kind operatorToKind(TNode n); + /** Get corresponding application kind for function + * + * Different functional nodes are applied differently, according to their + * type. For example, uninterpreted functions (of FUNCTION_TYPE) are applied + * via APPLY_UF, while constructors (of CONSTRUCTOR_TYPE) via + * APPLY_CONSTRUCTOR. This method provides the correct application according + * to which functional type fun has. + * + * @param fun The functional node + * @return the correct application kind for fun. If fun's type is not function + * like (see TypeNode::isFunctionLike), then UNDEFINED_KIND is returned. + */ + static Kind getKindForFunction(TNode fun); + // general expression-builders /** Create a node with one child. */ diff --git a/src/expr/proof_rule.cpp b/src/expr/proof_rule.cpp index 0e73d8c10..595e1d5f7 100644 --- a/src/expr/proof_rule.cpp +++ b/src/expr/proof_rule.cpp @@ -31,7 +31,15 @@ const char* toString(PfRule id) case PfRule::MACRO_SR_PRED_INTRO: return "MACRO_SR_PRED_INTRO"; case PfRule::MACRO_SR_PRED_ELIM: return "MACRO_SR_PRED_ELIM"; case PfRule::MACRO_SR_PRED_TRANSFORM: return "MACRO_SR_PRED_TRANSFORM"; - + //================================================= Equality rules + case PfRule::REFL: return "REFL"; + case PfRule::SYMM: return "SYMM"; + case PfRule::TRANS: return "TRANS"; + case PfRule::CONG: return "CONG"; + case PfRule::TRUE_INTRO: return "TRUE_INTRO"; + case PfRule::TRUE_ELIM: return "TRUE_ELIM"; + case PfRule::FALSE_INTRO: return "FALSE_INTRO"; + case PfRule::FALSE_ELIM: return "FALSE_ELIM"; //================================================= Boolean rules case PfRule::SPLIT: return "SPLIT"; case PfRule::AND_ELIM: return "AND_ELIM"; diff --git a/src/expr/proof_rule.h b/src/expr/proof_rule.h index 9dc1d017f..6acccfffd 100644 --- a/src/expr/proof_rule.h +++ b/src/expr/proof_rule.h @@ -71,6 +71,56 @@ enum class PfRule : uint32_t // has the conclusion (=> F F) and has no free assumptions. More generally, a // proof with no free assumptions always concludes a valid formula. SCOPE, + //================================================= Equality rules + // ======== Reflexive + // Children: none + // Arguments: (t) + // --------------------- + // Conclusion: (= t t) + REFL, + // ======== Symmetric + // Children: (P:(= t1 t2)) or (P:(not (= t1 t2))) + // Arguments: none + // ----------------------- + // Conclusion: (= t2 t1) or (not (= t2 t1)) + SYMM, + // ======== Transitivity + // Children: (P1:(= t1 t2), ..., Pn:(= t{n-1} tn)) + // Arguments: none + // ----------------------- + // Conclusion: (= t1 tn) + TRANS, + // ======== Congruence (subsumed by Substitute?) + // Children: (P1:(= t1 s1), ..., Pn:(= tn sn)) + // Arguments: (f) + // --------------------------------------------- + // Conclusion: (= (f t1 ... tn) (f s1 ... sn)) + CONG, + // ======== True intro + // Children: (P:F) + // Arguments: none + // ---------------------------------------- + // Conclusion: (= F true) + TRUE_INTRO, + // ======== True elim + // Children: (P:(= F true) + // Arguments: none + // ---------------------------------------- + // Conclusion: F + TRUE_ELIM, + // ======== False intro + // Children: (P:(not F)) + // Arguments: none + // ---------------------------------------- + // Conclusion: (= F false) + FALSE_INTRO, + // ======== False elim + // Children: (P:(= F false) + // Arguments: none + // ---------------------------------------- + // Conclusion: (not F) + FALSE_ELIM, + //================================================= Boolean rules // ======== Split // Children: none diff --git a/src/theory/uf/proof_checker.cpp b/src/theory/uf/proof_checker.cpp new file mode 100644 index 000000000..28ae34b7b --- /dev/null +++ b/src/theory/uf/proof_checker.cpp @@ -0,0 +1,172 @@ +/********************* */ +/*! \file proof_checker.cpp + ** \verbatim + ** Top contributors (to current version): + ** Haniel Barbosa, 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 equality proof checker + **/ + +#include "theory/uf/proof_checker.h" + +using namespace CVC4::kind; + +namespace CVC4 { +namespace theory { +namespace uf { + +void UfProofRuleChecker::registerTo(ProofChecker* pc) +{ + // add checkers + pc->registerChecker(PfRule::REFL, this); + pc->registerChecker(PfRule::SYMM, this); + pc->registerChecker(PfRule::TRANS, this); + pc->registerChecker(PfRule::CONG, this); + pc->registerChecker(PfRule::TRUE_INTRO, this); + pc->registerChecker(PfRule::TRUE_ELIM, this); + pc->registerChecker(PfRule::FALSE_INTRO, this); + pc->registerChecker(PfRule::FALSE_ELIM, this); +} + +Node UfProofRuleChecker::checkInternal(PfRule id, + const std::vector& children, + const std::vector& args) +{ + // compute what was proven + if (id == PfRule::REFL) + { + Assert(children.empty()); + Assert(args.size() == 1); + return args[0].eqNode(args[0]); + } + else if (id == PfRule::SYMM) + { + Assert(children.size() == 1); + Assert(args.empty()); + bool polarity = children[0].getKind() != NOT; + Node eqp = polarity ? children[0] : children[0][0]; + if (eqp.getKind() != EQUAL) + { + // not a (dis)equality + return Node::null(); + } + Node conc = eqp[1].eqNode(eqp[0]); + return polarity ? conc : conc.notNode(); + } + else if (id == PfRule::TRANS) + { + Assert(children.size() > 0); + Assert(args.empty()); + Node first; + Node curr; + for (size_t i = 0, nchild = children.size(); i < nchild; i++) + { + Node eqp = children[i]; + if (eqp.getKind() != EQUAL) + { + return Node::null(); + } + if (first.isNull()) + { + first = eqp[0]; + } + else if (eqp[0] != curr) + { + return Node::null(); + } + curr = eqp[1]; + } + return first.eqNode(curr); + } + else if (id == PfRule::CONG) + { + Assert(children.size() > 0); + Assert(args.size() == 1); + // We do congruence over builtin kinds using operatorToKind + std::vector lchildren; + std::vector rchildren; + // get the expected kind for args[0] + Kind k = NodeManager::getKindForFunction(args[0]); + if (k == kind::UNDEFINED_KIND) + { + k = NodeManager::operatorToKind(args[0]); + } + if (k == kind::UNDEFINED_KIND) + { + return Node::null(); + } + Trace("uf-pfcheck") << "congruence for " << args[0] << " uses kind " << k + << ", metakind=" << kind::metaKindOf(k) << std::endl; + if (kind::metaKindOf(k) == kind::metakind::PARAMETERIZED) + { + // parameterized kinds require the operator + lchildren.push_back(args[0]); + rchildren.push_back(args[0]); + } + for (size_t i = 0, nchild = children.size(); i < nchild; i++) + { + Node eqp = children[i]; + if (eqp.getKind() != EQUAL) + { + return Node::null(); + } + lchildren.push_back(eqp[0]); + rchildren.push_back(eqp[1]); + } + NodeManager* nm = NodeManager::currentNM(); + Node l = nm->mkNode(k, lchildren); + Node r = nm->mkNode(k, rchildren); + return l.eqNode(r); + } + else if (id == PfRule::TRUE_INTRO) + { + Assert(children.size() == 1); + Assert(args.empty()); + Node trueNode = NodeManager::currentNM()->mkConst(true); + return children[0].eqNode(trueNode); + } + else if (id == PfRule::TRUE_ELIM) + { + Assert(children.size() == 1); + Assert(args.empty()); + if (children[0].getKind() != EQUAL || !children[0][1].isConst() + || !children[0][1].getConst()) + { + return Node::null(); + } + return children[0][0]; + } + else if (id == PfRule::FALSE_INTRO) + { + Assert(children.size() == 1); + Assert(args.empty()); + if (children[0].getKind() != kind::NOT) + { + return Node::null(); + } + Node falseNode = NodeManager::currentNM()->mkConst(false); + return children[0][0].eqNode(falseNode); + } + else if (id == PfRule::FALSE_ELIM) + { + Assert(children.size() == 1); + Assert(args.empty()); + if (children[0].getKind() != EQUAL || !children[0][1].isConst() + || children[0][1].getConst()) + { + return Node::null(); + } + return children[0][0].notNode(); + } + // no rule + return Node::null(); +} + +} // namespace uf +} // namespace theory +} // namespace CVC4 diff --git a/src/theory/uf/proof_checker.h b/src/theory/uf/proof_checker.h new file mode 100644 index 000000000..022574eab --- /dev/null +++ b/src/theory/uf/proof_checker.h @@ -0,0 +1,49 @@ +/********************* */ +/*! \file proof_checker.h + ** \verbatim + ** Top contributors (to current version): + ** Haniel Barbosa, 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 Equality proof checker utility + **/ + +#include "cvc4_private.h" + +#ifndef CVC4__THEORY__UF__PROOF_CHECKER_H +#define CVC4__THEORY__UF__PROOF_CHECKER_H + +#include "expr/node.h" +#include "expr/proof_checker.h" +#include "expr/proof_node.h" + +namespace CVC4 { +namespace theory { +namespace uf { + +/** A checker for builtin proofs */ +class UfProofRuleChecker : public ProofRuleChecker +{ + public: + UfProofRuleChecker() {} + ~UfProofRuleChecker() {} + + /** Register all rules owned by this rule checker into pc. */ + void registerTo(ProofChecker* pc) override; + + protected: + /** Return the conclusion of the given proof step, or null if it is invalid */ + Node checkInternal(PfRule id, + const std::vector& children, + const std::vector& args) override; +}; + +} // namespace uf +} // namespace theory +} // namespace CVC4 + +#endif /* CVC4__THEORY__UF__PROOF_CHECKER_H */ -- cgit v1.2.3 From 5938faaf034a761f3462d8e03b86b1726a332f68 Mon Sep 17 00:00:00 2001 From: Aina Niemetz Date: Wed, 3 Jun 2020 20:56:24 -0700 Subject: New C++ Api: First batch of API guards. (#4557) This is the first batch of API guards, mainly extending existing guards in the Solver object with checks that Ops, Terms, Sorts, and datatype objects are associated to this solver object. This further changes how DatatypeConstructorDecl objects are created. Previously, they were not created via the Solver object (while DatatypeDecl was). Now, they are created via Solver::mkDatatypeConstructorDecl, consistent with how DatatypeDecl objects are created. --- examples/api/datatypes-new.cpp | 12 +- examples/api/python/datatypes.py | 12 +- src/api/cvc4cpp.cpp | 210 ++++++++++++++++++++++----- src/api/cvc4cpp.h | 47 ++++-- src/api/python/cvc4.pxd | 2 +- src/api/python/cvc4.pxi | 17 ++- src/parser/cvc/Cvc.g | 43 +++--- src/parser/smt2/Smt2.g | 7 +- src/parser/tptp/Tptp.g | 4 +- test/unit/api/datatype_api_black.h | 29 ++-- test/unit/api/solver_black.h | 286 ++++++++++++++++++++++++++++++++++--- test/unit/api/sort_black.h | 30 ++-- test/unit/api/term_black.h | 4 +- 13 files changed, 559 insertions(+), 144 deletions(-) (limited to 'src') diff --git a/examples/api/datatypes-new.cpp b/examples/api/datatypes-new.cpp index 9cfc7992c..500cb0710 100644 --- a/examples/api/datatypes-new.cpp +++ b/examples/api/datatypes-new.cpp @@ -91,8 +91,8 @@ void test(Solver& slv, Sort& consListSort) DatatypeDecl paramConsListSpec = slv.mkDatatypeDecl("paramlist", sort); // give the datatype a name - DatatypeConstructorDecl paramCons("cons"); - DatatypeConstructorDecl paramNil("nil"); + DatatypeConstructorDecl paramCons = slv.mkDatatypeConstructorDecl("cons"); + DatatypeConstructorDecl paramNil = slv.mkDatatypeConstructorDecl("nil"); paramCons.addSelector("head", sort); paramCons.addSelectorSelf("tail"); paramConsListSpec.addConstructor(paramCons); @@ -144,11 +144,11 @@ int main() DatatypeDecl consListSpec = slv.mkDatatypeDecl("list"); // give the datatype a name - DatatypeConstructorDecl cons("cons"); + DatatypeConstructorDecl cons = slv.mkDatatypeConstructorDecl("cons"); cons.addSelector("head", slv.getIntegerSort()); cons.addSelectorSelf("tail"); consListSpec.addConstructor(cons); - DatatypeConstructorDecl nil("nil"); + DatatypeConstructorDecl nil = slv.mkDatatypeConstructorDecl("nil"); consListSpec.addConstructor(nil); std::cout << "spec is:" << std::endl << consListSpec << std::endl; @@ -167,10 +167,10 @@ int main() << ">>> Alternatively, use declareDatatype" << std::endl; std::cout << std::endl; - DatatypeConstructorDecl cons2("cons"); + DatatypeConstructorDecl cons2 = slv.mkDatatypeConstructorDecl("cons"); cons2.addSelector("head", slv.getIntegerSort()); cons2.addSelectorSelf("tail"); - DatatypeConstructorDecl nil2("nil"); + DatatypeConstructorDecl nil2 = slv.mkDatatypeConstructorDecl("nil"); std::vector ctors = {cons2, nil2}; Sort consListSort2 = slv.declareDatatype("list2", ctors); test(slv, consListSort2); diff --git a/examples/api/python/datatypes.py b/examples/api/python/datatypes.py index ded268d69..1a67f5661 100755 --- a/examples/api/python/datatypes.py +++ b/examples/api/python/datatypes.py @@ -65,8 +65,8 @@ def test(slv, consListSort): # constructor "cons". sort = slv.mkParamSort("T") paramConsListSpec = slv.mkDatatypeDecl("paramlist", sort) - paramCons = pycvc4.DatatypeConstructorDecl("cons") - paramNil = pycvc4.DatatypeConstructorDecl("nil") + paramCons = slv.mkDatatypeConstructorDecl("cons") + paramNil = slv.mkDatatypeConstructorDecl("nil") paramCons.addSelector("head", sort) paramCons.addSelectorSelf("tail") paramConsListSpec.addConstructor(paramCons) @@ -102,11 +102,11 @@ if __name__ == "__main__": # symbols are assigned to its constructors, selectors, and testers. consListSpec = slv.mkDatatypeDecl("list") # give the datatype a name - cons = pycvc4.DatatypeConstructorDecl("cons") + cons = slv.mkDatatypeConstructorDecl("cons") cons.addSelector("head", slv.getIntegerSort()) cons.addSelectorSelf("tail") consListSpec.addConstructor(cons) - nil = pycvc4.DatatypeConstructorDecl("nil") + nil = slv.mkDatatypeConstructorDecl("nil") consListSpec.addConstructor(nil) print("spec is {}".format(consListSpec)) @@ -122,10 +122,10 @@ if __name__ == "__main__": print("### Alternatively, use declareDatatype") - cons2 = pycvc4.DatatypeConstructorDecl("cons") + cons2 = slv.mkDatatypeConstructorDecl("cons") cons2.addSelector("head", slv.getIntegerSort()) cons2.addSelectorSelf("tail") - nil2 = pycvc4.DatatypeConstructorDecl("nil") + nil2 = slv.mkDatatypeConstructorDecl("nil") ctors = [cons2, nil2] consListSort2 = slv.declareDatatype("list2", ctors) test(slv, consListSort2) diff --git a/src/api/cvc4cpp.cpp b/src/api/cvc4cpp.cpp index 0bfb9a325..f225da333 100644 --- a/src/api/cvc4cpp.cpp +++ b/src/api/cvc4cpp.cpp @@ -702,8 +702,6 @@ class CVC4ApiExceptionStream CVC4_API_CHECK(isDefinedKind(kind)) \ << "Invalid kind '" << kindToString(kind) << "'"; -#define CVC4_API_SORT_CHECK_SOLVER(sort) - #define CVC4_API_KIND_CHECK_EXPECTED(cond, kind) \ CVC4_PREDICT_TRUE(cond) \ ? (void)0 \ @@ -726,12 +724,12 @@ class CVC4ApiExceptionStream & CVC4ApiExceptionStream().ostream() \ << "Invalid size of argument '" << #arg << "', expected " -#define CVC4_API_ARG_AT_INDEX_CHECK_EXPECTED(cond, what, arg, idx) \ - CVC4_PREDICT_TRUE(cond) \ - ? (void)0 \ - : OstreamVoider() \ - & CVC4ApiExceptionStream().ostream() \ - << "Invalid " << what << " '" << arg << "' at index" << idx \ +#define CVC4_API_ARG_AT_INDEX_CHECK_EXPECTED(cond, what, arg, idx) \ + CVC4_PREDICT_TRUE(cond) \ + ? (void)0 \ + : OstreamVoider() \ + & CVC4ApiExceptionStream().ostream() \ + << "Invalid " << what << " '" << arg << "' at index " << idx \ << ", expected " #define CVC4_API_SOLVER_TRY_CATCH_BEGIN \ @@ -741,6 +739,19 @@ class CVC4ApiExceptionStream } \ catch (const CVC4::Exception& e) { throw CVC4ApiException(e.getMessage()); } \ catch (const std::invalid_argument& e) { throw CVC4ApiException(e.what()); } + +#define CVC4_API_SOLVER_CHECK_SORT(sort) \ + CVC4_API_CHECK(this == sort.d_solver) \ + << "Given sort is not associated with this solver"; + +#define CVC4_API_SOLVER_CHECK_TERM(term) \ + CVC4_API_CHECK(this == term.d_solver) \ + << "Given term is not associated with this solver"; + +#define CVC4_API_SOLVER_CHECK_OP(op) \ + CVC4_API_CHECK(this == op.d_solver) \ + << "Given operator is not associated with this solver"; + } // namespace /* -------------------------------------------------------------------------- */ @@ -1612,6 +1623,7 @@ Term::const_iterator::const_iterator(const const_iterator& it) { if (it.d_orig_expr != nullptr) { + d_solver = it.d_solver; d_orig_expr = it.d_orig_expr; d_pos = it.d_pos; } @@ -1619,6 +1631,7 @@ Term::const_iterator::const_iterator(const const_iterator& it) Term::const_iterator& Term::const_iterator::operator=(const const_iterator& it) { + d_solver = it.d_solver; d_orig_expr = it.d_orig_expr; d_pos = it.d_pos; return *this; @@ -1630,7 +1643,8 @@ bool Term::const_iterator::operator==(const const_iterator& it) const { return false; } - return (*d_orig_expr == *it.d_orig_expr) && (d_pos == it.d_pos); + return (d_solver == it.d_solver && *d_orig_expr == *it.d_orig_expr) + && (d_pos == it.d_pos); } bool Term::const_iterator::operator!=(const const_iterator& it) const @@ -1756,8 +1770,14 @@ size_t TermHashFunction::operator()(const Term& t) const /* DatatypeConstructorDecl -------------------------------------------------- */ -DatatypeConstructorDecl::DatatypeConstructorDecl(const std::string& name) - : d_ctor(new CVC4::DatatypeConstructor(name)) +DatatypeConstructorDecl::DatatypeConstructorDecl() + : d_solver(nullptr), d_ctor(nullptr) +{ +} + +DatatypeConstructorDecl::DatatypeConstructorDecl(const Solver* slv, + const std::string& name) + : d_solver(slv), d_ctor(new CVC4::DatatypeConstructor(name)) { } @@ -1804,10 +1824,13 @@ std::ostream& operator<<(std::ostream& out, /* DatatypeDecl ------------------------------------------------------------- */ +DatatypeDecl::DatatypeDecl() : d_solver(nullptr), d_dtype(nullptr) {} + DatatypeDecl::DatatypeDecl(const Solver* slv, const std::string& name, bool isCoDatatype) - : d_dtype(new CVC4::Datatype(slv->getExprManager(), name, isCoDatatype)) + : d_solver(slv), + d_dtype(new CVC4::Datatype(slv->getExprManager(), name, isCoDatatype)) { } @@ -1815,7 +1838,8 @@ DatatypeDecl::DatatypeDecl(const Solver* slv, const std::string& name, Sort param, bool isCoDatatype) - : d_dtype(new CVC4::Datatype(slv->getExprManager(), + : d_solver(slv), + d_dtype(new CVC4::Datatype(slv->getExprManager(), name, std::vector{*param.d_type}, isCoDatatype)) @@ -1826,6 +1850,7 @@ DatatypeDecl::DatatypeDecl(const Solver* slv, const std::string& name, const std::vector& params, bool isCoDatatype) + : d_solver(slv) { std::vector tparams; for (const Sort& p : params) @@ -1838,8 +1863,6 @@ DatatypeDecl::DatatypeDecl(const Solver* slv, bool DatatypeDecl::isNullHelper() const { return !d_dtype; } -DatatypeDecl::DatatypeDecl() {} - DatatypeDecl::~DatatypeDecl() {} void DatatypeDecl::addConstructor(const DatatypeConstructorDecl& ctor) @@ -2683,8 +2706,11 @@ Term Solver::mkTermHelper(Kind kind, const std::vector& children) const for (size_t i = 0, size = children.size(); i < size; ++i) { CVC4_API_ARG_AT_INDEX_CHECK_EXPECTED( - !children[i].isNull(), "parameter term", children[i], i) + !children[i].isNull(), "child term", children[i], i) << "non-null term"; + CVC4_API_ARG_AT_INDEX_CHECK_EXPECTED( + this == children[i].d_solver, "child term", children[i], i) + << "a child term associated to this solver object"; } std::vector echildren = termVectorToExprs(children); @@ -2749,13 +2775,24 @@ std::vector Solver::mkDatatypeSortsInternal( CVC4_API_SOLVER_TRY_CATCH_BEGIN; std::vector datatypes; - for (size_t i = 0, ndts = dtypedecls.size(); i < ndts; i++) - { - CVC4_API_ARG_CHECK_EXPECTED(dtypedecls[i].getNumConstructors() > 0, - dtypedecls[i]) + for (size_t i = 0, ndts = dtypedecls.size(); i < ndts; ++i) + { + CVC4_API_ARG_AT_INDEX_CHECK_EXPECTED(this == dtypedecls[i].d_solver, + "datatype declaration", + dtypedecls[i], + i) + << "a datatype declaration associated to this solver object"; + CVC4_API_ARG_AT_INDEX_CHECK_EXPECTED(dtypedecls[i].getNumConstructors() > 0, + "datatype declaration", + dtypedecls[i], + i) << "a datatype declaration with at least one constructor"; datatypes.push_back(dtypedecls[i].getDatatype()); } + for (auto& sort : unresolvedSorts) + { + CVC4_API_SOLVER_CHECK_SORT(sort); + } std::set utypes = sortSetToTypes(unresolvedSorts); std::vector dtypes = d_exprMgr->mkMutualDatatypeTypes(datatypes, utypes); @@ -2778,6 +2815,7 @@ std::vector Solver::sortVectorToTypes( std::vector res; for (const Sort& s : sorts) { + CVC4_API_SOLVER_CHECK_SORT(s); res.push_back(*s.d_type); } return res; @@ -2789,6 +2827,7 @@ std::vector Solver::termVectorToExprs( std::vector res; for (const Term& t : terms) { + CVC4_API_SOLVER_CHECK_TERM(t); res.push_back(*t.d_expr); } return res; @@ -2877,6 +2916,8 @@ Sort Solver::mkArraySort(Sort indexSort, Sort elemSort) const << "non-null index sort"; CVC4_API_ARG_CHECK_EXPECTED(!elemSort.isNull(), elemSort) << "non-null element sort"; + CVC4_API_SOLVER_CHECK_SORT(indexSort); + CVC4_API_SOLVER_CHECK_SORT(elemSort); return Sort(this, d_exprMgr->mkArrayType(*indexSort.d_type, *elemSort.d_type)); @@ -2908,6 +2949,8 @@ Sort Solver::mkFloatingPointSort(uint32_t exp, uint32_t sig) const Sort Solver::mkDatatypeSort(DatatypeDecl dtypedecl) const { CVC4_API_SOLVER_TRY_CATCH_BEGIN; + CVC4_API_CHECK(this == dtypedecl.d_solver) + << "Given datatype declaration is not associated with this solver"; CVC4_API_ARG_CHECK_EXPECTED(dtypedecl.getNumConstructors() > 0, dtypedecl) << "a datatype declaration with at least one constructor"; @@ -2934,6 +2977,8 @@ Sort Solver::mkFunctionSort(Sort domain, Sort codomain) const CVC4_API_SOLVER_TRY_CATCH_BEGIN; CVC4_API_ARG_CHECK_EXPECTED(!codomain.isNull(), codomain) << "non-null codomain sort"; + CVC4_API_SOLVER_CHECK_SORT(domain); + CVC4_API_SOLVER_CHECK_SORT(codomain); CVC4_API_ARG_CHECK_EXPECTED(domain.isFirstClass(), domain) << "first-class sort as domain sort for function sort"; CVC4_API_ARG_CHECK_EXPECTED(codomain.isFirstClass(), codomain) @@ -2956,12 +3001,16 @@ Sort Solver::mkFunctionSort(const std::vector& sorts, Sort codomain) const CVC4_API_ARG_AT_INDEX_CHECK_EXPECTED( !sorts[i].isNull(), "parameter sort", sorts[i], i) << "non-null sort"; + CVC4_API_ARG_AT_INDEX_CHECK_EXPECTED( + this == sorts[i].d_solver, "parameter sort", sorts[i], i) + << "sort associated to this solver object"; CVC4_API_ARG_AT_INDEX_CHECK_EXPECTED( sorts[i].isFirstClass(), "parameter sort", sorts[i], i) << "first-class sort as parameter sort for function sort"; } CVC4_API_ARG_CHECK_EXPECTED(!codomain.isNull(), codomain) << "non-null codomain sort"; + CVC4_API_SOLVER_CHECK_SORT(codomain); CVC4_API_ARG_CHECK_EXPECTED(codomain.isFirstClass(), codomain) << "first-class sort as codomain sort for function sort"; Assert(!codomain.isFunction()); /* A function sort is not first-class. */ @@ -2990,6 +3039,9 @@ Sort Solver::mkPredicateSort(const std::vector& sorts) const CVC4_API_ARG_AT_INDEX_CHECK_EXPECTED( !sorts[i].isNull(), "parameter sort", sorts[i], i) << "non-null sort"; + CVC4_API_ARG_AT_INDEX_CHECK_EXPECTED( + this == sorts[i].d_solver, "parameter sort", sorts[i], i) + << "sort associated to this solver object"; CVC4_API_ARG_AT_INDEX_CHECK_EXPECTED( sorts[i].isFirstClass(), "parameter sort", sorts[i], i) << "first-class sort as parameter sort for predicate sort"; @@ -3012,6 +3064,9 @@ Sort Solver::mkRecordSort( CVC4_API_ARG_AT_INDEX_CHECK_EXPECTED( !p.second.isNull(), "parameter sort", p.second, i) << "non-null sort"; + CVC4_API_ARG_AT_INDEX_CHECK_EXPECTED( + this == p.second.d_solver, "parameter sort", p.second, i) + << "sort associated to this solver object"; i += 1; f.emplace_back(p.first, *p.second.d_type); } @@ -3026,6 +3081,7 @@ Sort Solver::mkSetSort(Sort elemSort) const CVC4_API_SOLVER_TRY_CATCH_BEGIN; CVC4_API_ARG_CHECK_EXPECTED(!elemSort.isNull(), elemSort) << "non-null element sort"; + CVC4_API_SOLVER_CHECK_SORT(elemSort); return Sort(this, d_exprMgr->mkSetType(*elemSort.d_type)); @@ -3058,6 +3114,9 @@ Sort Solver::mkTupleSort(const std::vector& sorts) const CVC4_API_ARG_AT_INDEX_CHECK_EXPECTED( !sorts[i].isNull(), "parameter sort", sorts[i], i) << "non-null sort"; + CVC4_API_ARG_AT_INDEX_CHECK_EXPECTED( + this == sorts[i].d_solver, "parameter sort", sorts[i], i) + << "sort associated to this solver object"; CVC4_API_ARG_AT_INDEX_CHECK_EXPECTED( !sorts[i].isFunctionLike(), "parameter sort", sorts[i], i) << "non-function-like sort as parameter sort for tuple sort"; @@ -3207,6 +3266,8 @@ Term Solver::mkEmptySet(Sort s) const CVC4_API_SOLVER_TRY_CATCH_BEGIN; CVC4_API_ARG_CHECK_EXPECTED(s.isNull() || s.isSet(), s) << "null sort or set sort"; + CVC4_API_ARG_CHECK_EXPECTED(s.isNull() || this == s.d_solver, s) + << "set sort associated to this solver object"; return mkValHelper(CVC4::EmptySet(*s.d_type)); @@ -3217,6 +3278,7 @@ Term Solver::mkSepNil(Sort sort) const { CVC4_API_SOLVER_TRY_CATCH_BEGIN; CVC4_API_ARG_CHECK_EXPECTED(!sort.isNull(), sort) << "non-null sort"; + CVC4_API_SOLVER_CHECK_SORT(sort); Expr res = d_exprMgr->mkNullaryOperator(*sort.d_type, CVC4::kind::SEP_NIL); (void)res.getType(true); /* kick off type checking */ @@ -3272,6 +3334,7 @@ Term Solver::mkUniverseSet(Sort sort) const { CVC4_API_SOLVER_TRY_CATCH_BEGIN; CVC4_API_ARG_CHECK_EXPECTED(!sort.isNull(), sort) << "non-null sort"; + CVC4_API_SOLVER_CHECK_SORT(sort); Expr res = d_exprMgr->mkNullaryOperator(*sort.d_type, CVC4::kind::UNIVERSE_SET); @@ -3324,7 +3387,10 @@ Term Solver::mkBitVector(uint32_t size, std::string& s, uint32_t base) const Term Solver::mkConstArray(Sort sort, Term val) const { CVC4_API_SOLVER_TRY_CATCH_BEGIN; + CVC4_API_ARG_CHECK_NOT_NULL(sort); CVC4_API_ARG_CHECK_NOT_NULL(val); + CVC4_API_SOLVER_CHECK_SORT(sort); + CVC4_API_SOLVER_CHECK_TERM(val); CVC4_API_CHECK(sort.isArray()) << "Not an array sort."; CVC4_API_CHECK(sort.getArrayElementSort().isComparableTo(val.getSort())) << "Value does not match element sort."; @@ -3405,6 +3471,7 @@ Term Solver::mkUninterpretedConst(Sort sort, int32_t index) const { CVC4_API_SOLVER_TRY_CATCH_BEGIN; CVC4_API_ARG_CHECK_EXPECTED(!sort.isNull(), sort) << "non-null sort"; + CVC4_API_SOLVER_CHECK_SORT(sort); return mkValHelper( CVC4::UninterpretedConstant(*sort.d_type, index)); @@ -3448,6 +3515,7 @@ Term Solver::mkFloatingPoint(uint32_t exp, uint32_t sig, Term val) const CVC4_API_ARG_CHECK_EXPECTED(bw == val.getSort().getBVSize(), val) << "a bit-vector constant with bit-width '" << bw << "'"; CVC4_API_ARG_CHECK_EXPECTED(!val.isNull(), val) << "non-null term"; + CVC4_API_SOLVER_CHECK_TERM(val); CVC4_API_ARG_CHECK_EXPECTED( val.getSort().isBitVector() && val.d_expr->isConst(), val) << "bit-vector constant"; @@ -3465,6 +3533,7 @@ Term Solver::mkConst(Sort sort, const std::string& symbol) const { CVC4_API_SOLVER_TRY_CATCH_BEGIN; CVC4_API_ARG_CHECK_EXPECTED(!sort.isNull(), sort) << "non-null sort"; + CVC4_API_SOLVER_CHECK_SORT(sort); Expr res = symbol.empty() ? d_exprMgr->mkVar(*sort.d_type) : d_exprMgr->mkVar(symbol, *sort.d_type); @@ -3481,6 +3550,7 @@ Term Solver::mkVar(Sort sort, const std::string& symbol) const { CVC4_API_SOLVER_TRY_CATCH_BEGIN; CVC4_API_ARG_CHECK_EXPECTED(!sort.isNull(), sort) << "non-null sort"; + CVC4_API_SOLVER_CHECK_SORT(sort); Expr res = symbol.empty() ? d_exprMgr->mkBoundVar(*sort.d_type) : d_exprMgr->mkBoundVar(symbol, *sort.d_type); @@ -3490,6 +3560,15 @@ Term Solver::mkVar(Sort sort, const std::string& symbol) const CVC4_API_SOLVER_TRY_CATCH_END; } +/* Create datatype constructor declarations */ +/* -------------------------------------------------------------------------- */ + +DatatypeConstructorDecl Solver::mkDatatypeConstructorDecl( + const std::string& name) +{ + return DatatypeConstructorDecl(this, name); +} + /* Create datatype declarations */ /* -------------------------------------------------------------------------- */ @@ -3526,6 +3605,7 @@ Term Solver::mkTerm(Kind kind, Term child) const { CVC4_API_SOLVER_TRY_CATCH_BEGIN; CVC4_API_ARG_CHECK_EXPECTED(!child.isNull(), child) << "non-null term"; + CVC4_API_SOLVER_CHECK_TERM(child); checkMkTerm(kind, 1); Expr res = d_exprMgr->mkExpr(extToIntKind(kind), *child.d_expr); @@ -3540,6 +3620,8 @@ Term Solver::mkTerm(Kind kind, Term child1, Term child2) const CVC4_API_SOLVER_TRY_CATCH_BEGIN; CVC4_API_ARG_CHECK_EXPECTED(!child1.isNull(), child1) << "non-null term"; CVC4_API_ARG_CHECK_EXPECTED(!child2.isNull(), child2) << "non-null term"; + CVC4_API_SOLVER_CHECK_TERM(child1); + CVC4_API_SOLVER_CHECK_TERM(child2); checkMkTerm(kind, 2); Expr res = @@ -3564,6 +3646,7 @@ Term Solver::mkTerm(Kind kind, const std::vector& children) const Term Solver::mkTerm(Op op) const { CVC4_API_SOLVER_TRY_CATCH_BEGIN; + CVC4_API_SOLVER_CHECK_OP(op); Term res; if (op.isIndexedHelper()) @@ -3585,7 +3668,9 @@ Term Solver::mkTerm(Op op) const Term Solver::mkTerm(Op op, Term child) const { CVC4_API_SOLVER_TRY_CATCH_BEGIN; + CVC4_API_SOLVER_CHECK_OP(op); CVC4_API_ARG_CHECK_EXPECTED(!child.isNull(), child) << "non-null term"; + CVC4_API_SOLVER_CHECK_TERM(child); const CVC4::Kind int_kind = extToIntKind(op.d_kind); Expr res; @@ -3607,8 +3692,11 @@ Term Solver::mkTerm(Op op, Term child) const Term Solver::mkTerm(Op op, Term child1, Term child2) const { CVC4_API_SOLVER_TRY_CATCH_BEGIN; + CVC4_API_SOLVER_CHECK_OP(op); CVC4_API_ARG_CHECK_EXPECTED(!child1.isNull(), child1) << "non-null term"; CVC4_API_ARG_CHECK_EXPECTED(!child2.isNull(), child2) << "non-null term"; + CVC4_API_SOLVER_CHECK_TERM(child1); + CVC4_API_SOLVER_CHECK_TERM(child2); const CVC4::Kind int_kind = extToIntKind(op.d_kind); Expr res; @@ -3630,9 +3718,13 @@ Term Solver::mkTerm(Op op, Term child1, Term child2) const Term Solver::mkTerm(Op op, Term child1, Term child2, Term child3) const { CVC4_API_SOLVER_TRY_CATCH_BEGIN; + CVC4_API_SOLVER_CHECK_OP(op); CVC4_API_ARG_CHECK_EXPECTED(!child1.isNull(), child1) << "non-null term"; CVC4_API_ARG_CHECK_EXPECTED(!child2.isNull(), child2) << "non-null term"; CVC4_API_ARG_CHECK_EXPECTED(!child3.isNull(), child3) << "non-null term"; + CVC4_API_SOLVER_CHECK_TERM(child1); + CVC4_API_SOLVER_CHECK_TERM(child2); + CVC4_API_SOLVER_CHECK_TERM(child3); const CVC4::Kind int_kind = extToIntKind(op.d_kind); Expr res; @@ -3656,11 +3748,15 @@ Term Solver::mkTerm(Op op, Term child1, Term child2, Term child3) const Term Solver::mkTerm(Op op, const std::vector& children) const { CVC4_API_SOLVER_TRY_CATCH_BEGIN; + CVC4_API_SOLVER_CHECK_OP(op); for (size_t i = 0, size = children.size(); i < size; ++i) { CVC4_API_ARG_AT_INDEX_CHECK_EXPECTED( - !children[i].isNull(), "parameter term", children[i], i) + !children[i].isNull(), "child term", children[i], i) << "non-null term"; + CVC4_API_ARG_AT_INDEX_CHECK_EXPECTED( + this == children[i].d_solver, "child term", children[i], i) + << "child term associated to this solver object"; } const CVC4::Kind int_kind = extToIntKind(op.d_kind); @@ -3690,6 +3786,12 @@ Term Solver::mkTuple(const std::vector& sorts, std::vector args; for (size_t i = 0, size = sorts.size(); i < size; i++) { + CVC4_API_ARG_AT_INDEX_CHECK_EXPECTED( + this == terms[i].d_solver, "child term", terms[i], i) + << "child term associated to this solver object"; + CVC4_API_ARG_AT_INDEX_CHECK_EXPECTED( + this == sorts[i].d_solver, "child sort", sorts[i], i) + << "child sort associated to this solver object"; args.push_back(*(ensureTermSort(terms[i], sorts[i])).d_expr); } @@ -3913,12 +4015,13 @@ Op Solver::mkOp(Kind kind, uint32_t arg1, uint32_t arg2) const /* Non-SMT-LIB commands */ /* -------------------------------------------------------------------------- */ -Term Solver::simplify(const Term& t) +Term Solver::simplify(const Term& term) { CVC4_API_SOLVER_TRY_CATCH_BEGIN; - CVC4_API_ARG_CHECK_NOT_NULL(t); + CVC4_API_ARG_CHECK_NOT_NULL(term); + CVC4_API_SOLVER_CHECK_TERM(term); - return Term(this, d_smtEngine->simplify(*t.d_expr)); + return Term(this, d_smtEngine->simplify(*term.d_expr)); CVC4_API_SOLVER_TRY_CATCH_END; } @@ -3932,6 +4035,7 @@ Result Solver::checkEntailed(Term term) const << "Cannot make multiple queries unless incremental solving is enabled " "(try --incremental)"; CVC4_API_ARG_CHECK_NOT_NULL(term); + CVC4_API_SOLVER_CHECK_TERM(term); CVC4::Result r = d_smtEngine->checkEntailed(*term.d_expr); return Result(r); @@ -3949,6 +4053,7 @@ Result Solver::checkEntailed(const std::vector& terms) const "(try --incremental)"; for (const Term& term : terms) { + CVC4_API_SOLVER_CHECK_TERM(term); CVC4_API_ARG_CHECK_NOT_NULL(term); } @@ -3967,10 +4072,11 @@ Result Solver::checkEntailed(const std::vector& terms) const */ void Solver::assertFormula(Term term) const { - // CHECK: - // NodeManager::fromExprManager(d_exprMgr) - // == NodeManager::fromExprManager(expr.getExprManager()) + CVC4_API_SOLVER_TRY_CATCH_BEGIN; + CVC4_API_SOLVER_CHECK_TERM(term); + CVC4_API_ARG_CHECK_NOT_NULL(term); d_smtEngine->assertFormula(*term.d_expr); + CVC4_API_SOLVER_TRY_CATCH_END; } /** @@ -3978,10 +4084,15 @@ void Solver::assertFormula(Term term) const */ Result Solver::checkSat(void) const { - // CHECK: - // if d_queryMade -> incremental enabled + CVC4_API_SOLVER_TRY_CATCH_BEGIN; + CVC4::ExprManagerScope exmgrs(*(d_exprMgr.get())); + CVC4_API_CHECK(!d_smtEngine->isQueryMade() + || CVC4::options::incrementalSolving()) + << "Cannot make multiple queries unless incremental solving is enabled " + "(try --incremental)"; CVC4::Result r = d_smtEngine->checkSat(); return Result(r); + CVC4_API_SOLVER_TRY_CATCH_END; } /** @@ -3989,10 +4100,16 @@ Result Solver::checkSat(void) const */ Result Solver::checkSatAssuming(Term assumption) const { - // CHECK: - // if assumptions.size() > 0: incremental enabled? + CVC4_API_SOLVER_TRY_CATCH_BEGIN; + CVC4::ExprManagerScope exmgrs(*(d_exprMgr.get())); + CVC4_API_CHECK(!d_smtEngine->isQueryMade() + || CVC4::options::incrementalSolving()) + << "Cannot make multiple queries unless incremental solving is enabled " + "(try --incremental)"; + CVC4_API_SOLVER_CHECK_TERM(assumption); CVC4::Result r = d_smtEngine->checkSat(*assumption.d_expr); return Result(r); + CVC4_API_SOLVER_TRY_CATCH_END; } /** @@ -4000,11 +4117,21 @@ Result Solver::checkSatAssuming(Term assumption) const */ Result Solver::checkSatAssuming(const std::vector& assumptions) const { - // CHECK: - // if assumptions.size() > 0: incremental enabled? + CVC4_API_SOLVER_TRY_CATCH_BEGIN; + CVC4::ExprManagerScope exmgrs(*(d_exprMgr.get())); + CVC4_API_CHECK(!d_smtEngine->isQueryMade() || assumptions.size() == 0 + || CVC4::options::incrementalSolving()) + << "Cannot make multiple queries unless incremental solving is enabled " + "(try --incremental)"; + for (const Term& term : assumptions) + { + CVC4_API_SOLVER_CHECK_TERM(term); + CVC4_API_ARG_CHECK_NOT_NULL(term); + } std::vector eassumptions = termVectorToExprs(assumptions); CVC4::Result r = d_smtEngine->checkSat(eassumptions); return Result(r); + CVC4_API_SOLVER_TRY_CATCH_END; } /** @@ -4014,14 +4141,21 @@ Sort Solver::declareDatatype( const std::string& symbol, const std::vector& ctors) const { + CVC4_API_SOLVER_TRY_CATCH_BEGIN; CVC4_API_ARG_CHECK_EXPECTED(ctors.size() > 0, ctors) << "a datatype declaration with at least one constructor"; DatatypeDecl dtdecl(this, symbol); - for (const DatatypeConstructorDecl& ctor : ctors) + for (size_t i = 0, size = ctors.size(); i < size; i++) { - dtdecl.addConstructor(ctor); + CVC4_API_ARG_AT_INDEX_CHECK_EXPECTED(this == ctors[i].d_solver, + "datatype constructor declaration", + ctors[i], + i) + << "datatype constructor declaration associated to this solver object"; + dtdecl.addConstructor(ctors[i]); } return Sort(this, d_exprMgr->mkDatatypeType(*dtdecl.d_dtype)); + CVC4_API_SOLVER_TRY_CATCH_END; } /** @@ -4031,14 +4165,19 @@ Term Solver::declareFun(const std::string& symbol, const std::vector& sorts, Sort sort) const { + CVC4_API_SOLVER_TRY_CATCH_BEGIN; for (size_t i = 0, size = sorts.size(); i < size; ++i) { + CVC4_API_ARG_AT_INDEX_CHECK_EXPECTED( + this == sorts[i].d_solver, "parameter sort", sorts[i], i) + << "parameter sort associated to this solver object"; CVC4_API_ARG_AT_INDEX_CHECK_EXPECTED( sorts[i].isFirstClass(), "parameter sort", sorts[i], i) << "first-class sort as parameter sort for function sort"; } CVC4_API_ARG_CHECK_EXPECTED(sort.isFirstClass(), sort) << "first-class sort as function codomain sort"; + CVC4_API_SOLVER_CHECK_SORT(sort); Assert(!sort.isFunction()); /* A function sort is not first-class. */ Type type = *sort.d_type; if (!sorts.empty()) @@ -4047,6 +4186,7 @@ Term Solver::declareFun(const std::string& symbol, type = d_exprMgr->mkFunctionType(types, type); } return Term(this, d_exprMgr->mkVar(symbol, type)); + CVC4_API_SOLVER_TRY_CATCH_END; } /** diff --git a/src/api/cvc4cpp.h b/src/api/cvc4cpp.h index 87be7b74c..855ba4400 100644 --- a/src/api/cvc4cpp.h +++ b/src/api/cvc4cpp.h @@ -1167,14 +1167,13 @@ class DatatypeIterator; class CVC4_PUBLIC DatatypeConstructorDecl { friend class DatatypeDecl; + friend class Solver; public: /** - * Constructor. - * @param name the name of the datatype constructor - * @return the DatatypeConstructorDecl + * Nullary constructor for Cython. */ - DatatypeConstructorDecl(const std::string& name); + DatatypeConstructorDecl(); /** * Add datatype selector declaration. @@ -1198,6 +1197,19 @@ class CVC4_PUBLIC DatatypeConstructorDecl const CVC4::DatatypeConstructor& getDatatypeConstructor(void) const; private: + /** + * Constructor. + * @param slv the associated solver object + * @param name the name of the datatype constructor + * @return the DatatypeConstructorDecl + */ + DatatypeConstructorDecl(const Solver* slv, const std::string& name); + + /** + * The associated solver object. + */ + const Solver* d_solver; + /** * The internal (intermediate) datatype constructor wrapped by this * datatype constructor declaration. @@ -1219,7 +1231,7 @@ class CVC4_PUBLIC DatatypeDecl public: /** - * Nullary constructor for Cython + * Nullary constructor for Cython. */ DatatypeDecl(); @@ -1240,6 +1252,9 @@ class CVC4_PUBLIC DatatypeDecl /** Is this Datatype declaration parametric? */ bool isParametric() const; + /** + * @return true if this DatatypeDecl is a null object + */ bool isNull() const; /** @@ -1257,7 +1272,7 @@ class CVC4_PUBLIC DatatypeDecl private: /** * Constructor. - * @param slv the solver that created this datatype declaration + * @param slv the associated solver object * @param name the name of the datatype * @param isCoDatatype true if a codatatype is to be constructed * @return the DatatypeDecl @@ -1269,7 +1284,7 @@ class CVC4_PUBLIC DatatypeDecl /** * Constructor for parameterized datatype declaration. * Create sorts parameter with Solver::mkParamSort(). - * @param slv the solver that created this datatype declaration + * @param slv the associated solver object * @param name the name of the datatype * @param param the sort parameter * @param isCoDatatype true if a codatatype is to be constructed @@ -1282,7 +1297,7 @@ class CVC4_PUBLIC DatatypeDecl /** * Constructor for parameterized datatype declaration. * Create sorts parameter with Solver::mkParamSort(). - * @param slv the solver that created this datatype declaration + * @param slv the associated solver object * @param name the name of the datatype * @param params a list of sort parameters * @param isCoDatatype true if a codatatype is to be constructed @@ -1292,9 +1307,17 @@ class CVC4_PUBLIC DatatypeDecl const std::vector& params, bool isCoDatatype = false); - // helper for isNull() to avoid calling API functions from other API functions + /** + * Helper for isNull checks. This prevents calling an API function with + * CVC4_API_CHECK_NOT_NULL + */ bool isNullHelper() const; + /** + * The associated solver object. + */ + const Solver* d_solver; + /* The internal (intermediate) datatype wrapped by this datatype * declaration * This is a shared_ptr rather than a unique_ptr since CVC4::Datatype is @@ -2679,6 +2702,12 @@ class CVC4_PUBLIC Solver */ Term mkVar(Sort sort, const std::string& symbol = std::string()) const; + /* .................................................................... */ + /* Create datatype constructor declarations */ + /* .................................................................... */ + + DatatypeConstructorDecl mkDatatypeConstructorDecl(const std::string& name); + /* .................................................................... */ /* Create datatype declarations */ /* .................................................................... */ diff --git a/src/api/python/cvc4.pxd b/src/api/python/cvc4.pxd index 1c7071958..2ca4b3645 100644 --- a/src/api/python/cvc4.pxd +++ b/src/api/python/cvc4.pxd @@ -54,7 +54,6 @@ cdef extern from "api/cvc4cpp.h" namespace "CVC4::api": cdef cppclass DatatypeConstructorDecl: - DatatypeConstructorDecl(const string& name) except + void addSelector(const string& name, Sort sort) except + void addSelectorSelf(const string& name) except + string toString() except + @@ -160,6 +159,7 @@ cdef extern from "api/cvc4cpp.h" namespace "CVC4::api": # default value for symbol defined in cvc4cpp.h Term mkConst(Sort sort) except + Term mkVar(Sort sort, const string& symbol) except + + DatatypeConstructorDecl mkDatatypeConstructorDecl(const string& name) except + DatatypeDecl mkDatatypeDecl(const string& name) except + DatatypeDecl mkDatatypeDecl(const string& name, bint isCoDatatype) except + DatatypeDecl mkDatatypeDecl(const string& name, Sort param) except + diff --git a/src/api/python/cvc4.pxi b/src/api/python/cvc4.pxi index fa5313f0e..e742e0061 100644 --- a/src/api/python/cvc4.pxi +++ b/src/api/python/cvc4.pxi @@ -134,12 +134,14 @@ cdef class DatatypeConstructor: cdef class DatatypeConstructorDecl: - cdef c_DatatypeConstructorDecl* cddc - def __cinit__(self, str name): - self.cddc = new c_DatatypeConstructorDecl(name.encode()) + cdef c_DatatypeConstructorDecl cddc + + def __cinit__(self): + pass def addSelector(self, str name, Sort sort): self.cddc.addSelector(name.encode(), sort.csort) + def addSelectorSelf(self, str name): self.cddc.addSelectorSelf(name.encode()) @@ -156,7 +158,7 @@ cdef class DatatypeDecl: pass def addConstructor(self, DatatypeConstructorDecl ctor): - self.cdd.addConstructor(ctor.cddc[0]) + self.cdd.addConstructor(ctor.cddc) def isParametric(self): return self.cdd.isParametric() @@ -675,6 +677,11 @@ cdef class Solver: ( symbol).encode()) return term + def mkDatatypeConstructorDecl(self, str name): + cdef DatatypeConstructorDecl ddc = DatatypeConstructorDecl() + ddc.cddc = self.csolver.mkDatatypeConstructorDecl(name.encode()) + return ddc + def mkDatatypeDecl(self, str name, sorts_or_bool=None, isCoDatatype=None): cdef DatatypeDecl dd = DatatypeDecl() cdef vector[c_Sort] v @@ -790,7 +797,7 @@ cdef class Solver: cdef vector[c_DatatypeConstructorDecl] v for c in ctors: - v.push_back(( c).cddc[0]) + v.push_back(( c).cddc) sort.csort = self.csolver.declareDatatype(symbol.encode(), v) return sort diff --git a/src/parser/cvc/Cvc.g b/src/parser/cvc/Cvc.g index fdb6a081c..e604c7769 100644 --- a/src/parser/cvc/Cvc.g +++ b/src/parser/cvc/Cvc.g @@ -1159,7 +1159,7 @@ declareVariables[std::unique_ptr* cmd, CVC4::api::Sort& t, PARSER_STATE->checkDeclaration(*i, CHECK_UNDECLARED, SYM_VARIABLE); api::Term func = PARSER_STATE->mkVar( *i, - api::Sort(PARSER_STATE->getSolver(), t.getType()), + api::Sort(SOLVER, t.getType()), ExprManager::VAR_FLAG_GLOBAL | ExprManager::VAR_FLAG_DEFINED); PARSER_STATE->defineVar(*i, f); Command* decl = @@ -1654,9 +1654,7 @@ tupleStore[CVC4::api::Term& f] } const Datatype & dt = ((DatatypeType)t.getType()).getDatatype(); f2 = SOLVER->mkTerm( - api::APPLY_SELECTOR, - api::Term(PARSER_STATE->getSolver(), dt[0][k].getSelector()), - f); + api::APPLY_SELECTOR, api::Term(SOLVER, dt[0][k].getSelector()), f); } ( ( arrayStore[f2] | DOT ( tupleStore[f2] @@ -1689,9 +1687,7 @@ recordStore[CVC4::api::Term& f] } const Datatype & dt = ((DatatypeType)t.getType()).getDatatype(); f2 = SOLVER->mkTerm( - api::APPLY_SELECTOR, - api::Term(PARSER_STATE->getSolver(), dt[0][id].getSelector()), - f); + api::APPLY_SELECTOR, api::Term(SOLVER, dt[0][id].getSelector()), f); } ( ( arrayStore[f2] | DOT ( tupleStore[f2] @@ -1835,10 +1831,9 @@ postfixTerm[CVC4::api::Term& f] PARSER_STATE->parseError(std::string("no such field `") + id + "' in record"); } const Datatype & dt = ((DatatypeType)type.getType()).getDatatype(); - f = SOLVER->mkTerm( - api::APPLY_SELECTOR, - api::Term(PARSER_STATE->getSolver(), dt[0][id].getSelector()), - f); + f = SOLVER->mkTerm(api::APPLY_SELECTOR, + api::Term(SOLVER, dt[0][id].getSelector()), + f); } | k=numeral { @@ -1853,10 +1848,9 @@ postfixTerm[CVC4::api::Term& f] PARSER_STATE->parseError(ss.str()); } const Datatype & dt = ((DatatypeType)type.getType()).getDatatype(); - f = SOLVER->mkTerm( - api::APPLY_SELECTOR, - api::Term(PARSER_STATE->getSolver(), dt[0][k].getSelector()), - f); + f = SOLVER->mkTerm(api::APPLY_SELECTOR, + api::Term(SOLVER, dt[0][k].getSelector()), + f); } ) )* @@ -1896,8 +1890,7 @@ relationTerm[CVC4::api::Term& f] types.push_back(f.getSort()); api::Sort t = SOLVER->mkTupleSort(types); const Datatype& dt = Datatype(((DatatypeType)t.getType()).getDatatype()); - args.insert(args.begin(), - api::Term(PARSER_STATE->getSolver(), dt[0].getConstructor())); + args.insert(args.begin(), api::Term(SOLVER, dt[0].getConstructor())); f = MK_TERM(api::APPLY_CONSTRUCTOR, args); } | IDEN_TOK LPAREN formula[f] RPAREN @@ -2147,9 +2140,7 @@ simpleTerm[CVC4::api::Term& f] } api::Sort dtype = SOLVER->mkTupleSort(types); const Datatype& dt = ((DatatypeType)dtype.getType()).getDatatype(); - args.insert( - args.begin(), - api::Term(PARSER_STATE->getSolver(), dt[0].getConstructor())); + args.insert(args.begin(), api::Term(SOLVER, dt[0].getConstructor())); f = MK_TERM(api::APPLY_CONSTRUCTOR, args); } } @@ -2160,7 +2151,7 @@ simpleTerm[CVC4::api::Term& f] api::Sort dtype = SOLVER->mkTupleSort(types); const Datatype& dt = ((DatatypeType)dtype.getType()).getDatatype(); f = MK_TERM(api::APPLY_CONSTRUCTOR, - api::Term(PARSER_STATE->getSolver(), dt[0].getConstructor())); + api::Term(SOLVER, dt[0].getConstructor())); } /* empty record literal */ @@ -2170,7 +2161,7 @@ simpleTerm[CVC4::api::Term& f] std::vector>()); const Datatype& dt = ((DatatypeType)dtype.getType()).getDatatype(); f = MK_TERM(api::APPLY_CONSTRUCTOR, - api::Term(PARSER_STATE->getSolver(), dt[0].getConstructor())); + api::Term(SOLVER, dt[0].getConstructor())); } /* empty set literal */ | LBRACE RBRACE @@ -2268,8 +2259,7 @@ simpleTerm[CVC4::api::Term& f] } api::Sort dtype = SOLVER->mkRecordSort(typeIds); const Datatype& dt = ((DatatypeType)dtype.getType()).getDatatype(); - args.insert(args.begin(), - api::Term(PARSER_STATE->getSolver(), dt[0].getConstructor())); + args.insert(args.begin(), api::Term(SOLVER, dt[0].getConstructor())); f = MK_TERM(api::APPLY_CONSTRUCTOR, args); } @@ -2377,8 +2367,9 @@ constructorDef[CVC4::api::DatatypeDecl& type] std::unique_ptr ctor; } : identifier[id,CHECK_UNDECLARED,SYM_SORT] - { - ctor.reset(new CVC4::api::DatatypeConstructorDecl(id)); + { + ctor.reset(new CVC4::api::DatatypeConstructorDecl( + SOLVER->mkDatatypeConstructorDecl(id))); } ( LPAREN selector[&ctor] diff --git a/src/parser/smt2/Smt2.g b/src/parser/smt2/Smt2.g index f29e03380..62bf7e974 100644 --- a/src/parser/smt2/Smt2.g +++ b/src/parser/smt2/Smt2.g @@ -800,7 +800,7 @@ sygusGrammarV1[CVC4::api::Sort & ret, PARSER_STATE->getUnresolvedSorts().clear(); - ret = api::Sort(PARSER_STATE->getSolver(), datatypeTypes[0]); + ret = api::Sort(SOLVER, datatypeTypes[0]); }; // SyGuS grammar term. @@ -1798,7 +1798,7 @@ termNonVariable[CVC4::api::Term& expr, CVC4::api::Term& expr2] if (Datatype::datatypeOf(ef).isParametric()) { type = api::Sort( - PARSER_STATE->getSolver(), + SOLVER, Datatype::datatypeOf(ef)[Datatype::indexOf(ef)] .getSpecializedConstructorType(expr.getSort().getType())); } @@ -2521,7 +2521,8 @@ constructorDef[CVC4::api::DatatypeDecl& type] } : symbol[id,CHECK_NONE,SYM_VARIABLE] { - ctor = new api::DatatypeConstructorDecl(id); + ctor = new api::DatatypeConstructorDecl( + SOLVER->mkDatatypeConstructorDecl(id)); } ( LPAREN_TOK selector[*ctor] RPAREN_TOK )* { // make the constructor diff --git a/src/parser/tptp/Tptp.g b/src/parser/tptp/Tptp.g index c1d60ca31..e26709595 100644 --- a/src/parser/tptp/Tptp.g +++ b/src/parser/tptp/Tptp.g @@ -1441,7 +1441,7 @@ tffLetTermBinding[std::vector & bvlist, PARSER_STATE->checkLetBinding(bvlist, lhs, rhs, false); std::vector lchildren(++lhs.begin(), lhs.end()); rhs = MK_TERM(api::LAMBDA, MK_TERM(api::BOUND_VAR_LIST, lchildren), rhs); - lhs = api::Term(PARSER_STATE->getSolver(), lhs.getExpr().getOperator()); + lhs = api::Term(SOLVER, lhs.getExpr().getOperator()); } | LPAREN_TOK tffLetTermBinding[bvlist, lhs, rhs] RPAREN_TOK ; @@ -1463,7 +1463,7 @@ tffLetFormulaBinding[std::vector & bvlist, PARSER_STATE->checkLetBinding(bvlist, lhs, rhs, true); std::vector lchildren(++lhs.begin(), lhs.end()); rhs = MK_TERM(api::LAMBDA, MK_TERM(api::BOUND_VAR_LIST, lchildren), rhs); - lhs = api::Term(PARSER_STATE->getSolver(), lhs.getExpr().getOperator()); + lhs = api::Term(SOLVER, lhs.getExpr().getOperator()); } | LPAREN_TOK tffLetFormulaBinding[bvlist, lhs, rhs] RPAREN_TOK ; diff --git a/test/unit/api/datatype_api_black.h b/test/unit/api/datatype_api_black.h index b404dfb13..c9eaf103e 100644 --- a/test/unit/api/datatype_api_black.h +++ b/test/unit/api/datatype_api_black.h @@ -43,10 +43,10 @@ void DatatypeBlack::tearDown() {} void DatatypeBlack::testMkDatatypeSort() { DatatypeDecl dtypeSpec = d_solver.mkDatatypeDecl("list"); - DatatypeConstructorDecl cons("cons"); + DatatypeConstructorDecl cons = d_solver.mkDatatypeConstructorDecl("cons"); cons.addSelector("head", d_solver.getIntegerSort()); dtypeSpec.addConstructor(cons); - DatatypeConstructorDecl nil("nil"); + DatatypeConstructorDecl nil = d_solver.mkDatatypeConstructorDecl("nil"); dtypeSpec.addConstructor(nil); Sort listSort = d_solver.mkDatatypeSort(dtypeSpec); Datatype d = listSort.getDatatype(); @@ -75,22 +75,22 @@ void DatatypeBlack::testMkDatatypeSorts() unresTypes.insert(unresList); DatatypeDecl tree = d_solver.mkDatatypeDecl("tree"); - DatatypeConstructorDecl node("node"); + DatatypeConstructorDecl node = d_solver.mkDatatypeConstructorDecl("node"); node.addSelector("left", unresTree); node.addSelector("right", unresTree); tree.addConstructor(node); - DatatypeConstructorDecl leaf("leaf"); + DatatypeConstructorDecl leaf = d_solver.mkDatatypeConstructorDecl("leaf"); leaf.addSelector("data", unresList); tree.addConstructor(leaf); DatatypeDecl list = d_solver.mkDatatypeDecl("list"); - DatatypeConstructorDecl cons("cons"); + DatatypeConstructorDecl cons = d_solver.mkDatatypeConstructorDecl("cons"); cons.addSelector("car", unresTree); cons.addSelector("cdr", unresTree); list.addConstructor(cons); - DatatypeConstructorDecl nil("nil"); + DatatypeConstructorDecl nil = d_solver.mkDatatypeConstructorDecl("nil"); list.addConstructor(nil); std::vector dtdecls; @@ -130,13 +130,13 @@ void DatatypeBlack::testDatatypeStructs() // create datatype sort to test DatatypeDecl dtypeSpec = d_solver.mkDatatypeDecl("list"); - DatatypeConstructorDecl cons("cons"); + DatatypeConstructorDecl cons = d_solver.mkDatatypeConstructorDecl("cons"); cons.addSelector("head", intSort); cons.addSelectorSelf("tail"); Sort nullSort; TS_ASSERT_THROWS(cons.addSelector("null", nullSort), CVC4ApiException&); dtypeSpec.addConstructor(cons); - DatatypeConstructorDecl nil("nil"); + DatatypeConstructorDecl nil = d_solver.mkDatatypeConstructorDecl("nil"); dtypeSpec.addConstructor(nil); Sort dtypeSort = d_solver.mkDatatypeSort(dtypeSpec); Datatype dt = dtypeSort.getDatatype(); @@ -152,11 +152,11 @@ void DatatypeBlack::testDatatypeStructs() // create datatype sort to test DatatypeDecl dtypeSpecEnum = d_solver.mkDatatypeDecl("enum"); - DatatypeConstructorDecl ca("A"); + DatatypeConstructorDecl ca = d_solver.mkDatatypeConstructorDecl("A"); dtypeSpecEnum.addConstructor(ca); - DatatypeConstructorDecl cb("B"); + DatatypeConstructorDecl cb = d_solver.mkDatatypeConstructorDecl("B"); dtypeSpecEnum.addConstructor(cb); - DatatypeConstructorDecl cc("C"); + DatatypeConstructorDecl cc = d_solver.mkDatatypeConstructorDecl("C"); dtypeSpecEnum.addConstructor(cc); Sort dtypeSortEnum = d_solver.mkDatatypeSort(dtypeSpecEnum); Datatype dtEnum = dtypeSortEnum.getDatatype(); @@ -165,7 +165,8 @@ void DatatypeBlack::testDatatypeStructs() // create codatatype DatatypeDecl dtypeSpecStream = d_solver.mkDatatypeDecl("stream", true); - DatatypeConstructorDecl consStream("cons"); + DatatypeConstructorDecl consStream = + d_solver.mkDatatypeConstructorDecl("cons"); consStream.addSelector("head", intSort); consStream.addSelectorSelf("tail"); dtypeSpecStream.addConstructor(consStream); @@ -204,11 +205,11 @@ void DatatypeBlack::testDatatypeNames() DatatypeDecl dtypeSpec = d_solver.mkDatatypeDecl("list"); TS_ASSERT_THROWS_NOTHING(dtypeSpec.getName()); TS_ASSERT(dtypeSpec.getName() == std::string("list")); - DatatypeConstructorDecl cons("cons"); + DatatypeConstructorDecl cons = d_solver.mkDatatypeConstructorDecl("cons"); cons.addSelector("head", intSort); cons.addSelectorSelf("tail"); dtypeSpec.addConstructor(cons); - DatatypeConstructorDecl nil("nil"); + DatatypeConstructorDecl nil = d_solver.mkDatatypeConstructorDecl("nil"); dtypeSpec.addConstructor(nil); Sort dtypeSort = d_solver.mkDatatypeSort(dtypeSpec); Datatype dt = dtypeSort.getDatatype(); diff --git a/test/unit/api/solver_black.h b/test/unit/api/solver_black.h index 90d4c10c1..7e925df54 100644 --- a/test/unit/api/solver_black.h +++ b/test/unit/api/solver_black.h @@ -39,6 +39,7 @@ class SolverBlack : public CxxTest::TestSuite void testMkBitVectorSort(); void testMkFloatingPointSort(); void testMkDatatypeSort(); + void testMkDatatypeSorts(); void testMkFunctionSort(); void testMkOp(); void testMkParamSort(); @@ -96,9 +97,15 @@ class SolverBlack : public CxxTest::TestSuite void testPop3(); void testSimplify(); + + void testAssertFormula(); void testCheckEntailed(); void testCheckEntailed1(); void testCheckEntailed2(); + void testCheckSat(); + void testCheckSatAssuming(); + void testCheckSatAssuming1(); + void testCheckSatAssuming2(); void testSetInfo(); void testSetLogic(); @@ -173,6 +180,8 @@ void SolverBlack::testMkArraySort() TS_ASSERT_THROWS_NOTHING(d_solver->mkArraySort(boolSort, intSort)); TS_ASSERT_THROWS_NOTHING(d_solver->mkArraySort(realSort, bvSort)); TS_ASSERT_THROWS_NOTHING(d_solver->mkArraySort(bvSort, fpSort)); + Solver slv; + TS_ASSERT_THROWS(slv.mkArraySort(boolSort, boolSort), CVC4ApiException&); } void SolverBlack::testMkBitVectorSort() @@ -191,17 +200,64 @@ void SolverBlack::testMkFloatingPointSort() void SolverBlack::testMkDatatypeSort() { DatatypeDecl dtypeSpec = d_solver->mkDatatypeDecl("list"); - DatatypeConstructorDecl cons("cons"); + DatatypeConstructorDecl cons = d_solver->mkDatatypeConstructorDecl("cons"); cons.addSelector("head", d_solver->getIntegerSort()); dtypeSpec.addConstructor(cons); - DatatypeConstructorDecl nil("nil"); + DatatypeConstructorDecl nil = d_solver->mkDatatypeConstructorDecl("nil"); dtypeSpec.addConstructor(nil); TS_ASSERT_THROWS_NOTHING(d_solver->mkDatatypeSort(dtypeSpec)); + + Solver slv; + TS_ASSERT_THROWS(slv.mkDatatypeSort(dtypeSpec), CVC4ApiException&); + DatatypeDecl throwsDtypeSpec = d_solver->mkDatatypeDecl("list"); TS_ASSERT_THROWS(d_solver->mkDatatypeSort(throwsDtypeSpec), CVC4ApiException&); } +void SolverBlack::testMkDatatypeSorts() +{ + Solver slv; + + DatatypeDecl dtypeSpec1 = d_solver->mkDatatypeDecl("list1"); + DatatypeConstructorDecl cons1 = d_solver->mkDatatypeConstructorDecl("cons1"); + cons1.addSelector("head1", d_solver->getIntegerSort()); + dtypeSpec1.addConstructor(cons1); + DatatypeConstructorDecl nil1 = d_solver->mkDatatypeConstructorDecl("nil1"); + dtypeSpec1.addConstructor(nil1); + DatatypeDecl dtypeSpec2 = d_solver->mkDatatypeDecl("list2"); + DatatypeConstructorDecl cons2 = d_solver->mkDatatypeConstructorDecl("cons2"); + cons2.addSelector("head2", d_solver->getIntegerSort()); + dtypeSpec2.addConstructor(cons2); + DatatypeConstructorDecl nil2 = d_solver->mkDatatypeConstructorDecl("nil2"); + dtypeSpec2.addConstructor(nil2); + std::vector decls = {dtypeSpec1, dtypeSpec2}; + TS_ASSERT_THROWS_NOTHING(d_solver->mkDatatypeSorts(decls)); + + TS_ASSERT_THROWS(slv.mkDatatypeSorts(decls), CVC4ApiException&); + + DatatypeDecl throwsDtypeSpec = d_solver->mkDatatypeDecl("list"); + std::vector throwsDecls = {throwsDtypeSpec}; + TS_ASSERT_THROWS(d_solver->mkDatatypeSorts(throwsDecls), CVC4ApiException&); + + /* with unresolved sorts */ + Sort unresList = d_solver->mkUninterpretedSort("ulist"); + std::set unresSorts = {unresList}; + DatatypeDecl ulist = d_solver->mkDatatypeDecl("ulist"); + DatatypeConstructorDecl ucons = d_solver->mkDatatypeConstructorDecl("ucons"); + ucons.addSelector("car", unresList); + ucons.addSelector("cdr", unresList); + ulist.addConstructor(ucons); + DatatypeConstructorDecl unil = d_solver->mkDatatypeConstructorDecl("unil"); + ulist.addConstructor(unil); + std::vector udecls = {ulist}; + TS_ASSERT_THROWS_NOTHING(d_solver->mkDatatypeSorts(udecls, unresSorts)); + + TS_ASSERT_THROWS(slv.mkDatatypeSorts(udecls, unresSorts), CVC4ApiException&); + + /* Note: More tests are in datatype_api_black. */ +} + void SolverBlack::testMkFunctionSort() { TS_ASSERT_THROWS_NOTHING(d_solver->mkFunctionSort( @@ -228,6 +284,23 @@ void SolverBlack::testMkFunctionSort() {d_solver->getIntegerSort(), d_solver->mkUninterpretedSort("u")}, funSort2), CVC4ApiException&); + + Solver slv; + TS_ASSERT_THROWS(slv.mkFunctionSort(d_solver->mkUninterpretedSort("u"), + d_solver->getIntegerSort()), + CVC4ApiException&); + TS_ASSERT_THROWS(slv.mkFunctionSort(slv.mkUninterpretedSort("u"), + d_solver->getIntegerSort()), + CVC4ApiException&); + std::vector sorts1 = {d_solver->getBooleanSort(), + slv.getIntegerSort(), + d_solver->getIntegerSort()}; + std::vector sorts2 = {slv.getBooleanSort(), slv.getIntegerSort()}; + TS_ASSERT_THROWS_NOTHING(slv.mkFunctionSort(sorts2, slv.getIntegerSort())); + TS_ASSERT_THROWS(slv.mkFunctionSort(sorts1, slv.getIntegerSort()), + CVC4ApiException&); + TS_ASSERT_THROWS(slv.mkFunctionSort(sorts2, d_solver->getIntegerSort()), + CVC4ApiException&); } void SolverBlack::testMkParamSort() @@ -246,6 +319,10 @@ void SolverBlack::testMkPredicateSort() TS_ASSERT_THROWS( d_solver->mkPredicateSort({d_solver->getIntegerSort(), funSort}), CVC4ApiException&); + + Solver slv; + TS_ASSERT_THROWS(slv.mkPredicateSort({d_solver->getIntegerSort()}), + CVC4ApiException&); } void SolverBlack::testMkRecordSort() @@ -259,6 +336,9 @@ void SolverBlack::testMkRecordSort() TS_ASSERT_THROWS_NOTHING(d_solver->mkRecordSort(empty)); Sort recSort = d_solver->mkRecordSort(fields); TS_ASSERT_THROWS_NOTHING(recSort.getDatatype()); + + Solver slv; + TS_ASSERT_THROWS(slv.mkRecordSort(fields), CVC4ApiException&); } void SolverBlack::testMkSetSort() @@ -266,6 +346,9 @@ void SolverBlack::testMkSetSort() TS_ASSERT_THROWS_NOTHING(d_solver->mkSetSort(d_solver->getBooleanSort())); TS_ASSERT_THROWS_NOTHING(d_solver->mkSetSort(d_solver->getIntegerSort())); TS_ASSERT_THROWS_NOTHING(d_solver->mkSetSort(d_solver->mkBitVectorSort(4))); + Solver slv; + TS_ASSERT_THROWS(slv.mkSetSort(d_solver->mkBitVectorSort(4)), + CVC4ApiException&); } void SolverBlack::testMkUninterpretedSort() @@ -288,6 +371,10 @@ void SolverBlack::testMkTupleSort() d_solver->getIntegerSort()); TS_ASSERT_THROWS(d_solver->mkTupleSort({d_solver->getIntegerSort(), funSort}), CVC4ApiException&); + + Solver slv; + TS_ASSERT_THROWS(slv.mkTupleSort({d_solver->getIntegerSort()}), + CVC4ApiException&); } void SolverBlack::testMkBitVector() @@ -332,6 +419,8 @@ void SolverBlack::testMkVar() TS_ASSERT_THROWS_NOTHING(d_solver->mkVar(funSort, "")); TS_ASSERT_THROWS(d_solver->mkVar(Sort()), CVC4ApiException&); TS_ASSERT_THROWS(d_solver->mkVar(Sort(), "a"), CVC4ApiException&); + Solver slv; + TS_ASSERT_THROWS(slv.mkVar(boolSort, "x"), CVC4ApiException&); } void SolverBlack::testMkBoolean() @@ -352,6 +441,9 @@ void SolverBlack::testMkUninterpretedConst() d_solver->mkUninterpretedConst(d_solver->getBooleanSort(), 1)); TS_ASSERT_THROWS(d_solver->mkUninterpretedConst(Sort(), 1), CVC4ApiException&); + Solver slv; + TS_ASSERT_THROWS(slv.mkUninterpretedConst(d_solver->getBooleanSort(), 1), + CVC4ApiException&); } void SolverBlack::testMkAbstractValue() @@ -393,13 +485,23 @@ void SolverBlack::testMkFloatingPoint() TS_ASSERT_THROWS(d_solver->mkFloatingPoint(3, 0, t1), CVC4ApiException&); TS_ASSERT_THROWS(d_solver->mkFloatingPoint(3, 5, t2), CVC4ApiException&); TS_ASSERT_THROWS(d_solver->mkFloatingPoint(3, 5, t2), CVC4ApiException&); + + if (CVC4::Configuration::isBuiltWithSymFPU()) + { + Solver slv; + TS_ASSERT_THROWS(slv.mkFloatingPoint(3, 5, t1), CVC4ApiException&); + } } void SolverBlack::testMkEmptySet() { + Solver slv; + Sort s = d_solver->mkSetSort(d_solver->getBooleanSort()); + TS_ASSERT_THROWS_NOTHING(d_solver->mkEmptySet(Sort())); + TS_ASSERT_THROWS_NOTHING(d_solver->mkEmptySet(s)); TS_ASSERT_THROWS(d_solver->mkEmptySet(d_solver->getBooleanSort()), CVC4ApiException&); - TS_ASSERT_THROWS_NOTHING(d_solver->mkEmptySet(Sort())); + TS_ASSERT_THROWS(slv.mkEmptySet(s), CVC4ApiException&); } void SolverBlack::testMkFalse() @@ -558,6 +660,8 @@ void SolverBlack::testMkSepNil() { TS_ASSERT_THROWS_NOTHING(d_solver->mkSepNil(d_solver->getBooleanSort())); TS_ASSERT_THROWS(d_solver->mkSepNil(Sort()), CVC4ApiException&); + Solver slv; + TS_ASSERT_THROWS(slv.mkSepNil(d_solver->getIntegerSort()), CVC4ApiException&); } void SolverBlack::testMkString() @@ -592,6 +696,7 @@ void SolverBlack::testMkTerm() std::vector v4 = {d_solver->mkReal(1), d_solver->mkReal(2)}; std::vector v5 = {d_solver->mkReal(1), Term()}; std::vector v6 = {}; + Solver slv; // mkTerm(Kind kind) const TS_ASSERT_THROWS_NOTHING(d_solver->mkTerm(PI)); @@ -603,6 +708,7 @@ void SolverBlack::testMkTerm() TS_ASSERT_THROWS_NOTHING(d_solver->mkTerm(NOT, d_solver->mkTrue())); TS_ASSERT_THROWS(d_solver->mkTerm(NOT, Term()), CVC4ApiException&); TS_ASSERT_THROWS(d_solver->mkTerm(NOT, a), CVC4ApiException&); + TS_ASSERT_THROWS(slv.mkTerm(NOT, d_solver->mkTrue()), CVC4ApiException&); // mkTerm(Kind kind, Term child1, Term child2) const TS_ASSERT_THROWS_NOTHING(d_solver->mkTerm(EQUAL, a, b)); @@ -610,6 +716,7 @@ void SolverBlack::testMkTerm() TS_ASSERT_THROWS(d_solver->mkTerm(EQUAL, a, Term()), CVC4ApiException&); TS_ASSERT_THROWS(d_solver->mkTerm(EQUAL, a, d_solver->mkTrue()), CVC4ApiException&); + TS_ASSERT_THROWS(slv.mkTerm(EQUAL, a, b), CVC4ApiException&); // mkTerm(Kind kind, Term child1, Term child2, Term child3) const TS_ASSERT_THROWS_NOTHING(d_solver->mkTerm( @@ -626,6 +733,10 @@ void SolverBlack::testMkTerm() TS_ASSERT_THROWS( d_solver->mkTerm(ITE, d_solver->mkTrue(), d_solver->mkTrue(), b), CVC4ApiException&); + TS_ASSERT_THROWS( + slv.mkTerm( + ITE, d_solver->mkTrue(), d_solver->mkTrue(), d_solver->mkTrue()), + CVC4ApiException&); // mkTerm(Kind kind, const std::vector& children) const TS_ASSERT_THROWS_NOTHING(d_solver->mkTerm(EQUAL, v1)); @@ -643,15 +754,17 @@ void SolverBlack::testMkTermFromOp() std::vector v2 = {d_solver->mkReal(1), Term()}; std::vector v3 = {}; std::vector v4 = {d_solver->mkReal(5)}; + Solver slv; + // simple operator terms Op opterm1 = d_solver->mkOp(BITVECTOR_EXTRACT, 2, 1); Op opterm2 = d_solver->mkOp(DIVISIBLE, 1); - // list datatype + // list datatype Sort sort = d_solver->mkParamSort("T"); DatatypeDecl listDecl = d_solver->mkDatatypeDecl("paramlist", sort); - DatatypeConstructorDecl cons("cons"); - DatatypeConstructorDecl nil("nil"); + DatatypeConstructorDecl cons = d_solver->mkDatatypeConstructorDecl("cons"); + DatatypeConstructorDecl nil = d_solver->mkDatatypeConstructorDecl("nil"); cons.addSelector("head", sort); cons.addSelectorSelf("tail"); listDecl.addConstructor(cons); @@ -661,6 +774,7 @@ void SolverBlack::testMkTermFromOp() listSort.instantiate(std::vector{d_solver->getIntegerSort()}); Term c = d_solver->mkConst(intListSort, "c"); Datatype list = listSort.getDatatype(); + // list datatype constructor and selector operator terms Term consTerm1 = list.getConstructorTerm("cons"); Term consTerm2 = list.getConstructor("cons").getConstructorTerm(); @@ -684,6 +798,7 @@ void SolverBlack::testMkTermFromOp() TS_ASSERT_THROWS(d_solver->mkTerm(APPLY_SELECTOR, headTerm1), CVC4ApiException&); TS_ASSERT_THROWS(d_solver->mkTerm(opterm1), CVC4ApiException&); + TS_ASSERT_THROWS(slv.mkTerm(APPLY_CONSTRUCTOR, nilTerm1), CVC4ApiException&); // mkTerm(Op op, Term child) const TS_ASSERT_THROWS_NOTHING(d_solver->mkTerm(opterm1, a)); @@ -695,24 +810,29 @@ void SolverBlack::testMkTermFromOp() TS_ASSERT_THROWS( d_solver->mkTerm(APPLY_CONSTRUCTOR, consTerm1, d_solver->mkReal(0)), CVC4ApiException&); + TS_ASSERT_THROWS(slv.mkTerm(opterm1, a), CVC4ApiException&); // mkTerm(Op op, Term child1, Term child2) const - TS_ASSERT_THROWS( - d_solver->mkTerm(opterm2, d_solver->mkReal(1), d_solver->mkReal(2)), - CVC4ApiException&); TS_ASSERT_THROWS_NOTHING( d_solver->mkTerm(APPLY_CONSTRUCTOR, consTerm1, d_solver->mkReal(0), d_solver->mkTerm(APPLY_CONSTRUCTOR, nilTerm1))); + TS_ASSERT_THROWS( + d_solver->mkTerm(opterm2, d_solver->mkReal(1), d_solver->mkReal(2)), + CVC4ApiException&); TS_ASSERT_THROWS(d_solver->mkTerm(opterm1, a, b), CVC4ApiException&); TS_ASSERT_THROWS(d_solver->mkTerm(opterm2, d_solver->mkReal(1), Term()), CVC4ApiException&); TS_ASSERT_THROWS(d_solver->mkTerm(opterm2, Term(), d_solver->mkReal(1)), CVC4ApiException&); + TS_ASSERT_THROWS(slv.mkTerm(APPLY_CONSTRUCTOR, + consTerm1, + d_solver->mkReal(0), + d_solver->mkTerm(APPLY_CONSTRUCTOR, nilTerm1)), + CVC4ApiException&); - // mkTerm(Op op, Term child1, Term child2, Term child3) - // const + // mkTerm(Op op, Term child1, Term child2, Term child3) const TS_ASSERT_THROWS(d_solver->mkTerm(opterm1, a, b, a), CVC4ApiException&); TS_ASSERT_THROWS( d_solver->mkTerm( @@ -724,6 +844,7 @@ void SolverBlack::testMkTermFromOp() TS_ASSERT_THROWS(d_solver->mkTerm(opterm2, v1), CVC4ApiException&); TS_ASSERT_THROWS(d_solver->mkTerm(opterm2, v2), CVC4ApiException&); TS_ASSERT_THROWS(d_solver->mkTerm(opterm2, v3), CVC4ApiException&); + TS_ASSERT_THROWS(slv.mkTerm(opterm2, v4), CVC4ApiException&); } void SolverBlack::testMkTrue() @@ -747,12 +868,22 @@ void SolverBlack::testMkTuple() TS_ASSERT_THROWS(d_solver->mkTuple({d_solver->getIntegerSort()}, {d_solver->mkReal("5.3")}), CVC4ApiException&); + Solver slv; + TS_ASSERT_THROWS( + slv.mkTuple({d_solver->mkBitVectorSort(3)}, {slv.mkBitVector("101", 2)}), + CVC4ApiException&); + TS_ASSERT_THROWS( + slv.mkTuple({slv.mkBitVectorSort(3)}, {d_solver->mkBitVector("101", 2)}), + CVC4ApiException&); } void SolverBlack::testMkUniverseSet() { - TS_ASSERT_THROWS(d_solver->mkUniverseSet(Sort()), CVC4ApiException&); TS_ASSERT_THROWS_NOTHING(d_solver->mkUniverseSet(d_solver->getBooleanSort())); + TS_ASSERT_THROWS(d_solver->mkUniverseSet(Sort()), CVC4ApiException&); + Solver slv; + TS_ASSERT_THROWS(slv.mkUniverseSet(d_solver->getBooleanSort()), + CVC4ApiException&); } void SolverBlack::testMkConst() @@ -768,6 +899,9 @@ void SolverBlack::testMkConst() TS_ASSERT_THROWS_NOTHING(d_solver->mkConst(funSort, "")); TS_ASSERT_THROWS(d_solver->mkConst(Sort()), CVC4ApiException&); TS_ASSERT_THROWS(d_solver->mkConst(Sort(), "a"), CVC4ApiException&); + + Solver slv; + TS_ASSERT_THROWS(slv.mkConst(boolSort), CVC4ApiException&); } void SolverBlack::testMkConstArray() @@ -778,23 +912,29 @@ void SolverBlack::testMkConstArray() Term constArr = d_solver->mkConstArray(arrSort, zero); TS_ASSERT_THROWS_NOTHING(d_solver->mkConstArray(arrSort, zero)); + TS_ASSERT_THROWS(d_solver->mkConstArray(Sort(), zero), CVC4ApiException&); TS_ASSERT_THROWS(d_solver->mkConstArray(arrSort, Term()), CVC4ApiException&); TS_ASSERT_THROWS(d_solver->mkConstArray(arrSort, d_solver->mkBitVector(1, 1)), CVC4ApiException&); TS_ASSERT_THROWS(d_solver->mkConstArray(intSort, zero), CVC4ApiException&); + Solver slv; + Term zero2 = slv.mkReal(0); + Sort arrSort2 = slv.mkArraySort(slv.getIntegerSort(), slv.getIntegerSort()); + TS_ASSERT_THROWS(slv.mkConstArray(arrSort2, zero), CVC4ApiException&); + TS_ASSERT_THROWS(slv.mkConstArray(arrSort, zero2), CVC4ApiException&); } void SolverBlack::testDeclareDatatype() { - DatatypeConstructorDecl nil("nil"); + DatatypeConstructorDecl nil = d_solver->mkDatatypeConstructorDecl("nil"); std::vector ctors1 = {nil}; TS_ASSERT_THROWS_NOTHING(d_solver->declareDatatype(std::string("a"), ctors1)); - DatatypeConstructorDecl cons("cons"); - DatatypeConstructorDecl nil2("nil"); + DatatypeConstructorDecl cons = d_solver->mkDatatypeConstructorDecl("cons"); + DatatypeConstructorDecl nil2 = d_solver->mkDatatypeConstructorDecl("nil"); std::vector ctors2 = {cons, nil2}; TS_ASSERT_THROWS_NOTHING(d_solver->declareDatatype(std::string("b"), ctors2)); - DatatypeConstructorDecl cons2("cons"); - DatatypeConstructorDecl nil3("nil"); + DatatypeConstructorDecl cons2 = d_solver->mkDatatypeConstructorDecl("cons"); + DatatypeConstructorDecl nil3 = d_solver->mkDatatypeConstructorDecl("nil"); std::vector ctors3 = {cons2, nil3}; TS_ASSERT_THROWS_NOTHING(d_solver->declareDatatype(std::string(""), ctors3)); std::vector ctors4; @@ -802,6 +942,9 @@ void SolverBlack::testDeclareDatatype() CVC4ApiException&); TS_ASSERT_THROWS(d_solver->declareDatatype(std::string(""), ctors4), CVC4ApiException&); + Solver slv; + TS_ASSERT_THROWS(slv.declareDatatype(std::string("a"), ctors1), + CVC4ApiException&); } void SolverBlack::testDeclareFun() @@ -817,6 +960,8 @@ void SolverBlack::testDeclareFun() CVC4ApiException&); TS_ASSERT_THROWS(d_solver->declareFun("f5", {bvSort, bvSort}, funSort), CVC4ApiException&); + Solver slv; + TS_ASSERT_THROWS(slv.declareFun("f1", {}, bvSort), CVC4ApiException&); } void SolverBlack::testDeclareSort() @@ -959,11 +1104,11 @@ void SolverBlack::testGetOp() // Test Datatypes -- more complicated DatatypeDecl consListSpec = d_solver->mkDatatypeDecl("list"); - DatatypeConstructorDecl cons("cons"); + DatatypeConstructorDecl cons = d_solver->mkDatatypeConstructorDecl("cons"); cons.addSelector("head", d_solver->getIntegerSort()); cons.addSelectorSelf("tail"); consListSpec.addConstructor(cons); - DatatypeConstructorDecl nil("nil"); + DatatypeConstructorDecl nil = d_solver->mkDatatypeConstructorDecl("nil"); consListSpec.addConstructor(nil); Sort consListSort = d_solver->mkDatatypeSort(consListSpec); Datatype consList = consListSort.getDatatype(); @@ -1059,11 +1204,11 @@ void SolverBlack::testSimplify() Sort funSort1 = d_solver->mkFunctionSort({bvSort, bvSort}, bvSort); Sort funSort2 = d_solver->mkFunctionSort(uSort, d_solver->getIntegerSort()); DatatypeDecl consListSpec = d_solver->mkDatatypeDecl("list"); - DatatypeConstructorDecl cons("cons"); + DatatypeConstructorDecl cons = d_solver->mkDatatypeConstructorDecl("cons"); cons.addSelector("head", d_solver->getIntegerSort()); cons.addSelectorSelf("tail"); consListSpec.addConstructor(cons); - DatatypeConstructorDecl nil("nil"); + DatatypeConstructorDecl nil = d_solver->mkDatatypeConstructorDecl("nil"); consListSpec.addConstructor(nil); Sort consListSort = d_solver->mkDatatypeSort(consListSpec); @@ -1081,6 +1226,8 @@ void SolverBlack::testSimplify() TS_ASSERT_THROWS_NOTHING(d_solver->simplify(x_eq_b)); TS_ASSERT(d_solver->mkTrue() != x_eq_b); TS_ASSERT(d_solver->mkTrue() != d_solver->simplify(x_eq_b)); + Solver slv; + TS_ASSERT_THROWS(slv.simplify(x), CVC4ApiException&); Term i1 = d_solver->mkConst(d_solver->getIntegerSort(), "i1"); TS_ASSERT_THROWS_NOTHING(d_solver->simplify(i1)); @@ -1123,12 +1270,22 @@ void SolverBlack::testSimplify() TS_ASSERT_THROWS_NOTHING(d_solver->simplify(f2)); } +void SolverBlack::testAssertFormula() +{ + TS_ASSERT_THROWS_NOTHING(d_solver->assertFormula(d_solver->mkTrue())); + TS_ASSERT_THROWS(d_solver->assertFormula(Term()), CVC4ApiException&); + Solver slv; + TS_ASSERT_THROWS(slv.assertFormula(d_solver->mkTrue()), CVC4ApiException&); +} + void SolverBlack::testCheckEntailed() { d_solver->setOption("incremental", "false"); TS_ASSERT_THROWS_NOTHING(d_solver->checkEntailed(d_solver->mkTrue())); TS_ASSERT_THROWS(d_solver->checkEntailed(d_solver->mkTrue()), CVC4ApiException&); + Solver slv; + TS_ASSERT_THROWS(slv.checkEntailed(d_solver->mkTrue()), CVC4ApiException&); } void SolverBlack::testCheckEntailed1() @@ -1142,6 +1299,8 @@ void SolverBlack::testCheckEntailed1() TS_ASSERT_THROWS(d_solver->checkEntailed(Term()), CVC4ApiException&); TS_ASSERT_THROWS_NOTHING(d_solver->checkEntailed(d_solver->mkTrue())); TS_ASSERT_THROWS_NOTHING(d_solver->checkEntailed(z)); + Solver slv; + TS_ASSERT_THROWS(slv.checkEntailed(d_solver->mkTrue()), CVC4ApiException&); } void SolverBlack::testCheckEntailed2() @@ -1191,6 +1350,91 @@ void SolverBlack::testCheckEntailed2() TS_ASSERT_THROWS( d_solver->checkEntailed({n, d_solver->mkTerm(DISTINCT, x, y)}), CVC4ApiException&); + Solver slv; + TS_ASSERT_THROWS(slv.checkEntailed(d_solver->mkTrue()), CVC4ApiException&); +} + +void SolverBlack::testCheckSat() +{ + d_solver->setOption("incremental", "false"); + TS_ASSERT_THROWS_NOTHING(d_solver->checkSat()); + TS_ASSERT_THROWS(d_solver->checkSat(), CVC4ApiException&); +} + +void SolverBlack::testCheckSatAssuming() +{ + d_solver->setOption("incremental", "false"); + TS_ASSERT_THROWS_NOTHING(d_solver->checkSatAssuming(d_solver->mkTrue())); + TS_ASSERT_THROWS(d_solver->checkSatAssuming(d_solver->mkTrue()), + CVC4ApiException&); + Solver slv; + TS_ASSERT_THROWS(slv.checkSatAssuming(d_solver->mkTrue()), CVC4ApiException&); +} + +void SolverBlack::testCheckSatAssuming1() +{ + Sort boolSort = d_solver->getBooleanSort(); + Term x = d_solver->mkConst(boolSort, "x"); + Term y = d_solver->mkConst(boolSort, "y"); + Term z = d_solver->mkTerm(AND, x, y); + d_solver->setOption("incremental", "true"); + TS_ASSERT_THROWS_NOTHING(d_solver->checkSatAssuming(d_solver->mkTrue())); + TS_ASSERT_THROWS(d_solver->checkSatAssuming(Term()), CVC4ApiException&); + TS_ASSERT_THROWS_NOTHING(d_solver->checkSatAssuming(d_solver->mkTrue())); + TS_ASSERT_THROWS_NOTHING(d_solver->checkSatAssuming(z)); + Solver slv; + TS_ASSERT_THROWS(slv.checkSatAssuming(d_solver->mkTrue()), CVC4ApiException&); +} + +void SolverBlack::testCheckSatAssuming2() +{ + d_solver->setOption("incremental", "true"); + + Sort uSort = d_solver->mkUninterpretedSort("u"); + Sort intSort = d_solver->getIntegerSort(); + Sort boolSort = d_solver->getBooleanSort(); + Sort uToIntSort = d_solver->mkFunctionSort(uSort, intSort); + Sort intPredSort = d_solver->mkFunctionSort(intSort, boolSort); + + Term n = Term(); + // Constants + Term x = d_solver->mkConst(uSort, "x"); + Term y = d_solver->mkConst(uSort, "y"); + // Functions + Term f = d_solver->mkConst(uToIntSort, "f"); + Term p = d_solver->mkConst(intPredSort, "p"); + // Values + Term zero = d_solver->mkReal(0); + Term one = d_solver->mkReal(1); + // Terms + Term f_x = d_solver->mkTerm(APPLY_UF, f, x); + Term f_y = d_solver->mkTerm(APPLY_UF, f, y); + Term sum = d_solver->mkTerm(PLUS, f_x, f_y); + Term p_0 = d_solver->mkTerm(APPLY_UF, p, zero); + Term p_f_y = d_solver->mkTerm(APPLY_UF, p, f_y); + // Assertions + Term assertions = + d_solver->mkTerm(AND, + std::vector{ + d_solver->mkTerm(LEQ, zero, f_x), // 0 <= f(x) + d_solver->mkTerm(LEQ, zero, f_y), // 0 <= f(y) + d_solver->mkTerm(LEQ, sum, one), // f(x) + f(y) <= 1 + p_0.notTerm(), // not p(0) + p_f_y // p(f(y)) + }); + + TS_ASSERT_THROWS_NOTHING(d_solver->checkSatAssuming(d_solver->mkTrue())); + d_solver->assertFormula(assertions); + TS_ASSERT_THROWS_NOTHING( + d_solver->checkSatAssuming(d_solver->mkTerm(DISTINCT, x, y))); + TS_ASSERT_THROWS_NOTHING(d_solver->checkSatAssuming( + {d_solver->mkFalse(), d_solver->mkTerm(DISTINCT, x, y)})); + TS_ASSERT_THROWS(d_solver->checkSatAssuming(n), CVC4ApiException&); + TS_ASSERT_THROWS( + d_solver->checkSatAssuming({n, d_solver->mkTerm(DISTINCT, x, y)}), + CVC4ApiException&); + Solver slv; + TS_ASSERT_THROWS(slv.checkSatAssuming(d_solver->mkTrue()), CVC4ApiException&); } void SolverBlack::testSetLogic() diff --git a/test/unit/api/sort_black.h b/test/unit/api/sort_black.h index 42d2dcb25..437b5cf26 100644 --- a/test/unit/api/sort_black.h +++ b/test/unit/api/sort_black.h @@ -63,10 +63,10 @@ void SortBlack::testGetDatatype() { // create datatype sort, check should not fail DatatypeDecl dtypeSpec = d_solver.mkDatatypeDecl("list"); - DatatypeConstructorDecl cons("cons"); + DatatypeConstructorDecl cons = d_solver.mkDatatypeConstructorDecl("cons"); cons.addSelector("head", d_solver.getIntegerSort()); dtypeSpec.addConstructor(cons); - DatatypeConstructorDecl nil("nil"); + DatatypeConstructorDecl nil = d_solver.mkDatatypeConstructorDecl("nil"); dtypeSpec.addConstructor(nil); Sort dtypeSort = d_solver.mkDatatypeSort(dtypeSpec); TS_ASSERT_THROWS_NOTHING(dtypeSort.getDatatype()); @@ -80,11 +80,11 @@ void SortBlack::testDatatypeSorts() Sort intSort = d_solver.getIntegerSort(); // create datatype sort to test DatatypeDecl dtypeSpec = d_solver.mkDatatypeDecl("list"); - DatatypeConstructorDecl cons("cons"); + DatatypeConstructorDecl cons = d_solver.mkDatatypeConstructorDecl("cons"); cons.addSelector("head", intSort); cons.addSelectorSelf("tail"); dtypeSpec.addConstructor(cons); - DatatypeConstructorDecl nil("nil"); + DatatypeConstructorDecl nil = d_solver.mkDatatypeConstructorDecl("nil"); dtypeSpec.addConstructor(nil); Sort dtypeSort = d_solver.mkDatatypeSort(dtypeSpec); Datatype dt = dtypeSort.getDatatype(); @@ -121,8 +121,9 @@ void SortBlack::testInstantiate() // instantiate parametric datatype, check should not fail Sort sort = d_solver.mkParamSort("T"); DatatypeDecl paramDtypeSpec = d_solver.mkDatatypeDecl("paramlist", sort); - DatatypeConstructorDecl paramCons("cons"); - DatatypeConstructorDecl paramNil("nil"); + DatatypeConstructorDecl paramCons = + d_solver.mkDatatypeConstructorDecl("cons"); + DatatypeConstructorDecl paramNil = d_solver.mkDatatypeConstructorDecl("nil"); paramCons.addSelector("head", sort); paramDtypeSpec.addConstructor(paramCons); paramDtypeSpec.addConstructor(paramNil); @@ -131,10 +132,10 @@ void SortBlack::testInstantiate() paramDtypeSort.instantiate(std::vector{d_solver.getIntegerSort()})); // instantiate non-parametric datatype sort, check should fail DatatypeDecl dtypeSpec = d_solver.mkDatatypeDecl("list"); - DatatypeConstructorDecl cons("cons"); + DatatypeConstructorDecl cons = d_solver.mkDatatypeConstructorDecl("cons"); cons.addSelector("head", d_solver.getIntegerSort()); dtypeSpec.addConstructor(cons); - DatatypeConstructorDecl nil("nil"); + DatatypeConstructorDecl nil = d_solver.mkDatatypeConstructorDecl("nil"); dtypeSpec.addConstructor(nil); Sort dtypeSort = d_solver.mkDatatypeSort(dtypeSpec); TS_ASSERT_THROWS( @@ -265,8 +266,9 @@ void SortBlack::testGetDatatypeParamSorts() // create parametric datatype, check should not fail Sort sort = d_solver.mkParamSort("T"); DatatypeDecl paramDtypeSpec = d_solver.mkDatatypeDecl("paramlist", sort); - DatatypeConstructorDecl paramCons("cons"); - DatatypeConstructorDecl paramNil("nil"); + DatatypeConstructorDecl paramCons = + d_solver.mkDatatypeConstructorDecl("cons"); + DatatypeConstructorDecl paramNil = d_solver.mkDatatypeConstructorDecl("nil"); paramCons.addSelector("head", sort); paramDtypeSpec.addConstructor(paramCons); paramDtypeSpec.addConstructor(paramNil); @@ -274,10 +276,10 @@ void SortBlack::testGetDatatypeParamSorts() TS_ASSERT_THROWS_NOTHING(paramDtypeSort.getDatatypeParamSorts()); // create non-parametric datatype sort, check should fail DatatypeDecl dtypeSpec = d_solver.mkDatatypeDecl("list"); - DatatypeConstructorDecl cons("cons"); + DatatypeConstructorDecl cons = d_solver.mkDatatypeConstructorDecl("cons"); cons.addSelector("head", d_solver.getIntegerSort()); dtypeSpec.addConstructor(cons); - DatatypeConstructorDecl nil("nil"); + DatatypeConstructorDecl nil = d_solver.mkDatatypeConstructorDecl("nil"); dtypeSpec.addConstructor(nil); Sort dtypeSort = d_solver.mkDatatypeSort(dtypeSpec); TS_ASSERT_THROWS(dtypeSort.getDatatypeParamSorts(), CVC4ApiException&); @@ -287,10 +289,10 @@ void SortBlack::testGetDatatypeArity() { // create datatype sort, check should not fail DatatypeDecl dtypeSpec = d_solver.mkDatatypeDecl("list"); - DatatypeConstructorDecl cons("cons"); + DatatypeConstructorDecl cons = d_solver.mkDatatypeConstructorDecl("cons"); cons.addSelector("head", d_solver.getIntegerSort()); dtypeSpec.addConstructor(cons); - DatatypeConstructorDecl nil("nil"); + DatatypeConstructorDecl nil = d_solver.mkDatatypeConstructorDecl("nil"); dtypeSpec.addConstructor(nil); Sort dtypeSort = d_solver.mkDatatypeSort(dtypeSpec); TS_ASSERT_THROWS_NOTHING(dtypeSort.getDatatypeArity()); diff --git a/test/unit/api/term_black.h b/test/unit/api/term_black.h index d8f022201..a3cbd028f 100644 --- a/test/unit/api/term_black.h +++ b/test/unit/api/term_black.h @@ -197,8 +197,8 @@ void TermBlack::testGetOp() // Test Datatypes Ops Sort sort = d_solver.mkParamSort("T"); DatatypeDecl listDecl = d_solver.mkDatatypeDecl("paramlist", sort); - DatatypeConstructorDecl cons("cons"); - DatatypeConstructorDecl nil("nil"); + DatatypeConstructorDecl cons = d_solver.mkDatatypeConstructorDecl("cons"); + DatatypeConstructorDecl nil = d_solver.mkDatatypeConstructorDecl("nil"); cons.addSelector("head", sort); cons.addSelectorSelf("tail"); listDecl.addConstructor(cons); -- cgit v1.2.3 From a3670b55e0ef3d4c8e31800aa943688065ca029c Mon Sep 17 00:00:00 2001 From: Andrew Reynolds Date: Thu, 4 Jun 2020 09:52:55 -0500 Subject: Theory strings preprocess (#4534) This makes it so that the main reduce function in TheoryStringsPreprocess is static, so that it can be used both by the solver and the proof checker. It also updates the functions to make use of IndexVar for constructing canonical universal variables. --- src/theory/strings/theory_strings_preprocess.cpp | 284 ++++++++++++----------- src/theory/strings/theory_strings_preprocess.h | 44 ++-- 2 files changed, 183 insertions(+), 145 deletions(-) (limited to 'src') diff --git a/src/theory/strings/theory_strings_preprocess.cpp b/src/theory/strings/theory_strings_preprocess.cpp index 939146a3d..50c6ede62 100644 --- a/src/theory/strings/theory_strings_preprocess.cpp +++ b/src/theory/strings/theory_strings_preprocess.cpp @@ -38,48 +38,45 @@ StringsPreprocess::StringsPreprocess(SkolemCache* sc, SequencesStatistics& stats) : d_sc(sc), d_statistics(stats) { - //Constants - d_zero = NodeManager::currentNM()->mkConst(Rational(0)); - d_one = NodeManager::currentNM()->mkConst(Rational(1)); - d_neg_one = NodeManager::currentNM()->mkConst(Rational(-1)); } StringsPreprocess::~StringsPreprocess(){ } -Node StringsPreprocess::simplify( Node t, std::vector< Node > &new_nodes ) { - unsigned prev_new_nodes = new_nodes.size(); - Trace("strings-preprocess-debug") << "StringsPreprocess::simplify: " << t << std::endl; +Node StringsPreprocess::reduce(Node t, + std::vector& asserts, + SkolemCache* sc) +{ + Trace("strings-preprocess-debug") + << "StringsPreprocess::reduce: " << t << std::endl; Node retNode = t; - NodeManager *nm = NodeManager::currentNM(); + NodeManager* nm = NodeManager::currentNM(); + Node zero = nm->mkConst(Rational(0)); + Node one = nm->mkConst(Rational(1)); + Node negOne = nm->mkConst(Rational(-1)); if( t.getKind() == kind::STRING_SUBSTR ) { // processing term: substr( s, n, m ) Node s = t[0]; Node n = t[1]; Node m = t[2]; - Node skt = d_sc->mkSkolemCached(t, SkolemCache::SK_PURIFY, "sst"); + Node skt = sc->mkSkolemCached(t, SkolemCache::SK_PURIFY, "sst"); Node t12 = nm->mkNode(PLUS, n, m); t12 = Rewriter::rewrite(t12); Node lt0 = nm->mkNode(STRING_LENGTH, s); //start point is greater than or equal zero - Node c1 = nm->mkNode(GEQ, n, d_zero); + Node c1 = nm->mkNode(GEQ, n, zero); //start point is less than end of string Node c2 = nm->mkNode(GT, lt0, n); //length is positive - Node c3 = nm->mkNode(GT, m, d_zero); + Node c3 = nm->mkNode(GT, m, zero); Node cond = nm->mkNode(AND, c1, c2, c3); Node emp = Word::mkEmptyWord(t.getType()); - Node sk1 = n == d_zero ? emp - : d_sc->mkSkolemCached( - s, n, SkolemCache::SK_PREFIX, "sspre"); - Node sk2 = ArithEntail::check(t12, lt0) - ? emp - : d_sc->mkSkolemCached( - s, t12, SkolemCache::SK_SUFFIX_REM, "sssufr"); + Node sk1 = sc->mkSkolemCached(s, n, SkolemCache::SK_PREFIX, "sspre"); + Node sk2 = sc->mkSkolemCached(s, t12, SkolemCache::SK_SUFFIX_REM, "sssufr"); Node b11 = s.eqNode(nm->mkNode(STRING_CONCAT, sk1, skt, sk2)); //length of first skolem is second argument Node b12 = nm->mkNode(STRING_LENGTH, sk1).eqNode(n); @@ -89,7 +86,7 @@ Node StringsPreprocess::simplify( Node t, std::vector< Node > &new_nodes ) { Node b13 = nm->mkNode( OR, nm->mkNode(EQUAL, lsk2, nm->mkNode(MINUS, lt0, nm->mkNode(PLUS, n, m))), - nm->mkNode(EQUAL, lsk2, d_zero)); + nm->mkNode(EQUAL, lsk2, zero)); // Length of the result is at most m Node b14 = nm->mkNode(LEQ, nm->mkNode(STRING_LENGTH, skt), m); @@ -112,7 +109,7 @@ Node StringsPreprocess::simplify( Node t, std::vector< Node > &new_nodes ) { // satisfied. If n + m is less than the length of s, then len(sk2) = 0 // cannot be satisfied because we have the constraint that len(skt) <= m, // so sk2 must be greater than 0. - new_nodes.push_back( lemma ); + asserts.push_back(lemma); // Thus, substr( s, n, m ) = skt retNode = skt; @@ -123,15 +120,16 @@ Node StringsPreprocess::simplify( Node t, std::vector< Node > &new_nodes ) { Node x = t[0]; Node y = t[1]; Node n = t[2]; - Node skk = nm->mkSkolem("iok", nm->integerType(), "created for indexof"); + Node skk = sc->mkTypedSkolemCached( + nm->integerType(), t, SkolemCache::SK_PURIFY, "iok"); Node negone = nm->mkConst(Rational(-1)); Node krange = nm->mkNode(GEQ, skk, negone); // assert: indexof( x, y, n ) >= -1 - new_nodes.push_back( krange ); + asserts.push_back(krange); krange = nm->mkNode(GEQ, nm->mkNode(STRING_LENGTH, x), skk); // assert: len( x ) >= indexof( x, y, z ) - new_nodes.push_back( krange ); + asserts.push_back(krange); // substr( x, n, len( x ) - n ) Node st = nm->mkNode(STRING_SUBSTR, @@ -139,16 +137,16 @@ Node StringsPreprocess::simplify( Node t, std::vector< Node > &new_nodes ) { n, nm->mkNode(MINUS, nm->mkNode(STRING_LENGTH, x), n)); Node io2 = - d_sc->mkSkolemCached(st, y, SkolemCache::SK_FIRST_CTN_PRE, "iopre"); + sc->mkSkolemCached(st, y, SkolemCache::SK_FIRST_CTN_PRE, "iopre"); Node io4 = - d_sc->mkSkolemCached(st, y, SkolemCache::SK_FIRST_CTN_POST, "iopost"); + sc->mkSkolemCached(st, y, SkolemCache::SK_FIRST_CTN_POST, "iopost"); // ~contains( substr( x, n, len( x ) - n ), y ) Node c11 = nm->mkNode(STRING_STRCTN, st, y).negate(); // n > len( x ) Node c12 = nm->mkNode(GT, n, nm->mkNode(STRING_LENGTH, x)); // 0 > n - Node c13 = nm->mkNode(GT, d_zero, n); + Node c13 = nm->mkNode(GT, zero, n); Node cond1 = nm->mkNode(OR, c11, c12, c13); // skk = -1 Node cc1 = skk.eqNode(negone); @@ -171,8 +169,8 @@ Node StringsPreprocess::simplify( Node t, std::vector< Node > &new_nodes ) { nm->mkNode( STRING_SUBSTR, y, - d_zero, - nm->mkNode(MINUS, nm->mkNode(STRING_LENGTH, y), d_one))), + zero, + nm->mkNode(MINUS, nm->mkNode(STRING_LENGTH, y), one))), y) .negate(); // skk = n + len( io2 ) @@ -189,7 +187,7 @@ Node StringsPreprocess::simplify( Node t, std::vector< Node > &new_nodes ) { // skk = n + len( io2 ) // for fresh io2, io4. Node rr = nm->mkNode(ITE, cond1, cc1, nm->mkNode(ITE, cond2, cc2, cc3)); - new_nodes.push_back( rr ); + asserts.push_back(rr); // Thus, indexof( x, y, n ) = skk. retNode = skk; @@ -198,7 +196,7 @@ Node StringsPreprocess::simplify( Node t, std::vector< Node > &new_nodes ) { { // processing term: int.to.str( n ) Node n = t[0]; - Node itost = d_sc->mkSkolemCached(t, SkolemCache::SK_PURIFY, "itost"); + Node itost = sc->mkSkolemCached(t, SkolemCache::SK_PURIFY, "itost"); Node leni = nm->mkNode(STRING_LENGTH, itost); std::vector conc; @@ -206,21 +204,20 @@ Node StringsPreprocess::simplify( Node t, std::vector< Node > &new_nodes ) { argTypes.push_back(nm->integerType()); Node u = nm->mkSkolem("U", nm->mkFunctionType(argTypes, nm->integerType())); - Node lem = nm->mkNode(GEQ, leni, d_one); + Node lem = nm->mkNode(GEQ, leni, one); conc.push_back(lem); lem = n.eqNode(nm->mkNode(APPLY_UF, u, leni)); conc.push_back(lem); - lem = d_zero.eqNode(nm->mkNode(APPLY_UF, u, d_zero)); + lem = zero.eqNode(nm->mkNode(APPLY_UF, u, zero)); conc.push_back(lem); - Node x = nm->mkBoundVar(nm->integerType()); - Node xPlusOne = nm->mkNode(PLUS, x, d_one); + Node x = SkolemCache::mkIndexVar(t); + Node xPlusOne = nm->mkNode(PLUS, x, one); Node xbv = nm->mkNode(BOUND_VAR_LIST, x); - Node g = - nm->mkNode(AND, nm->mkNode(GEQ, x, d_zero), nm->mkNode(LT, x, leni)); - Node sx = nm->mkNode(STRING_SUBSTR, itost, x, d_one); + Node g = nm->mkNode(AND, nm->mkNode(GEQ, x, zero), nm->mkNode(LT, x, leni)); + Node sx = nm->mkNode(STRING_SUBSTR, itost, x, one); Node ux = nm->mkNode(APPLY_UF, u, x); Node ux1 = nm->mkNode(APPLY_UF, u, xPlusOne); Node c0 = nm->mkNode(STRING_TO_CODE, nm->mkConst(String("0"))); @@ -229,10 +226,10 @@ Node StringsPreprocess::simplify( Node t, std::vector< Node > &new_nodes ) { Node ten = nm->mkConst(Rational(10)); Node eq = ux1.eqNode(nm->mkNode(PLUS, c, nm->mkNode(MULT, ten, ux))); Node leadingZeroPos = - nm->mkNode(AND, x.eqNode(d_zero), nm->mkNode(GT, leni, d_one)); + nm->mkNode(AND, x.eqNode(zero), nm->mkNode(GT, leni, one)); Node cb = nm->mkNode( AND, - nm->mkNode(GEQ, c, nm->mkNode(ITE, leadingZeroPos, d_one, d_zero)), + nm->mkNode(GEQ, c, nm->mkNode(ITE, leadingZeroPos, one, zero)), nm->mkNode(LT, c, ten)); Node ux1lem = nm->mkNode(GEQ, n, ux1); @@ -241,11 +238,11 @@ Node StringsPreprocess::simplify( Node t, std::vector< Node > &new_nodes ) { lem = nm->mkNode(FORALL, xbv, lem); conc.push_back(lem); - Node nonneg = nm->mkNode(GEQ, n, d_zero); + Node nonneg = nm->mkNode(GEQ, n, zero); Node emp = Word::mkEmptyWord(t.getType()); lem = nm->mkNode(ITE, nonneg, nm->mkNode(AND, conc), itost.eqNode(emp)); - new_nodes.push_back(lem); + asserts.push_back(lem); // assert: // IF n>=0 // THEN: @@ -274,26 +271,27 @@ Node StringsPreprocess::simplify( Node t, std::vector< Node > &new_nodes ) { retNode = itost; } else if( t.getKind() == kind::STRING_STOI ) { Node s = t[0]; - Node stoit = nm->mkSkolem("stoit", nm->integerType(), "created for stoi"); + Node stoit = sc->mkTypedSkolemCached( + nm->integerType(), t, SkolemCache::SK_PURIFY, "stoit"); Node lens = nm->mkNode(STRING_LENGTH, s); std::vector conc1; - Node lem = stoit.eqNode(d_neg_one); + Node lem = stoit.eqNode(negOne); conc1.push_back(lem); 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 kc1 = nm->mkNode(GEQ, k, zero); Node kc2 = nm->mkNode(LT, k, lens); Node c0 = nm->mkNode(STRING_TO_CODE, nm->mkConst(String("0"))); Node codeSk = nm->mkNode( MINUS, - nm->mkNode(STRING_TO_CODE, nm->mkNode(STRING_SUBSTR, s, k, d_one)), + nm->mkNode(STRING_TO_CODE, nm->mkNode(STRING_SUBSTR, s, k, one)), c0); Node ten = nm->mkConst(Rational(10)); Node kc3 = nm->mkNode( - OR, nm->mkNode(LT, codeSk, d_zero), nm->mkNode(GEQ, codeSk, ten)); + OR, nm->mkNode(LT, codeSk, zero), nm->mkNode(GEQ, codeSk, ten)); conc1.push_back(nm->mkNode(OR, sEmpty, nm->mkNode(AND, kc1, kc2, kc3))); std::vector conc2; @@ -304,24 +302,22 @@ Node StringsPreprocess::simplify( Node t, std::vector< Node > &new_nodes ) { lem = stoit.eqNode(nm->mkNode(APPLY_UF, u, lens)); conc2.push_back(lem); - lem = d_zero.eqNode(nm->mkNode(APPLY_UF, u, d_zero)); + lem = zero.eqNode(nm->mkNode(APPLY_UF, u, zero)); conc2.push_back(lem); - lem = nm->mkNode(GT, lens, d_zero); + lem = nm->mkNode(GT, lens, zero); conc2.push_back(lem); - Node x = nm->mkBoundVar(nm->integerType()); + Node x = SkolemCache::mkIndexVar(t); Node xbv = nm->mkNode(BOUND_VAR_LIST, x); - Node g = - nm->mkNode(AND, nm->mkNode(GEQ, x, d_zero), nm->mkNode(LT, x, lens)); - Node sx = nm->mkNode(STRING_SUBSTR, s, x, d_one); + Node g = nm->mkNode(AND, nm->mkNode(GEQ, x, zero), nm->mkNode(LT, x, lens)); + Node sx = nm->mkNode(STRING_SUBSTR, s, x, one); Node ux = nm->mkNode(APPLY_UF, u, x); - Node ux1 = nm->mkNode(APPLY_UF, u, nm->mkNode(PLUS, x, d_one)); + Node ux1 = nm->mkNode(APPLY_UF, u, nm->mkNode(PLUS, x, one)); Node c = nm->mkNode(MINUS, nm->mkNode(STRING_TO_CODE, sx), c0); Node eq = ux1.eqNode(nm->mkNode(PLUS, c, nm->mkNode(MULT, ten, ux))); - Node cb = - nm->mkNode(AND, nm->mkNode(GEQ, c, d_zero), nm->mkNode(LT, c, ten)); + Node cb = nm->mkNode(AND, nm->mkNode(GEQ, c, zero), nm->mkNode(LT, c, ten)); Node ux1lem = nm->mkNode(GEQ, stoit, ux1); @@ -329,9 +325,9 @@ Node StringsPreprocess::simplify( Node t, std::vector< Node > &new_nodes ) { lem = nm->mkNode(FORALL, xbv, lem); conc2.push_back(lem); - Node sneg = nm->mkNode(LT, stoit, d_zero); + Node sneg = nm->mkNode(LT, stoit, zero); lem = nm->mkNode(ITE, sneg, nm->mkNode(AND, conc1), nm->mkNode(AND, conc2)); - new_nodes.push_back(lem); + asserts.push_back(lem); // assert: // IF stoit < 0 @@ -362,10 +358,10 @@ Node StringsPreprocess::simplify( Node t, std::vector< Node > &new_nodes ) { Node z = t[2]; TypeNode tn = t[0].getType(); Node rp1 = - d_sc->mkSkolemCached(x, y, SkolemCache::SK_FIRST_CTN_PRE, "rfcpre"); + sc->mkSkolemCached(x, y, SkolemCache::SK_FIRST_CTN_PRE, "rfcpre"); Node rp2 = - d_sc->mkSkolemCached(x, y, SkolemCache::SK_FIRST_CTN_POST, "rfcpost"); - Node rpw = d_sc->mkSkolemCached(t, SkolemCache::SK_PURIFY, "rpw"); + sc->mkSkolemCached(x, y, SkolemCache::SK_FIRST_CTN_POST, "rfcpost"); + Node rpw = sc->mkSkolemCached(t, SkolemCache::SK_PURIFY, "rpw"); // y = "" Node emp = Word::mkEmptyWord(tn); @@ -387,10 +383,10 @@ Node StringsPreprocess::simplify( Node t, std::vector< Node > &new_nodes ) { rp1, nm->mkNode(kind::STRING_SUBSTR, y, - d_zero, + zero, nm->mkNode(kind::MINUS, nm->mkNode(kind::STRING_LENGTH, y), - d_one))), + one))), y) .negate(); @@ -410,7 +406,7 @@ Node StringsPreprocess::simplify( Node t, std::vector< Node > &new_nodes ) { cond2, nm->mkNode(kind::AND, c21, c22, c23), rpw.eqNode(x))); - new_nodes.push_back( rr ); + asserts.push_back(rr); // Thus, replace( x, y, z ) = rpw. retNode = rpw; @@ -421,16 +417,16 @@ Node StringsPreprocess::simplify( Node t, std::vector< Node > &new_nodes ) { Node x = t[0]; Node y = t[1]; Node z = t[2]; - Node rpaw = d_sc->mkSkolemCached(t, SkolemCache::SK_PURIFY, "rpaw"); + Node rpaw = sc->mkSkolemCached(t, SkolemCache::SK_PURIFY, "rpaw"); - Node numOcc = d_sc->mkTypedSkolemCached( + Node numOcc = sc->mkTypedSkolemCached( nm->integerType(), x, y, SkolemCache::SK_NUM_OCCUR, "numOcc"); std::vector argTypes; argTypes.push_back(nm->integerType()); Node us = nm->mkSkolem("Us", nm->mkFunctionType(argTypes, nm->stringType())); TypeNode ufType = nm->mkFunctionType(argTypes, nm->integerType()); - Node uf = d_sc->mkTypedSkolemCached( + Node uf = sc->mkTypedSkolemCached( ufType, x, y, SkolemCache::SK_OCCUR_INDEX, "Uf"); Node ufno = nm->mkNode(APPLY_UF, uf, numOcc); @@ -438,27 +434,27 @@ Node StringsPreprocess::simplify( Node t, std::vector< Node > &new_nodes ) { Node rem = nm->mkNode(STRING_SUBSTR, x, ufno, nm->mkNode(STRING_LENGTH, x)); std::vector lem; - lem.push_back(nm->mkNode(GEQ, numOcc, d_zero)); - lem.push_back(rpaw.eqNode(nm->mkNode(APPLY_UF, us, d_zero))); + lem.push_back(nm->mkNode(GEQ, numOcc, zero)); + lem.push_back(rpaw.eqNode(nm->mkNode(APPLY_UF, us, zero))); lem.push_back(usno.eqNode(rem)); - lem.push_back(nm->mkNode(APPLY_UF, uf, d_zero).eqNode(d_zero)); - lem.push_back(nm->mkNode(STRING_STRIDOF, x, y, ufno).eqNode(d_neg_one)); + lem.push_back(nm->mkNode(APPLY_UF, uf, zero).eqNode(zero)); + lem.push_back(nm->mkNode(STRING_STRIDOF, x, y, ufno).eqNode(negOne)); - Node i = nm->mkBoundVar(nm->integerType()); + Node i = SkolemCache::mkIndexVar(t); Node bvli = nm->mkNode(BOUND_VAR_LIST, i); Node bound = - nm->mkNode(AND, nm->mkNode(GEQ, i, d_zero), nm->mkNode(LT, i, numOcc)); + nm->mkNode(AND, nm->mkNode(GEQ, i, zero), nm->mkNode(LT, i, numOcc)); Node ufi = nm->mkNode(APPLY_UF, uf, i); - Node ufip1 = nm->mkNode(APPLY_UF, uf, nm->mkNode(PLUS, i, d_one)); + Node ufip1 = nm->mkNode(APPLY_UF, uf, nm->mkNode(PLUS, i, one)); Node ii = nm->mkNode(STRING_STRIDOF, x, y, ufi); Node cc = nm->mkNode( STRING_CONCAT, nm->mkNode(STRING_SUBSTR, x, ufi, nm->mkNode(MINUS, ii, ufi)), z, - nm->mkNode(APPLY_UF, us, nm->mkNode(PLUS, i, d_one))); + nm->mkNode(APPLY_UF, us, nm->mkNode(PLUS, i, one))); std::vector flem; - flem.push_back(ii.eqNode(d_neg_one).negate()); + flem.push_back(ii.eqNode(negOne).negate()); flem.push_back(nm->mkNode(APPLY_UF, us, i).eqNode(cc)); flem.push_back( ufip1.eqNode(nm->mkNode(PLUS, ii, nm->mkNode(STRING_LENGTH, y)))); @@ -487,7 +483,7 @@ Node StringsPreprocess::simplify( Node t, std::vector< Node > &new_nodes ) { 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); + asserts.push_back(assert); // Thus, replaceall( x, y, z ) = rpaw retNode = rpaw; @@ -495,19 +491,17 @@ Node StringsPreprocess::simplify( Node t, std::vector< Node > &new_nodes ) { else if (t.getKind() == STRING_TOLOWER || t.getKind() == STRING_TOUPPER) { Node x = t[0]; - Node r = d_sc->mkSkolemCached(t, SkolemCache::SK_PURIFY, "r"); + Node r = sc->mkSkolemCached(t, SkolemCache::SK_PURIFY, "r"); Node lenx = nm->mkNode(STRING_LENGTH, x); Node lenr = nm->mkNode(STRING_LENGTH, r); Node eqLenA = lenx.eqNode(lenr); - Node i = nm->mkBoundVar(nm->integerType()); + Node i = SkolemCache::mkIndexVar(t); Node bvi = nm->mkNode(BOUND_VAR_LIST, i); - Node ci = - nm->mkNode(STRING_TO_CODE, nm->mkNode(STRING_SUBSTR, x, i, d_one)); - Node ri = - nm->mkNode(STRING_TO_CODE, nm->mkNode(STRING_SUBSTR, r, i, d_one)); + Node ci = nm->mkNode(STRING_TO_CODE, nm->mkNode(STRING_SUBSTR, x, i, one)); + Node ri = nm->mkNode(STRING_TO_CODE, nm->mkNode(STRING_SUBSTR, r, i, one)); Node lb = nm->mkConst(Rational(t.getKind() == STRING_TOUPPER ? 97 : 65)); Node ub = nm->mkConst(Rational(t.getKind() == STRING_TOUPPER ? 122 : 90)); @@ -521,7 +515,7 @@ Node StringsPreprocess::simplify( Node t, std::vector< Node > &new_nodes ) { ci); Node bound = - nm->mkNode(AND, nm->mkNode(LEQ, d_zero, i), nm->mkNode(LT, i, lenr)); + nm->mkNode(AND, nm->mkNode(LEQ, zero, i), nm->mkNode(LT, i, lenr)); Node rangeA = nm->mkNode(FORALL, bvi, nm->mkNode(OR, bound.negate(), ri.eqNode(res))); @@ -533,7 +527,7 @@ Node StringsPreprocess::simplify( Node t, std::vector< Node > &new_nodes ) { // str.code( str.substr(r,i,1) ) = ite( 97 <= ci <= 122, ci-32, ci) // where ci = str.code( str.substr(x,i,1) ) Node assert = nm->mkNode(AND, eqLenA, rangeA); - new_nodes.push_back(assert); + asserts.push_back(assert); // Thus, toLower( x ) = r retNode = r; @@ -541,22 +535,22 @@ Node StringsPreprocess::simplify( Node t, std::vector< Node > &new_nodes ) { else if (t.getKind() == STRING_REV) { Node x = t[0]; - Node r = d_sc->mkSkolemCached(t, SkolemCache::SK_PURIFY, "r"); + Node r = sc->mkSkolemCached(t, SkolemCache::SK_PURIFY, "r"); Node lenx = nm->mkNode(STRING_LENGTH, x); Node lenr = nm->mkNode(STRING_LENGTH, r); Node eqLenA = lenx.eqNode(lenr); - Node i = nm->mkBoundVar(nm->integerType()); + Node i = SkolemCache::mkIndexVar(t); Node bvi = nm->mkNode(BOUND_VAR_LIST, i); Node revi = nm->mkNode( - MINUS, nm->mkNode(STRING_LENGTH, x), nm->mkNode(PLUS, i, d_one)); - Node ssr = nm->mkNode(STRING_SUBSTR, r, i, d_one); - Node ssx = nm->mkNode(STRING_SUBSTR, x, revi, d_one); + MINUS, nm->mkNode(STRING_LENGTH, x), nm->mkNode(PLUS, i, one)); + Node ssr = nm->mkNode(STRING_SUBSTR, r, i, one); + Node ssx = nm->mkNode(STRING_SUBSTR, x, revi, one); Node bound = - nm->mkNode(AND, nm->mkNode(LEQ, d_zero, i), nm->mkNode(LT, i, lenr)); + nm->mkNode(AND, nm->mkNode(LEQ, zero, i), nm->mkNode(LT, i, lenr)); Node rangeA = nm->mkNode( FORALL, bvi, nm->mkNode(OR, bound.negate(), ssr.eqNode(ssx))); // assert: @@ -564,7 +558,7 @@ Node StringsPreprocess::simplify( Node t, std::vector< Node > &new_nodes ) { // forall i. 0 <= i < len(r) => // substr(r,i,1) = substr(x,len(x)-(i+1),1) Node assert = nm->mkNode(AND, eqLenA, rangeA); - new_nodes.push_back(assert); + asserts.push_back(assert); // Thus, (str.rev x) = r retNode = r; @@ -576,31 +570,38 @@ Node StringsPreprocess::simplify( Node t, std::vector< Node > &new_nodes ) { //negative contains reduces to existential Node lenx = NodeManager::currentNM()->mkNode(kind::STRING_LENGTH, x); Node lens = NodeManager::currentNM()->mkNode(kind::STRING_LENGTH, s); - Node b1 = NodeManager::currentNM()->mkBoundVar(NodeManager::currentNM()->integerType()); + Node b1 = SkolemCache::mkIndexVar(t); Node b1v = NodeManager::currentNM()->mkNode(kind::BOUND_VAR_LIST, b1); - Node body = NodeManager::currentNM()->mkNode( kind::AND, - NodeManager::currentNM()->mkNode( kind::LEQ, d_zero, b1 ), - NodeManager::currentNM()->mkNode( kind::LEQ, b1, NodeManager::currentNM()->mkNode( kind::MINUS, lenx, lens ) ), - NodeManager::currentNM()->mkNode( kind::EQUAL, NodeManager::currentNM()->mkNode(kind::STRING_SUBSTR, x, b1, lens), s ) - ); + Node body = NodeManager::currentNM()->mkNode( + kind::AND, + NodeManager::currentNM()->mkNode(kind::LEQ, zero, b1), + NodeManager::currentNM()->mkNode( + kind::LEQ, + b1, + NodeManager::currentNM()->mkNode(kind::MINUS, lenx, lens)), + NodeManager::currentNM()->mkNode( + kind::EQUAL, + NodeManager::currentNM()->mkNode(kind::STRING_SUBSTR, x, b1, lens), + s)); retNode = NodeManager::currentNM()->mkNode( kind::EXISTS, b1v, body ); } else if (t.getKind() == kind::STRING_LEQ) { - Node ltp = nm->mkSkolem("ltp", nm->booleanType()); + Node ltp = sc->mkTypedSkolemCached( + nm->booleanType(), t, SkolemCache::SK_PURIFY, "ltp"); Node k = nm->mkSkolem("k", nm->integerType()); std::vector conj; - conj.push_back(nm->mkNode(GEQ, k, d_zero)); + conj.push_back(nm->mkNode(GEQ, k, zero)); Node substr[2]; Node code[2]; for (unsigned r = 0; r < 2; r++) { Node ta = t[r]; Node tb = t[1 - r]; - substr[r] = nm->mkNode(STRING_SUBSTR, ta, d_zero, k); + substr[r] = nm->mkNode(STRING_SUBSTR, ta, zero, k); code[r] = - nm->mkNode(STRING_TO_CODE, nm->mkNode(STRING_SUBSTR, ta, k, d_one)); + nm->mkNode(STRING_TO_CODE, nm->mkNode(STRING_SUBSTR, ta, k, one)); conj.push_back(nm->mkNode(LEQ, k, nm->mkNode(STRING_LENGTH, ta))); } conj.push_back(substr[0].eqNode(substr[1])); @@ -632,18 +633,29 @@ Node StringsPreprocess::simplify( Node t, std::vector< Node > &new_nodes ) { // ELSE: str.code(substr( x, k, 1 )) > str.code(substr( y, k, 1 )) Node assert = nm->mkNode(ITE, t[0].eqNode(t[1]), ltp, nm->mkNode(AND, conj)); - new_nodes.push_back(assert); + asserts.push_back(assert); // Thus, str.<=( x, y ) = ltp retNode = ltp; } + return retNode; +} +Node StringsPreprocess::simplify(Node t, std::vector& asserts) +{ + size_t prev_asserts = asserts.size(); + // call the static reduce routine + Node retNode = reduce(t, asserts, d_sc); if( t!=retNode ){ Trace("strings-preprocess") << "StringsPreprocess::simplify: " << t << " -> " << retNode << std::endl; - if(!new_nodes.empty()) { - Trace("strings-preprocess") << " ... new nodes (" << (new_nodes.size()-prev_new_nodes) << "):" << std::endl; - for(unsigned int i=prev_new_nodes; i &new_nodes ) { return retNode; } -Node StringsPreprocess::simplifyRec( Node t, std::vector< Node > & new_nodes, std::map< Node, Node >& visited ){ +Node StringsPreprocess::simplifyRec(Node t, + std::vector& asserts, + std::map& visited) +{ std::map< Node, Node >::iterator it = visited.find(t); if( it!=visited.end() ){ return it->second; }else{ Node retNode = t; if( t.getNumChildren()==0 ){ - retNode = simplify( t, new_nodes ); + retNode = simplify(t, asserts); }else if( t.getKind()!=kind::FORALL ){ bool changed = false; std::vector< Node > cc; @@ -671,7 +686,7 @@ Node StringsPreprocess::simplifyRec( Node t, std::vector< Node > & new_nodes, st cc.push_back( t.getOperator() ); } for(unsigned i=0; i & new_nodes, st if( changed ){ tmp = NodeManager::currentNM()->mkNode( t.getKind(), cc ); } - retNode = simplify( tmp, new_nodes ); + retNode = simplify(tmp, asserts); } visited[t] = retNode; return retNode; } } -Node StringsPreprocess::processAssertion( Node n, std::vector< Node > &new_nodes ) { +Node StringsPreprocess::processAssertion(Node n, std::vector& asserts) +{ std::map< Node, Node > visited; - std::vector< Node > new_nodes_curr; - Node ret = simplifyRec( n, new_nodes_curr, visited ); - while( !new_nodes_curr.empty() ){ - Node curr = new_nodes_curr.back(); - new_nodes_curr.pop_back(); - std::vector< Node > new_nodes_tmp; - curr = simplifyRec( curr, new_nodes_tmp, visited ); - new_nodes_curr.insert( new_nodes_curr.end(), new_nodes_tmp.begin(), new_nodes_tmp.end() ); - new_nodes.push_back( curr ); + std::vector asserts_curr; + Node ret = simplifyRec(n, asserts_curr, visited); + while (!asserts_curr.empty()) + { + Node curr = asserts_curr.back(); + asserts_curr.pop_back(); + std::vector asserts_tmp; + curr = simplifyRec(curr, asserts_tmp, visited); + asserts_curr.insert( + asserts_curr.end(), asserts_tmp.begin(), asserts_tmp.end()); + asserts.push_back(curr); } return ret; } @@ -708,18 +726,22 @@ void StringsPreprocess::processAssertions( std::vector< Node > &vec_node ){ for( unsigned i=0; i new_nodes; - std::vector< Node > new_nodes_curr; - new_nodes_curr.push_back( vec_node[i] ); - while( !new_nodes_curr.empty() ){ - Node curr = new_nodes_curr.back(); - new_nodes_curr.pop_back(); - std::vector< Node > new_nodes_tmp; - curr = simplifyRec( curr, new_nodes_tmp, visited ); - new_nodes_curr.insert( new_nodes_curr.end(), new_nodes_tmp.begin(), new_nodes_tmp.end() ); - new_nodes.push_back( curr ); + std::vector asserts; + std::vector asserts_curr; + asserts_curr.push_back(vec_node[i]); + while (!asserts_curr.empty()) + { + Node curr = asserts_curr.back(); + asserts_curr.pop_back(); + std::vector asserts_tmp; + curr = simplifyRec(curr, asserts_tmp, visited); + asserts_curr.insert( + asserts_curr.end(), asserts_tmp.begin(), asserts_tmp.end()); + asserts.push_back(curr); } - Node res = new_nodes.size()==1 ? new_nodes[0] : NodeManager::currentNM()->mkNode( kind::AND, new_nodes ); + Node res = asserts.size() == 1 + ? asserts[0] + : NodeManager::currentNM()->mkNode(kind::AND, asserts); if( res!=vec_node[i] ){ res = Rewriter::rewrite( res ); PROOF( ProofManager::currentPM()->addDependence( res, vec_node[i] ); ); diff --git a/src/theory/strings/theory_strings_preprocess.h b/src/theory/strings/theory_strings_preprocess.h index fb6404aa6..1392b4ea1 100644 --- a/src/theory/strings/theory_strings_preprocess.h +++ b/src/theory/strings/theory_strings_preprocess.h @@ -44,21 +44,41 @@ class StringsPreprocess { context::UserContext* u, SequencesStatistics& stats); ~StringsPreprocess(); + /** The reduce routine + * + * This is the main routine for constructing the reduction lemma for + * an extended function t. It returns the simplified form of t, as well + * as assertions for t, interpeted conjunctively. The reduction lemma + * for t is: + * asserts[0] ^ ... ^ asserts[n] ^ t = t' + * where t' is the term returned by this method. + * The argument sc defines the methods for generating new Skolem variables. + * The return value is t itself if it is not reduced by this class. + * + * The reduction lemma for t is a way of specifying the complete semantics + * of t. In other words, any model satisfying the reduction lemma of t + * correctly interprets t. + * + * @param t The node to reduce, + * @param asserts The vector for storing the assertions that correspond to + * the reduction of t, + * @param sc The skolem cache for generating new variables, + * @return The reduced form of t. + */ + static Node reduce(Node t, std::vector& asserts, SkolemCache* sc); /** - * 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. + * Calls the above method for the skolem cache owned by this class, and + * records statistics. */ - Node simplify(Node t, std::vector& new_nodes); + Node simplify(Node t, std::vector& asserts); /** * 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' + * (exists k) asserts => t = t' * is valid, where k are the free skolems introduced when constructing - * new_nodes. + * asserts. */ - Node processAssertion(Node t, std::vector& new_nodes); + Node processAssertion(Node t, std::vector& asserts); /** * Replaces all formulas t in vec_node with an equivalent formula t' that * contains no free instances of extended functions (that is, extended @@ -68,21 +88,17 @@ class StringsPreprocess { void processAssertions(std::vector& vec_node); private: - /** commonly used constants */ - Node d_zero; - Node d_one; - Node d_neg_one; /** 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 + * assertions created in this reduction are added to asserts. The argument * visited stores a cache of previous results. */ Node simplifyRec(Node t, - std::vector& new_nodes, + std::vector& asserts, std::map& visited); }; -- cgit v1.2.3 From 8c467723be3746ed711c609fa6dafb19a5a49e8b Mon Sep 17 00:00:00 2001 From: Andrew Reynolds Date: Thu, 4 Jun 2020 11:31:55 -0500 Subject: Fix abduction with datatypes (#4566) Previously we were treating constructor/selector/tester symbols as arguments to the abduct-to-synthesize. --- src/theory/quantifiers/sygus/sygus_abduct.cpp | 6 ++++++ test/regress/CMakeLists.txt | 1 + test/regress/regress1/abduct-dt.smt2 | 8 ++++++++ 3 files changed, 15 insertions(+) create mode 100644 test/regress/regress1/abduct-dt.smt2 (limited to 'src') diff --git a/src/theory/quantifiers/sygus/sygus_abduct.cpp b/src/theory/quantifiers/sygus/sygus_abduct.cpp index a58c5d841..22b56eb6b 100644 --- a/src/theory/quantifiers/sygus/sygus_abduct.cpp +++ b/src/theory/quantifiers/sygus/sygus_abduct.cpp @@ -58,6 +58,12 @@ Node SygusAbduct::mkAbductionConjecture(const std::string& name, for (const Node& s : symset) { TypeNode tn = s.getType(); + if (tn.isConstructor() || tn.isSelector() || tn.isTester()) + { + // datatype symbols should be considered interpreted symbols here, not + // (higher-order) variables. + continue; + } // Notice that we allow for non-first class (e.g. function) variables here. // This is applicable to the case where we are doing get-abduct in a logic // with UF. diff --git a/test/regress/CMakeLists.txt b/test/regress/CMakeLists.txt index 801f38b29..290fca6bc 100644 --- a/test/regress/CMakeLists.txt +++ b/test/regress/CMakeLists.txt @@ -1200,6 +1200,7 @@ set(regress_0_tests # Regression level 1 tests set(regress_1_tests + regress1/abduct-dt.smt2 regress1/arith/arith-int-004.cvc regress1/arith/arith-int-011.cvc regress1/arith/arith-int-012.cvc diff --git a/test/regress/regress1/abduct-dt.smt2 b/test/regress/regress1/abduct-dt.smt2 new file mode 100644 index 000000000..d72d15a21 --- /dev/null +++ b/test/regress/regress1/abduct-dt.smt2 @@ -0,0 +1,8 @@ +; COMMAND-LINE: --produce-abducts +; SCRUBBER: grep -v -E '(\(define-fun)' +; EXIT: 0 +(set-logic ALL) +(declare-datatypes ((List 0)) (((nil) (cons (head Int) (tail List))))) +(declare-fun x () List) +(assert (distinct x nil)) +(get-abduct A (= x (cons (head x) (tail x)))) -- cgit v1.2.3 From c5bf818456ebe2dee833fecd4a0970f0105919f0 Mon Sep 17 00:00:00 2001 From: Andrew Reynolds Date: Thu, 4 Jun 2020 12:38:36 -0500 Subject: Add sygus datatype substitution utility method (#4390) This makes the method for substiutiton and generalization of sygus datatypes a generic utility method. It updates the abduction method to use it. Interpolation is another target user of this utility. --- src/theory/datatypes/theory_datatypes_utils.cpp | 218 ++++++++++++++++++++++++ src/theory/datatypes/theory_datatypes_utils.h | 40 +++++ src/theory/quantifiers/sygus/sygus_abduct.cpp | 176 +++---------------- 3 files changed, 281 insertions(+), 153 deletions(-) (limited to 'src') diff --git a/src/theory/datatypes/theory_datatypes_utils.cpp b/src/theory/datatypes/theory_datatypes_utils.cpp index ee0fd814e..ea67ab79d 100644 --- a/src/theory/datatypes/theory_datatypes_utils.cpp +++ b/src/theory/datatypes/theory_datatypes_utils.cpp @@ -23,6 +23,7 @@ #include "smt/smt_engine_scope.h" #include "theory/evaluator.h" #include "theory/rewriter.h" +#include "printer/sygus_print_callback.h" using namespace CVC4; using namespace CVC4::kind; @@ -711,6 +712,223 @@ Node sygusToBuiltinEval(Node n, const std::vector& args) return visited[n]; } +void getFreeSymbolsSygusType(TypeNode sdt, + std::unordered_set& syms) +{ + // datatype types we need to process + std::vector typeToProcess; + // datatype types we have processed + std::map typesProcessed; + typeToProcess.push_back(sdt); + while (!typeToProcess.empty()) + { + std::vector typeNextToProcess; + for (const TypeNode& curr : typeToProcess) + { + Assert(curr.isDatatype() && curr.getDType().isSygus()); + const DType& dtc = curr.getDType(); + for (unsigned j = 0, ncons = dtc.getNumConstructors(); j < ncons; j++) + { + // collect the symbols from the operator + Node op = dtc[j].getSygusOp(); + expr::getSymbols(op, syms); + // traverse the argument types + for (unsigned k = 0, nargs = dtc[j].getNumArgs(); k < nargs; k++) + { + TypeNode argt = dtc[j].getArgType(k); + if (!argt.isDatatype() || !argt.getDType().isSygus()) + { + // not a sygus datatype + continue; + } + if (typesProcessed.find(argt) == typesProcessed.end()) + { + typeNextToProcess.push_back(argt); + } + } + } + } + typeToProcess.clear(); + typeToProcess.insert(typeToProcess.end(), + typeNextToProcess.begin(), + typeNextToProcess.end()); + } +} + +TypeNode substituteAndGeneralizeSygusType(TypeNode sdt, + const std::vector& syms, + const std::vector& vars) +{ + NodeManager* nm = NodeManager::currentNM(); + const DType& sdtd = sdt.getDType(); + // compute the new formal argument list + std::vector formalVars; + Node prevVarList = sdtd.getSygusVarList(); + if (!prevVarList.isNull()) + { + for (const Node& v : prevVarList) + { + // if it is not being replaced + if (std::find(syms.begin(), syms.end(), v) != syms.end()) + { + formalVars.push_back(v); + } + } + } + for (const Node& v : vars) + { + if (v.getKind() == BOUND_VARIABLE) + { + formalVars.push_back(v); + } + } + // make the sygus variable list for the formal argument list + Node abvl = nm->mkNode(BOUND_VAR_LIST, formalVars); + Trace("sygus-abduct-debug") << "...finish" << std::endl; + + // must convert all constructors to version with variables in "vars" + std::vector sdts; + std::set unres; + + Trace("dtsygus-gen-debug") << "Process sygus type:" << std::endl; + Trace("dtsygus-gen-debug") << sdtd.getName() << std::endl; + + // datatype types we need to process + std::vector dtToProcess; + // datatype types we have processed + std::map dtProcessed; + dtToProcess.push_back(sdt); + std::stringstream ssutn0; + ssutn0 << sdtd.getName() << "_s"; + TypeNode abdTNew = + nm->mkSort(ssutn0.str(), ExprManager::SORT_FLAG_PLACEHOLDER); + unres.insert(abdTNew.toType()); + dtProcessed[sdt] = abdTNew; + + // We must convert all symbols in the sygus datatype type sdt to + // apply the substitution { syms -> vars }, where syms is the free + // variables of the input problem, and vars is the formal argument list + // of the function-to-synthesize. + + // We are traversing over the subfield types of the datatype to convert + // them into the form described above. + while (!dtToProcess.empty()) + { + std::vector dtNextToProcess; + for (const TypeNode& curr : dtToProcess) + { + Assert(curr.isDatatype() && curr.getDType().isSygus()); + const DType& dtc = curr.getDType(); + std::stringstream ssdtn; + ssdtn << dtc.getName() << "_s"; + sdts.push_back(SygusDatatype(ssdtn.str())); + Trace("dtsygus-gen-debug") + << "Process datatype " << sdts.back().getName() << "..." << std::endl; + for (unsigned j = 0, ncons = dtc.getNumConstructors(); j < ncons; j++) + { + Node op = dtc[j].getSygusOp(); + // apply the substitution to the argument + Node ops = + op.substitute(syms.begin(), syms.end(), vars.begin(), vars.end()); + Trace("dtsygus-gen-debug") << " Process constructor " << op << " / " + << ops << "..." << std::endl; + std::vector cargs; + for (unsigned k = 0, nargs = dtc[j].getNumArgs(); k < nargs; k++) + { + TypeNode argt = dtc[j].getArgType(k); + std::map::iterator itdp = dtProcessed.find(argt); + TypeNode argtNew; + if (itdp == dtProcessed.end()) + { + std::stringstream ssutn; + ssutn << argt.getDType().getName() << "_s"; + argtNew = + nm->mkSort(ssutn.str(), ExprManager::SORT_FLAG_PLACEHOLDER); + Trace("dtsygus-gen-debug") << " ...unresolved type " << argtNew + << " for " << argt << std::endl; + unres.insert(argtNew.toType()); + dtProcessed[argt] = argtNew; + dtNextToProcess.push_back(argt); + } + else + { + argtNew = itdp->second; + } + Trace("dtsygus-gen-debug") + << " Arg #" << k << ": " << argtNew << std::endl; + cargs.push_back(argtNew); + } + // callback prints as the expression + std::shared_ptr spc; + std::vector args; + if (op.getKind() == LAMBDA) + { + Node opBody = op[1]; + for (const Node& v : op[0]) + { + args.push_back(v.toExpr()); + } + spc = std::make_shared( + opBody.toExpr(), args); + } + else if (cargs.empty()) + { + spc = std::make_shared(op.toExpr(), + args); + } + std::stringstream ss; + ss << ops.getKind(); + Trace("dtsygus-gen-debug") << "Add constructor : " << ops << std::endl; + sdts.back().addConstructor(ops, ss.str(), cargs, spc); + } + Trace("dtsygus-gen-debug") + << "Set sygus : " << dtc.getSygusType() << " " << abvl << std::endl; + TypeNode stn = dtc.getSygusType(); + sdts.back().initializeDatatype( + stn, abvl, dtc.getSygusAllowConst(), dtc.getSygusAllowAll()); + } + dtToProcess.clear(); + dtToProcess.insert( + dtToProcess.end(), dtNextToProcess.begin(), dtNextToProcess.end()); + } + Trace("dtsygus-gen-debug") + << "Make " << sdts.size() << " datatype types..." << std::endl; + // extract the datatypes + std::vector datatypes; + for (unsigned i = 0, ndts = sdts.size(); i < ndts; i++) + { + datatypes.push_back(sdts[i].getDatatype()); + } + // make the datatype types + std::vector datatypeTypes = + nm->toExprManager()->mkMutualDatatypeTypes( + datatypes, unres, ExprManager::DATATYPE_FLAG_PLACEHOLDER); + TypeNode sdtS = TypeNode::fromType(datatypeTypes[0]); + if (Trace.isOn("dtsygus-gen-debug")) + { + Trace("dtsygus-gen-debug") << "Made datatype types:" << std::endl; + for (unsigned j = 0, ndts = datatypeTypes.size(); j < ndts; j++) + { + const DType& dtj = TypeNode::fromType(datatypeTypes[j]).getDType(); + Trace("dtsygus-gen-debug") << "#" << j << ": " << dtj << std::endl; + for (unsigned k = 0, ncons = dtj.getNumConstructors(); k < ncons; k++) + { + for (unsigned l = 0, nargs = dtj[k].getNumArgs(); l < nargs; l++) + { + if (!dtj[k].getArgType(l).isDatatype()) + { + Trace("dtsygus-gen-debug") + << "Argument " << l << " of " << dtj[k] + << " is not datatype : " << dtj[k].getArgType(l) << std::endl; + AlwaysAssert(false); + } + } + } + } + } + return sdtS; +} + } // namespace utils } // namespace datatypes } // namespace theory diff --git a/src/theory/datatypes/theory_datatypes_utils.h b/src/theory/datatypes/theory_datatypes_utils.h index 58f719910..038922f37 100644 --- a/src/theory/datatypes/theory_datatypes_utils.h +++ b/src/theory/datatypes/theory_datatypes_utils.h @@ -245,6 +245,46 @@ Node sygusToBuiltin(Node c, bool isExternal = false); */ Node sygusToBuiltinEval(Node n, const std::vector& args); +/** Get free symbols in a sygus datatype type + * + * Add the free symbols (expr::getSymbols) in terms that can be generated by + * sygus datatype sdt to the set syms. For example, given sdt encodes the + * grammar: + * G -> a | +( b, G ) | c | e + * We have that { a, b, c, e } are added to syms. Notice that expr::getSymbols + * excludes variables whose kind is BOUND_VARIABLE. + */ +void getFreeSymbolsSygusType(TypeNode sdt, + std::unordered_set& syms); + +/** Substitute and generalize a sygus datatype type + * + * This transforms a sygus datatype sdt into another one sdt' that generates + * terms t such that t * { vars -> syms } is generated by sdt. + * + * The arguments syms and vars should be vectors of the same size and types. + * It is recommended that the arguments in syms and vars should be variables + * (return true for .isVar()) but this is not required. + * + * The variables in vars of type BOUND_VARIABLE are added to the + * formal argument list of t. Other symbols are not. + * + * For example, given sdt encodes the grammar: + * G -> a | +( b, G ) | c | e + * Let syms = { a, b, c } and vars = { x_a, x_b, d }, where x_a and x_b have + * type BOUND_VARIABLE and d does not. + * The returned type encodes the grammar: + * G' -> x_a | +( x_b, G' ) | d | e + * Additionally, x_a and x_b are treated as formal arguments of a function + * to synthesize whose syntax restrictions are specified by G'. + * + * This method traverses the type definition of the datatype corresponding to + * the argument sdt. + */ +TypeNode substituteAndGeneralizeSygusType(TypeNode sdt, + const std::vector& syms, + const std::vector& vars); + // ------------------------ end sygus utils } // namespace utils diff --git a/src/theory/quantifiers/sygus/sygus_abduct.cpp b/src/theory/quantifiers/sygus/sygus_abduct.cpp index 22b56eb6b..ef2e7e445 100644 --- a/src/theory/quantifiers/sygus/sygus_abduct.cpp +++ b/src/theory/quantifiers/sygus/sygus_abduct.cpp @@ -19,7 +19,6 @@ #include "expr/dtype.h" #include "expr/node_algorithm.h" #include "expr/sygus_datatype.h" -#include "printer/sygus_print_callback.h" #include "theory/datatypes/theory_datatypes_utils.h" #include "theory/quantifiers/quantifiers_attributes.h" #include "theory/quantifiers/quantifiers_rewriter.h" @@ -79,8 +78,6 @@ Node SygusAbduct::mkAbductionConjecture(const std::string& name, SygusVarToTermAttribute sta; vlv.setAttribute(sta, s); } - // make the sygus variable list - Node abvl = nm->mkNode(BOUND_VAR_LIST, varlist); Trace("sygus-abduct-debug") << "...finish" << std::endl; Trace("sygus-abduct-debug") << "Make abduction predicate..." << std::endl; @@ -90,163 +87,23 @@ Node SygusAbduct::mkAbductionConjecture(const std::string& name, Node abd = nm->mkBoundVar(name.c_str(), abdType); Trace("sygus-abduct-debug") << "...finish" << std::endl; - // if provided, we will associate it with the function-to-synthesize + // the sygus variable list + Node abvl; + // if provided, we will associate the provide sygus datatype type with the + // function-to-synthesize. However, we must convert it so that its + // free symbols are universally quantified. if (!abdGType.isNull()) { Assert(abdGType.isDatatype() && abdGType.getDType().isSygus()); - // must convert all constructors to version with bound variables in "vars" - std::vector sdts; - std::set unres; - Trace("sygus-abduct-debug") << "Process abduction type:" << std::endl; Trace("sygus-abduct-debug") << abdGType.getDType().getName() << std::endl; - // datatype types we need to process - std::vector dtToProcess; - // datatype types we have processed - std::map dtProcessed; - dtToProcess.push_back(abdGType); - std::stringstream ssutn0; - ssutn0 << abdGType.getDType().getName() << "_s"; - TypeNode abdTNew = - nm->mkSort(ssutn0.str(), ExprManager::SORT_FLAG_PLACEHOLDER); - unres.insert(abdTNew.toType()); - dtProcessed[abdGType] = abdTNew; - - // We must convert all symbols in the sygus datatype type abdGType to - // apply the substitution { syms -> varlist }, where syms is the free - // variables of the input problem, and varlist is the formal argument list - // of the abduct-to-synthesize. For example, given user-provided sygus - // grammar: - // G -> a | +( b, G ) - // we synthesize a abduct A with two arguments x_a and x_b corresponding to - // a and b, and reconstruct the grammar: - // G' -> x_a | +( x_b, G' ) - // In this way, x_a and x_b are treated as bound variables and handled as - // arguments of the abduct-to-synthesize instead of as free variables with - // no relation to A. We additionally require that x_a, when printed, prints - // "a", which we do with a custom sygus callback below. + // substitute the free symbols of the grammar with variables corresponding + // to the formal argument list of the new sygus datatype type. + TypeNode abdGTypeS = datatypes::utils::substituteAndGeneralizeSygusType( + abdGType, syms, varlist); - // We are traversing over the subfield types of the datatype to convert - // them into the form described above. - while (!dtToProcess.empty()) - { - std::vector dtNextToProcess; - for (const TypeNode& curr : dtToProcess) - { - Assert(curr.isDatatype() && curr.getDType().isSygus()); - const DType& dtc = curr.getDType(); - std::stringstream ssdtn; - ssdtn << dtc.getName() << "_s"; - sdts.push_back(SygusDatatype(ssdtn.str())); - Trace("sygus-abduct-debug") - << "Process datatype " << sdts.back().getName() << "..." - << std::endl; - for (unsigned j = 0, ncons = dtc.getNumConstructors(); j < ncons; j++) - { - Node op = dtc[j].getSygusOp(); - // apply the substitution to the argument - Node ops = op.substitute( - syms.begin(), syms.end(), varlist.begin(), varlist.end()); - Trace("sygus-abduct-debug") << " Process constructor " << op << " / " - << ops << "..." << std::endl; - std::vector cargs; - for (unsigned k = 0, nargs = dtc[j].getNumArgs(); k < nargs; k++) - { - TypeNode argt = dtc[j].getArgType(k); - std::map::iterator itdp = - dtProcessed.find(argt); - TypeNode argtNew; - if (itdp == dtProcessed.end()) - { - std::stringstream ssutn; - ssutn << argt.getDType().getName() << "_s"; - argtNew = - nm->mkSort(ssutn.str(), ExprManager::SORT_FLAG_PLACEHOLDER); - Trace("sygus-abduct-debug") - << " ...unresolved type " << argtNew << " for " << argt - << std::endl; - unres.insert(argtNew.toType()); - dtProcessed[argt] = argtNew; - dtNextToProcess.push_back(argt); - } - else - { - argtNew = itdp->second; - } - Trace("sygus-abduct-debug") - << " Arg #" << k << ": " << argtNew << std::endl; - cargs.push_back(argtNew); - } - // callback prints as the expression - std::shared_ptr spc; - std::vector args; - if (op.getKind() == LAMBDA) - { - Node opBody = op[1]; - for (const Node& v : op[0]) - { - args.push_back(v.toExpr()); - } - spc = std::make_shared( - opBody.toExpr(), args); - } - else if (cargs.empty()) - { - spc = std::make_shared(op.toExpr(), - args); - } - std::stringstream ss; - ss << ops.getKind(); - Trace("sygus-abduct-debug") - << "Add constructor : " << ops << std::endl; - sdts.back().addConstructor(ops, ss.str(), cargs, spc); - } - Trace("sygus-abduct-debug") - << "Set sygus : " << dtc.getSygusType() << " " << abvl << std::endl; - TypeNode stn = dtc.getSygusType(); - sdts.back().initializeDatatype( - stn, abvl, dtc.getSygusAllowConst(), dtc.getSygusAllowAll()); - } - dtToProcess.clear(); - dtToProcess.insert( - dtToProcess.end(), dtNextToProcess.begin(), dtNextToProcess.end()); - } - Trace("sygus-abduct-debug") - << "Make " << sdts.size() << " datatype types..." << std::endl; - // extract the datatypes - std::vector datatypes; - for (unsigned i = 0, ndts = sdts.size(); i < ndts; i++) - { - datatypes.push_back(sdts[i].getDatatype()); - } - // make the datatype types - std::vector datatypeTypes = - nm->toExprManager()->mkMutualDatatypeTypes( - datatypes, unres, ExprManager::DATATYPE_FLAG_PLACEHOLDER); - TypeNode abdGTypeS = TypeNode::fromType(datatypeTypes[0]); - if (Trace.isOn("sygus-abduct-debug")) - { - Trace("sygus-abduct-debug") << "Made datatype types:" << std::endl; - for (unsigned j = 0, ndts = datatypeTypes.size(); j < ndts; j++) - { - const DType& dtj = TypeNode::fromType(datatypeTypes[j]).getDType(); - Trace("sygus-abduct-debug") << "#" << j << ": " << dtj << std::endl; - for (unsigned k = 0, ncons = dtj.getNumConstructors(); k < ncons; k++) - { - for (unsigned l = 0, nargs = dtj[k].getNumArgs(); l < nargs; l++) - { - if (!dtj[k].getArgType(l).isDatatype()) - { - Trace("sygus-abduct-debug") - << "Argument " << l << " of " << dtj[k] - << " is not datatype : " << dtj[k].getArgType(l) << std::endl; - AlwaysAssert(false); - } - } - } - } - } + Assert(abdGTypeS.isDatatype() && abdGTypeS.getDType().isSygus()); Trace("sygus-abduct-debug") << "Make sygus grammar attribute..." << std::endl; @@ -256,6 +113,19 @@ Node SygusAbduct::mkAbductionConjecture(const std::string& name, theory::SygusSynthGrammarAttribute ssg; abd.setAttribute(ssg, sym); Trace("sygus-abduct-debug") << "Finished setting up grammar." << std::endl; + + // use the bound variable list from the new substituted grammar type + const DType& agtsd = abdGTypeS.getDType(); + abvl = agtsd.getSygusVarList(); + Assert(!abvl.isNull() && abvl.getKind() == BOUND_VAR_LIST); + } + else + { + // the bound variable list of the abduct-to-synthesize is determined by + // the variable list above + abvl = nm->mkNode(BOUND_VAR_LIST, varlist); + // We do not set a grammar type for abd (SygusSynthGrammarAttribute). + // Its grammar will be constructed internally in the default way } Trace("sygus-abduct-debug") << "Make abduction predicate app..." << std::endl; -- cgit v1.2.3 From f0169b253759632aee0d21db916fe68702c66116 Mon Sep 17 00:00:00 2001 From: Aina Niemetz Date: Thu, 4 Jun 2020 11:07:41 -0700 Subject: New C++ Api: Second and last batch of API guards. (#4563) This adds the remaining API guards in the Solver object (incl. unit tests). --- src/api/cvc4cpp.cpp | 294 ++++++++++++++++++++++++++----------- src/smt/smt_engine.cpp | 102 ++++++++----- src/smt/smt_engine.h | 48 ++++--- test/unit/api/solver_black.h | 334 ++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 634 insertions(+), 144 deletions(-) (limited to 'src') diff --git a/src/api/cvc4cpp.cpp b/src/api/cvc4cpp.cpp index f225da333..734fcddae 100644 --- a/src/api/cvc4cpp.cpp +++ b/src/api/cvc4cpp.cpp @@ -4194,8 +4194,10 @@ Term Solver::declareFun(const std::string& symbol, */ Sort Solver::declareSort(const std::string& symbol, uint32_t arity) const { + CVC4_API_SOLVER_TRY_CATCH_BEGIN; if (arity == 0) return Sort(this, d_exprMgr->mkSort(symbol)); return Sort(this, d_exprMgr->mkSortConstructor(symbol, arity)); + CVC4_API_SOLVER_TRY_CATCH_END; } /** @@ -4206,26 +4208,28 @@ Term Solver::defineFun(const std::string& symbol, Sort sort, Term term) const { - // CHECK: - // for bv in bound_vars: - // NodeManager::fromExprManager(d_exprMgr) - // == NodeManager::fromExprManager(bv.getExprManager()) - // NodeManager::fromExprManager(d_exprMgr) - // == NodeManager::fromExprManager(expr.getExprManager()) - // CHECK: not recursive + CVC4_API_SOLVER_TRY_CATCH_BEGIN; CVC4_API_ARG_CHECK_EXPECTED(sort.isFirstClass(), sort) << "first-class sort as codomain sort for function sort"; - // CHECK: - // for v in bound_vars: is bound var std::vector domain_types; for (size_t i = 0, size = bound_vars.size(); i < size; ++i) { + CVC4_API_ARG_AT_INDEX_CHECK_EXPECTED( + this == bound_vars[i].d_solver, "bound variable", bound_vars[i], i) + << "bound variable associated to this solver object"; + CVC4_API_ARG_AT_INDEX_CHECK_EXPECTED( + bound_vars[i].d_expr->getKind() == CVC4::Kind::BOUND_VARIABLE, + "bound variable", + bound_vars[i], + i) + << "a bound variable"; CVC4::Type t = bound_vars[i].d_expr->getType(); CVC4_API_ARG_AT_INDEX_CHECK_EXPECTED( t.isFirstClass(), "sort of parameter", bound_vars[i], i) << "first-class sort of parameter of defined function"; domain_types.push_back(t); } + CVC4_API_SOLVER_CHECK_SORT(sort); CVC4_API_CHECK(sort == term.getSort()) << "Invalid sort of function body '" << term << "', expected '" << sort << "'"; @@ -4238,17 +4242,14 @@ Term Solver::defineFun(const std::string& symbol, std::vector ebound_vars = termVectorToExprs(bound_vars); d_smtEngine->defineFunction(fun, ebound_vars, *term.d_expr); return Term(this, fun); + CVC4_API_SOLVER_TRY_CATCH_END; } Term Solver::defineFun(Term fun, const std::vector& bound_vars, Term term) const { - // CHECK: - // NodeManager::fromExprManager(d_exprMgr) - // == NodeManager::fromExprManager(bv.getExprManager()) - // NodeManager::fromExprManager(d_exprMgr) - // == NodeManager::fromExprManager(expr.getExprManager()) + CVC4_API_SOLVER_TRY_CATCH_BEGIN; CVC4_API_ARG_CHECK_EXPECTED(fun.getSort().isFunction(), fun) << "function"; std::vector domain_sorts = fun.getSort().getFunctionDomainSorts(); size_t size = bound_vars.size(); @@ -4256,6 +4257,15 @@ Term Solver::defineFun(Term fun, << "'" << domain_sorts.size() << "'"; for (size_t i = 0; i < size; ++i) { + CVC4_API_ARG_AT_INDEX_CHECK_EXPECTED( + this == bound_vars[i].d_solver, "bound variable", bound_vars[i], i) + << "bound variable associated to this solver object"; + CVC4_API_ARG_AT_INDEX_CHECK_EXPECTED( + bound_vars[i].d_expr->getKind() == CVC4::Kind::BOUND_VARIABLE, + "bound variable", + bound_vars[i], + i) + << "a bound variable"; CVC4_API_ARG_AT_INDEX_CHECK_EXPECTED( domain_sorts[i] == bound_vars[i].getSort(), "sort of parameter", @@ -4264,16 +4274,15 @@ Term Solver::defineFun(Term fun, << "'" << domain_sorts[i] << "'"; } Sort codomain = fun.getSort().getFunctionCodomainSort(); + CVC4_API_SOLVER_CHECK_TERM(term); CVC4_API_CHECK(codomain == term.getSort()) << "Invalid sort of function body '" << term << "', expected '" << codomain << "'"; - // CHECK: not recursive - // CHECK: - // for v in bound_vars: is bound var std::vector ebound_vars = termVectorToExprs(bound_vars); d_smtEngine->defineFunction(*fun.d_expr, ebound_vars, *term.d_expr); return fun; + CVC4_API_SOLVER_TRY_CATCH_END; } /** @@ -4284,29 +4293,33 @@ Term Solver::defineFunRec(const std::string& symbol, Sort sort, Term term) const { - // CHECK: - // for bv in bound_vars: - // NodeManager::fromExprManager(d_exprMgr) - // == NodeManager::fromExprManager(bv.getExprManager()) - // NodeManager::fromExprManager(d_exprMgr) - // == NodeManager::fromExprManager(expr.getExprManager()) + CVC4_API_SOLVER_TRY_CATCH_BEGIN; CVC4_API_ARG_CHECK_EXPECTED(sort.isFirstClass(), sort) << "first-class sort as function codomain sort"; Assert(!sort.isFunction()); /* A function sort is not first-class. */ - // CHECK: - // for v in bound_vars: is bound var std::vector domain_types; for (size_t i = 0, size = bound_vars.size(); i < size; ++i) { + CVC4_API_ARG_AT_INDEX_CHECK_EXPECTED( + this == bound_vars[i].d_solver, "bound variable", bound_vars[i], i) + << "bound variable associated to this solver object"; + CVC4_API_ARG_AT_INDEX_CHECK_EXPECTED( + bound_vars[i].d_expr->getKind() == CVC4::Kind::BOUND_VARIABLE, + "bound variable", + bound_vars[i], + i) + << "a bound variable"; CVC4::Type t = bound_vars[i].d_expr->getType(); CVC4_API_ARG_AT_INDEX_CHECK_EXPECTED( t.isFirstClass(), "sort of parameter", bound_vars[i], i) << "first-class sort of parameter of defined function"; domain_types.push_back(t); } + CVC4_API_SOLVER_CHECK_SORT(sort); CVC4_API_CHECK(sort == term.getSort()) << "Invalid sort of function body '" << term << "', expected '" << sort << "'"; + CVC4_API_SOLVER_CHECK_TERM(term); Type type = *sort.d_type; if (!domain_types.empty()) { @@ -4316,18 +4329,14 @@ Term Solver::defineFunRec(const std::string& symbol, std::vector ebound_vars = termVectorToExprs(bound_vars); d_smtEngine->defineFunctionRec(fun, ebound_vars, *term.d_expr); return Term(this, fun); + CVC4_API_SOLVER_TRY_CATCH_END; } Term Solver::defineFunRec(Term fun, const std::vector& bound_vars, Term term) const { - // CHECK: - // for bv in bound_vars: - // NodeManager::fromExprManager(d_exprMgr) - // == NodeManager::fromExprManager(bv.getExprManager()) - // NodeManager::fromExprManager(d_exprMgr) - // == NodeManager::fromExprManager(expr.getExprManager()) + CVC4_API_SOLVER_TRY_CATCH_BEGIN; CVC4_API_ARG_CHECK_EXPECTED(fun.getSort().isFunction(), fun) << "function"; std::vector domain_sorts = fun.getSort().getFunctionDomainSorts(); size_t size = bound_vars.size(); @@ -4335,6 +4344,15 @@ Term Solver::defineFunRec(Term fun, << "'" << domain_sorts.size() << "'"; for (size_t i = 0; i < size; ++i) { + CVC4_API_ARG_AT_INDEX_CHECK_EXPECTED( + this == bound_vars[i].d_solver, "bound variable", bound_vars[i], i) + << "bound variable associated to this solver object"; + CVC4_API_ARG_AT_INDEX_CHECK_EXPECTED( + bound_vars[i].d_expr->getKind() == CVC4::Kind::BOUND_VARIABLE, + "bound variable", + bound_vars[i], + i) + << "a bound variable"; CVC4_API_ARG_AT_INDEX_CHECK_EXPECTED( domain_sorts[i] == bound_vars[i].getSort(), "sort of parameter", @@ -4342,15 +4360,15 @@ Term Solver::defineFunRec(Term fun, i) << "'" << domain_sorts[i] << "'"; } + CVC4_API_SOLVER_CHECK_TERM(term); Sort codomain = fun.getSort().getFunctionCodomainSort(); CVC4_API_CHECK(codomain == term.getSort()) << "Invalid sort of function body '" << term << "', expected '" << codomain << "'"; - // CHECK: - // for v in bound_vars: is bound var std::vector ebound_vars = termVectorToExprs(bound_vars); d_smtEngine->defineFunctionRec(*fun.d_expr, ebound_vars, *term.d_expr); return fun; + CVC4_API_SOLVER_TRY_CATCH_END; } /** @@ -4360,15 +4378,7 @@ void Solver::defineFunsRec(const std::vector& funs, const std::vector>& bound_vars, const std::vector& terms) const { - // CHECK: - // for f in funs: - // NodeManager::fromExprManager(d_exprMgr) - // == NodeManager::fromExprManager(f.getExprManager()) - // for bv in bound_vars: - // NodeManager::fromExprManager(d_exprMgr) - // == NodeManager::fromExprManager(bv.getExprManager()) - // NodeManager::fromExprManager(d_exprMgr) - // == NodeManager::fromExprManager(expr.getExprManager()) + CVC4_API_SOLVER_TRY_CATCH_BEGIN; size_t funs_size = funs.size(); CVC4_API_ARG_SIZE_CHECK_EXPECTED(funs_size == bound_vars.size(), bound_vars) << "'" << funs_size << "'"; @@ -4378,13 +4388,30 @@ void Solver::defineFunsRec(const std::vector& funs, const std::vector& bvars = bound_vars[j]; const Term& term = terms[j]; + CVC4_API_ARG_AT_INDEX_CHECK_EXPECTED( + this == fun.d_solver, "function", fun, j) + << "function associated to this solver object"; CVC4_API_ARG_CHECK_EXPECTED(fun.getSort().isFunction(), fun) << "function"; + CVC4_API_SOLVER_CHECK_TERM(term); + std::vector domain_sorts = fun.getSort().getFunctionDomainSorts(); size_t size = bvars.size(); CVC4_API_ARG_SIZE_CHECK_EXPECTED(size == domain_sorts.size(), bvars) << "'" << domain_sorts.size() << "'"; for (size_t i = 0; i < size; ++i) { + for (size_t k = 0, nbvars = bvars.size(); k < nbvars; ++k) + { + CVC4_API_ARG_AT_INDEX_CHECK_EXPECTED( + this == bvars[k].d_solver, "bound variable", bvars[k], k) + << "bound variable associated to this solver object"; + CVC4_API_ARG_AT_INDEX_CHECK_EXPECTED( + bvars[k].d_expr->getKind() == CVC4::Kind::BOUND_VARIABLE, + "bound variable", + bvars[k], + k) + << "a bound variable"; + } CVC4_API_ARG_AT_INDEX_CHECK_EXPECTED( domain_sorts[i] == bvars[i].getSort(), "sort of parameter", @@ -4397,8 +4424,6 @@ void Solver::defineFunsRec(const std::vector& funs, codomain == term.getSort(), "sort of function body", term, j) << "'" << codomain << "'"; } - // CHECK: - // for bv in bound_vars (for v in bv): is bound var std::vector efuns = termVectorToExprs(funs); std::vector> ebound_vars; for (const auto& v : bound_vars) @@ -4407,6 +4432,7 @@ void Solver::defineFunsRec(const std::vector& funs, } std::vector exprs = termVectorToExprs(terms); d_smtEngine->defineFunctionsRec(efuns, ebound_vars, exprs); + CVC4_API_SOLVER_TRY_CATCH_END; } /** @@ -4422,6 +4448,7 @@ void Solver::echo(std::ostream& out, const std::string& str) const */ std::vector Solver::getAssertions(void) const { + CVC4_API_SOLVER_TRY_CATCH_BEGIN; std::vector assertions = d_smtEngine->getAssertions(); /* Can not use * return std::vector(assertions.begin(), assertions.end()); @@ -4432,6 +4459,7 @@ std::vector Solver::getAssertions(void) const res.push_back(Term(this, e)); } return res; + CVC4_API_SOLVER_TRY_CATCH_END; } /** @@ -4439,8 +4467,11 @@ std::vector Solver::getAssertions(void) const */ std::vector> Solver::getAssignment(void) const { - // CHECK: produce-models set - // CHECK: result sat + CVC4_API_SOLVER_TRY_CATCH_BEGIN; + CVC4::ExprManagerScope exmgrs(*(d_exprMgr.get())); + CVC4_API_CHECK(CVC4::options::produceAssignments()) + << "Cannot get assignment unless assignment generation is enabled " + "(try --produce-assignments)"; std::vector> assignment = d_smtEngine->getAssignment(); std::vector> res; for (const auto& p : assignment) @@ -4448,6 +4479,7 @@ std::vector> Solver::getAssignment(void) const res.emplace_back(Term(this, p.first), Term(this, p.second)); } return res; + CVC4_API_SOLVER_TRY_CATCH_END; } /** @@ -4455,8 +4487,12 @@ std::vector> Solver::getAssignment(void) const */ std::string Solver::getInfo(const std::string& flag) const { - // CHECK: flag valid? + CVC4_API_SOLVER_TRY_CATCH_BEGIN; + CVC4_API_CHECK(d_smtEngine->isValidGetInfoFlag(flag)) + << "Unrecognized flag for getInfo."; + return d_smtEngine->getInfo(flag).toString(); + CVC4_API_SOLVER_TRY_CATCH_END; } /** @@ -4464,9 +4500,10 @@ std::string Solver::getInfo(const std::string& flag) const */ std::string Solver::getOption(const std::string& option) const { - // CHECK: option exists? + CVC4_API_SOLVER_TRY_CATCH_BEGIN; SExpr res = d_smtEngine->getOption(option); return res.toString(); + CVC4_API_SOLVER_TRY_CATCH_END; } /** @@ -4474,9 +4511,18 @@ std::string Solver::getOption(const std::string& option) const */ std::vector Solver::getUnsatAssumptions(void) const { - // CHECK: incremental? - // CHECK: option produce-unsat-assumptions set? - // CHECK: last check sat/valid result is unsat/invalid + CVC4_API_SOLVER_TRY_CATCH_BEGIN; + CVC4::ExprManagerScope exmgrs(*(d_exprMgr.get())); + CVC4_API_CHECK(CVC4::options::incrementalSolving()) + << "Cannot get unsat assumptions unless incremental solving is enabled " + "(try --incremental)"; + CVC4_API_CHECK(CVC4::options::unsatAssumptions()) + << "Cannot get unsat assumptions unless explicitly enabled " + "(try --produce-unsat-assumptions)"; + CVC4_API_CHECK(d_smtEngine->getSmtMode() + == SmtEngine::SmtMode::SMT_MODE_UNSAT) + << "Cannot get unsat assumptions unless in unsat mode."; + std::vector uassumptions = d_smtEngine->getUnsatAssumptions(); /* Can not use * return std::vector(uassumptions.begin(), uassumptions.end()); @@ -4487,6 +4533,7 @@ std::vector Solver::getUnsatAssumptions(void) const res.push_back(Term(this, e)); } return res; + CVC4_API_SOLVER_TRY_CATCH_END; } /** @@ -4494,7 +4541,14 @@ std::vector Solver::getUnsatAssumptions(void) const */ std::vector Solver::getUnsatCore(void) const { - // CHECK: result unsat? + CVC4_API_SOLVER_TRY_CATCH_BEGIN; + CVC4::ExprManagerScope exmgrs(*(d_exprMgr.get())); + CVC4_API_CHECK(CVC4::options::unsatCores()) + << "Cannot get unsat core unless explicitly enabled " + "(try --produce-unsat-cores)"; + CVC4_API_CHECK(d_smtEngine->getSmtMode() + == SmtEngine::SmtMode::SMT_MODE_UNSAT) + << "Cannot get unsat core unless in unsat mode."; UnsatCore core = d_smtEngine->getUnsatCore(); /* Can not use * return std::vector(core.begin(), core.end()); @@ -4505,6 +4559,7 @@ std::vector Solver::getUnsatCore(void) const res.push_back(Term(this, e)); } return res; + CVC4_API_SOLVER_TRY_CATCH_END; } /** @@ -4512,10 +4567,10 @@ std::vector Solver::getUnsatCore(void) const */ Term Solver::getValue(Term term) const { - // CHECK: - // NodeManager::fromExprManager(d_exprMgr) - // == NodeManager::fromExprManager(expr.getExprManager()) + CVC4_API_SOLVER_TRY_CATCH_BEGIN; + CVC4_API_SOLVER_CHECK_TERM(term); return Term(this, d_smtEngine->getValue(*term.d_expr)); + CVC4_API_SOLVER_TRY_CATCH_END; } /** @@ -4523,17 +4578,25 @@ Term Solver::getValue(Term term) const */ std::vector Solver::getValue(const std::vector& terms) const { - // CHECK: - // for e in exprs: - // NodeManager::fromExprManager(d_exprMgr) - // == NodeManager::fromExprManager(e.getExprManager()) + CVC4_API_SOLVER_TRY_CATCH_BEGIN; + CVC4::ExprManagerScope exmgrs(*(d_exprMgr.get())); + CVC4_API_CHECK(CVC4::options::produceModels()) + << "Cannot get value unless model generation is enabled " + "(try --produce-models)"; + CVC4_API_CHECK(d_smtEngine->getSmtMode() + != SmtEngine::SmtMode::SMT_MODE_UNSAT) + << "Cannot get value when in unsat mode."; std::vector res; - for (const Term& t : terms) + for (size_t i = 0, n = terms.size(); i < n; ++i) { + CVC4_API_ARG_AT_INDEX_CHECK_EXPECTED( + this == terms[i].d_solver, "term", terms[i], i) + << "term associated to this solver object"; /* Can not use emplace_back here since constructor is private. */ - res.push_back(Term(this, d_smtEngine->getValue(*t.d_expr))); + res.push_back(Term(this, d_smtEngine->getValue(*terms[i].d_expr))); } return res; + CVC4_API_SOLVER_TRY_CATCH_END; } /** @@ -4558,8 +4621,16 @@ void Solver::pop(uint32_t nscopes) const void Solver::printModel(std::ostream& out) const { - // CHECK: produce-models? + CVC4_API_SOLVER_TRY_CATCH_BEGIN; + CVC4::ExprManagerScope exmgrs(*(d_exprMgr.get())); + CVC4_API_CHECK(CVC4::options::produceModels()) + << "Cannot get value unless model generation is enabled " + "(try --produce-models)"; + CVC4_API_CHECK(d_smtEngine->getSmtMode() + != SmtEngine::SmtMode::SMT_MODE_UNSAT) + << "Cannot get value when in unsat mode."; out << *d_smtEngine->getModel(); + CVC4_API_SOLVER_TRY_CATCH_END; } /** @@ -4583,22 +4654,11 @@ void Solver::push(uint32_t nscopes) const /** * ( reset-assertions ) */ -void Solver::resetAssertions(void) const { d_smtEngine->resetAssertions(); } - -// TODO: issue #2781 -void Solver::setLogicHelper(const std::string& logic) const +void Solver::resetAssertions(void) const { - CVC4_API_CHECK(!d_smtEngine->isFullyInited()) - << "Invalid call to 'setLogic', solver is already fully initialized"; - try - { - CVC4::LogicInfo logic_info(logic); - d_smtEngine->setLogic(logic_info); - } - catch (CVC4::IllegalArgumentException& e) - { - throw CVC4ApiException(e.getMessage()); - } + CVC4_API_SOLVER_TRY_CATCH_BEGIN; + d_smtEngine->resetAssertions(); + CVC4_API_SOLVER_TRY_CATCH_END; } /** @@ -4606,6 +4666,7 @@ void Solver::setLogicHelper(const std::string& logic) const */ void Solver::setInfo(const std::string& keyword, const std::string& value) const { + CVC4_API_SOLVER_TRY_CATCH_BEGIN; CVC4_API_ARG_CHECK_EXPECTED( keyword == "source" || keyword == "category" || keyword == "difficulty" || keyword == "filename" || keyword == "license" || keyword == "name" @@ -4625,12 +4686,21 @@ void Solver::setInfo(const std::string& keyword, const std::string& value) const << "'sat', 'unsat' or 'unknown'"; d_smtEngine->setInfo(keyword, value); + CVC4_API_SOLVER_TRY_CATCH_END; } /** * ( set-logic ) */ -void Solver::setLogic(const std::string& logic) const { setLogicHelper(logic); } +void Solver::setLogic(const std::string& logic) const +{ + CVC4_API_SOLVER_TRY_CATCH_BEGIN; + CVC4_API_CHECK(!d_smtEngine->isFullyInited()) + << "Invalid call to 'setLogic', solver is already fully initialized"; + CVC4::LogicInfo logic_info(logic); + d_smtEngine->setLogic(logic_info); + CVC4_API_SOLVER_TRY_CATCH_END; +} /** * ( set-option