Skip to content

Commit 4e093d1

Browse files
Share the quantified expression via a let-binding in the Z3 workaround
The Z3 quantifier workaround emits the equivalence between the declared Boolean and its quantified definition as two implications. Writing the quantified expression out in both implications doubles its size in the SMT2 output, which can be significant for large quantified terms. Bind it once with an SMT-LIB `let` and reference the binding from both implications instead. This was verified to still work around Z3's solve_eqs elimination (Z3Prover/z3#7743) on the affected Z3 versions: get-value returns a plain Boolean rather than the quantified expression. The smt2_convt unit test is adjusted to expect the let-bound form. Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
1 parent 2543aaf commit 4e093d1

2 files changed

Lines changed: 20 additions & 25 deletions

File tree

‎src/solvers/smt2/smt2_conv.cpp‎

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -990,19 +990,18 @@ literalt smt2_convt::convert(const exprt &expr)
990990
// preprocessor eliminates B by substituting the quantified <expr>, and
991991
// then hands the eliminated form back through get-value (see
992992
// Z3Prover/z3#7743). Asserting the equivalence as two implications
993-
// instead prevents this. The underlying issue is fixed in Z3 >= 4.17
994-
// (which also exposes (set-option :smt.solve_eqs.non_ground false)), but
995-
// neither is widely available yet, so we keep the workaround for Z3.
996-
out << "(assert (=> ";
997-
convert_literal(l);
998-
out << ' ';
999-
convert_expr(prepared_expr);
1000-
out << "))\n";
1001-
out << "(assert (=> ";
993+
// instead prevents this; a let-binding shares the (possibly large)
994+
// quantified expression so that it is emitted only once. The underlying
995+
// issue is fixed in Z3 >= 4.17 (which also exposes
996+
// (set-option :smt.solve_eqs.non_ground false)), but neither is widely
997+
// available yet, so we keep the workaround for Z3.
998+
out << "(assert (let ((?def ";
1002999
convert_expr(prepared_expr);
1003-
out << ' ';
1000+
out << ")) (and (=> ";
10041001
convert_literal(l);
1005-
out << "))\n";
1002+
out << " ?def) (=> ?def ";
1003+
convert_literal(l);
1004+
out << "))))\n";
10061005
}
10071006
else
10081007
{

‎unit/solvers/smt2/smt2_conv.cpp‎

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -227,24 +227,20 @@ TEST_CASE("smt2_convt quantifier definition encoding", "[core][solvers][smt2]")
227227
const std::string out = convert_handle(quantified, smt2_convt::solvert::Z3);
228228
INFO("SMT2 output:\n" << out);
229229

230-
THEN("it is declared and constrained by two implications, in order")
230+
THEN("it is declared and constrained by a let-bound equivalence")
231231
{
232232
// A single `(assert (= B0 <quantifier>))` would be undone by Z3's
233233
// solve_eqs preprocessor (Z3Prover/z3#7743), so the equivalence is
234-
// emitted as two implications, B0 => <q> followed by <q> => B0.
234+
// emitted as two implications. A let-binding shares the quantified
235+
// expression so that it is written only once.
235236
const std::string declare = "(declare-fun B0 () Bool)";
236-
const std::string impl1 = "(assert (=> B0 " + quant + "))";
237-
const std::string impl2 = "(assert (=> " + quant + " B0))";
238-
239-
const auto p_declare = out.find(declare);
240-
const auto p_impl1 = out.find(impl1);
241-
const auto p_impl2 = out.find(impl2);
242-
243-
REQUIRE(p_declare != std::string::npos);
244-
REQUIRE(p_impl1 != std::string::npos);
245-
REQUIRE(p_impl2 != std::string::npos);
246-
REQUIRE(p_declare < p_impl1);
247-
REQUIRE(p_impl1 < p_impl2);
237+
const std::string assertion =
238+
"(assert (let ((?def " + quant + ")) (and (=> B0 ?def) (=> ?def B0))))";
239+
240+
REQUIRE(out.find(declare) != std::string::npos);
241+
REQUIRE(out.find(assertion) != std::string::npos);
242+
// the quantified expression is emitted exactly once
243+
REQUIRE(out.find(quant) == out.rfind(quant));
248244
// no plain equality definition
249245
REQUIRE(out.find("(assert (= B0 ") == std::string::npos);
250246
}

0 commit comments

Comments
 (0)