From 3f1ed7bbaff5dfb7b32217ba3c11e9d9a697045b Mon Sep 17 00:00:00 2001 From: Andrew Reynolds Date: Tue, 28 May 2024 11:00:37 -0500 Subject: [PATCH 01/35] Fix issues related to error handling in the parser (#10818) Fixes the first two issues from https://github.com/cvc5/cvc5/issues/10813. --- src/api/cpp/cvc5.cpp | 24 +++++++++++++++---- src/parser/smt2/smt2_term_parser.cpp | 10 +++++--- test/regress/cli/CMakeLists.txt | 2 ++ .../cli/regress0/parser/issue10813-1.smt2 | 11 +++++++++ .../cli/regress0/parser/issue10813-2.smt2 | 9 +++++++ test/unit/api/cpp/term_black.cpp | 3 +++ test/unit/api/java/TermTest.java | 3 +++ test/unit/api/python/test_term.py | 5 ++++ 8 files changed, 59 insertions(+), 8 deletions(-) create mode 100644 test/regress/cli/regress0/parser/issue10813-1.smt2 create mode 100644 test/regress/cli/regress0/parser/issue10813-2.smt2 diff --git a/src/api/cpp/cvc5.cpp b/src/api/cpp/cvc5.cpp index c755af5de96..8cc85d3182c 100644 --- a/src/api/cpp/cvc5.cpp +++ b/src/api/cpp/cvc5.cpp @@ -5372,9 +5372,23 @@ Term TermManager::mkRealOrIntegerFromStrHelper(const std::string& s, bool isInt) //////// all checks before this line try { - internal::Rational r = s.find('/') != std::string::npos - ? internal::Rational(s) - : internal::Rational::fromDecimal(s); + internal::Rational r; + size_t spos = s.find('/'); + if (spos != std::string::npos) + { + // Ensure the denominator contains a non-zero digit. We catch this here to + // avoid a floating point exception in GMP. This exception will be caught + // and given the standard error message below. + if (s.find_first_not_of('0', spos + 1) == std::string::npos) + { + throw std::invalid_argument("Zero denominator encountered"); + } + r = internal::Rational(s); + } + else + { + r = internal::Rational::fromDecimal(s); + } return TermManager::mkRationalValHelper(r, isInt); } catch (const std::invalid_argument& e) @@ -5382,8 +5396,8 @@ Term TermManager::mkRealOrIntegerFromStrHelper(const std::string& s, bool isInt) /* Catch to throw with a more meaningful error message. To be caught in * enclosing CVC5_API_TRY_CATCH_* block to throw CVC5ApiException. */ std::stringstream message; - message << "Cannot construct Real or Int from string argument '" << s << "'" - << std::endl; + message << "Cannot construct Real or Int from string argument '" << s + << "'"; throw std::invalid_argument(message.str()); } } diff --git a/src/parser/smt2/smt2_term_parser.cpp b/src/parser/smt2/smt2_term_parser.cpp index 2f17e2ce1f6..b08251bbd79 100644 --- a/src/parser/smt2/smt2_term_parser.cpp +++ b/src/parser/smt2/smt2_term_parser.cpp @@ -1002,17 +1002,21 @@ uint32_t Smt2TermParser::parseIntegerNumeral() uint32_t Smt2TermParser::tokenStrToUnsigned() { // forbid leading zeroes if in strict mode + std::string token = d_lex.tokenStr(); if (d_lex.isStrict()) { - std::string token = d_lex.tokenStr(); if (token.size() > 1 && token[0] == '0') { - d_lex.parseError("Numeral with leading zeroes are forbidden"); + d_lex.parseError("Numerals with leading zeroes are forbidden"); } } + if (token.size() > 1 && token[0] == '-') + { + d_lex.parseError("Negative numerals are forbidden in indices"); + } uint32_t result; std::stringstream ss; - ss << d_lex.tokenStr(); + ss << token; ss >> result; return result; } diff --git a/test/regress/cli/CMakeLists.txt b/test/regress/cli/CMakeLists.txt index fa6040804e4..cf401193d59 100644 --- a/test/regress/cli/CMakeLists.txt +++ b/test/regress/cli/CMakeLists.txt @@ -1151,6 +1151,8 @@ set(regress_0_tests regress0/parser/get-value-empty-err.smt2 regress0/parser/global-dec-cli.smt2 regress0/parser/issue10723-divisible0.smt2 + regress0/parser/issue10813-1.smt2 + regress0/parser/issue10813-2.smt2 regress0/parser/issue5163.smt2 regress0/parser/issue6908-get-value-uc.smt2 regress0/parser/issue7274.smt2 diff --git a/test/regress/cli/regress0/parser/issue10813-1.smt2 b/test/regress/cli/regress0/parser/issue10813-1.smt2 new file mode 100644 index 00000000000..af415225aa3 --- /dev/null +++ b/test/regress/cli/regress0/parser/issue10813-1.smt2 @@ -0,0 +1,11 @@ +; DISABLE-TESTER: dump +; SCRUBBER: grep -o 'Cannot construct Real or Int from string argument' +; EXPECT: Cannot construct Real or Int from string argument +; EXIT: 1 +(set-logic QF_LRA) +(declare-const x Real) +(declare-const y Real) +(assert (> x 1.5)) +(assert (< y 3.5)) +(assert (= (+ x y) 5/0)) +(check-sat) diff --git a/test/regress/cli/regress0/parser/issue10813-2.smt2 b/test/regress/cli/regress0/parser/issue10813-2.smt2 new file mode 100644 index 00000000000..971492d86a4 --- /dev/null +++ b/test/regress/cli/regress0/parser/issue10813-2.smt2 @@ -0,0 +1,9 @@ +; DISABLE-TESTER: dump +; SCRUBBER: grep -o 'Negative numerals are forbidden in indices' +; EXPECT: Negative numerals are forbidden in indices +; EXIT: 1 +(set-logic QF_BV) +(declare-const x (_ BitVec 32)) +(declare-const y (_ BitVec 32)) +(assert (= (bvadd x y) (_ bv10 -2))) +(check-sat) diff --git a/test/unit/api/cpp/term_black.cpp b/test/unit/api/cpp/term_black.cpp index beb254d4c2d..f673ec9870d 100644 --- a/test/unit/api/cpp/term_black.cpp +++ b/test/unit/api/cpp/term_black.cpp @@ -844,6 +844,9 @@ TEST_F(TestApiBlackTerm, getReal) ASSERT_EQ("18446744073709551617/1", real9.getRealValue()); ASSERT_EQ("23432343/10000", real10.getRealValue()); + + ASSERT_THROW(d_tm.mkReal("1/0"), CVC5ApiException); + ASSERT_THROW(d_tm.mkReal("2/0000"), CVC5ApiException); } TEST_F(TestApiBlackTerm, getConstArrayBase) diff --git a/test/unit/api/java/TermTest.java b/test/unit/api/java/TermTest.java index efbef443071..19bfab7a601 100644 --- a/test/unit/api/java/TermTest.java +++ b/test/unit/api/java/TermTest.java @@ -814,6 +814,9 @@ void getReal() throws CVC5ApiException assertEquals("1/18446744073709551617", Utils.getRational(real8.getRealValue())); assertEquals("18446744073709551617/1", Utils.getRational(real9.getRealValue())); assertEquals("23432343/10000", Utils.getRational(real10.getRealValue())); + + assertThrows(CVC5ApiException.class, () -> d_tm.mkReal("1/0")); + assertThrows(CVC5ApiException.class, () -> d_tm.mkReal("2/0000")); } @Test diff --git a/test/unit/api/python/test_term.py b/test/unit/api/python/test_term.py index f8c8cf6ba37..c0292411a92 100644 --- a/test/unit/api/python/test_term.py +++ b/test/unit/api/python/test_term.py @@ -1136,6 +1136,11 @@ def test_get_real(tm): assert Fraction("0.3") == real_decimal.getRealValue() assert Fraction(0.3) == Fraction(5404319552844595, 18014398509481984) assert Fraction(0.3) != real_decimal.getRealValue() + + with pytest.raises(RuntimeError): + tm.mkReal("1/0") + with pytest.raises(RuntimeError): + tm.mkReal("2/0000") def test_get_boolean(tm): From 36b0d146f5c15fe405cf175153bf4d0ba458fbd4 Mon Sep 17 00:00:00 2001 From: Andrew Reynolds Date: Tue, 28 May 2024 11:44:08 -0500 Subject: [PATCH 02/35] Disable dsl-proof on two regressions (#10819) Fixes one of the issues in the nightlies. --- test/regress/cli/regress1/quantifiers/qcft-smtlib3dbc51.smt2 | 1 + test/regress/cli/regress1/quantifiers/smtlibe99bbe.smt2 | 1 + 2 files changed, 2 insertions(+) diff --git a/test/regress/cli/regress1/quantifiers/qcft-smtlib3dbc51.smt2 b/test/regress/cli/regress1/quantifiers/qcft-smtlib3dbc51.smt2 index d659686ca80..a0b17eb7840 100644 --- a/test/regress/cli/regress1/quantifiers/qcft-smtlib3dbc51.smt2 +++ b/test/regress/cli/regress1/quantifiers/qcft-smtlib3dbc51.smt2 @@ -1,5 +1,6 @@ ; COMMAND-LINE: --cbqi-tconstraint --ieval=off ; EXPECT: unsat +; DISABLE-TESTER: dsl-proof (set-logic AUFLIRA) (set-info :source |http://proval.lri.fr/why-benchmarks |) (set-info :smt-lib-version 2.6) diff --git a/test/regress/cli/regress1/quantifiers/smtlibe99bbe.smt2 b/test/regress/cli/regress1/quantifiers/smtlibe99bbe.smt2 index ce36af5fdc0..c2c28478455 100644 --- a/test/regress/cli/regress1/quantifiers/smtlibe99bbe.smt2 +++ b/test/regress/cli/regress1/quantifiers/smtlibe99bbe.smt2 @@ -1,5 +1,6 @@ ; COMMAND-LINE: --relevant-triggers ; EXPECT: unsat +; DISABLE-TESTER: dsl-proof (set-logic AUFLIRA) (set-info :status unsat) (declare-sort Unit 0) From 8467bde055e06e2fea233bfc64f35cd033ab0ce7 Mon Sep 17 00:00:00 2001 From: Andrew Reynolds Date: Tue, 28 May 2024 12:43:49 -0500 Subject: [PATCH 03/35] Add support for more arithmetic rewrites in RARE (#10808) Note this also adds support for RARE for several new arithmetic operators, including 3 that are specific to cvc5 (/_total, mod_total and div_total). It also corrects the syntax for the cvc5-specific string operators tolower and toupper, and adds parsing support for re.loop. --- include/cvc5/cvc5_proof_rule.h | 36 ++++++++++++++++++++++++++++++---- src/rewriter/mkrewrites.py | 2 +- src/rewriter/node.py | 10 ++++++++-- src/rewriter/rw_parser.py | 2 +- src/theory/arith/rewrites | 23 +++++++++++++++++++--- 5 files changed, 62 insertions(+), 11 deletions(-) diff --git a/include/cvc5/cvc5_proof_rule.h b/include/cvc5/cvc5_proof_rule.h index 2218a95a4d3..59bcfa58f15 100644 --- a/include/cvc5/cvc5_proof_rule.h +++ b/include/cvc5/cvc5_proof_rule.h @@ -2512,8 +2512,20 @@ enum ENUM(ProofRewriteRule) : uint32_t EVALUE(ARITH_MUL_ONE), /** Auto-generated from RARE rule arith-mul-zero */ EVALUE(ARITH_MUL_ZERO), - /** Auto-generated from RARE rule arith-int-div-one */ - EVALUE(ARITH_INT_DIV_ONE), + /** Auto-generated from RARE rule arith-div-total */ + EVALUE(ARITH_DIV_TOTAL), + /** Auto-generated from RARE rule arith-int-div-total */ + EVALUE(ARITH_INT_DIV_TOTAL), + /** Auto-generated from RARE rule arith-int-div-total-one */ + EVALUE(ARITH_INT_DIV_TOTAL_ONE), + /** Auto-generated from RARE rule arith-int-div-total-zero */ + EVALUE(ARITH_INT_DIV_TOTAL_ZERO), + /** Auto-generated from RARE rule arith-int-mod-total */ + EVALUE(ARITH_INT_MOD_TOTAL), + /** Auto-generated from RARE rule arith-int-mod-total-one */ + EVALUE(ARITH_INT_MOD_TOTAL_ONE), + /** Auto-generated from RARE rule arith-int-mod-total-zero */ + EVALUE(ARITH_INT_MOD_TOTAL_ZERO), /** Auto-generated from RARE rule arith-neg-neg-one */ EVALUE(ARITH_NEG_NEG_ONE), /** Auto-generated from RARE rule arith-elim-uminus */ @@ -2530,8 +2542,10 @@ enum ENUM(ProofRewriteRule) : uint32_t EVALUE(ARITH_LEQ_NORM), /** Auto-generated from RARE rule arith-geq-tighten */ EVALUE(ARITH_GEQ_TIGHTEN), - /** Auto-generated from RARE rule arith-geq-norm */ - EVALUE(ARITH_GEQ_NORM), + /** Auto-generated from RARE rule arith-geq-norm1 */ + EVALUE(ARITH_GEQ_NORM1), + /** Auto-generated from RARE rule arith-geq-norm2 */ + EVALUE(ARITH_GEQ_NORM2), /** Auto-generated from RARE rule arith-refl-leq */ EVALUE(ARITH_REFL_LEQ), /** Auto-generated from RARE rule arith-refl-lt */ @@ -2540,6 +2554,10 @@ enum ENUM(ProofRewriteRule) : uint32_t EVALUE(ARITH_REFL_GEQ), /** Auto-generated from RARE rule arith-refl-gt */ EVALUE(ARITH_REFL_GT), + /** Auto-generated from RARE rule arith-real-eq-elim */ + EVALUE(ARITH_REAL_EQ_ELIM), + /** Auto-generated from RARE rule arith-int-eq-elim */ + EVALUE(ARITH_INT_EQ_ELIM), /** Auto-generated from RARE rule arith-plus-flatten */ EVALUE(ARITH_PLUS_FLATTEN), /** Auto-generated from RARE rule arith-mult-flatten */ @@ -2550,6 +2568,16 @@ enum ENUM(ProofRewriteRule) : uint32_t EVALUE(ARITH_PLUS_CANCEL1), /** Auto-generated from RARE rule arith-plus-cancel2 */ EVALUE(ARITH_PLUS_CANCEL2), + /** Auto-generated from RARE rule arith-abs-elim */ + EVALUE(ARITH_ABS_ELIM), + /** Auto-generated from RARE rule arith-to-real-elim */ + EVALUE(ARITH_TO_REAL_ELIM), + /** Auto-generated from RARE rule arith-to-int-elim-to-real */ + EVALUE(ARITH_TO_INT_ELIM_TO_REAL), + /** Auto-generated from RARE rule arith-div-elim-to-real1 */ + EVALUE(ARITH_DIV_ELIM_TO_REAL1), + /** Auto-generated from RARE rule arith-div-elim-to-real2 */ + EVALUE(ARITH_DIV_ELIM_TO_REAL2), /** Auto-generated from RARE rule array-read-over-write */ EVALUE(ARRAY_READ_OVER_WRITE), /** Auto-generated from RARE rule array-read-over-write2 */ diff --git a/src/rewriter/mkrewrites.py b/src/rewriter/mkrewrites.py index 244d0fc4f21..bb429105624 100644 --- a/src/rewriter/mkrewrites.py +++ b/src/rewriter/mkrewrites.py @@ -93,7 +93,7 @@ def gen_mk_node(defns, expr): elif isinstance(expr, App): args = ",".join(gen_mk_node(defns, child) for child in expr.children) if expr.op in {Op.EXTRACT, Op.REPEAT, Op.ZERO_EXTEND, Op.SIGN_EXTEND, - Op.ROTATE_LEFT, Op.ROTATE_RIGHT, Op.INT_TO_BV}: + Op.ROTATE_LEFT, Op.ROTATE_RIGHT, Op.INT_TO_BV, Op.REGEXP_LOOP}: args = f'nm->mkConst(GenericOp(Kind::{gen_kind(expr.op)})),' + args return f'nm->mkNode(Kind::APPLY_INDEXED_SYMBOLIC, {{ {args} }})' return f'nm->mkNode(Kind::{gen_kind(expr.op)}, {{ {args} }})' diff --git a/src/rewriter/node.py b/src/rewriter/node.py index a99748c3bea..e76d7245440 100644 --- a/src/rewriter/node.py +++ b/src/rewriter/node.py @@ -116,14 +116,19 @@ def __new__(cls, symbol, kind): SUB = ('-', 'SUB') MULT = ('*', 'MULT') INT_DIV = ('div', 'INTS_DIVISION') + INT_DIV_TOTAL = ('div_total', 'INTS_DIVISION_TOTAL') DIV = ('/', 'DIVISION') + DIV_TOTAL = ('/_total', 'DIVISION_TOTAL') MOD = ('mod', 'INTS_MODULUS') + MOD_TOTAL = ('mod_total', 'INTS_MODULUS_TOTAL') ABS = ('abs', 'ABS') LT = ('<', 'LT') GT = ('>', 'GT') LEQ = ('<=', 'LEQ') GEQ = ('>=', 'GEQ') POW2 = ('int.pow2', 'POW2') + TO_INT = ('to_int', 'TO_INTEGER') + TO_REAL = ('to_real', 'TO_REAL') INT_ISPOW2 = ('int.ispow2', 'INTS_ISPOW2') # Backdoor for some bv rewrites INT_LENGTH = ('int.log2', 'INTS_LOG2') # Backdoor for some bv rewrites @@ -170,8 +175,8 @@ def __new__(cls, symbol, kind): STRING_STOI = ('str.to_int', 'STRING_STOI') STRING_TO_CODE = ('str.to_code', 'STRING_TO_CODE') STRING_FROM_CODE = ('str.from_code', 'STRING_FROM_CODE') - STRING_TOLOWER = ('str.tolower', 'STRING_TOLOWER') - STRING_TOUPPER = ('str.toupper', 'STRING_TOUPPER') + STRING_TO_LOWER = ('str.to_lower', 'STRING_TO_LOWER') + STRING_TO_UPPER = ('str.to_upper', 'STRING_TO_UPPER') STRING_REV = ('str.rev', 'STRING_REV') SEQ_UNIT = ('seq.unit', 'SEQ_UNIT') @@ -187,6 +192,7 @@ def __new__(cls, symbol, kind): REGEXP_OPT = ('re.opt', 'REGEXP_OPT') REGEXP_RANGE = ('re.range', 'REGEXP_RANGE') REGEXP_COMPLEMENT = ('re.comp', 'REGEXP_COMPLEMENT') + REGEXP_LOOP = ('re.loop', 'REGEXP_LOOP') REGEXP_NONE = (None, 'REGEXP_NONE') # Handled as constants REGEXP_ALL = (None, 'REGEXP_ALL') diff --git a/src/rewriter/rw_parser.py b/src/rewriter/rw_parser.py index dd07ebe88cb..ab5dd2ce459 100644 --- a/src/rewriter/rw_parser.py +++ b/src/rewriter/rw_parser.py @@ -75,7 +75,7 @@ def bv_to_int(self, s): return int(s[2:]) def symbol(self): - special_chars = '=' + '_' + '+' + '-' + '<' + '>' + '*' + '.' + '@' + special_chars = '=' + '_' + '+' + '-' + '<' + '>' + '*' + '.' + '@' + '/' return pp.Word(pp.alphas + special_chars, pp.alphanums + special_chars) def app_action(self, s, l, t): diff --git a/src/theory/arith/rewrites b/src/theory/arith/rewrites index c2ce57a7cd0..1aaaef22fb2 100644 --- a/src/theory/arith/rewrites +++ b/src/theory/arith/rewrites @@ -9,9 +9,15 @@ (define-rule arith-mul-one ((t ? :list) (s ? :list)) (* t 1 s) (* t s)) (define-rule arith-mul-zero ((t ? :list) (s ? :list)) (* t 0 s) 0) -;(define-rule arith-div-one ((t ?)) (/ t 1) t) +(define-cond-rule arith-div-total ((t ?) (s ?)) (not (= s 0)) (/ t s) (/_total t s)) -(define-rule arith-int-div-one ((t Int)) (div t 1) t) +(define-cond-rule arith-int-div-total ((t Int) (s Int)) (not (= s 0)) (div t s) (div_total t s)) +(define-rule arith-int-div-total-one ((t Int)) (div_total t 1) t) +(define-rule arith-int-div-total-zero ((t Int)) (div_total t 0) 0) + +(define-cond-rule arith-int-mod-total ((t Int) (s Int)) (not (= s 0)) (mod t s) (mod_total t s)) +(define-rule arith-int-mod-total-one ((t Int)) (mod_total t 1) 0) +(define-rule arith-int-mod-total-zero ((t Int)) (mod_total t 0) 0) (define-rule arith-neg-neg-one ((t ?)) (* (- 1) (* (- 1) t)) t) @@ -26,13 +32,18 @@ (define-rule arith-geq-tighten ((t Int) (s Int)) (not (>= t s)) (>= s (+ t 1))) -(define-rule arith-geq-norm ((t ?) (s ?)) (>= t s) (>= (- t s) 0)) +(define-rule arith-geq-norm1 ((t ?) (s ?)) (>= t s) (>= (- t s) 0)) + +(define-rule arith-geq-norm2 ((t ?) (s ?)) (>= t s) (<= (- t) (- s))) (define-rule arith-refl-leq ((t ?)) (<= t t) true) (define-rule arith-refl-lt ((t ?)) (< t t) false) (define-rule arith-refl-geq ((t ?)) (>= t t) true) (define-rule arith-refl-gt ((t ?)) (> t t) false) +(define-rule arith-real-eq-elim ((t Real) (s Real)) (= t s) (and (>= t s) (<= t s))) +(define-rule arith-int-eq-elim ((t Int) (s Int)) (= t s) (and (>= t s) (<= t s))) + ;(define-cond-rule arith-geq-contra ((t ?) (s ?)) (not (>= (- t s) 0)) (>= t s) false) ;(define-cond-rule arith-eq-contra ((t ?) (s ?)) (not (= (- t s) 0)) (= t s) false) @@ -51,3 +62,9 @@ (define-rule* arith-plus-cancel1 ((t ? :list) (x ?) (s ? :list) (r ? :list)) (+ t x s (* (- 1) x) r) (+ t s r)) (define-rule* arith-plus-cancel2 ((t ? :list) (x ?) (s ? :list) (r ? :list)) (+ t (* (- 1) x) s x r) (+ t s r)) +(define-rule arith-abs-elim ((x ?)) (abs x) (ite (< x 0) (- x) x)) + +(define-rule arith-to-real-elim ((x Real)) (to_real x) x) +(define-rule arith-to-int-elim-to-real ((x ?)) (to_int (to_real x)) (to_int x)) +(define-rule arith-div-elim-to-real1 ((x ?) (y ?)) (/ (to_real x) y) (/ x y)) +(define-rule arith-div-elim-to-real2 ((x ?) (y ?)) (/ x (to_real y)) (/ x y)) From e7e5694521e1c9602d17674c0eb40a9de1ad7002 Mon Sep 17 00:00:00 2001 From: Andrew Reynolds Date: Tue, 28 May 2024 13:20:41 -0500 Subject: [PATCH 04/35] Refresh assertions for get-abduct and get-interpolant (#10252) This ensures e.g. variables that can be eliminated during preprocessing don't show up in the goal/grammar for abduction and interpolation. There are pros and cons to doing this, however it should be noted that this is a more consistent behavior since calling check-sat prior to a call to get-abduct would have the same effect. Fixes #10788. --- src/smt/solver_engine.cpp | 4 +++ test/regress/cli/CMakeLists.txt | 1 + .../regress1/issue10788-refresh-a-interp.smt2 | 25 +++++++++++++++++++ 3 files changed, 30 insertions(+) create mode 100644 test/regress/cli/regress1/issue10788-refresh-a-interp.smt2 diff --git a/src/smt/solver_engine.cpp b/src/smt/solver_engine.cpp index 0cbe718f165..d07b503fc65 100644 --- a/src/smt/solver_engine.cpp +++ b/src/smt/solver_engine.cpp @@ -1890,6 +1890,8 @@ Node SolverEngine::getQuantifierElimination(Node q, bool doFull) Node SolverEngine::getInterpolant(const Node& conj, const TypeNode& grammarType) { beginCall(true); + // Analogous to getAbduct, ensure that assertions are current. + d_smtDriver->refreshAssertions(); std::vector axioms = getSubstitutedAssertions(); // expand definitions in the conjecture as well Node conje = d_smtSolver->getPreprocessor()->applySubstitutions(conj); @@ -1926,6 +1928,8 @@ Node SolverEngine::getInterpolantNext() Node SolverEngine::getAbduct(const Node& conj, const TypeNode& grammarType) { beginCall(true); + // ensure that assertions are current + d_smtDriver->refreshAssertions(); std::vector axioms = getSubstitutedAssertions(); // expand definitions in the conjecture as well Node conje = d_smtSolver->getPreprocessor()->applySubstitutions(conj); diff --git a/test/regress/cli/CMakeLists.txt b/test/regress/cli/CMakeLists.txt index cf401193d59..fbef0bdbfd6 100644 --- a/test/regress/cli/CMakeLists.txt +++ b/test/regress/cli/CMakeLists.txt @@ -2431,6 +2431,7 @@ set(regress_1_tests regress1/interpolant-unk-570.smt2 regress1/ite5.smt2 regress1/issue10750-zll-repeat.smt2 + regress1/issue10788-refresh-a-interp.smt2 regress1/issue3970-nl-ext-purify.smt2 regress1/issue3990-sort-inference.smt2 regress1/issue4273-ext-rew-cache.smt2 diff --git a/test/regress/cli/regress1/issue10788-refresh-a-interp.smt2 b/test/regress/cli/regress1/issue10788-refresh-a-interp.smt2 new file mode 100644 index 00000000000..d13fd450aa6 --- /dev/null +++ b/test/regress/cli/regress1/issue10788-refresh-a-interp.smt2 @@ -0,0 +1,25 @@ +; COMMAND-LINE: --produce-interpolants -q +; SCRUBBER: grep -v -E '(\(define-fun)' +; EXIT: 0 +(set-option :produce-interpolants true) +(set-option :incremental true) +(set-logic QF_LIA) +(declare-fun i3 () Int) +(declare-fun i4 () Int) +(declare-fun i7 () Int) +(declare-fun i8 () Int) +(declare-fun i9 () Int) +(declare-fun i10 () Int) +(declare-fun i11 () Int) +(declare-fun i12 () Int) +(declare-fun i13 () Int) +(declare-fun i14 () Int) +(assert (= i4 2)) +(assert (> (- i14) 0)) +(assert (= i3 2)) +(assert (= i7 0)) +(assert (= i9 0)) +(assert (= i11 0)) +(assert (= i13 0)) +(get-interpolant A (or (distinct i4 2) (distinct i12 (+ i11 i10)) (distinct i10 (+ i9 i8)) (distinct i8 (+ i7 1)) (distinct i14 (+ i13 i12)) (distinct i3 2))) +(exit) From 6a683925f2652fc8f246ee81084540acac3bd4f9 Mon Sep 17 00:00:00 2001 From: Andrew Reynolds Date: Tue, 28 May 2024 16:10:04 -0500 Subject: [PATCH 05/35] Simplify ENCODE_PRED_TRANSFORM (#10799) This PR does two things: (1) It makes a necessary change to preprocessing terms before RARE reconstruction that applies ACI_NORM recursively to all subterms. This is required for proving rewrites involving large string constants, which after "unflattening" are not in ACI normal form. (2) It simplifies the definition of ENCODE_PRED_TRANSFORM to refer to the *small step* rewrites of this utility, not the large step. It make the converter proof producing for the small steps using a conversion proof generator. With this change `ENCODE_PRED_TRANSFORM` is now essentially equivalent to `REFL`. The new name of this rule is `ENCODE_EQ_INTRO`. --- include/cvc5/cvc5_proof_rule.h | 23 ++++--- proofs/alf/cvc5/Cvc5.smt3 | 30 --------- src/api/cpp/cvc5_proof_rule_template.cpp | 2 +- src/proof/alf/alf_printer.cpp | 8 ++- src/proof/lfsc/lfsc_printer.cpp | 2 +- src/rewriter/basic_rewrite_rcons.cpp | 14 +++++ src/rewriter/basic_rewrite_rcons.h | 11 ++++ src/rewriter/rewrite_db_proof_cons.cpp | 48 +++++++++----- src/rewriter/rewrite_db_proof_cons.h | 14 ++++- src/rewriter/rewrite_db_term_process.cpp | 80 +++++++++++++++++++++--- src/rewriter/rewrite_db_term_process.h | 43 ++++++++++++- src/theory/builtin/proof_checker.cpp | 17 ++--- 12 files changed, 213 insertions(+), 79 deletions(-) diff --git a/include/cvc5/cvc5_proof_rule.h b/include/cvc5/cvc5_proof_rule.h index 59bcfa58f15..35c67ac561d 100644 --- a/include/cvc5/cvc5_proof_rule.h +++ b/include/cvc5/cvc5_proof_rule.h @@ -272,19 +272,26 @@ enum ENUM(ProofRule) : uint32_t EVALUE(MACRO_SR_PRED_TRANSFORM), /** * \verbatim embed:rst:leading-asterisk - * **Builtin theory -- Encode predicate transformation** + * **Builtin theory -- Encode equality introduction** * * .. math:: - * \inferrule{F \mid G}{G} + * \inferrule{- \mid t}{t=t'} * - * where :math:`F` and :math:`G` are equivalent up to their encoding in an - * external proof format. This is currently verified by - * :math:`\texttt{RewriteDbNodeConverter::convert}(F) = \texttt{RewriteDbNodeConverter::convert}(G)`. - * This rule can be treated as a no-op when appropriate in external proof - * formats. + * where :math:`t` and :math:`t'` are equivalent up to their encoding in an + * external proof format. + * + * More specifically, it is the case that + * :math:`\texttt{RewriteDbNodeConverter::postConvert}(t) = t;`. + * This conversion method for instance may drop user patterns from quantified + * formulas or change the representation of :math:`t` in a way that is a + * no-op in external proof formats. + * + * Note this rule can be treated as a + * :cpp:enumerator:`REFL ` when appropriate in + * external proof formats. * \endverbatim */ - EVALUE(ENCODE_PRED_TRANSFORM), + EVALUE(ENCODE_EQ_INTRO), /** * \verbatim embed:rst:leading-asterisk * **Builtin theory -- DSL rewrite** diff --git a/proofs/alf/cvc5/Cvc5.smt3 b/proofs/alf/cvc5/Cvc5.smt3 index 9f945f7bdae..f4b4b071d3f 100644 --- a/proofs/alf/cvc5/Cvc5.smt3 +++ b/proofs/alf/cvc5/Cvc5.smt3 @@ -21,36 +21,6 @@ (declare-sort @ho-elim-sort 1) (declare-const @fmf-fun-sort (-> (! Type :var T :implicit) T Type)) -; Converts x to an equivalent form, for ProofRule::ENCODE_TRANSFORM. -; This side condition is used to convert between the native -; representation in cvc5 to the form expected by DSL rewrite rules. -; In particular, we convert: -; - Word constants "ABC" to concatenations of chars (str.++ "A" "B" "C"). -(program encode_transform ((T Type) (S Type) (f (-> S T)) (x T) (y S)) - (T) T - ( - ((encode_transform (f y)) (_ (encode_transform f) (encode_transform y))) - ((encode_transform x) (alf.ite ($is_str_literal x) - ; string constants - ; e.g. "AB" ---> (str.++ "A" "B") - (alf.ite (check_length_gt_one x) - ($str_flatten_word x) - x) - (alf.ite ($is_bin_literal x) - ; bitvector constants - ; e.g. #b0000 ----> (@bv 0 4) - (@bv (alf.to_z x) (alf.len x)) - x))) - ) -) - -(declare-rule encode_pred_transform ((F Bool) (G Bool)) - :premises (F) - :args (G) - :requires (((encode_transform F) (encode_transform G))) - :conclusion G) - - ; evaluate, for all theories (program run_evaluate ((T Type) (S Type) (x T) (y T) (z S) (ys S :list) diff --git a/src/api/cpp/cvc5_proof_rule_template.cpp b/src/api/cpp/cvc5_proof_rule_template.cpp index faf5604c8cf..eb7339b6605 100644 --- a/src/api/cpp/cvc5_proof_rule_template.cpp +++ b/src/api/cpp/cvc5_proof_rule_template.cpp @@ -34,7 +34,7 @@ const char* toString(ProofRule rule) case ProofRule::MACRO_SR_PRED_INTRO: return "MACRO_SR_PRED_INTRO"; case ProofRule::MACRO_SR_PRED_ELIM: return "MACRO_SR_PRED_ELIM"; case ProofRule::MACRO_SR_PRED_TRANSFORM: return "MACRO_SR_PRED_TRANSFORM"; - case ProofRule::ENCODE_PRED_TRANSFORM: return "ENCODE_PRED_TRANSFORM"; + case ProofRule::ENCODE_EQ_INTRO: return "ENCODE_EQ_INTRO"; case ProofRule::ANNOTATION: return "ANNOTATION"; case ProofRule::DSL_REWRITE: return "DSL_REWRITE"; case ProofRule::THEORY_REWRITE: return "THEORY_REWRITE"; diff --git a/src/proof/alf/alf_printer.cpp b/src/proof/alf/alf_printer.cpp index 4b80eb59ea6..c11da452889 100644 --- a/src/proof/alf/alf_printer.cpp +++ b/src/proof/alf/alf_printer.cpp @@ -143,7 +143,7 @@ bool AlfPrinter::isHandled(const ProofNode* pfn) const case ProofRule::INSTANTIATE: case ProofRule::SKOLEMIZE: case ProofRule::ALPHA_EQUIV: - case ProofRule::ENCODE_PRED_TRANSFORM: + case ProofRule::ENCODE_EQ_INTRO: case ProofRule::ACI_NORM: case ProofRule::DSL_REWRITE: return true; case ProofRule::THEORY_REWRITE: @@ -371,6 +371,12 @@ std::string AlfPrinter::getRuleName(const ProofNode* pfn) const ss << id; return ss.str(); } + else if (r == ProofRule::ENCODE_EQ_INTRO) + { + // ENCODE_EQ_INTRO proves (= t (convert t)) from argument t, + // where (convert t) is indistinguishable from t according to the proof. + return "refl"; + } std::string name = toString(r); std::transform(name.begin(), name.end(), name.begin(), [](unsigned char c) { return std::tolower(c); diff --git a/src/proof/lfsc/lfsc_printer.cpp b/src/proof/lfsc/lfsc_printer.cpp index b82f89fbce8..8ed9cf0c2cf 100644 --- a/src/proof/lfsc/lfsc_printer.cpp +++ b/src/proof/lfsc/lfsc_printer.cpp @@ -541,7 +541,7 @@ void LfscPrinter::printProofInternal( Assert(passumeIt != passumeMap.end()); out->printId(passumeIt->second, d_assumpPrefix); } - else if (r == ProofRule::ENCODE_PRED_TRANSFORM) + else if (r == ProofRule::ENCODE_EQ_INTRO) { // just add child visit.push_back(PExpr(cur->getChildren()[0].get())); diff --git a/src/rewriter/basic_rewrite_rcons.cpp b/src/rewriter/basic_rewrite_rcons.cpp index d76b2f6d369..76451af0ea8 100644 --- a/src/rewriter/basic_rewrite_rcons.cpp +++ b/src/rewriter/basic_rewrite_rcons.cpp @@ -118,6 +118,20 @@ bool BasicRewriteRCons::postProve( return success; } +void BasicRewriteRCons::ensureProofForEncodeTransform(CDProof* cdp, + const Node& eq, + const Node& eqi) +{ + ProofRewriteDbNodeConverter rdnc(d_env); + std::shared_ptr pfn = rdnc.convert(eq); + Node equiv = eq.eqNode(eqi); + Assert(pfn->getResult() == equiv); + cdp->addProof(pfn); + Node equivs = eqi.eqNode(eq); + cdp->addStep(equivs, ProofRule::SYMM, {equiv}, {}); + cdp->addStep(eq, ProofRule::EQ_RESOLVE, {eqi, equivs}, {}); +} + bool BasicRewriteRCons::tryRule(CDProof* cdp, Node eq, ProofRule r, diff --git a/src/rewriter/basic_rewrite_rcons.h b/src/rewriter/basic_rewrite_rcons.h index 95907357058..ea558dd93f6 100644 --- a/src/rewriter/basic_rewrite_rcons.h +++ b/src/rewriter/basic_rewrite_rcons.h @@ -80,6 +80,17 @@ class BasicRewriteRCons : protected EnvObj theory::TheoryId tid, MethodId mid, std::vector>& subgoals); + /** + * Add to cdp a proof of eq from free asumption eqi, where eqi is the result + * of term conversion via RewriteDbNodeConverter. + * + * @param cdp The proof to add to. + * @param eq The original equality. + * @param eqi The equality after conversion. + */ + void ensureProofForEncodeTransform(CDProof* cdp, + const Node& eq, + const Node& eqi); /** * Ensure we have a proof for theory rewrite id of eq in cdp. This typically * adds a single THEORY_REWRITE step to cdp. However, for rules with prefix diff --git a/src/rewriter/rewrite_db_proof_cons.cpp b/src/rewriter/rewrite_db_proof_cons.cpp index b49a56e69b3..022e63960cc 100644 --- a/src/rewriter/rewrite_db_proof_cons.cpp +++ b/src/rewriter/rewrite_db_proof_cons.cpp @@ -69,29 +69,49 @@ bool RewriteDbProofCons::prove( // clear the evaluate cache d_evalCache.clear(); Node eq = a.eqNode(b); + Trace("rpc") << "RewriteDbProofCons::prove: " << a << " == " << b + << std::endl; // As a heuristic, always apply CONG if we are an equality between two // binder terms with the same quantifier prefix. if (a.isClosure() && a.getKind() == b.getKind() && a[0] == b[0]) { - Node eqo = eq; - // Ensure the equality is converted, which resolves complications with - // patterns. - Node eqoi = d_rdnc.convert(eqo); - std::vector cargs; - ProofRule cr = expr::getCongRule(eqoi[0], cargs); + // Ensure patterns are removed by calling d_rdnc postConvert (single step). + // We do not apply convert recursively here or else it would e.g. convert + // the entire quantifier body to ACI normal form. + Node ai = d_rdnc.postConvert(a); + Node bi = d_rdnc.postConvert(b); // only apply this to standard binders (those with 2 children) - if (eqoi[0].getNumChildren() == 2) + if (ai.getNumChildren() == 2 && bi.getNumChildren()==2) { - eq = eqoi[0][1].eqNode(eqoi[1][1]); - cdp->addStep(eqoi, cr, {eq}, cargs); - if (eqo != eqoi) + Node eqo = eq; + std::vector transEq; + if (ai!=a) + { + Node aeq = a.eqNode(ai); + cdp->addStep(aeq, ProofRule::ENCODE_EQ_INTRO, {}, {a}); + transEq.push_back(aeq); + } + std::vector cargs; + ProofRule cr = expr::getCongRule(ai, cargs); + eq = ai[1].eqNode(bi[1]); + Node eqConv = ai.eqNode(bi); + cdp->addStep(eqConv, cr, {eq}, cargs); + transEq.push_back(eqConv); + if (bi!=b) { - cdp->addStep(eqo, ProofRule::ENCODE_PRED_TRANSFORM, {eqoi}, {eqo}); + Node beq = b.eqNode(bi); + cdp->addStep(beq, ProofRule::ENCODE_EQ_INTRO, {}, {b}); + Node beqs = bi.eqNode(b); + cdp->addStep(beqs, ProofRule::SYMM, {beq}, {}); + transEq.push_back(beqs); } + if (transEq.size()>1) + { + cdp->addStep(eqo, ProofRule::TRANS, transEq, {}); + } + Trace("rpc") << "- process to " << eq[0] << " == " << eq[1] << std::endl; } } - Trace("rpc") << "RewriteDbProofCons::prove: " << eq[0] << " == " << eq[1] - << std::endl; Trace("rpc-debug") << "- prove basic" << std::endl; // first, try with the basic utility bool success = false; @@ -181,7 +201,7 @@ bool RewriteDbProofCons::proveEq( // if it changed encoding, account for this if (eq != eqi) { - cdp->addStep(eq, ProofRule::ENCODE_PRED_TRANSFORM, {eqi}, {eq}); + d_trrc.ensureProofForEncodeTransform(cdp, eq, eqi); } ensureProofInternal(cdp, eqi, subgoals); AlwaysAssert(cdp->hasStep(eqi)) << eqi; diff --git a/src/rewriter/rewrite_db_proof_cons.h b/src/rewriter/rewrite_db_proof_cons.h index 5c79f52f69d..414dc62a8e6 100644 --- a/src/rewriter/rewrite_db_proof_cons.h +++ b/src/rewriter/rewrite_db_proof_cons.h @@ -46,9 +46,21 @@ class RewriteDbProofCons : protected EnvObj public: RewriteDbProofCons(Env& env, RewriteDb* db); /** - * Prove (= a b) with recursion limit recLimit and step limit stepLimit. + * Prove a = b with recursion limit recLimit and step limit stepLimit. * If cdp is provided, we add a proof for this fact on it. * + * More specifically, the strategy used by this method is: + * 1. Try to prove a=b via THEORY_REWRITE in context TheoryRewriteCtx::PRE_DSL, + * 2. Try to prove a=b via a proof involving RARE rewrites, + * 3. Try to prove a'=b' via a proof involving RARE rewrites, where a' and b' + * are obtained by transforming a and b via RewriteDbNodeConverter. + * 4. Try to prove a=b via THEORY_REWRITE in context + * TheoryRewriteCtx::POST_DSL. + * + * The option --proof-granularity=dsl-rewrite-strict essentially moves step 1 + * after step 3, that is, RARE rewrites are always preferred to + * THEORY_REWRITE. + * * @param cdp The object to add the proof of (= a b) to. * @param a The left hand side of the equality. * @param b The right hand side of the equality. diff --git a/src/rewriter/rewrite_db_term_process.cpp b/src/rewriter/rewrite_db_term_process.cpp index 47cc0875f55..0a7424f0867 100644 --- a/src/rewriter/rewrite_db_term_process.cpp +++ b/src/rewriter/rewrite_db_term_process.cpp @@ -17,6 +17,7 @@ #include "expr/attribute.h" #include "expr/nary_term_util.h" +#include "proof/conv_proof_generator.h" #include "theory/builtin/generic_op.h" #include "theory/bv/theory_bv_utils.h" #include "theory/strings/theory_strings_utils.h" @@ -29,8 +30,10 @@ using namespace cvc5::internal::kind; namespace cvc5::internal { namespace rewriter { -RewriteDbNodeConverter::RewriteDbNodeConverter(NodeManager* nm) - : NodeConverter(nm) +RewriteDbNodeConverter::RewriteDbNodeConverter(NodeManager* nm, + TConvProofGenerator* tpg, + CDProof* p) + : NodeConverter(nm), d_tpg(tpg), d_proof(p) { } @@ -54,11 +57,15 @@ Node RewriteDbNodeConverter::postConvert(Node n) tmp.push_back(c); children.push_back(nm->mkConst(String(tmp))); } - return nm->mkNode(Kind::STRING_CONCAT, children); + Node ret = nm->mkNode(Kind::STRING_CONCAT, children); + recordProofStep(n, ret, ProofRule::EVALUATE); + return ret; } else if (k == Kind::CONST_SEQUENCE) { - return theory::strings::utils::mkConcatForConstSequence(n); + Node ret = theory::strings::utils::mkConcatForConstSequence(n); + recordProofStep(n, ret, ProofRule::ENCODE_EQ_INTRO); + return ret; } else if (k == Kind::CONST_BITVECTOR) { @@ -68,7 +75,9 @@ Node RewriteDbNodeConverter::postConvert(Node n) children.push_back( nm->mkConstInt(Rational(n.getConst().toInteger()))); children.push_back(nm->mkConstInt(Rational(theory::bv::utils::getSize(n)))); - return nm->mkNode(Kind::CONST_BITVECTOR_SYMBOLIC, children); + Node ret = nm->mkNode(Kind::CONST_BITVECTOR_SYMBOLIC, children); + recordProofStep(n, ret, ProofRule::EVALUATE); + return ret; } else if (k == Kind::FORALL) { @@ -76,7 +85,9 @@ Node RewriteDbNodeConverter::postConvert(Node n) if (n.getNumChildren() == 3) { NodeManager* nm = NodeManager::currentNM(); - return nm->mkNode(Kind::FORALL, n[0], n[1]); + Node ret = nm->mkNode(Kind::FORALL, n[0], n[1]); + recordProofStep(n, ret, ProofRule::ENCODE_EQ_INTRO); + return ret; } } // convert indexed operators to symbolic @@ -87,10 +98,13 @@ Node RewriteDbNodeConverter::postConvert(Node n) GenericOp::getIndicesForOperator(k, n.getOperator()); indices.insert(indices.begin(), nm->mkConst(GenericOp(k))); indices.insert(indices.end(), n.begin(), n.end()); - return nm->mkNode(Kind::APPLY_INDEXED_SYMBOLIC, indices); + Node ret = nm->mkNode(Kind::APPLY_INDEXED_SYMBOLIC, indices); + recordProofStep(n, ret, ProofRule::ENCODE_EQ_INTRO); + return ret; } - - return n; + Node nacc = expr::getACINormalForm(n); + recordProofStep(n, nacc, ProofRule::ACI_NORM); + return nacc; } bool RewriteDbNodeConverter::shouldTraverse(Node n) @@ -98,5 +112,53 @@ bool RewriteDbNodeConverter::shouldTraverse(Node n) return n.getKind() != Kind::INST_PATTERN_LIST; } +void RewriteDbNodeConverter::recordProofStep(const Node& n, + const Node& ret, + ProofRule r) +{ + if (d_tpg == nullptr || n == ret) + { + return; + } + Assert(d_proof != nullptr); + switch (r) + { + case ProofRule::ACI_NORM: + d_tpg->addRewriteStep(n, ret, r, {}, {n.eqNode(ret)}); + break; + case ProofRule::EVALUATE: + { + // Evaluate step in reverse, using d_proof as an intermediate step. + // For instance, we require proving "ABC" = (str.++ "A" "B" "C"). + // We prove this by + // ---------------------------- EVALUATE + // (str.++ "A" "B" "C") = "ABC" + // ----------------------------- SYMM + // "ABC" = (str.++ "A" "B" "C") + Node eq = ret.eqNode(n); + d_proof->addStep(eq, ProofRule::EVALUATE, {}, {ret}); + d_tpg->addRewriteStep(n, ret, d_proof); + } + break; + case ProofRule::ENCODE_EQ_INTRO: + d_tpg->addRewriteStep(n, ret, r, {}, {n}); + break; + default: break; + } +} + +ProofRewriteDbNodeConverter::ProofRewriteDbNodeConverter(Env& env) + : EnvObj(env), d_tpg(env, nullptr), d_proof(env) +{ +} + +std::shared_ptr ProofRewriteDbNodeConverter::convert(const Node& n) +{ + RewriteDbNodeConverter rdnc(nodeManager(), &d_tpg, &d_proof); + Node nr = rdnc.convert(n); + Node equiv = n.eqNode(nr); + return d_tpg.getProofFor(equiv); +} + } // namespace rewriter } // namespace cvc5::internal diff --git a/src/rewriter/rewrite_db_term_process.h b/src/rewriter/rewrite_db_term_process.h index 9284d481569..c86026cf366 100644 --- a/src/rewriter/rewrite_db_term_process.h +++ b/src/rewriter/rewrite_db_term_process.h @@ -18,11 +18,12 @@ #ifndef CVC5__REWRITER__REWRITE_DB_TERM_PROCESS__H #define CVC5__REWRITER__REWRITE_DB_TERM_PROCESS__H -#include -#include +#include #include "expr/node.h" #include "expr/node_converter.h" +#include "proof/conv_proof_generator.h" +#include "proof/proof.h" namespace cvc5::internal { namespace rewriter { @@ -33,6 +34,9 @@ namespace rewriter { * differences include: * (1) cvc5 has (word) string literals; the DSL assumes these are * concatenations of constants, e.g. "ABC" is the term (str.++ "A" "B" "C"). + * (2) Constant bitvectors are lifted to CONST_BITVECTOR_SYMBOLIC. + * (3) Indexed operators are lifted to APPLY_INDEXED_SYMBOLIC. + * (4) Quantifier patterns are dropped. * * This node converter converts from the default representation of cvc5 terms * to the representation of terms required by the DSL proof reconstruction @@ -45,7 +49,13 @@ namespace rewriter { class RewriteDbNodeConverter : public NodeConverter { public: - RewriteDbNodeConverter(NodeManager* nm); + /** + * The latter two arguments are used internally if we are proof producing + * via ProofRewriteDbNodeConverter. + */ + RewriteDbNodeConverter(NodeManager* nm, + TConvProofGenerator* tpg = nullptr, + CDProof* p = nullptr); /** * This converts the node n to the internal shape that it should be in * for the DSL proof reconstruction algorithm. @@ -53,10 +63,37 @@ class RewriteDbNodeConverter : public NodeConverter Node postConvert(Node n) override; protected: + /** A pointer to a TConvProofGenerator, if proof producing */ + TConvProofGenerator* d_tpg; + /** A CDProof, if proof producing */ + CDProof* d_proof; + /** Record that n ---> ret, justifiable by proof rule r. */ + void recordProofStep(const Node& n, const Node& ret, ProofRule r); /** Should we traverse n? */ bool shouldTraverse(Node n) override; }; +/** A proof producing version of the above class */ +class ProofRewriteDbNodeConverter : protected EnvObj +{ + public: + ProofRewriteDbNodeConverter(Env& env); + /** + * Return the proof of the conversion of n based on the above class. + * Specifically, this returns a proof of + * n = RewriteDbNodeConverter::convert(n). + * The returned proof is a term conversion proof whose small steps are + * EVALUATE, ACI_NORM and ENCODE_EQ_INTRO. + */ + std::shared_ptr convert(const Node& n); + + private: + /** A pointer to a TConvProofGenerator, if proof producing */ + TConvProofGenerator d_tpg; + /** A CDProof */ + CDProof d_proof; +}; + } // namespace rewriter } // namespace cvc5::internal diff --git a/src/theory/builtin/proof_checker.cpp b/src/theory/builtin/proof_checker.cpp index d1f72b93ad1..b1f23bd6c77 100644 --- a/src/theory/builtin/proof_checker.cpp +++ b/src/theory/builtin/proof_checker.cpp @@ -51,7 +51,7 @@ void BuiltinProofRuleChecker::registerTo(ProofChecker* pc) pc->registerChecker(ProofRule::ACI_NORM, this); pc->registerChecker(ProofRule::ANNOTATION, this); pc->registerChecker(ProofRule::ITE_EQ, this); - pc->registerChecker(ProofRule::ENCODE_PRED_TRANSFORM, this); + pc->registerChecker(ProofRule::ENCODE_EQ_INTRO, this); pc->registerChecker(ProofRule::DSL_REWRITE, this); pc->registerChecker(ProofRule::THEORY_REWRITE, this); // rules depending on the rewriter @@ -417,19 +417,14 @@ Node BuiltinProofRuleChecker::checkInternal(ProofRule id, Assert(args[0].getType().isInteger()); return args[1]; } - else if (id == ProofRule::ENCODE_PRED_TRANSFORM) + else if (id == ProofRule::ENCODE_EQ_INTRO) { - Assert(children.size() == 1); + Assert(children.empty()); Assert(args.size() == 1); rewriter::RewriteDbNodeConverter rconv(nodeManager()); - Node f = children[0]; - Node g = args[0]; - // equivalent up to conversion via utility - if (rconv.convert(f) != rconv.convert(g)) - { - return Node::null(); - } - return g; + // run a single (small) step conversion + Node ac = rconv.postConvert(args[0]); + return args[0].eqNode(ac); } else if (id == ProofRule::ANNOTATION) { From 900587976da7620efb5d6c166a67f0e629805923 Mon Sep 17 00:00:00 2001 From: Andrew Reynolds Date: Tue, 28 May 2024 20:34:10 -0500 Subject: [PATCH 06/35] Fix link in proof documentation (#10821) --- docs/proofs/output_alf.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/proofs/output_alf.rst b/docs/proofs/output_alf.rst index 673d122892a..68511680cb1 100644 --- a/docs/proofs/output_alf.rst +++ b/docs/proofs/output_alf.rst @@ -7,7 +7,7 @@ The ALF proof format is based on the SMT-LIB 3 language. An efficient C++ proof For a quick start, the cvc5 repository contains a :cvc5repo:`script ` which will download and install the ALF proof checker (alfc), and create scripts for generating proofs with cvc5 and checking them with the ALF proof checker. -The AletheLF language is a meta-framework, meaning that the proof rules used by cvc5 are defined in signature files. The signature files are contained within the cvc5 repository in this :cvc5repo:`directory `. Based on these signatures, cvc5 provides basic support for ALF proofs over all theories that it supports. +The AletheLF language is a meta-framework, meaning that the proof rules used by cvc5 are defined in signature files. The signature files are contained within the cvc5 repository in this :cvc5repo:`directory `. Based on these signatures, cvc5 provides basic support for ALF proofs over all theories that it supports. Note that several proof rules in the internal calculus are not yet supported in ALF signatures. Steps that use such rules are printed as `trust` steps in the ALF proof. A trust step proves an arbitrary formula with no provided justification. The ALF proof contains warnings for trust steps that indicate which internal proof rules were recorded as trust steps in the ALF proof. From 7a86ff4ae282cc47df1db8eb87e577c12d22be36 Mon Sep 17 00:00:00 2001 From: Andrew Reynolds Date: Wed, 29 May 2024 11:45:59 -0500 Subject: [PATCH 07/35] Minor updates to skolems (#10756) Changes motivated by sharing lemmas in parallel/distributed setting. --- src/expr/plugin.cpp | 5 +++-- src/theory/quantifiers/skolemize.cpp | 15 ++++++++++++--- .../cli/regress1/quantifiers/dump-inst-i.smt2 | 10 +++++----- .../cli/regress1/quantifiers/dump-inst.smt2 | 6 +++--- .../regress1/quantifiers/lia-witness-div-pp.smt2 | 2 ++ 5 files changed, 25 insertions(+), 13 deletions(-) diff --git a/src/expr/plugin.cpp b/src/expr/plugin.cpp index 5d3f6df690e..bf2fe9b0d91 100644 --- a/src/expr/plugin.cpp +++ b/src/expr/plugin.cpp @@ -28,9 +28,10 @@ Plugin::~Plugin() {} Node Plugin::getSharableFormula(const Node& n) const { Node on = SkolemManager::getOriginalForm(n); - if (expr::hasSubtermKind(Kind::SKOLEM, on)) + if (expr::hasSubtermKinds({Kind::SKOLEM, Kind::INST_CONSTANT}, on)) { - // cannot share formulas with skolems currently + // We cannot share formulas with skolems currently. + // We should never share formulas with instantiation constants. return Node::null(); } // also eliminate subtyping diff --git a/src/theory/quantifiers/skolemize.cpp b/src/theory/quantifiers/skolemize.cpp index 8a905325887..18dbac2cc2d 100644 --- a/src/theory/quantifiers/skolemize.cpp +++ b/src/theory/quantifiers/skolemize.cpp @@ -106,6 +106,14 @@ TrustNode Skolemize::process(Node q) std::vector Skolemize::getSkolemConstants(const Node& q) { + if (q.getKind()==Kind::FORALL) + { + std::vector echildren(q.begin(), q.end()); + echildren[1] = echildren[1].notNode(); + NodeManager* nm = NodeManager::currentNM(); + Node existsq = nm->mkNode(Kind::EXISTS, echildren); + return getSkolemConstants(existsq); + } Assert(q.getKind() == Kind::EXISTS); std::vector skolems; for (size_t i = 0, nvars = q[0].getNumChildren(); i < nvars; i++) @@ -191,6 +199,7 @@ Node Skolemize::mkSkolemizedBodyInduction(const Options& opts, Node& sub, std::vector& sub_vars) { + Assert (f.getKind()==Kind::FORALL); NodeManager* nm = NodeManager::currentNM(); // compute the argument types from the free variables std::vector argTypes; @@ -205,7 +214,8 @@ Node Skolemize::mkSkolemizedBodyInduction(const Options& opts, std::vector ind_var_indicies; std::vector vars; std::vector var_indicies; - for (unsigned i = 0; i < f[0].getNumChildren(); i++) + std::vector skc = getSkolemConstants(f); + for (size_t i = 0, nvars = f[0].getNumChildren(); i < nvars; i++) { if (isInductionTerm(opts, f[0][i])) { @@ -223,8 +233,7 @@ Node Skolemize::mkSkolemizedBodyInduction(const Options& opts, { if (argTypes.empty()) { - s = sm->mkDummySkolem( - "skv", f[0][i].getType(), "created during skolemization"); + s = skc[i]; } else { diff --git a/test/regress/cli/regress1/quantifiers/dump-inst-i.smt2 b/test/regress/cli/regress1/quantifiers/dump-inst-i.smt2 index a207cc40fce..688b1e6e4bc 100644 --- a/test/regress/cli/regress1/quantifiers/dump-inst-i.smt2 +++ b/test/regress/cli/regress1/quantifiers/dump-inst-i.smt2 @@ -1,18 +1,18 @@ ; COMMAND-LINE: --dump-instantiations --incremental --print-inst-full -; SCRUBBER: sed -e 's/skv_.* )$/skv_TERM )/' +; SCRUBBER: sed -e 's/@quantifiers_skolemize_.* )$/@quantifiers_skolemize_TERM )/' ; EXPECT: unsat ; EXPECT: (skolem (forall ((x Int)) (or (P x) (Q x))) -; EXPECT: ( skv_TERM ) +; EXPECT: ( @quantifiers_skolemize_TERM ) ; EXPECT: ) ; EXPECT: (instantiations (forall ((x Int)) (P x)) -; EXPECT: ( skv_TERM ) +; EXPECT: ( @quantifiers_skolemize_TERM ) ; EXPECT: ) ; EXPECT: unsat ; EXPECT: (skolem (forall ((x Int)) (or (P x) (R x))) -; EXPECT: ( skv_TERM ) +; EXPECT: ( @quantifiers_skolemize_TERM ) ; EXPECT: ) ; EXPECT: (instantiations (forall ((x Int)) (P x)) -; EXPECT: ( skv_TERM ) +; EXPECT: ( @quantifiers_skolemize_TERM ) ; EXPECT: ) ; disable proofs since it impacts what is relevant (e.g. the skolem lemmas) ; DISABLE-TESTER: proof diff --git a/test/regress/cli/regress1/quantifiers/dump-inst.smt2 b/test/regress/cli/regress1/quantifiers/dump-inst.smt2 index 9fe2d310ba8..27e9497a741 100644 --- a/test/regress/cli/regress1/quantifiers/dump-inst.smt2 +++ b/test/regress/cli/regress1/quantifiers/dump-inst.smt2 @@ -1,11 +1,11 @@ ; COMMAND-LINE: --dump-instantiations --print-inst-full -; SCRUBBER: sed -e 's/skv_.* )$/skv_TERM )/' +; SCRUBBER: sed -e 's/@quantifiers_skolemize_.* )$/@quantifiers_skolemize_TERM )/' ; EXPECT: unsat ; EXPECT: (skolem (forall ((x Int)) (or (P x) (Q x))) -; EXPECT: ( skv_TERM ) +; EXPECT: ( @quantifiers_skolemize_TERM ) ; EXPECT: ) ; EXPECT: (instantiations (forall ((x Int)) (P x)) -; EXPECT: ( skv_TERM ) +; EXPECT: ( @quantifiers_skolemize_TERM ) ; EXPECT: ) ; disable proofs since it impacts what is relevant (e.g. the skolem lemmas) ; DISABLE-TESTER: proof diff --git a/test/regress/cli/regress1/quantifiers/lia-witness-div-pp.smt2 b/test/regress/cli/regress1/quantifiers/lia-witness-div-pp.smt2 index 1fa004ef49f..d3cf756125c 100644 --- a/test/regress/cli/regress1/quantifiers/lia-witness-div-pp.smt2 +++ b/test/regress/cli/regress1/quantifiers/lia-witness-div-pp.smt2 @@ -1,4 +1,6 @@ ; COMMAND-LINE: --no-sygus-inst +; times out after change to skolems +; DISABLE-TESTER: unsat-core (set-info :smt-lib-version 2.6) (set-logic NIA) (set-info :status unsat) From bbbdab668487af2f83960d447532c143a7b42630 Mon Sep 17 00:00:00 2001 From: Andrew Reynolds Date: Wed, 29 May 2024 12:27:36 -0500 Subject: [PATCH 08/35] Infrastructure for commutative skolems (#10779) This makes it so that we always generate the same extensionality skolem no matter what order the arguments are given in. The motivation here is to ensure that e.g. extensionality skolems are properly shared in portfolio/distributed settings. --- src/expr/skolem_manager.cpp | 25 ++++++++++++++++++++++--- src/expr/skolem_manager.h | 6 ++++++ src/theory/arrays/inference_manager.cpp | 9 ++++++++- 3 files changed, 36 insertions(+), 4 deletions(-) diff --git a/src/expr/skolem_manager.cpp b/src/expr/skolem_manager.cpp index 8bb7da12db9..911928e7bd2 100644 --- a/src/expr/skolem_manager.cpp +++ b/src/expr/skolem_manager.cpp @@ -111,9 +111,7 @@ Node SkolemManager::mkSkolemFunction(SkolemId id, Node cacheVal) cvals.push_back(cacheVal); } } - TypeNode ctn = getTypeFor(id, cvals); - Assert(!ctn.isNull()); - return mkSkolemFunctionTyped(id, ctn, cacheVal); + return mkSkolemFunction(id, cvals); } Node SkolemManager::mkSkolemFunction(SkolemId id, @@ -121,6 +119,14 @@ Node SkolemManager::mkSkolemFunction(SkolemId id, { TypeNode ctn = getTypeFor(id, cacheVals); Assert(!ctn.isNull()); + if (isCommutativeSkolemId(id)) + { + // sort arguments if commutative, which should not impact its type + std::vector cvs = cacheVals; + std::sort(cvs.begin(), cvs.end()); + Assert(getTypeFor(id, cvs) == ctn); + return mkSkolemFunctionTyped(id, ctn, cvs); + } return mkSkolemFunctionTyped(id, ctn, cacheVals); } @@ -135,6 +141,19 @@ Node SkolemManager::mkInternalSkolemFunction(InternalSkolemId id, return mkSkolemFunctionTyped(SkolemId::INTERNAL, tn, cvals); } +bool SkolemManager::isCommutativeSkolemId(SkolemId id) +{ + switch (id) + { + case cvc5::SkolemId::ARRAY_DEQ_DIFF: + case cvc5::SkolemId::BAGS_DEQ_DIFF: + case cvc5::SkolemId::SETS_DEQ_DIFF: + case cvc5::SkolemId::STRINGS_DEQ_DIFF: return true; + default: break; + } + return false; +} + Node SkolemManager::mkSkolemFunctionTyped(SkolemId id, TypeNode tn, Node cacheVal) diff --git a/src/expr/skolem_manager.h b/src/expr/skolem_manager.h index 8e9d29b470c..c2739ce39f7 100644 --- a/src/expr/skolem_manager.h +++ b/src/expr/skolem_manager.h @@ -276,6 +276,12 @@ class SkolemManager * by this node manager. */ size_t d_skolemCounter; + /** + * Is the given skolem identifier commutative, in the sense that its + * arguments can be reordered? If this method returns true, then + * we always sort the arguments to the skolem upon construction. + */ + static bool isCommutativeSkolemId(SkolemId id); /** Same as mkSkolemFunction, with explicit type */ Node mkSkolemFunctionTyped(SkolemId id, TypeNode tn, diff --git a/src/theory/arrays/inference_manager.cpp b/src/theory/arrays/inference_manager.cpp index 00af1252573..711cbedab7f 100644 --- a/src/theory/arrays/inference_manager.cpp +++ b/src/theory/arrays/inference_manager.cpp @@ -113,7 +113,14 @@ void InferenceManager::convert(ProofRule& id, Assert(exp.isConst()); args.push_back(conc[0]); break; - case ProofRule::ARRAYS_EXT: children.push_back(exp); break; + case ProofRule::ARRAYS_EXT: + // since this rule depends on the ARRAY_DEQ_DIFF skolem which sorts + // indices, we assert that the equality is ordered here, which it should + // be based on the standard order for equality. + Assert(exp.getKind() == Kind::NOT && exp[0].getKind() == Kind::EQUAL + && exp[0][0] < exp[0][1]); + children.push_back(exp); + break; default: if (id != ProofRule::TRUST) { From 324a016c8bce89eb9b56d4c62dfab83f29e8f254 Mon Sep 17 00:00:00 2001 From: Aina Niemetz Date: Wed, 29 May 2024 16:57:46 -0700 Subject: [PATCH 09/35] test: C API: Don't catch GMP specific error message in unit test. (#10829) --- test/unit/api/c/capi_term_manager_black.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/api/c/capi_term_manager_black.cpp b/test/unit/api/c/capi_term_manager_black.cpp index f8a8d3eb89d..df1a3b6bdd2 100644 --- a/test/unit/api/c/capi_term_manager_black.cpp +++ b/test/unit/api/c/capi_term_manager_black.cpp @@ -121,7 +121,7 @@ TEST_F(TestCApiBlackTermManager, mk_ff_sort) ASSERT_DEATH(cvc5_mk_ff_sort(d_tm, nullptr, 10), "unexpected NULL argument"); ASSERT_DEATH(cvc5_mk_ff_sort(d_tm, "6", 10), "expected modulus is prime"); - ASSERT_DEATH(cvc5_mk_ff_sort(d_tm, "b", 10), "mpz_set_str"); + ASSERT_DEATH(cvc5_mk_ff_sort(d_tm, "b", 10), ""); (void)cvc5_mk_ff_sort(d_tm, "1100101", 2); (void)cvc5_mk_ff_sort(d_tm, "10202", 3); From 0e3316fa95a072e137fc769fdf2d2c7f4dd106db Mon Sep 17 00:00:00 2001 From: Andrew Reynolds Date: Thu, 30 May 2024 12:50:58 -0500 Subject: [PATCH 10/35] Add new string theory rewrites in ALF (#10784) These will correspond to 3 new THEORY_REWRITE rules for the theory of strings. --- proofs/alf/cvc5/programs/Strings.smt3 | 43 +++++++++++++++++++ proofs/alf/cvc5/rules/Strings.smt3 | 21 +++++++++ proofs/alf/cvc5/theories/Strings.smt3 | 2 +- test/regress/cli/CMakeLists.txt | 1 + .../strings/str-term-small-rw_545.smt2 | 8 ++++ 5 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 test/regress/cli/regress1/strings/str-term-small-rw_545.smt2 diff --git a/proofs/alf/cvc5/programs/Strings.smt3 b/proofs/alf/cvc5/programs/Strings.smt3 index da66ef6e8b8..2f8b4c6bd48 100644 --- a/proofs/alf/cvc5/programs/Strings.smt3 +++ b/proofs/alf/cvc5/programs/Strings.smt3 @@ -567,6 +567,49 @@ ($singleton_elim ($str_mk_re_loop_elim_rec n d r (str.to_re ""))) ) +; Helper for ProofRewriteRule::STR_IN_RE_CONCAT_STAR_CHAR. +; Note that we do not require a singleton elimination step since the original input should be a concatenation term. +(program $str_mk_str_in_re_concat_star_char ((s1 String) (s2 String :list) (r RegLan)) + (String RegLan) Bool + ( + (($str_mk_str_in_re_concat_star_char (str.++ s1 s2) r) (alf.cons and (str.in_re s1 r) ($str_mk_str_in_re_concat_star_char s2 r))) + (($str_mk_str_in_re_concat_star_char "" r) true) + ) +) + + +; Helper for $str_mk_str_in_re_sigma. +; Calling `$str_mk_str_in_re_sigma_rec s r n b` means we have s in r, we have stripped +; off n re.allchar from r so far. If `b` is false, then we have stripped off +; a (re.* re.allchar). +(program $str_mk_str_in_re_sigma_rec ((s String) (r RegLan :list) (n Int) (b Bool)) + (String RegLan Int Bool) Bool + ( + (($str_mk_str_in_re_sigma_rec s @re.empty n b) (alf.ite b (= (str.len s) n) (>= (str.len s) n))) + (($str_mk_str_in_re_sigma_rec s (re.++ re.allchar r) n b) ($str_mk_str_in_re_sigma_rec s r (alf.add n 1) b)) + (($str_mk_str_in_re_sigma_rec s (re.++ (re.* re.allchar) r) n b) ($str_mk_str_in_re_sigma_rec s r n false)) + ) +) + +; Helper for ProofRewriteRule::STR_IN_RE_SIGMA. +(define $str_mk_str_in_re_sigma ((s String) (r RegLan)) + ($str_mk_str_in_re_sigma_rec s r 0 true)) + +; Helper for $str_mk_str_in_re_sigma. +; Calling `$str_mk_str_in_re_sigma_star_rec s r n` means we have s in r, we have +; stripped off n re.allchar from r so far. +(program $str_mk_str_in_re_sigma_star_rec ((s String) (r RegLan :list) (n Int)) + (String RegLan Int) Bool + ( + (($str_mk_str_in_re_sigma_star_rec s @re.empty n) (= (mod (str.len s) n) 0)) + (($str_mk_str_in_re_sigma_star_rec s (re.++ re.allchar r) n) ($str_mk_str_in_re_sigma_star_rec s r (alf.add n 1))) + ) +) + +; Helper for ProofRewriteRule::STR_IN_RE_SIGMA_STAR, r is the body of the star. +(define $str_mk_str_in_re_sigma_star ((s String) (r RegLan)) + ($str_mk_str_in_re_sigma_star_rec s r 0)) + ; Converts a str.++ application into "flat form" so that we are ready to ; process its prefix. This consists of the following steps: ; (1) convert s to n-ary form if it is not already a str.++ application, diff --git a/proofs/alf/cvc5/rules/Strings.smt3 b/proofs/alf/cvc5/rules/Strings.smt3 index bd974107e8e..524dec3174f 100644 --- a/proofs/alf/cvc5/rules/Strings.smt3 +++ b/proofs/alf/cvc5/rules/Strings.smt3 @@ -190,11 +190,13 @@ ;;-------------------- Regular expressions +; ProofRule::RE_INTER (declare-rule re_inter ((x String) (s RegLan) (t RegLan)) :premises ((str.in_re x s) (str.in_re x t)) :conclusion (str.in_re x (re.inter s t)) ) +; ProofRule::RE_UNFOLD_POS (declare-rule re_unfold_pos ((t String) (r RegLan)) :premises ((str.in_re t r)) :conclusion @@ -287,3 +289,22 @@ (($str_mk_re_loop_elim l (alf.add (alf.neg l) u) r1) r2)) :conclusion (= (re.loop l u r1) r2) ) + +(declare-rule str-in-re-concat-star-char ((s1 String) (s2 String :list) (r RegLan) (b Bool)) + :args ((= (str.in_re (str.++ s1 s2) (re.* r)) b)) + :requires ((($str_fixed_len_re r) 1) + (($str_mk_str_in_re_concat_star_char (str.++ s1 s2) (re.* r)) b)) + :conclusion (= (str.in_re (str.++ s1 s2) (re.* r)) b) +) + +(declare-rule str-in-re-sigma ((s String) (r RegLan) (b Bool)) + :args ((= (str.in_re s r) b)) + :requires ((($str_mk_str_in_re_sigma s r) b)) + :conclusion (= (str.in_re s r) b) +) + +(declare-rule str-in-re-sigma-star ((s String) (r RegLan) (b Bool)) + :args ((= (str.in_re s (re.* r)) b)) + :requires ((($str_mk_str_in_re_sigma_star s r) b)) + :conclusion (= (str.in_re s (re.* r)) b) +) diff --git a/proofs/alf/cvc5/theories/Strings.smt3 b/proofs/alf/cvc5/theories/Strings.smt3 index f52fc608ec4..b3b75d0d459 100644 --- a/proofs/alf/cvc5/theories/Strings.smt3 +++ b/proofs/alf/cvc5/theories/Strings.smt3 @@ -74,8 +74,8 @@ (declare-const re.allchar RegLan) (declare-const re.none RegLan) (declare-const re.all RegLan) -(declare-const re.empty RegLan) (declare-const str.to_re (-> String RegLan)) +(define @re.empty () (str.to_re "")) (declare-const re.* (-> RegLan RegLan)) (declare-const re.+ (-> RegLan RegLan)) (declare-const re.opt (-> RegLan RegLan)) diff --git a/test/regress/cli/CMakeLists.txt b/test/regress/cli/CMakeLists.txt index fbef0bdbfd6..73f83d61806 100644 --- a/test/regress/cli/CMakeLists.txt +++ b/test/regress/cli/CMakeLists.txt @@ -3251,6 +3251,7 @@ set(regress_1_tests regress1/strings/str-code-unsat.smt2 regress1/strings/str-pred-small-rw_392.smt2 regress1/strings/str-rev-simple-s.smt2 + regress1/strings/str-term-small-rw_545.smt2 regress1/strings/str001.smt2 regress1/strings/str002.smt2 regress1/strings/str006.smt2 diff --git a/test/regress/cli/regress1/strings/str-term-small-rw_545.smt2 b/test/regress/cli/regress1/strings/str-term-small-rw_545.smt2 new file mode 100644 index 00000000000..9b3442849b2 --- /dev/null +++ b/test/regress/cli/regress1/strings/str-term-small-rw_545.smt2 @@ -0,0 +1,8 @@ +; EXPECT: unsat +(set-logic QF_SLIA) +(declare-fun x () String) +(declare-fun y () String) +(declare-fun z () Int) +(assert (not (= (str.replace x (str.replace x "A" "B") "A") (str.replace x (str.replace x "A" x) "A")))) +(check-sat) +(exit) From 1501f21c8af175cfee6fc4afee1694227d99692f Mon Sep 17 00:00:00 2001 From: Andrew Reynolds Date: Thu, 30 May 2024 15:37:18 -0500 Subject: [PATCH 11/35] Add new RARE rewrites for regular expression membership (#10696) Also refactors existing string rules to properly take advantage of fixed point semantics and adds a few other missing rules. --- include/cvc5/cvc5_proof_rule.h | 123 ++++++++++-- src/CMakeLists.txt | 1 + src/theory/strings/rewrites | 77 ++++++-- src/theory/strings/rewrites-regexp-membership | 185 ++++++++++++++++++ 4 files changed, 355 insertions(+), 31 deletions(-) create mode 100644 src/theory/strings/rewrites-regexp-membership diff --git a/include/cvc5/cvc5_proof_rule.h b/include/cvc5/cvc5_proof_rule.h index 35c67ac561d..013bc6b5192 100644 --- a/include/cvc5/cvc5_proof_rule.h +++ b/include/cvc5/cvc5_proof_rule.h @@ -3083,16 +3083,6 @@ enum ENUM(ProofRewriteRule) : uint32_t EVALUE(STR_LEN_SUBSTR_UB1), /** Auto-generated from RARE rule str-len-substr-ub2 */ EVALUE(STR_LEN_SUBSTR_UB2), - /** Auto-generated from RARE rule re-in-empty */ - EVALUE(RE_IN_EMPTY), - /** Auto-generated from RARE rule re-in-sigma */ - EVALUE(RE_IN_SIGMA), - /** Auto-generated from RARE rule re-in-sigma-star */ - EVALUE(RE_IN_SIGMA_STAR), - /** Auto-generated from RARE rule re-in-cstring */ - EVALUE(RE_IN_CSTRING), - /** Auto-generated from RARE rule re-in-comp */ - EVALUE(RE_IN_COMP), /** Auto-generated from RARE rule str-concat-clash */ EVALUE(STR_CONCAT_CLASH), /** Auto-generated from RARE rule str-concat-clash-rev */ @@ -3105,6 +3095,10 @@ enum ENUM(ProofRewriteRule) : uint32_t EVALUE(STR_CONCAT_UNIFY), /** Auto-generated from RARE rule str-concat-unify-rev */ EVALUE(STR_CONCAT_UNIFY_REV), + /** Auto-generated from RARE rule str-concat-unify-base */ + EVALUE(STR_CONCAT_UNIFY_BASE), + /** Auto-generated from RARE rule str-concat-unify-base-rev */ + EVALUE(STR_CONCAT_UNIFY_BASE_REV), /** Auto-generated from RARE rule str-concat-clash-char */ EVALUE(STR_CONCAT_CLASH_CHAR), /** Auto-generated from RARE rule str-concat-clash-char-rev */ @@ -3133,14 +3127,26 @@ enum ENUM(ProofRewriteRule) : uint32_t EVALUE(STR_CONTAINS_SPLIT_CHAR), /** Auto-generated from RARE rule str-contains-leq-len-eq */ EVALUE(STR_CONTAINS_LEQ_LEN_EQ), + /** Auto-generated from RARE rule str-contains-emp */ + EVALUE(STR_CONTAINS_EMP), + /** Auto-generated from RARE rule str-contains-is-emp */ + EVALUE(STR_CONTAINS_IS_EMP), /** Auto-generated from RARE rule str-concat-emp */ EVALUE(STR_CONCAT_EMP), /** Auto-generated from RARE rule str-at-elim */ EVALUE(STR_AT_ELIM), + /** Auto-generated from RARE rule str-replace-no-contains */ + EVALUE(STR_REPLACE_NO_CONTAINS), + /** Auto-generated from RARE rule str-replace-empty */ + EVALUE(STR_REPLACE_EMPTY), + /** Auto-generated from RARE rule str-len-concat-rec */ + EVALUE(STR_LEN_CONCAT_REC), /** Auto-generated from RARE rule re-all-elim */ EVALUE(RE_ALL_ELIM), /** Auto-generated from RARE rule re-opt-elim */ EVALUE(RE_OPT_ELIM), + /** Auto-generated from RARE rule re-diff-elim */ + EVALUE(RE_DIFF_ELIM), /** Auto-generated from RARE rule re-concat-emp */ EVALUE(RE_CONCAT_EMP), /** Auto-generated from RARE rule re-concat-none */ @@ -3149,6 +3155,8 @@ enum ENUM(ProofRewriteRule) : uint32_t EVALUE(RE_CONCAT_FLATTEN), /** Auto-generated from RARE rule re-concat-star-swap */ EVALUE(RE_CONCAT_STAR_SWAP), + /** Auto-generated from RARE rule re-concat-merge */ + EVALUE(RE_CONCAT_MERGE), /** Auto-generated from RARE rule re-union-all */ EVALUE(RE_UNION_ALL), /** Auto-generated from RARE rule re-union-none */ @@ -3165,16 +3173,103 @@ enum ENUM(ProofRewriteRule) : uint32_t EVALUE(RE_INTER_FLATTEN), /** Auto-generated from RARE rule re-inter-dup */ EVALUE(RE_INTER_DUP), - /** Auto-generated from RARE rule str-len-concat-rec */ - EVALUE(STR_LEN_CONCAT_REC), - /** Auto-generated from RARE rule str-in-re-range-elim */ - EVALUE(STR_IN_RE_RANGE_ELIM), + /** Auto-generated from RARE rule re-inter-cstring */ + EVALUE(RE_INTER_CSTRING), + /** Auto-generated from RARE rule re-inter-cstring-neg */ + EVALUE(RE_INTER_CSTRING_NEG), + /** Auto-generated from RARE rule str-nth-elim-code */ + EVALUE(STR_NTH_ELIM_CODE), /** Auto-generated from RARE rule seq-len-unit */ EVALUE(SEQ_LEN_UNIT), /** Auto-generated from RARE rule seq-nth-unit */ EVALUE(SEQ_NTH_UNIT), /** Auto-generated from RARE rule seq-rev-unit */ EVALUE(SEQ_REV_UNIT), + /** Auto-generated from RARE rule re-in-empty */ + EVALUE(RE_IN_EMPTY), + /** Auto-generated from RARE rule re-in-sigma */ + EVALUE(RE_IN_SIGMA), + /** Auto-generated from RARE rule re-in-sigma-star */ + EVALUE(RE_IN_SIGMA_STAR), + /** Auto-generated from RARE rule re-in-cstring */ + EVALUE(RE_IN_CSTRING), + /** Auto-generated from RARE rule re-in-comp */ + EVALUE(RE_IN_COMP), + /** Auto-generated from RARE rule str-in-re-union-elim */ + EVALUE(STR_IN_RE_UNION_ELIM), + /** Auto-generated from RARE rule str-in-re-inter-elim */ + EVALUE(STR_IN_RE_INTER_ELIM), + /** Auto-generated from RARE rule str-in-re-range-elim */ + EVALUE(STR_IN_RE_RANGE_ELIM), + /** Auto-generated from RARE rule str-in-re-contains */ + EVALUE(STR_IN_RE_CONTAINS), + /** Auto-generated from RARE rule str-in-re-strip-prefix */ + EVALUE(STR_IN_RE_STRIP_PREFIX), + /** Auto-generated from RARE rule str-in-re-strip-prefix-neg */ + EVALUE(STR_IN_RE_STRIP_PREFIX_NEG), + /** Auto-generated from RARE rule str-in-re-strip-prefix-sr-single */ + EVALUE(STR_IN_RE_STRIP_PREFIX_SR_SINGLE), + /** Auto-generated from RARE rule str-in-re-strip-prefix-sr-single-neg */ + EVALUE(STR_IN_RE_STRIP_PREFIX_SR_SINGLE_NEG), + /** Auto-generated from RARE rule str-in-re-strip-prefix-srs-single */ + EVALUE(STR_IN_RE_STRIP_PREFIX_SRS_SINGLE), + /** Auto-generated from RARE rule str-in-re-strip-prefix-srs-single-neg */ + EVALUE(STR_IN_RE_STRIP_PREFIX_SRS_SINGLE_NEG), + /** Auto-generated from RARE rule str-in-re-strip-prefix-s-single */ + EVALUE(STR_IN_RE_STRIP_PREFIX_S_SINGLE), + /** Auto-generated from RARE rule str-in-re-strip-prefix-s-single-neg */ + EVALUE(STR_IN_RE_STRIP_PREFIX_S_SINGLE_NEG), + /** Auto-generated from RARE rule str-in-re-strip-prefix-base */ + EVALUE(STR_IN_RE_STRIP_PREFIX_BASE), + /** Auto-generated from RARE rule str-in-re-strip-prefix-base-neg */ + EVALUE(STR_IN_RE_STRIP_PREFIX_BASE_NEG), + /** Auto-generated from RARE rule str-in-re-strip-prefix-base-s-single */ + EVALUE(STR_IN_RE_STRIP_PREFIX_BASE_S_SINGLE), + /** Auto-generated from RARE rule str-in-re-strip-prefix-base-s-single-neg */ + EVALUE(STR_IN_RE_STRIP_PREFIX_BASE_S_SINGLE_NEG), + /** Auto-generated from RARE rule str-in-re-strip-char */ + EVALUE(STR_IN_RE_STRIP_CHAR), + /** Auto-generated from RARE rule str-in-re-strip-char-s-single */ + EVALUE(STR_IN_RE_STRIP_CHAR_S_SINGLE), + /** Auto-generated from RARE rule str-in-re-strip-prefix-rev */ + EVALUE(STR_IN_RE_STRIP_PREFIX_REV), + /** Auto-generated from RARE rule str-in-re-strip-prefix-neg-rev */ + EVALUE(STR_IN_RE_STRIP_PREFIX_NEG_REV), + /** Auto-generated from RARE rule str-in-re-strip-prefix-sr-single-rev */ + EVALUE(STR_IN_RE_STRIP_PREFIX_SR_SINGLE_REV), + /** Auto-generated from RARE rule str-in-re-strip-prefix-sr-single-neg-rev */ + EVALUE(STR_IN_RE_STRIP_PREFIX_SR_SINGLE_NEG_REV), + /** Auto-generated from RARE rule str-in-re-strip-prefix-srs-single-rev */ + EVALUE(STR_IN_RE_STRIP_PREFIX_SRS_SINGLE_REV), + /** Auto-generated from RARE rule str-in-re-strip-prefix-srs-single-neg-rev */ + EVALUE(STR_IN_RE_STRIP_PREFIX_SRS_SINGLE_NEG_REV), + /** Auto-generated from RARE rule str-in-re-strip-prefix-s-single-rev */ + EVALUE(STR_IN_RE_STRIP_PREFIX_S_SINGLE_REV), + /** Auto-generated from RARE rule str-in-re-strip-prefix-s-single-neg-rev */ + EVALUE(STR_IN_RE_STRIP_PREFIX_S_SINGLE_NEG_REV), + /** Auto-generated from RARE rule str-in-re-strip-prefix-base-rev */ + EVALUE(STR_IN_RE_STRIP_PREFIX_BASE_REV), + /** Auto-generated from RARE rule str-in-re-strip-prefix-base-neg-rev */ + EVALUE(STR_IN_RE_STRIP_PREFIX_BASE_NEG_REV), + /** Auto-generated from RARE rule str-in-re-strip-prefix-base-s-single-rev */ + EVALUE(STR_IN_RE_STRIP_PREFIX_BASE_S_SINGLE_REV), + /** Auto-generated from RARE rule str-in-re-strip-prefix-base-s-single-neg-rev + */ + EVALUE(STR_IN_RE_STRIP_PREFIX_BASE_S_SINGLE_NEG_REV), + /** Auto-generated from RARE rule str-in-re-strip-char-rev */ + EVALUE(STR_IN_RE_STRIP_CHAR_REV), + /** Auto-generated from RARE rule str-in-re-strip-char-s-single-rev */ + EVALUE(STR_IN_RE_STRIP_CHAR_S_SINGLE_REV), + /** Auto-generated from RARE rule str-in-re-no-prefix */ + EVALUE(STR_IN_RE_NO_PREFIX), + /** Auto-generated from RARE rule str-in-re-no-prefix-rev */ + EVALUE(STR_IN_RE_NO_PREFIX_REV), + /** Auto-generated from RARE rule str-in-re-concat-allchar */ + EVALUE(STR_IN_RE_CONCAT_ALLCHAR), + /** Auto-generated from RARE rule str-in-re-concat-allchar-base-geq */ + EVALUE(STR_IN_RE_CONCAT_ALLCHAR_BASE_GEQ), + /** Auto-generated from RARE rule str-in-re-concat-allchar-base-eq */ + EVALUE(STR_IN_RE_CONCAT_ALLCHAR_BASE_EQ), /** Auto-generated from RARE rule eq-refl */ EVALUE(EQ_REFL), /** Auto-generated from RARE rule eq-symm */ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 900f5666586..330347243cc 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1421,6 +1421,7 @@ set(REWRITES_FILES ${PROJECT_SOURCE_DIR}/src/theory/bv/rewrites-simplification ${PROJECT_SOURCE_DIR}/src/theory/sets/rewrites ${PROJECT_SOURCE_DIR}/src/theory/strings/rewrites + ${PROJECT_SOURCE_DIR}/src/theory/strings/rewrites-regexp-membership ${PROJECT_SOURCE_DIR}/src/theory/uf/rewrites ) diff --git a/src/theory/strings/rewrites b/src/theory/strings/rewrites index a61644a8aa5..227eb3aef82 100644 --- a/src/theory/strings/rewrites +++ b/src/theory/strings/rewrites @@ -11,7 +11,7 @@ (= (str.contains y x) false) (= (str.++ x1 x x2) y) false) - + ;(define-cond-rule str-eq-len-false ((x String) (y String)) ; (not (= (str.len x) (str.len y))) ; (= x y) @@ -62,12 +62,6 @@ (>= k (str.len (str.substr s n m))) true) -(define-rule re-in-empty ((t String)) (str.in_re t re.none) false) -(define-rule re-in-sigma ((t String)) (str.in_re t re.allchar) (= (str.len t) 1)) -(define-rule re-in-sigma-star ((t String)) (str.in_re t (re.* re.allchar)) true) -(define-rule re-in-cstring ((t String) (s String)) (str.in_re t (str.to_re s)) (= t s)) -(define-rule re-in-comp ((t String) (r RegLan)) (str.in_re t (re.comp r)) (not (str.in_re t r))) - (define-cond-rule str-concat-clash ((s1 String) (s2 String :list) (t1 String) (t2 String :list)) (and (not (= s1 t1)) (= (str.len s1) (str.len t1))) (= (str.++ s1 s2) (str.++ t1 t2)) @@ -87,10 +81,19 @@ (define-rule* str-concat-unify ((s1 String) (s2 String) (s3 String :list) (t2 String) (t3 String :list)) (= (str.++ s1 s2 s3) (str.++ s1 t2 t3)) - (= (str.++ s2 s3) (str.++ t2 t3))) + (= (str.++ s2 s3) (str.++ t2 t3)) + _) (define-rule* str-concat-unify-rev ((s1 String) (s2 String) (s3 String :list) (t2 String) (t3 String :list)) (= (str.++ s2 s3 s1) (str.++ t2 t3 s1)) - (= (str.++ s2 s3) (str.++ t2 t3))) + (= (str.++ s2 s3) (str.++ t2 t3)) + _) + +(define-rule str-concat-unify-base ((s String) (t1 String) (t2 String :list)) + (= s (str.++ s t1 t2)) + (= "" (str.++ t1 t2))) +(define-rule str-concat-unify-base-rev ((s String) (t1 String) (t2 String :list)) + (= s (str.++ t1 t2 s)) + (= "" (str.++ t1 t2))) (define-cond-rule str-concat-clash-char ((s1 String) (s2 String :list) (s3 String :list) (t1 String) (t2 String :list) (t3 String :list)) (and (not (= s1 t1)) (= (str.len s1) (str.len t1))) @@ -158,6 +161,21 @@ (str.contains x y) (= x y)) +(define-cond-rule str-contains-emp ((x String) (y String)) + (= (str.len y) 0) + (str.contains x y) + true) + +;(define-cond-rule str-contains-len-geq-false ((x String) (y String)) +; (> (str.len y) (str.len x)) +; (str.contains x y) +; false) + +(define-cond-rule str-contains-is-emp ((x String) (y String)) + (= (str.len x) 0) + (str.contains x y) + (= x y)) + (define-rule str-concat-emp ((xs String :list) (ys String :list)) (str.++ xs "" ys) (str.++ xs ys)) @@ -170,12 +188,28 @@ ; (str.replace t s t) ; t) +(define-cond-rule str-replace-no-contains ((t ?Seq) (s ?Seq) (r ?Seq)) + (not (str.contains t s)) + (str.replace t s r) + t) + +(define-rule str-replace-empty ((t ?Seq) (s ?Seq)) + (str.replace t "" s) + (str.++ s t)) + +(define-rule* str-len-concat-rec ((s1 String) (s2 String) (s3 String :list)) + (str.len (str.++ s1 s2 s3)) + (str.len (str.++ s2 s3)) + (+ (str.len s1) _)) + ; =============== Regular expression rules (define-rule re-all-elim () re.all (re.* re.allchar)) (define-rule re-opt-elim ((x RegLan)) (re.opt x) (re.union (str.to_re "") x)) +(define-rule re-diff-elim ((x RegLan) (y RegLan)) (re.diff x y) (re.inter x (re.comp y))) + (define-rule re-concat-emp ((xs RegLan :list) (ys RegLan :list)) (re.++ xs (str.to_re "") ys) (re.++ xs ys)) (define-rule re-concat-none ((xs RegLan :list) (ys RegLan :list)) (re.++ xs re.none ys) re.none) @@ -183,6 +217,11 @@ (define-rule re-concat-star-swap ((xs RegLan :list) (r RegLan) (ys RegLan :list)) (re.++ xs (re.* r) r ys) (re.++ xs r (re.* r) ys)) +(define-rule* re-concat-merge ((xs RegLan :list) (s String) (t String) (ys RegLan :list)) + (re.++ xs (str.to_re s) (str.to_re t) ys) + (re.++ xs (str.to_re (str.++ s t)) ys) + _) + (define-rule re-union-all ((xs RegLan :list) (ys RegLan :list)) (re.union xs (re.* re.allchar) ys) (re.* re.allchar)) (define-rule* re-union-none ((xs RegLan :list) (ys RegLan :list)) (re.union xs re.none ys) (re.union xs ys)) (define-rule* re-union-flatten ((xs RegLan :list) (b RegLan) (ys RegLan :list) (zs RegLan :list)) (re.union xs (re.union b ys) zs) (re.union xs b ys zs)) @@ -193,15 +232,19 @@ (define-rule* re-inter-flatten ((xs RegLan :list) (b RegLan) (ys RegLan :list) (zs RegLan :list)) (re.inter xs (re.inter b ys) zs) (re.inter xs b ys zs)) (define-rule* re-inter-dup ((xs RegLan :list) (b RegLan) (ys RegLan :list) (zs RegLan :list)) (re.inter xs b ys b zs) (re.inter xs b ys zs)) -(define-rule* str-len-concat-rec ((s1 String) (s2 String) (s3 String :list)) - (str.len (str.++ s1 s2 s3)) - (str.len (str.++ s2 s3)) - (+ (str.len s1) _)) +(define-cond-rule re-inter-cstring ((xs RegLan :list) (ys RegLan :list) (s String)) + (str.in_re s (re.inter xs ys)) + (re.inter xs (str.to_re s) ys) + (str.to_re s)) + +(define-cond-rule re-inter-cstring-neg ((xs RegLan :list) (ys RegLan :list) (s String)) + (not (str.in_re s (re.inter xs ys))) + (re.inter xs (str.to_re s) ys) + re.none) -(define-cond-rule str-in-re-range-elim ((s String) (c1 String) (c2 String)) - (and (= (str.len c1) 1) (= (str.len c2) 1)) - (str.in_re s (re.range c1 c2)) - (and (<= (str.to_code c1) (str.to_code s)) (<= (str.to_code s) (str.to_code c2)))) +(define-rule str-nth-elim-code ((s String) (n Int)) + (seq.nth s n) + (str.to_code (str.substr s n 1))) ; =============== Sequences-specific rules diff --git a/src/theory/strings/rewrites-regexp-membership b/src/theory/strings/rewrites-regexp-membership new file mode 100644 index 00000000000..07ade93153f --- /dev/null +++ b/src/theory/strings/rewrites-regexp-membership @@ -0,0 +1,185 @@ +; =============== Regular expression membership rules + +(define-rule re-in-empty ((t String)) (str.in_re t re.none) false) +(define-rule re-in-sigma ((t String)) (str.in_re t re.allchar) (= (str.len t) 1)) +(define-rule re-in-sigma-star ((t String)) (str.in_re t (re.* re.allchar)) true) +(define-rule re-in-cstring ((t String) (s String)) (str.in_re t (str.to_re s)) (= t s)) +(define-rule re-in-comp ((t String) (r RegLan)) (str.in_re t (re.comp r)) (not (str.in_re t r))) + +(define-rule* str-in-re-union-elim ((s String) (r1 RegLan) (r2 RegLan) (rs RegLan :list)) + (str.in_re s (re.union r1 r2 rs)) + (str.in_re s (re.union r2 rs)) + (or (str.in_re s r1) _)) + +(define-rule* str-in-re-inter-elim ((s String) (r1 RegLan) (r2 RegLan) (rs RegLan :list)) + (str.in_re s (re.inter r1 r2 rs)) + (str.in_re s (re.inter r2 rs)) + (and (str.in_re s r1) _)) + +(define-cond-rule str-in-re-range-elim ((s String) (c1 String) (c2 String)) + (and (= (str.len c1) 1) (= (str.len c2) 1)) + (str.in_re s (re.range c1 c2)) + (and (<= (str.to_code c1) (str.to_code s)) (<= (str.to_code s) (str.to_code c2)))) + +(define-rule str-in-re-contains ((t String) (s String)) + (str.in_re t (re.++ (re.* re.allchar) (str.to_re s) (re.* re.allchar))) + (str.contains t s)) + +(define-rule* str-in-re-strip-prefix ((s String) (s1 String) (s2 String :list) (sr1 String) (sr2 String :list) (r RegLan :list)) + (str.in_re (str.++ s s1 s2) (re.++ (str.to_re (str.++ s sr1 sr2)) r)) + (str.in_re (str.++ s1 s2) (re.++ (str.to_re (str.++ sr1 sr2)) r)) + _) + +(define-cond-rule str-in-re-strip-prefix-neg ((s String) (t String) (s1 String) (s2 String :list) (sr1 String) (sr2 String :list) (r RegLan :list)) + (and (= (str.len s) (str.len t)) (not (= s t))) + (str.in_re (str.++ s s1 s2) (re.++ (str.to_re (str.++ t sr1 sr2)) r)) + false) + +(define-rule* str-in-re-strip-prefix-sr-single ((s String) (s1 String) (s2 String :list) (r1 RegLan) (r2 RegLan :list)) + (str.in_re (str.++ s s1 s2) (re.++ (str.to_re s) r1 r2)) + (str.in_re (str.++ s1 s2) (re.++ r1 r2)) + _) + +(define-cond-rule str-in-re-strip-prefix-sr-single-neg ((s String) (t String) (s1 String) (s2 String :list) (r1 RegLan) (r2 RegLan :list)) + (and (= (str.len s) (str.len t)) (not (= s t))) + (str.in_re (str.++ s s1 s2) (re.++ (str.to_re t) r1 r2)) + false) + +(define-rule str-in-re-strip-prefix-srs-single ((s String) (r1 RegLan) (r2 RegLan :list)) + (str.in_re s (re.++ (str.to_re s) r1 r2)) + (str.in_re "" (re.++ r1 r2))) + +(define-cond-rule str-in-re-strip-prefix-srs-single-neg ((s String) (t String) (r1 RegLan) (r2 RegLan :list)) + (and (= (str.len s) (str.len t)) (not (= s t))) + (str.in_re s (re.++ (str.to_re t) r1 r2)) + false) + +(define-rule str-in-re-strip-prefix-s-single ((s String) (sr1 String) (sr2 String :list) (r RegLan :list)) + (str.in_re s (re.++ (str.to_re (str.++ s sr1 sr2)) r)) + (str.in_re "" (re.++ (str.to_re (str.++ sr1 sr2)) r))) + +(define-cond-rule str-in-re-strip-prefix-s-single-neg ((s String) (t String) (sr1 String) (sr2 String :list) (r RegLan :list)) + (and (= (str.len s) (str.len t)) (not (= s t))) + (str.in_re s (re.++ (str.to_re (str.++ t sr1 sr2)) r)) + false) + +(define-rule* str-in-re-strip-prefix-base ((s String) (s1 String) (s2 String :list) (sr1 String) (sr2 String :list)) + (str.in_re (str.++ s s1 s2) (str.to_re (str.++ s sr1 sr2))) + (str.in_re (str.++ s1 s2) (str.to_re (str.++ sr1 sr2))) + _) + +(define-cond-rule str-in-re-strip-prefix-base-neg ((s String) (t String) (s1 String) (s2 String :list) (sr1 String) (sr2 String :list)) + (and (= (str.len s) (str.len t)) (not (= s t))) + (str.in_re (str.++ s s1 s2) (str.to_re (str.++ t sr1 sr2))) + false) + +(define-rule str-in-re-strip-prefix-base-s-single ((s String) (sr1 String) (sr2 String :list)) + (str.in_re s (str.to_re (str.++ s sr1 sr2))) + (str.in_re "" (str.to_re (str.++ sr1 sr2)))) + +(define-cond-rule str-in-re-strip-prefix-base-s-single-neg ((s String) (t String) (sr1 String) (sr2 String :list)) + (and (= (str.len s) (str.len t)) (not (= s t))) + (str.in_re s (str.to_re (str.++ t sr1 sr2))) + false) + +(define-cond-rule str-in-re-strip-char ((s String) (s1 String) (s2 String :list) (r1 RegLan) (r2 RegLan :list)) + (= (str.len s) 1) + (str.in_re (str.++ s s1 s2) (re.++ re.allchar r1 r2)) + (str.in_re (str.++ s1 s2) (re.++ r1 r2))) + +(define-cond-rule str-in-re-strip-char-s-single ((s String) (r1 RegLan) (r2 RegLan :list)) + (= (str.len s) 1) + (str.in_re s (re.++ re.allchar r1 r2)) + (str.in_re "" (re.++ r1 r2))) + +(define-rule* str-in-re-strip-prefix-rev ((s String) (s1 String) (s2 String :list) (sr1 String) (sr2 String :list) (r RegLan :list)) + (str.in_re (str.++ s1 s2 s) (re.++ r (str.to_re (str.++ sr1 sr2 s)))) + (str.in_re (str.++ s1 s2) (re.++ r (str.to_re (str.++ sr1 sr2)))) + _) + +(define-cond-rule str-in-re-strip-prefix-neg-rev ((s String) (t String) (s1 String) (s2 String :list) (sr1 String) (sr2 String :list) (r RegLan :list)) + (and (= (str.len s) (str.len t)) (not (= s t))) + (str.in_re (str.++ s1 s2 s) (re.++ r (str.to_re (str.++ sr1 sr2 t)))) + false) + +(define-rule* str-in-re-strip-prefix-sr-single-rev ((s String) (s1 String) (s2 String :list) (r1 RegLan) (r2 RegLan :list)) + (str.in_re (str.++ s1 s2 s) (re.++ r1 r2 (str.to_re s))) + (str.in_re (str.++ s1 s2) (re.++ r1 r2)) + _) + +(define-cond-rule str-in-re-strip-prefix-sr-single-neg-rev ((s String) (t String) (s1 String) (s2 String :list) (r1 RegLan) (r2 RegLan :list)) + (and (= (str.len s) (str.len t)) (not (= s t))) + (str.in_re (str.++ s1 s2 s) (re.++ r1 r2 (str.to_re t))) + false) + +(define-rule str-in-re-strip-prefix-srs-single-rev ((s String) (r1 RegLan) (r2 RegLan :list)) + (str.in_re s (re.++ r1 r2 (str.to_re s))) + (str.in_re "" (re.++ r1 r2))) + +(define-cond-rule str-in-re-strip-prefix-srs-single-neg-rev ((s String) (t String) (r1 RegLan) (r2 RegLan :list)) + (and (= (str.len s) (str.len t)) (not (= s t))) + (str.in_re s (re.++ r1 r2 (str.to_re t))) + false) + +(define-rule str-in-re-strip-prefix-s-single-rev ((s String) (sr1 String) (sr2 String :list) (r RegLan :list)) + (str.in_re s (re.++ r (str.to_re (str.++ sr1 sr2 s)))) + (str.in_re "" (re.++ r (str.to_re (str.++ sr1 sr2))))) + +(define-cond-rule str-in-re-strip-prefix-s-single-neg-rev ((s String) (t String) (sr1 String) (sr2 String :list) (r RegLan :list)) + (and (= (str.len s) (str.len t)) (not (= s t))) + (str.in_re s (re.++ r (str.to_re (str.++ sr1 sr2 t)))) + false) + +(define-rule* str-in-re-strip-prefix-base-rev ((s String) (s1 String) (s2 String :list) (sr1 String) (sr2 String :list)) + (str.in_re (str.++ s1 s2 s) (str.to_re (str.++ sr1 sr2 s))) + (str.in_re (str.++ s1 s2) (str.to_re (str.++ sr1 sr2))) + _) + +(define-cond-rule str-in-re-strip-prefix-base-neg-rev ((s String) (t String) (s1 String) (s2 String :list) (sr1 String) (sr2 String :list)) + (and (= (str.len s) (str.len t)) (not (= s t))) + (str.in_re (str.++ s1 s2 s) (str.to_re (str.++ sr1 sr2 t))) + false) + +(define-rule str-in-re-strip-prefix-base-s-single-rev ((s String) (sr1 String) (sr2 String :list)) + (str.in_re s (str.to_re (str.++ sr1 sr2 s))) + (str.in_re "" (str.to_re (str.++ sr1 sr2)))) + +(define-cond-rule str-in-re-strip-prefix-base-s-single-neg-rev ((s String) (t String) (sr1 String) (sr2 String :list)) + (and (= (str.len s) (str.len t)) (not (= s t))) + (str.in_re s (str.to_re (str.++ sr1 sr2 t))) + false) + +(define-cond-rule str-in-re-strip-char-rev ((s String) (s1 String) (s2 String :list) (r1 RegLan) (r2 RegLan :list)) + (= (str.len s) 1) + (str.in_re (str.++ s1 s2 s) (re.++ r1 r2 re.allchar)) + (str.in_re (str.++ s1 s2) (re.++ r1 r2))) + +(define-cond-rule str-in-re-strip-char-s-single-rev ((s String) (r1 RegLan) (r2 RegLan :list)) + (= (str.len s) 1) + (str.in_re s (re.++ r1 r2 re.allchar)) + (str.in_re "" (re.++ r1 r2))) + +(define-cond-rule str-in-re-no-prefix ((s1 String) (s2 String :list) (r RegLan)) + (not (str.in_re s1 (re.++ r (re.* re.allchar)))) + (str.in_re (str.++ s1 s2) r) + false) +(define-cond-rule str-in-re-no-prefix-rev ((s1 String :list) (s2 String) (r RegLan)) + (not (str.in_re s2 (re.++ (re.* re.allchar) r))) + (str.in_re (str.++ s1 s2) r) + false) + +(define-rule* str-in-re-concat-allchar ((s String) (r1 RegLan) (r2 RegLan :list)) + (str.in_re s (re.++ re.allchar r1 r2)) + (str.in_re (str.substr s 1 (str.len s)) (re.++ r1 r2)) + _ +) + +(define-rule str-in-re-concat-allchar-base-geq ((s String)) + (str.in_re s (re.++ re.allchar (re.* re.allchar))) + (>= (str.len s) 1) +) + +(define-rule str-in-re-concat-allchar-base-eq ((s String)) + (str.in_re s (re.++ re.allchar re.allchar)) + (= (str.len s) 2) +) From c5ba0da878e2bdbb74e7c078a3f7dda4644c6fa8 Mon Sep 17 00:00:00 2001 From: Andrew Reynolds Date: Thu, 30 May 2024 16:37:44 -0500 Subject: [PATCH 12/35] Refactor strings arithmetic entailment utility (#10768) Includes new helper interfaces and adds interface that will be critical for proof support. Also makes it so that the rewriter is optional, since we cannot rely on the rewriter when proofs are enabled. We also can't rely on the search procedure in the proof checker, so we pass a flag isSimple to many methods to ensure we use only "simple" methods for provide when an approximation is sound. This is restricted only if we are attempting a proo rewrite step. This is in preparation for two new THEORY_REWRITE rules for strings. --- .../passes/foreign_theory_rewrite.cpp | 41 +-- .../passes/foreign_theory_rewrite.h | 8 - src/theory/quantifiers/extended_rewrite.cpp | 13 +- src/theory/strings/arith_entail.cpp | 244 ++++++++++++------ src/theory/strings/arith_entail.h | 122 +++++++-- src/theory/strings/sequences_rewriter.cpp | 11 +- test/regress/cli/CMakeLists.txt | 1 + .../str-term-small-rw_529-arith-subs-ent.smt2 | 8 + 8 files changed, 288 insertions(+), 160 deletions(-) create mode 100644 test/regress/cli/regress1/strings/str-term-small-rw_529-arith-subs-ent.smt2 diff --git a/src/preprocessing/passes/foreign_theory_rewrite.cpp b/src/preprocessing/passes/foreign_theory_rewrite.cpp index 22e140c2ba2..86e5ed97e9a 100644 --- a/src/preprocessing/passes/foreign_theory_rewrite.cpp +++ b/src/preprocessing/passes/foreign_theory_rewrite.cpp @@ -96,47 +96,20 @@ Node ForeignTheoryRewriter::foreignRewrite(Node n) Assert(n.getKind() != Kind::GT); Assert(n.getKind() != Kind::LT); Assert(n.getKind() != Kind::LEQ); - // apply rewrites according to the structure of n - if (n.getKind() == Kind::GEQ) + // apply rewrites according to the structure of n. + if ((n.getKind() == Kind::GEQ || n.getKind() == Kind::EQUAL) + && n[0].getType().isInteger()) { - return rewriteStringsGeq(n); - } - else if (n.getKind() == Kind::EQUAL) - { - if (n[0].getType().isInteger()) + theory::strings::ArithEntail ae(d_env.getRewriter()); + Node r = ae.rewritePredViaEntailment(n); + if (!r.isNull()) { - return rewriteStringsEq(n); + return r; } } return n; } -Node ForeignTheoryRewriter::rewriteStringsGeq(Node n) -{ - theory::strings::ArithEntail ae(d_env.getRewriter()); - // check if the node can be simplified to true or false - if (ae.check(n[0], n[1], false)) - { - return nodeManager()->mkConst(true); - } - else if (ae.check(n[1], n[0], true)) - { - return nodeManager()->mkConst(false); - } - return n; -} - -Node ForeignTheoryRewriter::rewriteStringsEq(Node n) -{ - theory::strings::ArithEntail ae(d_env.getRewriter()); - // check if the node can be simplified to false - if (ae.check(n[0], n[1], true) || ae.check(n[1], n[0], true)) - { - return nodeManager()->mkConst(false); - } - return n; -} - Node ForeignTheoryRewriter::reconstructNode(Node originalNode, std::vector newChildren) { diff --git a/src/preprocessing/passes/foreign_theory_rewrite.h b/src/preprocessing/passes/foreign_theory_rewrite.h index 4d0e6aee1bf..238fc21278f 100644 --- a/src/preprocessing/passes/foreign_theory_rewrite.h +++ b/src/preprocessing/passes/foreign_theory_rewrite.h @@ -39,14 +39,6 @@ class ForeignTheoryRewriter : protected EnvObj * does a traversal on n and call rewriting fucntions. */ Node simplify(Node n); - /** A specific simplification function specific for GEQ - * constraints in strings. - */ - Node rewriteStringsGeq(Node n); - /** A specific simplification function for EQUAL - * constraints in strings. - */ - Node rewriteStringsEq(Node n); /** invoke rewrite functions for n. * based on the structure of n (typically its kind) * we invoke rewrites from other theories. diff --git a/src/theory/quantifiers/extended_rewrite.cpp b/src/theory/quantifiers/extended_rewrite.cpp index 23236962912..1ec5e21eab4 100644 --- a/src/theory/quantifiers/extended_rewrite.cpp +++ b/src/theory/quantifiers/extended_rewrite.cpp @@ -236,9 +236,9 @@ Node ExtendedRewriter::extendedRewrite(Node n) const else if (ret[0].getType().isInteger()) { theory::strings::ArithEntail ae(&d_rew); - if (ae.check(ret[0], ret[1], true) || ae.check(ret[1], ret[0], true)) + new_ret = ae.rewritePredViaEntailment(ret); + if (!new_ret.isNull()) { - new_ret = d_false; debugExtendedRewrite(ret, new_ret, "String EQUAL len entailment"); } } @@ -248,16 +248,11 @@ Node ExtendedRewriter::extendedRewrite(Node n) const if (ret[0].getType().isInteger()) { theory::strings::ArithEntail ae(&d_rew); - if (ae.check(ret[0], ret[1], false)) + new_ret = ae.rewritePredViaEntailment(ret); + if (!new_ret.isNull()) { - new_ret = d_true; debugExtendedRewrite(ret, new_ret, "String GEQ len entailment"); } - else if (ae.check(ret[1], ret[0], true)) - { - new_ret = d_false; - debugExtendedRewrite(ret, new_ret, "String GEQ len strict entailment"); - } } } Assert(new_ret.isNull() || new_ret != ret); diff --git a/src/theory/strings/arith_entail.cpp b/src/theory/strings/arith_entail.cpp index c49579f1c52..67371c95bd1 100644 --- a/src/theory/strings/arith_entail.cpp +++ b/src/theory/strings/arith_entail.cpp @@ -18,6 +18,8 @@ #include "expr/attribute.h" #include "expr/node_algorithm.h" #include "theory/arith/arith_msum.h" +#include "theory/arith/arith_poly_norm.h" +#include "theory/arith/arith_subs.h" #include "theory/rewriter.h" #include "theory/strings/theory_strings_utils.h" #include "theory/strings/word.h" @@ -32,10 +34,71 @@ namespace strings { ArithEntail::ArithEntail(Rewriter* r) : d_rr(r) { + d_one = NodeManager::currentNM()->mkConstInt(Rational(1)); d_zero = NodeManager::currentNM()->mkConstInt(Rational(0)); } -Node ArithEntail::rewrite(Node a) { return d_rr->rewrite(a); } +Node ArithEntail::rewritePredViaEntailment(const Node& n, bool isSimple) +{ + Node exp; + return rewritePredViaEntailment(n, exp, isSimple); +} + +Node ArithEntail::rewritePredViaEntailment(const Node& n, + Node& exp, + bool isSimple) +{ + NodeManager* nm = NodeManager::currentNM(); + if (n.getKind() == Kind::EQUAL && n[0].getType().isInteger()) + { + exp = nm->mkNode(Kind::SUB, nm->mkNode(Kind::SUB, n[0], n[1]), d_one); + if (!findApprox(rewriteArith(exp), isSimple).isNull()) + { + return nm->mkConst(false); + } + exp = nm->mkNode(Kind::SUB, nm->mkNode(Kind::SUB, n[1], n[0]), d_one); + if (!findApprox(rewriteArith(exp), isSimple).isNull()) + { + return nm->mkConst(false); + } + exp = Node::null(); + if (checkEq(n[0], n[1])) + { + // explanation is null + return nm->mkConst(true); + } + } + else if (n.getKind() == Kind::GEQ) + { + exp = nm->mkNode(Kind::SUB, n[0], n[1]); + if (!findApprox(rewriteArith(exp), isSimple).isNull()) + { + return nm->mkConst(true); + } + exp = nm->mkNode(Kind::SUB, nm->mkNode(Kind::SUB, n[1], n[0]), d_one); + if (!findApprox(rewriteArith(exp), isSimple).isNull()) + { + return nm->mkConst(false); + } + exp = Node::null(); + } + return Node::null(); +} + +Node ArithEntail::rewriteArith(Node a) +{ + AlwaysAssert(a.getType().isInteger()) + << "Bad term: " << a << " " << a.getType(); + if (d_rr != nullptr) + { + return d_rr->rewrite(a); + } + // Otherwise, use the poly norm utility. This is important since the rewrite + // must be justified by ARITH_POLY_NORM when in proof mode (when d_rr is + // null). + Node an = arith::PolyNorm::getPolyNorm(a); + return an; +} bool ArithEntail::checkEq(Node a, Node b) { @@ -43,64 +106,64 @@ bool ArithEntail::checkEq(Node a, Node b) { return true; } - Node ar = d_rr->rewrite(a); - Node br = d_rr->rewrite(b); + Node ar = rewriteArith(a); + Node br = rewriteArith(b); return ar == br; } -bool ArithEntail::check(Node a, Node b, bool strict) +bool ArithEntail::check(Node a, Node b, bool strict, bool isSimple) { if (a == b) { return !strict; } Node diff = NodeManager::currentNM()->mkNode(Kind::SUB, a, b); - return check(diff, strict); + return check(diff, strict, isSimple); } -struct StrCheckEntailArithTag -{ -}; -struct StrCheckEntailArithComputedTag -{ -}; -/** Attribute true for expressions for which check returned true */ -typedef expr::Attribute StrCheckEntailArithAttr; -typedef expr::Attribute - StrCheckEntailArithComputedAttr; - -bool ArithEntail::check(Node a, bool strict) +bool ArithEntail::check(Node a, bool strict, bool isSimple) { if (a.isConst()) { return a.getConst().sgn() >= (strict ? 1 : 0); } - - Node ar = strict ? NodeManager::currentNM()->mkNode( - Kind::SUB, a, NodeManager::currentNM()->mkConstInt(Rational(1))) - : a; - ar = d_rr->rewrite(ar); - - if (ar.getAttribute(StrCheckEntailArithComputedAttr())) + Node ar = strict ? NodeManager::currentNM()->mkNode(Kind::SUB, a, d_one) : a; + ar = rewriteArith(ar); + // if simple, just call the checkSimple routine. + if (isSimple) { - return ar.getAttribute(StrCheckEntailArithAttr()); + return checkSimple(ar); } + Node ara = findApprox(ar, isSimple); + return !ara.isNull(); +} - bool ret = checkInternal(ar); - if (!ret) +Node ArithEntail::findApprox(Node ar, bool isSimple) +{ + std::map& cache = isSimple ? d_approxCacheSimple : d_approxCache; + std::map::iterator it = cache.find(ar); + if (it != cache.end()) { - // try with approximations - ret = checkApprox(ar); + return it->second; } - // cache the result - ar.setAttribute(StrCheckEntailArithAttr(), ret); - ar.setAttribute(StrCheckEntailArithComputedAttr(), true); + Node ret; + if (checkSimple(ar)) + { + // didn't need approximation + ret = ar; + } + else + { + ret = findApproxInternal(ar, isSimple); + } + cache[ar] = ret; return ret; } -bool ArithEntail::checkApprox(Node ar) +Node ArithEntail::findApproxInternal(Node ar, bool isSimple) { - Assert(d_rr->rewrite(ar) == ar); + Assert(rewriteArith(ar) == ar) + << "Not rewritten " << ar << ", got " << rewriteArith(ar); NodeManager* nm = NodeManager::currentNM(); std::map msum; Trace("strings-ent-approx-debug") @@ -109,7 +172,7 @@ bool ArithEntail::checkApprox(Node ar) { Trace("strings-ent-approx-debug") << "...failed to get monomial sum!" << std::endl; - return false; + return Node::null(); } // for each monomial v*c, mApprox[v] a list of // possibilities for how the term can be soundly approximated, that is, @@ -123,6 +186,8 @@ bool ArithEntail::checkApprox(Node ar) std::map > approxMsums; // aarSum stores each monomial that does not have multiple approximations std::vector aarSum; + // stores the witness + arith::ArithSubs approxMap; for (std::pair& m : msum) { Node v = m.first; @@ -146,13 +211,13 @@ bool ArithEntail::checkApprox(Node ar) { Node curr = toProcess.back(); Trace("strings-ent-approx-debug") << " process " << curr << std::endl; - curr = d_rr->rewrite(curr); + curr = rewriteArith(curr); toProcess.pop_back(); if (visited.find(curr) == visited.end()) { visited.insert(curr); std::vector currApprox; - getArithApproximations(curr, currApprox, isOverApprox); + getArithApproximations(curr, currApprox, isOverApprox, isSimple); if (currApprox.empty()) { Trace("strings-ent-approx-debug") @@ -160,6 +225,11 @@ bool ArithEntail::checkApprox(Node ar) // no approximations, thus curr is a possibility approx.push_back(curr); } + else if (isSimple) + { + // don't rewrite or re-approximate + approx = currApprox; + } else { toProcess.insert( @@ -171,7 +241,14 @@ bool ArithEntail::checkApprox(Node ar) // if we have only one approximation, move it to final if (approx.size() == 1) { - changed = v != approx[0]; + if (v != approx[0]) + { + changed = true; + Trace("strings-ent-approx") + << "- Propagate (" << (d_rr == nullptr) << ", " << isSimple + << ") " << v << " = " << approx[0] << std::endl; + approxMap.add(v, approx[0]); + } Node mn = ArithMSum::mkCoeffTerm(c, approx[0]); aarSum.push_back(mn); mApprox.erase(v); @@ -196,14 +273,14 @@ bool ArithEntail::checkApprox(Node ar) { // approximations had no effect, return Trace("strings-ent-approx-debug") << "...no approximations" << std::endl; - return false; + return Node::null(); } // get the current "fixed" sum for the abstraction of ar Node aar = aarSum.empty() ? d_zero : (aarSum.size() == 1 ? aarSum[0] : nm->mkNode(Kind::ADD, aarSum)); - aar = d_rr->rewrite(aar); + aar = rewriteArith(aar); Trace("strings-ent-approx-debug") << "...processed fixed sum " << aar << " with " << mApprox.size() << " approximated monomials." << std::endl; @@ -214,7 +291,7 @@ bool ArithEntail::checkApprox(Node ar) std::map msumAar; if (!ArithMSum::getMonomialSum(aar, msumAar)) { - return false; + return Node::null(); } if (TraceIsOn("strings-ent-approx")) { @@ -275,7 +352,7 @@ bool ArithEntail::checkApprox(Node ar) if (!cr.isNull()) { ci = ci.isNull() ? cr - : d_rr->rewrite(nm->mkNode(Kind::MULT, ci, cr)); + : rewriteArith(nm->mkNode(Kind::MULT, ci, cr)); } Trace("strings-ent-approx-debug") << ci << "*" << ti << " "; int ciSgn = ci.isNull() ? 1 : ci.getConst().sgn(); @@ -323,23 +400,24 @@ bool ArithEntail::checkApprox(Node ar) break; } } - Trace("strings-ent-approx") - << "- Decide " << v << " = " << vapprox << std::endl; + Trace("strings-ent-approx") << "- Decide (" << (d_rr == nullptr) << ") " + << v << " = " << vapprox << std::endl; // we incorporate v approximated by vapprox into the overall approximation // for ar Assert(!v.isNull() && !vapprox.isNull()); Assert(msum.find(v) != msum.end()); Node mn = ArithMSum::mkCoeffTerm(msum[v], vapprox); aar = nm->mkNode(Kind::ADD, aar, mn); + approxMap.add(v, vapprox); // update the msumAar map - aar = d_rr->rewrite(aar); + aar = rewriteArith(aar); msumAar.clear(); if (!ArithMSum::getMonomialSum(aar, msumAar)) { Assert(false); Trace("strings-ent-approx") << "...failed to get monomial sum!" << std::endl; - return false; + return Node::null(); } // we have processed the approximation for v mApprox.erase(v); @@ -352,7 +430,7 @@ bool ArithEntail::checkApprox(Node ar) << "...approximation had no effect" << std::endl; // this should never happen, but we avoid the infinite loop for sanity here Assert(false); - return false; + return Node::null(); } // Check entailment on the approximation of ar. // Notice that this may trigger further reasoning by approximation. For @@ -362,21 +440,28 @@ bool ArithEntail::checkApprox(Node ar) // len( substr( x, 0, n ) ) as len( x ). In this example, we can infer // that len( replace( x ++ y, substr( x, 0, n ), z ) ) >= len( y ) in two // steps. - if (check(aar)) + if (check(aar, false, isSimple)) { Trace("strings-ent-approx") << "*** StrArithApprox: showed " << ar << " >= 0 using under-approximation!" << std::endl; Trace("strings-ent-approx") - << "*** StrArithApprox: under-approximation was " << aar << std::endl; - return true; + << "*** StrArithApprox: rewritten was " << aar << std::endl; + // Apply arithmetic substitution, which ensures we only replace terms + // in the top-level arithmetic skeleton of ar. + Node approx = approxMap.applyArith(ar); + Trace("strings-ent-approx") + << "*** StrArithApprox: under-approximation was " << approx + << std::endl; + return approx; } - return false; + return Node::null(); } void ArithEntail::getArithApproximations(Node a, std::vector& approx, - bool isOverApprox) + bool isOverApprox, + bool isSimple) { NodeManager* nm = NodeManager::currentNM(); // We do not handle ADD here since this leads to exponential behavior. @@ -392,7 +477,8 @@ void ArithEntail::getArithApproximations(Node a, if (ArithMSum::getMonomial(a, c, v)) { bool isNeg = c.getConst().sgn() == -1; - getArithApproximations(v, approx, isNeg ? !isOverApprox : isOverApprox); + getArithApproximations( + v, approx, isNeg ? !isOverApprox : isOverApprox, isSimple); for (unsigned i = 0, size = approx.size(); i < size; i++) { approx[i] = nm->mkNode(Kind::MULT, c, approx[i]); @@ -410,11 +496,11 @@ void ArithEntail::getArithApproximations(Node a, { // m >= 0 implies // m >= len( substr( x, n, m ) ) - if (check(a[0][2])) + if (check(a[0][2], false, isSimple)) { approx.push_back(a[0][2]); } - if (check(lenx, a[0][1])) + if (check(lenx, a[0][1], false, isSimple)) { // n <= len( x ) implies // len( x ) - n >= len( substr( x, n, m ) ) @@ -431,13 +517,15 @@ void ArithEntail::getArithApproximations(Node a, // 0 <= n and n+m <= len( x ) implies // m <= len( substr( x, n, m ) ) Node npm = nm->mkNode(Kind::ADD, a[0][1], a[0][2]); - if (check(a[0][1]) && check(lenx, npm)) + if (check(a[0][1], false, isSimple) + && check(lenx, npm, false, isSimple)) { approx.push_back(a[0][2]); } // 0 <= n and n+m >= len( x ) implies // len(x)-n <= len( substr( x, n, m ) ) - if (check(a[0][1]) && check(npm, lenx)) + if (check(a[0][1], false, isSimple) + && check(npm, lenx, false, isSimple)) { approx.push_back(nm->mkNode(Kind::SUB, lenx, a[0][1])); } @@ -452,7 +540,7 @@ void ArithEntail::getArithApproximations(Node a, Node lenz = nm->mkNode(Kind::STRING_LENGTH, a[0][2]); if (isOverApprox) { - if (check(leny, lenz)) + if (check(leny, lenz, false, isSimple)) { // len( y ) >= len( z ) implies // len( x ) >= len( replace( x, y, z ) ) @@ -466,7 +554,8 @@ void ArithEntail::getArithApproximations(Node a, } else { - if (check(lenz, leny) || check(lenz, lenx)) + if (check(lenz, leny, false, isSimple) + || check(lenz, lenx, false, isSimple)) { // len( y ) <= len( z ) or len( x ) <= len( z ) implies // len( x ) <= len( replace( x, y, z ) ) @@ -484,9 +573,9 @@ void ArithEntail::getArithApproximations(Node a, // over,under-approximations for len( int.to.str( x ) ) if (isOverApprox) { - if (check(a[0][0], false)) + if (check(a[0][0], false, isSimple)) { - if (check(a[0][0], true)) + if (check(a[0][0], true, isSimple)) { // x > 0 implies // x >= len( int.to.str( x ) ) @@ -503,7 +592,7 @@ void ArithEntail::getArithApproximations(Node a, } else { - if (check(a[0][0])) + if (check(a[0][0], false, isSimple)) { // x >= 0 implies // len( int.to.str( x ) ) >= 1 @@ -521,7 +610,7 @@ void ArithEntail::getArithApproximations(Node a, { Node lenx = nm->mkNode(Kind::STRING_LENGTH, a[0]); Node leny = nm->mkNode(Kind::STRING_LENGTH, a[1]); - if (check(lenx, leny)) + if (check(lenx, leny, false, isSimple)) { // len( x ) >= len( y ) implies // len( x ) - len( y ) >= indexof( x, y, n ) @@ -559,13 +648,13 @@ void ArithEntail::getArithApproximations(Node a, approx.push_back(nm->mkConstInt(Rational(-1))); } } - Trace("strings-ent-approx-debug") << "Return " << approx.size() << std::endl; + Trace("strings-ent-approx-debug") + << "Return " << approx.size() << " approximations" << std::endl; } bool ArithEntail::checkWithEqAssumption(Node assumption, Node a, bool strict) { Assert(assumption.getKind() == Kind::EQUAL); - Assert(d_rr->rewrite(assumption) == assumption); Trace("strings-entail") << "checkWithEqAssumption: " << assumption << " " << a << ", strict=" << strict << std::endl; @@ -642,8 +731,6 @@ bool ArithEntail::checkWithAssumption(Node assumption, Node b, bool strict) { - Assert(d_rr->rewrite(assumption) == assumption); - NodeManager* nm = NodeManager::currentNM(); if (!assumption.isConst() && assumption.getKind() != Kind::EQUAL) @@ -668,8 +755,16 @@ bool ArithEntail::checkWithAssumption(Node assumption, Node s = nm->mkBoundVar("slackVal", nm->stringType()); Node slen = nm->mkNode(Kind::STRING_LENGTH, s); - assumption = d_rr->rewrite( - nm->mkNode(Kind::EQUAL, x, nm->mkNode(Kind::ADD, y, slen))); + Node sleny = nm->mkNode(Kind::ADD, y, slen); + Node rr = rewriteArith(nm->mkNode(Kind::SUB, x, sleny)); + if (rr.isConst()) + { + assumption = nm->mkConst(rr.getConst().sgn() == 0); + } + else + { + assumption = nm->mkNode(Kind::EQUAL, x, sleny); + } } Node diff = nm->mkNode(Kind::SUB, a, b); @@ -704,8 +799,6 @@ bool ArithEntail::checkWithAssumptions(std::vector assumptions, bool res = false; for (const auto& assumption : assumptions) { - Assert(d_rr->rewrite(assumption) == assumption); - if (checkWithAssumption(assumption, a, b, strict)) { res = true; @@ -766,7 +859,7 @@ bool ArithEntail::getConstantBoundCache(TNode n, bool isLower, Node& c) Node ArithEntail::getConstantBound(TNode a, bool isLower) { - Assert(d_rr->rewrite(a) == a); + Assert(rewriteArith(a) == a); Node ret; if (getConstantBoundCache(a, isLower, ret)) { @@ -835,7 +928,7 @@ Node ArithEntail::getConstantBound(TNode a, bool isLower) else { ret = NodeManager::currentNM()->mkNode(a.getKind(), children); - ret = d_rr->rewrite(ret); + ret = rewriteArith(ret); } } } @@ -906,9 +999,8 @@ Node ArithEntail::getConstantBoundLength(TNode s, bool isLower) const return ret; } -bool ArithEntail::checkInternal(Node a) +bool ArithEntail::checkSimple(Node a) { - Assert(d_rr->rewrite(a) == a); // check whether a >= 0 if (a.isConst()) { @@ -923,7 +1015,7 @@ bool ArithEntail::checkInternal(Node a) { for (unsigned i = 0; i < a.getNumChildren(); i++) { - if (!checkInternal(a[i])) + if (!checkSimple(a[i])) { return false; } diff --git a/src/theory/strings/arith_entail.h b/src/theory/strings/arith_entail.h index 34d0f16bd4f..cb59455af23 100644 --- a/src/theory/strings/arith_entail.h +++ b/src/theory/strings/arith_entail.h @@ -37,40 +37,39 @@ namespace strings { class ArithEntail { public: + /** + * @param r The rewriter, used for rewriting arithmetic terms. If none + * is provided, we rely on the ArithPolyNorm utility. + */ ArithEntail(Rewriter* r); /** - * Returns the rewritten form a term, intended (although not enforced) to be - * an arithmetic term. + * Returns the rewritten form of a term, which must be an integer term. + * This method invokes the rewriter, if one is provided, and uses the + * ArithPolyNorm utility (arith/arith_poly_norm.h) otherwise. */ - Node rewrite(Node a); + Node rewriteArith(Node a); /** check arithmetic entailment equal * Returns true if it is always the case that a = b. */ bool checkEq(Node a, Node b); /** check arithmetic entailment - * Returns true if it is always the case that a >= b, - * and a>b if strict is true. + * @param a The first term. + * @param b The second term + * @param strict Whether we are testing strict inequality. + * @param isSimple If true, then we do not use approximations for recursive + * calls when computing approximations. + * @return true if it is always the case a >= b, or a > b if strict is true. */ - bool check(Node a, Node b, bool strict = false); + bool check(Node a, Node b, bool strict = false, bool isSimple = false); /** check arithmetic entailment * Returns true if it is always the case that a >= 0. + * @param a The term. + * @param strict Whether we are testing strict inequality. + * @param isSimple If true, then we do not use approximations for recursive + * calls when computing approximations. + * @return true if it is always the case a >= 0, or a > 0 if strict is true. */ - bool check(Node a, bool strict = false); - /** check arithmetic entailment with approximations - * - * Returns true if it is always the case that a >= 0. We expect that a is in - * rewritten form. - * - * This function uses "approximation" techniques that under-approximate - * the value of a for the purposes of showing the entailment holds. For - * example, given: - * len( x ) - len( substr( y, 0, len( x ) ) ) - * Since we know that len( substr( y, 0, len( x ) ) ) <= len( x ), the above - * term can be under-approximated as len( x ) - len( x ) = 0, which is >= 0, - * and thus the entailment len( x ) - len( substr( y, 0, len( x ) ) ) >= 0 - * holds. - */ - bool checkApprox(Node a); + bool check(Node a, bool strict = false, bool isSimple = false); /** * Checks whether assumption |= a >= 0 (if strict is false) or @@ -168,12 +167,73 @@ class ArithEntail std::vector& ys, std::vector& zeroYs); + /** + * Find approximation of a such that it can be shown to be greater than + * zero. + * + * Returns a non-null node if it is always the case that a >= 0. We expect + * that a is in rewritten form. + * + * This function uses "approximation" techniques that under-approximate + * the value of a for the purposes of showing the entailment holds. For + * example, given: + * len( x ) - len( substr( y, 0, len( x ) ) ) + * Since we know that len( substr( y, 0, len( x ) ) ) <= len( x ), the above + * term can be under-approximated as len( x ) - len( x ) = 0, which is >= 0, + * and thus the entailment len( x ) - len( substr( y, 0, len( x ) ) ) >= 0 + * holds. + * + * @param a The node to find approximations for. + * @param isSimple If true, then we are only making recursive calls to check + * without approximations to determine the set of possible approximations. + * @return The approximated form of a, call it aa, such that a >= aa is + * entailed by the theory, and aa can be shown to be greater than zero (using + * checkSimple). + */ + Node findApprox(Node a, bool isSimple = false); + + /** + * Check entail arithmetic simple. + * Returns true if we can show a >= 0 always. This uses the fact that + * string length is non-negative but otherwise uses only basic properties + * of arithmetic. + * This method assumes that a is in rewritten form. + */ + static bool checkSimple(Node a); + + /** + * Rewrite the arithmetic predicate n based on string arithmetic entailment. + * + * We require that n is either an equality or GEQ between integers. + * + * This returns the term true or false if it is the case that n can be + * rewritten to that constant. This method returns null otherwise. + * + * If this returns a non-null node, then exp is updated to the term that + * we proved was always greater than or equal to zero. For example, + * given input: + * (= (str.len x) (- 5)), we return the false term and set exp to + * (- (- (str.len x) (- 5)) 1), since this term is always non-negative. + * + * @param n The arithmetic predicate (EQUAL or GEQ) to rewrite. + * @param exp The explanation term, if we return non-null. + * @return the Boolean constant that n can be rewritten to, or null if none + * exists. + */ + Node rewritePredViaEntailment(const Node& n, + Node& exp, + bool isSimple = false); + /** + * Same as above, without an explanation. + */ + Node rewritePredViaEntailment(const Node& n, bool isSimple = false); + private: - /** check entail arithmetic internal - * Returns true if we can show a >= 0 always. - * a is in rewritten form. + /** + * Helper for findApprox, called when the approximation for a is not in the + * cache. */ - bool checkInternal(Node a); + Node findApproxInternal(Node a, bool isSimple); /** Get arithmetic approximations * * This gets the (set of) arithmetic approximations for term a and stores @@ -191,7 +251,8 @@ class ArithEntail */ void getArithApproximations(Node a, std::vector& approx, - bool isOverApprox = false); + bool isOverApprox = false, + bool isSimple = false); /** Set bound cache */ static void setConstantBoundCache(TNode n, Node ret, bool isLower); /** @@ -199,10 +260,15 @@ class ArithEntail * computed. Used for getConstantBound and getConstantBoundLength. */ static bool getConstantBoundCache(TNode n, bool isLower, Node& c); - /** The underlying rewriter */ + /** The underlying rewriter, if one exists */ Rewriter* d_rr; /** Constant zero */ Node d_zero; + Node d_one; + /** A cache for findApprox above */ + std::map d_approxCache; + /** A cache for findApprox above when isSimple is true */ + std::map d_approxCacheSimple; }; } // namespace strings diff --git a/src/theory/strings/sequences_rewriter.cpp b/src/theory/strings/sequences_rewriter.cpp index a13617cd203..73697673287 100644 --- a/src/theory/strings/sequences_rewriter.cpp +++ b/src/theory/strings/sequences_rewriter.cpp @@ -2054,9 +2054,9 @@ Node SequencesRewriter::rewriteSubstr(Node node) else if (r == 1) { Node tot_len = - d_arithEntail.rewrite(nm->mkNode(Kind::STRING_LENGTH, node[0])); + d_arithEntail.rewriteArith(nm->mkNode(Kind::STRING_LENGTH, node[0])); Node end_pt = - d_arithEntail.rewrite(nm->mkNode(Kind::ADD, node[1], node[2])); + d_arithEntail.rewriteArith(nm->mkNode(Kind::ADD, node[1], node[2])); if (node[2] != tot_len) { if (d_arithEntail.check(node[2], tot_len)) @@ -2068,7 +2068,8 @@ Node SequencesRewriter::rewriteSubstr(Node node) else { // strip up to ( str.len(node[0]) - end_pt ) off the end of the string - curr = d_arithEntail.rewrite(nm->mkNode(Kind::SUB, tot_len, end_pt)); + curr = d_arithEntail.rewriteArith( + nm->mkNode(Kind::SUB, tot_len, end_pt)); } } } @@ -2109,8 +2110,8 @@ Node SequencesRewriter::rewriteSubstr(Node node) // the length of a string from the inner substr subtracts the start point // of the outer substr - Node len_from_inner = - d_arithEntail.rewrite(nm->mkNode(Kind::SUB, node[0][2], start_outer)); + Node len_from_inner = d_arithEntail.rewriteArith( + nm->mkNode(Kind::SUB, node[0][2], start_outer)); Node len_from_outer = node[2]; Node new_len; // take quantity that is for sure smaller than the other diff --git a/test/regress/cli/CMakeLists.txt b/test/regress/cli/CMakeLists.txt index 73f83d61806..7fde9570087 100644 --- a/test/regress/cli/CMakeLists.txt +++ b/test/regress/cli/CMakeLists.txt @@ -3250,6 +3250,7 @@ set(regress_1_tests regress1/strings/str-code-unsat-3.smt2 regress1/strings/str-code-unsat.smt2 regress1/strings/str-pred-small-rw_392.smt2 + regress1/strings/str-term-small-rw_529-arith-subs-ent.smt2 regress1/strings/str-rev-simple-s.smt2 regress1/strings/str-term-small-rw_545.smt2 regress1/strings/str001.smt2 diff --git a/test/regress/cli/regress1/strings/str-term-small-rw_529-arith-subs-ent.smt2 b/test/regress/cli/regress1/strings/str-term-small-rw_529-arith-subs-ent.smt2 new file mode 100644 index 00000000000..c2133a6cc02 --- /dev/null +++ b/test/regress/cli/regress1/strings/str-term-small-rw_529-arith-subs-ent.smt2 @@ -0,0 +1,8 @@ +; EXPECT: unsat +(set-logic QF_SLIA) +(declare-fun x () String) +(declare-fun y () String) +(declare-fun z () Int) +(assert (not (= (str.replace x (str.at x 0) "") (str.substr x 1 (str.len x))))) +(check-sat) +(exit) From f1816c75c9151cfcc24b8e1be74a9a93056d77a5 Mon Sep 17 00:00:00 2001 From: Andrew Reynolds Date: Fri, 31 May 2024 18:07:22 -0500 Subject: [PATCH 13/35] Add implementation of proof rewrite rules for arithmetic string entailment (#10834) This adds one simple theory rewrite (division by constant) and two key rewrites involving arithmetic entailment for strings. Notice that we must register these rewrites with the arithmetic rewriter, since arithmetic owns >=, despite the reasoning being primarily strings. The latter 2 rules will be used to elaborate a forthcoming MACRO_ARITH_STRING_PRED_ENTAIL theory rewrite, which will be the central tactic for proving arithmetic entailments involving strings. --- include/cvc5/cvc5_proof_rule.h | 44 +++++++++++++++++ src/api/cpp/cvc5_proof_rule_template.cpp | 6 +++ src/theory/arith/arith_rewriter.cpp | 60 ++++++++++++++++++++++++ src/theory/arith/arith_rewriter.h | 9 ++++ 4 files changed, 119 insertions(+) diff --git a/include/cvc5/cvc5_proof_rule.h b/include/cvc5/cvc5_proof_rule.h index 013bc6b5192..d748d75c2c7 100644 --- a/include/cvc5/cvc5_proof_rule.h +++ b/include/cvc5/cvc5_proof_rule.h @@ -2324,6 +2324,50 @@ enum ENUM(ProofRewriteRule) : uint32_t * \endverbatim */ EVALUE(MACRO_BOOL_NNF_NORM), + /** + * \verbatim embed:rst:leading-asterisk + * **Arith -- Division by constant elimination** + * + * .. math:: + * t / c = t * 1/c + * + * where :math:`c` is a constant. + * + * \endverbatim + */ + EVALUE(ARITH_DIV_BY_CONST_ELIM), + /** + * \verbatim embed:rst:leading-asterisk + * **Arithmetic - strings predicate entailment** + * + * .. math:: + * (>= n 0) = true + * + * Where :math:`n` can be shown to be greater than or equal to :math:`0` by + * reasoning about string length being positive and basic properties of + * addition and multiplication. + * + * \endverbatim + */ + EVALUE(ARITH_STRING_PRED_ENTAIL), + /** + * \verbatim embed:rst:leading-asterisk + * **Arithmetic - strings predicate entailment** + * + * .. math:: + * (>= n 0) = (>= m 0) + * + * Where :math:`m` is a safe under-approximation of :math:`n`, namely + * we have that :math:`(>= n m)` and :math:`(>= m 0)`. + * + * In detail, subterms of :math:`n` may be replaced with other terms to + * obtain :math:`m` based on the reasoning described in the paper + * Reynolds et al, CAV 2019, "High-Level Abstractions for Simplifying + * Extended String Constraints in SMT". + * + * \endverbatim + */ + EVALUE(ARITH_STRING_PRED_SAFE_APPROX), /** * \verbatim embed:rst:leading-asterisk * **Equality -- Beta reduction** diff --git a/src/api/cpp/cvc5_proof_rule_template.cpp b/src/api/cpp/cvc5_proof_rule_template.cpp index eb7339b6605..7048b7684fa 100644 --- a/src/api/cpp/cvc5_proof_rule_template.cpp +++ b/src/api/cpp/cvc5_proof_rule_template.cpp @@ -222,6 +222,12 @@ const char* toString(cvc5::ProofRewriteRule rule) //================================================= ad-hoc rules case ProofRewriteRule::DISTINCT_ELIM: return "distinct-elim"; case ProofRewriteRule::MACRO_BOOL_NNF_NORM: return "macro-bool-nnf-norm"; + case ProofRewriteRule::ARITH_DIV_BY_CONST_ELIM: + return "arith-div-by-const-elim"; + case ProofRewriteRule::ARITH_STRING_PRED_ENTAIL: + return "arith-string-pred-entail"; + case ProofRewriteRule::ARITH_STRING_PRED_SAFE_APPROX: + return "arith-string-pred-safe-approx"; case ProofRewriteRule::BETA_REDUCE: return "beta-reduce"; case ProofRewriteRule::ARRAYS_EQ_RANGE_EXPAND: return "arrays-eq-range-expand"; diff --git a/src/theory/arith/arith_rewriter.cpp b/src/theory/arith/arith_rewriter.cpp index 5cb900e4f8b..d93e3d57fce 100644 --- a/src/theory/arith/arith_rewriter.cpp +++ b/src/theory/arith/arith_rewriter.cpp @@ -34,6 +34,8 @@ #include "theory/arith/rewriter/node_utils.h" #include "theory/arith/rewriter/ordering.h" #include "theory/arith/rewriter/rewrite_atom.h" +#include "theory/rewriter.h" +#include "theory/strings/arith_entail.h" #include "theory/theory.h" #include "util/bitvector.h" #include "util/divisible.h" @@ -49,6 +51,64 @@ namespace arith { ArithRewriter::ArithRewriter(NodeManager* nm, OperatorElim& oe) : TheoryRewriter(nm), d_opElim(oe) { + registerProofRewriteRule(ProofRewriteRule::ARITH_DIV_BY_CONST_ELIM, + TheoryRewriteCtx::PRE_DSL); + // we don't register ARITH_STRING_PRED_ENTAIL or ARITH_STRING_PRED_SAFE_APPROX, + // as these are subsumed by MACRO_ARITH_STRING_PRED_ENTAIL. +} + +Node ArithRewriter::rewriteViaRule(ProofRewriteRule id, const Node& n) +{ + switch (id) + { + case ProofRewriteRule::ARITH_DIV_BY_CONST_ELIM: + { + if (n.getKind() == Kind::DIVISION && n[1].isConst()) + { + Rational r = n[1].getConst(); + if (r.sgn() != 0) + { + Rational rinv = Rational(1) / r; + NodeManager* nm = nodeManager(); + return nm->mkNode(Kind::MULT, n[0], nm->mkConstReal(rinv)); + } + } + } + break; + case ProofRewriteRule::ARITH_STRING_PRED_ENTAIL: + case ProofRewriteRule::ARITH_STRING_PRED_SAFE_APPROX: + { + if (n.getKind() != Kind::GEQ || !n[1].isConst() + || n[1].getConst().sgn() != 0) + { + return Node::null(); + } + if (id == ProofRewriteRule::ARITH_STRING_PRED_ENTAIL) + { + if (theory::strings::ArithEntail::checkSimple(n[0])) + { + return nodeManager()->mkConst(true); + } + } + else if (id == ProofRewriteRule::ARITH_STRING_PRED_SAFE_APPROX) + { + // Note that we do *not* pass a rewriter here, since the proof rule + // cannot depend on the rewriter. + theory::strings::ArithEntail ae(nullptr); + // must only use simple checks when computing the approximations + Node approx = ae.findApprox(n[0], true); + if (approx != n[0]) + { + Trace("arith-rewriter-proof") + << n[0] << " --> " << approx << " by safe approx" << std::endl; + return nodeManager()->mkNode(Kind::GEQ, approx, n[1]); + } + } + } + break; + default: break; + } + return Node::null(); } RewriteResponse ArithRewriter::preRewrite(TNode t) diff --git a/src/theory/arith/arith_rewriter.h b/src/theory/arith/arith_rewriter.h index 694210bc68d..e00bbabaafb 100644 --- a/src/theory/arith/arith_rewriter.h +++ b/src/theory/arith/arith_rewriter.h @@ -48,6 +48,15 @@ class ArithRewriter : public TheoryRewriter */ Node rewriteIneqToBv(const Node& ineq); + /** + * Rewrite n based on the proof rewrite rule id. + * @param id The rewrite rule. + * @param n The node to rewrite. + * @return The rewritten version of n based on id, or Node::null() if n + * cannot be rewritten. + */ + Node rewriteViaRule(ProofRewriteRule id, const Node& n) override; + private: /** preRewrite for atoms */ RewriteResponse preRewriteAtom(TNode t); From 635a2acb8393c1812b99d22fd12bb583e4d3cd7e Mon Sep 17 00:00:00 2001 From: Andrew Reynolds Date: Fri, 31 May 2024 18:41:23 -0500 Subject: [PATCH 14/35] Refactor strings entailment utility in preparation for new proof rewrite rule (#10838) Analogous to the arithmetic utility, we need a version of this utility which does not use the rewriter as a subroutine. --- src/theory/strings/sequences_rewriter.cpp | 2 +- src/theory/strings/strings_entail.cpp | 26 ++++++++++++++++------- src/theory/strings/strings_entail.h | 5 +++-- 3 files changed, 22 insertions(+), 11 deletions(-) diff --git a/src/theory/strings/sequences_rewriter.cpp b/src/theory/strings/sequences_rewriter.cpp index 73697673287..d6374aa6327 100644 --- a/src/theory/strings/sequences_rewriter.cpp +++ b/src/theory/strings/sequences_rewriter.cpp @@ -44,7 +44,7 @@ SequencesRewriter::SequencesRewriter(NodeManager* nm, d_statistics(statistics), d_rr(r), d_arithEntail(r), - d_stringsEntail(r, d_arithEntail, *this) + d_stringsEntail(r, d_arithEntail, this) { d_sigmaStar = nm->mkNode(Kind::REGEXP_STAR, nm->mkNode(Kind::REGEXP_ALLCHAR)); d_true = nm->mkConst(true); diff --git a/src/theory/strings/strings_entail.cpp b/src/theory/strings/strings_entail.cpp index 6d766d7e2b6..dbb5d1a2755 100644 --- a/src/theory/strings/strings_entail.cpp +++ b/src/theory/strings/strings_entail.cpp @@ -32,7 +32,7 @@ namespace strings { StringsEntail::StringsEntail(Rewriter* r, ArithEntail& aent, - SequencesRewriter& rewriter) + SequencesRewriter* rewriter) : d_rr(r), d_arithEntail(aent), d_rewriter(rewriter) { } @@ -136,7 +136,8 @@ bool StringsEntail::stripSymbolicLength(std::vector& n1, if (n1[sindex_use].isConst()) { // could strip part of a constant - Node lowerBound = d_arithEntail.getConstantBound(d_rr->rewrite(curr)); + Node lowerBound = + d_arithEntail.getConstantBound(d_arithEntail.rewriteArith(curr)); if (!lowerBound.isNull()) { Assert(lowerBound.isConst()); @@ -148,12 +149,12 @@ bool StringsEntail::stripSymbolicLength(std::vector& n1, size_t slen = Word::getLength(s); Node ncl = nm->mkConstInt(cvc5::internal::Rational(slen)); Node next_s = nm->mkNode(Kind::SUB, lowerBound, ncl); - next_s = d_rr->rewrite(next_s); + next_s = d_arithEntail.rewriteArith(next_s); Assert(next_s.isConst()); // we can remove the entire constant if (next_s.getConst().sgn() >= 0) { - curr = d_rr->rewrite(nm->mkNode(Kind::SUB, curr, ncl)); + curr = d_arithEntail.rewriteArith(nm->mkNode(Kind::SUB, curr, ncl)); success = true; sindex++; } @@ -163,7 +164,8 @@ bool StringsEntail::stripSymbolicLength(std::vector& n1, // lower bound minus the length of a concrete string is negative, // hence lowerBound cannot be larger than long max Assert(lbr < Rational(String::maxSize())); - curr = d_rr->rewrite(nm->mkNode(Kind::SUB, curr, lowerBound)); + curr = d_arithEntail.rewriteArith( + nm->mkNode(Kind::SUB, curr, lowerBound)); uint32_t lbsize = lbr.getNumerator().toUnsignedInt(); Assert(lbsize < slen); if (dir == 1) @@ -195,7 +197,7 @@ bool StringsEntail::stripSymbolicLength(std::vector& n1, curr, NodeManager::currentNM()->mkNode(Kind::STRING_LENGTH, n1[sindex_use])); - next_s = d_rr->rewrite(next_s); + next_s = d_arithEntail.rewriteArith(next_s); if (d_arithEntail.check(next_s)) { success = true; @@ -677,18 +679,26 @@ Node StringsEntail::checkContains(Node a, Node b, bool fullRewriter) if (fullRewriter) { + if (d_rr == nullptr) + { + return Node::null(); + } ctn = d_rr->rewrite(ctn); } else { + if (d_rewriter == nullptr) + { + return Node::null(); + } Node prev; do { prev = ctn; - ctn = d_rewriter.rewriteContains(ctn); + ctn = d_rewriter->rewriteContains(ctn); if (ctn != prev) { - ctn = d_rewriter.postProcessRewrite(prev, ctn); + ctn = d_rewriter->postProcessRewrite(prev, ctn); } } while (prev != ctn && ctn.getKind() == Kind::STRING_CONTAINS); } diff --git a/src/theory/strings/strings_entail.h b/src/theory/strings/strings_entail.h index 1d943f726bc..2ac25855255 100644 --- a/src/theory/strings/strings_entail.h +++ b/src/theory/strings/strings_entail.h @@ -22,6 +22,7 @@ #include "expr/node.h" #include "theory/strings/arith_entail.h" +#include "theory/strings/rewrites.h" namespace cvc5::internal { namespace theory { @@ -40,7 +41,7 @@ class SequencesRewriter; class StringsEntail { public: - StringsEntail(Rewriter* r, ArithEntail& aent, SequencesRewriter& rewriter); + StringsEntail(Rewriter* r, ArithEntail& aent, SequencesRewriter* rewriter); /** can constant contain list * return true if constant c can contain the list l in order @@ -394,7 +395,7 @@ class StringsEntail * Reference to the sequences rewriter that owns this `StringsEntail` * instance. */ - SequencesRewriter& d_rewriter; + SequencesRewriter* d_rewriter; }; } // namespace strings From 78be2504476666271272045c1252d0abba773df3 Mon Sep 17 00:00:00 2001 From: Andrew Reynolds Date: Fri, 31 May 2024 19:11:06 -0500 Subject: [PATCH 15/35] Update string util for regexp concatenation (#10839) When the proof rewrite rule corresponding to STR_IN_RE_CONSUME is added, it will require update to the mkConcat utility to ensure we handle regular expression concatenation as well. --- src/theory/strings/theory_strings_utils.cpp | 18 ++++++++++++------ src/theory/strings/theory_strings_utils.h | 1 + 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/theory/strings/theory_strings_utils.cpp b/src/theory/strings/theory_strings_utils.cpp index dc3d758aae3..87d878b2123 100644 --- a/src/theory/strings/theory_strings_utils.cpp +++ b/src/theory/strings/theory_strings_utils.cpp @@ -129,17 +129,23 @@ void getConcat(Node n, std::vector& c) Node mkConcat(const std::vector& c, TypeNode tn) { Assert(tn.isStringLike() || tn.isRegExp()); - if (c.empty()) + if (c.size() == 1) { - Assert(tn.isStringLike()); - return Word::mkEmptyWord(tn); + return c[0]; } - else if (c.size() == 1) + NodeManager* nm = NodeManager::currentNM(); + if (c.empty()) { - return c[0]; + if (tn.isRegExp()) + { + TypeNode stn = nm->stringType(); + Node emp = Word::mkEmptyWord(stn); + return nm->mkNode(Kind::STRING_TO_REGEXP, emp); + } + return Word::mkEmptyWord(tn); } Kind k = tn.isStringLike() ? Kind::STRING_CONCAT : Kind::REGEXP_CONCAT; - return NodeManager::currentNM()->mkNode(k, c); + return nm->mkNode(k, c); } Node mkPrefix(Node t, Node n) diff --git a/src/theory/strings/theory_strings_utils.h b/src/theory/strings/theory_strings_utils.h index 293e662ecbf..85321c53606 100644 --- a/src/theory/strings/theory_strings_utils.h +++ b/src/theory/strings/theory_strings_utils.h @@ -49,6 +49,7 @@ void flattenOp(Kind k, Node n, std::vector& conj); * when n = str.++( x, y ), c is { x, y } * when n = str.++( x, str.++( y, z ), w ), c is { x, str.++( y, z ), w ) * when n = x, c is { x } + * when n = "", c is { "" } * * Also applies to regular expressions (re.++ above). */ From 6b5f25ba281ea2ec4d59e4b3459b12cb11afc7bd Mon Sep 17 00:00:00 2001 From: Andrew Reynolds Date: Sun, 2 Jun 2024 11:59:34 -0500 Subject: [PATCH 16/35] Add remaining arithmetic and string RARE rewrites (#10832) Eliminates 3 rules that are very specific cases of an ad-hoc theory rewrite. Generalizes one existing RARE rewrite str-contains-concat-find. --- include/cvc5/cvc5_proof_rule.h | 60 ++++++++- src/theory/arith/rewrites | 2 + src/theory/strings/rewrites | 114 +++++++++++++++--- src/theory/strings/rewrites-regexp-membership | 44 ++++--- 4 files changed, 185 insertions(+), 35 deletions(-) diff --git a/include/cvc5/cvc5_proof_rule.h b/include/cvc5/cvc5_proof_rule.h index d748d75c2c7..4fd11d40f4f 100644 --- a/include/cvc5/cvc5_proof_rule.h +++ b/include/cvc5/cvc5_proof_rule.h @@ -2587,6 +2587,10 @@ enum ENUM(ProofRewriteRule) : uint32_t EVALUE(ARITH_ELIM_GT), /** Auto-generated from RARE rule arith-elim-lt */ EVALUE(ARITH_ELIM_LT), + /** Auto-generated from RARE rule arith-elim-int-gt */ + EVALUE(ARITH_ELIM_INT_GT), + /** Auto-generated from RARE rule arith-elim-int-lt */ + EVALUE(ARITH_ELIM_INT_LT), /** Auto-generated from RARE rule arith-elim-leq */ EVALUE(ARITH_ELIM_LEQ), /** Auto-generated from RARE rule arith-leq-norm */ @@ -3101,6 +3105,10 @@ enum ENUM(ProofRewriteRule) : uint32_t EVALUE(SETS_CARD_EMP), /** Auto-generated from RARE rule str-eq-ctn-false */ EVALUE(STR_EQ_CTN_FALSE), + /** Auto-generated from RARE rule str-eq-ctn-full-false1 */ + EVALUE(STR_EQ_CTN_FULL_FALSE1), + /** Auto-generated from RARE rule str-eq-ctn-full-false2 */ + EVALUE(STR_EQ_CTN_FULL_FALSE2), /** Auto-generated from RARE rule str-concat-flatten */ EVALUE(STR_CONCAT_FLATTEN), /** Auto-generated from RARE rule str-concat-flatten-eq */ @@ -3159,16 +3167,26 @@ enum ENUM(ProofRewriteRule) : uint32_t EVALUE(STR_SUBSTR_COMBINE1), /** Auto-generated from RARE rule str-substr-combine2 */ EVALUE(STR_SUBSTR_COMBINE2), + /** Auto-generated from RARE rule str-substr-combine3 */ + EVALUE(STR_SUBSTR_COMBINE3), + /** Auto-generated from RARE rule str-substr-combine4 */ + EVALUE(STR_SUBSTR_COMBINE4), /** Auto-generated from RARE rule str-substr-concat1 */ EVALUE(STR_SUBSTR_CONCAT1), + /** Auto-generated from RARE rule str-substr-concat2 */ + EVALUE(STR_SUBSTR_CONCAT2), /** Auto-generated from RARE rule str-substr-full */ EVALUE(STR_SUBSTR_FULL), + /** Auto-generated from RARE rule str-substr-full-eq */ + EVALUE(STR_SUBSTR_FULL_EQ), /** Auto-generated from RARE rule str-contains-refl */ EVALUE(STR_CONTAINS_REFL), /** Auto-generated from RARE rule str-contains-concat-find */ EVALUE(STR_CONTAINS_CONCAT_FIND), /** Auto-generated from RARE rule str-contains-split-char */ EVALUE(STR_CONTAINS_SPLIT_CHAR), + /** Auto-generated from RARE rule str-contains-lt-len */ + EVALUE(STR_CONTAINS_LT_LEN), /** Auto-generated from RARE rule str-contains-leq-len-eq */ EVALUE(STR_CONTAINS_LEQ_LEN_EQ), /** Auto-generated from RARE rule str-contains-emp */ @@ -3179,12 +3197,26 @@ enum ENUM(ProofRewriteRule) : uint32_t EVALUE(STR_CONCAT_EMP), /** Auto-generated from RARE rule str-at-elim */ EVALUE(STR_AT_ELIM), + /** Auto-generated from RARE rule str-replace-self */ + EVALUE(STR_REPLACE_SELF), /** Auto-generated from RARE rule str-replace-no-contains */ EVALUE(STR_REPLACE_NO_CONTAINS), /** Auto-generated from RARE rule str-replace-empty */ EVALUE(STR_REPLACE_EMPTY), /** Auto-generated from RARE rule str-len-concat-rec */ EVALUE(STR_LEN_CONCAT_REC), + /** Auto-generated from RARE rule str-indexof-self */ + EVALUE(STR_INDEXOF_SELF), + /** Auto-generated from RARE rule str-indexof-no-contains */ + EVALUE(STR_INDEXOF_NO_CONTAINS), + /** Auto-generated from RARE rule str-to-lower-concat */ + EVALUE(STR_TO_LOWER_CONCAT), + /** Auto-generated from RARE rule str-to-upper-concat */ + EVALUE(STR_TO_UPPER_CONCAT), + /** Auto-generated from RARE rule str-to-lower-upper */ + EVALUE(STR_TO_LOWER_UPPER), + /** Auto-generated from RARE rule str-to-upper-lower */ + EVALUE(STR_TO_UPPER_LOWER), /** Auto-generated from RARE rule re-all-elim */ EVALUE(RE_ALL_ELIM), /** Auto-generated from RARE rule re-opt-elim */ @@ -3217,12 +3249,22 @@ enum ENUM(ProofRewriteRule) : uint32_t EVALUE(RE_INTER_FLATTEN), /** Auto-generated from RARE rule re-inter-dup */ EVALUE(RE_INTER_DUP), + /** Auto-generated from RARE rule re-loop-neg */ + EVALUE(RE_LOOP_NEG), /** Auto-generated from RARE rule re-inter-cstring */ EVALUE(RE_INTER_CSTRING), /** Auto-generated from RARE rule re-inter-cstring-neg */ EVALUE(RE_INTER_CSTRING_NEG), /** Auto-generated from RARE rule str-nth-elim-code */ EVALUE(STR_NTH_ELIM_CODE), + /** Auto-generated from RARE rule str-substr-len-include */ + EVALUE(STR_SUBSTR_LEN_INCLUDE), + /** Auto-generated from RARE rule str-substr-len-include-pre */ + EVALUE(STR_SUBSTR_LEN_INCLUDE_PRE), + /** Auto-generated from RARE rule str-substr-len-skip */ + EVALUE(STR_SUBSTR_LEN_SKIP), + /** Auto-generated from RARE rule seq-rev-concat */ + EVALUE(SEQ_REV_CONCAT), /** Auto-generated from RARE rule seq-len-unit */ EVALUE(SEQ_LEN_UNIT), /** Auto-generated from RARE rule seq-nth-unit */ @@ -3308,12 +3350,18 @@ enum ENUM(ProofRewriteRule) : uint32_t EVALUE(STR_IN_RE_NO_PREFIX), /** Auto-generated from RARE rule str-in-re-no-prefix-rev */ EVALUE(STR_IN_RE_NO_PREFIX_REV), - /** Auto-generated from RARE rule str-in-re-concat-allchar */ - EVALUE(STR_IN_RE_CONCAT_ALLCHAR), - /** Auto-generated from RARE rule str-in-re-concat-allchar-base-geq */ - EVALUE(STR_IN_RE_CONCAT_ALLCHAR_BASE_GEQ), - /** Auto-generated from RARE rule str-in-re-concat-allchar-base-eq */ - EVALUE(STR_IN_RE_CONCAT_ALLCHAR_BASE_EQ), + /** Auto-generated from RARE rule str-in-re-req-unfold */ + EVALUE(STR_IN_RE_REQ_UNFOLD), + /** Auto-generated from RARE rule str-in-re-req-unfold-rev */ + EVALUE(STR_IN_RE_REQ_UNFOLD_REV), + /** Auto-generated from RARE rule str-in-re-skip-unfold */ + EVALUE(STR_IN_RE_SKIP_UNFOLD), + /** Auto-generated from RARE rule str-in-re-skip-unfold-rev */ + EVALUE(STR_IN_RE_SKIP_UNFOLD_REV), + /** Auto-generated from RARE rule str-in-re-test-unfold */ + EVALUE(STR_IN_RE_TEST_UNFOLD), + /** Auto-generated from RARE rule str-in-re-test-unfold-rev */ + EVALUE(STR_IN_RE_TEST_UNFOLD_REV), /** Auto-generated from RARE rule eq-refl */ EVALUE(EQ_REFL), /** Auto-generated from RARE rule eq-symm */ diff --git a/src/theory/arith/rewrites b/src/theory/arith/rewrites index 1aaaef22fb2..4f0afba8390 100644 --- a/src/theory/arith/rewrites +++ b/src/theory/arith/rewrites @@ -26,6 +26,8 @@ (define-rule arith-elim-minus ((t ?) (s ?)) (- t s) (+ t (* (- 1) s))) (define-rule arith-elim-gt ((t ?) (s ?)) (> t s) (not (<= t s))) (define-rule arith-elim-lt ((t ?) (s ?)) (< t s) (not (>= t s))) +(define-rule arith-elim-int-gt ((t Int) (s Int)) (> t s) (>= t (+ s 1))) +(define-rule arith-elim-int-lt ((t Int) (s Int)) (< t s) (>= s (+ t 1))) (define-rule arith-elim-leq ((t ?) (s ?)) (<= t s) (>= s t)) (define-rule arith-leq-norm ((t Int) (s Int)) (<= t s) (not (>= t (+ s 1)))) diff --git a/src/theory/strings/rewrites b/src/theory/strings/rewrites index 227eb3aef82..6a146c4b67b 100644 --- a/src/theory/strings/rewrites +++ b/src/theory/strings/rewrites @@ -12,6 +12,16 @@ (= (str.++ x1 x x2) y) false) +(define-cond-rule str-eq-ctn-full-false1 ((x ?Seq) (y ?Seq)) + (= (str.contains y x) false) + (= x y) + false) + +(define-cond-rule str-eq-ctn-full-false2 ((x ?Seq) (y ?Seq)) + (= (str.contains x y) false) + (= x y) + false) + ;(define-cond-rule str-eq-len-false ((x String) (y String)) ; (not (= (str.len x) (str.len y))) ; (= x y) @@ -130,34 +140,56 @@ (str.substr (str.substr s n1 m1) n2 m2) (str.substr s (+ n1 n2) m2)) +(define-cond-rule str-substr-combine3 ((s String) (n1 Int) (m1 Int) (n2 Int) (m2 Int)) + (and (>= n1 0) (>= n2 0) (>= (str.len (str.substr s n1 m1)) (+ n2 m2))) + (str.substr (str.substr s n1 m1) n2 m2) + (str.substr s (+ n1 n2) m2)) + +(define-cond-rule str-substr-combine4 ((s String) (n1 Int) (m1 Int) (n2 Int) (m2 Int)) + (and (>= n1 0) (>= n2 0) (>= (+ n2 m2) (str.len (str.substr s n1 m1)))) + (str.substr (str.substr s n1 m1) n2 m2) + (str.substr s (+ n1 n2) (- m1 n2))) + + (define-cond-rule str-substr-concat1 ((s1 ?Seq) (s2 ?Seq :list) (n Int) (m Int)) (and (>= n 0) (>= (str.len s1) (+ n m))) (str.substr (str.++ s1 s2) n m) (str.substr s1 n m)) +(define-cond-rule str-substr-concat2 ((s1 ?Seq) (s2 ?Seq) (s3 ?Seq :list) (n Int) (m Int)) + (>= n (str.len s1)) + (str.substr (str.++ s1 s2 s3) n m) + (str.substr (str.++ s2 s3) (- n (str.len s1)) m)) + (define-cond-rule str-substr-full ((s ?Seq) (n Int)) (>= n (str.len s)) (str.substr s 0 n) s) -;(define-cond-rule str-substr-self-len ((s ?Seq) (n Int) (m Int)) -; (>= m (str.len s)) -; (str.substr s n m) -; (str.substr s n (str.len s))) +(define-cond-rule str-substr-full-eq ((s ?Seq) (n Int)) + (= (str.len s) n) + (str.substr s 0 n) + s) (define-rule str-contains-refl ((x ?Seq)) (str.contains x x) true) -(define-rule str-contains-concat-find ((xs ?Seq :list) (y ?Seq) (zs ?Seq :list)) - (str.contains (str.++ xs y zs) y) +(define-cond-rule str-contains-concat-find ((xs ?Seq :list) (z ?Seq) (y ?Seq) (zs ?Seq :list)) + (str.contains z y) + (str.contains (str.++ xs z zs) y) true) (define-cond-rule str-contains-split-char ((x ?Seq) (y ?Seq) (z ?Seq :list) (w ?Seq)) (= (str.len w) 1) (str.contains (str.++ x y z) w) (or (str.contains x w) (str.contains (str.++ y z) w))) - + +(define-cond-rule str-contains-lt-len ((x String) (y String)) + (> (str.len y) (str.len x)) + (str.contains x y) + false) + (define-cond-rule str-contains-leq-len-eq ((x String) (y String)) - (<= (str.len x) (str.len y)) + (>= (str.len y) (str.len x)) (str.contains x y) (= x y)) @@ -166,11 +198,6 @@ (str.contains x y) true) -;(define-cond-rule str-contains-len-geq-false ((x String) (y String)) -; (> (str.len y) (str.len x)) -; (str.contains x y) -; false) - (define-cond-rule str-contains-is-emp ((x String) (y String)) (= (str.len x) 0) (str.contains x y) @@ -188,6 +215,10 @@ ; (str.replace t s t) ; t) +(define-rule str-replace-self ((t ?Seq) (s ?Seq)) + (str.replace t t s) + s) + (define-cond-rule str-replace-no-contains ((t ?Seq) (s ?Seq) (r ?Seq)) (not (str.contains t s)) (str.replace t s r) @@ -197,11 +228,38 @@ (str.replace t "" s) (str.++ s t)) -(define-rule* str-len-concat-rec ((s1 String) (s2 String) (s3 String :list)) +(define-rule* str-len-concat-rec ((s1 ?Seq) (s2 ?Seq) (s3 ?Seq :list)) (str.len (str.++ s1 s2 s3)) (str.len (str.++ s2 s3)) (+ (str.len s1) _)) +(define-rule str-indexof-self ((t String) (n Int)) + (str.indexof t t n) + (str.indexof "" "" n)) + +(define-cond-rule str-indexof-no-contains ((t String) (s String) (n Int)) + (not (str.contains (str.substr t n (str.len t)) s)) + (str.indexof t s n) + (- 1)) + +(define-rule* str-to-lower-concat ((s1 String) (s2 String) (s3 String :list)) + (str.to_lower (str.++ s1 s2 s3)) + (str.to_lower (str.++ s2 s3)) + (str.++ (str.to_lower s1) _)) + +(define-rule* str-to-upper-concat ((s1 String) (s2 String) (s3 String :list)) + (str.to_upper (str.++ s1 s2 s3)) + (str.to_upper (str.++ s2 s3)) + (str.++ (str.to_upper s1) _)) + +(define-rule str-to-lower-upper ((s String)) + (str.to_lower (str.to_upper s)) + (str.to_lower s)) + +(define-rule str-to-upper-lower ((s String)) + (str.to_upper (str.to_lower s)) + (str.to_upper s)) + ; =============== Regular expression rules (define-rule re-all-elim () re.all (re.* re.allchar)) @@ -232,6 +290,11 @@ (define-rule* re-inter-flatten ((xs RegLan :list) (b RegLan) (ys RegLan :list) (zs RegLan :list)) (re.inter xs (re.inter b ys) zs) (re.inter xs b ys zs)) (define-rule* re-inter-dup ((xs RegLan :list) (b RegLan) (ys RegLan :list) (zs RegLan :list)) (re.inter xs b ys b zs) (re.inter xs b ys zs)) +(define-cond-rule re-loop-neg ((n Int) (m Int) (r RegLan)) + (> n m) + (re.loop n m r) + re.none) + (define-cond-rule re-inter-cstring ((xs RegLan :list) (ys RegLan :list) (s String)) (str.in_re s (re.inter xs ys)) (re.inter xs (str.to_re s) ys) @@ -246,6 +309,29 @@ (seq.nth s n) (str.to_code (str.substr s n 1))) +(define-cond-rule str-substr-len-include ((s1 ?Seq) (s2 ?Seq :list) (n Int)) + (= n (str.len s1)) + (str.substr (str.++ s1 s2) 0 n) + s1 +) + +(define-cond-rule str-substr-len-include-pre ((s1 ?Seq) (s2 ?Seq) (s3 ?Seq :list) (n Int)) + (>= n (str.len s1)) + (str.substr (str.++ s1 s2 s3) 0 n) + (str.++ s1 (str.substr (str.++ s2 s3) 0 (- n (str.len s1)))) +) + +(define-cond-rule str-substr-len-skip ((s1 ?Seq) (s2 ?Seq) (s3 ?Seq :list) (n Int) (m Int)) + (>= n (str.len s1)) + (str.substr (str.++ s1 s2 s3) n m) + (str.substr (str.++ s2 s3) (- n (str.len s1)) m) +) + +(define-rule* seq-rev-concat ((x ?Seq) (y ?Seq :list) (z ?Seq)) + (str.rev (str.++ x y z)) + (str.rev (str.++ x y)) + (str.++ (str.rev z) _)) + ; =============== Sequences-specific rules (define-rule seq-len-unit ((x ?)) (str.len (seq.unit x)) 1) diff --git a/src/theory/strings/rewrites-regexp-membership b/src/theory/strings/rewrites-regexp-membership index 07ade93153f..1999fc1c5b0 100644 --- a/src/theory/strings/rewrites-regexp-membership +++ b/src/theory/strings/rewrites-regexp-membership @@ -168,18 +168,32 @@ (str.in_re (str.++ s1 s2) r) false) -(define-rule* str-in-re-concat-allchar ((s String) (r1 RegLan) (r2 RegLan :list)) - (str.in_re s (re.++ re.allchar r1 r2)) - (str.in_re (str.substr s 1 (str.len s)) (re.++ r1 r2)) - _ -) - -(define-rule str-in-re-concat-allchar-base-geq ((s String)) - (str.in_re s (re.++ re.allchar (re.* re.allchar))) - (>= (str.len s) 1) -) - -(define-rule str-in-re-concat-allchar-base-eq ((s String)) - (str.in_re s (re.++ re.allchar re.allchar)) - (= (str.len s) 2) -) +(define-cond-rule str-in-re-req-unfold ((s String) (r1 RegLan) (r2 RegLan) (r3 RegLan :list)) + (not (str.in_re s (re.++ r2 r3))) + (str.in_re s (re.++ (re.* r1) r2 r3)) + (str.in_re s (re.++ r1 (re.* r1) r2 r3))) + +(define-cond-rule str-in-re-req-unfold-rev ((s String) (r1 RegLan) (r2 RegLan :list) (r3 RegLan)) + (not (str.in_re s (re.++ r1 r2))) + (str.in_re s (re.++ r1 r2 (re.* r3))) + (str.in_re s (re.++ r1 r2 (re.* r3) r3))) + +(define-cond-rule str-in-re-skip-unfold ((s String) (r1 RegLan) (r2 RegLan) (r3 RegLan :list)) + (not (str.in_re s (re.++ r1 (re.* r1) r2 r3))) + (str.in_re s (re.++ (re.* r1) r2 r3)) + (str.in_re s (re.++ r2 r3))) + +(define-cond-rule str-in-re-skip-unfold-rev ((s String) (r1 RegLan) (r2 RegLan :list) (r3 RegLan)) + (not (str.in_re s (re.++ r1 r2 (re.* r3) r3))) + (str.in_re s (re.++ r1 r2 (re.* r3))) + (str.in_re s (re.++ r1 r2))) + +(define-cond-rule str-in-re-test-unfold ((s String) (r1 RegLan) (r2 RegLan) (r3 RegLan :list)) + (and (not (str.in_re s (re.++ r2 r3))) (not (str.in_re s (re.++ r1 (re.* r1) r2 r3)))) + (str.in_re s (re.++ (re.* r1) r2 r3)) + false) + +(define-cond-rule str-in-re-test-unfold-rev ((s String) (r1 RegLan) (r2 RegLan :list) (r3 RegLan)) + (and (not (str.in_re s (re.++ r1 r2))) (not (str.in_re s (re.++ r1 r2 (re.* r3) r3)))) + (str.in_re s (re.++ r1 r2 (re.* r3))) + false) From a85346296d5be71a33f900185b20d3ee0e828b65 Mon Sep 17 00:00:00 2001 From: Haniel Barbosa Date: Sun, 2 Jun 2024 14:27:28 -0300 Subject: [PATCH 17/35] [alethe] Updates to node converter (#10659) Makes it track errors that can result from unsupported elements in the proof, as well as proper support for the new architecture of Skolem handling, besides other miscellaneous changes. --- src/proof/alethe/alethe_node_converter.cpp | 205 ++++++++++++++++++++- src/proof/alethe/alethe_node_converter.h | 76 +++++++- 2 files changed, 264 insertions(+), 17 deletions(-) diff --git a/src/proof/alethe/alethe_node_converter.cpp b/src/proof/alethe/alethe_node_converter.cpp index 1c61b4732fe..7d41ac3ca87 100644 --- a/src/proof/alethe/alethe_node_converter.cpp +++ b/src/proof/alethe/alethe_node_converter.cpp @@ -17,11 +17,27 @@ #include "expr/node_algorithm.h" #include "expr/skolem_manager.h" +#include "proof/proof_rule_checker.h" +#include "util/bitvector.h" +#include "util/rational.h" namespace cvc5::internal { namespace proof { -AletheNodeConverter::AletheNodeConverter(NodeManager* nm) : NodeConverter(nm) {} +Node AletheNodeConverter::maybeConvert(Node n, bool isAssumption) +{ + d_error = ""; + Node res = convert(n); + if (!d_error.empty()) + { + return Node::null(); + } + if (isAssumption) + { + d_convToOriginalAssumption[res] = n; + } + return res; +} Node AletheNodeConverter::postConvert(Node n) { @@ -29,11 +45,159 @@ Node AletheNodeConverter::postConvert(Node n) Kind k = n.getKind(); switch (k) { + case Kind::BITVECTOR_BITOF: + { + std::stringstream ss; + ss << "(_ @bitOf " << n.getOperator().getConst().d_bitIndex + << ")"; + TypeNode fType = nm->mkFunctionType(n[0].getType(), n.getType()); + Node op = mkInternalSymbol(ss.str(), fType, true); + Node converted = nm->mkNode(Kind::APPLY_UF, op, n[0]); + return converted; + } + case Kind::BITVECTOR_BB_TERM: + { + std::vector children; + std::vector childrenTypes; + for (const Node& c : n) + { + childrenTypes.push_back(c.getType()); + children.push_back(c); + } + TypeNode fType = nm->mkFunctionType(childrenTypes, n.getType()); + Node op = mkInternalSymbol("@bbT", fType, true); + children.insert(children.begin(), op); + Node converted = nm->mkNode(Kind::APPLY_UF, children); + return converted; + } + case Kind::BITVECTOR_EAGER_ATOM: + { + std::stringstream ss; + ss << "Proof uses eager bit-blasting, which does not have support for " + "Alethe proofs."; + d_error = ss.str(); + return Node::null(); + } case Kind::SKOLEM: { Trace("alethe-conv") << "AletheNodeConverter: handling skolem " << n << "\n"; - Unreachable() << "Fresh Skolems are not allowed\n"; + SkolemManager* sm = nm->getSkolemManager(); + SkolemId sfi = SkolemId::NONE; + Node cacheVal; + sm->isSkolemFunction(n, sfi, cacheVal); + // skolems v print as their original forms + // v is (skolem W) where W is the original or original form of v + Node wi = SkolemManager::getUnpurifiedForm(n); + if (!wi.isNull() && wi != n) + { + Trace("alethe-conv") + << "...to convert original form " << wi << std::endl; + Node conv = convert(wi); + // ignore purification skolems + if (sfi != SkolemId::PURIFY) + { + d_skolems[n] = conv; + } + return conv; + } + // create the witness term (witness ((x_i T_i)) (exists ((x_i+1 T_i+1) + // ... (x_n T_n)) body), where the bound variables and the body come from + // the quantifier term which must be the first element of cacheVal (which + // should be a list), and i the second. + if (sfi == SkolemId::QUANTIFIERS_SKOLEMIZE) + { + Trace("alethe-conv") + << ".. to build witness with index/quant: " << cacheVal[1] << " / " + << cacheVal[0] << "\n"; + Assert(cacheVal.getKind() == Kind::SEXPR + && cacheVal.getNumChildren() == 2); + Node quant = cacheVal[0]; + Assert(quant.getKind() == Kind::EXISTS); + Node var = cacheVal[1]; + uint32_t index = -1; + for (size_t i = 0, size = quant[0].getNumChildren(); i < size; ++i) + { + if (var == quant[0][i]) + { + index = i; + break; + } + } + // Since cvc5 *always* skolemize FORALLs, we generate the choice term + // assuming it is gonna be introduced via a sko_forall rule, in which + // case the body of the choice is negated, which means to have + // universal quantification of the remaining variables in the choice + // body, and the whole thing negated. Likewise, since during + // Skolemization cvc5 will have negated the body of the original + // quantifier, we need to revert that as well. + Assert(index < quant[0].getNumChildren()); + Assert(quant[1].getKind() == Kind::NOT); + Node body = + index == quant[0].getNumChildren() - 1 + ? quant[1] + : nm->mkNode(Kind::FORALL, + nm->mkNode( + Kind::BOUND_VAR_LIST, + std::vector{quant[0].begin() + index + 1, + quant[0].end()}), + quant[1][0]) + .notNode(); + // we need to replace in the body all the free variables (i.e., from 0 + // to index) by their respective choice terms. To do this, we get + // the skolems for each of these variables, retrieve their + // conversions, and replace the variables by the conversions in body + if (index > 0) + { + std::vector subs; + for (size_t i = 0; i < index; ++i) + { + Node v = quant[0][i]; + std::vector cacheVals{quant, v}; + Node sk = sm->mkSkolemFunction(SkolemId::QUANTIFIERS_SKOLEMIZE, + cacheVals); + Assert(!sk.isNull()); + subs.push_back(d_defineSkolems ? sk : convert(sk)); + } + body = body.substitute(quant[0].begin(), + quant[0].begin() + index, + subs.begin(), + subs.end()); + } + Node witness = nm->mkNode( + Kind::WITNESS, nm->mkNode(Kind::BOUND_VAR_LIST, var), body); + Trace("alethe-conv") << ".. witness: " << witness << "\n"; + witness = convert(witness); + if (d_defineSkolems) + { + d_skolemsAux[n] = witness; + if (index == quant[0].getNumChildren() - 1) + { + Trace("alethe-conv") + << "....populate map from aux : " << d_skolemsAux << "\n"; + for (size_t i = index + 1; i > 0; --i) + { + Node v = quant[0][i - 1]; + std::vector cacheVals{quant, v}; + Node sk = sm->mkSkolemFunction(SkolemId::QUANTIFIERS_SKOLEMIZE, + cacheVals); + Assert(!sk.isNull()); + Assert(d_skolemsAux.find(sk) != d_skolemsAux.end()) + << "Could not find sk " << sk; + d_skolems[sk] = d_skolemsAux[sk]; + } + d_skolemsAux.clear(); + } + return n; + } + d_skolems[n] = witness; + return witness; + } + std::stringstream ss; + ss << "Proof contains Skolem (kind " << sfi << ", term " << n + << ") is not supported by Alethe."; + d_error = ss.str(); + return Node::null(); } case Kind::FORALL: { @@ -51,7 +215,9 @@ Node AletheNodeConverter::postConvert(Node n) } TypeNode fType = nm->mkFunctionType(childrenTypes, n.getType()); Node choiceOp = mkInternalSymbol("choice", fType); - return nm->mkNode(Kind::APPLY_UF, choiceOp, n[0], n[1]); + Node converted = nm->mkNode(Kind::APPLY_UF, choiceOp, n[0], n[1]); + Trace("alethe-conv") << ".. converted to choice: " << converted << "\n"; + return converted; } default: { @@ -61,12 +227,9 @@ Node AletheNodeConverter::postConvert(Node n) return n; } -Node AletheNodeConverter::mkInternalSymbol(const std::string& name) -{ - return mkInternalSymbol(name, NodeManager::currentNM()->sExprType()); -} - -Node AletheNodeConverter::mkInternalSymbol(const std::string& name, TypeNode tn) +Node AletheNodeConverter::mkInternalSymbol(const std::string& name, + TypeNode tn, + bool useRawSym) { std::pair key(tn, name); std::map, Node>::iterator it = @@ -76,10 +239,32 @@ Node AletheNodeConverter::mkInternalSymbol(const std::string& name, TypeNode tn) return it->second; } NodeManager* nm = NodeManager::currentNM(); - Node sym = nm->mkBoundVar(name, tn); + Node sym = useRawSym ? nm->mkRawSymbol(name, tn) : nm->mkBoundVar(name, tn); d_symbolsMap[key] = sym; return sym; } +Node AletheNodeConverter::mkInternalSymbol(const std::string& name) +{ + return mkInternalSymbol(name, NodeManager::currentNM()->sExprType()); +} + +const std::string& AletheNodeConverter::getError() { return d_error; } + +Node AletheNodeConverter::getOriginalAssumption(Node n) +{ + auto it = d_convToOriginalAssumption.find(n); + if (it != d_convToOriginalAssumption.end()) + { + return it->second; + } + return Node::null(); +} + +const std::map& AletheNodeConverter::getSkolemDefinitions() +{ + return d_skolems; +} + } // namespace proof } // namespace cvc5::internal diff --git a/src/proof/alethe/alethe_node_converter.h b/src/proof/alethe/alethe_node_converter.h index 0eb64717b44..9704c2bf4e4 100644 --- a/src/proof/alethe/alethe_node_converter.h +++ b/src/proof/alethe/alethe_node_converter.h @@ -20,26 +20,80 @@ #include "expr/node.h" #include "expr/node_converter.h" +#include "proof/alf/alf_node_converter.h" namespace cvc5::internal { namespace proof { /** * This is a helper class for the Alethe post-processor that converts nodes into - * the form that Alethe expects. + * their expected form in Alethe. */ -class AletheNodeConverter : public NodeConverter +class AletheNodeConverter : public BaseAlfNodeConverter { public: - AletheNodeConverter(NodeManager* nm); + /** Constructor + * + * @param nm The node manager + * @param defineSkolems Whether Skolem definitions will be saved to be printed + * separately. + */ + AletheNodeConverter(NodeManager* nm, bool defineSkolems = false) + : BaseAlfNodeConverter(nm), d_defineSkolems(defineSkolems) + { + } + ~AletheNodeConverter() {} + /** convert at post-order traversal */ Node postConvert(Node n) override; - private: - /** - * Make or get an internal symbol with custom name and type. + /** A wrapper for convert that checks whether there was an error during + * conversion. + * + * @param n The node to be converted + * @param isAssumption Whether the n is an assumption + * @return The converted node if there was no error, otherwise Node::null(). */ - Node mkInternalSymbol(const std::string& name, TypeNode tn); + Node maybeConvert(Node n, bool isAssumption = false); + + /** Retrieve the saved error message, if any. */ + const std::string& getError(); + + /** Return original assumption, if any, for a given (converted) node. */ + Node getOriginalAssumption(Node n); + + /** Retrieve a mapping between Skolems and their converted definitions. + * + * Note that this mapping is ordered in a way that a Skolem whose definition + * depends on another Skolem will come after that Skolem in the map. + */ + const std::map& getSkolemDefinitions(); + + Node mkInternalSymbol(const std::string& name, + TypeNode tn, + bool useRawSym = true) override; + + Node getOperatorOfTerm(Node n, bool reqCast = false) override + { + return Node::null(); + }; + + Node typeAsNode(TypeNode tni) override { return Node::null(); }; + + Node mkInternalApp(const std::string& name, + const std::vector& args, + TypeNode ret, + bool useRawSym = true) override + { + return Node::null(); + }; + + private: + /** Error message saved during failed conversion. */ + std::string d_error; + /** Whether Skolem definitions will be saved to be printed separately. */ + bool d_defineSkolems; + /** * As above but uses the s-expression type. */ @@ -47,6 +101,14 @@ class AletheNodeConverter : public NodeConverter /** Maps from internally generated symbols to the built nodes. */ std::map, Node> d_symbolsMap; + + /** Map from converted node to original (used only for assumptions). */ + std::map d_convToOriginalAssumption; + + /** Map between Skolems and their converted definitions. */ + std::map d_skolems; + /** Auxiliary map for maintaining the expected order in the above map. */ + std::map d_skolemsAux; }; } // namespace proof From 88b3de12e96f17b96a863e99b3813b16b803723c Mon Sep 17 00:00:00 2001 From: Andrew Reynolds Date: Mon, 3 Jun 2024 14:51:17 -0500 Subject: [PATCH 18/35] Use SET_CHOOSE operator for bounded set instantiation (#10847) This eliminates the use of WITNESS for bounded set instantiation. Towards further simplification of skolem manager and term formula removal passes. Eliminates 6 trusted proofs steps in our regressions of id WITNESS_AXIOM. --- .../quantifiers/fmf/bounded_integers.cpp | 24 +++++-------------- src/theory/quantifiers/fmf/bounded_integers.h | 11 ++++++++- 2 files changed, 16 insertions(+), 19 deletions(-) diff --git a/src/theory/quantifiers/fmf/bounded_integers.cpp b/src/theory/quantifiers/fmf/bounded_integers.cpp index 7e2f9be9bc7..2b835957195 100644 --- a/src/theory/quantifiers/fmf/bounded_integers.cpp +++ b/src/theory/quantifiers/fmf/bounded_integers.cpp @@ -719,31 +719,19 @@ Node BoundedIntegers::getSetRangeValue( Node q, Node v, RepSetIterator * rsi ) { } Assert(sr.getKind() == Kind::SET_SINGLETON); srCard++; - // choices[i] stores the canonical symbolic representation of the (i+1)^th - // element of sro - std::vector choices; Node choice_i; for (unsigned i = 0; i < srCard; i++) { if (i == d_setm_choice[sro].size()) { - choice_i = nm->mkBoundVar(tne); - choices.push_back(choice_i); - Node cBody = nm->mkNode(Kind::SET_MEMBER, choice_i, sro); - if (choices.size() > 1) - { - cBody = - nm->mkNode(Kind::AND, cBody, nm->mkNode(Kind::DISTINCT, choices)); - } - choices.pop_back(); - Node bvl = nm->mkNode(Kind::BOUND_VAR_LIST, choice_i); - choice_i = nm->mkNode( - Kind::WITNESS, bvl, nm->mkNode(Kind::OR, sro.eqNode(nsr), cBody)); + Node stgt = nsr.getKind() == Kind::SET_EMPTY + ? sro + : nm->mkNode(Kind::SET_MINUS, sro, nsr); + choice_i = nm->mkNode(Kind::SET_CHOOSE, stgt); d_setm_choice[sro].push_back(choice_i); } Assert(i < d_setm_choice[sro].size()); choice_i = d_setm_choice[sro][i]; - choices.push_back(choice_i); Node sChoiceI = nm->mkNode(Kind::SET_SINGLETON, choice_i); if (nsr.getKind() == Kind::SET_EMPTY) { @@ -758,8 +746,8 @@ Node BoundedIntegers::getSetRangeValue( Node q, Node v, RepSetIterator * rsi ) { // e.g. // singleton(0) union singleton(1) // becomes - // C1 union (witness y. S =(set.singleton C1) OR (y in S AND distinct(y, C1))) - // where C1 = (witness x. S=empty OR x in S). + // C1 union (set.singleton (set.choose (set.minus S C1))) + // where C1 = (set.singleton (set.choose S)). Trace("bound-int-rsi") << "...reconstructed " << nsr << std::endl; return nsr; } diff --git a/src/theory/quantifiers/fmf/bounded_integers.h b/src/theory/quantifiers/fmf/bounded_integers.h index a4f3b7f8409..ba0896f95fa 100644 --- a/src/theory/quantifiers/fmf/bounded_integers.h +++ b/src/theory/quantifiers/fmf/bounded_integers.h @@ -245,8 +245,17 @@ class BoundedIntegers : public QuantifiersModule void getBounds( Node f, Node v, RepSetIterator * rsi, Node & l, Node & u ); void getBoundValues( Node f, Node v, RepSetIterator * rsi, Node & l, Node & u ); bool isGroundRange(Node f, Node v); - //for set range + /** + * Get the current value for set variable v of quantified formula q based + * on the current iterator rsi. + */ Node getSetRange( Node q, Node v, RepSetIterator * rsi ); + /** + * Get the current value for set variable v of quantified formula q based + * on the current iterator rsi. Additionally transforms the model value for + * v based on the set_choose operator for the purposes of instantiating with + * symbolic elements of the model of v. + */ Node getSetRangeValue( Node q, Node v, RepSetIterator * rsi ); Node matchBoundVar( Node v, Node t, Node e ); From bd8437a04920b8764eca86c61885d5e3538b9479 Mon Sep 17 00:00:00 2001 From: Andrew Reynolds Date: Mon, 3 Jun 2024 20:31:32 -0500 Subject: [PATCH 19/35] Update skolem definitions in ALF to use opaque arguments (#10841) This removes the current hack for handling skolems in the ALF signature. It makes use of the new :opaque feature in alfc for representing arguments that are assumed to be part of the operator. A followup PR will make the ALF printer rely on the printer for skolems (once #10835 is merged). --- contrib/get-alf-checker | 2 +- proofs/alf/cvc5/programs/Quantifiers.smt3 | 1 - proofs/alf/cvc5/programs/Strings.smt3 | 20 +++++------ proofs/alf/cvc5/rules/Arrays.smt3 | 2 +- proofs/alf/cvc5/rules/Quantifiers.smt3 | 6 ++-- proofs/alf/cvc5/rules/Strings.smt3 | 16 ++++----- proofs/alf/cvc5/theories/Arrays.smt3 | 7 ++-- proofs/alf/cvc5/theories/Bags.smt3 | 20 +++++++---- proofs/alf/cvc5/theories/Builtin.smt3 | 16 ++------- proofs/alf/cvc5/theories/Quantifiers.smt3 | 2 +- proofs/alf/cvc5/theories/Sets.smt3 | 6 ++-- proofs/alf/cvc5/theories/Strings.smt3 | 34 +++++++++---------- proofs/alf/cvc5/theories/Transcendentals.smt3 | 4 +-- src/proof/alf/alf_node_converter.cpp | 22 ++---------- 14 files changed, 68 insertions(+), 90 deletions(-) diff --git a/contrib/get-alf-checker b/contrib/get-alf-checker index 4f9bc8a3ac5..5de212c98aa 100755 --- a/contrib/get-alf-checker +++ b/contrib/get-alf-checker @@ -24,7 +24,7 @@ ALFC_DIR="$BASE_DIR/alf-checker" mkdir -p $ALFC_DIR # download and unpack ALFC -ALF_VERSION="3c122c02bdb49d239223e683740da59f74816ba7" +ALF_VERSION="32f19e225a8bd560ead65ad0edb48144ecdbc168" download "https://github.com/cvc5/alfc/archive/$ALF_VERSION.tar.gz" $BASE_DIR/tmp/alfc.tgz tar --strip 1 -xzf $BASE_DIR/tmp/alfc.tgz -C $ALFC_DIR diff --git a/proofs/alf/cvc5/programs/Quantifiers.smt3 b/proofs/alf/cvc5/programs/Quantifiers.smt3 index 828f8b0855c..20dca42395b 100644 --- a/proofs/alf/cvc5/programs/Quantifiers.smt3 +++ b/proofs/alf/cvc5/programs/Quantifiers.smt3 @@ -6,7 +6,6 @@ (S S U) U ( ((substitute x y x) y) - ((substitute x y (skolem w)) (skolem w)) ; do not traverse into skolems ((substitute x y (f a)) (_ (substitute x y f) (substitute x y a))) ((substitute x y z) z) ) diff --git a/proofs/alf/cvc5/programs/Strings.smt3 b/proofs/alf/cvc5/programs/Strings.smt3 index 2f8b4c6bd48..1ab4f7d7709 100644 --- a/proofs/alf/cvc5/programs/Strings.smt3 +++ b/proofs/alf/cvc5/programs/Strings.smt3 @@ -171,7 +171,7 @@ ; Get the term corresponding to the suffix of s after the first occurrence of ; t in s. (define skolem_first_ctn_post ((U Type :implicit) (s (Seq U)) (t (Seq U))) - (skolem_suffix_rem s (+ (str.len (skolem (skolem_first_ctn_pre s t))) (str.len t)))) + (skolem_suffix_rem s (+ (str.len (@purify (skolem_first_ctn_pre s t))) (str.len t)))) ;;-------------------- Utilities @@ -215,10 +215,10 @@ ((Seq U) Int Int) Bool ( ((string_reduction_substr x n m) - (let ((k (skolem (str.substr x n m)))) + (let ((k (@purify (str.substr x n m)))) (let ((npm (+ n m))) - (let ((k1 (skolem (skolem_prefix x n)))) - (let ((k2 (skolem (skolem_suffix_rem x npm)))) + (let ((k1 (@purify (skolem_prefix x n)))) + (let ((k2 (@purify (skolem_suffix_rem x npm)))) (ite ; condition (and (>= n 0)(> (str.len x) n) (> m 0)) @@ -239,10 +239,10 @@ ((Seq U) (Seq U) Int) Bool ( ((string_reduction_indexof x y n) - (let ((k (skolem (str.indexof x y n)))) + (let ((k (@purify (str.indexof x y n)))) (let ((xn (str.substr x n (- (str.len x) n)))) - (let ((k1 (skolem (skolem_first_ctn_pre xn y)))) - (let ((k2 (skolem (skolem_first_ctn_post xn y)))) + (let ((k1 (@purify (skolem_first_ctn_pre xn y)))) + (let ((k2 (@purify (skolem_first_ctn_post xn y)))) (ite (or (not (str.contains xn y)) (> n (str.len x)) (> 0 n)) (= k (alf.neg 1)) @@ -285,8 +285,8 @@ ((Seq U) (Seq U)) Bool ( ((string_eager_reduction_contains t r) - (let ((k1 (skolem (skolem_first_ctn_pre t r)))) - (let ((k2 (skolem (skolem_first_ctn_post t r)))) + (let ((k1 (@purify (skolem_first_ctn_pre t r)))) + (let ((k2 (@purify (skolem_first_ctn_post t r)))) (ite (str.contains t r) (= t (str.++ k1 r k2)) @@ -358,7 +358,7 @@ ; a constant regular expression, append s ((str.to_re s) (@pair (str.++ s c) M)) ; otherwise, make the skolem and append with constraint - (r (let ((k (skolem (@re_unfold_pos_component t ro i)))) + (r (let ((k (@re_unfold_pos_component t ro i))) (@pair (str.++ k c) (and (str.in_re k r) M)))) ) ) diff --git a/proofs/alf/cvc5/rules/Arrays.smt3 b/proofs/alf/cvc5/rules/Arrays.smt3 index ee20b11b387..0ccfe6f6a8a 100644 --- a/proofs/alf/cvc5/rules/Arrays.smt3 +++ b/proofs/alf/cvc5/rules/Arrays.smt3 @@ -18,5 +18,5 @@ (declare-rule arrays_ext ((T Type) (U Type) (a (Array T U)) (b (Array T U))) :premises ((not (= a b))) - :conclusion (not (= (select a (skolem (@array_deq_diff a b))) (select b (skolem (@array_deq_diff a b))))) + :conclusion (not (= (select a (@array_deq_diff a b)) (select b (@array_deq_diff a b)))) ) diff --git a/proofs/alf/cvc5/rules/Quantifiers.smt3 b/proofs/alf/cvc5/rules/Quantifiers.smt3 index 2a9cf440e73..4ea889c8158 100644 --- a/proofs/alf/cvc5/rules/Quantifiers.smt3 +++ b/proofs/alf/cvc5/rules/Quantifiers.smt3 @@ -11,7 +11,7 @@ (program mk_skolems ((x @List) (xs @List :list) (F Bool)) (@List Bool) @List ( - ((mk_skolems (@list x xs) F) (alf.cons @list (skolem (@quantifiers_skolemize F x)) (mk_skolems xs F))) + ((mk_skolems (@list x xs) F) (alf.cons @list (@quantifiers_skolemize F x) (mk_skolems xs F))) ((mk_skolems @list.nil F) @list.nil) ) ) @@ -34,8 +34,8 @@ t ( ; special case for witness - ((skolem (@quantifiers_skolemize (exists (@list x) F) x)) (= t (witness (@list x) F))) - ((skolem x) (= t x)) + ((@quantifiers_skolemize (exists (@list x) F) x) (= t (witness (@list x) F))) + ((@purify x) (= t x)) ) ) ) diff --git a/proofs/alf/cvc5/rules/Strings.smt3 b/proofs/alf/cvc5/rules/Strings.smt3 index 524dec3174f..5c1147ae268 100644 --- a/proofs/alf/cvc5/rules/Strings.smt3 +++ b/proofs/alf/cvc5/rules/Strings.smt3 @@ -62,8 +62,8 @@ (alf.requires (string_check_length_one s1) true ; checks if char (= u (alf.ite rev - (str.++ (skolem (skolem_prefix u (- (str.len u) 1))) s1) - (str.++ s1 (skolem (skolem_suffix_rem u 1))))))))))))) + (str.++ (@purify (skolem_prefix u (- (str.len u) 1))) s1) + (str.++ s1 (@purify (skolem_suffix_rem u 1))))))))))))) ) ; ProofRule::CONCAT_SPLIT @@ -79,7 +79,7 @@ (string_to_flat_form s rev) (((str.++ s1 s2) (alf.requires s1 sc - (let ((k (skolem (skolem_unify_split t1 s1 rev)))) + (let ((k (@purify (skolem_unify_split t1 s1 rev)))) (and (or (= t1 @@ -108,7 +108,7 @@ (string_to_flat_form s rev) (((str.++ s1 s2) (alf.requires s1 sc - (let ((k (skolem (skolem_unify_split t1 s1 rev)))) + (let ((k (@purify (skolem_unify_split t1 s1 rev)))) (and (= t1 (alf.ite rev @@ -136,9 +136,9 @@ (= tc (alf.ite rev (let ((oc ($str_suffix_len s1 v))) - (str.++ (skolem (skolem_prefix tc (- (str.len tc) (str.len oc)))) oc)) + (str.++ (@purify (skolem_prefix tc (- (str.len tc) (str.len oc)))) oc)) (let ((oc (skolem_prefix s1 v))) - (str.++ oc (skolem (skolem_suffix_rem tc (str.len oc))))))) + (str.++ oc (@purify (skolem_suffix_rem tc (str.len oc))))))) ))))))))) ) @@ -234,7 +234,7 @@ (alf.ite rev (or (not (str.in_re ($str_suffix_len s n) r1)) (not (str.in_re (skolem_prefix s (- (str.len s) n)) ($singleton_elim ($str_rev rev r2))))) - (or (not (str.in_re (skolem_prefix s n) r1)) + (or (not (str.in_re (skolem_prefix s n) r1)) (not (str.in_re (skolem_suffix_rem s n) ($singleton_elim r2))))))) )) ) @@ -267,7 +267,7 @@ (declare-rule string_reduction ((U Type) (s U)) :args (s) - :conclusion (and (string_reduction_pred s) (= s (skolem s))) + :conclusion (and (string_reduction_pred s) (= s (@purify s))) ) (declare-rule string_eager_reduction ((U Type) (s U)) diff --git a/proofs/alf/cvc5/theories/Arrays.smt3 b/proofs/alf/cvc5/theories/Arrays.smt3 index c4ca423ab0e..3f581942f82 100644 --- a/proofs/alf/cvc5/theories/Arrays.smt3 +++ b/proofs/alf/cvc5/theories/Arrays.smt3 @@ -17,6 +17,9 @@ (Array U T) (Array U T) I I Bool)) ; The array diff skolem. -; (skolem (@array_deq_diff A B)) denotes an index where A and B differ if A and B are not equal. +; (@array_deq_diff A B) denotes an index where A and B differ if A and B are not equal. (declare-const @array_deq_diff - (-> (! Type :var T :implicit) (! Type :var U :implicit) (Array T U) (Array T U) T)) + (-> (! Type :var T :implicit) (! Type :var U :implicit) + (! (Array T U) :opaque) + (! (Array T U) :opaque) + T)) diff --git a/proofs/alf/cvc5/theories/Bags.smt3 b/proofs/alf/cvc5/theories/Bags.smt3 index 7ab52fdc989..95b1a7682fa 100644 --- a/proofs/alf/cvc5/theories/Bags.smt3 +++ b/proofs/alf/cvc5/theories/Bags.smt3 @@ -28,13 +28,19 @@ (declare-const table.group (-> (! Type :var T :implicit) @List (Bag T) (Bag (Bag T)))) ; Skolems for the theory of bags. -(declare-const @bags_deq_diff (-> (! Type :var T :implicit) (Bag T) (Bag T) T)) -(declare-const @tables_group_part (-> (! Type :var T :implicit) (Bag (Bag T)) T (Bag T))) -(declare-const @tables_group_part_element (-> (! Type :var T :implicit) (Bag (Bag T)) (Bag T) T)) -(declare-const @bags_map_sum (-> (! Type :var T :implicit) (! Type :var U :implicit) (-> T U) (Bag T) U Int Int)) -(declare-const @bags_distinct_elements (-> (! Type :var T :implicit) (Bag T) Int T)) -(declare-const @bags_distinct_elements_size (-> (! Type :var T :implicit) (Bag T) Int)) -(declare-const @bags_map_preimage_injective (-> (! Type :var T :implicit) (! Type :var U :implicit) (-> T U) (Bag T) U T)) +(declare-const @bags_deq_diff (-> (! Type :var T :implicit) (! (Bag T) :opaque) (! (Bag T) :opaque) T)) +(declare-const @tables_group_part (-> (! Type :var T :implicit) (! (Bag (Bag T)) :opaque) T (Bag T))) +(declare-const @tables_group_part_element (-> (! Type :var T :implicit) (! (Bag (Bag T)) :opaque) (! (Bag T) :opaque) T)) +(declare-const @bags_map_sum (-> + (! Type :var T :implicit) (! Type :var U :implicit) + (! (-> T U) :opaque) (! (Bag T) :opaque) (! U :opaque) Int + Int)) +(declare-const @bags_distinct_elements (-> (! Type :var T :implicit) (! (Bag T) :opaque) Int T)) +(declare-const @bags_distinct_elements_size (-> (! Type :var T :implicit) (! (Bag T) :opaque) Int)) +(declare-const @bags_map_preimage_injective (-> + (! Type :var T :implicit) (! Type :var U :implicit) + (! (-> T U) :opaque) (! (Bag T) :opaque) (! U :opaque) + T)) ;(declare-const bag.from_set (# x term (apply f_bag.from_set x))) ;(declare-const bag.to_set (# x term (apply f_bag.to_set x))) diff --git a/proofs/alf/cvc5/theories/Builtin.smt3 b/proofs/alf/cvc5/theories/Builtin.smt3 index e1dc98eb72f..a4b86ffe554 100644 --- a/proofs/alf/cvc5/theories/Builtin.smt3 +++ b/proofs/alf/cvc5/theories/Builtin.smt3 @@ -37,17 +37,5 @@ ; NOTE: does not check that U is a numeral (declare-const const (-> (! Type :var U :implicit) U (! Type :var T) T)) -(declare-const skolem (-> (! Type :var A :implicit) A A)) - -; We construct all skolems by wrapping them in an application of `skolem`. -; The argument is either -; (1) an application of an internal function symbol @ID where ID is the -; skolem function id, -; (2) a term t, in the case of purify skolems, where t is the term. - -; For example, the array diff skolem for arrays A and B is: -; (skolem (@array_deq_diff A B)) -; where we have: -; (declare-const @array_deq_diff -; (-> (! Type :var T :implicit) (! Type :var U :implicit) (Array T U) (Array T U) T)) - +; The purification skolem. +(declare-const @purify (-> (! Type :var A :implicit) (! A :opaque) A)) diff --git a/proofs/alf/cvc5/theories/Quantifiers.smt3 b/proofs/alf/cvc5/theories/Quantifiers.smt3 index 71f50da66af..2c8d5f6b03b 100644 --- a/proofs/alf/cvc5/theories/Quantifiers.smt3 +++ b/proofs/alf/cvc5/theories/Quantifiers.smt3 @@ -14,4 +14,4 @@ (declare-const witness (-> (! @List :var L) Bool (get_witness_type L)) :binder @list) ; skolems -(declare-const @quantifiers_skolemize (-> (! Type :var T :implicit) Bool T T)) +(declare-const @quantifiers_skolemize (-> (! Type :var T :implicit) (! Bool :opaque) (! T :opaque) T)) diff --git a/proofs/alf/cvc5/theories/Sets.smt3 b/proofs/alf/cvc5/theories/Sets.smt3 index 67262bc9ad6..32b6f7198db 100644 --- a/proofs/alf/cvc5/theories/Sets.smt3 +++ b/proofs/alf/cvc5/theories/Sets.smt3 @@ -45,9 +45,9 @@ (declare-const rel.join_image (-> (! Type :var T :implicit) (Set (Tuple T T)) Int (Set (Tuple T)))) ; Skolems for the theory of sets. -(declare-const @sets_deq_diff (-> (! Type :var T :implicit) (Set T) (Set T) T)) -(declare-const @relations_group_part (-> (! Type :var T :implicit) (Set (Set T)) T (Set T))) -(declare-const @relations_group_part_element (-> (! Type :var T :implicit) (Set (Set T)) (Set T) T)) +(declare-const @sets_deq_diff (-> (! Type :var T :implicit) (! (Set T) :opaque) (! (Set T) :opaque) T)) +(declare-const @relations_group_part (-> (! Type :var T :implicit) (! (Set (Set T)) :opaque) T (Set T))) +(declare-const @relations_group_part_element (-> (! Type :var T :implicit) (! (Set (Set T)) :opaque) (! (Set T) :opaque) T)) ; The following skolems are not yet incorporated into the signature: ;SETS_CHOOSE diff --git a/proofs/alf/cvc5/theories/Strings.smt3 b/proofs/alf/cvc5/theories/Strings.smt3 index b3b75d0d459..fdccec5cb8c 100644 --- a/proofs/alf/cvc5/theories/Strings.smt3 +++ b/proofs/alf/cvc5/theories/Strings.smt3 @@ -107,22 +107,22 @@ ; Skolem functions for strings and sequences. -(declare-const @re_unfold_pos_component (-> String RegLan Int String)) -(declare-const @strings_deq_diff (-> (! Type :var T :implicit) (Seq T) (Seq T) Int)) -(declare-const @strings_stoi_result (-> String Int Int)) -(declare-const @strings_stoi_non_digit (-> String Int)) -(declare-const @strings_itos_result (-> Int Int Int)) - -(declare-const @strings_num_occur (-> (! Type :var T :implicit) (Seq T) (Seq T) Int)) -(declare-const @strings_num_occur_re (-> String RegLan Int)) -(declare-const @strings_occur_index (-> (! Type :var T :implicit) (Seq T) (Seq T) Int Int)) -(declare-const @strings_occur_index_re (-> String RegLan Int Int)) -(declare-const @strings_occur_len_re (-> String RegLan Int Int)) - -(declare-const @strings_replace_all_result (-> (! Type :var T :implicit) (Seq T) Int (Seq T))) - -(declare-const @re_first_match_pre (-> String RegLan String)) -(declare-const @re_first_match (-> String RegLan String)) -(declare-const @re_first_match_post (-> String RegLan String)) +(declare-const @re_unfold_pos_component (-> (! String :opaque) (! RegLan :opaque) (! Int :opaque) String)) +(declare-const @strings_deq_diff (-> (! Type :var T :implicit) (! (Seq T) :opaque) (! (Seq T) :opaque) Int)) +(declare-const @strings_stoi_result (-> (! String :opaque) Int Int)) +(declare-const @strings_stoi_non_digit (-> (! String :opaque) Int)) +(declare-const @strings_itos_result (-> (! Int :opaque) Int Int)) + +(declare-const @strings_num_occur (-> (! Type :var T :implicit) (! (Seq T) :opaque) (! (Seq T) :opaque) Int)) +(declare-const @strings_num_occur_re (-> (! String :opaque) (! RegLan :opaque) Int)) +(declare-const @strings_occur_index (-> (! Type :var T :implicit) (! (Seq T) :opaque) (! (Seq T) :opaque) Int Int)) +(declare-const @strings_occur_index_re (-> (! String :opaque) (! RegLan :opaque) Int Int)) +(declare-const @strings_occur_len_re (-> (! String :opaque) (! RegLan :opaque) Int Int)) + +(declare-const @strings_replace_all_result (-> (! Type :var T :implicit) (! (Seq T) :opaque) Int (Seq T))) + +(declare-const @re_first_match_pre (-> (! String :opaque) (! RegLan :opaque) String)) +(declare-const @re_first_match (-> (! String :opaque) (! RegLan :opaque) String)) +(declare-const @re_first_match_post (-> (! String :opaque) (! RegLan :opaque) String)) diff --git a/proofs/alf/cvc5/theories/Transcendentals.smt3 b/proofs/alf/cvc5/theories/Transcendentals.smt3 index 304f80cf962..23655946d32 100644 --- a/proofs/alf/cvc5/theories/Transcendentals.smt3 +++ b/proofs/alf/cvc5/theories/Transcendentals.smt3 @@ -19,5 +19,5 @@ (declare-const sqrt (-> Real Real)) ; skolems -(declare-const @transcendental_purify (-> (-> Real Real) Real Real)) -(declare-const @transcendental_purify_arg (-> Real Real)) +(declare-const @transcendental_purify (-> (! (-> Real Real) :opaque) Real Real)) +(declare-const @transcendental_purify_arg (-> (! Real :opaque) Real)) diff --git a/src/proof/alf/alf_node_converter.cpp b/src/proof/alf/alf_node_converter.cpp index 1a0a7fc6184..9fc19c752ae 100644 --- a/src/proof/alf/alf_node_converter.cpp +++ b/src/proof/alf/alf_node_converter.cpp @@ -326,14 +326,7 @@ Node AlfNodeConverter::maybeMkSkolemFun(Node k) TypeNode tn = k.getType(); if (sm->isSkolemFunction(k, sfi, cacheVal)) { - Node app; - if (sfi == SkolemId::PURIFY) - { - Assert(cacheVal.getType() == k.getType()); - // special case: just use self - app = convert(cacheVal); - } - else if (isHandledSkolemId(sfi)) + if (isHandledSkolemId(sfi)) { // convert every skolem function to its name applied to arguments std::stringstream ss; @@ -351,18 +344,7 @@ Node AlfNodeConverter::maybeMkSkolemFun(Node k) args.push_back(convert(cacheVal)); } // must convert all arguments - app = mkInternalApp(ss.str(), args, k.getType()); - } - if (!app.isNull()) - { - // If it has no children, then we don't wrap in `(skolem ...)`, since it - // makes no difference for substitution. Moreover, it is important not - // to do this since bitvector concat uses @bv_empty as its nil terminator. - if (sfi == SkolemId::PURIFY || app.getNumChildren() > 0) - { - // wrap in "skolem" operator - return mkInternalApp("skolem", {app}, k.getType()); - } + Node app = mkInternalApp(ss.str(), args, k.getType()); return app; } } From d2295e513514b632aac4e5ad8a12c52030ee9f7f Mon Sep 17 00:00:00 2001 From: Andrew Reynolds Date: Mon, 3 Jun 2024 21:04:12 -0500 Subject: [PATCH 20/35] Implement new simple proof rewrite rules for strings (#10830) Adds the implementation required for elaborating proofs into the rewrites introduced here: 0e3316f This currently fixes a timeout in the nightlies related to proof elaboration, where STR_IN_RE_EVAL is necessary. --- include/cvc5/cvc5_proof_rule.h | 56 +++++++++ src/api/cpp/cvc5_proof_rule_template.cpp | 5 + src/proof/alf/alf_printer.cpp | 5 +- src/theory/strings/sequences_rewriter.cpp | 107 ++++++++++++++++++ src/theory/strings/sequences_rewriter.h | 8 ++ test/regress/cli/CMakeLists.txt | 1 + .../regress0/strings/re-mem-eval-large.smt2 | 1 + .../regress0/strings/sigma-star-mod-3.smt2 | 6 + 8 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 test/regress/cli/regress0/strings/sigma-star-mod-3.smt2 diff --git a/include/cvc5/cvc5_proof_rule.h b/include/cvc5/cvc5_proof_rule.h index 4fd11d40f4f..70845234326 100644 --- a/include/cvc5/cvc5_proof_rule.h +++ b/include/cvc5/cvc5_proof_rule.h @@ -2537,6 +2537,62 @@ enum ENUM(ProofRewriteRule) : uint32_t * \endverbatim */ EVALUE(RE_LOOP_ELIM), + /** + * \verbatim embed:rst:leading-asterisk + * **Strings - regular expression membership evaluation** + * + * .. math:: + * \mathit{str.in\_re}(s, R) = c + * + * where :math:`s` is a constant string, :math:`R` is a constant regular + * expression and :math:`c` is true or false. + * + * \endverbatim + */ + EVALUE(STR_IN_RE_EVAL), + /** + * \verbatim embed:rst:leading-asterisk + * **Strings - regular expression loop elimination** + * + * .. math:: + * \mathit{str.in\_re}(\mathit{str}.\text{++}(s_1, \ldots, s_n), \mathit{re}.\text{*}(R)) = + * \mathit{str.in\_re}(s_1, \mathit{re}.\text{*}(R)) \wedge \ldots \wedge \mathit{str.in\_re}(s_n, \mathit{re}.\text{*}(R)) + * + * where all strings in :math:`R` have length one. + * + * \endverbatim + */ + EVALUE(STR_IN_RE_CONCAT_STAR_CHAR), + /** + * \verbatim embed:rst:leading-asterisk + * **Strings - string in regular expression sigma** + * + * .. math:: + * \mathit{str.in\_re}(s, \mathit{re}.\text{++}(\mathit{re.allchar}, \ldots, \mathit{re.allchar})) = + * (\mathit{str.len}(s) = n) + * + * or alternatively: + * + * .. math:: + * \mathit{str.in\_re}(s, \mathit{re}.\text{++}(\mathit{re.allchar}, \ldots, \mathit{re.allchar}, \mathit{re}.\text{*}(\mathit{re.allchar}))) = + * (\mathit{str.len}(s) \ge n) + * + * \endverbatim + */ + EVALUE(STR_IN_RE_SIGMA), + /** + * \verbatim embed:rst:leading-asterisk + * **Strings - string in regular expression sigma star** + * + * .. math:: + * \mathit{str.in\_re}(s, \mathit{re}.\text{*}(\mathit{re}.\text{++}(\mathit{re.allchar}, \ldots, \mathit{re.allchar}))) = + * (\mathit{str.len}(s) \ \% \ n = 0) + * + * where :math:`n` is the number of :math:`\mathit{re.allchar}` arguments to :math:`\mathit{re}.\text{++}`. + * + * \endverbatim + */ + EVALUE(STR_IN_RE_SIGMA_STAR), /** * \verbatim embed:rst:leading-asterisk * **Sets - empty tester evaluation** diff --git a/src/api/cpp/cvc5_proof_rule_template.cpp b/src/api/cpp/cvc5_proof_rule_template.cpp index 7048b7684fa..66c31043c46 100644 --- a/src/api/cpp/cvc5_proof_rule_template.cpp +++ b/src/api/cpp/cvc5_proof_rule_template.cpp @@ -245,6 +245,11 @@ const char* toString(cvc5::ProofRewriteRule rule) case ProofRewriteRule::DT_CONS_EQ: return "dt-cons-eq"; case ProofRewriteRule::RE_LOOP_ELIM: return "re-loop-elim"; + case ProofRewriteRule::STR_IN_RE_EVAL: return "str-in-re-eval"; + case ProofRewriteRule::STR_IN_RE_CONCAT_STAR_CHAR: + return "str-in-re-concat-star-char"; + case ProofRewriteRule::STR_IN_RE_SIGMA: return "str-in-re-sigma"; + case ProofRewriteRule::STR_IN_RE_SIGMA_STAR: return "str-in-re-sigma-star"; case ProofRewriteRule::SETS_IS_EMPTY_EVAL: return "sets-is-empty-eval"; //================================================= RARE rules diff --git a/src/proof/alf/alf_printer.cpp b/src/proof/alf/alf_printer.cpp index c11da452889..6631d868c39 100644 --- a/src/proof/alf/alf_printer.cpp +++ b/src/proof/alf/alf_printer.cpp @@ -203,7 +203,10 @@ bool AlfPrinter::isHandledTheoryRewrite(ProofRewriteRule id, switch (id) { case ProofRewriteRule::DISTINCT_ELIM: - case ProofRewriteRule::RE_LOOP_ELIM: return true; + case ProofRewriteRule::RE_LOOP_ELIM: + case ProofRewriteRule::STR_IN_RE_CONCAT_STAR_CHAR: + case ProofRewriteRule::STR_IN_RE_SIGMA: + case ProofRewriteRule::STR_IN_RE_SIGMA_STAR: return true; default: break; } return false; diff --git a/src/theory/strings/sequences_rewriter.cpp b/src/theory/strings/sequences_rewriter.cpp index d6374aa6327..56dab563a97 100644 --- a/src/theory/strings/sequences_rewriter.cpp +++ b/src/theory/strings/sequences_rewriter.cpp @@ -51,6 +51,14 @@ SequencesRewriter::SequencesRewriter(NodeManager* nm, d_false = nm->mkConst(false); registerProofRewriteRule(ProofRewriteRule::RE_LOOP_ELIM, TheoryRewriteCtx::PRE_DSL); + registerProofRewriteRule(ProofRewriteRule::STR_IN_RE_EVAL, + TheoryRewriteCtx::DSL_SUBCALL); + registerProofRewriteRule(ProofRewriteRule::STR_IN_RE_CONCAT_STAR_CHAR, + TheoryRewriteCtx::PRE_DSL); + registerProofRewriteRule(ProofRewriteRule::STR_IN_RE_SIGMA, + TheoryRewriteCtx::PRE_DSL); + registerProofRewriteRule(ProofRewriteRule::STR_IN_RE_SIGMA_STAR, + TheoryRewriteCtx::PRE_DSL); } Node SequencesRewriter::rewriteViaRule(ProofRewriteRule id, const Node& n) @@ -59,6 +67,12 @@ Node SequencesRewriter::rewriteViaRule(ProofRewriteRule id, const Node& n) { case ProofRewriteRule::RE_LOOP_ELIM: return rewriteViaReLoopElim(n); + case ProofRewriteRule::STR_IN_RE_EVAL: return rewriteViaStrInReEval(n); + case ProofRewriteRule::STR_IN_RE_CONCAT_STAR_CHAR: + return rewriteViaStrInReConcatStarChar(n); + case ProofRewriteRule::STR_IN_RE_SIGMA: return rewriteViaStrInReSigma(n); + case ProofRewriteRule::STR_IN_RE_SIGMA_STAR: + return rewriteViaStrInReSigmaStar(n); default: break; } return Node::null(); @@ -1291,6 +1305,99 @@ Node SequencesRewriter::rewriteViaReLoopElim(const Node& node) return retNode; } +Node SequencesRewriter::rewriteViaStrInReEval(const Node& node) +{ + if (node.getKind() != Kind::STRING_IN_REGEXP || !node[0].isConst() + || !RegExpEntail::isConstRegExp(node[1])) + { + return Node::null(); + } + // test whether x in node[1] + String s = node[0].getConst(); + bool test = RegExpEntail::testConstStringInRegExp(s, node[1]); + return nodeManager()->mkConst(test); +} + +Node SequencesRewriter::rewriteViaStrInReConcatStarChar(const Node& n) +{ + if (n.getKind() != Kind::STRING_IN_REGEXP + || n[0].getKind() != Kind::STRING_CONCAT + || n[1].getKind() != Kind::REGEXP_STAR) + { + return Node::null(); + } + Node len = RegExpEntail::getFixedLengthForRegexp(n[1][0]); + if (len.isNull() || !len.isConst() || len.getConst() != Rational(1)) + { + return Node::null(); + } + NodeManager* nm = nodeManager(); + std::vector cc; + utils::getConcat(n[0], cc); + std::vector conj; + for (const Node& c : cc) + { + conj.push_back(nm->mkNode(Kind::STRING_IN_REGEXP, c, n[1])); + } + return nm->mkAnd(conj); +} + +Node SequencesRewriter::rewriteViaStrInReSigma(const Node& n) +{ + if (n.getKind() != Kind::STRING_IN_REGEXP + || n[1].getKind() != Kind::REGEXP_CONCAT) + { + return Node::null(); + } + const Node& r = n[1]; + bool allSigmaStrict = true; + size_t allSigmaMinSize = 0; + for (const Node& rc : r) + { + if (rc.getKind() == Kind::REGEXP_ALLCHAR) + { + allSigmaMinSize++; + } + else if (rc.getKind() == Kind::REGEXP_STAR + && rc[0].getKind() == Kind::REGEXP_ALLCHAR) + { + allSigmaStrict = false; + } + else + { + return Node::null(); + } + } + // x in re.++(_*, _, _) ---> str.len(x) >= 2 + NodeManager* nm = nodeManager(); + Node num = nm->mkConstInt(Rational(allSigmaMinSize)); + Node lenx = nm->mkNode(Kind::STRING_LENGTH, n[0]); + return nm->mkNode(allSigmaStrict ? Kind::EQUAL : Kind::GEQ, lenx, num); +} +Node SequencesRewriter::rewriteViaStrInReSigmaStar(const Node& n) +{ + if (n.getKind() != Kind::STRING_IN_REGEXP + || n[1].getKind() != Kind::REGEXP_STAR + || n[1][0].getKind() != Kind::REGEXP_CONCAT) + { + return Node::null(); + } + const Node& r = n[1][0]; + for (const Node& rc : r) + { + if (rc.getKind() != Kind::REGEXP_ALLCHAR) + { + return Node::null(); + } + } + NodeManager* nm = nodeManager(); + Node zero = nm->mkConstInt(Rational(0)); + Node num = nm->mkConstInt(Rational(r.getNumChildren())); + Node lenx = nm->mkNode(Kind::STRING_LENGTH, n[0]); + Node t = nm->mkNode(Kind::INTS_MODULUS, lenx, num); + return nm->mkNode(Kind::EQUAL, t, zero); +} + Node SequencesRewriter::rewriteRepeatRegExp(TNode node) { Assert(node.getKind() == Kind::REGEXP_REPEAT); diff --git a/src/theory/strings/sequences_rewriter.h b/src/theory/strings/sequences_rewriter.h index c6483593e9b..a22b1d0d82b 100644 --- a/src/theory/strings/sequences_rewriter.h +++ b/src/theory/strings/sequences_rewriter.h @@ -142,6 +142,14 @@ class SequencesRewriter : public TheoryRewriter //-------------------- ProofRewriteRule /** Rewrite based on RE_LOOP_ELIM */ Node rewriteViaReLoopElim(const Node& n); + /** Rewrite based on STR_IN_RE_EVAL */ + Node rewriteViaStrInReEval(const Node& n); + /** Rewrite based on STR_IN_RE_CONCAT_STAR_CHAR */ + Node rewriteViaStrInReConcatStarChar(const Node& n); + /** Rewrite based on STR_IN_RE_SIGMA */ + Node rewriteViaStrInReSigma(const Node& n); + /** Rewrite based on STR_IN_RE_SIGMA_STAR */ + Node rewriteViaStrInReSigmaStar(const Node& n); public: RewriteResponse postRewrite(TNode node) override; diff --git a/test/regress/cli/CMakeLists.txt b/test/regress/cli/CMakeLists.txt index 7fde9570087..b45475664a7 100644 --- a/test/regress/cli/CMakeLists.txt +++ b/test/regress/cli/CMakeLists.txt @@ -1848,6 +1848,7 @@ set(regress_0_tests regress0/strings/replaceall-eval.smt2 regress0/strings/rewrites-re-concat.smt2 regress0/strings/rewrites-v2.smt2 + regress0/strings/sigma-star-mod-3.smt2 regress0/strings/simple-include-mem.smt2 regress0/strings/simple-include-subrange.smt2 regress0/strings/simple-nth-fail.smt2 diff --git a/test/regress/cli/regress0/strings/re-mem-eval-large.smt2 b/test/regress/cli/regress0/strings/re-mem-eval-large.smt2 index ebbfd2ae465..4c5edd98995 100644 --- a/test/regress/cli/regress0/strings/re-mem-eval-large.smt2 +++ b/test/regress/cli/regress0/strings/re-mem-eval-large.smt2 @@ -1,3 +1,4 @@ +; DISABLE-TESTER: alf (set-logic ALL) (set-info :status unsat) (assert (str.in_re "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbcccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccddddddddddddddddddddeeeeeeeeeeeeeeeeeeeeee" (re.++ (re.* (str.to_re "a")) (re.* (str.to_re "b")) (re.* (str.to_re "c"))))) diff --git a/test/regress/cli/regress0/strings/sigma-star-mod-3.smt2 b/test/regress/cli/regress0/strings/sigma-star-mod-3.smt2 new file mode 100644 index 00000000000..5e7ad944d68 --- /dev/null +++ b/test/regress/cli/regress0/strings/sigma-star-mod-3.smt2 @@ -0,0 +1,6 @@ +; EXPECT: unsat +(set-logic ALL) +(declare-const s String) +(assert (str.in_re s (re.* (re.++ re.allchar re.allchar re.allchar)))) +(assert (= (str.len s) 10)) +(check-sat) From f072fede9cf36418caf9b4145b936c6aae4f2fce Mon Sep 17 00:00:00 2001 From: Andrew Reynolds Date: Tue, 4 Jun 2024 06:41:05 -0500 Subject: [PATCH 21/35] Change variable comparison order in ALF signature (#10843) This change leads to significantly better proof checking times, as most instances of ACI_NORM will leave terms close to unchanged. Previously, we were doing worse-case performance where terms would mostly always be reversed. Also removes a mistakenly duplicated side condition. --- proofs/alf/cvc5/programs/Utils.smt3 | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/proofs/alf/cvc5/programs/Utils.smt3 b/proofs/alf/cvc5/programs/Utils.smt3 index 0964d799d97..259268cf110 100644 --- a/proofs/alf/cvc5/programs/Utils.smt3 +++ b/proofs/alf/cvc5/programs/Utils.smt3 @@ -29,17 +29,6 @@ (define $compare_geq ((T Type :implicit) (x T) (y T)) (alf.not (alf.is_neg (alf.add (alf.neg y) x)))) -; Compare arithmetic greater than. Assumes x and y are values. -; Returns true if x > y. -(define $compare_gt ((T Type :implicit) (x T) (y T)) - (alf.is_neg (alf.add (alf.neg x) y))) - -; Compare arithmetic greater than. Assumes x and y are values. -; Returns true if x >= y. -(define $compare_geq ((T Type :implicit) (x T) (y T)) - (alf.not (alf.is_neg (alf.add (alf.neg y) x)))) - - (declare-type @Pair (Type Type)) (declare-const @pair (-> (! Type :var U :implicit) (! Type :var T :implicit) U T (@Pair U T))) @@ -50,8 +39,12 @@ ; This is used to have a canonical ordering of variables. ; It could potentially be improved by having a builtin operator, e.g. alf.compare. +; The variable ordering always returns true for the variable with the lower +; hash. This leads to best case performance if sorting a term with distinct +; children, where if we ask for the hash of the children in order and prefer +; the ones where this compare returns true, then the term remains unchanged. (define compare_var ((T Type :implicit) (U Type :implicit) (a T) (b U)) - (alf.is_neg (alf.add (alf.hash a) (alf.neg (alf.hash b))))) + (alf.is_neg (alf.add (alf.hash b) (alf.neg (alf.hash a))))) ; Returns the tail of x, where x is expected to be a function application. (define $tail ((S Type :implicit) (x S)) @@ -147,4 +140,3 @@ t (((f x y) (let ((id (alf.nil f x y))) (singleton_elim f id (get_a_norm_rec f id t)))))) ) - From 0297d3fbd09a107aa52cd8877af32113582df3c7 Mon Sep 17 00:00:00 2001 From: Andrew Reynolds Date: Tue, 4 Jun 2024 07:02:01 -0500 Subject: [PATCH 22/35] Do not multiply by one in MACRO_ARITH_SUM_UB (#10846) This modifies the MACRO_ARITH_SUM_UB to sum (lhs,rhs) instead of ((* 1 lhs), (* 1 rhs)) when multiplying a relation by 1. This reductions our overall average proof size by about 1.7%, on regressions: --- src/theory/arith/arith_proof_utilities.cpp | 6 ++++++ src/theory/arith/proof_checker.cpp | 13 +++++++++++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/theory/arith/arith_proof_utilities.cpp b/src/theory/arith/arith_proof_utilities.cpp index 13c1261a020..e98f09b318c 100644 --- a/src/theory/arith/arith_proof_utilities.cpp +++ b/src/theory/arith/arith_proof_utilities.cpp @@ -69,6 +69,12 @@ Node expandMacroSumUb(const std::vector& children, { TNode child = children[i]; TNode scalar = args[i]; + if (scalar.getConst() == 1) + { + // if scaled by one, just take original + scaledRels.push_back(child); + continue; + } bool isPos = scalar.getConst() > 0; Node scalarCmp = nm->mkNode(isPos ? Kind::GT : Kind::LT, diff --git a/src/theory/arith/proof_checker.cpp b/src/theory/arith/proof_checker.cpp index 7b559491203..3a2223ec03d 100644 --- a/src/theory/arith/proof_checker.cpp +++ b/src/theory/arith/proof_checker.cpp @@ -277,8 +277,17 @@ Node ArithProofRuleChecker::checkInternal(ProofRule id, << "Bad kind: " << children[i].getKind() << std::endl; } } - leftSum << nm->mkNode(Kind::MULT, args[i], children[i][0]); - rightSum << nm->mkNode(Kind::MULT, args[i], children[i][1]); + // if multiplying by one, don't introduce MULT + if (scalar == 1) + { + leftSum << children[i][0]; + rightSum << children[i][1]; + } + else + { + leftSum << nm->mkNode(Kind::MULT, args[i], children[i][0]); + rightSum << nm->mkNode(Kind::MULT, args[i], children[i][1]); + } } Node r = nm->mkNode(strict ? Kind::LT : Kind::LEQ, leftSum.constructNode(), From a0ac967b2d6890fa418cfac0844f44e369f4ee72 Mon Sep 17 00:00:00 2001 From: Andrew Reynolds Date: Tue, 4 Jun 2024 09:12:52 -0500 Subject: [PATCH 23/35] Add support for sets is-empty theory rewrite in ALF (#10842) --- proofs/alf/cvc5/Cvc5.smt3 | 2 +- proofs/alf/cvc5/rules/Sets.smt3 | 18 ++++++++++++++++++ src/proof/alf/alf_printer.cpp | 1 + 3 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 proofs/alf/cvc5/rules/Sets.smt3 diff --git a/proofs/alf/cvc5/Cvc5.smt3 b/proofs/alf/cvc5/Cvc5.smt3 index f4b4b071d3f..5277f0924fb 100644 --- a/proofs/alf/cvc5/Cvc5.smt3 +++ b/proofs/alf/cvc5/Cvc5.smt3 @@ -9,7 +9,7 @@ (include "./theories/Transcendentals.smt3") (include "./theories/BitVectors.smt3") (include "./rules/Strings.smt3") -(include "./theories/Sets.smt3") +(include "./rules/Sets.smt3") (include "./theories/Bags.smt3") (include "./theories/FiniteFields.smt3") (include "./rules/Quantifiers.smt3") diff --git a/proofs/alf/cvc5/rules/Sets.smt3 b/proofs/alf/cvc5/rules/Sets.smt3 new file mode 100644 index 00000000000..498f2628c21 --- /dev/null +++ b/proofs/alf/cvc5/rules/Sets.smt3 @@ -0,0 +1,18 @@ +(include "../theories/Sets.smt3") + +(define $set_is_empty_eval ((T Type :implicit) (t (Set T))) + (alf.match ((U Type) (x U) (s (Set U))) + t + ( + ((set.empty U) true) + ((set.singleton x) false) + ((set.union (set.singleton x) s) false) + ) + ) +) + +(declare-rule sets-is-empty-eval ((T Type) (t (Set T)) (b Bool)) + :args ((= (set.is_empty t) b)) + :requires ((($set_is_empty_eval t) b)) + :conclusion (= (set.is_empty t) b) +) diff --git a/src/proof/alf/alf_printer.cpp b/src/proof/alf/alf_printer.cpp index 6631d868c39..900a0eda568 100644 --- a/src/proof/alf/alf_printer.cpp +++ b/src/proof/alf/alf_printer.cpp @@ -204,6 +204,7 @@ bool AlfPrinter::isHandledTheoryRewrite(ProofRewriteRule id, { case ProofRewriteRule::DISTINCT_ELIM: case ProofRewriteRule::RE_LOOP_ELIM: + case ProofRewriteRule::SETS_IS_EMPTY_EVAL: case ProofRewriteRule::STR_IN_RE_CONCAT_STAR_CHAR: case ProofRewriteRule::STR_IN_RE_SIGMA: case ProofRewriteRule::STR_IN_RE_SIGMA_STAR: return true; From c91bea9fb0b50a3ebfe7355c1c3e4edeac5432a4 Mon Sep 17 00:00:00 2001 From: Daniel Larraz Date: Tue, 4 Jun 2024 11:04:30 -0500 Subject: [PATCH 24/35] Specify Threads as a dependency explicitly (#10790) Building cvc5 with CryptoMiniSat generates a dependency on the thread library. The config file cvc5Targets.cmake automatically generated by CMake didn't reflect this because the dependency was not explicitly specified. It was relying on passing the -pthread compiler and linker flag. This PR explicitly adds Threads as a dependency. --- CMakeLists.txt | 3 --- cmake/cvc5Config.cmake.in | 5 +++++ src/CMakeLists.txt | 1 + 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index eb7a345512f..ccc7801246a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -513,9 +513,6 @@ if(USE_CRYPTOMINISAT) # CryptoMiniSat requires pthreads support set(THREADS_PREFER_PTHREAD_FLAG ON) find_package(Threads REQUIRED) - if(THREADS_HAVE_PTHREAD_ARG) - add_c_cxx_flag(-pthread) - endif() find_package(CryptoMiniSat 5.8 REQUIRED) add_definitions(-DCVC5_USE_CRYPTOMINISAT) endif() diff --git a/cmake/cvc5Config.cmake.in b/cmake/cvc5Config.cmake.in index 7ebec6c830c..1ee0ea32809 100644 --- a/cmake/cvc5Config.cmake.in +++ b/cmake/cvc5Config.cmake.in @@ -17,6 +17,11 @@ set(CVC5_BINDINGS_JAVA @BUILD_BINDINGS_JAVA@) set(CVC5_BINDINGS_PYTHON @BUILD_BINDINGS_PYTHON@) set(CVC5_BINDINGS_PYTHON_VERSION @BUILD_BINDINGS_PYTHON_VERSION@) set(CVC5_USE_COCOA @USE_COCOA@) +set(CVC5_USE_CRYPTOMINISAT @USE_CRYPTOMINISAT@) + +if (CVC5_USE_CRYPTOMINISAT) + find_package(Threads REQUIRED) +endif() if(NOT TARGET cvc5::cvc5) include(${CMAKE_CURRENT_LIST_DIR}/cvc5Targets.cmake) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 330347243cc..ba949708ad3 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1504,6 +1504,7 @@ if(USE_CRYPTOMINISAT) add_dependencies(cvc5-obj CryptoMiniSat) target_include_directories(cvc5-obj SYSTEM PRIVATE ${CryptoMiniSat_INCLUDE_DIR}) target_link_libraries(cvc5 PRIVATE $ $) + target_link_libraries(cvc5 PRIVATE Threads::Threads) # Required by CryptoMiniSat endif() if(USE_KISSAT) add_dependencies(cvc5-obj Kissat) From d4f3860dd446b97514b64a59f1aeaa856dd7c3f8 Mon Sep 17 00:00:00 2001 From: alanctprado <63134523+alanctprado@users.noreply.github.com> Date: Tue, 4 Jun 2024 13:43:02 -0300 Subject: [PATCH 25/35] Improve error message when 'patch' command is missing (#10760) Previously, if the 'patch' command was absent from the system when using CoCoA, the configuration phase proceeded without issues. However, upon running 'make' to build the system, the following error message was encountered: ``` [ 7%] No update step for 'CoCoA-EP' [ 7%] Performing patch step for 'CoCoA-EP' CMake Error at /home/alan/cvc5/build/deps/src/CoCoA-EP-stamp/CoCoA-EP-patch-Production.cmake:37 (message): Command failed: No such file or directory 'patch' '-p1' '-d' '/home/alan/cvc5/build/deps/src/CoCoA-EP' '-i' '/home/alan/cvc5/cmake/deps-utils/CoCoALib-0.9980 0-trace.patch' See also /home/alan/cvc5/build/deps/src/CoCoA-EP-stamp/CoCoA-EP-patch.log -- Log output is: CMake Error at /home/alan/cvc5/build/deps/src/CoCoA-EP-stamp/CoCoA-EP-patch-Production.cmake:47 (message): Stopping after outputting logs. make[2]: *** [CMakeFiles/CoCoA-EP.dir/build.make:122: deps/src/CoCoA-EP-stamp/CoCoA-EP-patch] Error 1 make[1]: *** [CMakeFiles/Makefile2:620: CMakeFiles/CoCoA-EP.dir/all] Error 2 make[1]: *** Waiting for unfinished jobs.... ``` In addition to lacking clarity regarding the underlying issue, the message is somewhat misleading because the file 'CoCoA-EP-patch.log' is empty. This PR addresses these issues by checking the presence of the 'patch' command in the user's system during the configuration phase of CVC5 (naturally, this occurs only if the '--cocoa' option is set and 'CoCoA' isn't already installed). These changes do not affect users that are configuring CVC5 without the '--cocoa' flag, regardless of having the 'patch' package installed on their system. The improved error message provided is: ``` -- Could NOT find Patch (missing: Patch_EXECUTABLE) CMake Error at cmake/FindCoCoA.cmake:58 (MESSAGE): The command `patch` required for installing CoCoA was not found. Please install it using one of the following commands: - On Debian/Ubuntu: sudo apt install patch - On Fedora/Red Hat: sudo dnf install patch - For other OS's, refer to system-specific resources for guidance on installing `patch`. Call Stack (most recent call first): CMakeLists.txt:541 (find_package) -- Configuring incomplete, errors occurred! ``` Signed-off-by: Alan Prado --- cmake/FindCoCoA.cmake | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cmake/FindCoCoA.cmake b/cmake/FindCoCoA.cmake index 0de3e06c1d0..ae09a6eb7e2 100644 --- a/cmake/FindCoCoA.cmake +++ b/cmake/FindCoCoA.cmake @@ -53,6 +53,11 @@ if(NOT CoCoA_FOUND_SYSTEM) get_target_property(GMP_LIBRARY GMP IMPORTED_LOCATION) + find_program(PATCH_BIN patch) + if(NOT PATCH_BIN) + message(FATAL_ERROR "Can not build CoCoA, missing binary for patch") + endif() + ExternalProject_Add( CoCoA-EP ${COMMON_EP_CONFIG} From b8d6875b5b7a10c09cb093de49663f3a6f641de0 Mon Sep 17 00:00:00 2001 From: Aina Niemetz Date: Tue, 4 Jun 2024 11:47:09 -0700 Subject: [PATCH 26/35] c api: Add sort functions and tests. (#10787) --- include/cvc5/c/cvc5.h | 6 +- src/api/c/cvc5.cpp | 849 +++++++++++++++++- src/api/c/cvc5_checks.h | 7 + src/api/cpp/cvc5.cpp | 126 +-- test/unit/api/c/CMakeLists.txt | 1 + test/unit/api/c/capi_sort_black.cpp | 718 +++++++++++++++ test/unit/api/cpp/CMakeLists.txt | 2 +- .../{sort_black.cpp => api_sort_black.cpp} | 29 +- 8 files changed, 1645 insertions(+), 93 deletions(-) create mode 100644 test/unit/api/c/capi_sort_black.cpp rename test/unit/api/cpp/{sort_black.cpp => api_sort_black.cpp} (96%) diff --git a/include/cvc5/c/cvc5.h b/include/cvc5/c/cvc5.h index 0aadb088804..9979f10a8d4 100644 --- a/include/cvc5/c/cvc5.h +++ b/include/cvc5/c/cvc5.h @@ -343,7 +343,7 @@ bool cvc5_sort_is_string(Cvc5Sort sort); * @param sort The sort. * @return True if given sort is the regular expression sort. */ -bool cvc5_sort_is_reg_exp(Cvc5Sort sort); +bool cvc5_sort_is_regexp(Cvc5Sort sort); /** * Determine if given sort is the rounding mode sort @@ -531,7 +531,7 @@ Cvc5Sort cvc5_sort_get_uninterpreted_sort_constructor(Cvc5Sort sort); * @param sort The sort. * @return The underlying datatype of a datatype sort. */ -Cvc5Datatype* cvc5_sort_get_datatype(Cvc5Sort sort); +Cvc5Datatype cvc5_sort_get_datatype(Cvc5Sort sort); /** * Instantiate a parameterized datatype sort or uninterpreted sort @@ -782,7 +782,7 @@ uint32_t cvc5_sort_bv_get_size(Cvc5Sort sort); * @note The returned char* pointer is only valid until the next call to this * function. */ -const char* cvc5_sort_ff_get_size(); +const char* cvc5_sort_ff_get_size(Cvc5Sort sort); /* Floating-point sort ------------------------------------------------- */ diff --git a/src/api/c/cvc5.cpp b/src/api/c/cvc5.cpp index 3d7a1cf2d45..53ea131a1fd 100644 --- a/src/api/c/cvc5.cpp +++ b/src/api/c/cvc5.cpp @@ -675,24 +675,834 @@ void Cvc5TermManager::release() } /* -------------------------------------------------------------------------- */ -/* Cvc5Datatype */ +/* Cvc5Sort */ /* -------------------------------------------------------------------------- */ -Cvc5Sort cvc5_sort_instantiate(Cvc5Sort sort, - size_t size, - const Cvc5Sort params[]) +bool cvc5_sort_is_equal(Cvc5Sort a, Cvc5Sort b) +{ + bool res = false; + CVC5_CAPI_TRY_CATCH_BEGIN; + if (a == nullptr || b == nullptr) + { + res = a == b; + } + else + { + res = a->d_sort == b->d_sort; + } + CVC5_CAPI_TRY_CATCH_END; + return res; +} + +bool cvc5_sort_is_disequal(Cvc5Sort a, Cvc5Sort b) +{ + bool res = false; + CVC5_CAPI_TRY_CATCH_BEGIN; + if (a == nullptr || b == nullptr) + { + res = a != b; + } + else + { + res = a->d_sort != b->d_sort; + } + CVC5_CAPI_TRY_CATCH_END; + return res; +} + +int64_t cvc5_sort_compare(Cvc5Sort a, Cvc5Sort b) +{ + int64_t res = false; + CVC5_CAPI_TRY_CATCH_BEGIN; + CVC5_CAPI_CHECK_SORT(a); + CVC5_CAPI_CHECK_SORT(b); + res = a->d_sort < b->d_sort ? -1 : (a->d_sort > b->d_sort ? 1 : 0); + CVC5_CAPI_TRY_CATCH_END; + return res; +} + +Cvc5SortKind cvc5_sort_get_kind(Cvc5Sort sort) +{ + Cvc5SortKind res = CVC5_SORT_KIND_INTERNAL_SORT_KIND; + CVC5_CAPI_TRY_CATCH_BEGIN; + CVC5_CAPI_CHECK_SORT(sort); + res = static_cast(sort->d_sort.getKind()); + CVC5_CAPI_TRY_CATCH_END; + return res; +} + +bool cvc5_sort_has_symbol(Cvc5Sort sort) +{ + bool res = false; + CVC5_CAPI_TRY_CATCH_BEGIN; + if (sort) + { + res = sort->d_sort.hasSymbol(); + } + CVC5_CAPI_TRY_CATCH_END; + return res; +} + +const char* cvc5_sort_get_symbol(Cvc5Sort sort) +{ + const char* res = nullptr; + CVC5_CAPI_TRY_CATCH_BEGIN; + CVC5_CAPI_CHECK_SORT(sort); + CVC5_API_CHECK(cvc5_sort_has_symbol(sort)) + << "cannot get symbol of sort that has no symbol"; + static thread_local std::string str; + if (sort->d_sort.hasSymbol()) + { + str = sort->d_sort.getSymbol(); + res = str.c_str(); + } + CVC5_CAPI_TRY_CATCH_END; + return res; +} + +bool cvc5_sort_is_boolean(Cvc5Sort sort) +{ + bool res = false; + CVC5_CAPI_TRY_CATCH_BEGIN; + if (sort) + { + res = sort->d_sort.isBoolean(); + } + CVC5_CAPI_TRY_CATCH_END; + return res; +} + +bool cvc5_sort_is_integer(Cvc5Sort sort) +{ + bool res = false; + CVC5_CAPI_TRY_CATCH_BEGIN; + if (sort) + { + res = sort->d_sort.isInteger(); + } + CVC5_CAPI_TRY_CATCH_END; + return res; +} + +bool cvc5_sort_is_real(Cvc5Sort sort) +{ + bool res = false; + CVC5_CAPI_TRY_CATCH_BEGIN; + if (sort) + { + res = sort->d_sort.isReal(); + } + CVC5_CAPI_TRY_CATCH_END; + return res; +} + +bool cvc5_sort_is_string(Cvc5Sort sort) +{ + bool res = false; + CVC5_CAPI_TRY_CATCH_BEGIN; + if (sort) + { + res = sort->d_sort.isString(); + } + CVC5_CAPI_TRY_CATCH_END; + return res; +} + +bool cvc5_sort_is_regexp(Cvc5Sort sort) +{ + bool res = false; + CVC5_CAPI_TRY_CATCH_BEGIN; + if (sort) + { + res = sort->d_sort.isRegExp(); + } + CVC5_CAPI_TRY_CATCH_END; + return res; +} + +bool cvc5_sort_is_rm(Cvc5Sort sort) +{ + bool res = false; + CVC5_CAPI_TRY_CATCH_BEGIN; + if (sort) + { + res = sort->d_sort.isRoundingMode(); + } + CVC5_CAPI_TRY_CATCH_END; + return res; +} + +bool cvc5_sort_is_bv(Cvc5Sort sort) +{ + bool res = false; + CVC5_CAPI_TRY_CATCH_BEGIN; + if (sort) + { + res = sort->d_sort.isBitVector(); + } + CVC5_CAPI_TRY_CATCH_END; + return res; +} + +bool cvc5_sort_is_fp(Cvc5Sort sort) +{ + bool res = false; + CVC5_CAPI_TRY_CATCH_BEGIN; + if (sort) + { + res = sort->d_sort.isFloatingPoint(); + } + CVC5_CAPI_TRY_CATCH_END; + return res; +} + +bool cvc5_sort_is_dt(Cvc5Sort sort) +{ + bool res = false; + CVC5_CAPI_TRY_CATCH_BEGIN; + if (sort) + { + res = sort->d_sort.isDatatype(); + } + CVC5_CAPI_TRY_CATCH_END; + return res; +} + +bool cvc5_sort_is_dt_constructor(Cvc5Sort sort) +{ + bool res = false; + CVC5_CAPI_TRY_CATCH_BEGIN; + if (sort) + { + res = sort->d_sort.isDatatypeConstructor(); + } + CVC5_CAPI_TRY_CATCH_END; + return res; +} + +bool cvc5_sort_is_dt_selector(Cvc5Sort sort) +{ + bool res = false; + CVC5_CAPI_TRY_CATCH_BEGIN; + if (sort) + { + res = sort->d_sort.isDatatypeSelector(); + } + CVC5_CAPI_TRY_CATCH_END; + return res; +} + +bool cvc5_sort_is_dt_tester(Cvc5Sort sort) +{ + bool res = false; + CVC5_CAPI_TRY_CATCH_BEGIN; + if (sort) + { + res = sort->d_sort.isDatatypeTester(); + } + CVC5_CAPI_TRY_CATCH_END; + return res; +} + +bool cvc5_sort_is_dt_updater(Cvc5Sort sort) +{ + bool res = false; + CVC5_CAPI_TRY_CATCH_BEGIN; + if (sort) + { + res = sort->d_sort.isDatatypeUpdater(); + } + CVC5_CAPI_TRY_CATCH_END; + return res; +} + +bool cvc5_sort_is_fun(Cvc5Sort sort) +{ + bool res = false; + CVC5_CAPI_TRY_CATCH_BEGIN; + if (sort) + { + res = sort->d_sort.isFunction(); + } + CVC5_CAPI_TRY_CATCH_END; + return res; +} + +bool cvc5_sort_is_predicate(Cvc5Sort sort) +{ + bool res = false; + CVC5_CAPI_TRY_CATCH_BEGIN; + if (sort) + { + res = sort->d_sort.isPredicate(); + } + CVC5_CAPI_TRY_CATCH_END; + return res; +} + +bool cvc5_sort_is_tuple(Cvc5Sort sort) +{ + bool res = false; + CVC5_CAPI_TRY_CATCH_BEGIN; + if (sort) + { + res = sort->d_sort.isTuple(); + } + CVC5_CAPI_TRY_CATCH_END; + return res; +} + +bool cvc5_sort_is_nullable(Cvc5Sort sort) +{ + bool res = false; + CVC5_CAPI_TRY_CATCH_BEGIN; + if (sort) + { + res = sort->d_sort.isNullable(); + } + CVC5_CAPI_TRY_CATCH_END; + return res; +} + +bool cvc5_sort_is_record(Cvc5Sort sort) +{ + bool res = false; + CVC5_CAPI_TRY_CATCH_BEGIN; + if (sort) + { + res = sort->d_sort.isRecord(); + } + CVC5_CAPI_TRY_CATCH_END; + return res; +} + +bool cvc5_sort_is_array(Cvc5Sort sort) +{ + bool res = false; + CVC5_CAPI_TRY_CATCH_BEGIN; + if (sort) + { + res = sort->d_sort.isArray(); + } + CVC5_CAPI_TRY_CATCH_END; + return res; +} + +bool cvc5_sort_is_ff(Cvc5Sort sort) +{ + bool res = false; + CVC5_CAPI_TRY_CATCH_BEGIN; + if (sort) + { + res = sort->d_sort.isFiniteField(); + } + CVC5_CAPI_TRY_CATCH_END; + return res; +} + +bool cvc5_sort_is_set(Cvc5Sort sort) +{ + bool res = false; + CVC5_CAPI_TRY_CATCH_BEGIN; + if (sort) + { + res = sort->d_sort.isSet(); + } + CVC5_CAPI_TRY_CATCH_END; + return res; +} + +bool cvc5_sort_is_bag(Cvc5Sort sort) +{ + bool res = false; + CVC5_CAPI_TRY_CATCH_BEGIN; + if (sort) + { + res = sort->d_sort.isBag(); + } + CVC5_CAPI_TRY_CATCH_END; + return res; +} + +bool cvc5_sort_is_sequence(Cvc5Sort sort) +{ + bool res = false; + CVC5_CAPI_TRY_CATCH_BEGIN; + if (sort) + { + res = sort->d_sort.isSequence(); + } + CVC5_CAPI_TRY_CATCH_END; + return res; +} + +bool cvc5_sort_is_abstract(Cvc5Sort sort) +{ + bool res = false; + CVC5_CAPI_TRY_CATCH_BEGIN; + if (sort) + { + res = sort->d_sort.isAbstract(); + } + CVC5_CAPI_TRY_CATCH_END; + return res; +} + +bool cvc5_sort_is_uninterpreted_sort(Cvc5Sort sort) +{ + bool res = false; + CVC5_CAPI_TRY_CATCH_BEGIN; + if (sort) + { + res = sort->d_sort.isUninterpretedSort(); + } + CVC5_CAPI_TRY_CATCH_END; + return res; +} + +bool cvc5_sort_is_uninterpreted_sort_constructor(Cvc5Sort sort) +{ + bool res = false; + CVC5_CAPI_TRY_CATCH_BEGIN; + if (sort) + { + res = sort->d_sort.isUninterpretedSortConstructor(); + } + CVC5_CAPI_TRY_CATCH_END; + return res; +} + +bool cvc5_sort_is_instantiated(Cvc5Sort sort) +{ + bool res = false; + CVC5_CAPI_TRY_CATCH_BEGIN; + if (sort) + { + res = sort->d_sort.isInstantiated(); + } + CVC5_CAPI_TRY_CATCH_END; + return res; +} + +Cvc5Sort cvc5_sort_get_uninterpreted_sort_constructor(Cvc5Sort sort) +{ + Cvc5Sort res = nullptr; + CVC5_CAPI_TRY_CATCH_BEGIN; + CVC5_CAPI_CHECK_SORT(sort); + res = sort->d_tm->export_sort(sort->d_sort.getUninterpretedSortConstructor()); + CVC5_CAPI_TRY_CATCH_END; + return res; +} + +Cvc5Datatype cvc5_sort_get_datatype(Cvc5Sort sort) +{ + Cvc5Datatype res = nullptr; + CVC5_CAPI_TRY_CATCH_BEGIN; + CVC5_CAPI_CHECK_SORT(sort); + res = sort->d_tm->export_dt(sort->d_sort.getDatatype()); + CVC5_CAPI_TRY_CATCH_END; + return res; +} + +Cvc5Sort cvc5_sort_instantiate(Cvc5Sort sort, + size_t size, + const Cvc5Sort params[]) +{ + Cvc5Sort res = nullptr; + CVC5_CAPI_TRY_CATCH_BEGIN; + CVC5_CAPI_CHECK_SORT(sort); + CVC5_CAPI_CHECK_NOT_NULL(params); + std::vector cparams; + for (uint32_t i = 0; i < size; ++i) + { + CVC5_CAPI_CHECK_SORT_AT_IDX(params, i); + cparams.push_back(params[i]->d_sort); + } + res = sort->d_tm->export_sort(sort->d_sort.instantiate(cparams)); + CVC5_CAPI_TRY_CATCH_END; + return res; +} + +const Cvc5Sort* cvc5_sort_get_instantiated_parameters(Cvc5Sort sort, + size_t* size) +{ + static thread_local std::vector res; + CVC5_CAPI_TRY_CATCH_BEGIN; + CVC5_CAPI_CHECK_SORT(sort); + CVC5_CAPI_CHECK_NOT_NULL(size); + res.clear(); + auto sorts = sort->d_sort.getInstantiatedParameters(); + auto tm = sort->d_tm; + for (auto& s : sorts) + { + res.push_back(tm->export_sort(s)); + } + *size = res.size(); + CVC5_CAPI_TRY_CATCH_END; + return *size > 0 ? res.data() : nullptr; +} + +Cvc5Sort cvc5_sort_substitute(Cvc5Sort sort, Cvc5Sort s, Cvc5Sort replacement) +{ + Cvc5Sort res = nullptr; + CVC5_CAPI_TRY_CATCH_BEGIN; + CVC5_CAPI_CHECK_SORT(sort); + CVC5_CAPI_CHECK_SORT(s); + CVC5_CAPI_CHECK_SORT(replacement); + res = sort->d_tm->export_sort( + sort->d_sort.substitute(s->d_sort, replacement->d_sort)); + CVC5_CAPI_TRY_CATCH_END; + return res; +} + +Cvc5Sort cvc5_sort_substitute_sorts(Cvc5Sort sort, + size_t size, + const Cvc5Sort sorts[], + const Cvc5Sort replacements[]) +{ + Cvc5Sort res = nullptr; + CVC5_CAPI_TRY_CATCH_BEGIN; + CVC5_CAPI_CHECK_SORT(sort); + CVC5_CAPI_CHECK_NOT_NULL(sorts); + CVC5_CAPI_CHECK_NOT_NULL(replacements); + std::vector csorts; + for (uint32_t i = 0; i < size; ++i) + { + CVC5_CAPI_CHECK_SORT_AT_IDX(sorts, i); + csorts.push_back(sorts[i]->d_sort); + } + std::vector creplacements; + for (uint32_t i = 0; i < size; ++i) + { + CVC5_CAPI_CHECK_SORT_AT_IDX(replacements, i); + creplacements.push_back(replacements[i]->d_sort); + } + res = sort->d_tm->export_sort(sort->d_sort.substitute(csorts, creplacements)); + CVC5_CAPI_TRY_CATCH_END; + return res; +} + +const char* cvc5_sort_to_string(Cvc5Sort sort) +{ + static thread_local std::string str; + CVC5_CAPI_TRY_CATCH_BEGIN; + CVC5_CAPI_CHECK_SORT(sort); + str = sort->d_sort.toString(); + CVC5_CAPI_TRY_CATCH_END; + return str.c_str(); +} + +size_t cvc5_sort_hash(Cvc5Sort sort) +{ + size_t res = 0; + CVC5_CAPI_TRY_CATCH_BEGIN; + CVC5_CAPI_CHECK_SORT(sort); + res = std::hash{}(sort->d_sort); + CVC5_CAPI_TRY_CATCH_END; + return res; +} + +/* Datatype constructor sort ------------------------------------------- */ + +size_t cvc5_sort_dt_constructor_get_arity(Cvc5Sort sort) +{ + size_t res = 0; + CVC5_CAPI_TRY_CATCH_BEGIN; + CVC5_CAPI_CHECK_SORT(sort); + res = sort->d_sort.getDatatypeConstructorArity(); + CVC5_CAPI_TRY_CATCH_END; + return res; +} + +const Cvc5Sort* cvc5_sort_dt_constructor_get_domain(Cvc5Sort sort, size_t* size) +{ + static thread_local std::vector res; + CVC5_CAPI_TRY_CATCH_BEGIN; + CVC5_CAPI_CHECK_SORT(sort); + CVC5_CAPI_CHECK_NOT_NULL(size); + res.clear(); + auto sorts = sort->d_sort.getDatatypeConstructorDomainSorts(); + auto tm = sort->d_tm; + for (auto& s : sorts) + { + res.push_back(tm->export_sort(s)); + } + *size = res.size(); + CVC5_CAPI_TRY_CATCH_END; + return *size > 0 ? res.data() : nullptr; +} + +Cvc5Sort cvc5_sort_dt_constructor_get_codomain(Cvc5Sort sort) { Cvc5Sort res = nullptr; CVC5_CAPI_TRY_CATCH_BEGIN; CVC5_CAPI_CHECK_SORT(sort); - CVC5_CAPI_CHECK_NOT_NULL(params); - std::vector cparams; - for (uint32_t i = 0; i < size; ++i) + res = sort->d_tm->export_sort( + sort->d_sort.getDatatypeConstructorCodomainSort()); + CVC5_CAPI_TRY_CATCH_END; + return res; +} + +/* Dataype Selector sort ------------------------------------------------ */ + +Cvc5Sort cvc5_sort_dt_selector_get_domain(Cvc5Sort sort) +{ + Cvc5Sort res = nullptr; + CVC5_CAPI_TRY_CATCH_BEGIN; + CVC5_CAPI_CHECK_SORT(sort); + res = sort->d_tm->export_sort(sort->d_sort.getDatatypeSelectorDomainSort()); + CVC5_CAPI_TRY_CATCH_END; + return res; +} + +Cvc5Sort cvc5_sort_dt_selector_get_codomain(Cvc5Sort sort) +{ + Cvc5Sort res = nullptr; + CVC5_CAPI_TRY_CATCH_BEGIN; + CVC5_CAPI_CHECK_SORT(sort); + res = sort->d_tm->export_sort(sort->d_sort.getDatatypeSelectorCodomainSort()); + CVC5_CAPI_TRY_CATCH_END; + return res; +} + +/* Datatype Tester sort ------------------------------------------------ */ + +Cvc5Sort cvc5_sort_dt_tester_get_domain(Cvc5Sort sort) +{ + Cvc5Sort res = nullptr; + CVC5_CAPI_TRY_CATCH_BEGIN; + CVC5_CAPI_CHECK_SORT(sort); + res = sort->d_tm->export_sort(sort->d_sort.getDatatypeTesterDomainSort()); + CVC5_CAPI_TRY_CATCH_END; + return res; +} + +Cvc5Sort cvc5_sort_dt_tester_get_codomain(Cvc5Sort sort) +{ + Cvc5Sort res = nullptr; + CVC5_CAPI_TRY_CATCH_BEGIN; + CVC5_CAPI_CHECK_SORT(sort); + res = sort->d_tm->export_sort(sort->d_sort.getDatatypeTesterCodomainSort()); + CVC5_CAPI_TRY_CATCH_END; + return res; +} + +/* Function sort ------------------------------------------------------- */ + +size_t cvc5_sort_fun_get_arity(Cvc5Sort sort) +{ + size_t res = 0; + CVC5_CAPI_TRY_CATCH_BEGIN; + CVC5_CAPI_CHECK_SORT(sort); + res = sort->d_sort.getFunctionArity(); + CVC5_CAPI_TRY_CATCH_END; + return res; +} + +const Cvc5Sort* cvc5_sort_fun_get_domain(Cvc5Sort sort, size_t* size) +{ + static thread_local std::vector res; + CVC5_CAPI_TRY_CATCH_BEGIN; + CVC5_CAPI_CHECK_SORT(sort); + CVC5_CAPI_CHECK_NOT_NULL(size); + res.clear(); + auto sorts = sort->d_sort.getFunctionDomainSorts(); + auto tm = sort->d_tm; + for (auto& s : sorts) { - CVC5_CAPI_CHECK_SORT_AT_IDX(params, i); - cparams.push_back(params[i]->d_sort); + res.push_back(tm->export_sort(s)); } - res = sort->d_tm->export_sort(sort->d_sort.instantiate(cparams)); + *size = res.size(); + CVC5_CAPI_TRY_CATCH_END; + return *size > 0 ? res.data() : nullptr; +} + +Cvc5Sort cvc5_sort_fun_get_codomain(Cvc5Sort sort) +{ + Cvc5Sort res = nullptr; + CVC5_CAPI_TRY_CATCH_BEGIN; + CVC5_CAPI_CHECK_SORT(sort); + res = sort->d_tm->export_sort(sort->d_sort.getFunctionCodomainSort()); + CVC5_CAPI_TRY_CATCH_END; + return res; +} + +/* Array sort ---------------------------------------------------------- */ + +Cvc5Sort cvc5_sort_array_get_index_sort(Cvc5Sort sort) +{ + Cvc5Sort res = nullptr; + CVC5_CAPI_TRY_CATCH_BEGIN; + CVC5_CAPI_CHECK_SORT(sort); + res = sort->d_tm->export_sort(sort->d_sort.getArrayIndexSort()); + CVC5_CAPI_TRY_CATCH_END; + return res; +} + +Cvc5Sort cvc5_sort_array_get_element_sort(Cvc5Sort sort) +{ + Cvc5Sort res = nullptr; + CVC5_CAPI_TRY_CATCH_BEGIN; + CVC5_CAPI_CHECK_SORT(sort); + res = sort->d_tm->export_sort(sort->d_sort.getArrayElementSort()); + CVC5_CAPI_TRY_CATCH_END; + return res; +} + +/* Set sort ------------------------------------------------------------ */ + +Cvc5Sort cvc5_sort_set_get_element_sort(Cvc5Sort sort) +{ + Cvc5Sort res = nullptr; + CVC5_CAPI_TRY_CATCH_BEGIN; + CVC5_CAPI_CHECK_SORT(sort); + res = sort->d_tm->export_sort(sort->d_sort.getSetElementSort()); + CVC5_CAPI_TRY_CATCH_END; + return res; +} + +/* Bag sort ------------------------------------------------------------ */ + +Cvc5Sort cvc5_sort_bag_get_element_sort(Cvc5Sort sort) +{ + Cvc5Sort res = nullptr; + CVC5_CAPI_TRY_CATCH_BEGIN; + CVC5_CAPI_CHECK_SORT(sort); + res = sort->d_tm->export_sort(sort->d_sort.getBagElementSort()); + CVC5_CAPI_TRY_CATCH_END; + return res; +} + +/* Sequence sort ------------------------------------------------------- */ + +Cvc5Sort cvc5_sort_sequence_get_element_sort(Cvc5Sort sort) +{ + Cvc5Sort res = nullptr; + CVC5_CAPI_TRY_CATCH_BEGIN; + CVC5_CAPI_CHECK_SORT(sort); + res = sort->d_tm->export_sort(sort->d_sort.getSequenceElementSort()); + CVC5_CAPI_TRY_CATCH_END; + return res; +} + +/* Abstract sort ------------------------------------------------------- */ + +Cvc5SortKind cvc5_sort_abstract_get_kind(Cvc5Sort sort) +{ + Cvc5SortKind res = CVC5_SORT_KIND_INTERNAL_SORT_KIND; + CVC5_CAPI_TRY_CATCH_BEGIN; + CVC5_CAPI_CHECK_SORT(sort); + res = static_cast(sort->d_sort.getAbstractedKind()); + CVC5_CAPI_TRY_CATCH_END; + return res; +} + +/* Uninterpreted sort constructor sort --------------------------------- */ + +size_t cvc5_sort_uninterpreted_sort_constructor_get_arity(Cvc5Sort sort) +{ + size_t res = 0; + CVC5_CAPI_TRY_CATCH_BEGIN; + CVC5_CAPI_CHECK_SORT(sort); + res = sort->d_sort.getUninterpretedSortConstructorArity(); + CVC5_CAPI_TRY_CATCH_END; + return res; +} + +/* Bit-vector sort ----------------------------------------------------- */ + +uint32_t cvc5_sort_bv_get_size(Cvc5Sort sort) +{ + uint32_t res = 0; + CVC5_CAPI_TRY_CATCH_BEGIN; + CVC5_CAPI_CHECK_SORT(sort); + res = sort->d_sort.getBitVectorSize(); + CVC5_CAPI_TRY_CATCH_END; + return res; +} + +/* Finite field sort --------------------------------------------------- */ + +const char* cvc5_sort_ff_get_size(Cvc5Sort sort) +{ + static thread_local std::string str; + CVC5_CAPI_TRY_CATCH_BEGIN; + CVC5_CAPI_CHECK_SORT(sort); + str = sort->d_sort.getFiniteFieldSize(); + CVC5_CAPI_TRY_CATCH_END; + return str.c_str(); +} + +/* Floating-point sort ------------------------------------------------- */ + +uint32_t cvc5_sort_fp_get_exp_size(Cvc5Sort sort) +{ + uint32_t res = 0; + CVC5_CAPI_TRY_CATCH_BEGIN; + CVC5_CAPI_CHECK_SORT(sort); + res = sort->d_sort.getFloatingPointExponentSize(); + CVC5_CAPI_TRY_CATCH_END; + return res; +} + +uint32_t cvc5_sort_fp_get_sig_size(Cvc5Sort sort) +{ + uint32_t res = 0; + CVC5_CAPI_TRY_CATCH_BEGIN; + CVC5_CAPI_CHECK_SORT(sort); + res = sort->d_sort.getFloatingPointSignificandSize(); + CVC5_CAPI_TRY_CATCH_END; + return res; +} + +/* Datatype sort ------------------------------------------------------- */ + +size_t cvc5_sort_dt_get_arity(Cvc5Sort sort) +{ + size_t res = 0; + CVC5_CAPI_TRY_CATCH_BEGIN; + CVC5_CAPI_CHECK_SORT(sort); + res = sort->d_sort.getDatatypeArity(); + CVC5_CAPI_TRY_CATCH_END; + return res; +} + +/* Tuple sort ---------------------------------------------------------- */ + +size_t cvc5_sort_tuple_get_length(Cvc5Sort sort) +{ + size_t res = 0; + CVC5_CAPI_TRY_CATCH_BEGIN; + CVC5_CAPI_CHECK_SORT(sort); + res = sort->d_sort.getTupleLength(); + CVC5_CAPI_TRY_CATCH_END; + return res; +} + +const Cvc5Sort* cvc5_sort_tuple_get_element_sorts(Cvc5Sort sort, size_t* size) +{ + static thread_local std::vector res; + CVC5_CAPI_TRY_CATCH_BEGIN; + CVC5_CAPI_CHECK_SORT(sort); + CVC5_CAPI_CHECK_NOT_NULL(size); + res.clear(); + auto sorts = sort->d_sort.getTupleSorts(); + auto tm = sort->d_tm; + for (auto& s : sorts) + { + res.push_back(tm->export_sort(s)); + } + *size = res.size(); + CVC5_CAPI_TRY_CATCH_END; + return *size > 0 ? res.data() : nullptr; +} + +Cvc5Sort cvc5_sort_nullable_get_element_sort(Cvc5Sort sort) +{ + Cvc5Sort res = nullptr; + CVC5_CAPI_TRY_CATCH_BEGIN; + CVC5_CAPI_CHECK_SORT(sort); + res = sort->d_tm->export_sort(sort->d_sort.getNullableElementSort()); CVC5_CAPI_TRY_CATCH_END; return res; } @@ -836,7 +1646,7 @@ Cvc5Term cvc5_dt_sel_get_updater_term(Cvc5DatatypeSelector sel) Cvc5Term res = nullptr; CVC5_CAPI_TRY_CATCH_BEGIN; CVC5_CAPI_CHECK_DT_SEL(sel); - res = sel->d_tm->export_term(sel->d_dt_sel.getTerm()); + res = sel->d_tm->export_term(sel->d_dt_sel.getUpdaterTerm()); CVC5_CAPI_TRY_CATCH_END; return res; } @@ -1010,6 +1820,7 @@ const Cvc5Sort* cvc5_dt_get_parameters(Cvc5Datatype dt, size_t* size) CVC5_CAPI_TRY_CATCH_BEGIN; CVC5_CAPI_CHECK_DT(dt); CVC5_CAPI_CHECK_NOT_NULL(size); + res.clear(); auto sorts = dt->d_dt.getParameters(); auto tm = dt->d_tm; for (auto& s : sorts) @@ -1091,6 +1902,20 @@ const char* cvc5_dt_to_string(Cvc5Datatype dt) return str.c_str(); } +/* -------------------------------------------------------------------------- */ +/* Cvc5Term */ +/* -------------------------------------------------------------------------- */ + +Cvc5Sort cvc5_term_get_sort(Cvc5Term term) +{ + Cvc5Sort res = nullptr; + CVC5_CAPI_TRY_CATCH_BEGIN; + CVC5_CAPI_CHECK_TERM(term); + res = term->d_tm->export_sort(term->d_term.getSort()); + CVC5_CAPI_TRY_CATCH_END; + return res; +} + /* -------------------------------------------------------------------------- */ /* Cvc5TermManager */ /* -------------------------------------------------------------------------- */ @@ -1241,6 +2066,7 @@ const Cvc5Sort* cvc5_mk_dt_sorts(Cvc5TermManager* tm, CVC5_CAPI_TRY_CATCH_BEGIN; CVC5_CAPI_CHECK_NOT_NULL(tm); CVC5_CAPI_CHECK_NOT_NULL(decls); + res.clear(); std::vector cdecls; for (size_t i = 0; i < size; ++i) { @@ -1383,7 +2209,6 @@ Cvc5Sort cvc5_mk_uninterpreted_sort(Cvc5TermManager* tm, const char* symbol) Cvc5Sort res = nullptr; CVC5_CAPI_TRY_CATCH_BEGIN; CVC5_CAPI_CHECK_NOT_NULL(tm); - CVC5_CAPI_CHECK_NOT_NULL(symbol); if (symbol) { res = tm->export_sort(tm->d_tm.mkUninterpretedSort(symbol)); diff --git a/src/api/c/cvc5_checks.h b/src/api/c/cvc5_checks.h index 938e2831d69..508296b69ce 100644 --- a/src/api/c/cvc5_checks.h +++ b/src/api/c/cvc5_checks.h @@ -129,6 +129,8 @@ class Cvc5CApiAbortStream #define CVC5_CAPI_CHECK_SORT_AT_IDX(sorts, i) \ CVC5_API_CHECK(sorts[i] != nullptr) << "invalid sort at index " << i +/* -------------------------------------------------------------------------- */ + #define CVC5_CAPI_CHECK_DT_DECL(decl) \ CVC5_API_CHECK(decl != nullptr) << "invalid datatype declaration" @@ -149,5 +151,10 @@ class Cvc5CApiAbortStream #define CVC5_CAPI_CHECK_DT(dt) \ CVC5_API_CHECK(dt != nullptr) << "invalid datatype" +/* -------------------------------------------------------------------------- */ + +#define CVC5_CAPI_CHECK_TERM(term) \ + CVC5_API_CHECK(term != nullptr) << "invalid term" + /* -------------------------------------------------------------------------- */ } diff --git a/src/api/cpp/cvc5.cpp b/src/api/cpp/cvc5.cpp index 8cc85d3182c..0a30b5679a8 100644 --- a/src/api/cpp/cvc5.cpp +++ b/src/api/cpp/cvc5.cpp @@ -1624,7 +1624,7 @@ Sort Sort::getUninterpretedSortConstructor() const CVC5_API_TRY_CATCH_BEGIN; CVC5_API_CHECK_NOT_NULL; CVC5_API_CHECK(d_type->isInstantiatedUninterpretedSort()) - << "Expected instantiated uninterpreted sort."; + << "expected instantiated uninterpreted sort."; //////// all checks before this line return Sort(d_tm, d_type->getUninterpretedSortConstructor()); //////// @@ -1635,7 +1635,7 @@ Datatype Sort::getDatatype() const { CVC5_API_TRY_CATCH_BEGIN; CVC5_API_CHECK_NOT_NULL; - CVC5_API_CHECK(d_type->isDatatype()) << "Expected datatype sort."; + CVC5_API_CHECK(d_type->isDatatype()) << "expected datatype sort."; //////// all checks before this line return Datatype(d_tm, d_type->getDType()); //////// @@ -1659,7 +1659,7 @@ Sort Sort::instantiate(const std::vector& params) const CVC5_API_CHECK_DOMAIN_SORTS(params); CVC5_API_CHECK(d_type->isParametricDatatype() || d_type->isUninterpretedSortConstructor()) - << "Expected parametric datatype or sort constructor sort."; + << "expected parametric datatype or sort constructor sort."; CVC5_API_CHECK(!d_type->isParametricDatatype() || d_type->getNumChildren() == params.size() + 1) << "Arity mismatch for instantiated parametric datatype"; @@ -1679,7 +1679,7 @@ std::vector Sort::getInstantiatedParameters() const CVC5_API_TRY_CATCH_BEGIN; CVC5_API_CHECK_NOT_NULL; CVC5_API_CHECK(d_type->isInstantiated()) - << "Expected instantiated parametric sort"; + << "expected instantiated parametric sort"; //////// all checks before this line return typeNodeVectorToSorts(d_tm, d_type->getInstantiatedParamTypes()); //////// @@ -1738,7 +1738,7 @@ size_t Sort::getDatatypeConstructorArity() const CVC5_API_TRY_CATCH_BEGIN; CVC5_API_CHECK_NOT_NULL; CVC5_API_CHECK(d_type->isDatatypeConstructor()) - << "Not a constructor sort: " << (*this); + << "not a constructor sort: " << (*this); //////// all checks before this line return d_type->getNumChildren() - 1; //////// @@ -1750,7 +1750,7 @@ std::vector Sort::getDatatypeConstructorDomainSorts() const CVC5_API_TRY_CATCH_BEGIN; CVC5_API_CHECK_NOT_NULL; CVC5_API_CHECK(d_type->isDatatypeConstructor()) - << "Not a constructor sort: " << (*this); + << "not a constructor sort: " << (*this); //////// all checks before this line return typeNodeVectorToSorts(d_tm, d_type->getArgTypes()); //////// @@ -1762,7 +1762,7 @@ Sort Sort::getDatatypeConstructorCodomainSort() const CVC5_API_TRY_CATCH_BEGIN; CVC5_API_CHECK_NOT_NULL; CVC5_API_CHECK(d_type->isDatatypeConstructor()) - << "Not a constructor sort: " << (*this); + << "not a constructor sort: " << (*this); //////// all checks before this line return Sort(d_tm, d_type->getDatatypeConstructorRangeType()); //////// @@ -1776,7 +1776,7 @@ Sort Sort::getDatatypeSelectorDomainSort() const CVC5_API_TRY_CATCH_BEGIN; CVC5_API_CHECK_NOT_NULL; CVC5_API_CHECK(d_type->isDatatypeSelector()) - << "Not a selector sort: " << (*this); + << "not a selector sort: " << (*this); //////// all checks before this line return Sort(d_tm, d_type->getDatatypeSelectorDomainType()); //////// @@ -1788,7 +1788,7 @@ Sort Sort::getDatatypeSelectorCodomainSort() const CVC5_API_TRY_CATCH_BEGIN; CVC5_API_CHECK_NOT_NULL; CVC5_API_CHECK(d_type->isDatatypeSelector()) - << "Not a selector sort: " << (*this); + << "not a selector sort: " << (*this); //////// all checks before this line return Sort(d_tm, d_type->getDatatypeSelectorRangeType()); //////// @@ -1802,7 +1802,7 @@ Sort Sort::getDatatypeTesterDomainSort() const CVC5_API_TRY_CATCH_BEGIN; CVC5_API_CHECK_NOT_NULL; CVC5_API_CHECK(d_type->isDatatypeTester()) - << "Not a tester sort: " << (*this); + << "not a tester sort: " << (*this); //////// all checks before this line return Sort(d_tm, d_type->getDatatypeTesterDomainType()); //////// @@ -1814,7 +1814,7 @@ Sort Sort::getDatatypeTesterCodomainSort() const CVC5_API_TRY_CATCH_BEGIN; CVC5_API_CHECK_NOT_NULL; CVC5_API_CHECK(d_type->isDatatypeTester()) - << "Not a tester sort: " << (*this); + << "not a tester sort: " << (*this); //////// all checks before this line return Sort(d_tm, d_tm->d_nm->booleanType()); //////// @@ -1827,7 +1827,7 @@ size_t Sort::getFunctionArity() const { CVC5_API_TRY_CATCH_BEGIN; CVC5_API_CHECK_NOT_NULL; - CVC5_API_CHECK(isFunction()) << "Not a function sort: " << (*this); + CVC5_API_CHECK(isFunction()) << "not a function sort: " << (*this); //////// all checks before this line return d_type->getNumChildren() - 1; //////// @@ -1838,7 +1838,7 @@ std::vector Sort::getFunctionDomainSorts() const { CVC5_API_TRY_CATCH_BEGIN; CVC5_API_CHECK_NOT_NULL; - CVC5_API_CHECK(isFunction()) << "Not a function sort: " << (*this); + CVC5_API_CHECK(isFunction()) << "not a function sort: " << (*this); //////// all checks before this line return typeNodeVectorToSorts(d_tm, d_type->getArgTypes()); //////// @@ -1849,7 +1849,7 @@ Sort Sort::getFunctionCodomainSort() const { CVC5_API_TRY_CATCH_BEGIN; CVC5_API_CHECK_NOT_NULL; - CVC5_API_CHECK(isFunction()) << "Not a function sort" << (*this); + CVC5_API_CHECK(isFunction()) << "not a function sort" << (*this); //////// all checks before this line return Sort(d_tm, d_type->getRangeType()); //////// @@ -1862,7 +1862,7 @@ Sort Sort::getArrayIndexSort() const { CVC5_API_TRY_CATCH_BEGIN; CVC5_API_CHECK_NOT_NULL; - CVC5_API_CHECK(isArray()) << "Not an array sort."; + CVC5_API_CHECK(isArray()) << "not an array sort."; //////// all checks before this line return Sort(d_tm, d_type->getArrayIndexType()); //////// @@ -1873,7 +1873,7 @@ Sort Sort::getArrayElementSort() const { CVC5_API_TRY_CATCH_BEGIN; CVC5_API_CHECK_NOT_NULL; - CVC5_API_CHECK(isArray()) << "Not an array sort."; + CVC5_API_CHECK(isArray()) << "not an array sort."; //////// all checks before this line return Sort(d_tm, d_type->getArrayConstituentType()); //////// @@ -1886,7 +1886,7 @@ Sort Sort::getSetElementSort() const { CVC5_API_TRY_CATCH_BEGIN; CVC5_API_CHECK_NOT_NULL; - CVC5_API_CHECK(isSet()) << "Not a set sort."; + CVC5_API_CHECK(isSet()) << "not a set sort."; //////// all checks before this line return Sort(d_tm, d_type->getSetElementType()); //////// @@ -1899,7 +1899,7 @@ Sort Sort::getBagElementSort() const { CVC5_API_TRY_CATCH_BEGIN; CVC5_API_CHECK_NOT_NULL; - CVC5_API_CHECK(isBag()) << "Not a bag sort."; + CVC5_API_CHECK(isBag()) << "not a bag sort."; //////// all checks before this line return Sort(d_tm, d_type->getBagElementType()); //////// @@ -1912,7 +1912,7 @@ Sort Sort::getSequenceElementSort() const { CVC5_API_TRY_CATCH_BEGIN; CVC5_API_CHECK_NOT_NULL; - CVC5_API_CHECK(isSequence()) << "Not a sequence sort."; + CVC5_API_CHECK(isSequence()) << "not a sequence sort."; //////// all checks before this line return Sort(d_tm, d_type->getSequenceElementType()); //////// @@ -1925,7 +1925,7 @@ SortKind Sort::getAbstractedKind() const { CVC5_API_TRY_CATCH_BEGIN; CVC5_API_CHECK_NOT_NULL; - CVC5_API_CHECK(isAbstract()) << "Not an abstract sort."; + CVC5_API_CHECK(isAbstract()) << "not an abstract sort."; //////// all checks before this line return intToExtSortKind(d_type->getAbstractedKind()); //////// @@ -1939,7 +1939,7 @@ size_t Sort::getUninterpretedSortConstructorArity() const CVC5_API_TRY_CATCH_BEGIN; CVC5_API_CHECK_NOT_NULL; CVC5_API_CHECK(d_type->isUninterpretedSortConstructor()) - << "Not a sort constructor sort."; + << "not a sort constructor sort."; //////// all checks before this line return d_type->getUninterpretedSortConstructorArity(); //////// @@ -1952,7 +1952,7 @@ uint32_t Sort::getBitVectorSize() const { CVC5_API_TRY_CATCH_BEGIN; CVC5_API_CHECK_NOT_NULL; - CVC5_API_CHECK(isBitVector()) << "Not a bit-vector sort."; + CVC5_API_CHECK(isBitVector()) << "not a bit-vector sort."; //////// all checks before this line return d_type->getBitVectorSize(); //////// @@ -1965,7 +1965,7 @@ std::string Sort::getFiniteFieldSize() const { CVC5_API_TRY_CATCH_BEGIN; CVC5_API_CHECK_NOT_NULL; - CVC5_API_CHECK(isFiniteField()) << "Not a finite field sort."; + CVC5_API_CHECK(isFiniteField()) << "not a finite field sort."; //////// all checks before this line return d_type->getFfSize().toString(); //////// @@ -1978,7 +1978,7 @@ uint32_t Sort::getFloatingPointExponentSize() const { CVC5_API_TRY_CATCH_BEGIN; CVC5_API_CHECK_NOT_NULL; - CVC5_API_CHECK(d_type->isFloatingPoint()) << "Not a floating-point sort."; + CVC5_API_CHECK(d_type->isFloatingPoint()) << "not a floating-point sort."; //////// all checks before this line return d_type->getFloatingPointExponentSize(); //////// @@ -1989,7 +1989,7 @@ uint32_t Sort::getFloatingPointSignificandSize() const { CVC5_API_TRY_CATCH_BEGIN; CVC5_API_CHECK_NOT_NULL; - CVC5_API_CHECK(d_type->isFloatingPoint()) << "Not a floating-point sort."; + CVC5_API_CHECK(d_type->isFloatingPoint()) << "not a floating-point sort."; //////// all checks before this line return d_type->getFloatingPointSignificandSize(); //////// @@ -2002,7 +2002,7 @@ size_t Sort::getDatatypeArity() const { CVC5_API_TRY_CATCH_BEGIN; CVC5_API_CHECK_NOT_NULL; - CVC5_API_CHECK(d_type->isDatatype()) << "Not a datatype sort."; + CVC5_API_CHECK(d_type->isDatatype()) << "not a datatype sort."; //////// all checks before this line return d_type->isParametricDatatype() ? d_type->getNumChildren() - 1 : 0; //////// @@ -2015,7 +2015,7 @@ size_t Sort::getTupleLength() const { CVC5_API_TRY_CATCH_BEGIN; CVC5_API_CHECK_NOT_NULL; - CVC5_API_CHECK(isTuple()) << "Not a tuple sort."; + CVC5_API_CHECK(isTuple()) << "not a tuple sort."; //////// all checks before this line return d_type->getTupleLength(); //////// @@ -2026,7 +2026,7 @@ std::vector Sort::getTupleSorts() const { CVC5_API_TRY_CATCH_BEGIN; CVC5_API_CHECK_NOT_NULL; - CVC5_API_CHECK(d_type->isTuple()) << "Not a tuple sort."; + CVC5_API_CHECK(d_type->isTuple()) << "not a tuple sort."; //////// all checks before this line return typeNodeVectorToSorts(d_tm, d_type->getTupleTypes()); //////// @@ -2037,7 +2037,7 @@ Sort Sort::getNullableElementSort() const { CVC5_API_TRY_CATCH_BEGIN; CVC5_API_CHECK_NOT_NULL; - CVC5_API_CHECK(isNullable()) << "Not a nullable sort."; + CVC5_API_CHECK(isNullable()) << "not a nullable sort."; //////// all checks before this line return Sort(d_tm, d_type->getNullableElementType()); //////// @@ -2113,7 +2113,7 @@ bool Op::operator!=(const Op& t) const Kind Op::getKind() const { - CVC5_API_CHECK(d_kind != Kind::NULL_TERM) << "Expecting a non-null Kind"; + CVC5_API_CHECK(d_kind != Kind::NULL_TERM) << "expecting a non-null Kind"; //////// all checks before this line return d_kind; } @@ -2200,7 +2200,7 @@ Term Op::getIndexHelper(size_t index) CVC5_API_TRY_CATCH_BEGIN; CVC5_API_CHECK_NOT_NULL; CVC5_API_CHECK(!d_node->isNull()) - << "Expecting a non-null internal expression. This Op is not indexed."; + << "expecting a non-null internal expression. This Op is not indexed."; CVC5_API_CHECK(index < getNumIndicesHelper()) << "index out of bound"; Kind k = intToExtKind(d_node->getKind()); Term t; @@ -2386,7 +2386,7 @@ std::string Op::toString() const else { CVC5_API_CHECK(!d_node->isNull()) - << "Expecting a non-null internal expression"; + << "expecting a non-null internal expression"; Assert(isNull() || d_tm != nullptr); return d_node->toString(); } @@ -2506,7 +2506,7 @@ Term Term::operator[](size_t index) const CVC5_API_CHECK_NOT_NULL; CVC5_API_CHECK(index < getNumChildren()) << "index out of bound"; CVC5_API_CHECK(!isApplyKind(d_node->getKind()) || d_node->hasOperator()) - << "Expected apply kind to have operator when accessing child of Term"; + << "expected apply kind to have operator when accessing child of Term"; //////// all checks before this line // special cases for apply kinds @@ -2565,7 +2565,7 @@ Term Term::substitute(const Term& term, const Term& replacement) const CVC5_API_CHECK_TERM(term); CVC5_API_CHECK_TERM(replacement); CVC5_API_CHECK(term.getSort() == replacement.getSort()) - << "Expecting terms of the same sort in substitute"; + << "expecting terms of the same sort in substitute"; //////// all checks before this line return Term(d_tm, d_node->substitute(internal::TNode(*term.d_node), @@ -2580,7 +2580,7 @@ Term Term::substitute(const std::vector& terms, CVC5_API_TRY_CATCH_BEGIN; CVC5_API_CHECK_NOT_NULL; CVC5_API_CHECK(terms.size() == replacements.size()) - << "Expecting vectors of the same arity in substitute"; + << "expecting vectors of the same arity in substitute"; CVC5_API_TERM_CHECK_TERMS_WITH_TERMS_SORT_EQUAL_TO(terms, replacements); //////// all checks before this line std::vector nodes = Term::termVectorToNodes(terms); @@ -2610,7 +2610,7 @@ Op Term::getOp() const CVC5_API_TRY_CATCH_BEGIN; CVC5_API_CHECK_NOT_NULL; CVC5_API_CHECK(d_node->hasOperator()) - << "Expecting Term to have an Op when calling getOp()"; + << "expecting Term to have an Op when calling getOp()"; //////// all checks before this line // special cases for parameterized operators that are not indexed operators @@ -3577,11 +3577,11 @@ Term Term::getRealAlgebraicNumberDefiningPolynomial(const Term& v) const << "Term to be a real algebraic number when calling " "getRealAlgebraicNumberDefiningPolynomial()"; CVC5_API_ARG_CHECK_EXPECTED(v.getKind() == Kind::VARIABLE, v) - << "Expected a variable as argument when calling " + << "expected a variable as argument when calling " "getRealAlgebraicNumberDefiningPolynomial()"; #ifndef CVC5_POLY_IMP throw CVC5ApiException( - "Expected libpoly enabled build when calling " + "expected libpoly enabled build when calling " "getRealAlgebraicNumberDefiningPolynomial"); #endif //////// all checks before this line @@ -3608,7 +3608,7 @@ Term Term::getRealAlgebraicNumberLowerBound() const "getRealAlgebraicNumberDefiningPolynomial()"; #ifndef CVC5_POLY_IMP throw CVC5ApiException( - "Expected libpoly enabled build when calling " + "expected libpoly enabled build when calling " "getRealAlgebraicNumberLowerBound"); #endif //////// all checks before this line @@ -3633,7 +3633,7 @@ Term Term::getRealAlgebraicNumberUpperBound() const "getRealAlgebraicNumberDefiningPolynomial()"; #ifndef CVC5_POLY_IMP throw CVC5ApiException( - "Expected libpoly enabled build when calling " + "expected libpoly enabled build when calling " "getRealAlgebraicNumberUpperBound"); #endif //////// all checks before this line @@ -4048,7 +4048,7 @@ DatatypeSelector::DatatypeSelector(TermManager* tm, const internal::DTypeSelector& stor) : d_tm(tm), d_stor(new internal::DTypeSelector(stor)) { - CVC5_API_CHECK(d_stor->isResolved()) << "Expected resolved datatype selector"; + CVC5_API_CHECK(d_stor->isResolved()) << "expected resolved datatype selector"; } DatatypeSelector::~DatatypeSelector() @@ -4146,7 +4146,7 @@ DatatypeConstructor::DatatypeConstructor(TermManager* tm, : d_tm(tm), d_ctor(new internal::DTypeConstructor(ctor)) { CVC5_API_CHECK(d_ctor->isResolved()) - << "Expected resolved datatype constructor"; + << "expected resolved datatype constructor"; } DatatypeConstructor::~DatatypeConstructor() @@ -4192,7 +4192,7 @@ Term DatatypeConstructor::getInstantiatedTerm(const Sort& retSort) const CVC5_API_TRY_CATCH_BEGIN; CVC5_API_CHECK_NOT_NULL; CVC5_API_CHECK(d_ctor->isResolved()) - << "Expected resolved datatype constructor"; + << "expected resolved datatype constructor"; CVC5_API_CHECK(retSort.isDatatype()) << "Cannot get specialized constructor type for non-datatype type " << retSort; @@ -4230,6 +4230,7 @@ DatatypeSelector DatatypeConstructor::operator[](size_t index) const { CVC5_API_TRY_CATCH_BEGIN; CVC5_API_CHECK_NOT_NULL; + CVC5_API_CHECK(index < d_ctor->getNumArgs()) << "index out of bounds"; //////// all checks before this line return DatatypeSelector(d_tm, (*d_ctor)[index]); //////// @@ -4397,7 +4398,7 @@ std::ostream& operator<<(std::ostream& out, const DatatypeConstructor& ctor) Datatype::Datatype(TermManager* tm, const internal::DType& dtype) : d_tm(tm), d_dtype(new internal::DType(dtype)) { - CVC5_API_CHECK(d_dtype->isResolved()) << "Expected resolved datatype"; + CVC5_API_CHECK(d_dtype->isResolved()) << "expected resolved datatype"; } Datatype::Datatype() : d_tm(nullptr), d_dtype(nullptr) {} @@ -4424,7 +4425,7 @@ DatatypeConstructor Datatype::operator[](size_t idx) const { CVC5_API_TRY_CATCH_BEGIN; CVC5_API_CHECK_NOT_NULL; - CVC5_API_CHECK(idx < getNumConstructors()) << "Index out of bounds."; + CVC5_API_CHECK(idx < getNumConstructors()) << "index out of bounds."; //////// all checks before this line return DatatypeConstructor(d_tm, (*d_dtype)[idx]); //////// @@ -4485,7 +4486,7 @@ std::vector Datatype::getParameters() const { CVC5_API_TRY_CATCH_BEGIN; CVC5_API_CHECK_NOT_NULL; - CVC5_API_CHECK(isParametric()) << "Expected parametric datatype"; + CVC5_API_CHECK(isParametric()) << "expected parametric datatype"; //////// all checks before this line std::vector params = d_dtype->getParameters(); return Sort::typeNodeVectorToSorts(d_tm, params); @@ -4746,8 +4747,9 @@ void Grammar::addRule(const Term& ntSymbol, const Term& rule) ntSymbol) << "ntSymbol to be one of the non-terminal symbols given in the " "predeclaration"; - CVC5_API_CHECK(ntSymbol.d_node->getType().isInstanceOf(rule.d_node->getType())) - << "Expected ntSymbol and rule to have the same sort"; + CVC5_API_CHECK( + ntSymbol.d_node->getType().isInstanceOf(rule.d_node->getType())) + << "expected ntSymbol and rule to have the same sort"; //////// all checks before this line d_sg->addRule(*ntSymbol.d_node, *rule.d_node); //////// @@ -4896,7 +4898,7 @@ int64_t Stat::getInt() const { CVC5_API_TRY_CATCH_BEGIN; CVC5_API_RECOVERABLE_CHECK(static_cast(d_data)) << "Stat holds no value"; - CVC5_API_RECOVERABLE_CHECK(isInt()) << "Expected Stat of type int64_t."; + CVC5_API_RECOVERABLE_CHECK(isInt()) << "expected Stat of type int64_t."; return std::get(d_data->data); CVC5_API_TRY_CATCH_END; } @@ -4909,7 +4911,7 @@ double Stat::getDouble() const { CVC5_API_TRY_CATCH_BEGIN; CVC5_API_RECOVERABLE_CHECK(static_cast(d_data)) << "Stat holds no value"; - CVC5_API_RECOVERABLE_CHECK(isDouble()) << "Expected Stat of type double."; + CVC5_API_RECOVERABLE_CHECK(isDouble()) << "expected Stat of type double."; return std::get(d_data->data); CVC5_API_TRY_CATCH_END; } @@ -4923,7 +4925,7 @@ const std::string& Stat::getString() const CVC5_API_TRY_CATCH_BEGIN; CVC5_API_RECOVERABLE_CHECK(static_cast(d_data)) << "Stat holds no value"; CVC5_API_RECOVERABLE_CHECK(isString()) - << "Expected Stat of type std::string."; + << "expected Stat of type std::string."; return std::get(d_data->data); CVC5_API_TRY_CATCH_END; } @@ -4937,7 +4939,7 @@ const Stat::HistogramData& Stat::getHistogram() const CVC5_API_TRY_CATCH_BEGIN; CVC5_API_RECOVERABLE_CHECK(static_cast(d_data)) << "Stat holds no value"; CVC5_API_RECOVERABLE_CHECK(isHistogram()) - << "Expected Stat of type histogram."; + << "expected Stat of type histogram."; return std::get(d_data->data); CVC5_API_TRY_CATCH_END; } @@ -5094,7 +5096,7 @@ ProofRewriteRule Proof::getRewriteRule() const CVC5_API_CHECK(this->getProofNode()->getRule() == ProofRule::DSL_REWRITE || this->getProofNode()->getRule() == ProofRule::THEORY_REWRITE) - << "Expected `getRule()` to return `DSL_REWRITE` or `THEORY_REWRITE`, " + << "expected `getRule()` to return `DSL_REWRITE` or `THEORY_REWRITE`, " "got " << this->getProofNode()->getRule() << " instead."; //////// all checks before this line @@ -5997,7 +5999,7 @@ Op TermManager::mkOp(Kind kind, const std::vector& args) if (nargs == 0) { CVC5_API_CHECK(s_indexed_kinds.find(kind) == s_indexed_kinds.end()) - << "Expected a kind for a non-indexed operator."; + << "expected a kind for a non-indexed operator."; return Op(this, kind); } else @@ -6694,7 +6696,7 @@ Term Solver::synthFunHelper(const std::string& symbol, if (grammar) { CVC5_API_CHECK(grammar->d_sg->getNtSyms()[0].getType() == *sort.d_type) - << "Invalid Start symbol for grammar, Expected Start's sort to be " + << "Invalid Start symbol for grammar, expected Start's sort to be " << *sort.d_type << " but found " << grammar->d_sg->getNtSyms()[0].getType(); } @@ -7976,7 +7978,7 @@ std::vector Solver::getModelDomainElements(const Sort& s) const << "Cannot get domain elements unless after a SAT or UNKNOWN response."; CVC5_API_SOLVER_CHECK_SORT(s); CVC5_API_RECOVERABLE_CHECK(s.isUninterpretedSort()) - << "Expecting an uninterpreted sort as argument to " + << "expecting an uninterpreted sort as argument to " "getModelDomainElements."; //////// all checks before this line std::vector res; @@ -8003,7 +8005,7 @@ bool Solver::isModelCoreSymbol(const Term& v) const "response."; CVC5_API_SOLVER_CHECK_TERM(v); CVC5_API_RECOVERABLE_CHECK(v.getKind() == Kind::CONSTANT) - << "Expecting a free constant as argument to isModelCoreSymbol."; + << "expecting a free constant as argument to isModelCoreSymbol."; //////// all checks before this line return d_slv->isModelCoreSymbol(v.getNode()); //////// @@ -8024,14 +8026,14 @@ std::string Solver::getModel(const std::vector& sorts, for (const Sort& s : sorts) { CVC5_API_RECOVERABLE_CHECK(s.isUninterpretedSort()) - << "Expecting an uninterpreted sort as argument to " + << "expecting an uninterpreted sort as argument to " "getModel."; } CVC5_API_SOLVER_CHECK_TERMS(vars); for (const Term& v : vars) { CVC5_API_RECOVERABLE_CHECK(v.getKind() == Kind::CONSTANT) - << "Expecting a free constant as argument to getModel."; + << "expecting a free constant as argument to getModel."; } //////// all checks before this line return d_slv->getModel(Sort::sortVectorToTypeNodes(sorts), @@ -8609,10 +8611,10 @@ void Solver::addSygusInvConstraint(const Term& inv, << "boolean range"; CVC5_API_CHECK(pre.d_node->getType() == invType) - << "Expected inv and pre to have the same sort"; + << "expected inv and pre to have the same sort"; CVC5_API_CHECK(post.d_node->getType() == invType) - << "Expected inv and post to have the same sort"; + << "expected inv and post to have the same sort"; CVC5_API_CHECK(d_slv->getOptions().quantifiers.sygus) << "Cannot addSygusInvConstraint unless sygus is enabled (use --" << internal::options::quantifiers::longName::sygus << ")"; @@ -8634,7 +8636,7 @@ void Solver::addSygusInvConstraint(const Term& inv, d_tm.d_nm->mkFunctionType(expectedTypes); CVC5_API_CHECK(trans.d_node->getType() == expectedTransType) - << "Expected trans's sort to be " << invType; + << "expected trans's sort to be " << invType; d_slv->assertSygusInvConstraint( *inv.d_node, *pre.d_node, *trans.d_node, *post.d_node); diff --git a/test/unit/api/c/CMakeLists.txt b/test/unit/api/c/CMakeLists.txt index 03ba668f744..fa4207c5757 100644 --- a/test/unit/api/c/CMakeLists.txt +++ b/test/unit/api/c/CMakeLists.txt @@ -15,6 +15,7 @@ # Generate and add unit test. cvc5_add_unit_test_black(capi_kind_black api/c) +cvc5_add_unit_test_black(capi_sort_black api/c) cvc5_add_unit_test_black(capi_sort_kind_black api/c) cvc5_add_unit_test_black(capi_term_manager_black api/c) cvc5_add_unit_test_black(capi_types_black api/c) diff --git a/test/unit/api/c/capi_sort_black.cpp b/test/unit/api/c/capi_sort_black.cpp new file mode 100644 index 00000000000..f4c1a3157d1 --- /dev/null +++ b/test/unit/api/c/capi_sort_black.cpp @@ -0,0 +1,718 @@ +/****************************************************************************** + * Top contributors (to current version): + * Aina Niemetz + * + * This file is part of the cvc5 project. + * + * Copyright (c) 2009-2024 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. + * **************************************************************************** + * + * Black box testing of the guards of the C API functions. + */ + +extern "C" { +#include +} + +#include "base/output.h" +#include "gtest/gtest.h" + +namespace cvc5::internal::test { + +class TestCApiBlackSort : public ::testing::Test +{ + protected: + void SetUp() override + { + d_tm = cvc5_term_manager_new(); + d_bool = cvc5_get_boolean_sort(d_tm); + d_int = cvc5_get_integer_sort(d_tm); + d_real = cvc5_get_real_sort(d_tm); + } + void TearDown() override { cvc5_term_manager_delete(d_tm); } + + Cvc5Sort create_datatype_sort() + { + Cvc5DatatypeDecl decl = cvc5_mk_dt_decl(d_tm, "list", false); + Cvc5DatatypeConstructorDecl cons = cvc5_mk_dt_cons_decl(d_tm, "cons"); + cvc5_dt_cons_decl_add_selector(cons, "head", cvc5_get_integer_sort(d_tm)); + cvc5_dt_cons_decl_add_selector_self(cons, "tail"); + cvc5_dt_decl_add_constructor(decl, cons); + Cvc5DatatypeConstructorDecl nil = cvc5_mk_dt_cons_decl(d_tm, "nil"); + cvc5_dt_decl_add_constructor(decl, nil); + return cvc5_mk_dt_sort(d_tm, decl); + } + + Cvc5Sort create_param_datatype_sort() + { + std::vector sorts = {cvc5_mk_param_sort(d_tm, "T")}; + Cvc5DatatypeDecl decl = + cvc5_mk_dt_decl_with_params(d_tm, "paramlist", 1, sorts.data(), false); + Cvc5DatatypeConstructorDecl cons = cvc5_mk_dt_cons_decl(d_tm, "cons"); + cvc5_dt_cons_decl_add_selector(cons, "head", cvc5_get_integer_sort(d_tm)); + cvc5_dt_decl_add_constructor(decl, cons); + Cvc5DatatypeConstructorDecl nil = cvc5_mk_dt_cons_decl(d_tm, "nil"); + cvc5_dt_decl_add_constructor(decl, nil); + return cvc5_mk_dt_sort(d_tm, decl); + } + + Cvc5TermManager* d_tm; + Cvc5Sort d_bool; + Cvc5Sort d_int; + Cvc5Sort d_real; +}; + +TEST_F(TestCApiBlackSort, hash) +{ + ASSERT_DEATH(cvc5_sort_hash(nullptr), "invalid sort"); + (void)cvc5_sort_hash(d_int); +} + +TEST_F(TestCApiBlackSort, compare) +{ + ASSERT_DEATH(cvc5_sort_compare(d_int, nullptr), "invalid sort"); + ASSERT_DEATH(cvc5_sort_compare(nullptr, d_int), "invalid sort"); + ASSERT_FALSE(cvc5_sort_is_equal(d_int, nullptr)); + ASSERT_TRUE(cvc5_sort_is_disequal(d_int, nullptr)); + ASSERT_EQ(cvc5_sort_compare(d_int, d_int), 0); +} + +TEST_F(TestCApiBlackSort, get_kind) +{ + ASSERT_DEATH(cvc5_sort_get_kind(nullptr), "invalid sort"); + ASSERT_EQ(cvc5_sort_get_kind(d_bool), CVC5_SORT_KIND_BOOLEAN_SORT); + Cvc5Sort dt_sort = create_datatype_sort(); + ASSERT_EQ(cvc5_sort_get_kind(dt_sort), CVC5_SORT_KIND_DATATYPE_SORT); + Cvc5Sort arr_sort = cvc5_mk_array_sort(d_tm, d_real, d_int); + ASSERT_EQ(cvc5_sort_get_kind(arr_sort), CVC5_SORT_KIND_ARRAY_SORT); + Cvc5Sort fp_sort = cvc5_mk_fp_sort(d_tm, 8, 24); + ASSERT_EQ(cvc5_sort_get_kind(fp_sort), CVC5_SORT_KIND_FLOATINGPOINT_SORT); + Cvc5Sort bv_sort = cvc5_mk_bv_sort(d_tm, 8); + ASSERT_EQ(cvc5_sort_get_kind(bv_sort), CVC5_SORT_KIND_BITVECTOR_SORT); + Cvc5Sort abs_sort = + cvc5_mk_abstract_sort(d_tm, CVC5_SORT_KIND_BITVECTOR_SORT); + ASSERT_EQ(cvc5_sort_get_kind(abs_sort), CVC5_SORT_KIND_ABSTRACT_SORT); +} + +TEST_F(TestCApiBlackSort, has_get_symbol) +{ + Cvc5Sort s0 = cvc5_mk_param_sort(d_tm, "s0"); + Cvc5Sort s1 = cvc5_mk_param_sort(d_tm, "|s1\\|"); + + ASSERT_FALSE(cvc5_sort_has_symbol(nullptr)); + ASSERT_FALSE(cvc5_sort_has_symbol(d_bool)); + ASSERT_TRUE(cvc5_sort_has_symbol(s0)); + ASSERT_TRUE(cvc5_sort_has_symbol(s1)); + + ASSERT_DEATH(cvc5_sort_get_symbol(nullptr), "invalid sort"); + ASSERT_DEATH(cvc5_sort_get_symbol(d_bool), "has no symbol"); + ASSERT_EQ(cvc5_sort_get_symbol(s0), std::string("s0")); + ASSERT_EQ(cvc5_sort_get_symbol(s1), std::string("|s1\\|")); +} + +TEST_F(TestCApiBlackSort, is_boolean) +{ + ASSERT_FALSE(cvc5_sort_is_boolean(nullptr)); + ASSERT_TRUE(cvc5_sort_is_boolean(d_bool)); +} + +TEST_F(TestCApiBlackSort, is_integer) +{ + ASSERT_FALSE(cvc5_sort_is_integer(nullptr)); + ASSERT_TRUE(cvc5_sort_is_integer(d_int)); + ASSERT_FALSE(cvc5_sort_is_integer(d_real)); +} + +TEST_F(TestCApiBlackSort, is_real) +{ + ASSERT_FALSE(cvc5_sort_is_real(nullptr)); + ASSERT_TRUE(cvc5_sort_is_real(d_real)); + ASSERT_FALSE(cvc5_sort_is_real(d_int)); +} + +TEST_F(TestCApiBlackSort, is_string) +{ + ASSERT_FALSE(cvc5_sort_is_string(nullptr)); + ASSERT_TRUE(cvc5_sort_is_string(cvc5_get_string_sort(d_tm))); + ASSERT_FALSE(cvc5_sort_is_string(d_int)); +} + +TEST_F(TestCApiBlackSort, is_regexp) +{ + ASSERT_FALSE(cvc5_sort_is_regexp(nullptr)); + ASSERT_TRUE(cvc5_sort_is_regexp(cvc5_get_regexp_sort(d_tm))); + ASSERT_FALSE(cvc5_sort_is_regexp(d_int)); +} + +TEST_F(TestCApiBlackSort, is_rm) +{ + ASSERT_FALSE(cvc5_sort_is_rm(nullptr)); + ASSERT_TRUE(cvc5_sort_is_rm(cvc5_get_rm_sort(d_tm))); + ASSERT_FALSE(cvc5_sort_is_rm(d_int)); +} + +TEST_F(TestCApiBlackSort, is_bv) +{ + ASSERT_FALSE(cvc5_sort_is_bv(nullptr)); + ASSERT_TRUE(cvc5_sort_is_bv(cvc5_mk_bv_sort(d_tm, 8))); + ASSERT_FALSE(cvc5_sort_is_bv(d_int)); +} + +TEST_F(TestCApiBlackSort, is_ff) +{ + ASSERT_FALSE(cvc5_sort_is_ff(nullptr)); + ASSERT_TRUE(cvc5_sort_is_ff(cvc5_mk_ff_sort(d_tm, "7", 10))); + ASSERT_FALSE(cvc5_sort_is_ff(d_int)); +} + +TEST_F(TestCApiBlackSort, is_fp) +{ + ASSERT_FALSE(cvc5_sort_is_fp(nullptr)); + ASSERT_TRUE(cvc5_sort_is_fp(cvc5_mk_fp_sort(d_tm, 8, 24))); + ASSERT_FALSE(cvc5_sort_is_fp(d_int)); +} + +TEST_F(TestCApiBlackSort, is_dt) +{ + Cvc5Sort dt_sort = create_datatype_sort(); + ASSERT_FALSE(cvc5_sort_is_dt(nullptr)); + ASSERT_TRUE(cvc5_sort_is_dt(dt_sort)); + ASSERT_FALSE(cvc5_sort_is_dt(d_int)); +} + +TEST_F(TestCApiBlackSort, is_dt_constructor) +{ + Cvc5Sort dt_sort = create_datatype_sort(); + Cvc5Datatype dt = cvc5_sort_get_datatype(dt_sort); + ASSERT_DEATH(cvc5_dt_get_constructor(dt, 3), "index out of bounds"); + Cvc5DatatypeConstructor cons = cvc5_dt_get_constructor(dt, 0); + Cvc5Sort cons_sort = cvc5_term_get_sort(cvc5_dt_cons_get_term(cons)); + ASSERT_FALSE(cvc5_sort_is_dt(nullptr)); + ASSERT_TRUE(cvc5_sort_is_dt_constructor(cons_sort)); + ASSERT_FALSE(cvc5_sort_is_dt_constructor(d_int)); +} + +TEST_F(TestCApiBlackSort, is_dt_selector) +{ + Cvc5Sort dt_sort = create_datatype_sort(); + Cvc5Datatype dt = cvc5_sort_get_datatype(dt_sort); + Cvc5DatatypeConstructor cons = cvc5_dt_get_constructor(dt, 0); + ASSERT_DEATH(cvc5_dt_cons_get_selector(cons, 2), "index out of bounds"); + Cvc5DatatypeSelector sel = cvc5_dt_cons_get_selector(cons, 1); + Cvc5Sort sel_sort = cvc5_term_get_sort(cvc5_dt_sel_get_term(sel)); + ASSERT_FALSE(cvc5_sort_is_dt_selector(nullptr)); + ASSERT_TRUE(cvc5_sort_is_dt_selector(sel_sort)); + ASSERT_FALSE(cvc5_sort_is_dt_selector(d_int)); +} + +TEST_F(TestCApiBlackSort, is_dt_tester) +{ + Cvc5Sort dt_sort = create_datatype_sort(); + Cvc5Datatype dt = cvc5_sort_get_datatype(dt_sort); + Cvc5DatatypeConstructor cons = cvc5_dt_get_constructor(dt, 0); + Cvc5Term tester = cvc5_dt_cons_get_tester_term(cons); + Cvc5Sort tester_sort = cvc5_term_get_sort(tester); + ASSERT_FALSE(cvc5_sort_is_dt_tester(nullptr)); + ASSERT_TRUE(cvc5_sort_is_dt_tester(tester_sort)); + ASSERT_FALSE(cvc5_sort_is_dt_tester(d_int)); +} + +TEST_F(TestCApiBlackSort, is_dt_updater) +{ + Cvc5Sort dt_sort = create_datatype_sort(); + Cvc5Datatype dt = cvc5_sort_get_datatype(dt_sort); + Cvc5DatatypeConstructor cons = cvc5_dt_get_constructor(dt, 0); + Cvc5DatatypeSelector sel = cvc5_dt_cons_get_selector(cons, 1); + Cvc5Term updater = cvc5_dt_sel_get_updater_term(sel); + Cvc5Sort updater_sort = cvc5_term_get_sort(updater); + ASSERT_FALSE(cvc5_sort_is_dt_updater(nullptr)); + ASSERT_TRUE(cvc5_sort_is_dt_updater(updater_sort)); + ASSERT_FALSE(cvc5_sort_is_dt_updater(d_int)); +} + +TEST_F(TestCApiBlackSort, is_fun) +{ + std::vector domain = {d_real}; + Cvc5Sort sort = cvc5_mk_fun_sort(d_tm, 1, domain.data(), d_int); + ASSERT_FALSE(cvc5_sort_is_fun(nullptr)); + ASSERT_TRUE(cvc5_sort_is_fun(sort)); + ASSERT_FALSE(cvc5_sort_is_fun(d_int)); +} + +TEST_F(TestCApiBlackSort, is_predicate) +{ + std::vector args = {d_real}; + Cvc5Sort sort = cvc5_mk_predicate_sort(d_tm, 1, args.data()); + ASSERT_FALSE(cvc5_sort_is_predicate(nullptr)); + ASSERT_TRUE(cvc5_sort_is_predicate(sort)); + ASSERT_FALSE(cvc5_sort_is_predicate(d_int)); +} + +TEST_F(TestCApiBlackSort, is_tuple) +{ + std::vector args = {d_real}; + Cvc5Sort sort = cvc5_mk_tuple_sort(d_tm, 1, args.data()); + ASSERT_FALSE(cvc5_sort_is_tuple(nullptr)); + ASSERT_TRUE(cvc5_sort_is_tuple(sort)); + ASSERT_FALSE(cvc5_sort_is_tuple(d_int)); +} + +TEST_F(TestCApiBlackSort, is_nullable) +{ + Cvc5Sort sort = cvc5_mk_nullable_sort(d_tm, d_real); + ASSERT_FALSE(cvc5_sort_is_nullable(nullptr)); + ASSERT_TRUE(cvc5_sort_is_nullable(sort)); + ASSERT_FALSE(cvc5_sort_is_nullable(d_int)); +} + +TEST_F(TestCApiBlackSort, is_record) +{ + std::vector names = {"asdf"}; + std::vector args = {d_real}; + Cvc5Sort sort = cvc5_mk_record_sort(d_tm, 1, names.data(), args.data()); + ASSERT_FALSE(cvc5_sort_is_record(nullptr)); + ASSERT_TRUE(cvc5_sort_is_record(sort)); + ASSERT_FALSE(cvc5_sort_is_record(d_int)); +} + +TEST_F(TestCApiBlackSort, is_array) +{ + Cvc5Sort sort = cvc5_mk_array_sort(d_tm, d_real, d_int); + ASSERT_FALSE(cvc5_sort_is_array(nullptr)); + ASSERT_TRUE(cvc5_sort_is_array(sort)); + ASSERT_FALSE(cvc5_sort_is_array(d_int)); +} + +TEST_F(TestCApiBlackSort, is_set) +{ + Cvc5Sort sort = cvc5_mk_set_sort(d_tm, d_int); + ASSERT_FALSE(cvc5_sort_is_set(nullptr)); + ASSERT_TRUE(cvc5_sort_is_set(sort)); + ASSERT_FALSE(cvc5_sort_is_set(d_int)); +} + +TEST_F(TestCApiBlackSort, is_bag) +{ + Cvc5Sort sort = cvc5_mk_bag_sort(d_tm, d_int); + ASSERT_FALSE(cvc5_sort_is_bag(nullptr)); + ASSERT_TRUE(cvc5_sort_is_bag(sort)); + ASSERT_FALSE(cvc5_sort_is_bag(d_int)); +} + +TEST_F(TestCApiBlackSort, is_sequence) +{ + Cvc5Sort sort = cvc5_mk_sequence_sort(d_tm, d_int); + ASSERT_FALSE(cvc5_sort_is_sequence(nullptr)); + ASSERT_TRUE(cvc5_sort_is_sequence(sort)); + ASSERT_FALSE(cvc5_sort_is_sequence(d_int)); +} + +TEST_F(TestCApiBlackSort, is_abstract) +{ + ASSERT_FALSE(cvc5_sort_is_abstract(nullptr)); + ASSERT_TRUE(cvc5_sort_is_abstract( + cvc5_mk_abstract_sort(d_tm, CVC5_SORT_KIND_BITVECTOR_SORT))); + // ?Array is syntax sugar for (Array ? ?), thus the constructed sort + // is an Array sort, not an abstract sort. + ASSERT_FALSE(cvc5_sort_is_abstract( + cvc5_mk_abstract_sort(d_tm, CVC5_SORT_KIND_ARRAY_SORT))); + ASSERT_TRUE(cvc5_sort_is_abstract( + cvc5_mk_abstract_sort(d_tm, CVC5_SORT_KIND_ABSTRACT_SORT))); + ASSERT_FALSE(cvc5_sort_is_abstract(d_int)); +} + +TEST_F(TestCApiBlackSort, is_uninterpreted) +{ + ASSERT_FALSE(cvc5_sort_is_uninterpreted_sort(nullptr)); + ASSERT_TRUE(cvc5_sort_is_uninterpreted_sort( + cvc5_mk_uninterpreted_sort(d_tm, "asdf"))); + ASSERT_TRUE(cvc5_sort_is_uninterpreted_sort( + cvc5_mk_uninterpreted_sort(d_tm, nullptr))); +} + +TEST_F(TestCApiBlackSort, is_uninterpreted_sort_constructor) +{ + ASSERT_FALSE(cvc5_sort_is_uninterpreted_sort_constructor(nullptr)); + ASSERT_TRUE(cvc5_sort_is_uninterpreted_sort_constructor( + cvc5_mk_uninterpreted_sort_constructor_sort(d_tm, 1, "asdf"))); + ASSERT_TRUE(cvc5_sort_is_uninterpreted_sort_constructor( + cvc5_mk_uninterpreted_sort_constructor_sort(d_tm, 2, nullptr))); +} + +TEST_F(TestCApiBlackSort, get_datatype) +{ + Cvc5Sort dt_sort = create_datatype_sort(); + (void)cvc5_sort_get_datatype(dt_sort); + // create bv sort, check should fail + ASSERT_DEATH(cvc5_sort_get_datatype(d_int), "expected datatype sort"); +} + +TEST_F(TestCApiBlackSort, dt_domain_codomain_sorts) +{ + size_t size; + Cvc5Sort sort = create_datatype_sort(); + Cvc5Datatype dt = cvc5_sort_get_datatype(sort); + ASSERT_FALSE(cvc5_sort_is_dt_constructor(sort)); + ASSERT_DEATH(cvc5_sort_dt_constructor_get_codomain(sort), + "not a constructor sort"); + ASSERT_DEATH(cvc5_sort_dt_constructor_get_domain(sort, &size), + "not a constructor sort"); + ASSERT_DEATH(cvc5_sort_dt_constructor_get_arity(sort), + "not a constructor sort"); + + // get constructor + ASSERT_DEATH(cvc5_dt_get_constructor(nullptr, 0), "invalid datatype"); + Cvc5DatatypeConstructor cons = cvc5_dt_get_constructor(dt, 0); + Cvc5Sort cons_sort = cvc5_term_get_sort(cvc5_dt_cons_get_term(cons)); + ASSERT_TRUE(cvc5_sort_is_dt_constructor(cons_sort)); + ASSERT_FALSE(cvc5_sort_is_dt_tester(cons_sort)); + ASSERT_FALSE(cvc5_sort_is_dt_selector(cons_sort)); + ASSERT_EQ(cvc5_sort_dt_constructor_get_arity(cons_sort), 2); + const Cvc5Sort* domain = + cvc5_sort_dt_constructor_get_domain(cons_sort, &size); + ASSERT_TRUE(cvc5_sort_is_equal(domain[0], d_int)); + ASSERT_TRUE(cvc5_sort_is_equal(domain[1], sort)); + ASSERT_TRUE(cvc5_sort_is_equal( + cvc5_sort_dt_constructor_get_codomain(cons_sort), sort)); + + // get tester + ASSERT_DEATH(cvc5_dt_cons_get_tester_term(nullptr), + "invalid datatype constructor"); + ASSERT_DEATH(cvc5_sort_dt_tester_get_domain(d_bool), "not a tester sort"); + ASSERT_DEATH(cvc5_sort_dt_tester_get_codomain(d_bool), "not a tester sort"); + Cvc5Term tester = cvc5_dt_cons_get_tester_term(cons); + Cvc5Sort tester_sort = cvc5_term_get_sort(tester); + ASSERT_TRUE(cvc5_sort_is_dt_tester(tester_sort)); + ASSERT_TRUE( + cvc5_sort_is_equal(cvc5_sort_dt_tester_get_domain(tester_sort), sort)); + ASSERT_TRUE(cvc5_sort_is_equal(cvc5_sort_dt_tester_get_codomain(tester_sort), + d_bool)); + + // get selector + ASSERT_DEATH(cvc5_dt_cons_get_selector(nullptr, 1), + "invalid datatype constructor"); + ASSERT_DEATH(cvc5_sort_dt_selector_get_domain(d_bool), "not a selector sort"); + ASSERT_DEATH(cvc5_sort_dt_selector_get_codomain(d_bool), + "not a selector sort"); + Cvc5DatatypeSelector sel = cvc5_dt_cons_get_selector(cons, 1); + Cvc5Term tail = cvc5_dt_sel_get_term(sel); + Cvc5Sort tail_sort = cvc5_term_get_sort(tail); + ASSERT_TRUE(cvc5_sort_is_dt_selector(tail_sort)); + ASSERT_EQ(cvc5_sort_dt_selector_get_domain(tail_sort), sort); + ASSERT_EQ(cvc5_sort_dt_selector_get_codomain(tail_sort), sort); +} + +TEST_F(TestCApiBlackSort, instantiate) +{ + // instantiate parametric datatype, check should not fail + Cvc5Sort param_sort = create_param_datatype_sort(); + std::vector args = {d_int}; + (void)cvc5_sort_instantiate(param_sort, 1, args.data()); + // instantiate non-parametric datatype sort, check should fail + Cvc5Sort sort = create_datatype_sort(); + ASSERT_DEATH(cvc5_sort_instantiate(sort, 1, args.data()), + "expected parametric datatype or sort constructor sort"); + // instantiate uninterpreted sort constructor + Cvc5Sort sort_cons_sort = + cvc5_mk_uninterpreted_sort_constructor_sort(d_tm, 1, "s"); + (void)cvc5_sort_instantiate(sort_cons_sort, 1, args.data()); +} + +TEST_F(TestCApiBlackSort, is_instantiated) +{ + std::vector args = {d_int}; + Cvc5Sort param_sort = create_param_datatype_sort(); + ASSERT_FALSE(cvc5_sort_is_instantiated(param_sort)); + ASSERT_TRUE(cvc5_sort_is_instantiated( + cvc5_sort_instantiate(param_sort, 1, args.data()))); + + Cvc5Sort sort_cons_sort = + cvc5_mk_uninterpreted_sort_constructor_sort(d_tm, 1, "s"); + ASSERT_FALSE(cvc5_sort_is_instantiated(sort_cons_sort)); + ASSERT_TRUE(cvc5_sort_is_instantiated( + cvc5_sort_instantiate(sort_cons_sort, 1, args.data()))); + + ASSERT_FALSE(cvc5_sort_is_instantiated(d_int)); +} + +TEST_F(TestCApiBlackSort, get_instantiated_parameters) +{ + size_t size; + Cvc5Sort bv_sort = cvc5_mk_bv_sort(d_tm, 8); + + // parametric datatype instantiation + Cvc5Sort p1 = cvc5_mk_param_sort(d_tm, "p1"); + Cvc5Sort p2 = cvc5_mk_param_sort(d_tm, "p2"); + std::vector sorts = {p1, p2}; + Cvc5DatatypeDecl decl = + cvc5_mk_dt_decl_with_params(d_tm, "pdtype", 2, sorts.data(), false); + Cvc5DatatypeConstructorDecl cons1 = cvc5_mk_dt_cons_decl(d_tm, "cons1"); + Cvc5DatatypeConstructorDecl cons2 = cvc5_mk_dt_cons_decl(d_tm, "cons2"); + Cvc5DatatypeConstructorDecl nil = cvc5_mk_dt_cons_decl(d_tm, "nil"); + cvc5_dt_cons_decl_add_selector(cons1, "sel", p1); + cvc5_dt_cons_decl_add_selector(cons2, "sel", p2); + cvc5_dt_decl_add_constructor(decl, cons1); + cvc5_dt_decl_add_constructor(decl, cons2); + cvc5_dt_decl_add_constructor(decl, nil); + Cvc5Sort sort = cvc5_mk_dt_sort(d_tm, decl); + + ASSERT_DEATH(cvc5_sort_get_instantiated_parameters(nullptr, &size), + "invalid sort"); + ASSERT_DEATH(cvc5_sort_get_instantiated_parameters(sort, &size), + "expected instantiated parametric sort"); + + { + std::vector args = {d_real, d_bool}; + Cvc5Sort inst_sort = cvc5_sort_instantiate(sort, 2, args.data()); + ASSERT_DEATH(cvc5_sort_get_instantiated_parameters(inst_sort, nullptr), + "unexpected NULL argument"); + + const Cvc5Sort* inst_sorts = + cvc5_sort_get_instantiated_parameters(inst_sort, &size); + ASSERT_TRUE(cvc5_sort_is_equal(inst_sorts[0], d_real)); + ASSERT_TRUE(cvc5_sort_is_equal(inst_sorts[1], d_bool)); + } + + // uninterpreted sort constructor sort instantiation + Cvc5Sort sort_cons_sort = + cvc5_mk_uninterpreted_sort_constructor_sort(d_tm, 4, "a"); + ASSERT_DEATH(cvc5_sort_get_instantiated_parameters(sort_cons_sort, &size), + "expected instantiated parametric sort"); + + { + std::vector args = {d_bool, d_int, bv_sort, d_real}; + Cvc5Sort inst_sort = cvc5_sort_instantiate(sort_cons_sort, 4, args.data()); + const Cvc5Sort* inst_sorts = + cvc5_sort_get_instantiated_parameters(inst_sort, &size); + ASSERT_TRUE(cvc5_sort_is_equal(inst_sorts[0], d_bool)); + ASSERT_TRUE(cvc5_sort_is_equal(inst_sorts[1], d_int)); + ASSERT_TRUE(cvc5_sort_is_equal(inst_sorts[2], bv_sort)); + ASSERT_TRUE(cvc5_sort_is_equal(inst_sorts[3], d_real)); + } + + ASSERT_DEATH(cvc5_sort_get_instantiated_parameters(d_int, &size), + "expected instantiated parametric sort"); +} + +TEST_F(TestCApiBlackSort, get_uninterpreted_sort_constructor) +{ + Cvc5Sort bv_sort = cvc5_mk_bv_sort(d_tm, 8); + Cvc5Sort sort = cvc5_mk_uninterpreted_sort_constructor_sort(d_tm, 4, "s"); + std::vector args = {d_bool, d_int, bv_sort, d_real}; + ASSERT_DEATH(cvc5_sort_get_uninterpreted_sort_constructor(nullptr), + "invalid sort"); + ASSERT_DEATH(cvc5_sort_get_uninterpreted_sort_constructor(sort), + "expected instantiated uninterpreted sort"); + Cvc5Sort inst_sort = cvc5_sort_instantiate(sort, 4, args.data()); + ASSERT_TRUE(cvc5_sort_is_equal( + sort, cvc5_sort_get_uninterpreted_sort_constructor(inst_sort))); +} + +TEST_F(TestCApiBlackSort, get_fun_arity) +{ + std::vector domain = {cvc5_mk_uninterpreted_sort(d_tm, "u"), d_int}; + Cvc5Sort sort = cvc5_mk_fun_sort(d_tm, 2, domain.data(), d_int); + ASSERT_EQ(cvc5_sort_fun_get_arity(sort), 2); + ASSERT_DEATH(cvc5_sort_fun_get_arity(nullptr), "invalid sort"); + ASSERT_DEATH(cvc5_sort_fun_get_arity(d_int), "not a function sort"); +} + +TEST_F(TestCApiBlackSort, get_fun_domain_sorts) +{ + Cvc5Sort usort = cvc5_mk_uninterpreted_sort(d_tm, "u"); + std::vector domain = {usort, d_int}; + Cvc5Sort sort = cvc5_mk_fun_sort(d_tm, 2, domain.data(), d_int); + size_t size; + const Cvc5Sort* sorts = cvc5_sort_fun_get_domain(sort, &size); + ASSERT_EQ(size, 2); + ASSERT_TRUE(cvc5_sort_is_equal(sorts[0], usort)); + ASSERT_TRUE(cvc5_sort_is_equal(sorts[1], d_int)); + ASSERT_DEATH(cvc5_sort_fun_get_domain(nullptr, &size), "invalid sort"); + ASSERT_DEATH(cvc5_sort_fun_get_domain(sort, nullptr), + "unexpected NULL argument"); + ASSERT_DEATH(cvc5_sort_fun_get_domain(d_int, &size), "not a function sort"); +} + +TEST_F(TestCApiBlackSort, get_fun_codomain) +{ + Cvc5Sort usort = cvc5_mk_uninterpreted_sort(d_tm, "u"); + std::vector domain = {usort, d_int}; + Cvc5Sort sort = cvc5_mk_fun_sort(d_tm, 2, domain.data(), d_int); + ASSERT_TRUE(cvc5_sort_is_equal(cvc5_sort_fun_get_codomain(sort), d_int)); + ASSERT_DEATH(cvc5_sort_fun_get_codomain(nullptr), "invalid sort"); + ASSERT_DEATH(cvc5_sort_fun_get_codomain(d_int), "not a function sort"); +} + +TEST_F(TestCApiBlackSort, get_array_index_element) +{ + Cvc5Sort elem_sort = cvc5_mk_bv_sort(d_tm, 32); + Cvc5Sort index_sort = cvc5_mk_bv_sort(d_tm, 32); + Cvc5Sort sort = cvc5_mk_array_sort(d_tm, index_sort, elem_sort); + ASSERT_TRUE( + cvc5_sort_is_equal(cvc5_sort_array_get_index_sort(sort), index_sort)); + ASSERT_TRUE( + cvc5_sort_is_equal(cvc5_sort_array_get_element_sort(sort), elem_sort)); + ASSERT_DEATH(cvc5_sort_array_get_index_sort(nullptr), "invalid sort"); + ASSERT_DEATH(cvc5_sort_array_get_element_sort(nullptr), "invalid sort"); + ASSERT_DEATH(cvc5_sort_array_get_index_sort(d_int), "not an array sort"); + ASSERT_DEATH(cvc5_sort_array_get_element_sort(d_int), "not an array sort"); +} + +TEST_F(TestCApiBlackSort, get_set_element) +{ + Cvc5Sort sort = cvc5_mk_set_sort(d_tm, d_int); + ASSERT_TRUE(cvc5_sort_is_equal(cvc5_sort_set_get_element_sort(sort), d_int)); + ASSERT_DEATH(cvc5_sort_set_get_element_sort(nullptr), "invalid sort"); + ASSERT_DEATH(cvc5_sort_set_get_element_sort(d_int), "not a set sort"); +} + +TEST_F(TestCApiBlackSort, get_bag_element) +{ + Cvc5Sort sort = cvc5_mk_bag_sort(d_tm, d_int); + ASSERT_TRUE(cvc5_sort_is_equal(cvc5_sort_bag_get_element_sort(sort), d_int)); + ASSERT_DEATH(cvc5_sort_bag_get_element_sort(nullptr), "invalid sort"); + ASSERT_DEATH(cvc5_sort_bag_get_element_sort(d_int), "not a bag sort"); +} + +TEST_F(TestCApiBlackSort, get_sequence_element) +{ + Cvc5Sort sort = cvc5_mk_sequence_sort(d_tm, d_int); + ASSERT_TRUE( + cvc5_sort_is_equal(cvc5_sort_sequence_get_element_sort(sort), d_int)); + ASSERT_DEATH(cvc5_sort_sequence_get_element_sort(nullptr), "invalid sort"); + ASSERT_DEATH(cvc5_sort_sequence_get_element_sort(d_int), + "not a sequence sort"); +} + +TEST_F(TestCApiBlackSort, abstract_get_kind) +{ + Cvc5Sort sort = cvc5_mk_abstract_sort(d_tm, CVC5_SORT_KIND_BITVECTOR_SORT); + ASSERT_EQ(cvc5_sort_abstract_get_kind(sort), CVC5_SORT_KIND_BITVECTOR_SORT); + // ?Array is syntax sugar for (Array ? ?), thus the constructed sort + // is an Array sort, not an abstract sort and its abstract kind cannot be + // extracted. + sort = cvc5_mk_abstract_sort(d_tm, CVC5_SORT_KIND_ARRAY_SORT); + ASSERT_DEATH(cvc5_sort_abstract_get_kind(sort), "not an abstract sort"); + sort = cvc5_mk_abstract_sort(d_tm, CVC5_SORT_KIND_ABSTRACT_SORT); + ASSERT_EQ(cvc5_sort_abstract_get_kind(sort), CVC5_SORT_KIND_ABSTRACT_SORT); +} + +TEST_F(TestCApiBlackSort, get_uninterpreted_sort_constructor_name) +{ + Cvc5Sort sort = cvc5_mk_uninterpreted_sort_constructor_sort(d_tm, 2, "s"); + ASSERT_EQ(cvc5_sort_get_symbol(sort), std::string("s")); + ASSERT_DEATH(cvc5_sort_get_symbol(nullptr), "invalid sort"); + ASSERT_DEATH(cvc5_sort_get_symbol(d_int), "has no symbol"); +} + +TEST_F(TestCApiBlackSort, uninterpreted_sort_constructor_get_arity) +{ + Cvc5Sort sort = cvc5_mk_uninterpreted_sort_constructor_sort(d_tm, 2, "s"); + ASSERT_EQ(cvc5_sort_uninterpreted_sort_constructor_get_arity(sort), 2); + ASSERT_DEATH(cvc5_sort_uninterpreted_sort_constructor_get_arity(nullptr), + "invalid sort"); + ASSERT_DEATH(cvc5_sort_uninterpreted_sort_constructor_get_arity(d_int), + "not a sort constructor sort"); +} + +TEST_F(TestCApiBlackSort, bv_get_size) +{ + Cvc5Sort sort = cvc5_mk_bv_sort(d_tm, 32); + ASSERT_EQ(cvc5_sort_bv_get_size(sort), 32); + ASSERT_DEATH(cvc5_sort_bv_get_size(nullptr), "invalid sort"); + ASSERT_DEATH(cvc5_sort_bv_get_size(d_int), "not a bit-vector sort"); +} + +TEST_F(TestCApiBlackSort, ff_get_size) +{ + Cvc5Sort sort = cvc5_mk_ff_sort(d_tm, "31", 10); + ASSERT_EQ(cvc5_sort_ff_get_size(sort), std::string("31")); + ASSERT_DEATH(cvc5_sort_ff_get_size(nullptr), "invalid sort"); + ASSERT_DEATH(cvc5_sort_ff_get_size(d_int), "not a finite field sort"); +} + +TEST_F(TestCApiBlackSort, fp_get_exp_sig_size) +{ + Cvc5Sort sort = cvc5_mk_fp_sort(d_tm, 8, 24); + ASSERT_EQ(cvc5_sort_fp_get_exp_size(sort), 8); + ASSERT_EQ(cvc5_sort_fp_get_sig_size(sort), 24); + ASSERT_DEATH(cvc5_sort_fp_get_exp_size(nullptr), "invalid sort"); + ASSERT_DEATH(cvc5_sort_fp_get_exp_size(d_int), "not a floating-point sort"); + ASSERT_DEATH(cvc5_sort_fp_get_sig_size(nullptr), "invalid sort"); + ASSERT_DEATH(cvc5_sort_fp_get_sig_size(d_int), "not a floating-point sort"); +} + +TEST_F(TestCApiBlackSort, dt_get_arity) +{ + // create datatype sort, check should not fail + Cvc5Sort sort = create_datatype_sort(); + ASSERT_EQ(cvc5_sort_dt_get_arity(sort), 0); + // create bv sort, check should fail + ASSERT_DEATH(cvc5_sort_dt_get_arity(nullptr), "invalid sort"); + ASSERT_DEATH(cvc5_sort_dt_get_arity(d_int), "not a datatype sort"); +} + +TEST_F(TestCApiBlackSort, tuple_get_length) +{ + std::vector args = {d_int, d_int}; + Cvc5Sort sort = cvc5_mk_tuple_sort(d_tm, 2, args.data()); + ASSERT_EQ(cvc5_sort_tuple_get_length(sort), 2); + ASSERT_DEATH(cvc5_sort_tuple_get_length(nullptr), "invalid sort"); + ASSERT_DEATH(cvc5_sort_tuple_get_length(d_int), "not a tuple sort"); +} + +TEST_F(TestCApiBlackSort, tuple_get_element_sorts) +{ + size_t size; + std::vector args = {d_int, d_int}; + Cvc5Sort sort = cvc5_mk_tuple_sort(d_tm, 2, args.data()); + const Cvc5Sort* sorts = cvc5_sort_tuple_get_element_sorts(sort, &size); + ASSERT_TRUE(cvc5_sort_is_equal(sorts[0], d_int)); + ASSERT_TRUE(cvc5_sort_is_equal(sorts[1], d_int)); + ASSERT_DEATH(cvc5_sort_tuple_get_element_sorts(nullptr, &size), + "invalid sort"); + ASSERT_DEATH(cvc5_sort_tuple_get_element_sorts(sort, nullptr), + "unexpected NULL argument"); + ASSERT_DEATH(cvc5_sort_tuple_get_element_sorts(d_int, &size), + "not a tuple sort"); +} + +TEST_F(TestCApiBlackSort, nullable_get_element_sort) +{ + Cvc5Sort sort = cvc5_mk_nullable_sort(d_tm, d_real); + ASSERT_TRUE( + cvc5_sort_is_equal(cvc5_sort_nullable_get_element_sort(sort), d_real)); + ASSERT_DEATH(cvc5_sort_nullable_get_element_sort(nullptr), "invalid sort"); + ASSERT_DEATH(cvc5_sort_nullable_get_element_sort(d_int), + "not a nullable sort"); +} + +TEST_F(TestCApiBlackSort, scoped_to_string) +{ + std::string name = "uninterp-sort"; + Cvc5Sort bvsort = cvc5_mk_bv_sort(d_tm, 8); + Cvc5Sort usort = cvc5_mk_uninterpreted_sort(d_tm, name.c_str()); + ASSERT_EQ(cvc5_sort_to_string(bvsort), std::string("(_ BitVec 8)")); + ASSERT_EQ(cvc5_sort_to_string(usort), name); + ASSERT_EQ(cvc5_sort_to_string(bvsort), std::string("(_ BitVec 8)")); + ASSERT_EQ(cvc5_sort_to_string(usort), name); +} + +TEST_F(TestCApiBlackSort, substitute) +{ + Cvc5Sort p0 = cvc5_mk_param_sort(d_tm, "T0"); + Cvc5Sort p1 = cvc5_mk_param_sort(d_tm, "T1"); + Cvc5Sort arrsort0 = cvc5_mk_array_sort(d_tm, p0, p0); + Cvc5Sort arrsort1 = cvc5_mk_array_sort(d_tm, p1, p1); + // Now create instantiations of the defined sorts + (void)cvc5_sort_substitute(arrsort0, p0, d_int); + std::vector sorts = {p0, p1}; + std::vector replacements = {d_int, d_real}; + (void)cvc5_sort_substitute_sorts( + arrsort1, 2, sorts.data(), replacements.data()); +} + +} // namespace cvc5::internal::test diff --git a/test/unit/api/cpp/CMakeLists.txt b/test/unit/api/cpp/CMakeLists.txt index bbd07f3e9b6..0d7ca366a1e 100644 --- a/test/unit/api/cpp/CMakeLists.txt +++ b/test/unit/api/cpp/CMakeLists.txt @@ -15,6 +15,7 @@ cvc5_add_unit_test_black(api_kind_black api/cpp) cvc5_add_unit_test_black(api_proof_rule_black api/cpp) cvc5_add_unit_test_black(api_skolem_id_black api/cpp) +cvc5_add_unit_test_black(api_sort_black api/cpp) cvc5_add_unit_test_black(api_sort_kind_black api/cpp) cvc5_add_unit_test_black(api_term_manager_black api/cpp) cvc5_add_unit_test_black(api_types_black api/cpp) @@ -27,7 +28,6 @@ cvc5_add_unit_test_black(parametric_datatype_black api/cpp) cvc5_add_unit_test_black(proof_black api/cpp) cvc5_add_unit_test_black(result_black api/cpp) cvc5_add_unit_test_black(solver_black api/cpp) -cvc5_add_unit_test_black(sort_black api/cpp) cvc5_add_unit_test_black(symbol_manager_black api/cpp) cvc5_add_unit_test_black(synth_result_black api/cpp) cvc5_add_unit_test_black(term_black api/cpp) diff --git a/test/unit/api/cpp/sort_black.cpp b/test/unit/api/cpp/api_sort_black.cpp similarity index 96% rename from test/unit/api/cpp/sort_black.cpp rename to test/unit/api/cpp/api_sort_black.cpp index 8cfb0025e02..1ab0a0e5740 100644 --- a/test/unit/api/cpp/sort_black.cpp +++ b/test/unit/api/cpp/api_sort_black.cpp @@ -168,25 +168,27 @@ TEST_F(TestApiBlackSort, isDatatype) ASSERT_NO_THROW(Sort().isDatatype()); } -TEST_F(TestApiBlackSort, isConstructor) +TEST_F(TestApiBlackSort, isDatatypeConstructor) { Sort dt_sort = create_datatype_sort(); Datatype dt = dt_sort.getDatatype(); Sort cons_sort = dt[0].getTerm().getSort(); + ASSERT_THROW(dt[3], CVC5ApiException); ASSERT_TRUE(cons_sort.isDatatypeConstructor()); ASSERT_NO_THROW(Sort().isDatatypeConstructor()); } -TEST_F(TestApiBlackSort, isSelector) +TEST_F(TestApiBlackSort, isDatatypeSelector) { Sort dt_sort = create_datatype_sort(); Datatype dt = dt_sort.getDatatype(); - Sort cons_sort = dt[0][1].getTerm().getSort(); - ASSERT_TRUE(cons_sort.isDatatypeSelector()); + Sort sel_sort = dt[0][1].getTerm().getSort(); + ASSERT_THROW(dt[0][2], CVC5ApiException); + ASSERT_TRUE(sel_sort.isDatatypeSelector()); ASSERT_NO_THROW(Sort().isDatatypeSelector()); } -TEST_F(TestApiBlackSort, isTester) +TEST_F(TestApiBlackSort, isDatatypeTester) { Sort dt_sort = create_datatype_sort(); Datatype dt = dt_sort.getDatatype(); @@ -195,7 +197,7 @@ TEST_F(TestApiBlackSort, isTester) ASSERT_NO_THROW(Sort().isDatatypeTester()); } -TEST_F(TestApiBlackSort, isUpdater) +TEST_F(TestApiBlackSort, isDatatypeUpdater) { Sort dt_sort = create_datatype_sort(); Datatype dt = dt_sort.getDatatype(); @@ -331,11 +333,11 @@ TEST_F(TestApiBlackSort, datatypeSorts) ASSERT_EQ(consSort.getDatatypeConstructorCodomainSort(), dtypeSort); // get tester - Term isConsTerm = dcons.getTesterTerm(); - ASSERT_TRUE(isConsTerm.getSort().isDatatypeTester()); - ASSERT_EQ(isConsTerm.getSort().getDatatypeTesterDomainSort(), dtypeSort); + Term testerTerm = dcons.getTesterTerm(); + ASSERT_TRUE(testerTerm.getSort().isDatatypeTester()); + ASSERT_EQ(testerTerm.getSort().getDatatypeTesterDomainSort(), dtypeSort); Sort booleanSort = d_tm.getBooleanSort(); - ASSERT_EQ(isConsTerm.getSort().getDatatypeTesterCodomainSort(), booleanSort); + ASSERT_EQ(testerTerm.getSort().getDatatypeTesterCodomainSort(), booleanSort); ASSERT_THROW(booleanSort.getDatatypeTesterDomainSort(), CVC5ApiException); ASSERT_THROW(booleanSort.getDatatypeTesterCodomainSort(), CVC5ApiException); @@ -450,7 +452,7 @@ TEST_F(TestApiBlackSort, getFunctionArity) { Sort funSort = d_tm.mkFunctionSort({d_tm.mkUninterpretedSort("u")}, d_tm.getIntegerSort()); - ASSERT_NO_THROW(funSort.getFunctionArity()); + ASSERT_EQ(funSort.getFunctionArity(), 1); Sort bvSort = d_tm.mkBitVectorSort(32); ASSERT_THROW(bvSort.getFunctionArity(), CVC5ApiException); } @@ -659,10 +661,7 @@ TEST_F(TestApiBlackSort, sortScopedToString) ASSERT_EQ(uninterp_sort.toString(), name); } -TEST_F(TestApiBlackSort, toString) -{ - ASSERT_NO_THROW(Sort().toString()); -} +TEST_F(TestApiBlackSort, toString) { ASSERT_NO_THROW(Sort().toString()); } TEST_F(TestApiBlackSort, substitute) { From f6748e08d96b797826e13556f88b67ed1752e5ea Mon Sep 17 00:00:00 2001 From: Andrew Reynolds Date: Tue, 4 Jun 2024 16:27:01 -0500 Subject: [PATCH 27/35] Update syntax for list operators in ALF (#10853) This updates the syntax of list operators in ALF to one that has no ambiguity in terms of arity, using the updated operator names. --- contrib/get-alf-checker | 2 +- proofs/alf/cvc5/programs/Nary.smt3 | 2 +- proofs/alf/cvc5/programs/Strings.smt3 | 2 +- proofs/alf/cvc5/programs/Utils.smt3 | 2 +- proofs/alf/cvc5/rules/Booleans.smt3 | 14 +++++++------- proofs/alf/cvc5/theories/Bags.smt3 | 2 +- proofs/alf/cvc5/theories/Datatypes.smt3 | 2 +- proofs/alf/cvc5/theories/Sets.smt3 | 2 +- 8 files changed, 14 insertions(+), 14 deletions(-) diff --git a/contrib/get-alf-checker b/contrib/get-alf-checker index 5de212c98aa..5a2f78b344b 100755 --- a/contrib/get-alf-checker +++ b/contrib/get-alf-checker @@ -24,7 +24,7 @@ ALFC_DIR="$BASE_DIR/alf-checker" mkdir -p $ALFC_DIR # download and unpack ALFC -ALF_VERSION="32f19e225a8bd560ead65ad0edb48144ecdbc168" +ALF_VERSION="6f041dbdcbffe1e79a9c9d65bd6a2757ec51f7b7" download "https://github.com/cvc5/alfc/archive/$ALF_VERSION.tar.gz" $BASE_DIR/tmp/alfc.tgz tar --strip 1 -xzf $BASE_DIR/tmp/alfc.tgz -C $ALFC_DIR diff --git a/proofs/alf/cvc5/programs/Nary.smt3 b/proofs/alf/cvc5/programs/Nary.smt3 index c0317132f57..805c1de315b 100644 --- a/proofs/alf/cvc5/programs/Nary.smt3 +++ b/proofs/alf/cvc5/programs/Nary.smt3 @@ -31,7 +31,7 @@ ((L Type) (cons (-> L L L)) (nil L) (c L) (t L) (xs L :list)) ((-> L L L) L L L) Bool ( - ((nary.is_subset cons nil (cons c xs) t) (alf.ite (alf.is_neg (alf.find cons t c)) false (nary.is_subset cons nil xs t))) + ((nary.is_subset cons nil (cons c xs) t) (alf.ite (alf.is_neg (alf.list_find cons t c)) false (nary.is_subset cons nil xs t))) ((nary.is_subset cons nil nil t) true) ) ) diff --git a/proofs/alf/cvc5/programs/Strings.smt3 b/proofs/alf/cvc5/programs/Strings.smt3 index 1ab4f7d7709..c0a33ec78a8 100644 --- a/proofs/alf/cvc5/programs/Strings.smt3 +++ b/proofs/alf/cvc5/programs/Strings.smt3 @@ -25,7 +25,7 @@ ; Return the concatenation of strings x and y, treated as lists. (define $str_concat ((T Type :implicit) (x (Seq T)) (y (Seq T))) - (alf.concat str.++ x y) + (alf.list_concat str.++ x y) ) ; Return the result of prepending of string x to string y where the latter is diff --git a/proofs/alf/cvc5/programs/Utils.smt3 b/proofs/alf/cvc5/programs/Utils.smt3 index 259268cf110..e6d1ffff0fe 100644 --- a/proofs/alf/cvc5/programs/Utils.smt3 +++ b/proofs/alf/cvc5/programs/Utils.smt3 @@ -128,7 +128,7 @@ ((-> T U S) S S) S ( ((get_a_norm_rec f id (f id x2)) (get_a_norm_rec f id x2)) - ((get_a_norm_rec f id (f x1 x2)) (alf.concat f (get_a_norm_rec f id x1) (get_a_norm_rec f id x2))) + ((get_a_norm_rec f id (f x1 x2)) (alf.list_concat f (get_a_norm_rec f id x1) (get_a_norm_rec f id x2))) ((get_a_norm_rec f id id) id) ((get_a_norm_rec f id x) (alf.cons f x id)) ) diff --git a/proofs/alf/cvc5/rules/Booleans.smt3 b/proofs/alf/cvc5/rules/Booleans.smt3 index f01a9e4f431..ae801bb62c1 100644 --- a/proofs/alf/cvc5/rules/Booleans.smt3 +++ b/proofs/alf/cvc5/rules/Booleans.smt3 @@ -42,7 +42,7 @@ ((resolve C1 C2 pol L) (let ((lp (alf.ite pol L (not L)))) (let ((ln (alf.ite pol (not L) L))) - (from_clause (alf.concat or + (from_clause (alf.list_concat or (removeSelf lp (to_clause C1)) (removeSelf ln (to_clause C2))))))) ) @@ -64,7 +64,7 @@ (chainResolveRec (let ((lp (alf.ite pol L (not L)))) (let ((ln (alf.ite pol (not L) L))) - (alf.concat or + (alf.list_concat or (removeSelf lp C1) (removeSelf ln (to_clause C2))))) Cs pols lits)) ) @@ -90,7 +90,7 @@ (program factorLiterals ((xs Bool :list) (l Bool) (ls Bool :list)) (Bool Bool) Bool ( - ((factorLiterals xs (or l ls)) (let ((cond (alf.is_neg (alf.find or xs l)))) + ((factorLiterals xs (or l ls)) (let ((cond (alf.is_neg (alf.list_find or xs l)))) (let ((ret (factorLiterals (alf.ite cond (alf.cons or l xs) xs) ls))) @@ -139,7 +139,7 @@ (declare-rule and_elim ((Fs Bool) (i Int)) :premises (Fs) :args (i) - :conclusion (alf.extract and Fs i) + :conclusion (alf.list_nth and Fs i) ) ; AND_INTRO @@ -152,7 +152,7 @@ (declare-rule not_or_elim ((Fs Bool) (i Int)) :premises ((not Fs)) :args (i) - :conclusion (not (alf.extract or Fs i)) + :conclusion (not (alf.list_nth or Fs i)) ) ; IMPLIES_ELIM @@ -266,7 +266,7 @@ ; CNF_AND_POS (declare-rule cnf_and_pos ((Fs Bool) (i Int)) :args (Fs i) - :conclusion (or (not Fs) (alf.extract and Fs i)) + :conclusion (or (not Fs) (alf.list_nth and Fs i)) ) ; CNF_AND_NEG @@ -284,7 +284,7 @@ ; CNF_OR_NEG (declare-rule cnf_or_neg ((Fs Bool) (i Int)) :args (Fs i) - :conclusion (or Fs (not (alf.extract or Fs i))) + :conclusion (or Fs (not (alf.list_nth or Fs i))) ) ; CNF_IMPLIES_POS diff --git a/proofs/alf/cvc5/theories/Bags.smt3 b/proofs/alf/cvc5/theories/Bags.smt3 index 95b1a7682fa..d5b1059c518 100644 --- a/proofs/alf/cvc5/theories/Bags.smt3 +++ b/proofs/alf/cvc5/theories/Bags.smt3 @@ -24,7 +24,7 @@ (declare-const bag.filter (-> (! Type :var T :implicit) (-> T Bool) (Bag T) (Bag T))) (declare-const bag.map (-> (! Type :var T :implicit) (! Type :var U :implicit) (-> T U) (Bag T) (Bag U))) (declare-const bag.fold (-> (! Type :var T :implicit) (! Type :var U :implicit) (-> T U U) U (Bag T) U)) -(declare-const table.product (-> (! Type :var T :implicit) (! Type :var U :implicit) (Bag T) (Bag U) (Bag (alf.concat Tuple U T)))) +(declare-const table.product (-> (! Type :var T :implicit) (! Type :var U :implicit) (Bag T) (Bag U) (Bag (alf.list_concat Tuple U T)))) (declare-const table.group (-> (! Type :var T :implicit) @List (Bag T) (Bag (Bag T)))) ; Skolems for the theory of bags. diff --git a/proofs/alf/cvc5/theories/Datatypes.smt3 b/proofs/alf/cvc5/theories/Datatypes.smt3 index 02f00027fe4..debbb236cf1 100644 --- a/proofs/alf/cvc5/theories/Datatypes.smt3 +++ b/proofs/alf/cvc5/theories/Datatypes.smt3 @@ -8,7 +8,7 @@ T U (alf.cons Tuple T U)) :right-assoc-nil tuple.unit) (declare-const tuple.select (-> (! Type :var T :implicit) - (! Int :var i) T (alf.extract Tuple T i))) + (! Int :var i) T (alf.list_nth Tuple T i))) (declare-const tuple.update (-> (! Type :var T :implicit) (! Type :var S :implicit) Int T S T)) diff --git a/proofs/alf/cvc5/theories/Sets.smt3 b/proofs/alf/cvc5/theories/Sets.smt3 index 32b6f7198db..343be478248 100644 --- a/proofs/alf/cvc5/theories/Sets.smt3 +++ b/proofs/alf/cvc5/theories/Sets.smt3 @@ -36,7 +36,7 @@ ; Relation operators. (declare-const rel.tclosure (-> (! Type :var T :implicit) (Set (Tuple T T)) (Set (Tuple T T)))) (declare-const rel.transpose (-> (! Type :var T :implicit) (Set T) (Set (nary.reverse T)))) -(declare-const rel.product (-> (! Type :var T :implicit) (! Type :var U :implicit) (Set T) (Set U) (Set (alf.concat Tuple T U)))) +(declare-const rel.product (-> (! Type :var T :implicit) (! Type :var U :implicit) (Set T) (Set U) (Set (alf.list_concat Tuple T U)))) (declare-const rel.join (-> (! Type :var T :implicit) (! Type :var U :implicit) (Set T) (Set U) (Set (nary.join Tuple UnitTuple T U)))) (declare-const rel.group (-> (! Type :var T :implicit) @List (Set T) (Set (Set T)))) From 48d3546ed6083d9318ab80ec249ac2d2fdbb98bc Mon Sep 17 00:00:00 2001 From: Andrew Reynolds Date: Tue, 4 Jun 2024 17:01:34 -0500 Subject: [PATCH 28/35] Test DSL proofs with ALF by default (#10840) --- contrib/get-alf-checker | 2 +- test/regress/cli/run_regression.py | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/contrib/get-alf-checker b/contrib/get-alf-checker index 5a2f78b344b..0e525c4bd59 100755 --- a/contrib/get-alf-checker +++ b/contrib/get-alf-checker @@ -24,7 +24,7 @@ ALFC_DIR="$BASE_DIR/alf-checker" mkdir -p $ALFC_DIR # download and unpack ALFC -ALF_VERSION="6f041dbdcbffe1e79a9c9d65bd6a2757ec51f7b7" +ALF_VERSION="da1903230c1d7cc5adbacb65d4eee602dcba93a0" download "https://github.com/cvc5/alfc/archive/$ALF_VERSION.tar.gz" $BASE_DIR/tmp/alfc.tgz tar --strip 1 -xzf $BASE_DIR/tmp/alfc.tgz -C $ALFC_DIR diff --git a/test/regress/cli/run_regression.py b/test/regress/cli/run_regression.py index 6a2bf13d0e8..5cb43b67713 100755 --- a/test/regress/cli/run_regression.py +++ b/test/regress/cli/run_regression.py @@ -279,7 +279,7 @@ def run_internal(self, benchmark_info): cvc5_args = [ "--dump-proofs", "--proof-format=alf", - "--proof-granularity=theory-rewrite", + "--proof-granularity=dsl-rewrite", "--proof-print-conclusion", ] + benchmark_info.command_line_args output, error, exit_status = run_process( @@ -754,6 +754,9 @@ def run_regression( return EXIT_FAILURE if disable_tester in testers: testers.remove(disable_tester) + if disable_tester == "dsl-proof": + if "alf" in testers: + testers.remove("alf") if disable_tester == "proof": if "lfsc" in testers: testers.remove("lfsc") From 94b6eb7290ef4f785dc8ad50c35547f63724e502 Mon Sep 17 00:00:00 2001 From: Andrew Reynolds Date: Tue, 4 Jun 2024 17:40:49 -0500 Subject: [PATCH 29/35] Move slow regression to regress3 (#10851) Fixes a timeout on the nightlies, this benchmark has been slow (7 seconds on production) and is timing out on our model tester in a build on the nightlies. --- test/regress/cli/CMakeLists.txt | 2 +- .../cli/{regress2 => regress3}/issue4707-bv-to-bool-large.smt2 | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename test/regress/cli/{regress2 => regress3}/issue4707-bv-to-bool-large.smt2 (100%) diff --git a/test/regress/cli/CMakeLists.txt b/test/regress/cli/CMakeLists.txt index b45475664a7..7aa584eddbb 100644 --- a/test/regress/cli/CMakeLists.txt +++ b/test/regress/cli/CMakeLists.txt @@ -3556,7 +3556,6 @@ set(regress_2_tests regress2/hole8.cvc.smt2 regress2/instance_1444.smtv1.smt2 regress2/issue3687-check-models.smt2 - regress2/issue4707-bv-to-bool-large.smt2 regress2/issue6495-dup-pat-term.smt2 regress2/lemmas/simple_startup_9nodes.abstract.base.smtv1.smt2 regress2/javafe.ast.StandardPrettyPrint.319_no_forall.smt2 @@ -3674,6 +3673,7 @@ set(regress_3_tests regress3/interpol2.smt2 regress3/inv_gen_n_c11.sy regress3/issue4170.smt2 + regress3/issue4707-bv-to-bool-large.smt2 regress3/lpsat-goal-9.smt2 regress3/nia-max-square.sy regress3/nl/iand-native-1.smt2 diff --git a/test/regress/cli/regress2/issue4707-bv-to-bool-large.smt2 b/test/regress/cli/regress3/issue4707-bv-to-bool-large.smt2 similarity index 100% rename from test/regress/cli/regress2/issue4707-bv-to-bool-large.smt2 rename to test/regress/cli/regress3/issue4707-bv-to-bool-large.smt2 From aa749226911f8656ee7d878508349bf55990d324 Mon Sep 17 00:00:00 2001 From: Andrew Reynolds Date: Wed, 5 Jun 2024 09:11:35 -0500 Subject: [PATCH 30/35] Add RARE rule for bv2nat inequality elimination (#10849) Towards eliminating the diversity of trusted steps from our regressions. This is required to fill the remaining 5 PP_STATIC_REWRITE proof holes from our regressions. Also updates the ALF signature as we now have a symbolic term in an index position in int2bv. --- include/cvc5/cvc5_proof_rule.h | 2 ++ proofs/alf/cvc5/theories/BitVectors.smt3 | 2 +- src/theory/arith/arith_rewriter.cpp | 6 +++++- src/theory/uf/rewrites | 6 ++++++ 4 files changed, 14 insertions(+), 2 deletions(-) diff --git a/include/cvc5/cvc5_proof_rule.h b/include/cvc5/cvc5_proof_rule.h index 70845234326..3f91f31d412 100644 --- a/include/cvc5/cvc5_proof_rule.h +++ b/include/cvc5/cvc5_proof_rule.h @@ -3424,6 +3424,8 @@ enum ENUM(ProofRewriteRule) : uint32_t EVALUE(EQ_SYMM), /** Auto-generated from RARE rule distinct-binary-elim */ EVALUE(DISTINCT_BINARY_ELIM), + /** Auto-generated from RARE rule uf-bv2nat-geq-elim */ + EVALUE(UF_BV2NAT_GEQ_ELIM), // ${rules}$ #ifdef CVC5_API_USE_C_ENUMS // must be last entry diff --git a/proofs/alf/cvc5/theories/BitVectors.smt3 b/proofs/alf/cvc5/theories/BitVectors.smt3 index e13dd520bce..4e4cacb635c 100644 --- a/proofs/alf/cvc5/theories/BitVectors.smt3 +++ b/proofs/alf/cvc5/theories/BitVectors.smt3 @@ -298,7 +298,7 @@ (declare-const int2bv (-> (! Int :var w) - Int (BitVec w)) + Int (BitVec ($eval_bv_sym w))) ) (declare-const bv2nat (-> diff --git a/src/theory/arith/arith_rewriter.cpp b/src/theory/arith/arith_rewriter.cpp index d93e3d57fce..a86974c62c1 100644 --- a/src/theory/arith/arith_rewriter.cpp +++ b/src/theory/arith/arith_rewriter.cpp @@ -1298,7 +1298,11 @@ Node ArithRewriter::rewriteIneqToBv(Kind kind, ? zero : (otherSum.size() == 1 ? otherSum[0] : nm->mkNode(Kind::ADD, otherSum)); - Node o = bv2natPol ? nm->mkNode(Kind::NEG, osum) : osum; + // possibly negate the sum + Node o = bv2natPol + ? (osum.getKind() == Kind::NEG ? osum[0] + : nm->mkNode(Kind::NEG, osum)) + : osum; Node ub = nm->mkNode(Kind::GEQ, o, w); Node lb = nm->mkNode(Kind::LT, o, zero); Node iToBvop = nm->mkConst(IntToBitVector(bvsize)); diff --git a/src/theory/uf/rewrites b/src/theory/uf/rewrites index 9b7c219db0a..312876db40b 100644 --- a/src/theory/uf/rewrites +++ b/src/theory/uf/rewrites @@ -15,3 +15,9 @@ ;(define-rule uf-int2bv-bv2nat ((w Int) (t Int)) ; (bv2nat (int2bv w t)) ; (mod t (^ 2 w))) + + +(define-rule uf-bv2nat-geq-elim ((x ?BitVec) (n Int)) + (def (w (@bvsize x))) + (>= (bv2nat x) n) + (ite (>= n w) false (ite (< n 0) true (bvuge x (int2bv w n))))) From 5a989da99809141eca27b145981de8c7ceed2baa Mon Sep 17 00:00:00 2001 From: Andrew Reynolds Date: Wed, 5 Jun 2024 10:29:15 -0500 Subject: [PATCH 31/35] Another unit test for plugins (#10739) Fixes some of the C++ coverage failures in nightlies. --- test/unit/api/cpp/solver_black.cpp | 52 ++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/test/unit/api/cpp/solver_black.cpp b/test/unit/api/cpp/solver_black.cpp index 030b525b38e..e8a60a96429 100644 --- a/test/unit/api/cpp/solver_black.cpp +++ b/test/unit/api/cpp/solver_black.cpp @@ -2538,10 +2538,62 @@ TEST_F(TestApiBlackSolver, pluginUnsat) { PluginUnsat pu(d_tm); d_solver->addPlugin(pu); + ASSERT_TRUE(pu.getName() == "PluginUnsat"); // should be unsat since the plugin above asserts "false" as a lemma ASSERT_TRUE(d_solver->checkSat().isUnsat()); } +class PluginListen : public Plugin +{ + public: + PluginListen(TermManager& tm) + : Plugin(tm), + d_tm(tm), + d_hasSeenTheoryLemma(false), + d_hasSeenSatClause(false) + { + } + virtual ~PluginListen() {} + void notifySatClause(const Term& cl) override { d_hasSeenSatClause = true; } + bool hasSeenSatClause() const { return d_hasSeenSatClause; } + void notifyTheoryLemma(const Term& lem) override + { + d_hasSeenTheoryLemma = true; + } + bool hasSeenTheoryLemma() const { return d_hasSeenTheoryLemma; } + std::string getName() override { return "PluginListen"; } + + private: + /** Reference to the term manager */ + TermManager& d_tm; + /** have we seen a theory lemma? */ + bool d_hasSeenTheoryLemma; + /** have we seen a SAT clause? */ + bool d_hasSeenSatClause; +}; + +TEST_F(TestApiBlackSolver, pluginListen) +{ + // NOTE: this shouldn't be necessary but ensures notifySatClause is called here. + d_solver->setOption("plugin-notify-sat-clause-in-solve", "false"); + PluginListen pl(d_tm); + d_solver->addPlugin(pl); + Sort stringSort = d_tm.getStringSort(); + Term x = d_tm.mkConst(stringSort, "x"); + Term y = d_tm.mkConst(stringSort, "y"); + Term ctn1 = d_tm.mkTerm(Kind::STRING_CONTAINS, {x, y}); + Term ctn2 = d_tm.mkTerm(Kind::STRING_CONTAINS, {y, x}); + d_solver->assertFormula(d_tm.mkTerm(Kind::OR, {ctn1, ctn2})); + Term lx = d_tm.mkTerm(Kind::STRING_LENGTH, {x}); + Term ly = d_tm.mkTerm(Kind::STRING_LENGTH, {y}); + Term lc = d_tm.mkTerm(Kind::GT, {lx, ly}); + d_solver->assertFormula(lc); + ASSERT_TRUE(d_solver->checkSat().isSat()); + // above input formulas should induce a theory lemma and SAT clause learning + ASSERT_TRUE(pl.hasSeenTheoryLemma()); + ASSERT_TRUE(pl.hasSeenSatClause()); +} + TEST_F(TestApiBlackSolver, verticalBars) { Term a = d_solver->declareFun("|a |", {}, d_tm.getRealSort()); From fa90865f20aac935083620775878006a00573702 Mon Sep 17 00:00:00 2001 From: Andrew Reynolds Date: Wed, 5 Jun 2024 11:17:14 -0500 Subject: [PATCH 32/35] Make sets theory solver proof producing (#10751) Adds proofs for theory lemmas from sets for the main theory inference ids (upwards/downwards closure, extensionality, singleton injectivity). Other more specific inferences will be added in followup PRs as needed. --- src/CMakeLists.txt | 2 + src/theory/sets/infer_proof_cons.cpp | 333 +++++++++++++++++++++++ src/theory/sets/infer_proof_cons.h | 109 ++++++++ src/theory/sets/inference_manager.cpp | 23 +- src/theory/sets/inference_manager.h | 3 + src/theory/sets/theory_sets_rewriter.cpp | 4 + 6 files changed, 469 insertions(+), 5 deletions(-) create mode 100644 src/theory/sets/infer_proof_cons.cpp create mode 100644 src/theory/sets/infer_proof_cons.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ba949708ad3..ba8de49f370 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1093,6 +1093,8 @@ libcvc5_add_sources( theory/sep/theory_sep_type_rules.h theory/sets/cardinality_extension.cpp theory/sets/cardinality_extension.h + theory/sets/infer_proof_cons.cpp + theory/sets/infer_proof_cons.h theory/sets/inference_manager.cpp theory/sets/inference_manager.h theory/sets/normal_form.h diff --git a/src/theory/sets/infer_proof_cons.cpp b/src/theory/sets/infer_proof_cons.cpp new file mode 100644 index 00000000000..cb780feabd1 --- /dev/null +++ b/src/theory/sets/infer_proof_cons.cpp @@ -0,0 +1,333 @@ +/****************************************************************************** + * Top contributors (to current version): + * Andrew Reynolds + * + * This file is part of the cvc5 project. + * + * Copyright (c) 2009-2024 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. + * **************************************************************************** + * + * Inference to proof conversion for sets. + */ + +#include "theory/sets/infer_proof_cons.h" + +#include "expr/skolem_manager.h" +#include "proof/proof_node_algorithm.h" +#include "proof/proof_node_manager.h" +#include "proof/theory_proof_step_buffer.h" +#include "theory/builtin/proof_checker.h" +#include "theory/sets/theory_sets_rewriter.h" + +namespace cvc5::internal { +namespace theory { +namespace sets { + +InferProofCons::InferProofCons(Env& env, TheorySetsRewriter* tsr) + : EnvObj(env), d_tsr(tsr), d_imap(userContext()), d_expMap(userContext()) +{ + d_false = nodeManager()->mkConst(false); + d_tid = builtin::BuiltinProofRuleChecker::mkTheoryIdNode(THEORY_SETS); +} + +void InferProofCons::notifyFact(const Node& conc, + const Node& exp, + InferenceId id) +{ + Assert(conc.getKind() != Kind::AND && conc.getKind() != Kind::IMPLIES); + d_imap[conc] = id; + d_expMap[conc] = exp; +} + +void InferProofCons::notifyConflict(const Node& conf, InferenceId id) +{ + d_imap[conf.notNode()] = id; +} + +void InferProofCons::notifyLemma(const Node& lem, InferenceId id) +{ + d_imap[lem] = id; +} + +std::shared_ptr InferProofCons::getProofFor(Node fact) +{ + NodeInferenceMap::iterator it = d_imap.find(fact); + Assert(it != d_imap.end()); + InferenceId id = it->second; + + // temporary proof + CDProof cdp(d_env); + std::vector assumps; + Node conc = fact; + // First split into conclusion and assumptions. + if (fact.getKind() == Kind::IMPLIES || fact.getKind() == Kind::NOT) + { + if (fact[0].getKind() == Kind::AND) + { + assumps.insert(assumps.begin(), fact[0].begin(), fact[0].end()); + } + else + { + assumps.push_back(fact[0]); + } + if (fact.getKind() == Kind::IMPLIES) + { + conc = fact[1]; + } + else + { + conc = d_false; + } + cdp.addStep(fact, ProofRule::SCOPE, {conc}, {assumps}); + } + else + { + NodeExpMap::iterator itex = d_expMap.find(fact); + if (itex != d_expMap.end()) + { + Node exp = itex->second; + if (exp.getKind() == Kind::AND) + { + assumps.insert(assumps.end(), exp.begin(), exp.end()); + } + else + { + assumps.push_back(exp); + } + } + } + // Try to convert. + if (!convert(cdp, id, assumps, conc)) + { + cdp.addTrustedStep(conc, TrustId::THEORY_INFERENCE, assumps, {d_tid}); + } + return cdp.getProofFor(fact); +} + +bool InferProofCons::convert(CDProof& cdp, + InferenceId id, + const std::vector& assumps, + const Node& conc) +{ + // these are handled manually + Assert(id != InferenceId::SETS_PROXY + && id != InferenceId::SETS_PROXY_SINGLETON); + Trace("sets-ipc") << "InferProofCons::convert " << id << std::endl; + Trace("sets-ipc") << "- assumptions: " << assumps << std::endl; + Trace("sets-ipc") << "- conclusion: " << conc << std::endl; + bool success = false; + TheoryProofStepBuffer psb(cdp.getManager()->getChecker(), true); + switch (id) + { + case InferenceId::SETS_DOWN_CLOSURE: + case InferenceId::SETS_MEM_EQ: + case InferenceId::SETS_MEM_EQ_CONFLICT: + { + Assert(assumps.size() >= 1); + Assert(assumps[0].getKind() == Kind::SET_MEMBER); + Assert(assumps.size() == 1 || assumps[1].getKind() == Kind::EQUAL); + // (and (set.member x S) (= S (op T1 T2))) => + // rewrite((set.member x (op T1 T2))) + // this holds by applying the equality as a substitution to the first + // assumption and rewriting. + std::vector exp(assumps.begin() + 1, assumps.end()); + Node aelim = psb.applyPredElim(assumps[0], exp); + success = (aelim == conc); + // should never fail + Assert(success); + } + break; + case InferenceId::SETS_UP_CLOSURE: + case InferenceId::SETS_UP_CLOSURE_2: + { + NodeManager* nm = nodeManager(); + // An example inference is: + // (set.member x A) ^ (set.member y B) ^ (= x y) => (set.member x k) + // where k is the purification skolem for (set.inter A B). + Assert(conc.getKind() == Kind::SET_MEMBER); + Node so = SkolemManager::getUnpurifiedForm(conc[1]); + Trace("sets-ipc") << "Unpurified form " << so << std::endl; + // We first compute the single step rewriting of the conclusion. + // For the above example, memor would be: + // (and (set.member x A) (set.member x B)). + Node memo = nm->mkNode(Kind::SET_MEMBER, conc[0], so); + Node memor = d_tsr->rewriteMembershipBinaryOp(memo); + Trace("sets-ipc") << "Single step rewriting of membership " << memor + << std::endl; + Assert(memo != memor); + // collect the memberships in the premise + std::vector assumpMem; + std::vector assumpOther; + // We now partition the antecedant to the membership + // part (assumpMem) and the substitution part (assumpOther). The + // membership part will be equivalent via rewriting to the conclusion. + for (const Node& a : assumps) + { + Node aa = a.getKind() == Kind::NOT ? a[0] : a; + if (aa.getKind() == Kind::SET_MEMBER) + { + assumpMem.push_back(a); + } + else + { + assumpOther.push_back(a); + } + } + Assert(assumpMem.size() == 1 || assumpMem.size() == 2); + Node msrc; + // Use AND_INTRO to put the memberships together if necessary. + if (assumpMem.size() == 2) + { + msrc = nm->mkAnd(assumpMem); + psb.addStep(ProofRule::AND_INTRO, {assumpMem}, {}, msrc); + } + else + { + msrc = assumpMem[0]; + } + // Now, prove the equivalence between the memberships and the + // conclusion, possibly using the substituion in assumpOther. + bool isOr = (memor.getKind() == Kind::OR); + size_t ntgts = isOr ? 2 : 1; + for (size_t i = 0; i < ntgts; i++) + { + Node mtgt = isOr ? memor[i] : memor; + Trace("sets-ipc") << "...try target " << mtgt << std::endl; + if (psb.applyPredTransform(msrc, mtgt, assumpOther)) + { + success = true; + if (isOr) + { + // if union, we get the desired (left or right) conclusion + success = psb.applyPredIntro(memor, {mtgt}, MethodId::SB_FORMULA); + // should never fail + Assert(success); + } + Trace("sets-ipc") << "......success" << std::endl; + break; + } + } + // If successful, we have proven: + // + // (set.member x A) (set.member y B) + // --------------------------------------- AND_INTRO + // (and (set.member x A) (set.member y B)) (= x y) + // ------------------------------------------------- MACRO_SR_PRED_TRANS + // (set.member x (set.inter A B)) + if (!success) + { + Assert(success); + break; + } + // If successful, go back and show memor holds. + Trace("sets-ipc") << "* Prove transform " << memor << " to " << memo + << std::endl; + if (!psb.applyPredTransform(memor, memo, {})) + { + // should never fail + success = false; + Assert(success); + break; + } + if (so != conc[1]) + { + std::vector ceqs; + Node ceq = conc[0].eqNode(conc[0]); + psb.addStep(ProofRule::REFL, {}, {conc[0]}, ceq); + ceqs.push_back(ceq); + ceq = so.eqNode(conc[1]); + Trace("sets-ipc") << "* Prove equal (by original forms) " << ceq + << std::endl; + if (!psb.addStep(ProofRule::MACRO_SR_PRED_INTRO, {}, {ceq}, ceq)) + { + // should never fail + success = false; + Assert(success); + break; + } + ceqs.push_back(ceq); + std::vector cargs; + Node cequiv = memo.eqNode(conc); + ProofRule cr = expr::getCongRule(memo, cargs); + if (!psb.addStep(cr, ceqs, cargs, cequiv)) + { + // should never fail + success = false; + Assert(success); + break; + } + if (!psb.addStep(ProofRule::EQ_RESOLVE, {memo, cequiv}, {}, conc)) + { + // should never fail + success = false; + Assert(success); + break; + } + } + // Final proof now is,using A^B as shorthand for (set.inter A B): + // + // ----- REFL ---------- MACRO_SR_PRED_INTRO + // ... x = x A^B = k + // ------------------ -------------------------------------- CONG + // (set.member x A^B) (set.member x A^B) = (set.member x k) + // --------------------------------------------------------- EQ_RESOLVE + // (set.member x k) + // + // where ... is the proof from above. + } + break; + case InferenceId::SETS_SKOLEM: + { + Assert(assumps.empty()); + success = psb.applyPredIntro(conc, {}); + Assert(success); + } + break; + case InferenceId::SETS_DEQ: + { + Assert(assumps.size() == 1); + Node exp = assumps[0]; + // ensure we are properly ordered + Assert(exp.getKind() == Kind::NOT && exp[0].getKind() == Kind::EQUAL + && exp[0][0] < exp[0][1]); + Node res = psb.tryStep(ProofRule::SETS_EXT, {exp}, {}, conc); + success = (res == conc); + Assert(success); + } + break; + case InferenceId::SETS_SINGLETON_EQ: + { + // SINGLETON_INJ + Assert(assumps.size() == 1); + Node res = + psb.tryStep(ProofRule::SETS_SINGLETON_INJ, {assumps[0]}, {}, conc); + success = (res == conc); + Assert(success); + } + break; + case InferenceId::SETS_EQ_CONFLICT: + case InferenceId::SETS_EQ_MEM_CONFLICT: + case InferenceId::SETS_EQ_MEM: + default: Trace("sets-ipc") << "Unhandled " << id; break; + } + if (success) + { + if (!cdp.addSteps(psb)) + { + // should not fail if success was computed correctly above + Assert(false); + success = false; + } + } + Trace("sets-ipc") << "...success = " << success << std::endl; + return success; +} + +std::string InferProofCons::identify() const { return "sets::InferProofCons"; } + +} // namespace sets +} // namespace theory +} // namespace cvc5::internal diff --git a/src/theory/sets/infer_proof_cons.h b/src/theory/sets/infer_proof_cons.h new file mode 100644 index 00000000000..abb65c61da7 --- /dev/null +++ b/src/theory/sets/infer_proof_cons.h @@ -0,0 +1,109 @@ +/****************************************************************************** + * Top contributors (to current version): + * Andrew Reynolds + * + * This file is part of the cvc5 project. + * + * Copyright (c) 2009-2024 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. + * **************************************************************************** + * + * Inference to proof conversion for sets. + */ + +#include "cvc5_private.h" + +#ifndef CVC5__THEORY__SETS__INFER_PROOF_CONS_H +#define CVC5__THEORY__SETS__INFER_PROOF_CONS_H + +#include "context/cdhashmap.h" +#include "expr/node.h" +#include "proof/proof.h" +#include "proof/proof_generator.h" +#include "smt/env_obj.h" +#include "theory/inference_id.h" + +namespace cvc5::internal { +namespace theory { +namespace sets { + +class TheorySetsRewriter; + +/** + * A class that is responsible for proofs for sets theory lemmas. Proofs are + * constructed lazily when asked for via getProofFor. This class maintains + * a (user-context-dependent) mapping from formulas that we are responsible + * for proving and the inference identifier that they correspond to. + * + * The main (private) method of this class is convert below, which is + * called when we need to construct a proof node from an InferInfo. + */ +class InferProofCons : protected EnvObj, public ProofGenerator +{ + typedef context::CDHashMap NodeInferenceMap; + typedef context::CDHashMap NodeExpMap; + + public: + InferProofCons(Env& env, TheorySetsRewriter* tsr); + virtual ~InferProofCons() {} + + /** + * This is called to notify that fact was inferred from exp as a fact with + * inference identifier id. + */ + void notifyFact(const Node& conc, const Node& exp, InferenceId id); + /** + * This is called to notify that conf was called as a conflict with inference + * identifier id. + */ + void notifyConflict(const Node& conf, InferenceId id); + /** + * This is called to notify that conf was called as a lemma with inference + * identifier id. + */ + void notifyLemma(const Node& lem, InferenceId id); + + /** + * This returns the proof for fact. This is required for using this class as + * a lazy proof generator. + * + * It should be the case that a call was made to notifyConflict or + * notifyLemma was called on fact. + */ + std::shared_ptr getProofFor(Node fact) override; + /** Identify this generator (for debugging, etc..) */ + virtual std::string identify() const override; + + private: + /** + * Main conversion routine. Ensures there is a proof of conc with free + * assumptions assumps stored in cdp. + * + * @param cdp The proof to add to. + * @param id The inference id of the original lemma or conflict. + * @param assumps The free assumptions (antecendant) of the inference. + * @param conc The conclusion of the inference + * @return true if we successfully added a proof to cdp. + */ + bool convert(CDProof& cdp, + InferenceId id, + const std::vector& assumps, + const Node& conc); + /** The sets rewriter */ + TheorySetsRewriter* d_tsr; + /** Common constants */ + Node d_tid; + Node d_false; + /** Maps formulas to the inference id they were notified with */ + NodeInferenceMap d_imap; + /** Maps conclusions to their explanations */ + NodeExpMap d_expMap; +}; + +} // namespace sets +} // namespace theory +} // namespace cvc5::internal + +#endif /* CVC5__THEORY__SETS__INFER_PROOF_CONS_H */ diff --git a/src/theory/sets/inference_manager.cpp b/src/theory/sets/inference_manager.cpp index 8164dc27035..dafc0e5f883 100644 --- a/src/theory/sets/inference_manager.cpp +++ b/src/theory/sets/inference_manager.cpp @@ -31,7 +31,9 @@ InferenceManager::InferenceManager(Env& env, Theory& t, TheorySetsRewriter* tr, SolverState& s) - : InferenceManagerBuffered(env, t, s, "theory::sets::"), d_state(s) + : InferenceManagerBuffered(env, t, s, "theory::sets::"), + d_state(s), + d_ipc(isProofEnabled() ? new InferProofCons(env, tr) : nullptr) { d_true = nodeManager()->mkConst(true); d_false = nodeManager()->mkConst(false); @@ -118,8 +120,11 @@ bool InferenceManager::assertSetsFact(Node atom, Node exp) { Node conc = polarity ? atom : atom.notNode(); - return assertInternalFact( - atom, polarity, id, ProofRule::TRUST, {exp}, {d_tid, conc, d_tsid}); + if (d_ipc) + { + d_ipc->notifyFact(conc, exp, id); + } + return assertInternalFact(atom, polarity, id, {exp}, d_ipc.get()); } void InferenceManager::assertInference(Node fact, @@ -193,7 +198,11 @@ void InferenceManager::setupAndAddPendingLemma(const Node& exp, { if (conc == d_false) { - TrustNode trn = TrustNode::mkTrustConflict(exp); + if (d_ipc) + { + d_ipc->notifyConflict(exp, id); + } + TrustNode trn = TrustNode::mkTrustConflict(exp, d_ipc.get()); trustedConflict(trn, id); return; } @@ -202,7 +211,11 @@ void InferenceManager::setupAndAddPendingLemma(const Node& exp, { lem = nodeManager()->mkNode(Kind::IMPLIES, exp, conc); } - addPendingLemma(lem, id, LemmaProperty::NONE); + if (d_ipc) + { + d_ipc->notifyLemma(lem, id); + } + addPendingLemma(lem, id, LemmaProperty::NONE, d_ipc.get()); } } // namespace sets diff --git a/src/theory/sets/inference_manager.h b/src/theory/sets/inference_manager.h index e3d794e4fde..e5f8f3bbad3 100644 --- a/src/theory/sets/inference_manager.h +++ b/src/theory/sets/inference_manager.h @@ -19,6 +19,7 @@ #define CVC5__THEORY__SETS__INFERENCE_MANAGER_H #include "theory/inference_manager_buffered.h" +#include "theory/sets/infer_proof_cons.h" #include "theory/sets/solver_state.h" namespace cvc5::internal { @@ -99,6 +100,8 @@ class InferenceManager : public InferenceManagerBuffered * class. */ SolverState& d_state; + /** The inference to proof converter */ + std::unique_ptr d_ipc; /** Assert fact recursive * * This is a helper function for assertInference, which calls assertFact diff --git a/src/theory/sets/theory_sets_rewriter.cpp b/src/theory/sets/theory_sets_rewriter.cpp index dd4b3c70cf1..3b9d56952c5 100644 --- a/src/theory/sets/theory_sets_rewriter.cpp +++ b/src/theory/sets/theory_sets_rewriter.cpp @@ -646,6 +646,10 @@ RewriteResponse TheorySetsRewriter::postRewrite(TNode node) { Node TheorySetsRewriter::rewriteMembershipBinaryOp(const Node& node) { + Assert(node.getKind() == Kind::SET_MEMBER); + Assert(node[1].getKind() == Kind::SET_UNION + || node[1].getKind() == Kind::SET_INTER + || node[1].getKind() == Kind::SET_MINUS); NodeManager* nm = nodeManager(); std::vector children; for (size_t i = 0, nchild = node[1].getNumChildren(); i < nchild; i++) From 858d23c7ac87b1e054c9304ffef77abebff96543 Mon Sep 17 00:00:00 2001 From: Andrew Reynolds Date: Wed, 5 Jun 2024 15:07:48 -0500 Subject: [PATCH 33/35] Eliminate witness from BV invertibility instantiations (#10848) This makes an explicit pass to eliminate WITNESS terms from instantiations in the instantiation strategy from Niemetz et al CAV 2018. The motivation is two-fold: (1) It is work towards eliminating an incorrect dependency on proofs from SkolemManager. Proofs are local to solving instances, while SkolemManager has a lifetime that is equivalent to its containing NodeManager. (2) It will make proofs easier to track from the BV instantiator later, as they now can be given on individual lemmas instead of relying on a central utility. A followup PR will eliminate support for WITNESS in skolem manager and the term formula removal pass. --- src/expr/CMakeLists.txt | 2 + src/expr/elim_witness_converter.cpp | 48 ++++++++++++++++ src/expr/elim_witness_converter.h | 55 +++++++++++++++++++ src/theory/inference_id.cpp | 2 + src/theory/inference_id.h | 3 + .../quantifiers/cegqi/ceg_instantiator.cpp | 31 ++++++++++- .../quantifiers/qbv-disequality3.smt2 | 2 +- 7 files changed, 139 insertions(+), 4 deletions(-) create mode 100644 src/expr/elim_witness_converter.cpp create mode 100644 src/expr/elim_witness_converter.h diff --git a/src/expr/CMakeLists.txt b/src/expr/CMakeLists.txt index 23c4139ce98..1442bf54b7c 100644 --- a/src/expr/CMakeLists.txt +++ b/src/expr/CMakeLists.txt @@ -33,6 +33,8 @@ libcvc5_add_sources( codatatype_bound_variable.h elim_shadow_converter.cpp elim_shadow_converter.h + elim_witness_converter.cpp + elim_witness_converter.h emptyset.cpp emptyset.h emptybag.cpp diff --git a/src/expr/elim_witness_converter.cpp b/src/expr/elim_witness_converter.cpp new file mode 100644 index 00000000000..bc7a9617761 --- /dev/null +++ b/src/expr/elim_witness_converter.cpp @@ -0,0 +1,48 @@ +/****************************************************************************** + * Top contributors (to current version): + * Andrew Reynolds + * + * This file is part of the cvc5 project. + * + * Copyright (c) 2009-2024 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. + * **************************************************************************** + * + * Implementation of witness elimination node conversion + */ + +#include "expr/elim_witness_converter.h" + +#include "expr/skolem_manager.h" + +namespace cvc5::internal { + +ElimWitnessNodeConverter::ElimWitnessNodeConverter(Env& env) + : EnvObj(env), NodeConverter(nodeManager()) +{ +} + +Node ElimWitnessNodeConverter::postConvert(Node n) +{ + if (n.getKind() == Kind::WITNESS) + { + Trace("elim-witness") << "Eliminate " << n << std::endl; + NodeManager* nm = nodeManager(); + SkolemManager* skm = nm->getSkolemManager(); + std::vector nchildren(n.begin(), n.end()); + Node exists = nm->mkNode(Kind::EXISTS, nchildren); + Node k = skm->mkSkolemFunction(SkolemId::QUANTIFIERS_SKOLEMIZE, + {exists, n[0][0]}); + d_exists.push_back(exists); + return k; + } + return n; +} +const std::vector& ElimWitnessNodeConverter::getExistentials() const +{ + return d_exists; +} + +} // namespace cvc5::internal diff --git a/src/expr/elim_witness_converter.h b/src/expr/elim_witness_converter.h new file mode 100644 index 00000000000..fce8f373b67 --- /dev/null +++ b/src/expr/elim_witness_converter.h @@ -0,0 +1,55 @@ +/****************************************************************************** + * Top contributors (to current version): + * Andrew Reynolds + * + * This file is part of the cvc5 project. + * + * Copyright (c) 2009-2024 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. + * **************************************************************************** + * + * Implementation of witness elimination node conversion + */ +#include "cvc5_private.h" + +#ifndef CVC5__EXPR__ELIM_WITNESS_NODE_CONVERTER_H +#define CVC5__EXPR__ELIM_WITNESS_NODE_CONVERTER_H + +#include + +#include "expr/node.h" +#include "expr/node_converter.h" +#include "smt/env_obj.h" + +namespace cvc5::internal { + +/** + * Node converter to eliminate all terms of kind WITNESS. Each term replaced + * in this way is captured by an existential, which can be obtained by + * getExistentials. + */ +class ElimWitnessNodeConverter : protected EnvObj, public NodeConverter +{ + public: + /** Eliminate witness terms.*/ + ElimWitnessNodeConverter(Env& env); + ~ElimWitnessNodeConverter() {} + /** + * Convert node n as described above during post-order traversal. + */ + Node postConvert(Node n) override; + /** + * Get the existentials + */ + const std::vector& getExistentials() const; + + private: + /** The list of existentials introduced by eliminating witness */ + std::vector d_exists; +}; + +} // namespace cvc5::internal + +#endif diff --git a/src/theory/inference_id.cpp b/src/theory/inference_id.cpp index 38f0176860c..ac17405238f 100644 --- a/src/theory/inference_id.cpp +++ b/src/theory/inference_id.cpp @@ -346,6 +346,8 @@ const char* toString(InferenceId i) case InferenceId::QUANTIFIERS_GT_PURIFY: return "QUANTIFIERS_GT_PURIFY"; case InferenceId::QUANTIFIERS_TDB_DEQ_CONG: return "QUANTIFIERS_TDB_DEQ_CONG"; + case InferenceId::QUANTIFIERS_CEGQI_WITNESS: + return "QUANTIFIERS_CEGQI_WITNESS"; case InferenceId::SEP_PTO_NEG_PROP: return "SEP_PTO_NEG_PROP"; case InferenceId::SEP_PTO_PROP: return "SEP_PTO_PROP"; diff --git a/src/theory/inference_id.h b/src/theory/inference_id.h index 97fa41c88b7..afefcf36b0d 100644 --- a/src/theory/inference_id.h +++ b/src/theory/inference_id.h @@ -486,6 +486,9 @@ enum class InferenceId // when term indexing discovers disequal congruent terms in the master // equality engine QUANTIFIERS_TDB_DEQ_CONG, + // An existential corresponding to a witness term generated based on BV + // invertibility conditions. + QUANTIFIERS_CEGQI_WITNESS, //-------------------------------------- end quantifiers theory // ---------------------------------- sep theory diff --git a/src/theory/quantifiers/cegqi/ceg_instantiator.cpp b/src/theory/quantifiers/cegqi/ceg_instantiator.cpp index 9843a969fd7..3ff814e7d5b 100644 --- a/src/theory/quantifiers/cegqi/ceg_instantiator.cpp +++ b/src/theory/quantifiers/cegqi/ceg_instantiator.cpp @@ -32,6 +32,7 @@ #include "theory/quantifiers/term_database.h" #include "theory/quantifiers/term_util.h" #include "theory/rewriter.h" +#include "expr/elim_witness_converter.h" #include "util/rational.h" using namespace std; @@ -975,24 +976,48 @@ bool CegInstantiator::doAddInstantiation(std::vector& vars, } Trace("cegqi-inst-debug") << "Do the instantiation...." << std::endl; + // construct the final instantiation by eliminating witness terms + std::vector svec; + std::vector exists; + for (const Node& s : subs) + { + if (expr::hasSubtermKind(Kind::WITNESS, s)) + { + ElimWitnessNodeConverter ewc(d_env); + Node sc = ewc.convert(s); + const std::vector& wexists = ewc.getExistentials(); + exists.insert(exists.end(), wexists.begin(), wexists.end()); + svec.push_back(sc); + } + else + { + svec.push_back(s); + } + } + Assert(!d_quant.isNull()); // check if we need virtual term substitution (if used delta or infinity) VtsTermCache* vtc = d_treg.getVtsTermCache(); - bool usedVts = vtc->containsVtsTerm(subs, false); + bool usedVts = vtc->containsVtsTerm(svec, false); Instantiate* inst = d_qim.getInstantiate(); // if doing partial quantifier elimination, record the instantiation and set // the incomplete flag instead of sending instantiation lemma if (d_qreg.getQuantAttributes().isQuantElimPartial(d_quant)) { - inst->recordInstantiation(d_quant, subs, usedVts); + inst->recordInstantiation(d_quant, svec, usedVts); return true; } else if (inst->addInstantiation(d_quant, - subs, + svec, InferenceId::QUANTIFIERS_INST_CEGQI, Node::null(), usedVts)) { + // add the existentials, if any witness term was eliminated + for (const Node& q : exists) + { + d_qim.addPendingLemma(q, InferenceId::QUANTIFIERS_CEGQI_WITNESS); + } return true; } // this should never happen for monotonic selection strategies diff --git a/test/regress/cli/regress1/quantifiers/qbv-disequality3.smt2 b/test/regress/cli/regress1/quantifiers/qbv-disequality3.smt2 index 78f5b7c883e..6eff0223e8c 100644 --- a/test/regress/cli/regress1/quantifiers/qbv-disequality3.smt2 +++ b/test/regress/cli/regress1/quantifiers/qbv-disequality3.smt2 @@ -1,4 +1,4 @@ -; COMMAND-LINE: --cegqi-bv --cegqi-bv-ineq=keep --no-cegqi-full +; COMMAND-LINE: --cegqi-bv --cegqi-bv-ineq=keep ; EXPECT: sat (set-logic BV) (set-info :status sat) From 142680488dbd1177d0146ff13acaddd71fbe8403 Mon Sep 17 00:00:00 2001 From: Andrew Reynolds Date: Wed, 5 Jun 2024 16:38:43 -0500 Subject: [PATCH 34/35] Add mode for if and when to try THEORY_REWRITE in RARE proof reconstruction (#10826) This change is required to avoid circular dependencies if a THEORY_REWRITE can be used to justify its own subgoals. With this change, we disable use of THEORY_REWRITE in the RARE reconstruction when elaborating the subgoals of theory rewrites, specifically those marked with id MACRO_THEORY_REWRITE_RCONS_SIMPLE. It also simplifies the interface by removing deprecated debugging info. --- src/proof/trust_id.cpp | 3 +- src/proof/trust_id.h | 7 ++- src/rewriter/basic_rewrite_rcons.cpp | 62 +++++++++++--------- src/rewriter/basic_rewrite_rcons.h | 37 +++++++----- src/rewriter/rewrite_db_proof_cons.cpp | 19 +++--- src/rewriter/rewrite_db_proof_cons.h | 11 ++-- src/smt/proof_post_processor_dsl.cpp | 40 ++++++++++++- src/smt/proof_post_processor_dsl.h | 2 + src/theory/booleans/theory_bool_rewriter.cpp | 4 +- src/theory/theory_rewriter.cpp | 13 ++++ src/theory/theory_rewriter.h | 3 + 11 files changed, 138 insertions(+), 63 deletions(-) diff --git a/src/proof/trust_id.cpp b/src/proof/trust_id.cpp index b6f5eaf0dc6..d540c678bdb 100644 --- a/src/proof/trust_id.cpp +++ b/src/proof/trust_id.cpp @@ -47,7 +47,8 @@ const char* toString(TrustId id) case TrustId::SUBTYPE_ELIMINATION: return "SUBTYPE_ELIMINATION"; case TrustId::MACRO_THEORY_REWRITE_RCONS: return "MACRO_THEORY_REWRITE_RCONS"; - case TrustId::MACRO_BOOL_NNF_NORM_RCONS: return "MACRO_BOOL_NNF_NORM_RCONS"; + case TrustId::MACRO_THEORY_REWRITE_RCONS_SIMPLE: + return "MACRO_THEORY_REWRITE_RCONS_SIMPLE"; default: return "TrustId::Unknown"; }; } diff --git a/src/proof/trust_id.h b/src/proof/trust_id.h index e12dd5d3f68..4c686ff53f0 100644 --- a/src/proof/trust_id.h +++ b/src/proof/trust_id.h @@ -68,8 +68,11 @@ enum class TrustId : uint32_t SUBTYPE_ELIMINATION, /** A rewrite required for showing a macro theory rewrite */ MACRO_THEORY_REWRITE_RCONS, - /** A rewrite required for the macro Bool NNF theory rewrite */ - MACRO_BOOL_NNF_NORM_RCONS, + /** + * A rewrite required for showing a macro theory rewrite that should not + * require the use of theory rewrites to prove. + */ + MACRO_THEORY_REWRITE_RCONS_SIMPLE, }; /** Converts a trust id to a string. */ const char* toString(TrustId id); diff --git a/src/rewriter/basic_rewrite_rcons.cpp b/src/rewriter/basic_rewrite_rcons.cpp index 76451af0ea8..648726f1a0e 100644 --- a/src/rewriter/basic_rewrite_rcons.cpp +++ b/src/rewriter/basic_rewrite_rcons.cpp @@ -35,22 +35,31 @@ using namespace cvc5::internal::kind; namespace cvc5::internal { namespace rewriter { +std::ostream& operator<<(std::ostream& os, TheoryRewriteMode tm) +{ + switch (tm) + { + case TheoryRewriteMode::STANDARD: return os << "STANDARD"; + case TheoryRewriteMode::RESORT: return os << "RESORT"; + case TheoryRewriteMode::NEVER: return os << "NEVER"; + } + Unreachable(); + return os; +} + BasicRewriteRCons::BasicRewriteRCons(Env& env) : EnvObj(env) { - d_isDslStrict = (options().proof.proofGranularityMode - == options::ProofGranularityMode::DSL_REWRITE_STRICT); + } bool BasicRewriteRCons::prove(CDProof* cdp, Node a, Node b, - theory::TheoryId tid, - MethodId mid, - std::vector>& subgoals) + std::vector>& subgoals, + TheoryRewriteMode tmode) { Node eq = a.eqNode(b); - Trace("trewrite-rcons") << "Reconstruct " << eq << " (from " << tid << ", " - << mid << ")" << std::endl; + Trace("trewrite-rcons") << "Reconstruct " << eq << std::endl; Node lhs = eq[0]; Node rhs = eq[1]; // this probably should never happen @@ -68,7 +77,7 @@ bool BasicRewriteRCons::prove(CDProof* cdp, } // try theory rewrite (pre-rare) - if (!d_isDslStrict) + if (tmode == TheoryRewriteMode::STANDARD) { if (tryTheoryRewrite(cdp, eq, theory::TheoryRewriteCtx::PRE_DSL, subgoals)) { @@ -85,22 +94,21 @@ bool BasicRewriteRCons::postProve( CDProof* cdp, Node a, Node b, - theory::TheoryId tid, - MethodId mid, - std::vector>& subgoals) + std::vector>& subgoals, + TheoryRewriteMode tmode) { Node eq = a.eqNode(b); // try theory rewrite (post-rare), which may try both pre and post if // the proof-granularity mode is dsl-rewrite-strict. bool success = false; - if (d_isDslStrict) + if (tmode == TheoryRewriteMode::RESORT) { if (tryTheoryRewrite(cdp, eq, theory::TheoryRewriteCtx::PRE_DSL, subgoals)) { success = true; } } - if (!success + if (!success && tmode != TheoryRewriteMode::NEVER && tryTheoryRewrite( cdp, eq, theory::TheoryRewriteCtx::POST_DSL, subgoals)) { @@ -118,20 +126,6 @@ bool BasicRewriteRCons::postProve( return success; } -void BasicRewriteRCons::ensureProofForEncodeTransform(CDProof* cdp, - const Node& eq, - const Node& eqi) -{ - ProofRewriteDbNodeConverter rdnc(d_env); - std::shared_ptr pfn = rdnc.convert(eq); - Node equiv = eq.eqNode(eqi); - Assert(pfn->getResult() == equiv); - cdp->addProof(pfn); - Node equivs = eqi.eqNode(eq); - cdp->addStep(equivs, ProofRule::SYMM, {equiv}, {}); - cdp->addStep(eq, ProofRule::EQ_RESOLVE, {eqi, equivs}, {}); -} - bool BasicRewriteRCons::tryRule(CDProof* cdp, Node eq, ProofRule r, @@ -154,6 +148,20 @@ bool BasicRewriteRCons::tryRule(CDProof* cdp, return false; } +void BasicRewriteRCons::ensureProofForEncodeTransform(CDProof* cdp, + const Node& eq, + const Node& eqi) +{ + ProofRewriteDbNodeConverter rdnc(d_env); + std::shared_ptr pfn = rdnc.convert(eq); + Node equiv = eq.eqNode(eqi); + Assert(pfn->getResult() == equiv); + cdp->addProof(pfn); + Node equivs = eqi.eqNode(eq); + cdp->addStep(equivs, ProofRule::SYMM, {equiv}, {}); + cdp->addStep(eq, ProofRule::EQ_RESOLVE, {eqi, equivs}, {}); +} + void BasicRewriteRCons::ensureProofForTheoryRewrite( CDProof* cdp, ProofRewriteRule id, diff --git a/src/rewriter/basic_rewrite_rcons.h b/src/rewriter/basic_rewrite_rcons.h index ea558dd93f6..cf039f589ad 100644 --- a/src/rewriter/basic_rewrite_rcons.h +++ b/src/rewriter/basic_rewrite_rcons.h @@ -31,6 +31,22 @@ namespace cvc5::internal { namespace rewriter { +/** + * Mode for if/when to try THEORY_REWRITE. + */ +enum class TheoryRewriteMode +{ + // Attempt to use the theory rewrites based on their TheoryRewriteCtx setting. + STANDARD, + // Only resort to theory rewrites after trying RARE rewrites. + RESORT, + // Do not try theory rewrites. + NEVER, +}; + +/** Print a TheoryRewriteMode to an output stream */ +std::ostream& operator<<(std::ostream& os, TheoryRewriteMode tm); + /** * The module for basic (non-DSL-dependent) automatic reconstructing proofs of * THEORY_REWRITE steps. It handles special cases that are independent @@ -48,18 +64,16 @@ class BasicRewriteRCons : protected EnvObj * @param cdp The proof to add to. * @param a The left hand side of the equality. * @param b The left hand side of the equality. - * @param tid The theory that was the source of the rewrite (if any). - * @param tid The method that was the source of the rewrite (if any). * @param subgoals The list of proofs introduced when proving eq that * are trusted steps. + * @param tmode Determines if/when to try THEORY_REWRITE. * @return true if we successfully added a proof of (= a b) to cdp. */ bool prove(CDProof* cdp, Node a, Node b, - theory::TheoryId tid, - MethodId mid, - std::vector>& subgoals); + std::vector>& subgoals, + TheoryRewriteMode tmode); /** * There are theory rewrites which cannot be expressed in RARE rules. In this * case we need to use proof rules which are not written in RARE. It is only @@ -68,18 +82,16 @@ class BasicRewriteRCons : protected EnvObj * @param cdp The proof to add to. * @param a The left hand side of the equality. * @param b The left hand side of the equality. - * @param tid The theory that was the source of the rewrite (if any). - * @param tid The method that was the source of the rewrite (if any). * @param subgoals The list of proofs introduced when proving eq that * are trusted steps. + * @param tmode Determines if/when to try THEORY_REWRITE. * @return true if we successfully added a proof of (= a b) to cdp. */ bool postProve(CDProof* cdp, Node a, Node b, - theory::TheoryId tid, - MethodId mid, - std::vector>& subgoals); + std::vector>& subgoals, + TheoryRewriteMode tmode); /** * Add to cdp a proof of eq from free asumption eqi, where eqi is the result * of term conversion via RewriteDbNodeConverter. @@ -108,11 +120,6 @@ class BasicRewriteRCons : protected EnvObj std::vector>& subgoals); private: - /** - * Is proof-granularity set to dsl-rewrite-strict? This impacts when - * THEORY_REWRITE are tried. - */ - bool d_isDslStrict; /** * Try rule r, return true if eq could be proven by r with arguments args. * If this method returns true, a proof of eq was added to cdp. diff --git a/src/rewriter/rewrite_db_proof_cons.cpp b/src/rewriter/rewrite_db_proof_cons.cpp index 022e63960cc..2132028eb93 100644 --- a/src/rewriter/rewrite_db_proof_cons.cpp +++ b/src/rewriter/rewrite_db_proof_cons.cpp @@ -58,12 +58,12 @@ bool RewriteDbProofCons::prove( CDProof* cdp, const Node& a, const Node& b, - theory::TheoryId tid, - MethodId mid, int64_t recLimit, int64_t stepLimit, - std::vector>& subgoals) + std::vector>& subgoals, + TheoryRewriteMode tmode) { + d_tmode = tmode; // clear the proof caches d_pcache.clear(); // clear the evaluate cache @@ -115,7 +115,7 @@ bool RewriteDbProofCons::prove( Trace("rpc-debug") << "- prove basic" << std::endl; // first, try with the basic utility bool success = false; - if (d_trrc.prove(cdp, eq[0], eq[1], tid, mid, subgoals)) + if (d_trrc.prove(cdp, eq[0], eq[1], subgoals, tmode)) { Trace("rpc") << "...success (basic)" << std::endl; success = true; @@ -155,7 +155,7 @@ bool RewriteDbProofCons::prove( if (!success) { // now try the "post-prove" method as a last resort - if (d_trrc.postProve(cdp, eq[0], eq[1], tid, mid, subgoals)) + if (d_trrc.postProve(cdp, eq[0], eq[1], subgoals, tmode)) { Trace("rpc") << "...success (post-prove basic)" << std::endl; success = true; @@ -257,10 +257,13 @@ RewriteProofStatus RewriteDbProofCons::proveInternalViaStrategy(const Node& eqi) } // Maybe holds via a THEORY_REWRITE that has been marked with // TheoryRewriteCtx::DSL_SUBCALL. - if (proveWithRule( - RewriteProofStatus::THEORY_REWRITE, eqi, {}, {}, false, false, true)) + if (d_tmode==TheoryRewriteMode::STANDARD) { - return RewriteProofStatus::THEORY_REWRITE; + if (proveWithRule( + RewriteProofStatus::THEORY_REWRITE, eqi, {}, {}, false, false, true)) + { + return RewriteProofStatus::THEORY_REWRITE; + } } Trace("rpc-debug2") << "...not proved via builtin tactic" << std::endl; d_currRecLimit--; diff --git a/src/rewriter/rewrite_db_proof_cons.h b/src/rewriter/rewrite_db_proof_cons.h index 414dc62a8e6..8b3c229c75b 100644 --- a/src/rewriter/rewrite_db_proof_cons.h +++ b/src/rewriter/rewrite_db_proof_cons.h @@ -64,24 +64,21 @@ class RewriteDbProofCons : protected EnvObj * @param cdp The object to add the proof of (= a b) to. * @param a The left hand side of the equality. * @param b The right hand side of the equality. - * @param tid The theory identifier responsible for the rewrite, if one - * exists. - * @param mid The method id (the kind of rewrite) * @param recLimit The recursion limit for this call. * @param stepLimit The step limit for this call. * @param subgoals The list of proofs introduced when proving (= a b) that * are trusted steps, and thus would require further elaboration from the * caller of this method. + * @param tmode Determines if/when to try THEORY_REWRITE. * @return true if we successfully added a proof of (= a b) to cdp */ bool prove(CDProof* cdp, const Node& a, const Node& b, - theory::TheoryId tid, - MethodId mid, int64_t recLimit, int64_t stepLimit, - std::vector>& subgoals); + std::vector>& subgoals, + TheoryRewriteMode tmode); private: /** @@ -304,6 +301,8 @@ class RewriteDbProofCons : protected EnvObj uint64_t d_currRecLimit; /** current step recursion limit */ uint64_t d_currStepLimit; + /** The mode for if/when to try theory rewrites */ + rewriter::TheoryRewriteMode d_tmode; /** current rule we are applying to fixed point */ ProofRewriteRule d_currFixedPointId; /** current substitution from fixed point */ diff --git a/src/smt/proof_post_processor_dsl.cpp b/src/smt/proof_post_processor_dsl.cpp index e2c73668b53..ad4ec7ac137 100644 --- a/src/smt/proof_post_processor_dsl.cpp +++ b/src/smt/proof_post_processor_dsl.cpp @@ -28,6 +28,10 @@ ProofPostprocessDsl::ProofPostprocessDsl(Env& env, rewriter::RewriteDb* rdb) : EnvObj(env), d_rdbPc(env, rdb) { d_true = NodeManager::currentNM()->mkConst(true); + d_tmode = (options().proof.proofGranularityMode + == options::ProofGranularityMode::DSL_REWRITE_STRICT) + ? rewriter::TheoryRewriteMode::RESORT + : rewriter::TheoryRewriteMode::STANDARD; } void ProofPostprocessDsl::reconstruct( @@ -41,16 +45,46 @@ void ProofPostprocessDsl::reconstruct( { pnu.process(p); } - if (!d_subgoals.empty()) + // We run until subgoals are empty. Note that this loop is only expected + // to run once, and moreover is guaranteed to run only once if the only + // trusted steps added have id MACRO_THEORY_REWRITE_RCONS_SIMPLE. However, + // in rare cases, an elaboration may require adding a trust step that itself + // expects to require theory rewrites to prove (MACRO_THEORY_REWRITE_RCONS) + // in which case this loop may run twice. We manually limit this loop to + // run no more than 2 times. + size_t iter = 0; + while (!d_subgoals.empty()) { + iter++; + if (iter >= 3) + { + // prevent any accidental infinite loops + break; + } std::vector> sgs = d_subgoals; Trace("pp-dsl") << "Also reconstruct proofs for " << sgs.size() << " subgoals..." << std::endl; d_subgoals.clear(); + // Do not use theory rewrites to fill in remaining subgoals. This prevents + // generating subgoals in proofs of subgoals. + rewriter::TheoryRewriteMode mprev = d_tmode; + TrustId tid; for (std::shared_ptr p : sgs) { + // determine if we should disable theory rewrites, this is the case if the + // trust id is MACRO_THEORY_REWRITE_RCONS_SIMPLE. + d_tmode = mprev; + if (p->getRule() == ProofRule::TRUST) + { + getTrustId(p->getArguments()[0], tid); + if (tid == TrustId::MACRO_THEORY_REWRITE_RCONS_SIMPLE) + { + d_tmode = rewriter::TheoryRewriteMode::NEVER; + } + } pnu.process(p); } + d_tmode = mprev; } // should never construct a subgoal for a step from a subgoal if (!d_subgoals.empty()) @@ -103,13 +137,15 @@ bool ProofPostprocessDsl::update(Node res, builtin::BuiltinProofRuleChecker::getTheoryId(args[1], tid); getMethodId(args[2], mid); } + Trace("pp-dsl") << "Prove " << res << " from " << tid << " / " << mid + << ", in mode " << d_tmode << std::endl; int64_t recLimit = options().proof.proofRewriteRconsRecLimit; int64_t stepLimit = options().proof.proofRewriteRconsStepLimit; // Attempt to reconstruct the proof of the equality into cdp using the // rewrite database proof reconstructor. // We record the subgoals in d_subgoals. if (d_rdbPc.prove( - cdp, res[0], res[1], tid, mid, recLimit, stepLimit, d_subgoals)) + cdp, res[0], res[1], recLimit, stepLimit, d_subgoals, d_tmode)) { // If we made (= res true) above, conclude the original res. if (reqTrueElim) diff --git a/src/smt/proof_post_processor_dsl.h b/src/smt/proof_post_processor_dsl.h index cf01f20712a..6a8267449ab 100644 --- a/src/smt/proof_post_processor_dsl.h +++ b/src/smt/proof_post_processor_dsl.h @@ -62,6 +62,8 @@ class ProofPostprocessDsl : protected EnvObj, public ProofNodeUpdaterCallback Node d_true; /** The rewrite database proof generator */ rewriter::RewriteDbProofCons d_rdbPc; + /** The default mode for if/when to try theory rewrites */ + rewriter::TheoryRewriteMode d_tmode; /** The accumulated subgoals from calls to d_rdbPc */ std::vector> d_subgoals; }; diff --git a/src/theory/booleans/theory_bool_rewriter.cpp b/src/theory/booleans/theory_bool_rewriter.cpp index bf08cf5a42f..5484fbba8c0 100644 --- a/src/theory/booleans/theory_bool_rewriter.cpp +++ b/src/theory/booleans/theory_bool_rewriter.cpp @@ -183,7 +183,7 @@ Node TheoryBoolRewriter::computeNnfNorm(NodeManager* nm, if (preCur != cur) { pg->addRewriteStep( - cur, preCur, nullptr, true, TrustId::MACRO_BOOL_NNF_NORM_RCONS); + cur, preCur, nullptr, true, TrustId::MACRO_THEORY_REWRITE_RCONS_SIMPLE); } } } @@ -255,7 +255,7 @@ Node TheoryBoolRewriter::computeNnfNorm(NodeManager* nm, if (pcpc != ret) { pg->addRewriteStep( - pcpc, ret, nullptr, false, TrustId::MACRO_BOOL_NNF_NORM_RCONS); + pcpc, ret, nullptr, false, TrustId::MACRO_THEORY_REWRITE_RCONS_SIMPLE); } } visited[cur] = ret; diff --git a/src/theory/theory_rewriter.cpp b/src/theory/theory_rewriter.cpp index df50f042089..bdafe844011 100644 --- a/src/theory/theory_rewriter.cpp +++ b/src/theory/theory_rewriter.cpp @@ -20,6 +20,18 @@ namespace cvc5::internal { namespace theory { +std::ostream& operator<<(std::ostream& os, TheoryRewriteCtx trc) +{ + switch (trc) + { + case TheoryRewriteCtx::PRE_DSL: return os << "PRE_DSL"; + case TheoryRewriteCtx::DSL_SUBCALL: return os << "DSL_SUBCALL"; + case TheoryRewriteCtx::POST_DSL: return os << "POST_DSL"; + } + Unreachable(); + return os; +} + std::ostream& operator<<(std::ostream& os, RewriteStatus rs) { switch (rs) @@ -103,6 +115,7 @@ void TheoryRewriter::registerProofRewriteRule(ProofRewriteRule id, { std::unordered_set& rules = d_pfTheoryRewrites[ctx]; rules.insert(id); + // theory rewrites marked DSL_SUBCALL are also tried at PRE_DSL effort. if (ctx == TheoryRewriteCtx::DSL_SUBCALL) { d_pfTheoryRewrites[TheoryRewriteCtx::PRE_DSL].insert(id); diff --git a/src/theory/theory_rewriter.h b/src/theory/theory_rewriter.h index 2603a515cd5..61dc27ca0b2 100644 --- a/src/theory/theory_rewriter.h +++ b/src/theory/theory_rewriter.h @@ -45,6 +45,9 @@ enum class TheoryRewriteCtx POST_DSL, }; +/** Print a TheoryRewriteCtx to an output stream */ +std::ostream& operator<<(std::ostream& os, TheoryRewriteCtx trc); + /** * Theory rewriters signal whether more rewriting is needed (or not) * by using a member of this enumeration. See RewriteResponse, below. From 62aa67e353b3c510546c297d022531bc56230a65 Mon Sep 17 00:00:00 2001 From: Andrew Reynolds Date: Wed, 5 Jun 2024 18:09:46 -0500 Subject: [PATCH 35/35] Fix case where substitution fails to elaborate (#10845) This eliminates the 1 occurrence of SUBS_NO_ELABORATE from our regressions. This should fix all further cases where substitutions fail to be elaborated. --- src/smt/proof_post_processor.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/smt/proof_post_processor.cpp b/src/smt/proof_post_processor.cpp index e1c9c701f3a..e0a9fddb6bf 100644 --- a/src/smt/proof_post_processor.cpp +++ b/src/smt/proof_post_processor.cpp @@ -791,6 +791,11 @@ Node ProofPostprocessCallback::expandMacros(ProofRule id, { // substitutions are pre-rewrites tcpg.addRewriteStep(vvec[j], svec[j], pgs[j], true); + if (ida == MethodId::SBA_FIXPOINT) + { + // fixed point substitutions are also post-rewrites + tcpg.addRewriteStep(vvec[j], svec[j], pgs[j], false); + } } // add the proof constructed by the term conversion utility std::shared_ptr pfn = tcpg.getProofForRewriting(t);