/** 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 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 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_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)); } } }