3
0
Fork 0
mirror of https://github.com/Z3Prover/z3 synced 2025-04-10 19:27:06 +00:00
z3/lib/qe_sat_tactic.cpp
Leonardo de Moura e9eab22e5c Z3 sources
Signed-off-by: Leonardo de Moura <leonardo@microsoft.com>
2012-10-02 11:35:25 -07:00

707 lines
26 KiB
C++

/*++
Copyright (c) 2012 Microsoft Corporation
Module Name:
qe_sat_tactic.cpp
Abstract:
Procedure for quantifier satisfiability using quantifier projection.
Based on generalizations by Bjorner & Monniaux
(see tvm\papers\z3qe\altqe.tex)
Author:
Nikolaj Bjorner (nbjorner) 2012-02-24
Revision History:
--*/
#include "qe_sat_tactic.h"
#include "quant_hoist.h"
#include "ast_pp.h"
#include "smt_solver.h"
#include "qe.h"
#include "cooperate.h"
#include "model_v2_pp.h"
#include "expr_replacer.h"
#include "th_rewriter.h"
#include "expr_context_simplifier.h"
// plugin registration.
// solver specific projection operators.
// connect goals to tactic
namespace qe {
class is_relevant_default : public i_expr_pred {
public:
bool operator()(expr* e) {
return true;
}
};
class mk_atom_default : public i_nnf_atom {
public:
virtual void operator()(expr* e, bool pol, expr_ref& result) {
if (pol) result = e;
else result = result.get_manager().mk_not(e);
}
};
class sat_tactic : public tactic {
// forall x . not forall y . not forall z . not forall u . fml.
ast_manager& m;
expr_ref m_false;
volatile bool m_cancel;
front_end_params m_fparams;
params_ref m_params;
unsigned m_extrapolate_strategy_param;
bool m_projection_mode_param;
bool m_strong_context_simplify_param;
bool m_ctx_simplify_local_param;
vector<app_ref_vector> m_vars;
ptr_vector<smt::solver> m_solvers;
smt::solver m_solver;
expr_ref m_fml;
expr_ref_vector m_Ms;
expr_ref_vector m_assignments;
is_relevant_default m_is_relevant;
mk_atom_default m_mk_atom;
th_rewriter m_rewriter;
expr_strong_context_simplifier m_ctx_rewriter;
class solver_context : public i_solver_context {
ast_manager& m;
sat_tactic& m_super;
smt::solver& m_solver;
atom_set m_pos;
atom_set m_neg;
app_ref_vector m_vars;
expr_ref m_fml;
ptr_vector<contains_app> m_contains_app;
bool m_projection_mode_param;
public:
solver_context(sat_tactic& s, unsigned idx):
m(s.m),
m_super(s),
m_solver(*s.m_solvers[idx+1]),
m_vars(m),
m_fml(m),
m_projection_mode_param(true) {}
virtual ~solver_context() {
std::for_each(m_contains_app.begin(), m_contains_app.end(), delete_proc<contains_app>());
}
void init(expr* fml, unsigned idx) {
m_fml = fml;
for (unsigned j = 0; j < m_super.vars(idx).size(); ++j) {
add_var(m_super.vars(idx)[j]);
}
get_nnf(m_fml, get_is_relevant(), get_mk_atom(), m_pos, m_neg);
}
void set_projection_mode(bool p) { m_projection_mode_param = p; }
ast_manager& get_manager() { return m; }
expr* fml() { return m_fml; }
// set of atoms in current formula.
virtual atom_set const& pos_atoms() const { return m_pos; }
virtual atom_set const& neg_atoms() const { return m_neg; }
// Access current set of variables to solve
virtual unsigned get_num_vars() const { return m_vars.size(); }
virtual app* get_var(unsigned idx) const { return m_vars[idx]; }
virtual app*const* get_vars() const { return m_vars.c_ptr(); }
virtual bool is_var(expr* e, unsigned& idx) const {
for (unsigned i = 0; i < m_vars.size(); ++i) {
if (e == m_vars[i]) return (idx = i, true);
}
return false;
}
virtual contains_app& contains(unsigned idx) { return *m_contains_app[idx]; }
// callback to replace variable at index 'idx' with definition 'def' and updated formula 'fml'
virtual void elim_var(unsigned idx, expr* fml, expr* def) {
m_fml = fml;
m_pos.reset();
m_neg.reset();
get_nnf(m_fml, get_is_relevant(), get_mk_atom(), m_pos, m_neg);
m_vars.erase(idx);
dealloc(m_contains_app[idx]);
m_contains_app.erase(m_contains_app.c_ptr() + idx);
}
// callback to add new variable to branch.
virtual void add_var(app* x) {
m_vars.push_back(x);
m_contains_app.push_back(alloc(contains_app, m, x));
}
// callback to add constraints in branch.
virtual void add_constraint(bool use_var, expr* l1 = 0, expr* l2 = 0, expr* l3 = 0) {
ptr_buffer<expr> args;
if (l1) args.push_back(l1);
if (l2) args.push_back(l2);
if (l3) args.push_back(l3);
expr_ref cnstr(m.mk_or(args.size(), args.c_ptr()), m);
m_solver.assert_expr(cnstr);
TRACE("qe", tout << "add_constraint " << mk_pp(cnstr,m) << "\n";);
}
// eliminate finite domain variable 'var' from fml.
virtual void blast_or(app* var, expr_ref& fml) {
expr_ref result(m);
expr_quant_elim qelim(m, m_super.m_fparams);
qe::mk_exists(1, &var, fml);
qelim(m.mk_true(), fml, result);
fml = result;
TRACE("qe", tout << mk_pp(var, m) << " " << mk_pp(fml, m) << "\n";);
}
void project_var_partial(unsigned i) {
app* x = get_var(i);
m_super.check_success(has_plugin(x));
qe_solver_plugin& p = plugin(m.get_sort(x)->get_family_id());
model_ref model;
m_solver.get_model(model);
m_super.check_success(p.project(contains(i), model, m_fml));
m_super.m_rewriter(m_fml);
TRACE("qe", model_v2_pp(tout, *model); tout << "\n";
tout << mk_pp(m_fml, m) << "\n";);
elim_var(i, m_fml, 0);
}
void project_var_full(unsigned i) {
expr_ref result(m);
app* x = get_var(i);
expr_quant_elim qelim(m, m_super.m_fparams);
qe::mk_exists(1, &x, m_fml);
qelim(m.mk_true(), m_fml, result);
m_fml = result;
m_super.m_rewriter(m_fml);
TRACE("qe", tout << mk_pp(m_fml, m) << "\n";);
elim_var(i, m_fml, 0);
}
void project_var(unsigned i) {
if (m_projection_mode_param) {
project_var_full(i);
}
else {
project_var_partial(i);
}
}
};
public:
sat_tactic(ast_manager& m, params_ref const& p = params_ref()):
m(m),
m_false(m.mk_false(), m),
m_cancel(false),
m_params(p),
m_extrapolate_strategy_param(0),
m_projection_mode_param(true),
m_strong_context_simplify_param(true),
m_ctx_simplify_local_param(false),
m_fml(m),
m_Ms(m),
m_assignments(m),
m_rewriter(m),
m_ctx_rewriter(m_fparams, m),
m_solver(m, m_fparams) {
m_fparams.m_model = true;
}
virtual tactic * translate(ast_manager & m) {
return alloc(sat_tactic, m);
}
~sat_tactic() {
for (unsigned i = 0; i < m_solvers.size(); ++i) {
dealloc(m_solvers[i]);
}
}
virtual void set_cancel(bool f) {
m_cancel = f;
// not thread-safe when solvers are reset.
// TBD: lock - this, reset() and init_Ms.
for (unsigned i = 0; i < m_solvers.size(); ++i) {
m_solvers[i]->set_cancel(f);
}
m_solver.set_cancel(f);
m_ctx_rewriter.set_cancel(f);
}
virtual void operator()(
goal_ref const& goal,
goal_ref_buffer& result,
model_converter_ref& mc,
proof_converter_ref & pc,
expr_dependency_ref& core)
{
checkpoint();
reset();
ptr_vector<expr> fmls;
goal->get_formulas(fmls);
m_fml = m.mk_and(fmls.size(), fmls.c_ptr());
TRACE("qe", tout << "input: " << mk_pp(m_fml,m) << "\n";);
simplify_rewriter_star rw(m);
expr_ref tmp(m);
rw(m_fml, tmp);
m_fml = tmp;
TRACE("qe", tout << "reduced: " << mk_pp(m_fml,m) << "\n";);
skolemize_existential_prefix();
extract_alt_form(m_fml);
model_ref model;
expr_ref res = qt(0, m.mk_true(), model);
goal->inc_depth();
if (m.is_false(res)) {
goal->assert_expr(res);
}
else {
goal->reset();
// equi-satisfiable. What to do with model?
mc = model2model_converter(&*model);
}
result.push_back(goal.get());
}
virtual void collect_statistics(statistics & st) const {
for (unsigned i = 0; i < m_solvers.size(); ++i) {
m_solvers[i]->collect_statistics(st);
}
m_solver.collect_statistics(st);
m_ctx_rewriter.collect_statistics(st);
}
virtual void reset_statistics() {
for (unsigned i = 0; i < m_solvers.size(); ++i) {
m_solvers[i]->reset_statistics();
}
m_solver.reset_statistics();
m_ctx_rewriter.reset_statistics();
}
virtual void cleanup() {}
virtual void updt_params(params_ref const & p) {
m_extrapolate_strategy_param = p.get_uint(":extrapolate-strategy", m_extrapolate_strategy_param);
m_projection_mode_param = p.get_bool(":projection-mode", m_projection_mode_param);
m_strong_context_simplify_param = p.get_bool(":strong-context-simplify", m_strong_context_simplify_param);
m_ctx_simplify_local_param = p.get_bool(":strong-context-simplify-local", m_ctx_simplify_local_param);
}
virtual void collect_param_descrs(param_descrs & r) {
r.insert(":extrapolate-strategy",CPK_UINT, "(default: 0 trivial extrapolation) 1 - nnf strengthening 2 - smt-test 3 - nnf_weakening");
r.insert(":projection-mode", CPK_BOOL, "(default: true - full) false - partial quantifier instantiation");
r.insert(":strong-context-simplify", CPK_BOOL, "(default: true) use strong context simplifier on result of quantifier elimination");
r.insert(":strong-context-simplify-local", CPK_BOOL, "(default: false) use strong context simplifier locally on the new formula only");
}
private:
unsigned num_alternations() const { return m_vars.size(); }
void init_Ms() {
for (unsigned i = 0; i < num_alternations(); ++i) {
m_Ms.push_back(m.mk_true());
m_solvers.push_back(alloc(smt::solver, m, m_fparams, m_params));
}
m_Ms.push_back(m_fml);
m_solvers.push_back(alloc(smt::solver, m, m_fparams, m_params));
m_solvers.back()->assert_expr(m_fml);
}
expr* M(unsigned i) { return m_Ms[i].get(); }
app_ref_vector const& vars(unsigned i) { return m_vars[i]; }
smt::solver& solver(unsigned i) { return *m_solvers[i]; }
void reset() {
m_fml = 0;
m_Ms.reset();
m_vars.reset();
}
void skolemize_existential_prefix() {
quantifier_hoister hoist(m);
expr_ref result(m);
app_ref_vector vars(m);
hoist.pull_exists(m_fml, vars, result);
m_fml = result;
}
//
// fa x ex y fa z . phi
// fa x ! fa y ! fa z ! (!phi)
//
void extract_alt_form(expr* fml) {
quantifier_hoister hoist(m);
expr_ref result(m);
bool is_fa;
unsigned parity = 0;
m_fml = fml;
while (true) {
app_ref_vector vars(m);
hoist(m_fml, vars, is_fa, result);
if (vars.empty()) {
break;
}
SASSERT(((parity & 0x1) == 0) == is_fa);
++parity;
TRACE("qe", tout << "Hoist " << (is_fa?"fa":"ex") << "\n";
for (unsigned i = 0; i < vars.size(); ++i) {
tout << mk_pp(vars[i].get(), m) << " ";
}
tout << "\n";
tout << mk_pp(result, m) << "\n";);
m_vars.push_back(vars);
m_fml = result;
}
//
// negate formula if the last quantifier is universal.
// so that normal form fa x ! fa y ! fa z ! psi
// is obtained.
//
if ((parity & 0x1) == 1) {
m_fml = m.mk_not(m_fml);
}
init_Ms();
checkpoint();
}
/**
\brief Main quantifier test algorithm loop.
*/
expr_ref qt(unsigned i, expr* ctx, model_ref& model) {
model_ref model1;
while (true) {
IF_VERBOSE(1, verbose_stream() << "qt " << i << "\n";);
TRACE("qe",
tout << i << " " << mk_pp(ctx, m) << "\n";
display(tout););
checkpoint();
if (is_sat(i, ctx, model)) {
expr_ref ctxM = extrapolate(i, ctx, M(i));
if (i == num_alternations()) {
return ctxM;
}
expr_ref res = qt(i+1, ctxM, model1);
if (m.is_false(res)) {
return ctxM;
}
project(i, res);
}
else {
return m_false;
}
}
UNREACHABLE();
return expr_ref(m);
}
/**
\brief compute extrapolant
Assume A & B is sat.
Compute C, such that
1. A & C is sat,
2. not B & C is unsat.
(C strengthens B and is still satisfiable with A).
*/
expr_ref extrapolate(unsigned i, expr* A, expr* B) {
switch(m_extrapolate_strategy_param) {
case 0: return trivial_extrapolate(i, A, B);
case 1: return nnf_strengthening_extrapolate(i, A, B);
case 2: return smt_test_extrapolate(i, A, B);
case 3: return nnf_weakening_extrapolate(i, A, B);
default: return trivial_extrapolate(i, A, B);
}
}
expr_ref trivial_extrapolate(unsigned i, expr* A, expr* B) {
return expr_ref(B, m);
}
expr_ref conjunction_extrapolate(unsigned i, expr* A, expr* B) {
return expr_ref(m.mk_and(A, B), m);
}
/**
Set C = nnf(B), That is, C is the NNF version of B.
For each literal in C in order, replace the literal by False and
check the conditions for extrapolation:
1. not B & C is unsat,
2. A & C is sat.
The first check holds by construction, so it is redundant.
The second is not redundant.
Instead of replacing literals in an NNF formula,
one simply asserts the negation of that literal.
*/
expr_ref nnf_strengthening_extrapolate(unsigned i, expr* A, expr* B) {
expr_ref Bnnf(B, m);
atom_set pos, neg;
get_nnf(Bnnf, m_is_relevant, m_mk_atom, pos, neg);
expr_substitution sub(m);
remove_duplicates(pos, neg);
// Assumption: B is already asserted in solver[i].
smt::solver& solver = *m_solvers[i];
solver.push();
solver.assert_expr(A);
nnf_strengthen(solver, pos, m.mk_false(), sub);
nnf_strengthen(solver, neg, m.mk_true(), sub);
solver.pop(1);
scoped_ptr<expr_replacer> replace = mk_default_expr_replacer(m);
replace->set_substitution(&sub);
(*replace)(Bnnf);
m_rewriter(Bnnf);
TRACE("qe",
tout << "A: " << mk_pp(A, m) << "\n";
tout << "B: " << mk_pp(B, m) << "\n";
tout << "B': " << mk_pp(Bnnf.get(), m) << "\n";
// solver.display(tout);
);
DEBUG_CODE(
solver.push();
solver.assert_expr(m.mk_not(B));
solver.assert_expr(Bnnf);
lbool is_sat = solver.check();
TRACE("qe", tout << "is sat: " << is_sat << "\n";);
SASSERT(is_sat == l_false);
solver.pop(1););
DEBUG_CODE(
solver.push();
solver.assert_expr(A);
solver.assert_expr(Bnnf);
lbool is_sat = solver.check();
TRACE("qe", tout << "is sat: " << is_sat << "\n";);
SASSERT(is_sat == l_true);
solver.pop(1););
return Bnnf;
}
void nnf_strengthen(smt::solver& solver, atom_set& atoms, expr* value, expr_substitution& sub) {
atom_set::iterator it = atoms.begin(), end = atoms.end();
for (; it != end; ++it) {
solver.push();
solver.assert_expr(m.mk_iff(*it, value));
lbool is_sat = solver.check();
solver.pop(1);
if (is_sat == l_true) {
TRACE("qe", tout << "consistent with: " << mk_pp(*it, m) << " " << mk_pp(value, m) << "\n";);
sub.insert(*it, value);
solver.assert_expr(m.mk_iff(*it, value));
}
checkpoint();
}
}
void remove_duplicates(atom_set& pos, atom_set& neg) {
ptr_vector<app> to_delete;
atom_set::iterator it = pos.begin(), end = pos.end();
for (; it != end; ++it) {
if (neg.contains(*it)) {
to_delete.push_back(*it);
}
}
for (unsigned j = 0; j < to_delete.size(); ++j) {
pos.remove(to_delete[j]);
neg.remove(to_delete[j]);
}
}
/**
Set C = nnf(B), That is, C is the NNF version of B.
For each literal in C in order, replace the literal by True and
check the conditions for extrapolation
1. not B & C is unsat,
2. A & C is sat.
The second holds by construction and is redundant.
The first is not redundant.
*/
expr_ref nnf_weakening_extrapolate(unsigned i, expr* A, expr* B) {
expr_ref Bnnf(B, m);
atom_set pos, neg;
get_nnf(Bnnf, m_is_relevant, m_mk_atom, pos, neg);
remove_duplicates(pos, neg);
expr_substitution sub(m);
m_solver.push();
m_solver.assert_expr(A);
m_solver.assert_expr(m.mk_not(B));
nnf_weaken(m_solver, Bnnf, pos, m.mk_true(), sub);
nnf_weaken(m_solver, Bnnf, neg, m.mk_false(), sub);
scoped_ptr<expr_replacer> replace = mk_default_expr_replacer(m);
replace->set_substitution(&sub);
(*replace)(Bnnf);
m_rewriter(Bnnf);
m_solver.pop(1);
return Bnnf;
}
void nnf_weaken(smt::solver& solver, expr_ref& B, atom_set& atoms, expr* value, expr_substitution& sub) {
atom_set::iterator it = atoms.begin(), end = atoms.end();
for (; it != end; ++it) {
solver.push();
expr_ref fml = B;
mk_default_expr_replacer(m)->apply_substitution(*it, value, fml);
solver.assert_expr(fml);
if (solver.check() == l_false) {
sub.insert(*it, value);
B = fml;
}
solver.pop(1);
checkpoint();
}
}
/**
Use the model for A & B to extrapolate.
Initially, C is conjunction of literals from B
that are in model of A & B.
The model is a conjunction of literals.
Let us denote this set $C$. We see that the conditions
for extrapolation are satisfied. Furthermore,
C can be generalized by removing literals
from C as long as !B & A & C is unsatisfiable.
*/
expr_ref smt_test_extrapolate(unsigned i, expr* A, expr* B) {
expr_ref_vector proxies(m), core(m);
obj_map<expr, expr*> proxy_map;
checkpoint();
m_solver.push();
m_solver.assert_expr(m.mk_not(B));
for (unsigned i = 0; i < m_assignments.size(); ++i) {
proxies.push_back(m.mk_fresh_const("proxy",m.mk_bool_sort()));
proxy_map.insert(proxies.back(), m_assignments[i].get());
m_solver.assert_expr(m.mk_iff(proxies.back(), m_assignments[i].get()));
TRACE("qe", tout << "assignment: " << mk_pp(m_assignments[i].get(), m) << "\n";);
}
lbool is_sat = m_solver.check(proxies.size(), proxies.c_ptr());
SASSERT(is_sat == l_false);
unsigned core_size = m_solver.get_unsat_core_size();
for (unsigned i = 0; i < core_size; ++i) {
core.push_back(proxy_map.find(m_solver.get_unsat_core_expr(i)));
}
expr_ref result(m.mk_and(core.size(), core.c_ptr()), m);
TRACE("qe", tout << "core: " << mk_pp(result, m) << "\n";
tout << mk_pp(A, m) << "\n";
tout << mk_pp(B, m) << "\n";);
m_solver.pop(1);
return result;
}
/**
\brief project vars(idx) from fml relative to M(idx).
*/
void project(unsigned idx, expr* _fml) {
SASSERT(idx < num_alternations());
expr_ref fml(_fml, m);
if (m_strong_context_simplify_param && m_ctx_simplify_local_param) {
m_ctx_rewriter.push();
m_ctx_rewriter.assert(M(idx));
m_ctx_rewriter(fml);
m_ctx_rewriter.pop();
TRACE("qe", tout << mk_pp(_fml, m) << "\n-- context simplify -->\n" << mk_pp(fml, m) << "\n";);
}
solver_context ctx(*this, idx);
ctx.add_plugin(mk_arith_plugin(ctx, false, m_fparams));
ctx.add_plugin(mk_bool_plugin(ctx));
ctx.add_plugin(mk_bv_plugin(ctx));
ctx.init(fml, idx);
ctx.set_projection_mode(m_projection_mode_param);
m_solvers[idx+1]->push();
while (ctx.get_num_vars() > 0) {
VERIFY(l_true == m_solvers[idx+1]->check());
ctx.project_var(ctx.get_num_vars()-1);
}
m_solvers[idx+1]->pop(1);
expr_ref not_fml(m.mk_not(ctx.fml()), m);
m_rewriter(not_fml);
if (m_strong_context_simplify_param && !m_ctx_simplify_local_param) {
m_ctx_rewriter.push();
m_ctx_rewriter.assert(M(idx));
m_ctx_rewriter(not_fml);
m_ctx_rewriter.pop();
}
expr_ref tmp(m.mk_and(M(idx), not_fml), m);
m_rewriter(tmp);
m_Ms[idx] = tmp;
m_solvers[idx]->assert_expr(not_fml);
TRACE("qe",
tout << mk_pp(fml, m) << "\n--->\n";
tout << mk_pp(tmp, m) << "\n";);
}
void checkpoint() {
if (m_cancel) {
throw tactic_exception(TACTIC_CANCELED_MSG);
}
cooperate("qe-sat");
}
void check_success(bool ok) {
if (!ok) {
TRACE("qe", tout << "check failed\n";);
throw tactic_exception(TACTIC_CANCELED_MSG);
}
}
bool is_sat(unsigned i, expr* ctx, model_ref& model) {
smt::solver& solver = *m_solvers[i];
solver.push();
solver.assert_expr(ctx);
lbool r = solver.check();
m_assignments.reset();
solver.get_assignments(m_assignments);
solver.pop(1);
check_success(r != l_undef);
if (r == l_true && i == 0) solver.get_model(model);
return r == l_true;
}
void display(std::ostream& out) {
bool is_fa = true;
for (unsigned i = 0; i < num_alternations(); ++i) {
out << (is_fa?"fa ":"ex ");
for (unsigned j = 0; j < vars(i).size(); ++j) {
out << mk_pp(m_vars[i][j].get(), m) << " ";
}
out << "\n";
out << mk_pp(m_Ms[i+1].get(), m) << "\n";
is_fa = !is_fa;
}
}
};
tactic * mk_sat_tactic(ast_manager& m, params_ref const& p) {
return alloc(sat_tactic, m, p);
}
};