3
0
Fork 0
mirror of https://github.com/Z3Prover/z3 synced 2025-07-19 19:02:02 +00:00

spacer: counterexample to pushing (ctp)

Enable using fixedpoint.spacer.ctp=true

For each lemma L currently at level k, keep a model M that justifies
why L cannot be pushed to (k+1). L is not pushed while the model M
remains valid.
This commit is contained in:
Arie Gurfinkel 2018-05-22 14:45:05 -07:00
parent 95d820196b
commit 55126692c9
3 changed files with 123 additions and 62 deletions

View file

@ -200,5 +200,6 @@ def_module_params('fixedpoint',
('spacer.p3.share_lemmas', BOOL, False, 'Share frame lemmas'), ('spacer.p3.share_lemmas', BOOL, False, 'Share frame lemmas'),
('spacer.p3.share_invariants', BOOL, False, "Share invariants lemmas"), ('spacer.p3.share_invariants', BOOL, False, "Share invariants lemmas"),
('spacer.from_level', UINT, 0, 'starting level to explore'), ('spacer.from_level', UINT, 0, 'starting level to explore'),
('spacer.print_json', SYMBOL, '', 'print pobs tree in JSON format to a given file') ('spacer.print_json', SYMBOL, '', 'print pobs tree in JSON format to a given file'),
('spacer.ctp', BOOL, False, 'enable counterexample-to-pushing technique'),
)) ))

View file

@ -443,7 +443,7 @@ lemma::lemma (ast_manager &manager, expr * body, unsigned lvl) :
m_ref_count(0), m(manager), m_ref_count(0), m(manager),
m_body(body, m), m_cube(m), m_body(body, m), m_cube(m),
m_zks(m), m_bindings(m), m_lvl(lvl), m_zks(m), m_bindings(m), m_lvl(lvl),
m_pob(nullptr), m_external(false) { m_pob(nullptr), m_ctp(nullptr), m_external(false) {
SASSERT(m_body); SASSERT(m_body);
normalize(m_body, m_body); normalize(m_body, m_body);
} }
@ -452,7 +452,7 @@ lemma::lemma(pob_ref const &p) :
m_ref_count(0), m(p->get_ast_manager()), m_ref_count(0), m(p->get_ast_manager()),
m_body(m), m_cube(m), m_body(m), m_cube(m),
m_zks(m), m_bindings(m), m_lvl(p->level()), m_zks(m), m_bindings(m), m_lvl(p->level()),
m_pob(p), m_external(false) { m_pob(p), m_ctp(nullptr), m_external(false) {
SASSERT(m_pob); SASSERT(m_pob);
m_pob->get_skolems(m_zks); m_pob->get_skolems(m_zks);
add_binding(m_pob->get_binding()); add_binding(m_pob->get_binding());
@ -463,7 +463,7 @@ lemma::lemma(pob_ref const &p, expr_ref_vector &cube, unsigned lvl) :
m(p->get_ast_manager()), m(p->get_ast_manager()),
m_body(m), m_cube(m), m_body(m), m_cube(m),
m_zks(m), m_bindings(m), m_lvl(p->level()), m_zks(m), m_bindings(m), m_lvl(p->level()),
m_pob(p), m_external(false) m_pob(p), m_ctp(nullptr), m_external(false)
{ {
if (m_pob) { if (m_pob) {
m_pob->get_skolems(m_zks); m_pob->get_skolems(m_zks);
@ -687,11 +687,15 @@ void pred_transformer::collect_statistics(statistics& st) const
// -- number of proof obligations (0 if pobs are not reused) // -- number of proof obligations (0 if pobs are not reused)
st.update("SPACER num pobs", m_pobs.size()); st.update("SPACER num pobs", m_pobs.size());
st.update("SPACER num ctp", m_stats.m_num_ctp);
st.update("SPACER num is_invariant", m_stats.m_num_is_invariant);
// -- time in rule initialization // -- time in rule initialization
st.update ("time.spacer.init_rules.pt.init", m_initialize_watch.get_seconds ()); st.update ("time.spacer.init_rules.pt.init", m_initialize_watch.get_seconds ());
// -- time is must_reachable() // -- time is must_reachable()
st.update ("time.spacer.solve.pt.must_reachable", st.update ("time.spacer.solve.pt.must_reachable",
m_must_reachable_watch.get_seconds ()); m_must_reachable_watch.get_seconds ());
st.update("time.spacer.ctp", m_ctp_watch.get_seconds());
} }
void pred_transformer::reset_statistics() void pred_transformer::reset_statistics()
@ -701,6 +705,7 @@ void pred_transformer::reset_statistics()
m_stats.reset(); m_stats.reset();
m_initialize_watch.reset (); m_initialize_watch.reset ();
m_must_reachable_watch.reset (); m_must_reachable_watch.reset ();
m_ctp_watch.reset();
} }
void pred_transformer::init_sig() void pred_transformer::init_sig()
@ -781,20 +786,31 @@ reach_fact *pred_transformer::get_used_origin_reach_fact (model_evaluator_util&
return res; return res;
} }
datalog::rule const* pred_transformer::find_rule(model &model, const datalog::rule *pred_transformer::find_rule(model &model) {
expr_ref val(m);
for (auto &entry : m_tag2rule) {
app *tag = to_app(entry.m_key);
if (model.eval(tag->get_decl(), val) && m.is_true(val)) {
return entry.m_value;
}
}
return nullptr;
}
const datalog::rule *pred_transformer::find_rule(model &model,
bool& is_concrete, bool& is_concrete,
vector<bool>& reach_pred_used, vector<bool>& reach_pred_used,
unsigned& num_reuse_reach) unsigned& num_reuse_reach)
{ {
typedef obj_map<expr, datalog::rule const*> tag2rule;
TRACE ("spacer_verbose", TRACE ("spacer_verbose",
datalog::rule_manager& rm = ctx.get_datalog_context().get_rule_manager(); datalog::rule_manager& rm = ctx.get_datalog_context().get_rule_manager();
tag2rule::iterator it = m_tag2rule.begin(); for (auto &entry : m_tag2rule) {
tag2rule::iterator end = m_tag2rule.end(); expr* pred = entry.m_key;
for (; it != end; ++it) {
expr* pred = it->m_key;
tout << mk_pp(pred, m) << ":\n"; tout << mk_pp(pred, m) << ":\n";
if (it->m_value) { rm.display_smt2(*(it->m_value), tout) << "\n"; } if (entry.m_value) {
rm.display_smt2(*(entry.m_value), tout) << "\n";
}
} }
); );
@ -802,33 +818,32 @@ datalog::rule const* pred_transformer::find_rule(model &model,
// prefer a rule where the model intersects with reach facts of all predecessors; // prefer a rule where the model intersects with reach facts of all predecessors;
// also find how many predecessors' reach facts are true in the model // also find how many predecessors' reach facts are true in the model
expr_ref vl(m); expr_ref vl(m);
datalog::rule const* r = ((datalog::rule*)nullptr); const datalog::rule *r = ((datalog::rule*)nullptr);
tag2rule::iterator it = m_tag2rule.begin(), end = m_tag2rule.end(); for (auto &entry : m_tag2rule) {
for (; it != end; ++it) { expr* tag = entry.m_key;
expr* tag = it->m_key;
if (model.eval(to_app(tag)->get_decl(), vl) && m.is_true(vl)) { if (model.eval(to_app(tag)->get_decl(), vl) && m.is_true(vl)) {
r = it->m_value; r = entry.m_value;
is_concrete = true; is_concrete = true;
num_reuse_reach = 0; num_reuse_reach = 0;
reach_pred_used.reset (); reach_pred_used.reset();
unsigned tail_sz = r->get_uninterpreted_tail_size (); unsigned tail_sz = r->get_uninterpreted_tail_size();
for (unsigned i = 0; i < tail_sz; i++) { for (unsigned i = 0; i < tail_sz; i++) {
bool used = false; bool used = false;
func_decl* d = r->get_tail(i)->get_decl(); func_decl* d = r->get_tail(i)->get_decl();
pred_transformer const& pt = ctx.get_pred_transformer (d); const pred_transformer &pt = ctx.get_pred_transformer(d);
if (!pt.has_reach_facts()) { is_concrete = false; } if (!pt.has_reach_facts()) {is_concrete = false;}
else { else {
expr_ref v(m); expr_ref v(m);
pm.formula_n2o (pt.get_last_reach_case_var (), v, i); pm.formula_n2o(pt.get_last_reach_case_var (), v, i);
model.eval (to_app (v.get ())->get_decl (), vl); model.eval(to_app (v.get ())->get_decl (), vl);
used = m.is_false (vl); used = m.is_false (vl);
is_concrete = is_concrete && used; is_concrete = is_concrete && used;
} }
reach_pred_used.push_back (used); reach_pred_used.push_back (used);
if (used) { num_reuse_reach++; } if (used) {num_reuse_reach++;}
} }
if (is_concrete) { break; } if (is_concrete) {break;}
} }
} }
// SASSERT (r); // SASSERT (r);
@ -1268,30 +1283,21 @@ lbool pred_transformer::is_reachable(pob& n, expr_ref_vector* core,
post.push_back (n.post ()); post.push_back (n.post ());
// populate reach_assumps // populate reach_assumps
if (n.level () > 0 && !m_all_init) {
// XXX eager_reach_check must always be for (auto &entry : m_tag2rule) {
// XXX enabled. Otherwise, we can get into an infinite loop in datalog::rule const* r = entry.m_value;
// XXX which a model is consistent with a must-summary, but the if (!r) {continue;}
// XXX appropriate assumption is not set correctly by the model.
// XXX Original code handled reachability-events differently.
if (/* ctx.get_params ().eager_reach_check () && */
n.level () > 0 && !m_all_init) {
obj_map<expr, datalog::rule const*>::iterator it = m_tag2rule.begin (),
end = m_tag2rule.end ();
for (; it != end; ++it) {
datalog::rule const* r = it->m_value;
if (!r) { continue; }
find_predecessors(*r, m_predicates); find_predecessors(*r, m_predicates);
if (m_predicates.empty()) { continue; } if (m_predicates.empty()) {continue;}
for (unsigned i = 0; i < m_predicates.size(); i++) { for (unsigned i = 0; i < m_predicates.size(); i++) {
const pred_transformer &pt = const pred_transformer &pt =
ctx.get_pred_transformer (m_predicates [i]); ctx.get_pred_transformer(m_predicates[i]);
if (pt.has_reach_facts()) { if (pt.has_reach_facts()) {
expr_ref a(m); expr_ref a(m);
pm.formula_n2o (pt.get_last_reach_case_var (), a, i); pm.formula_n2o(pt.get_last_reach_case_var (), a, i);
reach_assumps.push_back (m.mk_not (a)); reach_assumps.push_back(m.mk_not (a));
} else if (ctx.get_params().spacer_init_reach_facts()) { } else if (ctx.get_params().spacer_init_reach_facts()) {
reach_assumps.push_back (m.mk_not (it->m_key)); reach_assumps.push_back(m.mk_not (entry.m_key));
break; break;
} }
} }
@ -1325,7 +1331,7 @@ lbool pred_transformer::is_reachable(pob& n, expr_ref_vector* core,
if (is_sat == l_true || is_sat == l_undef) { if (is_sat == l_true || is_sat == l_undef) {
if (core) { core->reset(); } if (core) { core->reset(); }
if (model) { if (model) {
r = find_rule (**model, is_concrete, reach_pred_used, num_reuse_reach); r = find_rule(**model, is_concrete, reach_pred_used, num_reuse_reach);
TRACE ("spacer", tout << "reachable " TRACE ("spacer", tout << "reachable "
<< "is_concrete " << is_concrete << " rused: "; << "is_concrete " << is_concrete << " rused: ";
for (unsigned i = 0, sz = reach_pred_used.size (); i < sz; ++i) for (unsigned i = 0, sz = reach_pred_used.size (); i < sz; ++i)
@ -1352,11 +1358,47 @@ lbool pred_transformer::is_reachable(pob& n, expr_ref_vector* core,
return l_undef; return l_undef;
} }
/// returns true if lemma is blocked by an existing model
bool pred_transformer::is_ctp_blocked(lemma *lem) {
if (!ctx.get_params().spacer_ctp()) {return false;}
if (!lem->has_ctp()) {return false;}
scoped_watch _t_(m_ctp_watch);
model_ref &ctp = lem->get_ctp();
// -- find rule of the ctp
const datalog::rule *r;
r = find_rule(*ctp);
if (r == nullptr) {return false;}
// -- find predicates along the rule
find_predecessors(*r, m_predicates);
// check if any lemmas block the model
for (unsigned i = 0, sz = m_predicates.size(); i < sz; ++i) {
pred_transformer &pt = ctx.get_pred_transformer(m_predicates[i]);
expr_ref lemmas(m), val(m);
lemmas = pt.get_formulas(lem->level(), false);
pm.formula_n2o(lemmas.get(), lemmas, i);
if (ctp->eval(lemmas, val) && m.is_false(val)) {return true;}
}
return false;
}
bool pred_transformer::is_invariant(unsigned level, lemma* lem, bool pred_transformer::is_invariant(unsigned level, lemma* lem,
unsigned& solver_level, expr_ref_vector* core) unsigned& solver_level,
expr_ref_vector* core)
{ {
expr_ref lemma(m); m_stats.m_num_is_invariant++;
lemma = lem->get_expr(); if (is_ctp_blocked(lem)) {
m_stats.m_num_ctp++;
return false;
}
expr_ref lemma_expr(m);
lemma_expr = lem->get_expr();
expr_ref_vector conj(m), aux(m); expr_ref_vector conj(m), aux(m);
expr_ref gnd_lemma(m); expr_ref gnd_lemma(m);
@ -1364,28 +1406,36 @@ bool pred_transformer::is_invariant(unsigned level, lemma* lem,
if (!get_context().use_qlemmas() && !lem->is_ground()) { if (!get_context().use_qlemmas() && !lem->is_ground()) {
app_ref_vector tmp(m); app_ref_vector tmp(m);
ground_expr(to_quantifier(lemma)->get_expr (), gnd_lemma, tmp); ground_expr(to_quantifier(lemma_expr)->get_expr (), gnd_lemma, tmp);
lemma = gnd_lemma.get(); lemma_expr = gnd_lemma.get();
} }
conj.push_back(mk_not(m, lemma)); conj.push_back(mk_not(m, lemma_expr));
flatten_and (conj); flatten_and (conj);
prop_solver::scoped_level _sl(m_solver, level); prop_solver::scoped_level _sl(m_solver, level);
prop_solver::scoped_subset_core _sc (m_solver, true); prop_solver::scoped_subset_core _sc (m_solver, true);
prop_solver::scoped_weakness _sw (m_solver, 1, prop_solver::scoped_weakness _sw (m_solver, 1,
ctx.weak_abs() ? lem->weakness() : UINT_MAX); ctx.weak_abs() ? lem->weakness() : UINT_MAX);
model_ref mdl;
m_solver.set_core(core); m_solver.set_core(core);
m_solver.set_model(nullptr); m_solver.set_model(&mdl);
expr * bg = m_extend_lit.get (); expr * bg = m_extend_lit.get ();
lbool r = m_solver.check_assumptions (conj, aux, 1, &bg, 1); lbool r = m_solver.check_assumptions (conj, aux, 1, &bg, 1);
if (r == l_false) { if (r == l_false) {
solver_level = m_solver.uses_level (); solver_level = m_solver.uses_level ();
lem->reset_ctp();
CTRACE ("spacer", level < m_solver.uses_level (), CTRACE ("spacer", level < m_solver.uses_level (),
tout << "Checking at level " << level tout << "Checking at level " << level
<< " but only using " << m_solver.uses_level () << "\n";); << " but only using " << m_solver.uses_level () << "\n";);
SASSERT (level <= solver_level); SASSERT (level <= solver_level);
} }
else if (r == l_true) {
// optionally remove unused symbols from the model
lem->set_ctp(mdl);
}
else {lem->reset_ctp();}
return r == l_false; return r == l_false;
} }
@ -1795,9 +1845,7 @@ bool pred_transformer::frames::propagate_to_next_level (unsigned level)
m_pt.ensure_level (tgt_level); m_pt.ensure_level (tgt_level);
for (unsigned i = 0, sz = m_lemmas.size(); i < sz && m_lemmas [i]->level() <= level;) { for (unsigned i = 0, sz = m_lemmas.size(); i < sz && m_lemmas [i]->level() <= level;) {
if (m_lemmas [i]->level () < level) if (m_lemmas [i]->level () < level) {++i; continue;}
{++i; continue;}
unsigned solver_level; unsigned solver_level;
if (m_pt.is_invariant(tgt_level, m_lemmas.get(i), solver_level)) { if (m_pt.is_invariant(tgt_level, m_lemmas.get(i), solver_level)) {

View file

@ -116,6 +116,7 @@ class lemma {
app_ref_vector m_bindings; app_ref_vector m_bindings;
unsigned m_lvl; unsigned m_lvl;
pob_ref m_pob; pob_ref m_pob;
model_ref m_ctp; // counter-example to pushing
bool m_external; bool m_external;
void mk_expr_core(); void mk_expr_core();
@ -127,6 +128,12 @@ public:
// lemma(const lemma &other) = delete; // lemma(const lemma &other) = delete;
ast_manager &get_ast_manager() {return m;} ast_manager &get_ast_manager() {return m;}
model_ref& get_ctp() {return m_ctp;}
bool has_ctp() {return !is_inductive() && m_ctp;}
void set_ctp(model_ref &v) {m_ctp = v;}
void reset_ctp() {m_ctp.reset();}
expr *get_expr(); expr *get_expr();
bool is_false(); bool is_false();
expr_ref_vector const &get_cube(); expr_ref_vector const &get_cube();
@ -141,6 +148,7 @@ public:
inline void set_external(bool ext){m_external = ext;} inline void set_external(bool ext){m_external = ext;}
inline bool external() { return m_external;} inline bool external() { return m_external;}
bool is_inductive() const {return is_infty_level(m_lvl);}
unsigned level () const {return m_lvl;} unsigned level () const {return m_lvl;}
void set_level (unsigned lvl); void set_level (unsigned lvl);
app_ref_vector& get_bindings() {return m_bindings;} app_ref_vector& get_bindings() {return m_bindings;}
@ -151,11 +159,10 @@ public:
bool is_ground () {return !is_quantifier (get_expr());} bool is_ground () {return !is_quantifier (get_expr());}
void inc_ref () {++m_ref_count;} void inc_ref () {++m_ref_count;}
void dec_ref () void dec_ref () {
{
SASSERT (m_ref_count > 0); SASSERT (m_ref_count > 0);
--m_ref_count; --m_ref_count;
if(m_ref_count == 0) { dealloc(this); } if(m_ref_count == 0) {dealloc(this);}
} }
}; };
@ -180,6 +187,8 @@ class pred_transformer {
struct stats { struct stats {
unsigned m_num_propagations; unsigned m_num_propagations;
unsigned m_num_invariants; unsigned m_num_invariants;
unsigned m_num_ctp;
unsigned m_num_is_invariant;
stats() { reset(); } stats() { reset(); }
void reset() { memset(this, 0, sizeof(*this)); } void reset() { memset(this, 0, sizeof(*this)); }
}; };
@ -293,7 +302,7 @@ class pred_transformer {
stats m_stats; stats m_stats;
stopwatch m_initialize_watch; stopwatch m_initialize_watch;
stopwatch m_must_reachable_watch; stopwatch m_must_reachable_watch;
stopwatch m_ctp_watch;
/// Auxiliary variables to represent different disjunctive /// Auxiliary variables to represent different disjunctive
@ -367,7 +376,9 @@ public:
unsigned level, unsigned oidx, bool must, unsigned level, unsigned oidx, bool must,
const ptr_vector<app> **aux); const ptr_vector<app> **aux);
datalog::rule const* find_rule(model &mev, bool& is_concrete, bool is_ctp_blocked(lemma *lem);
const datalog::rule *find_rule(model &mdl);
const datalog::rule *find_rule(model &mev, bool& is_concrete,
vector<bool>& reach_pred_used, vector<bool>& reach_pred_used,
unsigned& num_reuse_reach); unsigned& num_reuse_reach);
expr* get_transition(datalog::rule const& r) { return m_rule2transition.find(&r); } expr* get_transition(datalog::rule const& r) { return m_rule2transition.find(&r); }
@ -403,7 +414,8 @@ public:
vector<bool>& reach_pred_used, vector<bool>& reach_pred_used,
unsigned& num_reuse_reach); unsigned& num_reuse_reach);
bool is_invariant(unsigned level, lemma* lem, bool is_invariant(unsigned level, lemma* lem,
unsigned& solver_level, expr_ref_vector* core = nullptr); unsigned& solver_level,
expr_ref_vector* core = nullptr);
bool is_invariant(unsigned level, expr* lem, bool is_invariant(unsigned level, expr* lem,
unsigned& solver_level, expr_ref_vector* core = nullptr) { unsigned& solver_level, expr_ref_vector* core = nullptr) {