mirror of
https://github.com/Z3Prover/z3
synced 2025-04-07 09:55:19 +00:00
470 lines
13 KiB
C++
470 lines
13 KiB
C++
/**
|
|
Copyright (c) 2017 Arie Gurfinkel
|
|
|
|
Module Name:
|
|
|
|
spacer_iuc_solver.cpp
|
|
|
|
Abstract:
|
|
|
|
A solver that produces interpolated unsat cores (IUCs)
|
|
|
|
Author:
|
|
|
|
Arie Gurfinkel
|
|
|
|
Notes:
|
|
|
|
--*/
|
|
#include"muz/spacer/spacer_iuc_solver.h"
|
|
#include"ast/ast.h"
|
|
#include"muz/spacer/spacer_util.h"
|
|
#include"ast/proofs/proof_utils.h"
|
|
#include"muz/spacer/spacer_farkas_learner.h"
|
|
#include"ast/rewriter/expr_replacer.h"
|
|
#include"muz/spacer/spacer_unsat_core_learner.h"
|
|
#include"muz/spacer/spacer_unsat_core_plugin.h"
|
|
#include "muz/spacer/spacer_iuc_proof.h"
|
|
|
|
namespace spacer {
|
|
void iuc_solver::push ()
|
|
{
|
|
m_defs.push_back (def_manager (*this));
|
|
m_solver.push ();
|
|
}
|
|
|
|
void iuc_solver::pop (unsigned n)
|
|
{
|
|
m_solver.pop (n);
|
|
unsigned lvl = m_defs.size ();
|
|
SASSERT (n <= lvl);
|
|
unsigned new_lvl = lvl-n;
|
|
while (m_defs.size() > new_lvl) {
|
|
m_num_proxies -= m_defs.back ().m_defs.size ();
|
|
m_defs.pop_back ();
|
|
}
|
|
}
|
|
|
|
app* iuc_solver::fresh_proxy ()
|
|
{
|
|
if (m_num_proxies == m_proxies.size()) {
|
|
std::stringstream name;
|
|
name << "spacer_proxy!" << m_proxies.size ();
|
|
app_ref res(m);
|
|
res = m.mk_const (symbol (name.str ().c_str ()),
|
|
m.mk_bool_sort ());
|
|
m_proxies.push_back (res);
|
|
|
|
// -- add the new proxy to proxy eliminator
|
|
proof_ref pr(m);
|
|
pr = m.mk_asserted (m.mk_true ());
|
|
m_elim_proxies_sub.insert (res, m.mk_true (), pr);
|
|
|
|
}
|
|
return m_proxies.get (m_num_proxies++);
|
|
}
|
|
|
|
app* iuc_solver::mk_proxy (expr *v)
|
|
{
|
|
{
|
|
expr *e = v;
|
|
m.is_not (v, e);
|
|
if (is_uninterp_const(e)) { return to_app(v); }
|
|
}
|
|
|
|
def_manager &def = m_defs.size () > 0 ? m_defs.back () : m_base_defs;
|
|
return def.mk_proxy (v);
|
|
}
|
|
|
|
bool iuc_solver::mk_proxies (expr_ref_vector &v, unsigned from)
|
|
{
|
|
bool dirty = false;
|
|
for (unsigned i = from, sz = v.size(); i < sz; ++i) {
|
|
app *p = mk_proxy (v.get (i));
|
|
dirty |= (v.get (i) != p);
|
|
v[i] = p;
|
|
}
|
|
return dirty;
|
|
}
|
|
|
|
void iuc_solver::push_bg (expr *e)
|
|
{
|
|
if (m_assumptions.size () > m_first_assumption)
|
|
{ m_assumptions.shrink(m_first_assumption); }
|
|
m_assumptions.push_back (e);
|
|
m_first_assumption = m_assumptions.size ();
|
|
}
|
|
|
|
void iuc_solver::pop_bg (unsigned n)
|
|
{
|
|
if (n == 0) { return; }
|
|
|
|
if (m_assumptions.size () > m_first_assumption) {
|
|
m_assumptions.shrink(m_first_assumption);
|
|
}
|
|
m_first_assumption = m_first_assumption > n ? m_first_assumption - n : 0;
|
|
m_assumptions.shrink (m_first_assumption);
|
|
}
|
|
|
|
unsigned iuc_solver::get_num_bg () {return m_first_assumption;}
|
|
|
|
lbool iuc_solver::check_sat (unsigned num_assumptions, expr * const *assumptions)
|
|
{
|
|
// -- remove any old assumptions
|
|
m_assumptions.shrink(m_first_assumption);
|
|
|
|
// -- replace theory literals in background assumptions with proxies
|
|
mk_proxies (m_assumptions);
|
|
// -- in case mk_proxies added new literals, they are all background
|
|
m_first_assumption = m_assumptions.size ();
|
|
|
|
m_assumptions.append (num_assumptions, assumptions);
|
|
m_is_proxied = mk_proxies (m_assumptions, m_first_assumption);
|
|
|
|
return set_status (m_solver.check_sat (m_assumptions));
|
|
}
|
|
|
|
lbool iuc_solver::check_sat_cc(const expr_ref_vector &cube,
|
|
vector<expr_ref_vector> const & clauses) {
|
|
if (clauses.empty())
|
|
return check_sat(cube.size(), cube.c_ptr());
|
|
|
|
// -- remove any old assumptions
|
|
m_assumptions.shrink(m_first_assumption);
|
|
|
|
// -- replace theory literals in background assumptions with proxies
|
|
mk_proxies(m_assumptions);
|
|
// -- in case mk_proxies added new literals, they are all background
|
|
m_first_assumption = m_assumptions.size();
|
|
|
|
m_assumptions.append(cube);
|
|
m_is_proxied = mk_proxies(m_assumptions, m_first_assumption);
|
|
|
|
return set_status (m_solver.check_sat_cc(m_assumptions, clauses));
|
|
}
|
|
|
|
|
|
app* iuc_solver::def_manager::mk_proxy (expr *v)
|
|
{
|
|
app* r;
|
|
if (m_expr2proxy.find(v, r))
|
|
return r;
|
|
|
|
ast_manager &m = m_parent.m;
|
|
app* proxy = m_parent.fresh_proxy ();
|
|
app* def = m.mk_or (m.mk_not (proxy), v);
|
|
m_defs.push_back (def);
|
|
m_expr2proxy.insert (v, proxy);
|
|
m_proxy2def.insert (proxy, def);
|
|
|
|
m_parent.assert_expr (def);
|
|
return proxy;
|
|
}
|
|
|
|
bool iuc_solver::def_manager::is_proxy (app *k, app_ref &def)
|
|
{
|
|
app *r = nullptr;
|
|
bool found = m_proxy2def.find (k, r);
|
|
def = r;
|
|
return found;
|
|
}
|
|
|
|
void iuc_solver::def_manager::reset ()
|
|
{
|
|
m_expr2proxy.reset ();
|
|
m_proxy2def.reset ();
|
|
m_defs.reset ();
|
|
}
|
|
|
|
bool iuc_solver::def_manager::is_proxy_def (expr *v)
|
|
{
|
|
// XXX This might not be the most robust way to check
|
|
return m_defs.contains (v);
|
|
}
|
|
|
|
bool iuc_solver::is_proxy(expr *e, app_ref &def)
|
|
{
|
|
if (!is_uninterp_const(e))
|
|
return false;
|
|
|
|
app* a = to_app (e);
|
|
|
|
for (int i = m_defs.size (); i-- > 0; )
|
|
if (m_defs[i].is_proxy (a, def))
|
|
return true;
|
|
|
|
return m_base_defs.is_proxy (a, def);
|
|
}
|
|
|
|
void iuc_solver::collect_statistics (statistics &st) const
|
|
{
|
|
m_solver.collect_statistics (st);
|
|
st.update ("time.iuc_solver.get_iuc", m_iuc_sw.get_seconds());
|
|
st.update ("time.iuc_solver.get_iuc.hyp_reduce1", m_hyp_reduce1_sw.get_seconds());
|
|
st.update ("time.iuc_solver.get_iuc.hyp_reduce2", m_hyp_reduce2_sw.get_seconds());
|
|
st.update ("time.iuc_solver.get_iuc.learn_core", m_learn_core_sw.get_seconds());
|
|
|
|
st.update("iuc_solver.num_proxies", m_proxies.size());
|
|
}
|
|
|
|
void iuc_solver::reset_statistics ()
|
|
{
|
|
m_iuc_sw.reset();
|
|
m_hyp_reduce1_sw.reset();
|
|
m_hyp_reduce2_sw.reset();
|
|
m_learn_core_sw.reset();
|
|
}
|
|
|
|
void iuc_solver::get_unsat_core (expr_ref_vector &core) {
|
|
m_solver.get_unsat_core (core);
|
|
undo_proxies_in_core (core);
|
|
}
|
|
|
|
void iuc_solver::undo_proxies_in_core (expr_ref_vector &r)
|
|
{
|
|
app_ref e(m);
|
|
expr_fast_mark1 bg;
|
|
for (unsigned i = 0; i < m_first_assumption; ++i) {
|
|
bg.mark(m_assumptions.get(i));
|
|
}
|
|
|
|
// expand proxies
|
|
unsigned j = 0;
|
|
for (expr* rr : r) {
|
|
// skip background assumptions
|
|
if (bg.is_marked(rr))
|
|
continue;
|
|
|
|
// -- undo proxies, but only if they were introduced in check_sat
|
|
if (m_is_proxied && is_proxy(rr, e)) {
|
|
SASSERT (m.is_or (e));
|
|
r[j++] = e->get_arg (1);
|
|
}
|
|
else {
|
|
r[j++] = rr;
|
|
}
|
|
}
|
|
r.shrink (j);
|
|
}
|
|
|
|
void iuc_solver::undo_proxies (expr_ref_vector &r)
|
|
{
|
|
app_ref e(m);
|
|
// expand proxies
|
|
for (unsigned i = 0, sz = r.size (); i < sz; ++i)
|
|
if (is_proxy(r.get(i), e)) {
|
|
SASSERT (m.is_or (e));
|
|
r[i] = e->get_arg (1);
|
|
}
|
|
}
|
|
|
|
void iuc_solver::elim_proxies (expr_ref_vector &v)
|
|
{
|
|
expr_ref f = mk_and (v);
|
|
scoped_ptr<expr_replacer> rep = mk_expr_simp_replacer (m);
|
|
rep->set_substitution (&m_elim_proxies_sub);
|
|
(*rep)(f);
|
|
v.reset();
|
|
flatten_and(f, v);
|
|
}
|
|
|
|
void iuc_solver::get_iuc(expr_ref_vector &core)
|
|
{
|
|
scoped_watch _t_ (m_iuc_sw);
|
|
|
|
typedef obj_hashtable<expr> expr_set;
|
|
expr_set core_lits;
|
|
for (unsigned i = m_first_assumption, sz = m_assumptions.size(); i < sz; ++i) {
|
|
expr *a = m_assumptions.get (i);
|
|
app_ref def(m);
|
|
if (is_proxy(a, def)) { core_lits.insert(def.get()); }
|
|
core_lits.insert (a);
|
|
}
|
|
|
|
if (m_iuc == 0) {
|
|
// ORIGINAL PDR CODE
|
|
// AG: deprecated
|
|
proof_ref pr(m);
|
|
pr = get_proof ();
|
|
|
|
farkas_learner learner_old;
|
|
learner_old.set_split_literals(m_split_literals);
|
|
|
|
learner_old.get_lemmas (pr, core_lits, core);
|
|
elim_proxies (core);
|
|
simplify_bounds (core); // XXX potentially redundant
|
|
}
|
|
else {
|
|
// NEW IUC
|
|
proof_ref res(get_proof(), m);
|
|
|
|
// -- old hypothesis reducer while the new one is broken
|
|
if (m_old_hyp_reducer) {
|
|
scoped_watch _t_ (m_hyp_reduce1_sw);
|
|
// AG: deprecated
|
|
// pre-process proof in order to get a proof which is
|
|
// better suited for unsat-core-extraction
|
|
if (m_print_farkas_stats) {
|
|
iuc_proof iuc_before(m, res.get(), core_lits);
|
|
verbose_stream() << "\nOld reduce_hypotheses. Before:";
|
|
iuc_before.dump_farkas_stats();
|
|
}
|
|
|
|
proof_utils::reduce_hypotheses(res);
|
|
proof_utils::permute_unit_resolution(res);
|
|
|
|
if (m_print_farkas_stats) {
|
|
iuc_proof iuc_after(m, res.get(), core_lits);
|
|
verbose_stream() << "Old reduce_hypothesis. After:";
|
|
iuc_after.dump_farkas_stats();
|
|
}
|
|
}
|
|
// -- new hypothesis reducer
|
|
else
|
|
{
|
|
#if 0
|
|
static unsigned bcnt = 0;
|
|
{
|
|
bcnt++;
|
|
TRACE("spacer", tout << "Dumping pf bcnt: " << bcnt << "\n";);
|
|
if (bcnt == 123) {
|
|
std::ofstream ofs;
|
|
ofs.open("/tmp/bpf_" + std::to_string(bcnt) + ".dot");
|
|
iuc_proof iuc_pf_before(m, res.get(), core_lits);
|
|
iuc_pf_before.display_dot(ofs);
|
|
ofs.close();
|
|
|
|
proof_checker pc(m);
|
|
expr_ref_vector side(m);
|
|
ENSURE(pc.check(res, side));
|
|
}
|
|
}
|
|
#endif
|
|
scoped_watch _t_ (m_hyp_reduce2_sw);
|
|
|
|
// pre-process proof for better iuc extraction
|
|
if (m_print_farkas_stats) {
|
|
iuc_proof iuc_before(m, res.get(), core_lits);
|
|
verbose_stream() << "\n New hypothesis_reducer. Before:";
|
|
iuc_before.dump_farkas_stats();
|
|
}
|
|
|
|
proof_ref pr1(m);
|
|
{
|
|
scoped_watch _t_ (m_hyp_reduce1_sw);
|
|
theory_axiom_reducer ta_reducer(m);
|
|
pr1 = ta_reducer.reduce (res.get());
|
|
}
|
|
|
|
proof_ref pr2(m);
|
|
{
|
|
scoped_watch _t_ (m_hyp_reduce2_sw);
|
|
hypothesis_reducer hyp_reducer(m);
|
|
pr2 = hyp_reducer.reduce(pr1);
|
|
}
|
|
|
|
res = pr2;
|
|
|
|
if (m_print_farkas_stats) {
|
|
iuc_proof iuc_after(m, res.get(), core_lits);
|
|
verbose_stream() << "New hypothesis_reducer. After:";
|
|
iuc_after.dump_farkas_stats();
|
|
}
|
|
}
|
|
|
|
iuc_proof iuc_pf(m, res, core_lits);
|
|
|
|
#if 0
|
|
static unsigned cnt = 0;
|
|
{
|
|
cnt++;
|
|
TRACE("spacer", tout << "Dumping pf cnt: " << cnt << "\n";);
|
|
if (cnt == 123) {
|
|
std::ofstream ofs;
|
|
ofs.open("/tmp/pf_" + std::to_string(cnt) + ".dot");
|
|
iuc_pf.display_dot(ofs);
|
|
ofs.close();
|
|
proof_checker pc(m);
|
|
expr_ref_vector side(m);
|
|
ENSURE(pc.check(res, side));
|
|
}
|
|
}
|
|
#endif
|
|
unsat_core_learner learner(m, iuc_pf);
|
|
|
|
unsat_core_plugin* plugin;
|
|
// -- register iuc plugins
|
|
switch (m_iuc_arith) {
|
|
case 0:
|
|
case 1:
|
|
plugin =
|
|
alloc(unsat_core_plugin_farkas_lemma,
|
|
learner, m_split_literals,
|
|
(m_iuc_arith == 1) /* use constants from A */);
|
|
learner.register_plugin(plugin);
|
|
break;
|
|
case 2:
|
|
SASSERT(false && "Broken");
|
|
plugin = alloc(unsat_core_plugin_farkas_lemma_optimized, learner, m);
|
|
learner.register_plugin(plugin);
|
|
break;
|
|
case 3:
|
|
plugin = alloc(unsat_core_plugin_farkas_lemma_bounded, learner, m);
|
|
learner.register_plugin(plugin);
|
|
break;
|
|
default:
|
|
UNREACHABLE();
|
|
break;
|
|
}
|
|
|
|
switch (m_iuc) {
|
|
case 1:
|
|
// -- iuc based on the lowest cut in the proof
|
|
plugin = alloc(unsat_core_plugin_lemma, learner);
|
|
learner.register_plugin(plugin);
|
|
break;
|
|
case 2:
|
|
// -- iuc based on the smallest cut in the proof
|
|
plugin = alloc(unsat_core_plugin_min_cut, learner, m);
|
|
learner.register_plugin(plugin);
|
|
break;
|
|
default:
|
|
UNREACHABLE();
|
|
break;
|
|
}
|
|
|
|
{
|
|
scoped_watch _t_ (m_learn_core_sw);
|
|
// compute interpolating unsat core
|
|
learner.compute_unsat_core(core);
|
|
}
|
|
|
|
elim_proxies (core);
|
|
// AG: this should be taken care of by minimizing the iuc cut
|
|
simplify_bounds (core);
|
|
}
|
|
|
|
IF_VERBOSE(2,
|
|
verbose_stream () << "IUC Core:\n" << core << "\n";);
|
|
}
|
|
|
|
void iuc_solver::refresh ()
|
|
{
|
|
// only refresh in non-pushed state
|
|
SASSERT (m_defs.empty());
|
|
expr_ref_vector assertions (m);
|
|
for (unsigned i = 0, e = m_solver.get_num_assertions(); i < e; ++i) {
|
|
expr* a = m_solver.get_assertion (i);
|
|
if (!m_base_defs.is_proxy_def(a)) { assertions.push_back(a); }
|
|
|
|
}
|
|
m_base_defs.reset ();
|
|
NOT_IMPLEMENTED_YET ();
|
|
// solver interface does not have a reset method. need to introduce it somewhere.
|
|
// m_solver.reset ();
|
|
for (unsigned i = 0, e = assertions.size (); i < e; ++i)
|
|
{ m_solver.assert_expr(assertions.get(i)); }
|
|
}
|
|
|
|
}
|