/** Copyright (c) 2017 Arie Gurfinkel Module Name: spacer_prop_solver.cpp Abstract: SAT solver abstraction for SPACER. Author: Arie Gurfinkel Anvesh Komuravelli Revision History: --*/ #include "ast/ast_smt2_pp.h" #include "ast/datatype_decl_plugin.h" #include "ast/bv_decl_plugin.h" #include "ast/rewriter/expr_replacer.h" #include "smt/params/smt_params.h" #include "model/model.h" #include "model/model_pp.h" #include "muz/base/dl_util.h" #include "muz/spacer/spacer_util.h" #include "muz/spacer/spacer_farkas_learner.h" #include "muz/spacer/spacer_prop_solver.h" #include "model/model_evaluator.h" #include "muz/base/fp_params.hpp" namespace spacer { prop_solver::prop_solver(ast_manager &m, solver *solver0, solver *solver1, fp_params const& p, symbol const& name) : m(m), m_name(name), m_ctx(nullptr), m_pos_level_atoms(m), m_neg_level_atoms(m), m_core(nullptr), m_subset_based_core(false), m_uses_level(infty_level()), m_delta_level(false), m_in_level(false), m_use_push_bg(p.spacer_keep_proxy()) { m_random.set_seed(p.spacer_random_seed()); m_solvers[0] = solver0; m_solvers[1] = solver1; m_contexts[0] = alloc(spacer::iuc_solver, *(m_solvers[0]), p.spacer_iuc(), p.spacer_iuc_arith(), p.spacer_iuc_print_farkas_stats(), p.spacer_iuc_old_hyp_reducer(), p.spacer_iuc_split_farkas_literals()); m_contexts[1] = alloc(spacer::iuc_solver, *(m_solvers[1]), p.spacer_iuc(), p.spacer_iuc_arith(), p.spacer_iuc_print_farkas_stats(), p.spacer_iuc_old_hyp_reducer(), p.spacer_iuc_split_farkas_literals()); } void prop_solver::add_level() { unsigned idx = level_cnt(); std::stringstream name; name << m_name << "#level_" << idx; auto str = name.str(); func_decl * lev_pred = m.mk_fresh_func_decl(str.c_str(), 0, nullptr, m.mk_bool_sort()); m_level_preds.push_back(lev_pred); app_ref pos_la(m.mk_const(lev_pred), m); app_ref neg_la(m.mk_not(pos_la.get()), m); m_pos_level_atoms.push_back(pos_la); m_neg_level_atoms.push_back(neg_la); m_level_atoms_set.insert(pos_la.get()); m_level_atoms_set.insert(neg_la.get()); } void prop_solver::ensure_level(unsigned lvl) { if (is_infty_level(lvl)) return; while (lvl >= level_cnt()) { add_level(); } } unsigned prop_solver::level_cnt() const { return m_level_preds.size(); } void prop_solver::assert_level_atoms(unsigned level) { unsigned lev_cnt = level_cnt(); for (unsigned i = 0; i < lev_cnt; i++) { bool active = m_delta_level ? i == level : i >= level; app * lev_atom = active ? m_neg_level_atoms.get(i) : m_pos_level_atoms.get(i); m_ctx->push_bg(lev_atom); } } void prop_solver::assert_expr(expr * form) { SASSERT(!m_in_level); m_contexts[0]->assert_expr(form); m_contexts[1]->assert_expr(form); IF_VERBOSE(21, verbose_stream() << "$ asserted " << mk_pp(form, m) << "\n";); TRACE("spacer", tout << "add_formula: " << mk_pp(form, m) << "\n";); } void prop_solver::assert_expr(expr * form, unsigned level) { if (is_infty_level(level)) {assert_expr(form);return;} ensure_level(level); app * lev_atom = m_pos_level_atoms[level].get(); app_ref lform(m.mk_or(form, lev_atom), m); assert_expr(lform); } /// Local model guided maxsmt lbool prop_solver::mss(expr_ref_vector &hard, expr_ref_vector &soft) { // replace expressions by assumption literals iuc_solver::scoped_mk_proxy _p_(*m_ctx, hard); unsigned hard_sz = hard.size(); lbool res = m_ctx->check_sat(hard.size(), hard.data()); // bail out if hard constraints are not sat, or if there are no // soft constraints if (res != l_true || soft.empty()) {return res;} // the main loop model_ref mdl; m_ctx->get_model(mdl); // don't proxy soft literals. Assume that they are propositional. hard.append(soft); soft.reset(); // hard is divided into 4 regions // x < hard_sz ---> hard constraints // hard_sz <= x < i ---> sat soft constraints // i <= x < j ---> backbones (unsat soft constraints) // j <= x < hard.size() ---> unprocessed soft constraints unsigned i, j; i = hard_sz; j = hard_sz; while (j < hard.size()) { model_evaluator mev(*mdl); // move all true soft constraints to [hard_sz, i) for (unsigned k = j; k < hard.size(); ++k) { expr_ref e(m); e = hard.get(k); if (!mev.is_false(e) /* true or unset */) { expr_ref tmp(m); tmp = hard.get(i); hard[i] = e; if (i < j) { // tmp is a backbone, put it at j if (j == k) {hard[j] = tmp;} else /* j < k */ { e = hard.get(j); hard[j] = tmp; hard[k] = e; } j++; } else { // there are no backbone literals hard[k] = tmp; j++; } i++; } } // done with the model. Reset to avoid confusion in debugging mdl.reset(); // -- grow the set of backbone literals for (;j < hard.size(); ++j) { res = m_ctx->check_sat(j+1, hard.data()); if (res == l_false) { // -- flip non-true literal to be false hard[j] = mk_not(m, hard.get(j)); } else if (res == l_true) { // -- get the model for the next iteration of the outer loop m_ctx->get_model(mdl); break; } else if (res == l_undef) { // -- conservatively bail out hard.resize(hard_sz); return l_undef; } } } // move sat soft constraints to the output vector for (unsigned k = i; k < j; ++k) { soft.push_back(hard.get(k)); } // cleanup hard constraints hard.resize(hard_sz); return l_true; } /// Poor man's maxsat. No guarantees of maximum solution /// Runs maxsat loop on m_ctx Returns l_false if hard is unsat, /// otherwise reduces soft such that hard & soft is sat. lbool prop_solver::maxsmt(expr_ref_vector &hard, expr_ref_vector &soft, vector const & clauses) { // replace expressions by assumption literals iuc_solver::scoped_mk_proxy _p_(*m_ctx, hard); unsigned hard_sz = hard.size(); // assume soft constraints are propositional literals (no need to proxy) hard.append(soft); lbool res = m_ctx->check_sat_cc(hard, clauses); // if hard constraints alone are unsat or there are no soft // constraints, we are done if (res != l_false || soft.empty()) { return res; } // clear soft constraints, we will recompute them later soft.reset(); expr_ref saved(m); expr_ref_vector core(m); m_ctx->get_unsat_core(core); // while there are soft constraints while (hard.size() > hard_sz) { bool found = false; // look for a soft constraint that is in the unsat core for (unsigned i = hard_sz, sz = hard.size(); i < sz; ++i) if (core.contains(hard.get(i))) { found = true; // AG: not sure why we are saving it saved = hard.get(i); hard[i] = hard.back(); hard.pop_back(); break; } // if no soft constraints in the core, return this should // not happen because it implies that hard alone is unsat // and that is taken care of earlier if (!found) { hard.resize(hard_sz); return l_false; } // check that the NEW constraints became sat res = m_ctx->check_sat_cc(hard, clauses); if (res != l_false) { break; } // still unsat, update the core and repeat core.reset(); m_ctx->get_unsat_core(core); } // update soft with found soft constraints if (res == l_true) { for (unsigned i = hard_sz, sz = hard.size(); i < sz; ++i) { soft.push_back(hard.get(i)); } } // revert hard back to the right size // proxies are undone on exit via scoped_mk_proxy hard.resize(hard_sz); return res; } lbool prop_solver::internal_check_assumptions(expr_ref_vector &hard_atoms, expr_ref_vector &soft_atoms, vector const & clauses) { // XXX Turn model generation if m_model != 0 SASSERT(m_ctx); params_ref p; if (m_model != nullptr) { p.set_bool("produce_models", true); m_ctx->updt_params(p); } if (m_in_level) { assert_level_atoms(m_current_level); } lbool result = maxsmt(hard_atoms, soft_atoms, clauses); if (result != l_false && m_model) { m_ctx->get_model(*m_model); } SASSERT(result != l_false || soft_atoms.empty()); /// compute level used in the core // XXX this is a poor approximation because the core will get minimized further if (result == l_false) { ptr_vector core; m_ctx->get_full_unsat_core(core); unsigned core_size = core.size(); m_uses_level = infty_level(); for (unsigned i = 0; i < core_size; ++i) { if (m_level_atoms_set.contains(core[i])) { unsigned sz = std::min(m_uses_level, m_neg_level_atoms.size()); for (unsigned j = 0; j < sz; ++j) if (m_neg_level_atoms [j].get() == core[i]) { m_uses_level = j; break; } SASSERT(!is_infty_level(m_uses_level)); } } } if (result == l_false && m_core && m.proofs_enabled() && !m_subset_based_core) { TRACE("spacer", tout << "Using IUC core\n";); m_core->reset(); m_ctx->get_iuc(*m_core); } else if (result == l_false && m_core) { m_core->reset(); m_ctx->get_unsat_core(*m_core); // manually undo proxies because maxsmt() call above manually adds proxies // AG: don't think this is needed. maxsmt() undoes the proxies already m_ctx->undo_proxies(*m_core); } if (m_model != nullptr) { p.set_bool("produce_models", false); m_ctx->updt_params(p); } return result; } lbool prop_solver::check_assumptions(const expr_ref_vector & _hard, expr_ref_vector& soft, const expr_ref_vector &clause, unsigned num_bg, expr * const * bg, unsigned solver_id) { expr_ref cls(m); // current clients expect that flattening of HARD is // done implicitly during check_assumptions expr_ref_vector hard(m); hard.append(_hard.size(), _hard.data()); flatten_and(hard); shuffle(hard.size(), hard.data(), m_random); m_ctx = m_contexts [solver_id == 0 ? 0 : 0 /* 1 */].get(); // can be disabled if use_push_bg == true // solver::scoped_push _s_(*m_ctx); if (!m_use_push_bg) {m_ctx->push();} iuc_solver::scoped_bg _b_(*m_ctx); for (unsigned i = 0; i < num_bg; ++i) if (m_use_push_bg) { m_ctx->push_bg(bg [i]); } else { m_ctx->assert_expr(bg[i]); } unsigned soft_sz = soft.size(); (void) soft_sz; vector clauses; if (!clause.empty()) clauses.push_back(clause); lbool res = internal_check_assumptions(hard, soft, clauses); if (!m_use_push_bg) { m_ctx->pop(1); } TRACE("psolve_verbose", tout << "sat: " << mk_pp(mk_and(hard), m) << "\n" << mk_pp(mk_and(soft), m) << "\n"; for (unsigned i = 0; i < num_bg; ++i) tout << "bg" << i << ": " << mk_pp(bg[i], m) << "\n"; tout << "res: " << res << "\n";); CTRACE("psolve", m_core, tout << "core is: " << mk_pp(mk_and(*m_core), m) << "\n";); SASSERT(soft_sz >= soft.size()); // -- reset all parameters m_core = nullptr; m_model = nullptr; m_subset_based_core = false; return res; } void prop_solver::collect_statistics(statistics& st) const { m_contexts[0]->collect_statistics(st); m_contexts[1]->collect_statistics(st); } void prop_solver::reset_statistics() { } }