Skip to content

Commit e92bda3

Browse files
committed
Work around Z3 not producing models for some quantified expressions
Until the Z3 bug-fix (see Z3Prover/z3#7743) is available widely work around the problem that Z3's preprocessing introduces by adding extra symbols. This may cause solving to take longer, to be determined empirically. Fixes: #8679
1 parent 6b6c8d3 commit e92bda3

File tree

2 files changed

+99
-73
lines changed

2 files changed

+99
-73
lines changed

src/solvers/smt2/smt2_conv.cpp

Lines changed: 98 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ smt2_convt::smt2_convt(
6767
use_datatypes(false),
6868
use_lambda_for_array(false),
6969
emit_set_logic(true),
70+
quantifier_as_defined_expr(false),
7071
ns(_ns),
7172
out(_out),
7273
benchmark(_benchmark),
@@ -136,6 +137,7 @@ smt2_convt::smt2_convt(
136137
use_lambda_for_array = true;
137138
emit_set_logic = false;
138139
use_datatypes = true;
140+
quantifier_as_defined_expr = true;
139141
break;
140142
}
141143

@@ -888,16 +890,6 @@ void smt2_convt::convert_address_of_rec(
888890
expr.id_string());
889891
}
890892

891-
static bool has_quantifier(const exprt &expr)
892-
{
893-
bool result = false;
894-
expr.visit_post([&result](const exprt &node) {
895-
if(node.id() == ID_exists || node.id() == ID_forall)
896-
result = true;
897-
});
898-
return result;
899-
}
900-
901893
literalt smt2_convt::convert(const exprt &expr)
902894
{
903895
PRECONDITION(expr.is_boolean());
@@ -928,28 +920,16 @@ literalt smt2_convt::convert(const exprt &expr)
928920
// Note that here we are always converting, so we do not need to consider
929921
// other literal kinds, only "|B###|"
930922

931-
// Z3 refuses get-value when a defined symbol contains a quantifier.
932-
if(has_quantifier(prepared_expr))
933-
{
934-
out << "(declare-fun ";
935-
convert_literal(l);
936-
out << " () Bool)\n";
937-
out << "(assert (= ";
938-
convert_literal(l);
939-
out << ' ';
940-
convert_expr(prepared_expr);
941-
out << "))\n";
942-
}
943-
else
944-
{
945-
auto identifier =
946-
convert_identifier(std::string{"B"} + std::to_string(l.var_no()));
923+
auto identifier =
924+
convert_identifier(std::string{"B"} + std::to_string(l.var_no()));
925+
// we might already have put expr in defined_expressions via
926+
// prepare_for_convert_expr
927+
if(defined_expressions.find(expr) == defined_expressions.end())
947928
defined_expressions[expr] = identifier;
948-
smt2_identifiers.insert(identifier);
949-
out << "(define-fun " << identifier << " () Bool ";
950-
convert_expr(prepared_expr);
951-
out << ")\n";
952-
}
929+
smt2_identifiers.insert(identifier);
930+
out << "(define-fun " << identifier << " () Bool ";
931+
convert_expr(prepared_expr);
932+
out << ")\n";
953933

954934
return l;
955935
}
@@ -2439,36 +2419,50 @@ void smt2_convt::convert_expr(const exprt &expr)
24392419
else if(expr.id()==ID_forall ||
24402420
expr.id()==ID_exists)
24412421
{
2442-
const quantifier_exprt &quantifier_expr = to_quantifier_expr(expr);
2422+
bool already_converted = false;
2423+
if(quantifier_as_defined_expr)
2424+
{
2425+
defined_expressionst::const_iterator it = defined_expressions.find(expr);
2426+
if(it != defined_expressions.end())
2427+
{
2428+
already_converted = true;
2429+
out << it->second;
2430+
}
2431+
}
24432432

2444-
if(solver==solvert::MATHSAT)
2445-
// NOLINTNEXTLINE(readability/throw)
2446-
throw "MathSAT does not support quantifiers";
2433+
if(!already_converted)
2434+
{
2435+
const quantifier_exprt &quantifier_expr = to_quantifier_expr(expr);
24472436

2448-
if(quantifier_expr.id() == ID_forall)
2449-
out << "(forall ";
2450-
else if(quantifier_expr.id() == ID_exists)
2451-
out << "(exists ";
2437+
if(solver == solvert::MATHSAT)
2438+
// NOLINTNEXTLINE(readability/throw)
2439+
throw "MathSAT does not support quantifiers";
2440+
2441+
if(quantifier_expr.id() == ID_forall)
2442+
out << "(forall ";
2443+
else if(quantifier_expr.id() == ID_exists)
2444+
out << "(exists ";
24522445

2453-
out << '(';
2454-
bool first = true;
2455-
for(const auto &bound : quantifier_expr.variables())
2456-
{
2457-
if(first)
2458-
first = false;
2459-
else
2460-
out << ' ';
24612446
out << '(';
2462-
convert_expr(bound);
2463-
out << ' ';
2464-
convert_type(bound.type());
2465-
out << ')';
2466-
}
2467-
out << ") ";
2447+
bool first = true;
2448+
for(const auto &bound : quantifier_expr.variables())
2449+
{
2450+
if(first)
2451+
first = false;
2452+
else
2453+
out << ' ';
2454+
out << '(';
2455+
convert_expr(bound);
2456+
out << ' ';
2457+
convert_type(bound.type());
2458+
out << ')';
2459+
}
2460+
out << ") ";
24682461

2469-
convert_expr(quantifier_expr.where());
2462+
convert_expr(quantifier_expr.where());
24702463

2471-
out << ')';
2464+
out << ')';
2465+
}
24722466
}
24732467
else if(
24742468
const auto object_size = expr_try_dynamic_cast<object_size_exprt>(expr))
@@ -5137,30 +5131,61 @@ void smt2_convt::find_symbols(const exprt &expr)
51375131

51385132
if(expr.id() == ID_exists || expr.id() == ID_forall)
51395133
{
5134+
if(
5135+
quantifier_as_defined_expr &&
5136+
defined_expressions.find(expr) != defined_expressions.end())
5137+
{
5138+
return;
5139+
}
5140+
51405141
std::unordered_map<irep_idt, std::optional<identifiert>> shadowed_syms;
51415142

51425143
// do not declare the quantified symbol, but record
51435144
// as 'bound symbol'
51445145
const auto &q_expr = to_quantifier_expr(expr);
5145-
for(const auto &symbol : q_expr.variables())
5146-
{
5147-
const auto identifier = symbol.get_identifier();
5148-
auto id_entry =
5149-
identifier_map.insert({identifier, identifiert{symbol.type(), true}});
5150-
shadowed_syms.insert(
5151-
{identifier,
5152-
id_entry.second ? std::nullopt
5153-
: std::optional{id_entry.first->second}});
5154-
}
5155-
find_symbols(q_expr.where());
5156-
for(const auto &[id, shadowed_val] : shadowed_syms)
5157-
{
5158-
auto previous_entry = identifier_map.find(id);
5159-
if(!shadowed_val.has_value())
5160-
identifier_map.erase(previous_entry);
5161-
else
5162-
previous_entry->second = std::move(*shadowed_val);
5146+
if(!quantifier_as_defined_expr)
5147+
{
5148+
for(const auto &symbol : q_expr.variables())
5149+
{
5150+
const auto identifier = symbol.get_identifier();
5151+
auto id_entry =
5152+
identifier_map.insert({identifier, identifiert{symbol.type(), true}});
5153+
shadowed_syms.insert(
5154+
{identifier,
5155+
id_entry.second ? std::nullopt
5156+
: std::optional{id_entry.first->second}});
5157+
}
5158+
find_symbols(q_expr.where());
5159+
for(const auto &[id, shadowed_val] : shadowed_syms)
5160+
{
5161+
auto previous_entry = identifier_map.find(id);
5162+
if(!shadowed_val.has_value())
5163+
identifier_map.erase(previous_entry);
5164+
else
5165+
previous_entry->second = std::move(*shadowed_val);
5166+
}
5167+
}
5168+
else
5169+
{
5170+
find_symbols(q_expr.where());
5171+
5172+
const irep_idt id =
5173+
"quantified_expr." + std::to_string(defined_expressions.size());
5174+
5175+
out << "; the following is a workaround for solvers removing quantified "
5176+
"expression during preprocessing\n";
5177+
out << "(declare-fun " << id << " () Bool)\n";
5178+
5179+
out << "(assert (=> " << id << ' ';
5180+
convert_expr(expr);
5181+
out << "))\n";
5182+
out << "(assert (=> ";
5183+
convert_expr(expr);
5184+
out << ' ' << id << "))\n";
5185+
5186+
defined_expressions[expr] = id;
51635187
}
5188+
51645189
return;
51655190
}
51665191

src/solvers/smt2/smt2_conv.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ class smt2_convt : public stack_decision_proceduret
7171
bool use_datatypes;
7272
bool use_lambda_for_array;
7373
bool emit_set_logic;
74+
bool quantifier_as_defined_expr;
7475

7576
exprt handle(const exprt &expr) override;
7677
void set_to(const exprt &expr, bool value) override;

0 commit comments

Comments
 (0)