/*++ Copyright (c) 2015 Microsoft Corporation Module Name: qe_mbp.cpp Abstract: Model-based projection utilities Author: Nikolaj Bjorner (nbjorner) 2015-5-29 Revision History: --*/ #include "qe/qe_mbp.h" #include "ast/ast_pp.h" #include "ast/ast_util.h" #include "ast/expr_functors.h" #include "ast/for_each_expr.h" #include "ast/occurs.h" #include "ast/rewriter/expr_safe_replace.h" #include "ast/rewriter/th_rewriter.h" #include "ast/rewriter/rewriter.h" #include "ast/rewriter/rewriter_def.h" #include "ast/scoped_proof.h" #include "ast/seq_decl_plugin.h" #include "util/gparams.h" #include "model/model_evaluator.h" #include "model/model_pp.h" #include "qe/lite/qe_lite_tactic.h" #include "qe/lite/qel.h" #include "qe/mbp/mbp_arith.h" #include "qe/mbp/mbp_arrays.h" #include "qe/mbp/mbp_qel.h" #include "qe/mbp/mbp_datatypes.h" using namespace qe; namespace { // rewrite select(store(a, i, k), j) into k if m \models i = j and select(a, j) if m \models i != j struct rd_over_wr_rewriter : public default_rewriter_cfg { ast_manager &m; array_util m_arr; model_evaluator m_eval; expr_ref_vector m_sc; rd_over_wr_rewriter(ast_manager& man, model& mdl): m(man), m_arr(m), m_eval(mdl), m_sc(m) { m_eval.set_model_completion(false); } br_status reduce_app(func_decl *f, unsigned num, expr *const *args, expr_ref &result, proof_ref &result_pr) { if (m_arr.is_select(f) && m_arr.is_store(args[0])) { expr_ref ind1(m), ind2(m); ind1 = m_eval(args[1]); ind2 = m_eval(to_app(args[0])->get_arg(1)); if (ind1 == ind2) { result = to_app(args[0])->get_arg(2); m_sc.push_back(m.mk_eq(args[1], to_app(args[0])->get_arg(1))); return BR_DONE; } m_sc.push_back(m.mk_not(m.mk_eq(args[1], to_app(args[0])->get_arg(1)))); expr_ref_vector new_args(m); new_args.push_back(to_app(args[0])->get_arg(0)); new_args.push_back(args[1]); result = m_arr.mk_select(new_args); return BR_REWRITE1; } return BR_FAILED; } }; // rewrite all occurrences of (as const arr c) to (as const arr v) where v = m_eval(c) struct app_const_arr_rewriter : public default_rewriter_cfg { ast_manager &m; array_util m_arr; datatype_util m_dt_util; model_evaluator m_eval; expr_ref val; app_const_arr_rewriter(ast_manager& man, model& mdl): m(man), m_arr(m), m_dt_util(m), m_eval(mdl), val(m) { m_eval.set_model_completion(false); } br_status reduce_app(func_decl *f, unsigned num, expr *const *args, expr_ref &result, proof_ref &result_pr) { if (m_arr.is_const(f) && !m.is_value(args[0])) { val = m_eval(args[0]); SASSERT(m.is_value(val)); result = m_arr.mk_const_array(f->get_range(), val); return BR_DONE; } if (m_dt_util.is_constructor(f)) { // cons(head(x), tail(x)) --> x ptr_vector const *accessors = m_dt_util.get_constructor_accessors(f); SASSERT(num == accessors->size()); // -- all accessors must have exactly one argument if (any_of(*accessors, [&](const func_decl* acc) { return acc->get_arity() != 1; })) { return BR_FAILED; } if (num >= 1 && is_app(args[0]) && to_app(args[0])->get_decl() == accessors->get(0)) { bool is_all = true; expr* t = to_app(args[0])->get_arg(0); for(unsigned i = 1; i < num && is_all; ++i) { is_all &= (is_app(args[i]) && to_app(args[i])->get_decl() == accessors->get(i) && to_app(args[i])->get_arg(0) == t); } if (is_all) { result = t; return BR_DONE; } } } return BR_FAILED; } }; } void rewrite_as_const_arr(expr* in, model& mdl, expr_ref& out) { app_const_arr_rewriter cfg(out.m(), mdl); rewriter_tpl rw(out.m(), false, cfg); rw(in, out); } void rewrite_read_over_write(expr *in, model &mdl, expr_ref &out) { rd_over_wr_rewriter cfg(out.m(), mdl); rewriter_tpl rw(out.m(), false, cfg); rw(in, out); if (cfg.m_sc.empty()) return; expr_ref_vector sc(out.m()); SASSERT(out.m().is_and(out)); flatten_and(out, sc); sc.append(cfg.m_sc); out = mk_and(sc); } class mbproj::impl { ast_manager& m; params_ref m_params; th_rewriter m_rw; ptr_vector m_plugins; // parameters bool m_reduce_all_selects; bool m_dont_sub; bool m_use_qel; void add_plugin(mbp::project_plugin* p) { family_id fid = p->get_family_id(); SASSERT(!m_plugins.get(fid, nullptr)); m_plugins.setx(fid, p, nullptr); } mbp::project_plugin* get_plugin(app* var) { family_id fid = var->get_sort()->get_family_id(); return m_plugins.get(fid, nullptr); } bool solve(model& model, app_ref_vector& vars, expr_ref_vector& lits) { expr_mark is_var, is_rem; if (vars.empty()) { return false; } bool reduced = false; for (expr* v : vars) is_var.mark(v); expr_ref tmp(m), t(m), v(m); for (unsigned i = 0; i < lits.size(); ++i) { expr* e = lits.get(i), * l, * r; if (m.is_eq(e, l, r) && reduce_eq(is_var, l, r, v, t)) { reduced = true; mbp::project_plugin::erase(lits, i); expr_safe_replace sub(m); sub.insert(v, t); is_rem.mark(v); for (unsigned j = 0; j < lits.size(); ++j) { sub(lits.get(j), tmp); m_rw(tmp); lits[j] = tmp; } } } if (reduced) { unsigned j = 0; for (app* v : vars) if (!is_rem.is_marked(v)) vars[j++] = v; vars.shrink(j); } return reduced; } bool reduce_eq(expr_mark& is_var, expr* l, expr* r, expr_ref& v, expr_ref& t) { if (is_var.is_marked(r)) std::swap(l, r); if (is_var.is_marked(l)) { contains_app cont(m, to_app(l)); if (!cont(r)) { v = to_app(l); t = r; return true; } } return false; } void filter_variables(model& model, app_ref_vector& vars, expr_ref_vector& lits, expr_ref_vector& unused_lits) { expr_mark lit_visited; mbp::project_plugin::mark_rec(lit_visited, lits); unsigned j = 0; for (app* var : vars) if (lit_visited.is_marked(var)) vars[j++] = var; vars.shrink(j); } void project_bools(model& mdl, app_ref_vector& vars, expr_ref_vector& fmls) { expr_safe_replace sub(m); expr_ref val(m); model_evaluator eval(mdl, m_params); eval.set_model_completion(true); unsigned j = 0; for (app* var : vars) { if (m.is_bool(var)) sub.insert(var, eval(var)); else vars[j++] = var; } if (j == vars.size()) return; vars.shrink(j); j = 0; for (expr* fml : fmls) { sub(fml, val); m_rw(val); if (!m.is_true(val)) { TRACE("qe", tout << mk_pp(fml, m) << " -> " << val << "\n";); fmls[j++] = val; } } fmls.shrink(j); } void subst_vars(model_evaluator& eval, app_ref_vector const& vars, expr_ref& fml) { expr_safe_replace sub(m); for (app* v : vars) sub.insert(v, eval(v)); sub(fml); } struct index_term_finder { ast_manager& m; array_util m_array; app_ref m_var; expr_ref_vector& m_res; index_term_finder(ast_manager& mgr, app* v, expr_ref_vector& res) : m(mgr), m_array(m), m_var(v, m), m_res(res) {} void operator() (var* n) {} void operator() (quantifier* n) {} void operator() (app* n) { expr* e1, * e2; if (m_array.is_select(n)) { for (expr* arg : *n) { if (arg->get_sort() == m_var->get_sort() && arg != m_var) m_res.push_back(arg); } } else if (m.is_eq(n, e1, e2)) { if (e1 == m_var) m_res.push_back(e2); else if (e2 == m_var) m_res.push_back(e1); } } }; bool project_var(model_evaluator& eval, app* var, expr_ref& fml) { expr_ref val = eval(var); TRACE("mbqi_project_verbose", tout << "MBQI: var: " << mk_pp(var, m) << "\n" << "fml: " << fml << "\n";); expr_ref_vector terms(m); index_term_finder finder(m, var, terms); for_each_expr(finder, fml); TRACE("mbqi_project_verbose", tout << "terms:\n" << terms;); for (expr* term : terms) { expr_ref tval = eval(term); TRACE("mbqi_project_verbose", tout << "term: " << mk_pp(term, m) << " tval: " << tval << " val: " << val << "\n";); // -- if the term does not contain an occurrence of var // -- and is in the same equivalence class in the model if (tval == val && !occurs(var, term)) { TRACE("mbqi_project", tout << "MBQI: replacing " << mk_pp(var, m) << " with " << mk_pp(term, m) << "\n";); expr_safe_replace sub(m); sub.insert(var, term); sub(fml); return true; } } TRACE("mbqi_project", tout << "MBQI: failed to eliminate " << mk_pp(var, m) << " from " << fml << "\n";); return false; } void project_vars(model& M, app_ref_vector& vars, expr_ref& fml) { model_evaluator eval(M); eval.set_model_completion(false); // -- evaluate to initialize eval cache eval(fml); unsigned j = 0; for (app* v : vars) if (!project_var(eval, v, fml)) vars[j++] = v; vars.shrink(j); } public: opt::inf_eps maximize(expr_ref_vector const& fmls, model& mdl, app* t, expr_ref& ge, expr_ref& gt) { mbp::arith_project_plugin arith(m); return arith.maximize(fmls, mdl, t, ge, gt); } void extract_literals(model& model, app_ref_vector const& vars, expr_ref_vector& fmls) { mbp::project_plugin proj(m); proj.extract_literals(model, vars, fmls); } impl(ast_manager& m, params_ref const& p) :m(m), m_params(p), m_rw(m) { add_plugin(alloc(mbp::arith_project_plugin, m)); add_plugin(alloc(mbp::datatype_project_plugin, m)); add_plugin(alloc(mbp::array_project_plugin, m)); updt_params(p); } ~impl() { std::for_each(m_plugins.begin(), m_plugins.end(), delete_proc()); } void updt_params(params_ref const& p) { m_params.append(p); m_reduce_all_selects = m_params.get_bool("reduce_all_selects", false); m_dont_sub = m_params.get_bool("dont_sub", false); auto q = gparams::get_module("smt"); m_params.append(q); m_use_qel = m_params.get_bool("qsat_use_qel", true); } void preprocess_solve(model& model, app_ref_vector& vars, expr_ref_vector& fmls) { if (m_use_qel) { extract_literals(model, vars, fmls); expr_ref e(m); bool change = true; while (change && !vars.empty()) { change = false; e = mk_and(fmls); do_qel(vars, e); fmls.reset(); flatten_and(e, fmls); for (auto* p : m_plugins) { if (p && p->solve(model, vars, fmls)) { change = true; } } } //rewrite as_const_arr terms expr_ref fml(m); fml = mk_and(fmls); rewrite_as_const_arr(fml, model, fml); flatten_and(fml, fmls); } else { extract_literals(model, vars, fmls); bool change = true; while (change && !vars.empty()) { change = solve(model, vars, fmls); for (auto* p : m_plugins) { if (p && p->solve(model, vars, fmls)) { change = true; } } } } } bool validate_model(model& model, expr_ref_vector const& fmls) { for (expr* f : fmls) { VERIFY(!model.is_false(f)); } return true; } bool has_unsupported_th(const expr_ref_vector fmls) { seq_util seq(m); expr_ref e(m); e = mk_and(fmls); return any_of(subterms::all(e), [&](expr* c) { return seq.is_char(c) || seq.is_seq(c); }); } void operator()(bool force_elim, app_ref_vector& vars, model& model, expr_ref_vector& fmls) { //don't use mbp_qel on some theories where model evaluation is //incomplete This is not a limitation of qel. Fix this either by //making mbp choices less dependent on the model evaluation methods //or fix theory rewriters to make terms evaluation complete if (m_use_qel && !has_unsupported_th(fmls)) { bool dsub = m_dont_sub; m_dont_sub = !force_elim; expr_ref fml(m); fml = mk_and(fmls); spacer_qel(vars, model, fml); fmls.reset(); flatten_and(fml, fmls); m_dont_sub = dsub; } else { mbp(force_elim, vars, model, fmls); } } void mbp(bool force_elim, app_ref_vector& vars, model& model, expr_ref_vector& fmls) { SASSERT(validate_model(model, fmls)); expr_ref val(m), tmp(m); app_ref var(m); expr_ref_vector unused_fmls(m); bool progress = true; preprocess_solve(model, vars, fmls); filter_variables(model, vars, fmls, unused_fmls); project_bools(model, vars, fmls); while (progress && !vars.empty() && !fmls.empty() && m.limit().inc()) { app_ref_vector new_vars(m); progress = false; for (mbp::project_plugin* p : m_plugins) { if (p) (*p)(model, vars, fmls); } while (!vars.empty() && !fmls.empty() && m.limit().inc()) { var = vars.back(); vars.pop_back(); mbp::project_plugin* p = get_plugin(var); if (p && (*p)(model, var, vars, fmls)) { progress = true; } else { new_vars.push_back(var); } } if (!progress && !new_vars.empty() && !fmls.empty() && force_elim && m.limit().inc()) { var = new_vars.back(); new_vars.pop_back(); expr_safe_replace sub(m); val = model(var); sub.insert(var, val); unsigned j = 0; for (expr* f : fmls) { sub(f, tmp); m_rw(tmp); if (!m.is_true(tmp)) fmls[j++] = tmp; } fmls.shrink(j); progress = true; } if (!m.limit().inc()) return; vars.append(new_vars); if (progress) { preprocess_solve(model, vars, fmls); } } if (fmls.empty()) { vars.reset(); } fmls.append(unused_fmls); SASSERT(validate_model(model, fmls)); TRACE("qe", tout << vars << " " << fmls << "\n";); } void do_qe_lite(app_ref_vector& vars, expr_ref& fml) { qe_lite qe(m, m_params, false); qe(vars, fml); m_rw(fml); TRACE("qe", tout << "After qe_lite:\n" << fml << "\n" << "Vars: " << vars << "\n";); SASSERT(!m.is_false(fml)); } void do_qel(app_ref_vector &vars, expr_ref &fml) { qel qe(m, m_params); qe(vars, fml); m_rw(fml); TRACE("qe", tout << "After qel:\n" << fml << "\n" << "Vars: " << vars << "\n";); SASSERT(!m.is_false(fml)); } void do_qe_bool(model& mdl, app_ref_vector& vars, expr_ref& fml) { expr_ref_vector fmls(m); fmls.push_back(fml); project_bools(mdl, vars, fmls); fml = mk_and(fmls); } void qel_project(app_ref_vector &vars, model &mdl, expr_ref &fml, bool reduce_all_selects) { flatten_and(fml); mbp::mbp_qel mbptg(m, m_params); mbptg(vars, fml, mdl); if (reduce_all_selects) rewrite_read_over_write(fml, mdl, fml); m_rw(fml); TRACE("qe", tout << "After mbp_tg:\n" << fml << " models " << mdl.is_true(fml) << "\n" << "Vars: " << vars << "\n";); } void spacer_qel(app_ref_vector& vars, model& mdl, expr_ref& fml) { TRACE("qe", tout << "Before projection:\n" << fml << "\n" << "Vars: " << vars << "\n";); model_evaluator eval(mdl, m_params); eval.set_model_completion(true); app_ref_vector other_vars(m); app_ref_vector sub_vars(m); array_util arr_u(m); arith_util ari_u(m); datatype_util dt_u(m); do_qel(vars, fml); qel_project(vars, mdl, fml, m_reduce_all_selects); flatten_and(fml); m_rw(fml); rewrite_as_const_arr(fml, mdl, fml); for (app* v : vars) { SASSERT(!arr_u.is_array(v) && !dt_u.is_datatype(v->get_sort())); other_vars.push_back(v); } // project reals, ints and other variables. if (!other_vars.empty()) { TRACE("qe", tout << "Other vars: " << other_vars << "\n" << mdl;); expr_ref_vector fmls(m); flatten_and(fml, fmls); mbp(false, other_vars, mdl, fmls); fml = mk_and(fmls); m_rw(fml); TRACE("qe", tout << "Projected other vars:\n" << fml << "\n"; tout << "Remaining other vars:\n" << other_vars << "\n";); SASSERT(!m.is_false(fml)); } if (!other_vars.empty()) { project_vars(mdl, other_vars, fml); m_rw(fml); } // substitute any remaining other vars if (!m_dont_sub && !other_vars.empty()) { subst_vars(eval, other_vars, fml); TRACE("qe", tout << "After substituting remaining other vars:\n" << fml << "\n";); // an extra round of simplification because subst_vars is not simplifying m_rw(fml); other_vars.reset(); } SASSERT(!eval.is_false(fml)); vars.reset(); vars.append(other_vars); } void spacer(app_ref_vector& vars, model& mdl, expr_ref& fml) { if (m_use_qel) { spacer_qel(vars, mdl, fml); } else { spacer_qe_lite(vars, mdl, fml); } } void spacer_qe_lite(app_ref_vector& vars, model& mdl, expr_ref& fml) { TRACE("qe", tout << "Before projection:\n" << fml << "\n" << "Vars: " << vars << "\n";); model_evaluator eval(mdl, m_params); eval.set_model_completion(true); app_ref_vector other_vars(m); app_ref_vector array_vars(m); array_util arr_u(m); arith_util ari_u(m); flatten_and(fml); while (!vars.empty()) { do_qe_lite(vars, fml); do_qe_bool(mdl, vars, fml); // sort out vars into bools, arith (int/real), and arrays for (app* v : vars) { if (arr_u.is_array(v)) { array_vars.push_back(v); } else { other_vars.push_back(v); } } TRACE("qe", tout << "Array vars: " << array_vars << "\n";); vars.reset(); // project arrays mbp::array_project_plugin ap(m); ap(mdl, array_vars, fml, vars, m_reduce_all_selects); SASSERT(array_vars.empty()); m_rw(fml); SASSERT(!m.is_false(fml)); TRACE("qe", tout << "extended model:\n" << mdl; tout << "Vars: " << vars << "\n";); } // project reals, ints and other variables. if (!other_vars.empty()) { TRACE("qe", tout << "Other vars: " << other_vars << "\n" << mdl;); expr_ref_vector fmls(m); flatten_and(fml, fmls); (*this)(false, other_vars, mdl, fmls); fml = mk_and(fmls); m_rw(fml); TRACE("qe", tout << "Projected other vars:\n" << fml << "\n"; tout << "Remaining other vars:\n" << other_vars << "\n";); SASSERT(!m.is_false(fml)); } if (!other_vars.empty()) { project_vars(mdl, other_vars, fml); m_rw(fml); } // substitute any remaining other vars if (!m_dont_sub && !other_vars.empty()) { subst_vars(eval, other_vars, fml); TRACE("qe", tout << "After substituting remaining other vars:\n" << fml << "\n";); // an extra round of simplification because subst_vars is not simplifying m_rw(fml); other_vars.reset(); } SASSERT(!eval.is_false(fml)); vars.reset(); vars.append(other_vars); } }; mbproj::mbproj(ast_manager& m, params_ref const& p) { scoped_no_proof _sp(m); m_impl = alloc(impl, m, p); } mbproj::~mbproj() { dealloc(m_impl); } void mbproj::updt_params(params_ref const& p) { m_impl->updt_params(p); } void mbproj::get_param_descrs(param_descrs& r) { r.insert("reduce_all_selects", CPK_BOOL, "(default: false) reduce selects"); r.insert("dont_sub", CPK_BOOL, "(default: false) disable substitution of values for free variables"); r.insert("use_qel", CPK_BOOL, "(default: true) use egraph based QEL"); } void mbproj::operator()(bool force_elim, app_ref_vector& vars, model& mdl, expr_ref_vector& fmls) { scoped_no_proof _sp(fmls.get_manager()); (*m_impl)(force_elim, vars, mdl, fmls); } void mbproj::spacer(app_ref_vector& vars, model& mdl, expr_ref& fml) { scoped_no_proof _sp(fml.get_manager()); m_impl->spacer(vars, mdl, fml); } void mbproj::solve(model& model, app_ref_vector& vars, expr_ref_vector& fmls) { scoped_no_proof _sp(fmls.get_manager()); m_impl->preprocess_solve(model, vars, fmls); } opt::inf_eps mbproj::maximize(expr_ref_vector const& fmls, model& mdl, app* t, expr_ref& ge, expr_ref& gt) { scoped_no_proof _sp(fmls.get_manager()); return m_impl->maximize(fmls, mdl, t, ge, gt); } template class rewriter_tpl; template class rewriter_tpl;