From 94b32b8c636f445ada798021c390ba6d84a0ce50 Mon Sep 17 00:00:00 2001 From: Arie Gurfinkel Date: Wed, 27 Apr 2022 10:38:11 -0400 Subject: [PATCH 01/78] Make spacer_sem_matcher::reset() public --- src/muz/spacer/spacer_sem_matcher.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/muz/spacer/spacer_sem_matcher.h b/src/muz/spacer/spacer_sem_matcher.h index f39aef30745..1df4a77b52a 100644 --- a/src/muz/spacer/spacer_sem_matcher.h +++ b/src/muz/spacer/spacer_sem_matcher.h @@ -38,11 +38,11 @@ class sem_matcher { substitution * m_subst; svector m_todo; - void reset(); bool match_var(var *v, expr *e); public: sem_matcher(ast_manager &man); + void reset(); /** \brief Return true if e2 is an instance of e1. From ed44d3b2a8c170584f6026fabd8e051eb54bae97 Mon Sep 17 00:00:00 2001 From: hgvk94 Date: Tue, 16 Feb 2021 10:41:44 -0500 Subject: [PATCH 02/78] Add .clang-format for src/muz/spacer --- src/muz/spacer/.clang-format | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 src/muz/spacer/.clang-format diff --git a/src/muz/spacer/.clang-format b/src/muz/spacer/.clang-format new file mode 100644 index 00000000000..d1e1290601a --- /dev/null +++ b/src/muz/spacer/.clang-format @@ -0,0 +1,8 @@ +--- +BasedOnStyle: LLVM +AllowShortFunctionsOnASingleLine: All +IndentWidth: '4' +AllowShortBlocksOnASingleLine: true +AllowShortIfStatementsOnASingleLine: true +AllowShortLoopsOnASingleLine: true +... From bd52554ff3386d8eca9b0fef04bc417dbfca9220 Mon Sep 17 00:00:00 2001 From: Arie Gurfinkel Date: Wed, 27 Apr 2022 10:37:18 -0400 Subject: [PATCH 03/78] Mark substitution::get_bindings() as const --- src/ast/substitution/substitution.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ast/substitution/substitution.h b/src/ast/substitution/substitution.h index 336272621a7..e2329fb83bf 100644 --- a/src/ast/substitution/substitution.h +++ b/src/ast/substitution/substitution.h @@ -152,7 +152,7 @@ class substitution { return find(to_var(v.get_expr()), v.get_offset(), r); } - void get_binding(unsigned binding_num, var_offset& var, expr_offset& r) { + void get_binding(unsigned binding_num, var_offset& var, expr_offset& r) const { var = m_vars[binding_num]; VERIFY(m_subst.find(var.first, var.second, r)); } From 87ff0e98fbaece146b449b08df4c40550c2c7fa5 Mon Sep 17 00:00:00 2001 From: hgvk94 Date: Fri, 12 Feb 2021 16:38:00 -0500 Subject: [PATCH 04/78] Fix in spacer_antiunify --- src/muz/spacer/spacer_antiunify.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/muz/spacer/spacer_antiunify.cpp b/src/muz/spacer/spacer_antiunify.cpp index d51d240b0a5..11fe80f607e 100644 --- a/src/muz/spacer/spacer_antiunify.cpp +++ b/src/muz/spacer/spacer_antiunify.cpp @@ -155,6 +155,7 @@ void anti_unifier::operator()(expr *e1, expr *e2, expr_ref &res, m_pinned.push_back(u); m_cache.insert(n1, n2, u); } + m_todo.pop_back(); } expr *r; From 4dcd1bfc8716809a58d0960f0374163a1874dc8e Mon Sep 17 00:00:00 2001 From: Arie Gurfinkel Date: Wed, 27 Apr 2022 16:56:35 -0400 Subject: [PATCH 05/78] Various helper methods in spacer_util Minor functions to compute number of free variables, detect presence of certain sub-expressions, etc. The diff is ugly because of clang-format --- src/muz/spacer/spacer_util.cpp | 1787 ++++++++++++++++---------------- src/muz/spacer/spacer_util.h | 237 +++-- 2 files changed, 1054 insertions(+), 970 deletions(-) diff --git a/src/muz/spacer/spacer_util.cpp b/src/muz/spacer/spacer_util.cpp index d16b3797226..40e02551fca 100644 --- a/src/muz/spacer/spacer_util.cpp +++ b/src/muz/spacer/spacer_util.cpp @@ -25,986 +25,1037 @@ Revision History: --*/ -#include #include +#include -#include "util/util.h" +#include "ast/arith_decl_plugin.h" +#include "ast/array_decl_plugin.h" #include "ast/ast.h" -#include "ast/occurs.h" #include "ast/ast_pp.h" -#include "ast/scoped_proof.h" +#include "ast/bv_decl_plugin.h" +#include "ast/datatype_decl_plugin.h" #include "ast/for_each_expr.h" +#include "ast/occurs.h" #include "ast/rewriter/bool_rewriter.h" +#include "ast/rewriter/expr_replacer.h" #include "ast/rewriter/expr_safe_replace.h" -#include "ast/array_decl_plugin.h" -#include "ast/arith_decl_plugin.h" -#include "ast/datatype_decl_plugin.h" -#include "ast/bv_decl_plugin.h" +#include "ast/rewriter/factor_equivs.h" #include "ast/rewriter/rewriter.h" #include "ast/rewriter/rewriter_def.h" -#include "ast/rewriter/factor_equivs.h" -#include "ast/rewriter/expr_replacer.h" - +#include "ast/scoped_proof.h" +#include "util/util.h" -#include "smt/params/smt_params.h" #include "model/model.h" #include "model/model_evaluator.h" -#include "model/model_smt2_pp.h" #include "model/model_pp.h" +#include "model/model_smt2_pp.h" +#include "smt/params/smt_params.h" #include "qe/lite/qe_lite.h" -#include "qe/qe_mbp.h" -#include "qe/mbp/mbp_term_graph.h" #include "qe/mbp/mbp_plugin.h" +#include "qe/mbp/mbp_term_graph.h" +#include "qe/qe_mbp.h" -#include "tactic/tactical.h" -#include "tactic/core/propagate_values_tactic.h" -#include "tactic/arith/propagate_ineqs_tactic.h" #include "tactic/arith/arith_bounds_tactic.h" +#include "tactic/arith/propagate_ineqs_tactic.h" +#include "tactic/core/propagate_values_tactic.h" +#include "tactic/tactical.h" #include "muz/base/dl_util.h" #include "muz/spacer/spacer_legacy_mev.h" -#include "muz/spacer/spacer_qe_project.h" #include "muz/spacer/spacer_manager.h" +#include "muz/spacer/spacer_qe_project.h" #include "muz/spacer/spacer_util.h" namespace spacer { - bool is_clause(ast_manager& m, expr* n) { - if (spacer::is_literal(m, n)) - return true; - if (m.is_or(n)) { - for (expr* arg : *to_app(n)) { - if (!spacer::is_literal(m, arg)) - return false; - return true; - } - } - return false; - } - - bool is_literal(ast_manager& m, expr* n) { - return spacer::is_atom(m, n) || (m.is_not(n) && spacer::is_atom(m, to_app(n)->get_arg(0))); - } - - bool is_atom(ast_manager& m, expr* n) { - if (is_quantifier(n) || !m.is_bool(n)) - return false; - if (is_var(n)) - return true; - SASSERT(is_app(n)); - if (to_app(n)->get_family_id() != m.get_basic_family_id()) { +bool is_clause(ast_manager &m, expr *n) { + if (spacer::is_literal(m, n)) return true; + if (m.is_or(n)) { + for (expr *arg : *to_app(n)) { + if (!spacer::is_literal(m, arg)) return false; return true; } - - if ((m.is_eq(n) && !m.is_bool(to_app(n)->get_arg(0))) || - m.is_true(n) || m.is_false(n)) - return true; - - // x=y is atomic if x and y are Bool and atomic - expr* e1, * e2; - if (m.is_eq(n, e1, e2) && spacer::is_atom(m, e1) && spacer::is_atom(m, e2)) - return true; - return false; } + return false; +} + +bool is_literal(ast_manager &m, expr *n) { + return spacer::is_atom(m, n) || + (m.is_not(n) && spacer::is_atom(m, to_app(n)->get_arg(0))); +} + +bool is_atom(ast_manager &m, expr *n) { + if (is_quantifier(n) || !m.is_bool(n)) return false; + if (is_var(n)) return true; + SASSERT(is_app(n)); + if (to_app(n)->get_family_id() != m.get_basic_family_id()) { return true; } + + if ((m.is_eq(n) && !m.is_bool(to_app(n)->get_arg(0))) || m.is_true(n) || + m.is_false(n)) + return true; + + // x=y is atomic if x and y are Bool and atomic + expr *e1, *e2; + if (m.is_eq(n, e1, e2) && spacer::is_atom(m, e1) && spacer::is_atom(m, e2)) + return true; + return false; +} + +void subst_vars(ast_manager &m, app_ref_vector const &vars, model &mdl, + expr_ref &fml) { + model::scoped_model_completion _sc_(mdl, true); + expr_safe_replace sub(m); + for (app *v : vars) sub.insert(v, mdl(v)); + sub(fml); +} + +void to_mbp_benchmark(std::ostream &out, expr *fml, + const app_ref_vector &vars) { + ast_manager &m = vars.m(); + ast_pp_util pp(m); + pp.collect(fml); + pp.display_decls(out); + + out << "(define-fun mbp_benchmark_fml () Bool\n "; + out << mk_pp(fml, m) << ")\n\n"; + + out << "(push 1)\n" + << "(assert mbp_benchmark_fml)\n" + << "(check-sat)\n" + << "(mbp mbp_benchmark_fml ("; + for (auto v : vars) { out << mk_pp(v, m) << " "; } + out << "))\n" + << "(pop 1)\n" + << "(exit)\n"; +} + +void qe_project_z3(ast_manager &m, app_ref_vector &vars, expr_ref &fml, + model &mdl, bool reduce_all_selects, bool use_native_mbp, + bool dont_sub) { + params_ref p; + p.set_bool("reduce_all_selects", reduce_all_selects); + p.set_bool("dont_sub", dont_sub); + + qe::mbproj mbp(m, p); + mbp.spacer(vars, mdl, fml); +} + +/* + * eliminate simple equalities using qe_lite + * then, MBP for Booleans (substitute), reals (based on LW), ints (based on + * Cooper), and arrays + */ +void qe_project_spacer(ast_manager &m, app_ref_vector &vars, expr_ref &fml, + model &mdl, bool reduce_all_selects, bool use_native_mbp, + bool dont_sub) { + th_rewriter rw(m); + TRACE("spacer_mbp", tout << "Before projection:\n"; tout << fml << "\n"; + tout << "Vars:\n" + << vars;); - void subst_vars(ast_manager& m, - app_ref_vector const& vars, model& mdl, expr_ref& fml) { - model::scoped_model_completion _sc_(mdl, true); - expr_safe_replace sub(m); - for (app* v : vars) sub.insert(v, mdl(v)); - sub(fml); + { + // Ensure that top-level AND of fml is flat + expr_ref_vector flat(m); + flatten_and(fml, flat); + fml = mk_and(flat); } - void to_mbp_benchmark(std::ostream& out, expr* fml, const app_ref_vector& vars) { - ast_manager& m = vars.m(); - ast_pp_util pp(m); - pp.collect(fml); - pp.display_decls(out); + // uncomment for benchmarks + // to_mbp_benchmark(verbose_stream(), fml, vars); - out << "(define-fun mbp_benchmark_fml () Bool\n "; - out << mk_pp(fml, m) << ")\n\n"; + app_ref_vector arith_vars(m); + app_ref_vector array_vars(m); + array_util arr_u(m); + arith_util ari_u(m); + expr_safe_replace bool_sub(m); + expr_ref bval(m); - out << "(push 1)\n" - << "(assert mbp_benchmark_fml)\n" - << "(check-sat)\n" - << "(mbp mbp_benchmark_fml ("; - for (auto v : vars) { out << mk_pp(v, m) << " "; } - out << "))\n" - << "(pop 1)\n" - << "(exit)\n"; - } - - void qe_project_z3(ast_manager& m, app_ref_vector& vars, expr_ref& fml, - model& mdl, bool reduce_all_selects, bool use_native_mbp, - bool dont_sub) { + while (true) { params_ref p; - p.set_bool("reduce_all_selects", reduce_all_selects); - p.set_bool("dont_sub", dont_sub); - - qe::mbproj mbp(m, p); - mbp.spacer(vars, mdl, fml); - } - - /* - * eliminate simple equalities using qe_lite - * then, MBP for Booleans (substitute), reals (based on LW), ints (based on Cooper), and arrays - */ - void qe_project_spacer(ast_manager& m, app_ref_vector& vars, expr_ref& fml, - model& mdl, bool reduce_all_selects, bool use_native_mbp, - bool dont_sub) { - th_rewriter rw(m); - TRACE("spacer_mbp", - tout << "Before projection:\n"; - tout << fml << "\n"; - tout << "Vars:\n" << vars;); - - { - // Ensure that top-level AND of fml is flat - expr_ref_vector flat(m); - flatten_and(fml, flat); - fml = mk_and(flat); + qe_lite qe(m, p, false); + qe(vars, fml); + rw(fml); + + TRACE("spacer_mbp", tout << "After qe_lite:\n"; + tout << mk_pp(fml, m) << "\n"; tout << "Vars:\n" + << vars;); + + SASSERT(!m.is_false(fml)); + + // sort out vars into bools, arith (int/real), and arrays + for (app *v : vars) { + if (m.is_bool(v)) { + // obtain the interpretation of the ith var + // using model completion + model::scoped_model_completion _sc_(mdl, true); + bool_sub.insert(v, mdl(v)); + } else if (arr_u.is_array(v)) { + array_vars.push_back(v); + } else { + SASSERT(ari_u.is_int(v) || ari_u.is_real(v)); + arith_vars.push_back(v); + } } - - // uncomment for benchmarks - //to_mbp_benchmark(verbose_stream(), fml, vars); - - app_ref_vector arith_vars(m); - app_ref_vector array_vars(m); - array_util arr_u(m); - arith_util ari_u(m); - expr_safe_replace bool_sub(m); - expr_ref bval(m); - - while (true) { - params_ref p; - qe_lite qe(m, p, false); - qe(vars, fml); + // substitute Booleans + if (!bool_sub.empty()) { + bool_sub(fml); + // -- bool_sub is not simplifying rw(fml); - - TRACE("spacer_mbp", - tout << "After qe_lite:\n"; - tout << mk_pp(fml, m) << "\n"; - tout << "Vars:\n" << vars;); - SASSERT(!m.is_false(fml)); - - - // sort out vars into bools, arith (int/real), and arrays - for (app* v : vars) { - if (m.is_bool(v)) { - // obtain the interpretation of the ith var - // using model completion - model::scoped_model_completion _sc_(mdl, true); - bool_sub.insert(v, mdl(v)); - } - else if (arr_u.is_array(v)) { - array_vars.push_back(v); - } - else { - SASSERT(ari_u.is_int(v) || ari_u.is_real(v)); - arith_vars.push_back(v); - } - } - - // substitute Booleans - if (!bool_sub.empty()) { - bool_sub(fml); - // -- bool_sub is not simplifying - rw(fml); - SASSERT(!m.is_false(fml)); - TRACE("spacer_mbp", tout << "Projected Booleans:\n" << fml << "\n"; ); - bool_sub.reset(); - } - - TRACE("spacer_mbp", tout << "Array vars:\n"; tout << array_vars;); - - vars.reset(); - - // project arrays - { - scoped_no_proof _sp(m); - // -- local rewriter that is aware of current proof mode - th_rewriter srw(m); - spacer_qe::array_project(mdl, array_vars, fml, vars, reduce_all_selects); - SASSERT(array_vars.empty()); - srw(fml); - SASSERT(!m.is_false(fml)); - } - - TRACE("spacer_mbp", - tout << "extended model:\n"; - model_pp(tout, mdl); - tout << "Auxiliary variables of index and value sorts:\n"; - tout << vars;); - - if (vars.empty()) { break; } + TRACE("spacer_mbp", tout << "Projected Booleans:\n" + << fml << "\n";); + bool_sub.reset(); } - // project reals and ints - if (!arith_vars.empty()) { - TRACE("spacer_mbp", tout << "Arith vars:\n" << arith_vars;); + TRACE("spacer_mbp", tout << "Array vars:\n"; tout << array_vars;); - if (use_native_mbp) { - qe::mbproj mbp(m); - expr_ref_vector fmls(m); - flatten_and(fml, fmls); - - mbp(true, arith_vars, mdl, fmls); - fml = mk_and(fmls); - SASSERT(arith_vars.empty()); - } - else { - scoped_no_proof _sp(m); - spacer_qe::arith_project(mdl, arith_vars, fml); - } + vars.reset(); - TRACE("spacer_mbp", - tout << "Projected arith vars:\n" << fml << "\n"; - tout << "Remaining arith vars:\n" << arith_vars << "\n";); + // project arrays + { + scoped_no_proof _sp(m); + // -- local rewriter that is aware of current proof mode + th_rewriter srw(m); + spacer_qe::array_project(mdl, array_vars, fml, vars, + reduce_all_selects); + SASSERT(array_vars.empty()); + srw(fml); SASSERT(!m.is_false(fml)); } - if (!arith_vars.empty()) { - mbqi_project(mdl, arith_vars, fml); - } + TRACE("spacer_mbp", tout << "extended model:\n"; model_pp(tout, mdl); + tout << "Auxiliary variables of index and value sorts:\n"; + tout << vars;); - // substitute any remaining arith vars - if (!dont_sub && !arith_vars.empty()) { - subst_vars(m, arith_vars, mdl, fml); - TRACE("spacer_mbp", - tout << "After substituting remaining arith vars:\n"; - tout << mk_pp(fml, m) << "\n"; - ); - // an extra round of simplification because subst_vars is not simplifying - rw(fml); - } - - DEBUG_CODE( - model_evaluator mev(mdl); - mev.set_model_completion(false); - SASSERT(mev.is_true(fml)); - ); + if (vars.empty()) { break; } + } - vars.reset(); - if (dont_sub && !arith_vars.empty()) { - vars.append(arith_vars); + // project reals and ints + if (!arith_vars.empty()) { + TRACE("spacer_mbp", tout << "Arith vars:\n" << arith_vars;); + + if (use_native_mbp) { + qe::mbproj mbp(m); + expr_ref_vector fmls(m); + flatten_and(fml, fmls); + + mbp(true, arith_vars, mdl, fmls); + fml = mk_and(fmls); + SASSERT(arith_vars.empty()); + } else { + scoped_no_proof _sp(m); + spacer_qe::arith_project(mdl, arith_vars, fml); } + + TRACE("spacer_mbp", tout << "Projected arith vars:\n" + << fml << "\n"; + tout << "Remaining arith vars:\n" + << arith_vars << "\n";); + SASSERT(!m.is_false(fml)); } + if (!arith_vars.empty()) { mbqi_project(mdl, arith_vars, fml); } - static expr* apply_accessor(ast_manager& m, - ptr_vector const& acc, - unsigned j, - func_decl* f, - expr* c) { - if (is_app(c) && to_app(c)->get_decl() == f) { - return to_app(c)->get_arg(j); - } - else { - return m.mk_app(acc[j], c); - } + // substitute any remaining arith vars + if (!dont_sub && !arith_vars.empty()) { + subst_vars(m, arith_vars, mdl, fml); + TRACE("spacer_mbp", + tout << "After substituting remaining arith vars:\n"; + tout << mk_pp(fml, m) << "\n";); + // an extra round of simplification because subst_vars is not + // simplifying + rw(fml); } - void qe_project(ast_manager& m, app_ref_vector& vars, expr_ref& fml, - model& mdl, bool reduce_all_selects, bool use_native_mbp, - bool dont_sub) { - if (use_native_mbp) - qe_project_z3(m, vars, fml, mdl, - reduce_all_selects, use_native_mbp, dont_sub); - else - qe_project_spacer(m, vars, fml, mdl, - reduce_all_selects, use_native_mbp, dont_sub); - } - - void expand_literals(ast_manager& m, expr_ref_vector& conjs) { - if (conjs.empty()) - return; - arith_util arith(m); - datatype_util dt(m); - bv_util bv(m); - expr* e1, * e2, * c, * val; - rational r; - unsigned bv_size; - - TRACE("spacer_expand", tout << "begin expand\n" << conjs << "\n";); - - for (unsigned i = 0; i < conjs.size(); ++i) { - expr* e = conjs[i].get(); - if (m.is_eq(e, e1, e2) && arith.is_int_real(e1)) { - conjs[i] = arith.mk_le(e1, e2); - if (i + 1 == conjs.size()) { - conjs.push_back(arith.mk_ge(e1, e2)); - } - else { - conjs.push_back(conjs[i + 1].get()); - conjs[i + 1] = arith.mk_ge(e1, e2); - } - ++i; + DEBUG_CODE(model_evaluator mev(mdl); mev.set_model_completion(false); + SASSERT(mev.is_true(fml));); + + vars.reset(); + if (dont_sub && !arith_vars.empty()) { vars.append(arith_vars); } +} + +static expr *apply_accessor(ast_manager &m, ptr_vector const &acc, + unsigned j, func_decl *f, expr *c) { + if (is_app(c) && to_app(c)->get_decl() == f) { + return to_app(c)->get_arg(j); + } else { + return m.mk_app(acc[j], c); + } +} + +void qe_project(ast_manager &m, app_ref_vector &vars, expr_ref &fml, model &mdl, + bool reduce_all_selects, bool use_native_mbp, bool dont_sub) { + if (use_native_mbp) + qe_project_z3(m, vars, fml, mdl, reduce_all_selects, use_native_mbp, + dont_sub); + else + qe_project_spacer(m, vars, fml, mdl, reduce_all_selects, use_native_mbp, + dont_sub); +} + +void expand_literals(ast_manager &m, expr_ref_vector &conjs) { + if (conjs.empty()) return; + arith_util arith(m); + datatype_util dt(m); + bv_util bv(m); + expr *e1, *e2, *c, *val; + rational r; + unsigned bv_size; + + TRACE("spacer_expand", tout << "begin expand\n" << conjs << "\n";); + + for (unsigned i = 0; i < conjs.size(); ++i) { + expr *e = conjs[i].get(); + if (m.is_eq(e, e1, e2) && arith.is_int_real(e1)) { + conjs[i] = arith.mk_le(e1, e2); + if (i + 1 == conjs.size()) { + conjs.push_back(arith.mk_ge(e1, e2)); + } else { + conjs.push_back(conjs[i + 1].get()); + conjs[i + 1] = arith.mk_ge(e1, e2); } - else if ((m.is_eq(e, c, val) && is_app(val) && dt.is_constructor(to_app(val))) || - (m.is_eq(e, val, c) && is_app(val) && dt.is_constructor(to_app(val)))) { - func_decl* f = to_app(val)->get_decl(); - func_decl* r = dt.get_constructor_is(f); - conjs[i] = m.mk_app(r, c); - ptr_vector const& acc = *dt.get_constructor_accessors(f); - for (unsigned j = 0; j < acc.size(); ++j) { - conjs.push_back(m.mk_eq(apply_accessor(m, acc, j, f, c), to_app(val)->get_arg(j))); - } + ++i; + } else if ((m.is_eq(e, c, val) && is_app(val) && + dt.is_constructor(to_app(val))) || + (m.is_eq(e, val, c) && is_app(val) && + dt.is_constructor(to_app(val)))) { + func_decl *f = to_app(val)->get_decl(); + func_decl *r = dt.get_constructor_is(f); + conjs[i] = m.mk_app(r, c); + ptr_vector const &acc = *dt.get_constructor_accessors(f); + for (unsigned j = 0; j < acc.size(); ++j) { + conjs.push_back(m.mk_eq(apply_accessor(m, acc, j, f, c), + to_app(val)->get_arg(j))); } - else if ((m.is_eq(e, c, val) && bv.is_numeral(val, r, bv_size)) || - (m.is_eq(e, val, c) && bv.is_numeral(val, r, bv_size))) { - rational two(2); - for (unsigned j = 0; j < bv_size; ++j) { - parameter p(j); - expr* e = m.mk_eq(m.mk_app(bv.get_family_id(), OP_BIT1), bv.mk_extract(j, j, c)); - if ((r % two).is_zero()) { - e = m.mk_not(e); - } - r = div(r, two); - if (j == 0) { - conjs[i] = e; - } - else { - conjs.push_back(e); - } + } else if ((m.is_eq(e, c, val) && bv.is_numeral(val, r, bv_size)) || + (m.is_eq(e, val, c) && bv.is_numeral(val, r, bv_size))) { + rational two(2); + for (unsigned j = 0; j < bv_size; ++j) { + parameter p(j); + expr *e = m.mk_eq(m.mk_app(bv.get_family_id(), OP_BIT1), + bv.mk_extract(j, j, c)); + if ((r % two).is_zero()) { e = m.mk_not(e); } + r = div(r, two); + if (j == 0) { + conjs[i] = e; + } else { + conjs.push_back(e); } } } - TRACE("spacer_expand", tout << "end expand\n" << conjs << "\n";); } - - namespace { - class implicant_picker { - model& m_model; - ast_manager& m; - arith_util m_arith; - - expr_ref_vector m_todo; - expr_mark m_visited; - - // add literal to the implicant - // applies lightweight normalization - void add_literal(expr* e, expr_ref_vector& out) { - SASSERT(m.is_bool(e)); - - expr_ref res(m), v(m); - v = m_model(e); - // the literal must have a value - SASSERT(m.limit().is_canceled() || m.is_true(v) || m.is_false(v)); - - res = m.is_false(v) ? m.mk_not(e) : e; - - if (m.is_distinct(res)) { - // --(distinct a b) == (not (= a b)) - if (to_app(res)->get_num_args() == 2) { - res = m.mk_eq(to_app(res)->get_arg(0), - to_app(res)->get_arg(1)); - res = m.mk_not(res); - } - } - - expr* nres = nullptr, * f1 = nullptr, * f2 = nullptr; - if (m.is_not(res, nres)) { - // --(not (xor a b)) == (= a b) - if (m.is_xor(nres, f1, f2)) - res = m.mk_eq(f1, f2); - // -- split arithmetic inequality - else if (m.is_eq(nres, f1, f2) && m_arith.is_int_real(f1)) { - res = m_arith.mk_lt(f1, f2); - if (!m_model.is_true(res)) - res = m_arith.mk_lt(f2, f1); - } - } - - - if (!m_model.is_true(res)) { - IF_VERBOSE(2, verbose_stream() - << "(spacer-model-anomaly: " << res << ")\n"); - } - out.push_back(res); + TRACE("spacer_expand", tout << "end expand\n" << conjs << "\n";); +} + +namespace { +class implicant_picker { + model &m_model; + ast_manager &m; + arith_util m_arith; + + expr_ref_vector m_todo; + expr_mark m_visited; + + // add literal to the implicant + // applies lightweight normalization + void add_literal(expr *e, expr_ref_vector &out) { + SASSERT(m.is_bool(e)); + + expr_ref res(m), v(m); + v = m_model(e); + // the literal must have a value + SASSERT(m.limit().is_canceled() || m.is_true(v) || m.is_false(v)); + + res = m.is_false(v) ? m.mk_not(e) : e; + + if (m.is_distinct(res)) { + // --(distinct a b) == (not (= a b)) + if (to_app(res)->get_num_args() == 2) { + res = m.mk_eq(to_app(res)->get_arg(0), to_app(res)->get_arg(1)); + res = m.mk_not(res); } + } - void process_app(app* a, expr_ref_vector& out) { - if (m_visited.is_marked(a)) return; - SASSERT(m.is_bool(a)); - expr_ref v(m); - v = m_model(a); - bool is_true = m.is_true(v); - - if (!is_true && !m.is_false(v)) return; + expr *nres = nullptr, *f1 = nullptr, *f2 = nullptr; + if (m.is_not(res, nres)) { + // --(not (xor a b)) == (= a b) + if (m.is_xor(nres, f1, f2)) res = m.mk_eq(f1, f2); + // -- split arithmetic inequality + else if (m.is_eq(nres, f1, f2) && m_arith.is_int_real(f1)) { + res = m_arith.mk_lt(f1, f2); + if (!m_model.is_true(res)) res = m_arith.mk_lt(f2, f1); + } + } - expr* na = nullptr, * f1 = nullptr, * f2 = nullptr, * f3 = nullptr; + if (!m_model.is_true(res)) { + IF_VERBOSE(2, verbose_stream() + << "(spacer-model-anomaly: " << res << ")\n"); + } + out.push_back(res); + } - SASSERT(!m.is_false(a)); - if (m.is_true(a)) { - // noop - } - else if (a->get_family_id() != m.get_basic_family_id()) { - add_literal(a, out); - } - else if (is_uninterp_const(a)) { - add_literal(a, out); - } - else if (m.is_not(a, na)) { - m_todo.push_back(na); - } - else if (m.is_distinct(a)) { - if (!is_true) { - expr_ref tmp = mbp::project_plugin::pick_equality(m, m_model, a); - m_todo.push_back(tmp); - } - else if (a->get_num_args() == 2) { - add_literal(a, out); - } - else { - m_todo.push_back(m.mk_distinct_expanded(a->get_num_args(), - a->get_args())); - } - } - else if (m.is_and(a)) { - if (is_true) { - m_todo.append(a->get_num_args(), a->get_args()); - } - else { - for (expr* e : *a) { - if (m_model.is_false(e)) { - m_todo.push_back(e); - break; - } - } - } - } - else if (m.is_or(a)) { - if (!is_true) - m_todo.append(a->get_num_args(), a->get_args()); - else { - for (expr* e : *a) { - if (m_model.is_true(e)) { - m_todo.push_back(e); - break; - } - } - } - } - else if (m.is_eq(a, f1, f2) || - (is_true && m.is_not(a, na) && m.is_xor(na, f1, f2))) { - if (!m.are_equal(f1, f2) && !m.are_distinct(f1, f2)) { - if (m.is_bool(f1) && - (!is_uninterp_const(f1) || !is_uninterp_const(f2))) - m_todo.append(a->get_num_args(), a->get_args()); - else - add_literal(a, out); - } - } - else if (m.is_ite(a, f1, f2, f3)) { - if (m.are_equal(f2, f3)) { - m_todo.push_back(f2); - } - else if (m_model.is_true(f2) && m_model.is_true(f3)) { - m_todo.push_back(f2); - m_todo.push_back(f3); - } - else if (m_model.is_false(f2) && m_model.is_false(f3)) { - m_todo.push_back(f2); - m_todo.push_back(f3); - } - else if (m_model.is_true(f1)) { - m_todo.push_back(f1); - m_todo.push_back(f2); - } - else if (m_model.is_false(f1)) { - m_todo.push_back(f1); - m_todo.push_back(f3); - } - } - else if (m.is_xor(a, f1, f2)) { - m_todo.append(a->get_num_args(), a->get_args()); - } - else if (m.is_implies(a, f1, f2)) { - if (is_true) { - if (m_model.is_true(f2)) - m_todo.push_back(f2); - else if (m_model.is_false(f1)) - m_todo.push_back(f1); - } - else - m_todo.append(a->get_num_args(), a->get_args()); - } - else { - IF_VERBOSE(0, - verbose_stream() << "Unexpected expression: " - << mk_pp(a, m) << "\n"); - UNREACHABLE(); - } + void process_app(app *a, expr_ref_vector &out) { + if (m_visited.is_marked(a)) return; + SASSERT(m.is_bool(a)); + expr_ref v(m); + v = m_model(a); + bool is_true = m.is_true(v); + + if (!is_true && !m.is_false(v)) return; + + expr *na = nullptr, *f1 = nullptr, *f2 = nullptr, *f3 = nullptr; + + SASSERT(!m.is_false(a)); + if (m.is_true(a)) { + // noop + } else if (a->get_family_id() != m.get_basic_family_id()) { + add_literal(a, out); + } else if (is_uninterp_const(a)) { + add_literal(a, out); + } else if (m.is_not(a, na)) { + m_todo.push_back(na); + } else if (m.is_distinct(a)) { + if (!is_true) { + expr_ref tmp = + mbp::project_plugin::pick_equality(m, m_model, a); + m_todo.push_back(tmp); + } else if (a->get_num_args() == 2) { + add_literal(a, out); + } else { + m_todo.push_back( + m.mk_distinct_expanded(a->get_num_args(), a->get_args())); } - - void pick_literals(expr* e, expr_ref_vector& out) { - SASSERT(m_todo.empty()); - if (m_visited.is_marked(e) || !is_app(e)) return; - - // -- keep track of all created expressions to - // -- make sure that expression ids are stable - expr_ref_vector pinned(m); - - m_todo.reset(); - m_todo.push_back(e); - while (!m_todo.empty()) { - pinned.push_back(m_todo.back()); - m_todo.pop_back(); - if (!is_app(pinned.back())) continue; - app* a = to_app(pinned.back()); - process_app(a, out); - m_visited.mark(a, true); + } else if (m.is_and(a)) { + if (is_true) { + m_todo.append(a->get_num_args(), a->get_args()); + } else { + for (expr *e : *a) { + if (m_model.is_false(e)) { + m_todo.push_back(e); + break; + } } - m_todo.reset(); } - - bool pick_implicant(const expr_ref_vector& in, expr_ref_vector& out) { - m_visited.reset(); - bool is_true = m_model.is_true(in); - - for (expr* e : in) { - if (is_true || m_model.is_true(e)) { - pick_literals(e, out); + } else if (m.is_or(a)) { + if (!is_true) + m_todo.append(a->get_num_args(), a->get_args()); + else { + for (expr *e : *a) { + if (m_model.is_true(e)) { + m_todo.push_back(e); + break; } } - m_visited.reset(); - return is_true; } - - public: - - implicant_picker(model& mdl) : - m_model(mdl), m(m_model.get_manager()), m_arith(m), m_todo(m) {} - - void operator()(expr_ref_vector& in, expr_ref_vector& out) { - model::scoped_model_completion _sc_(m_model, false); - pick_implicant(in, out); + } else if (m.is_eq(a, f1, f2) || + (is_true && m.is_not(a, na) && m.is_xor(na, f1, f2))) { + if (!m.are_equal(f1, f2) && !m.are_distinct(f1, f2)) { + if (m.is_bool(f1) && + (!is_uninterp_const(f1) || !is_uninterp_const(f2))) + m_todo.append(a->get_num_args(), a->get_args()); + else + add_literal(a, out); + } + } else if (m.is_ite(a, f1, f2, f3)) { + if (m.are_equal(f2, f3)) { + m_todo.push_back(f2); + } else if (m_model.is_true(f2) && m_model.is_true(f3)) { + m_todo.push_back(f2); + m_todo.push_back(f3); + } else if (m_model.is_false(f2) && m_model.is_false(f3)) { + m_todo.push_back(f2); + m_todo.push_back(f3); + } else if (m_model.is_true(f1)) { + m_todo.push_back(f1); + m_todo.push_back(f2); + } else if (m_model.is_false(f1)) { + m_todo.push_back(f1); + m_todo.push_back(f3); } - }; + } else if (m.is_xor(a, f1, f2)) { + m_todo.append(a->get_num_args(), a->get_args()); + } else if (m.is_implies(a, f1, f2)) { + if (is_true) { + if (m_model.is_true(f2)) + m_todo.push_back(f2); + else if (m_model.is_false(f1)) + m_todo.push_back(f1); + } else + m_todo.append(a->get_num_args(), a->get_args()); + } else { + IF_VERBOSE(0, verbose_stream() << "Unexpected expression: " + << mk_pp(a, m) << "\n"); + UNREACHABLE(); + } } - expr_ref_vector compute_implicant_literals(model& mdl, - expr_ref_vector& formula) { - // flatten the formula and remove all trivial literals - - // TBD: not clear why there is a dependence on it(other than - // not handling of Boolean constants by implicant_picker), however, - // it was a source of a problem on a benchmark - expr_ref_vector res(formula.get_manager()); - flatten_and(formula); - if (!formula.empty()) { - implicant_picker ipick(mdl); - ipick(formula, res); - } - return res; - } - - void simplify_bounds_old(expr_ref_vector& cube) { - ast_manager& m = cube.m(); - scoped_no_proof _no_pf_(m); - goal_ref g(alloc(goal, m, false, false, false)); - for (expr* c : cube) - g->assert_expr(c); - - goal_ref_buffer result; - tactic_ref simplifier = mk_arith_bounds_tactic(m); - (*simplifier)(g, result); - SASSERT(result.size() == 1); - goal* r = result[0]; - cube.reset(); - for (unsigned i = 0; i < r->size(); ++i) { - cube.push_back(r->form(i)); + void pick_literals(expr *e, expr_ref_vector &out) { + SASSERT(m_todo.empty()); + if (m_visited.is_marked(e) || !is_app(e)) return; + + // -- keep track of all created expressions to + // -- make sure that expression ids are stable + expr_ref_vector pinned(m); + + m_todo.reset(); + m_todo.push_back(e); + while (!m_todo.empty()) { + pinned.push_back(m_todo.back()); + m_todo.pop_back(); + if (!is_app(pinned.back())) continue; + app *a = to_app(pinned.back()); + process_app(a, out); + m_visited.mark(a, true); } + m_todo.reset(); } - void simplify_bounds_new(expr_ref_vector& cube) { - ast_manager& m = cube.m(); - scoped_no_proof _no_pf_(m); - goal_ref g(alloc(goal, m, false, false, false)); - for (expr* c : cube) - g->assert_expr(c); - - goal_ref_buffer goals; - tactic_ref prop_values = mk_propagate_values_tactic(m); - tactic_ref prop_bounds = mk_propagate_ineqs_tactic(m); - tactic_ref t = and_then(prop_values.get(), prop_bounds.get()); - - (*t)(g, goals); - SASSERT(goals.size() == 1); + bool pick_implicant(const expr_ref_vector &in, expr_ref_vector &out) { + m_visited.reset(); + bool is_true = m_model.is_true(in); - g = goals[0]; - cube.reset(); - for (unsigned i = 0; i < g->size(); ++i) { - cube.push_back(g->form(i)); + for (expr *e : in) { + if (is_true || m_model.is_true(e)) { pick_literals(e, out); } } + m_visited.reset(); + return is_true; } - void simplify_bounds(expr_ref_vector& cube) { - simplify_bounds_new(cube); - } - - /// Adhoc rewriting of arithmetic expressions - struct adhoc_rewriter_cfg : public default_rewriter_cfg { - ast_manager& m; - arith_util m_util; - - adhoc_rewriter_cfg(ast_manager& manager) : m(manager), m_util(m) {} - - bool is_le(func_decl const* n) const { return m_util.is_le(n); } - bool is_ge(func_decl const* n) const { return m_util.is_ge(n); } + public: + implicant_picker(model &mdl) + : m_model(mdl), m(m_model.get_manager()), m_arith(m), m_todo(m) {} - br_status reduce_app(func_decl* f, unsigned num, expr* const* args, - expr_ref& result, proof_ref& result_pr) { - expr* e; - if (is_le(f)) - return mk_le_core(args[0], args[1], result); - if (is_ge(f)) - return mk_ge_core(args[0], args[1], result); - if (m.is_not(f) && m.is_not(args[0], e)) { - result = e; - return BR_DONE; - } - return BR_FAILED; - } - - br_status mk_le_core(expr* arg1, expr* arg2, expr_ref& result) { - // t <= -1 ==> t < 0 ==> !(t >= 0) - if (m_util.is_int(arg1) && m_util.is_minus_one(arg2)) { - result = m.mk_not(m_util.mk_ge(arg1, mk_zero())); - return BR_DONE; - } - return BR_FAILED; + void operator()(expr_ref_vector &in, expr_ref_vector &out) { + model::scoped_model_completion _sc_(m_model, false); + pick_implicant(in, out); + } +}; +} // namespace + +expr_ref_vector compute_implicant_literals(model &mdl, + expr_ref_vector &formula) { + // flatten the formula and remove all trivial literals + + // TBD: not clear why there is a dependence on it(other than + // not handling of Boolean constants by implicant_picker), however, + // it was a source of a problem on a benchmark + expr_ref_vector res(formula.get_manager()); + flatten_and(formula); + if (!formula.empty()) { + implicant_picker ipick(mdl); + ipick(formula, res); + } + return res; +} + +void simplify_bounds_old(expr_ref_vector &cube) { + ast_manager &m = cube.m(); + scoped_no_proof _no_pf_(m); + goal_ref g(alloc(goal, m, false, false, false)); + for (expr *c : cube) g->assert_expr(c); + + goal_ref_buffer result; + tactic_ref simplifier = mk_arith_bounds_tactic(m); + (*simplifier)(g, result); + SASSERT(result.size() == 1); + goal *r = result[0]; + cube.reset(); + for (unsigned i = 0; i < r->size(); ++i) { cube.push_back(r->form(i)); } +} + +void simplify_bounds_new(expr_ref_vector &cube) { + ast_manager &m = cube.m(); + scoped_no_proof _no_pf_(m); + goal_ref g(alloc(goal, m, false, false, false)); + for (expr *c : cube) g->assert_expr(c); + + goal_ref_buffer goals; + tactic_ref prop_values = mk_propagate_values_tactic(m); + tactic_ref prop_bounds = mk_propagate_ineqs_tactic(m); + tactic_ref t = and_then(prop_values.get(), prop_bounds.get()); + + (*t)(g, goals); + SASSERT(goals.size() == 1); + + g = goals[0]; + cube.reset(); + for (unsigned i = 0; i < g->size(); ++i) { cube.push_back(g->form(i)); } +} + +void simplify_bounds(expr_ref_vector &cube) { simplify_bounds_new(cube); } + +/// Adhoc rewriting of arithmetic expressions +struct adhoc_rewriter_cfg : public default_rewriter_cfg { + ast_manager &m; + arith_util m_util; + + adhoc_rewriter_cfg(ast_manager &manager) : m(manager), m_util(m) {} + + bool is_le(func_decl const *n) const { return m_util.is_le(n); } + bool is_ge(func_decl const *n) const { return m_util.is_ge(n); } + + br_status reduce_app(func_decl *f, unsigned num, expr *const *args, + expr_ref &result, proof_ref &result_pr) { + expr *e; + if (is_le(f)) return mk_le_core(args[0], args[1], result); + if (is_ge(f)) return mk_ge_core(args[0], args[1], result); + if (m.is_not(f) && m.is_not(args[0], e)) { + result = e; + return BR_DONE; } - br_status mk_ge_core(expr* arg1, expr* arg2, expr_ref& result) { - // t >= 1 ==> t > 0 ==> !(t <= 0) - if (m_util.is_int(arg1) && is_one(arg2)) { + return BR_FAILED; + } - result = m.mk_not(m_util.mk_le(arg1, mk_zero())); - return BR_DONE; - } - return BR_FAILED; - } - expr* mk_zero() { return m_util.mk_numeral(rational(0), true); } - bool is_one(expr const* n) const { - rational val; return m_util.is_numeral(n, val) && val.is_one(); + br_status mk_le_core(expr *arg1, expr *arg2, expr_ref &result) { + // t <= -1 ==> t < 0 ==> !(t >= 0) + if (m_util.is_int(arg1) && m_util.is_minus_one(arg2)) { + result = m.mk_not(m_util.mk_ge(arg1, mk_zero())); + return BR_DONE; } - }; - - void normalize(expr* e, expr_ref& out, - bool use_simplify_bounds, - bool use_factor_eqs) - { + return BR_FAILED; + } + br_status mk_ge_core(expr *arg1, expr *arg2, expr_ref &result) { + // t >= 1 ==> t > 0 ==> !(t <= 0) + if (m_util.is_int(arg1) && is_one(arg2)) { - params_ref params; - // arith_rewriter - params.set_bool("sort_sums", true); - params.set_bool("gcd_rounding", true); - params.set_bool("arith_lhs", true); - // poly_rewriter - params.set_bool("som", true); - params.set_bool("flat", true); - - // apply rewriter - th_rewriter rw(out.m(), params); - rw(e, out); - - adhoc_rewriter_cfg adhoc_cfg(out.m()); - rewriter_tpl adhoc_rw(out.m(), false, adhoc_cfg); - adhoc_rw(out.get(), out); - - if (out.m().is_and(out)) { - expr_ref_vector v(out.m()); - flatten_and(out, v); - - if (v.size() > 1) { - if (use_simplify_bounds) { - // remove redundant inequalities - simplify_bounds(v); - } - if (use_factor_eqs) { - // -- refactor equivalence classes and choose a representative - mbp::term_graph egraph(out.m()); - egraph.add_lits(v); - v.reset(); - egraph.to_lits(v); - } - // sort arguments of the top-level and - std::stable_sort(v.data(), v.data() + v.size(), ast_lt_proc()); - - TRACE("spacer_normalize", - tout << "Normalized:\n" - << out << "\n" - << "to\n" - << mk_and(v) << "\n";); - TRACE("spacer_normalize", - mbp::term_graph egraph(out.m()); - for (expr* e : v) egraph.add_lit(to_app(e)); - tout << "Reduced app:\n" - << mk_pp(egraph.to_expr(), out.m()) << "\n";); - out = mk_and(v); - } + result = m.mk_not(m_util.mk_le(arg1, mk_zero())); + return BR_DONE; } + return BR_FAILED; } - - // rewrite term such that the pretty printing is easier to read - struct adhoc_rewriter_rpp : public default_rewriter_cfg { - ast_manager& m; - arith_util m_arith; - - adhoc_rewriter_rpp(ast_manager& manager) : m(manager), m_arith(m) {} - - bool is_le(func_decl const* n) const { return m_arith.is_le(n); } - bool is_ge(func_decl const* n) const { return m_arith.is_ge(n); } - bool is_lt(func_decl const* n) const { return m_arith.is_lt(n); } - bool is_gt(func_decl const* n) const { return m_arith.is_gt(n); } - bool is_zero(expr const* n) const { rational val; return m_arith.is_numeral(n, val) && val.is_zero(); } - - br_status reduce_app(func_decl* f, unsigned num, expr* const* args, - expr_ref& result, proof_ref& result_pr) - { - br_status st = BR_FAILED; - expr* e1, * e2, * e3, * e4; - - // rewrites(=(+ A(* -1 B)) 0) into(= A B) - if (m.is_eq(f) && is_zero(args[1]) && - m_arith.is_add(args[0], e1, e2) && - m_arith.is_mul(e2, e3, e4) && m_arith.is_minus_one(e3)) { - result = m.mk_eq(e1, e4); - return BR_DONE; - } - // simplify normalized leq, where right side is different from 0 - // rewrites(<=(+ A(* -1 B)) C) into(<= A B+C) - else if ((is_le(f) || is_lt(f) || is_ge(f) || is_gt(f)) && - m_arith.is_add(args[0], e1, e2) && - m_arith.is_mul(e2, e3, e4) && m_arith.is_minus_one(e3)) { - expr_ref rhs(m); - rhs = is_zero(args[1]) ? e4 : m_arith.mk_add(e4, args[1]); - - if (is_le(f)) { - result = m_arith.mk_le(e1, rhs); - st = BR_DONE; - } - else if (is_lt(f)) { - result = m_arith.mk_lt(e1, rhs); - st = BR_DONE; - } - else if (is_ge(f)) { - result = m_arith.mk_ge(e1, rhs); - st = BR_DONE; - } - else if (is_gt(f)) { - result = m_arith.mk_gt(e1, rhs); - st = BR_DONE; - } - else - { - UNREACHABLE(); - } + expr *mk_zero() { return m_util.mk_numeral(rational(0), true); } + bool is_one(expr const *n) const { + rational val; + return m_util.is_numeral(n, val) && val.is_one(); + } +}; + +void normalize(expr *e, expr_ref &out, bool use_simplify_bounds, + bool use_factor_eqs) { + + params_ref params; + // arith_rewriter + params.set_bool("sort_sums", true); + params.set_bool("gcd_rounding", true); + params.set_bool("arith_lhs", true); + // poly_rewriter + params.set_bool("som", true); + params.set_bool("flat", true); + + // apply rewriter + th_rewriter rw(out.m(), params); + rw(e, out); + + adhoc_rewriter_cfg adhoc_cfg(out.m()); + rewriter_tpl adhoc_rw(out.m(), false, adhoc_cfg); + adhoc_rw(out.get(), out); + + if (out.m().is_and(out)) { + expr_ref_vector v(out.m()); + flatten_and(out, v); + + if (v.size() > 1) { + if (use_simplify_bounds) { + // remove redundant inequalities + simplify_bounds(v); } - // simplify negation of ordering predicate - else if (m.is_not(f)) { - if (m_arith.is_lt(args[0], e1, e2)) { - result = m_arith.mk_ge(e1, e2); - st = BR_DONE; - } - else if (m_arith.is_le(args[0], e1, e2)) { - result = m_arith.mk_gt(e1, e2); - st = BR_DONE; - } - else if (m_arith.is_gt(args[0], e1, e2)) { - result = m_arith.mk_le(e1, e2); - st = BR_DONE; - } - else if (m_arith.is_ge(args[0], e1, e2)) { - result = m_arith.mk_lt(e1, e2); - st = BR_DONE; - } + if (use_factor_eqs) { + // -- refactor equivalence classes and choose a representative + mbp::term_graph egraph(out.m()); + egraph.add_lits(v); + v.reset(); + egraph.to_lits(v); } - return st; - } - }; - - mk_epp::mk_epp(ast* t, ast_manager& m, unsigned indent, - unsigned num_vars, char const* var_prefix) : - mk_pp(t, m, m_epp_params, indent, num_vars, var_prefix), m_epp_expr(m) { - m_epp_params.set_uint("min_alias_size", UINT_MAX); - m_epp_params.set_uint("max_depth", UINT_MAX); - - if (is_expr(m_ast)) { - rw(to_expr(m_ast), m_epp_expr); - m_ast = m_epp_expr; + // sort arguments of the top-level and + std::stable_sort(v.data(), v.data() + v.size(), ast_lt_proc()); + + TRACE("spacer_normalize", tout << "Normalized:\n" + << out << "\n" + << "to\n" + << mk_and(v) << "\n";); + TRACE("spacer_normalize", mbp::term_graph egraph(out.m()); + for (expr *e + : v) egraph.add_lit(to_app(e)); + tout << "Reduced app:\n" + << mk_pp(egraph.to_expr(), out.m()) << "\n";); + out = mk_and(v); } } - - void mk_epp::rw(expr* e, expr_ref& out) { - adhoc_rewriter_rpp cfg(out.m()); - rewriter_tpl arw(out.m(), false, cfg); - arw(e, out); +} + +// rewrite term such that the pretty printing is easier to read +struct adhoc_rewriter_rpp : public default_rewriter_cfg { + ast_manager &m; + arith_util m_arith; + + adhoc_rewriter_rpp(ast_manager &manager) : m(manager), m_arith(m) {} + + bool is_le(func_decl const *n) const { return m_arith.is_le(n); } + bool is_ge(func_decl const *n) const { return m_arith.is_ge(n); } + bool is_lt(func_decl const *n) const { return m_arith.is_lt(n); } + bool is_gt(func_decl const *n) const { return m_arith.is_gt(n); } + bool is_zero(expr const *n) const { + rational val; + return m_arith.is_numeral(n, val) && val.is_zero(); } - void ground_expr(expr* e, expr_ref& out, app_ref_vector& vars) { - expr_free_vars fv; - ast_manager& m = out.m(); + br_status reduce_app(func_decl *f, unsigned num, expr *const *args, + expr_ref &result, proof_ref &result_pr) { + br_status st = BR_FAILED; + expr *e1, *e2, *e3, *e4; - fv(e); - if (vars.size() < fv.size()) { - vars.resize(fv.size()); - } - for (unsigned i = 0, sz = fv.size(); i < sz; ++i) { - sort* s = fv[i] ? fv[i] : m.mk_bool_sort(); - vars[i] = mk_zk_const(m, i, s); - var_subst vs(m, false); - out = vs(e, vars.size(), (expr**)vars.data()); + // rewrites(=(+ A(* -1 B)) 0) into(= A B) + if (m.is_eq(f) && is_zero(args[1]) && m_arith.is_add(args[0], e1, e2) && + m_arith.is_mul(e2, e3, e4) && m_arith.is_minus_one(e3)) { + result = m.mk_eq(e1, e4); + return BR_DONE; } - } - - 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) { - if (m_array.is_select(n) || m.is_eq(n)) { - unsigned i = 0; - for (expr* arg : *n) { - if ((m.is_eq(n) || i > 0) && m_var != arg) m_res.push_back(arg); - ++i; - } + // simplify normalized leq, where right side is different from 0 + // rewrites(<=(+ A(* -1 B)) C) into(<= A B+C) + else if ((is_le(f) || is_lt(f) || is_ge(f) || is_gt(f)) && + m_arith.is_add(args[0], e1, e2) && + m_arith.is_mul(e2, e3, e4) && m_arith.is_minus_one(e3)) { + expr_ref rhs(m); + rhs = is_zero(args[1]) ? e4 : m_arith.mk_add(e4, args[1]); + + if (is_le(f)) { + result = m_arith.mk_le(e1, rhs); + st = BR_DONE; + } else if (is_lt(f)) { + result = m_arith.mk_lt(e1, rhs); + st = BR_DONE; + } else if (is_ge(f)) { + result = m_arith.mk_ge(e1, rhs); + st = BR_DONE; + } else if (is_gt(f)) { + result = m_arith.mk_gt(e1, rhs); + st = BR_DONE; + } else { + UNREACHABLE(); } } - }; - - bool mbqi_project_var(model& mdl, app* var, expr_ref& fml) { - ast_manager& m = fml.get_manager(); - model::scoped_model_completion _sc_(mdl, false); - - expr_ref val(m); - val = mdl(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 << "\n";); - - for (expr* term : terms) { - expr_ref tval(m); - tval = mdl(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; + // simplify negation of ordering predicate + else if (m.is_not(f)) { + if (m_arith.is_lt(args[0], e1, e2)) { + result = m_arith.mk_ge(e1, e2); + st = BR_DONE; + } else if (m_arith.is_le(args[0], e1, e2)) { + result = m_arith.mk_gt(e1, e2); + st = BR_DONE; + } else if (m_arith.is_gt(args[0], e1, e2)) { + result = m_arith.mk_le(e1, e2); + st = BR_DONE; + } else if (m_arith.is_ge(args[0], e1, e2)) { + result = m_arith.mk_lt(e1, e2); + st = BR_DONE; } } + return st; + } +}; - TRACE("mbqi_project", - tout << "MBQI: failed to eliminate " << mk_pp(var, m) - << " from " << fml << "\n";); +mk_epp::mk_epp(ast *t, ast_manager &m, unsigned indent, unsigned num_vars, + char const *var_prefix) + : mk_pp(t, m, m_epp_params, indent, num_vars, var_prefix), m_epp_expr(m) { + m_epp_params.set_uint("min_alias_size", UINT_MAX); + m_epp_params.set_uint("max_depth", UINT_MAX); - return false; + if (is_expr(m_ast)) { + rw(to_expr(m_ast), m_epp_expr); + m_ast = m_epp_expr; } - - void mbqi_project(model& mdl, app_ref_vector& vars, expr_ref& fml) { - ast_manager& m = fml.get_manager(); - expr_ref tmp(m); - model::scoped_model_completion _sc_(mdl, false); - // -- evaluate to initialize mev cache - tmp = mdl(fml); - tmp.reset(); - - unsigned j = 0; - for (app* v : vars) - if (!mbqi_project_var(mdl, v, fml)) - vars[j++] = v; - vars.shrink(j); - } - - struct found {}; - struct check_select { - array_util a; - check_select(ast_manager& m) : a(m) {} - void operator()(expr* n) {} - void operator()(app* n) { if (a.is_select(n)) throw found(); } - }; - - bool contains_selects(expr* fml, ast_manager& m) { - check_select cs(m); - try { - for_each_expr(cs, fml); - return false; +} + +void mk_epp::rw(expr *e, expr_ref &out) { + adhoc_rewriter_rpp cfg(out.m()); + rewriter_tpl arw(out.m(), false, cfg); + arw(e, out); +} + +void ground_expr(expr *e, expr_ref &out, app_ref_vector &vars) { + expr_free_vars fv; + ast_manager &m = out.m(); + + fv(e); + if (vars.size() < fv.size()) { vars.resize(fv.size()); } + for (unsigned i = 0, sz = fv.size(); i < sz; ++i) { + sort *s = fv[i] ? fv[i] : m.mk_bool_sort(); + vars[i] = mk_zk_const(m, i, s); + var_subst vs(m, false); + out = vs(e, vars.size(), (expr **)vars.data()); + } +} + +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) { + if (m_array.is_select(n) || m.is_eq(n)) { + unsigned i = 0; + for (expr *arg : *n) { + if ((m.is_eq(n) || i > 0) && m_var != arg) m_res.push_back(arg); + ++i; + } } - catch (const found&) { + } +}; + +bool mbqi_project_var(model &mdl, app *var, expr_ref &fml) { + ast_manager &m = fml.get_manager(); + model::scoped_model_completion _sc_(mdl, false); + + expr_ref val(m); + val = mdl(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 << "\n";); + + for (expr *term : terms) { + expr_ref tval(m); + tval = mdl(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; } } - struct collect_indices { - app_ref_vector& m_indices; - array_util a; - collect_indices(app_ref_vector& indices) : m_indices(indices), - a(indices.get_manager()) {} - void operator()(expr* n) {} - void operator()(app* n) { - if (a.is_select(n)) { - // for all but first argument - for (unsigned i = 1; i < n->get_num_args(); ++i) { - expr* arg = n->get_arg(i); - if (is_app(arg)) - m_indices.push_back(to_app(arg)); - } - } - } - }; - - void get_select_indices(expr* fml, app_ref_vector& indices) { - collect_indices ci(indices); - for_each_expr(ci, fml); + TRACE("mbqi_project", tout << "MBQI: failed to eliminate " << mk_pp(var, m) + << " from " << fml << "\n";); + + return false; +} + +void mbqi_project(model &mdl, app_ref_vector &vars, expr_ref &fml) { + ast_manager &m = fml.get_manager(); + expr_ref tmp(m); + model::scoped_model_completion _sc_(mdl, false); + // -- evaluate to initialize mev cache + tmp = mdl(fml); + tmp.reset(); + + unsigned j = 0; + for (app *v : vars) + if (!mbqi_project_var(mdl, v, fml)) vars[j++] = v; + vars.shrink(j); +} + +struct found {}; +struct check_select { + array_util a; + check_select(ast_manager &m) : a(m) {} + void operator()(expr *n) {} + void operator()(app *n) { + if (a.is_select(n)) throw found(); } +}; - struct collect_decls { - app_ref_vector& m_decls; - std::string& prefix; - collect_decls(app_ref_vector& decls, std::string& p) : m_decls(decls), prefix(p) {} - void operator()(expr* n) {} - void operator()(app* n) { - if (n->get_decl()->get_name().str().find(prefix) != std::string::npos) - m_decls.push_back(n); +bool contains_selects(expr *fml, ast_manager &m) { + check_select cs(m); + try { + for_each_expr(cs, fml); + return false; + } catch (const found &) { return true; } +} + +struct collect_indices { + app_ref_vector &m_indices; + array_util a; + collect_indices(app_ref_vector &indices) + : m_indices(indices), a(indices.get_manager()) {} + void operator()(expr *n) {} + void operator()(app *n) { + if (a.is_select(n)) { + // for all but first argument + for (unsigned i = 1; i < n->get_num_args(); ++i) { + expr *arg = n->get_arg(i); + if (is_app(arg)) m_indices.push_back(to_app(arg)); + } } - }; - - void find_decls(expr* fml, app_ref_vector& decls, std::string& prefix) { - collect_decls cd(decls, prefix); - for_each_expr(cd, fml); } - - // set the value of a boolean function to true in model - void set_true_in_mdl(model& model, func_decl* f) { - SASSERT(f->get_arity() == 0); - model.unregister_decl(f); - model.register_decl(f, model.get_manager().mk_true()); - model.reset_eval_cache(); +}; + +void get_select_indices(expr *fml, app_ref_vector &indices) { + collect_indices ci(indices); + for_each_expr(ci, fml); +} + +struct collect_decls { + app_ref_vector &m_decls; + std::string &prefix; + collect_decls(app_ref_vector &decls, std::string &p) + : m_decls(decls), prefix(p) {} + void operator()(expr *n) {} + void operator()(app *n) { + if (n->get_decl()->get_name().str().find(prefix) != std::string::npos) + m_decls.push_back(n); + } +}; + +void find_decls(expr *fml, app_ref_vector &decls, std::string &prefix) { + collect_decls cd(decls, prefix); + for_each_expr(cd, fml); +} + +// set the value of a boolean function to true in model +void set_true_in_mdl(model &model, func_decl *f) { + SASSERT(f->get_arity() == 0); + model.unregister_decl(f); + model.register_decl(f, model.get_manager().mk_true()); + model.reset_eval_cache(); +} + +// Return number of variables in \p e +unsigned get_num_vars(expr *e) { + expr_free_vars fv; + fv(e); + unsigned count = 0; + for (unsigned i = 0, sz = fv.size(); i < sz; ++i) { + if (fv[i]) { count++; } + } + return count; +} + +namespace collect_uninterp_consts_ns { +struct proc { + expr_ref_vector &m_out; + proc(expr_ref_vector &out) : m_out(out) {} + void operator()(expr *n) const {} + void operator()(app *n) { + if (is_uninterp_const(n)) m_out.push_back(n); + } +}; +} // namespace collect_uninterp_consts_ns + +// Return all uninterpreted constants of \p q +void collect_uninterp_consts(expr *e, expr_ref_vector &out) { + collect_uninterp_consts_ns::proc proc(out); + for_each_expr(proc, e); +} + +namespace has_nonlinear_var_mul_ns { +struct found {}; +// Detects multiplication of a variable by not-a-number +struct proc { + arith_util m_arith; + bv_util m_bv; + proc(ast_manager &m) : m_arith(m), m_bv(m) {} + bool is_numeral(expr *e) const { + return m_arith.is_numeral(e) || m_bv.is_numeral(e); + } + bool is_mul(const expr *n, expr *&e1, expr *&e2) const { + if (m_arith.is_mul(n, e1, e2)) return true; + if (m_bv.is_bv_mul(n, e1, e2)) return true; + return false; } + void operator()(var *n) const {} + void operator()(quantifier *q) const {} + void operator()(app const *n) const { + expr *e1, *e2; + if (is_mul(n, e1, e2) && ((is_var(e1) && !is_numeral(e2)) || + (is_var(e2) && !is_numeral(e1)))) + throw found(); + } +}; +} // namespace has_nonlinear_var_mul_ns + +// Returns true if \p e contains a multiplication a variable by not-a-number +bool has_nonlinear_var_mul(expr *e, ast_manager &m) { + has_nonlinear_var_mul_ns::proc proc(m); + try { + for_each_expr(proc, e); + } catch (const has_nonlinear_var_mul_ns::found &) { return true; } + return false; +} + +namespace contains_mod_ns { +struct found {}; +struct contains_mod_proc { + ast_manager &m; + arith_util m_arith; + contains_mod_proc(ast_manager &a_m) : m(a_m), m_arith(m) {} + void operator()(expr *n) const {} + void operator()(app *n) { + if (m_arith.is_mod(n)) throw found(); + } +}; +} // namespace contains_mod_ns + +// Returns true if \p e contains \p mod +bool contains_mod(expr *e, ast_manager &m) { + contains_mod_ns::contains_mod_proc t(m); + try { + for_each_expr(t, e); + } catch (const contains_mod_ns::found &) { return true; } + + return false; +} +bool contains_mod(const expr_ref &e) { return contains_mod(e.get(), e.get_manager()); } + +namespace contains_real_ns { +struct found {}; +struct contains_real_proc { + ast_manager &m; + arith_util m_arith; + contains_real_proc(ast_manager &a_m) : m(a_m), m_arith(m) {} + void operator()(expr *n) const {} + void operator()(app *n) { + if (m_arith.is_real(n)) throw found(); + } +}; +} // namespace contains_real_ns + +// Returns true if \p e contains a real-valued sub-term +bool contains_real(expr *e, ast_manager &m) { + contains_real_ns::contains_real_proc t(m); + try { + for_each_expr(t, e); + return false; + } catch (const contains_real_ns::found &) { return true; } +} +bool contains_real(const expr_ref &e) { + return contains_real(e.get(), e.get_manager()); +} + +/// Returns true if the range of substitution \p s is numeric +bool is_numeric_sub(const substitution &s) { + ast_manager &m(s.get_manager()); + arith_util arith(m); + bv_util bv(m); + std::pair var; + expr_offset r; + for (unsigned i = 0, sz = s.get_num_bindings(); i < sz; ++i) { + s.get_binding(i, var, r); + if (!(bv.is_numeral(r.get_expr()) || arith.is_numeral(r.get_expr()))) + return false; + } + return true; +} + } // namespace spacer template class rewriter_tpl; template class rewriter_tpl; diff --git a/src/muz/spacer/spacer_util.h b/src/muz/spacer/spacer_util.h index 1c8c64f6145..4f07265fd31 100644 --- a/src/muz/spacer/spacer_util.h +++ b/src/muz/spacer/spacer_util.h @@ -21,126 +21,159 @@ Revision History: #pragma once +#include "ast/arith_decl_plugin.h" +#include "ast/array_decl_plugin.h" #include "ast/ast.h" #include "ast/ast_pp.h" +#include "ast/ast_util.h" +#include "ast/bv_decl_plugin.h" +#include "ast/expr_map.h" +#include "model/model.h" #include "util/obj_hashtable.h" #include "util/ref_vector.h" #include "util/trace.h" #include "util/vector.h" -#include "ast/arith_decl_plugin.h" -#include "ast/array_decl_plugin.h" -#include "ast/bv_decl_plugin.h" -#include "ast/ast_util.h" -#include "ast/expr_map.h" -#include "model/model.h" -#include "util/stopwatch.h" #include "muz/spacer/spacer_antiunify.h" +#include "util/stopwatch.h" class model; class model_core; namespace spacer { - inline unsigned infty_level () { - return UINT_MAX; - } +inline unsigned infty_level() { return UINT_MAX; } - inline bool is_infty_level(unsigned lvl) { - // XXX: level is 16 bits in class pob - return lvl >= 65535; - } +inline bool is_infty_level(unsigned lvl) { + // XXX: level is 16 bits in class pob + return lvl >= 65535; +} - inline unsigned next_level(unsigned lvl) { - return is_infty_level(lvl)?lvl:(lvl+1); - } +inline unsigned next_level(unsigned lvl) { + return is_infty_level(lvl) ? lvl : (lvl + 1); +} - inline unsigned prev_level (unsigned lvl) { - if (is_infty_level(lvl)) return infty_level(); - if (lvl == 0) return 0; - return lvl - 1; - } +inline unsigned prev_level(unsigned lvl) { + if (is_infty_level(lvl)) return infty_level(); + if (lvl == 0) return 0; + return lvl - 1; +} - struct pp_level { - unsigned m_level; - pp_level(unsigned l): m_level(l) {} - }; - - inline std::ostream& operator<<(std::ostream& out, pp_level const& p) { - if (is_infty_level(p.m_level)) { - return out << "oo"; - } else { - return out << p.m_level; - } - } +struct pp_level { + unsigned m_level; + pp_level(unsigned l) : m_level(l) {} +}; - typedef ptr_vector app_vector; - typedef ptr_vector decl_vector; - typedef obj_hashtable func_decl_set; - - /** - \brief hoist non-boolean if expressions. - */ - - void to_mbp_benchmark(std::ostream &out, const expr* fml, const app_ref_vector &vars); - - - // TBD: deprecate by qe::mbp - /** - * do the following in sequence - * 1. use qe_lite to cheaply eliminate vars - * 2. for remaining boolean vars, substitute using M - * 3. use MBP for remaining array and arith variables - * 4. for any remaining arith variables, substitute using M - */ - void qe_project (ast_manager& m, app_ref_vector& vars, - expr_ref& fml, model &mdl, - bool reduce_all_selects=false, - bool native_mbp=false, - bool dont_sub=false); - - // deprecate - void qe_project (ast_manager& m, app_ref_vector& vars, expr_ref& fml, - model_ref& M, expr_map& map); - - // TBD: sort out - void expand_literals(ast_manager &m, expr_ref_vector& conjs); - expr_ref_vector compute_implicant_literals(model &mdl, - expr_ref_vector &formula); - void simplify_bounds (expr_ref_vector &lemmas); - void normalize(expr *e, expr_ref &out, bool use_simplify_bounds = true, bool factor_eqs = false); - - /** - * Ground expression by replacing all free variables by skolem - * constants. On return, out is the resulting expression, and vars is - * a map from variable ids to corresponding skolem constants. - */ - void ground_expr (expr *e, expr_ref &out, app_ref_vector &vars); - - void mbqi_project(model &mdl, app_ref_vector &vars, expr_ref &fml); - - bool contains_selects (expr* fml, ast_manager& m); - void get_select_indices (expr* fml, app_ref_vector& indices); - - void find_decls (expr* fml, app_ref_vector& decls, std::string& prefix); - - /** - * extended pretty-printer - * used for debugging - * disables aliasing of common sub-expressions - */ - struct mk_epp : public mk_pp { - params_ref m_epp_params; - expr_ref m_epp_expr; - mk_epp(ast *t, ast_manager &m, unsigned indent = 0, unsigned num_vars = 0, char const * var_prefix = nullptr); - void rw(expr *e, expr_ref &out); - }; - - bool is_clause(ast_manager &m, expr *n); - bool is_literal(ast_manager &m, expr *n); - bool is_atom(ast_manager &m, expr *n); - - // set f to true in model - void set_true_in_mdl(model &model, func_decl *f); +inline std::ostream &operator<<(std::ostream &out, pp_level const &p) { + if (is_infty_level(p.m_level)) { + return out << "oo"; + } else { + return out << p.m_level; + } } +typedef ptr_vector app_vector; +typedef ptr_vector decl_vector; +typedef obj_hashtable func_decl_set; + +/** + \brief hoist non-boolean if expressions. +*/ + +void to_mbp_benchmark(std::ostream &out, const expr *fml, + const app_ref_vector &vars); + +// TBD: deprecate by qe::mbp +/** + * do the following in sequence + * 1. use qe_lite to cheaply eliminate vars + * 2. for remaining boolean vars, substitute using M + * 3. use MBP for remaining array and arith variables + * 4. for any remaining arith variables, substitute using M + */ +void qe_project(ast_manager &m, app_ref_vector &vars, expr_ref &fml, model &mdl, + bool reduce_all_selects = false, bool native_mbp = false, + bool dont_sub = false); + +// deprecate +void qe_project(ast_manager &m, app_ref_vector &vars, expr_ref &fml, + model_ref &M, expr_map &map); + +// TBD: sort out +void expand_literals(ast_manager &m, expr_ref_vector &conjs); +expr_ref_vector compute_implicant_literals(model &mdl, + expr_ref_vector &formula); +void simplify_bounds(expr_ref_vector &lemmas); +void normalize(expr *e, expr_ref &out, bool use_simplify_bounds = true, + bool factor_eqs = false); + +void normalize_order(expr *e, expr_ref &out); +/** + * Ground expression by replacing all free variables by skolem + * constants. On return, out is the resulting expression, and vars is + * a map from variable ids to corresponding skolem constants. + */ +void ground_expr(expr *e, expr_ref &out, app_ref_vector &vars); + +void mbqi_project(model &mdl, app_ref_vector &vars, expr_ref &fml); + +bool contains_selects(expr *fml, ast_manager &m); +void get_select_indices(expr *fml, app_ref_vector &indices); + +void find_decls(expr *fml, app_ref_vector &decls, std::string &prefix); + +/** + * extended pretty-printer + * used for debugging + * disables aliasing of common sub-expressions + */ +struct mk_epp : public mk_pp { + params_ref m_epp_params; + expr_ref m_epp_expr; + mk_epp(ast *t, ast_manager &m, unsigned indent = 0, unsigned num_vars = 0, + char const *var_prefix = nullptr); + void rw(expr *e, expr_ref &out); +}; + +bool is_clause(ast_manager &m, expr *n); +bool is_literal(ast_manager &m, expr *n); +bool is_atom(ast_manager &m, expr *n); + +// set f to true in model +void set_true_in_mdl(model &model, func_decl *f); +/// Returns number of free variables in \p e +unsigned get_num_vars(expr *e); +// Return all uninterpreted constants of \p q +void collect_uninterp_consts(expr *a, expr_ref_vector &out); +bool has_nonlinear_mul(expr *e, ast_manager &m); + +// Returns true if \p e contains a multiplication a variable by not-a-number +bool has_nonlinear_var_mul(expr *e, ast_manager &m); + +// check whether lit is an instance of mono_var_pattern +bool is_mono_var(expr *lit, ast_manager &m, arith_util &a_util); + +// a mono_var_pattern has only one variable in the whole expression and is +// linear. lit is the literal with the variable +bool should_conjecture(const expr_ref &p, expr_ref &lit); + +/// Drop all literals that numerically match \p lit, from \p fml_vec. +/// +/// \p abs_fml holds the result. Returns true if any literal has been dropped +bool drop_lit(expr_ref_vector &in, expr_ref &lit, expr_ref_vector &out); + +/// Returns true if range of s is numeric +bool is_numeric_sub(const substitution &s); + +// Returns true if \p e contains \p mod +bool contains_mod(const expr_ref &e); + +// Returns true if \p e contains a real-valued sub-term +bool contains_real(const expr_ref &e); + +// multiply fml with num and simplify rationals to ints +// fml should be in LIA/LRA/Arrays +// assumes that fml is a sum of products +void mul_by_rat(expr_ref &fml, rational num); + +} // namespace spacer From 54f3e520ec5bddec97aa9da3909e35b195b32fc4 Mon Sep 17 00:00:00 2001 From: Arie Gurfinkel Date: Sat, 30 Apr 2022 08:30:39 -0400 Subject: [PATCH 06/78] Add spacer_cluster for clustering lemmas A cluster of lemmas is a set of lemmas that are all instances of the same pattern, where a pattern is a qff formula with free variables. Currently, the instances are required to be explicit, that is, they are all obtained by substituting concrete values (i.e., numbers) for free variables of the pattern. Lemmas are clustered in cluster_db in each predicate transformer. --- src/muz/spacer/CMakeLists.txt | 2 + src/muz/spacer/spacer_cluster.cpp | 391 +++++++++++++++++++++++++ src/muz/spacer/spacer_cluster.h | 174 +++++++++++ src/muz/spacer/spacer_cluster_util.cpp | 230 +++++++++++++++ 4 files changed, 797 insertions(+) create mode 100644 src/muz/spacer/spacer_cluster.cpp create mode 100644 src/muz/spacer/spacer_cluster.h create mode 100644 src/muz/spacer/spacer_cluster_util.cpp diff --git a/src/muz/spacer/CMakeLists.txt b/src/muz/spacer/CMakeLists.txt index d703c5db4eb..c1ee41e95f3 100644 --- a/src/muz/spacer/CMakeLists.txt +++ b/src/muz/spacer/CMakeLists.txt @@ -10,6 +10,7 @@ z3_add_component(spacer spacer_prop_solver.cpp spacer_sym_mux.cpp spacer_util.cpp + spacer_cluster_util.cpp spacer_iuc_solver.cpp spacer_legacy_mbp.cpp spacer_proof_utils.cpp @@ -22,6 +23,7 @@ z3_add_component(spacer spacer_sem_matcher.cpp spacer_quant_generalizer.cpp spacer_arith_generalizers.cpp + spacer_cluster.cpp spacer_callback.cpp spacer_json.cpp spacer_iuc_proof.cpp diff --git a/src/muz/spacer/spacer_cluster.cpp b/src/muz/spacer/spacer_cluster.cpp new file mode 100644 index 00000000000..4e3f843fdd2 --- /dev/null +++ b/src/muz/spacer/spacer_cluster.cpp @@ -0,0 +1,391 @@ +/*++ +Copyright (c) 2020 Arie Gurfinkel + +Module Name: + + spacer_cluster.cpp + +Abstract: + + Discover and mark lemma clusters + +Author: + + Hari Govind V K + Arie Gurfinkel + + +--*/ +#include + +#include "ast/arith_decl_plugin.h" +#include "ast/ast_util.h" +#include "ast/rewriter/var_subst.h" +#include "ast/substitution/substitution.h" +#include "muz/spacer/spacer_antiunify.h" +#include "muz/spacer/spacer_cluster.h" +#include "muz/spacer/spacer_context.h" +#include "muz/spacer/spacer_manager.h" +#include "muz/spacer/spacer_util.h" +#include "smt/tactic/unit_subsumption_tactic.h" +#include "util/mpq.h" +#include "util/vector.h" + +#define MAX_CLUSTER_SIZE 5 +#define MAX_CLUSTERS 5 +#define GAS_INIT 10 + +namespace spacer { + +using var_offset = std::pair; + +lemma_cluster::lemma_cluster(const expr_ref &pattern) + : m(pattern.get_manager()), m_arith(m), m_bv(m), m_ref_count(0), + m_pattern(pattern), m_matcher(m), m_gas(GAS_INIT) { + m_num_vars = get_num_vars(m_pattern); +} + +lemma_cluster::lemma_cluster(const lemma_cluster &other) + : m(other.get_manager()), m_arith(m), m_bv(m), m_ref_count(0), + m_pattern(other.get_pattern()), m_num_vars(other.m_num_vars), + m_matcher(m), m_gas(other.get_gas()) { + for (const auto &li : other.get_lemmas()) { m_lemma_vec.push_back(li); } +} + +/// Get a conjunction of all the lemmas in cluster +void lemma_cluster::get_conj_lemmas(expr_ref &e) const { + expr_ref_vector conj(m); + for (const auto &lem : get_lemmas()) { + conj.push_back(lem.get_lemma()->get_expr()); + } + e = mk_and(conj); +} + +bool lemma_cluster::contains(const lemma_ref &lemma) { + for (const auto &li : get_lemmas()) { + if (lemma->get_expr() == li.get_lemma()->get_expr()) return true; + } + return false; +} + +unsigned lemma_cluster::get_min_lvl() { + if (m_lemma_vec.empty()) return 0; + unsigned lvl = m_lemma_vec[0].get_lemma()->level(); + for (auto l : m_lemma_vec) { lvl = std::min(lvl, l.get_lemma()->level()); } + return lvl; +} + +/// Checks whether \p e matches the pattern of the cluster +/// Returns true on success and set \p sub to the corresponding substitution +bool lemma_cluster::match(const expr_ref &e, substitution &sub) { + bool pos; + var_offset var; + expr_offset r; + + m_matcher.reset(); + bool is_match = m_matcher(m_pattern, e, sub, pos); + if (!(is_match && pos)) return false; + + unsigned n_binds = sub.get_num_bindings(); + auto is_numeral = [&](expr *e) { + return m_arith.is_numeral(e) || m_bv.is_numeral(e); + }; + // All the matches should be numerals + for (unsigned i = 0; i < n_binds; i++) { + sub.get_binding(i, var, r); + if (!is_numeral(r.get_expr())) return false; + } + return true; +} + +bool lemma_cluster::can_contain(const lemma_ref &lemma) { + substitution sub(m); + expr_ref cube(m); + + sub.reserve(1, m_num_vars); + cube = mk_and(lemma->get_cube()); + normalize_order(cube, cube); + return match(cube, sub); +} + +lemma_cluster::lemma_info * +lemma_cluster::get_lemma_info(const lemma_ref &lemma) { + SASSERT(contains(lemma)); + for (auto &li : m_lemma_vec) { + if (lemma == li.get_lemma()) { return &li; } + } + UNREACHABLE(); + return nullptr; +} + +/// Removes subsumed lemmas in the cluster +/// +/// Removed lemmas are placed into \p removed_lemmas +void lemma_cluster::rm_subsumed(lemma_info_vector &removed_lemmas) { + removed_lemmas.reset(); + if (m_lemma_vec.size() <= 1) return; + + // set up and run the simplifier + tactic_ref simplifier = mk_unit_subsumption_tactic(m); + goal_ref g(alloc(goal, m, false, false, false)); + goal_ref_buffer result; + for (auto l : m_lemma_vec) { g->assert_expr(l.get_lemma()->get_expr()); } + (*simplifier)(g, result); + + SASSERT(result.size() == 1); + goal *r = result[0]; + + // nothing removed + if (r->size() == m_lemma_vec.size()) return; + + // collect removed lemmas + lemma_info_vector keep; + for (auto lem : m_lemma_vec) { + bool found = false; + for (unsigned i = 0; i < r->size(); i++) { + if (lem.get_lemma()->get_expr() == r->form(i)) { + found = true; + keep.push_back(lem); + TRACE("cluster_stats_verb", tout << "Keeping lemma " + << lem.get_lemma()->get_cube() + << "\n";); + break; + } + } + if (!found) { + TRACE("cluster_stats_verb", tout << "Removing subsumed lemma " + << lem.get_lemma()->get_cube() + << "\n";); + removed_lemmas.push_back(lem); + } + } + m_lemma_vec.reset(); + m_lemma_vec.append(keep); +} + +/// A a lemma to a cluster +/// +/// Removes subsumed lemmas if \p subs_check is true +/// +/// Returns false if lemma does not match the pattern or if it is already in the +/// cluster. Repetition of lemmas is avoided by doing a linear scan over the +/// lemmas in the cluster. Adding a lemma can reduce the size of the cluster due +/// to subsumption reduction. +bool lemma_cluster::add_lemma(const lemma_ref &lemma, bool subsume) { + substitution sub(m); + expr_ref cube(m); + + sub.reserve(1, m_num_vars); + cube = mk_and(lemma->get_cube()); + normalize_order(cube, cube); + + if (!match(cube, sub)) return false; + + // cluster already contains the lemma + if (contains(lemma)) return false; + + TRACE("cluster_stats_verb", + tout << "Trying to add lemma " << lemma->get_cube() << "\n";); + + lemma_cluster::lemma_info li(lemma, sub); + m_lemma_vec.push_back(li); + + if (subsume) { + lemma_info_vector removed_lemmas; + rm_subsumed(removed_lemmas); + for (auto rm : removed_lemmas) { + // There is going to at most one removed lemma that matches l_i + // if there is one, return false since the new lemma was not added + if (rm.get_lemma() == li.get_lemma()) return false; + } + } + TRACE("cluster_stats", tout << "Added lemma " << mk_and(lemma->get_cube()) + << " to existing cluster " << m_pattern + << "\n";); + return true; +} + +lemma_cluster_finder::lemma_cluster_finder(ast_manager &_m) + : m(_m), m_arith(m), m_bv(m) {} + +/// Check whether \p cube and \p lcube differ only in interpreted constants +bool lemma_cluster_finder::are_neighbours(const expr_ref &cube1, + const expr_ref &cube2) { + SASSERT(is_ground(cube1)); + SASSERT(is_ground(cube2)); + + anti_unifier antiunify(m); + expr_ref pat(m); + substitution sub1(m), sub2(m); + + antiunify(cube1, cube2, pat, sub1, sub2); + SASSERT(sub1.get_num_bindings() == sub2.get_num_bindings()); + return is_numeric_sub(sub1) && is_numeric_sub(sub2); +} + +/// Compute antiunification of \p cube with all formulas in \p fmls. +/// +/// Should return +/// \exist res (\forall f \in fmls (\exist i_sub res[i_sub] == f)) +/// However, the algorithm is incomplete: it returns such a res iff +/// res \in {antiU(cube, e) | e \in fmls} +/// Returns true if res is found +/// TODO: do complete n-ary anti-unification. Not done now +/// because anti_unifier does not support free variables +bool lemma_cluster_finder::anti_unify_n_intrp(const expr_ref &cube, + expr_ref_vector &fmls, + expr_ref &res) { + expr_ref_vector patterns(m); + expr_ref pat(m); + anti_unifier antiunify(m); + substitution sub1(m), sub2(m); + + TRACE("cluster_stats_verb", + tout << "Trying to generate a general pattern for " << cube + << " neighbours are " << fmls << "\n";); + + // collect candidates for res + for (expr *c : fmls) { + antiunify.reset(); + sub1.reset(); + sub2.reset(); + + SASSERT(are_neighbours(cube, {c, m})); + antiunify(cube, expr_ref(c, m), pat, sub1, sub2); + patterns.push_back(pat); + } + + // go through all the patterns to see if there is a pattern which is general + // enough to include all lemmas. + bool is_general_pattern = false, pos = true, all_same = true; + sem_matcher matcher(m); + unsigned n_vars_pat = 0; + for (expr *e : patterns) { + TRACE("cluster_stats_verb", + tout << "Checking pattern " << mk_pp(e, m) << "\n";); + is_general_pattern = true; + n_vars_pat = get_num_vars(e); + all_same = all_same && n_vars_pat == 0; + for (auto *lcube : fmls) { + matcher.reset(); + sub1.reset(); + sub1.reserve(1, n_vars_pat); + if (!(matcher(e, lcube, sub1, pos) && pos)) { + // this pattern is no good + is_general_pattern = false; + break; + } + } + if (is_general_pattern) { + SASSERT(e != nullptr); + TRACE("cluster_stats", + tout << "Found a general pattern " << mk_pp(e, m) << "\n";); + // found a good pattern + res = expr_ref(e, m); + return true; + } + } + + CTRACE("cluster_stats", !all_same, + tout << "Failed to find a general pattern for cluster. Cube is: " + << cube << " Patterns are " << patterns << "\n";); + return false; +} + +/// Add a new lemma \p lemma to a cluster +/// +/// Creates a new cluster for the lemma if necessary +void lemma_cluster_finder::cluster(lemma_ref &lemma) { + scoped_watch _w_(m_st.watch); + pred_transformer &pt = (lemma->get_pob())->pt(); + + // check whether lemmas has already been added + if (pt.clstr_contains(lemma)) return; + + /// Add the lemma to a cluster it is matched against + lemma_cluster *clstr = pt.clstr_match(lemma); + if (clstr && clstr->get_size() <= MAX_CLUSTER_SIZE) { + TRACE("cluster_stats_verb", { + tout << "Trying to add lemma " << lemma->get_cube() + << " to an existing cluster "; + for (auto lem : clstr->get_lemmas()) + tout << lem.get_lemma()->get_cube() << "\n"; + }); + clstr->add_lemma(lemma); + return; + } + + /// Dont create more than MAX_CLUSTERS number of clusters + if (clstr && pt.clstr_count(clstr->get_pattern()) > MAX_CLUSTERS) { + return; + } + + // Try to create a new cluster with lemma even if it can belong to an + // oversized cluster. The new cluster will not contain any lemma that is + // already in another cluster. + lemma_ref_vector all_lemmas; + pt.get_all_lemmas(all_lemmas, false); + + expr_ref lcube(m), cube(m); + lcube = mk_and(lemma->get_cube()); + normalize_order(lcube, lcube); + + expr_ref_vector lma_cubes(m); + lemma_ref_vector neighbours; + + for (auto *l : all_lemmas) { + cube.reset(); + cube = mk_and(l->get_cube()); + normalize_order(cube, cube); + // make sure that l is not in any other clusters + if (are_neighbours(lcube, cube) && cube != lcube && + !pt.clstr_contains(l)) { + neighbours.push_back(l); + lma_cubes.push_back(cube); + } + } + + if (neighbours.empty()) return; + + // compute the most general pattern to which lemmas fit + expr_ref pattern(m); + bool is_cluster = anti_unify_n_intrp(lcube, lma_cubes, pattern); + + // no general pattern + if (!is_cluster || get_num_vars(pattern) == 0) return; + + // When creating a cluster, its size can be more than MAX_CLUSTER_SIZE. The + // size limitation is only for adding new lemmas to the cluster. The size is + // just an arbitrary number. + // What matters is that we do not allow a cluster to grow indefinitely. + // for example, given a cluster in which one lemma subsumes all other + // lemmas. No matter how big the cluster is, GSpacer is going to produce the + // exact same pob on this cluster. This can lead to divergence. The + // subsumption check we do is based on unit propagation, it is not complete. + lemma_cluster *cluster = pt.mk_cluster(pattern); + + TRACE("cluster_stats", + tout << "created new cluster with pattern: " << pattern << "\n" + << " and lemma cube: " << lcube << "\n";); + + IF_VERBOSE(2, verbose_stream() << "\ncreated new cluster with pattern: " + << pattern << "\n" + << " and lemma cube: " << lcube << "\n";); + + for (const lemma_ref &l : neighbours) { + SASSERT(cluster->can_contain(l)); + cluster->add_lemma(l, false); + TRACE("cluster_stats", + tout << "Added lemma " << mk_and(l->get_cube()) << "\n";); + } + + // finally add the lemma and do subsumption check + cluster->add_lemma(lemma, true); + SASSERT(cluster->get_size() >= 1); +} + +void lemma_cluster_finder::collect_statistics(statistics &st) const { + st.update("time.spacer.solve.reach.cluster", m_st.watch.get_seconds()); +} + +} // namespace spacer diff --git a/src/muz/spacer/spacer_cluster.h b/src/muz/spacer/spacer_cluster.h new file mode 100644 index 00000000000..2ed0c9333f6 --- /dev/null +++ b/src/muz/spacer/spacer_cluster.h @@ -0,0 +1,174 @@ +#pragma once +/*++ +Copyright (c) 2020 Arie Gurfinkel + +Module Name: + + spacer_cluster.h + +Abstract: + + Discover and mark lemma clusters + +Author: + + Hari Govind V K + Arie Gurfinkel + + +--*/ + +#include +#include +#include +#include +#include +#include + +#define GAS_POB_COEFF 5 + +namespace spacer { +class lemma; +using lemma_ref = ref; + +/// Representation of a cluster of lemmas +/// +/// A cluster of lemmas is a collection of lemma instances. A cluster is +/// defined by a \p pattern that is a qff formula with free variables, and +/// contains lemmas that are instances of the pattern (i.e., obtained from the +/// pattern by substitution of constants for variables). That is, each lemma +/// in the cluster matches the pattern. +class lemma_cluster { + /// Lemma in a cluster + /// + /// A lemma and a substitution witnessing that lemma is an instance of a + /// pattern + class lemma_info { + // a lemma + lemma_ref m_lemma; + // a substitution such that for some pattern, \p m_lemma is an instance + substitution m_sub; + + public: + lemma_info(const lemma_ref &body, const substitution &sub) + : m_lemma(body), m_sub(sub) {} + + const lemma_ref &get_lemma() const { return m_lemma; } + const substitution &get_sub() const { return m_sub; } + }; + + public: + using lemma_info_vector = vector; + + private: + ast_manager &m; + arith_util m_arith; + bv_util m_bv; + + // reference counter + unsigned m_ref_count; + // pattern defining the cluster + expr_ref m_pattern; + unsigned m_num_vars; + + // vector of lemmas in the cluster + lemma_info_vector m_lemma_vec; + + // shared matcher object to match lemmas against the pattern + sem_matcher m_matcher; + + // The number of times CSM has to be tried using this cluster + unsigned m_gas; + + /// Remove subsumed lemmas in the cluster. + /// + /// Returns list of removed lemmas in \p removed_lemmas + void rm_subsumed(lemma_info_vector &removed_lemmas); + + /// Checks whether \p e matches m_pattern. + /// + /// Returns true on success and sets \p sub to the corresponding + /// substitution + bool match(const expr_ref &e, substitution &sub); + + ast_manager &get_manager() const { return m; } + + public: + lemma_cluster(const expr_ref &pattern); + lemma_cluster(const lemma_cluster &other); + + const lemma_info_vector &get_lemmas() const { return m_lemma_vec; } + + void dec_gas() { + if (m_gas > 0) m_gas--; + } + + unsigned get_gas() const { return m_gas; } + unsigned get_pob_gas() const { return GAS_POB_COEFF * m_lemma_vec.size(); } + + /// Get a conjunction of all the lemmas in cluster + void get_conj_lemmas(expr_ref &e) const; + + /// Try to add \p lemma to cluster. Remove subsumed lemmas if \p subs_check + /// is true + /// + /// Returns false if lemma does not match the pattern or if it is already in + /// the cluster Repetition of lemmas is avoided by doing a linear scan over + /// the lemmas in the cluster. Adding a lemma can reduce the size of the + /// cluster due to subs_check + bool add_lemma(const lemma_ref &lemma, bool subsume = false); + + bool contains(const lemma_ref &lemma); + bool can_contain(const lemma_ref &lemma); + + /// Return the minimum level of lemmas in he cluster + unsigned get_min_lvl(); + + lemma_cluster::lemma_info *get_lemma_info(const lemma_ref &lemma); + unsigned get_size() const { return m_lemma_vec.size(); } + const expr_ref &get_pattern() const { return m_pattern; } + + void inc_ref() { ++m_ref_count; } + void dec_ref() { + --m_ref_count; + if (m_ref_count == 0) { dealloc(this); } + } +}; + +class lemma_cluster_finder { + struct stats { + unsigned max_group_size; + stopwatch watch; + stats() { reset(); } + void reset() { + max_group_size = 0; + watch.reset(); + } + }; + stats m_st; + ast_manager &m; + arith_util m_arith; + bv_util m_bv; + + /// Check whether \p cube and \p lcube differ only in interpreted constants + bool are_neighbours(const expr_ref &cube, const expr_ref &lcube); + + /// N-ary antiunify + /// + /// Returns whether there is a substitution with only interpreted consts + bool anti_unify_n_intrp(const expr_ref &cube, expr_ref_vector &fmls, + expr_ref &res); + + public: + lemma_cluster_finder(ast_manager &m); + + /// Add a new lemma \p lemma to a cluster + /// + /// Creates a new cluster for the lemma if necessary + void cluster(lemma_ref &lemma); + void collect_statistics(statistics &st) const; + void reset_statistics() { m_st.reset(); } +}; + +using lemma_info_vector = lemma_cluster::lemma_info_vector; +} // namespace spacer diff --git a/src/muz/spacer/spacer_cluster_util.cpp b/src/muz/spacer/spacer_cluster_util.cpp new file mode 100644 index 00000000000..cafb40e3139 --- /dev/null +++ b/src/muz/spacer/spacer_cluster_util.cpp @@ -0,0 +1,230 @@ +/**++ +Copyright (c) 2020 Arie Gurfinkel + +Module Name: + + spacer_cluster_util.cpp + +Abstract: + + Utility methods for clustering + +Author: + + Hari Govind + Arie Gurfinkel + +Notes: + +--*/ + +#include "ast/arith_decl_plugin.h" +#include "ast/ast.h" +#include "ast/ast_pp.h" +#include "ast/for_each_expr.h" +#include "ast/rewriter/rewriter.h" +#include "ast/rewriter/rewriter_def.h" +#include "ast/rewriter/th_rewriter.h" +#include "muz/spacer/spacer_util.h" + +namespace spacer { +/// Arithmetic term order +struct arith_add_less_proc { + const arith_util &m_arith; + + arith_add_less_proc(const arith_util &arith) : m_arith(arith) {} + + bool operator()(expr *e1, expr *e2) const { + if (e1 == e2) return false; + + ast_lt_proc ast_lt; + expr *k1 = nullptr, *t1 = nullptr, *k2 = nullptr, *t2 = nullptr; + + // k1*t1 < k2*t2 iff t1 < t2 or t1 == t2 && k1 < k2 + // k1 and k2 can be null + + if (!m_arith.is_mul(e1, k1, t1)) { t1 = e1; } + if (!m_arith.is_mul(e2, k2, t2)) { t2 = e2; } + + SASSERT(t1 && t2); + if (t1 != t2) return ast_lt(t1, t2); + + // here: t1 == t2 && k1 != k2 + SASSERT(k1 != k2); + + // check for null + if (!k1 || !k2) return !k1; + return ast_lt(k1, k2); + } +}; + +struct bool_and_less_proc { + ast_manager &m; + const arith_util &m_arith; + bool_and_less_proc(ast_manager &mgr, const arith_util &arith) + : m(mgr), m_arith(arith) {} + + bool operator()(expr *e1, expr *e2) const { + expr *a1 = nullptr, *a2 = nullptr; + bool is_not1, is_not2; + if (e1 == e2) return false; + + is_not1 = m.is_not(e1, a1); + a1 = is_not1 ? a1 : e1; + is_not2 = m.is_not(e2, a2); + a2 = is_not2 ? a2 : e2; + + return a1 == a2 ? is_not1 < is_not2 : arith_lt(a1, a2); + } + + bool arith_lt(expr *e1, expr *e2) const { + ast_lt_proc ast_lt; + expr *t1, *k1, *t2, *k2; + + if (e1 == e2) return false; + + if (!(m_arith.is_le(e1, t1, k1) || m_arith.is_lt(e1, t1, k1) || + m_arith.is_ge(e1, t1, k1) || m_arith.is_gt(e1, t1, k1))) { + t1 = e1; + k1 = nullptr; + } + if (!(m_arith.is_le(e2, t2, k2) || m_arith.is_lt(e2, t2, k2) || + m_arith.is_ge(e2, t2, k2) || m_arith.is_gt(e2, t2, k2))) { + t2 = e2; + k2 = nullptr; + } + + if (!k1 || !k2) { return k1 == k2 ? ast_lt(t1, t2) : k1 < k2; } + + if (t1 == t2) return ast_lt(k1, k2); + + if (!(is_app(t1) && is_app(t2))) { + return is_app(t1) == is_app(t2) ? ast_lt(t1, t2) + : is_app(t1) < is_app(t2); + } + + unsigned d1 = to_app(t1)->get_depth(); + unsigned d2 = to_app(t2)->get_depth(); + if (d1 != d2) return d1 < d2; + + // AG: order by the leading uninterpreted constant + expr *u1 = nullptr, *u2 = nullptr; + + u1 = get_first_uc(t1); + u2 = get_first_uc(t2); + if (!u1 || !u2) { return u1 == u2 ? ast_lt(t1, t2) : u1 < u2; } + return u1 == u2 ? ast_lt(t1, t2) : ast_lt(u1, u2); + } + + /// Returns first in left-most traversal uninterpreted constant of \p e + /// + /// Returns null when no uninterpreted constant is found. + /// Recursive, assumes that expression is shallow and recursion is bounded. + expr *get_first_uc(expr *e) const { + expr *t, *k; + if (is_uninterp_const(e)) + return e; + else if (m_arith.is_add(e)) { + if (to_app(e)->get_num_args() == 0) return nullptr; + expr *a1 = to_app(e)->get_arg(0); + // HG: for 3 + a, returns nullptr + return get_first_uc(a1); + } else if (m_arith.is_mul(e, k, t)) { + return get_first_uc(t); + } + return nullptr; + } +}; + +// Rewriter for normalize_order() +struct term_ordered_rpp : public default_rewriter_cfg { + ast_manager &m; + arith_util m_arith; + arith_add_less_proc m_add_less; + bool_and_less_proc m_and_less; + + term_ordered_rpp(ast_manager &man) + : m(man), m_arith(m), m_add_less(m_arith), m_and_less(m, m_arith) {} + + bool is_add(func_decl const *n) const { + return is_decl_of(n, m_arith.get_family_id(), OP_ADD); + } + + br_status reduce_app(func_decl *f, unsigned num, expr *const *args, + expr_ref &result, proof_ref &result_pr) { + br_status st = BR_FAILED; + + if (is_add(f)) { + ptr_buffer kids; + kids.append(num, args); + std::stable_sort(kids.data(), kids.data() + kids.size(), + m_add_less); + result = m_arith.mk_add(num, kids.data()); + return BR_DONE; + } + + if (m.is_and(f)) { + ptr_buffer kids; + kids.append(num, args); + std::stable_sort(kids.data(), kids.data() + kids.size(), + m_and_less); + result = m.mk_and(num, kids.data()); + return BR_DONE; + } + return st; + } +}; + +// Normalize an arithmetic expression using term order +void normalize_order(expr *e, expr_ref &out) { + params_ref params; + // -- arith_rewriter params + params.set_bool("sort_sums", true); + // params.set_bool("gcd_rounding", true); + // params.set_bool("arith_lhs", true); + // -- poly_rewriter params + // params.set_bool("som", true); + // params.set_bool("flat", true); + + // apply theory rewriter + th_rewriter rw1(out.m(), params); + rw1(e, out); + + STRACE("spacer_normalize_order'", + tout << "OUT Before:" << mk_pp(out, out.m()) << "\n";); + // apply term ordering + term_ordered_rpp t_ordered(out.m()); + rewriter_tpl rw2(out.m(), false, t_ordered); + rw2(out.get(), out); + STRACE("spacer_normalize_order'", + tout << "OUT After :" << mk_pp(out, out.m()) << "\n";); +} + +/// Multiply an expression \p fml by a rational \p num +/// +/// \p fml should be of sort Int, Real, or BitVec +/// multiplication is simplifying +void mul_by_rat(expr_ref &fml, rational num) { + if (num.is_one()) return; + + ast_manager &m = fml.get_manager(); + arith_util m_arith(m); + bv_util m_bv(m); + expr_ref e(m); + SASSERT(m_arith.is_int_real(fml) || m_bv.is_bv(fml)); + if (m_arith.is_int_real(fml)) { + e = m_arith.mk_mul(m_arith.mk_numeral(num, m_arith.is_int(fml)), fml); + } else if (m_bv.is_bv(fml)) { + unsigned sz = m_bv.get_bv_size(fml); + e = m_bv.mk_bv_mul(m_bv.mk_numeral(num, sz), fml); + } + + // use theory rewriter to simplify + params_ref params; + params.set_bool("som", true); + params.set_bool("flat", true); + th_rewriter rw(m, params); + rw(e, fml); +} +} // namespace spacer +template class rewriter_tpl; From 68dd69d717380177d3019798041e3b275143ed68 Mon Sep 17 00:00:00 2001 From: Arie Gurfinkel Date: Sat, 30 Apr 2022 08:48:17 -0400 Subject: [PATCH 07/78] Integrate spacer_cluster into spacer_context --- src/muz/spacer/spacer_context.h | 368 ++++++++++++++++++++++---------- 1 file changed, 254 insertions(+), 114 deletions(-) diff --git a/src/muz/spacer/spacer_context.h b/src/muz/spacer/spacer_context.h index 6ef56c737db..b163f346f52 100644 --- a/src/muz/spacer/spacer_context.h +++ b/src/muz/spacer/spacer_context.h @@ -22,21 +22,23 @@ Module Name: #pragma once -#include -#include #include +#include +#include -#include "util/scoped_ptr_vector.h" +#include "muz/spacer/spacer_cluster.h" +#include "muz/spacer/spacer_json.h" #include "muz/spacer/spacer_manager.h" #include "muz/spacer/spacer_prop_solver.h" -#include "muz/spacer/spacer_json.h" +#include "muz/spacer/spacer_sem_matcher.h" +#include "util/scoped_ptr_vector.h" #include "muz/base/fp_params.hpp" namespace datalog { class rule_set; class context; -}; +}; // namespace datalog namespace spacer { @@ -46,6 +48,8 @@ class pred_transformer; class derivation; class pob_queue; class context; +class lemma_cluster; +class lemma_cluster_finder; typedef obj_map rule2inst; typedef obj_map decl2rel; @@ -59,6 +63,10 @@ class reach_fact; typedef ref reach_fact_ref; typedef sref_vector reach_fact_ref_vector; +class lemma; +using lemma_ref = ref; +using lemma_ref_vector = sref_vector; + class reach_fact { unsigned m_ref_count; @@ -74,45 +82,43 @@ class reach_fact { bool m_init; public: - reach_fact (ast_manager &m, const datalog::rule &rule, - expr* fact, const ptr_vector &aux_vars, - bool init = false) : - m_ref_count (0), m_fact (fact, m), m_aux_vars (aux_vars), - m_rule(rule), m_tag(m), m_init (init) {} - reach_fact (ast_manager &m, const datalog::rule &rule, - expr* fact, bool init = false) : - m_ref_count (0), m_fact (fact, m), m_rule(rule), m_tag(m), m_init (init) {} + reach_fact(ast_manager &m, const datalog::rule &rule, expr *fact, + const ptr_vector &aux_vars, bool init = false) + : m_ref_count(0), m_fact(fact, m), m_aux_vars(aux_vars), m_rule(rule), + m_tag(m), m_init(init) {} + reach_fact(ast_manager &m, const datalog::rule &rule, expr *fact, + bool init = false) + : m_ref_count(0), m_fact(fact, m), m_rule(rule), m_tag(m), + m_init(init) {} bool is_init () {return m_init;} const datalog::rule& get_rule () {return m_rule;} void add_justification (reach_fact *f) {m_justification.push_back (f);} - const reach_fact_ref_vector& get_justifications () {return m_justification;} + const reach_fact_ref_vector &get_justifications() { + return m_justification; + } expr *get () {return m_fact.get ();} const ptr_vector &aux_vars () {return m_aux_vars;} - app* tag() const {SASSERT(m_tag); return m_tag;} + app *tag() const { + SASSERT(m_tag); + return m_tag; + } void set_tag(app* tag) {m_tag = tag;} void inc_ref () {++m_ref_count;} - void dec_ref () - { + void dec_ref() { SASSERT (m_ref_count > 0); --m_ref_count; if(m_ref_count == 0) { dealloc(this); } } }; - -class lemma; -typedef ref lemma_ref; -typedef sref_vector lemma_ref_vector; - -typedef pob pob; - // a lemma class lemma { + // clang-format off unsigned m_ref_count; ast_manager &m; @@ -129,9 +135,11 @@ class lemma { unsigned m_external:1; // external lemma from another solver unsigned m_blocked:1; // blocked by CTP unsigned m_background:1; // background assumed fact + // clang-format on void mk_expr_core(); void mk_cube_core(); + public: lemma(ast_manager &manager, expr * fml, unsigned lvl); lemma(pob_ref const &p); @@ -195,8 +203,6 @@ struct lemma_lt_proc { } }; - - // // Predicate transformer state. // A predicate transformer corresponds to the @@ -206,12 +212,15 @@ struct lemma_lt_proc { class pred_transformer { struct stats { - unsigned m_num_propagations; // num of times lemma is pushed higher - unsigned m_num_invariants; // num of infty lemmas found - unsigned m_num_ctp_blocked; // num of time ctp blocked lemma pushing - unsigned m_num_is_invariant; // num of times lemmas are pushed - unsigned m_num_lemma_level_jump; // lemma learned at higher level than expected + // clang-format off + unsigned m_num_propagations; // num of times lemma is pushed higher + unsigned m_num_invariants; // num of infty lemmas found + unsigned m_num_ctp_blocked; // num of time ctp blocked lemma pushing + unsigned m_num_is_invariant; // num of times lemmas are pushed + unsigned m_num_lemma_level_jump; // lemma learned at higher level than + // expected unsigned m_num_reach_queries; + // clang-format on stats() { reset(); } void reset() { memset(this, 0, sizeof(*this)); } @@ -221,6 +230,7 @@ class pred_transformer { #include "muz/spacer/spacer_legacy_frames.h" class frames { private: + // clang-format off pred_transformer &m_pt; // parent pred_transformer lemma_ref_vector m_pinned_lemmas; // all created lemmas lemma_ref_vector m_lemmas; // active lemmas @@ -229,18 +239,17 @@ class pred_transformer { bool m_sorted; // true if m_lemmas is sorted by m_lt lemma_lt_proc m_lt; // sort order for m_lemmas + // clang-format on void sort (); public: - frames (pred_transformer &pt) : m_pt (pt), - m_size(0), m_sorted (true) {} + frames(pred_transformer &pt) : m_pt(pt), m_size(0), m_sorted(true) {} void simplify_formulas (); pred_transformer& pt() const {return m_pt;} const lemma_ref_vector &lemmas() const {return m_lemmas;} - void get_frame_lemmas (unsigned level, expr_ref_vector &out) const { for (auto &lemma : m_lemmas) { if (lemma->level() == level) { @@ -256,11 +265,17 @@ class pred_transformer { } } if (with_bg) { - for (auto &lemma : m_bg_invs) - out.push_back(lemma->get_expr()); + for (auto &lemma : m_bg_invs) out.push_back(lemma->get_expr()); } } + void get_frame_all_lemmas(lemma_ref_vector &out, + bool with_bg = false) const { + for (auto &lemma : m_lemmas) { out.push_back(lemma); } + if (with_bg) { + for (auto &lemma : m_bg_invs) out.push_back(lemma); + } + } const lemma_ref_vector& get_bg_invs() const {return m_bg_invs;} unsigned size() const {return m_size;} unsigned lemma_size() const {return m_lemmas.size ();} @@ -269,9 +284,9 @@ class pred_transformer { void add_frame() {m_size++;} void inherit_frames (frames &other) { for (auto &other_lemma : other.m_lemmas) { - lemma_ref new_lemma = alloc(lemma, m_pt.get_ast_manager(), - other_lemma->get_expr(), - other_lemma->level()); + lemma_ref new_lemma = + alloc(lemma, m_pt.get_ast_manager(), + other_lemma->get_expr(), other_lemma->level()); new_lemma->add_binding(other_lemma->get_bindings()); add_lemma(new_lemma.get()); } @@ -308,13 +323,13 @@ class pred_transformer { // a store pob_ref_vector m_pinned; + public: pob_manager(pred_transformer &pt) : m_pt(pt) {} - pob* mk_pob(pob *parent, unsigned level, unsigned depth, - expr *post, app_ref_vector const &b); + pob *mk_pob(pob *parent, unsigned level, unsigned depth, expr *post, + app_ref_vector const &b); - pob* mk_pob(pob *parent, unsigned level, unsigned depth, - expr *post) { + pob *mk_pob(pob *parent, unsigned level, unsigned depth, expr *post) { app_ref_vector b(m_pt.get_ast_manager()); return mk_pob (parent, level, depth, post, b); } @@ -324,28 +339,41 @@ class pred_transformer { }; class pt_rule { + // clang-format off const datalog::rule &m_rule; expr_ref m_trans; // ground version of m_rule ptr_vector m_auxs; // auxiliary variables in m_trans app_ref_vector m_reps; // map from fv in m_rule to ground constants app_ref m_tag; // a unique tag for the rule + // clang-format on public: - pt_rule(ast_manager &m, const datalog::rule &r) : - m_rule(r), m_trans(m), m_reps(m), m_tag(m) {} + pt_rule(ast_manager &m, const datalog::rule &r) + : m_rule(r), m_trans(m), m_reps(m), m_tag(m) {} const datalog::rule &rule() const {return m_rule;} - void set_tag(expr *tag) {SASSERT(is_app(tag)); set_tag(to_app(tag));} + void set_tag(expr *tag) { + SASSERT(is_app(tag)); + set_tag(to_app(tag)); + } void set_tag(app* tag) {m_tag = tag;} app* tag() const {return m_tag;} ptr_vector &auxs() {return m_auxs;} - void set_auxs(ptr_vector &v) {m_auxs.reset(); m_auxs.append(v);} - void set_reps(app_ref_vector &v) {m_reps.reset(); m_reps.append(v);} + void set_auxs(ptr_vector &v) { + m_auxs.reset(); + m_auxs.append(v); + } + void set_reps(app_ref_vector &v) { + m_reps.reset(); + m_reps.append(v); + } void set_trans(expr_ref &v) {m_trans=v;} expr* trans() const {return m_trans;} - bool is_init() const {return m_rule.get_uninterpreted_tail_size() == 0;} + bool is_init() const { + return m_rule.get_uninterpreted_tail_size() == 0; + } }; class pt_rules { @@ -354,8 +382,11 @@ class pred_transformer { typedef rule2ptrule::iterator iterator; rule2ptrule m_rules; tag2ptrule m_tags; + public: - ~pt_rules() {for (auto &kv : m_rules) {dealloc(kv.m_value);}} + ~pt_rules() { + for (auto &kv : m_rules) { dealloc(kv.m_value); } + } bool find_by_rule(const datalog::rule &r, pt_rule* &ptr) { return m_rules.find(&r, ptr); @@ -377,9 +408,73 @@ class pred_transformer { bool empty() {return m_rules.empty();} iterator begin() {return m_rules.begin();} iterator end() {return m_rules.end();} - }; + /// Clusters of lemmas + class cluster_db { + sref_vector m_clusters; + unsigned m_max_cluster_size; + + public: + cluster_db() : m_max_cluster_size(0) {} + unsigned get_max_cluster_size() const { return m_max_cluster_size; } + + /// Return the smallest cluster than can contain \p lemma + lemma_cluster *can_contain(const lemma_ref &lemma) { + unsigned sz = UINT_MAX; + lemma_cluster *res = nullptr; + for (auto *c : m_clusters) { + if (c->get_gas() > 0 && c->get_size() < sz && + c->can_contain(lemma)) { + res = c; + sz = res->get_size(); + } + } + return res; + } + + bool contains(const lemma_ref &lemma) { + for (auto *c : m_clusters) { + if (c->contains(lemma)) { return true; } + } + return false; + } + + /// The number of clusters with pattern \p pattern + unsigned clstr_count(const expr_ref &pattern) { + unsigned count = 0; + for (auto c : m_clusters) { + if (c->get_pattern() == pattern) count++; + } + return count; + } + + lemma_cluster *mk_cluster(const expr_ref &pattern) { + m_clusters.push_back(alloc(lemma_cluster, pattern)); + return m_clusters.back(); + } + + /// Return the smallest cluster containing \p lemma + lemma_cluster *get_cluster(const lemma_ref &lemma) { + unsigned sz = UINT_MAX; + lemma_cluster *res = nullptr; + for (auto *c : m_clusters) { + if (c->get_size() < sz && c->contains(lemma)) { + res = c; + sz = res->get_size(); + } + } + return res; + } + + lemma_cluster *get_cluster(const expr *pattern) { + for (lemma_cluster *lc : m_clusters) { + if (lc->get_pattern().get() == pattern) return lc; + } + return nullptr; + } + }; + // clang-format off manager& pm; // spacer::manager ast_manager& m; // ast_manager context& ctx; // spacer::context @@ -408,6 +503,8 @@ class pred_transformer { stopwatch m_ctp_watch; stopwatch m_mbp_watch; bool m_has_quantified_frame; // True when a quantified lemma is in the frame + cluster_db m_cluster_db; + // clang-format on void init_sig(); app_ref mk_extend_lit(); @@ -426,14 +523,17 @@ class pred_transformer { void simplify_formulas(tactic& tac, expr_ref_vector& fmls); - void add_premises(decl2rel const& pts, unsigned lvl, datalog::rule& rule, expr_ref_vector& r); + void add_premises(decl2rel const &pts, unsigned lvl, datalog::rule &rule, + expr_ref_vector &r); app_ref mk_fresh_rf_tag (); // get tagged formulae of all of the background invariants for all of the // predecessors of the current transformer void get_pred_bg_invs(expr_ref_vector &out); - const lemma_ref_vector &get_bg_invs() const {return m_frames.get_bg_invs();} + const lemma_ref_vector &get_bg_invs() const { + return m_frames.get_bg_invs(); + } public: pred_transformer(context& ctx, manager& pm, func_decl* head); @@ -446,10 +546,13 @@ class pred_transformer { } return nullptr; } - void find_predecessors(datalog::rule const& r, ptr_vector& predicates) const; + void find_predecessors(datalog::rule const &r, + ptr_vector &predicates) const; void add_rule(datalog::rule* r) {m_rules.push_back(r);} - void add_use(pred_transformer* pt) {if (!m_use.contains(pt)) {m_use.insert(pt);}} + void add_use(pred_transformer *pt) { + if (!m_use.contains(pt)) { m_use.insert(pt); } + } void initialize(decl2rel const& pts); func_decl* head() const {return m_head;} @@ -482,9 +585,8 @@ class pred_transformer { /// \brief Collects all the reachable facts used in mdl void get_all_used_rf(model &mdl, unsigned oidx, reach_fact_ref_vector& res); void get_all_used_rf(model &mdl, reach_fact_ref_vector &res); - expr_ref get_origin_summary(model &mdl, - unsigned level, unsigned oidx, bool must, - const ptr_vector **aux); + expr_ref get_origin_summary(model &mdl, unsigned level, unsigned oidx, + bool must, const ptr_vector **aux); bool is_ctp_blocked(lemma *lem); const datalog::rule *find_rule(model &mdl); @@ -516,32 +618,30 @@ class pred_transformer { reach_fact* get_last_rf () const { return m_reach_facts.back (); } expr* get_last_rf_tag () const; - pob* mk_pob(pob *parent, unsigned level, unsigned depth, - expr *post, app_ref_vector const &b){ + pob *mk_pob(pob *parent, unsigned level, unsigned depth, expr *post, + app_ref_vector const &b) { return m_pobs.mk_pob(parent, level, depth, post, b); } pob* find_pob(pob *parent, expr *post) { return m_pobs.find_pob(parent, post); } - pob* mk_pob(pob *parent, unsigned level, unsigned depth, - expr *post) { + pob *mk_pob(pob *parent, unsigned level, unsigned depth, expr *post) { return m_pobs.mk_pob(parent, level, depth, post); } lbool is_reachable(pob& n, expr_ref_vector* core, model_ref *model, unsigned& uses_level, bool& is_concrete, - datalog::rule const*& r, - bool_vector& reach_pred_used, + datalog::rule const *&r, bool_vector &reach_pred_used, unsigned& num_reuse_reach); - bool is_invariant(unsigned level, lemma* lem, - unsigned& solver_level, + bool is_invariant(unsigned level, lemma *lem, unsigned &solver_level, expr_ref_vector* core = nullptr); - bool is_invariant(unsigned level, expr* lem, - unsigned& solver_level, expr_ref_vector* core = nullptr) { + bool is_invariant(unsigned level, expr *lem, unsigned &solver_level, + expr_ref_vector *core = nullptr) { // XXX only needed for legacy_frames to compile - UNREACHABLE(); return false; + UNREACHABLE(); + return false; } bool check_inductive(unsigned level, expr_ref_vector& state, @@ -559,15 +659,17 @@ class pred_transformer { void inherit_lemmas(pred_transformer& other); - void ground_free_vars(expr* e, app_ref_vector& vars, ptr_vector& aux_vars, - bool is_init); + void ground_free_vars(expr *e, app_ref_vector &vars, + ptr_vector &aux_vars, bool is_init); /// \brief Adds a given expression to the set of initial rules app* extend_initial (expr *e); - /// \brief Returns true if the obligation is already blocked by current lemmas + /// \brief Returns true if the obligation is already blocked by current + /// lemmas bool is_blocked (pob &n, unsigned &uses_level); - /// \brief Returns true if the obligation is already blocked by current quantified lemmas + /// \brief Returns true if the obligation is already blocked by current + /// quantified lemmas bool is_qblocked (pob &n); /// \brief interface to Model Based Projection @@ -577,19 +679,48 @@ class pred_transformer { void updt_solver(prop_solver *solver); void updt_solver_with_lemmas(prop_solver *solver, - const pred_transformer &pt, - app *rule_tag, unsigned pos); - void update_solver_with_rfs(prop_solver *solver, - const pred_transformer &pt, + const pred_transformer &pt, app *rule_tag, + unsigned pos); + // exposing ACTIVE lemmas (alternatively, one can expose `m_pinned_lemmas` + // for ALL lemmas) + void get_all_lemmas(lemma_ref_vector &out, bool with_bg = false) const { + m_frames.get_frame_all_lemmas(out, with_bg); + } + void update_solver_with_rfs(prop_solver *solver, const pred_transformer &pt, app *rule_tag, unsigned pos); -}; + lemma_cluster *mk_cluster(const expr_ref &pattern) { + return m_cluster_db.mk_cluster(pattern); + } + + // Checks whether \p lemma is in any existing cluster + bool clstr_contains(const lemma_ref &lemma) { + return m_cluster_db.contains(lemma); + } + + /// The number of clusters with pattern \p pattern + unsigned clstr_count(const expr_ref & pattern) { + return m_cluster_db.clstr_count(pattern); + } + + /// Checks whether \p lemma matches any cluster + lemma_cluster *clstr_match(const lemma_ref &lemma) { + lemma_cluster *res = m_cluster_db.get_cluster(lemma); + if (!res) res = m_cluster_db.can_contain(lemma); + return res; + } + /// Returns a cluster with pattern \p pattern + lemma_cluster *get_cluster(const expr *pattern) { + return m_cluster_db.get_cluster(pattern); + } +}; /** * A proof obligation. */ class pob { + // clang-format off // TBD: remove this friend class context; unsigned m_ref_count; @@ -610,7 +741,8 @@ class pob { /// whether a concrete answer to the post is found unsigned m_open:1; - /// whether to use farkas generalizer to construct a lemma blocking this node + /// whether to use farkas generalizer to construct a lemma blocking this + /// node unsigned m_use_farkas:1; /// true if this pob is in pob_queue unsigned m_in_queue:1; @@ -628,12 +760,15 @@ class pob { // depth -> watch std::map m_expand_watches; unsigned m_blocked_lvl; + // clang-format on public: - pob (pob* parent, pred_transformer& pt, - unsigned level, unsigned depth=0, bool add_to_parent=true); + pob(pob *parent, pred_transformer &pt, unsigned level, unsigned depth = 0, + bool add_to_parent = true); - ~pob() {if (m_parent) { m_parent->erase_child(*this); }} + ~pob() { + if (m_parent) { m_parent->erase_child(*this); } + } // TBD: move into constructor and make private void set_post(expr *post, app_ref_vector const &binding); @@ -645,7 +780,9 @@ class pob { void inc_level () { SASSERT(!is_in_queue()); - m_level++; m_depth++;reset_weakness(); + m_level++; + m_depth++; + reset_weakness(); } void inherit(pob const &p); @@ -705,7 +842,9 @@ class pob { m_expand_watches[m_depth].stop(); if (m_parent.get()){m_parent.get()->off_expand();} } - double get_expand_time(unsigned depth) { return m_expand_watches[depth].get_seconds();} + double get_expand_time(unsigned depth) { + return m_expand_watches[depth].get_seconds(); + } void inc_ref () {++m_ref_count;} void dec_ref () { @@ -714,9 +853,9 @@ class pob { } std::ostream &display(std::ostream &out, bool full = false) const; - class on_expand_event - { + class on_expand_event { pob &m_p; + public: on_expand_event(pob &p) : m_p(p) {m_p.on_expand();} ~on_expand_event() {m_p.off_expand();} @@ -767,7 +906,7 @@ class derivation { const ptr_vector *aux_vars = nullptr); }; - + // clang-format off /// parent model node pob& m_parent; @@ -782,16 +921,19 @@ class derivation { expr_ref m_trans; // implicitly existentially quantified variables in m_trans app_ref_vector m_evars; + // clang-format on + /// -- create next child using given model as the guide /// -- returns NULL if there is no next child pob* create_next_child (model &mdl); /// existentially quantify vars and skolemize the result void exist_skolemize(expr *fml, app_ref_vector &vars, expr_ref &res); + public: - derivation (pob& parent, datalog::rule const& rule, - expr *trans, app_ref_vector const &evars); - void add_premise (pred_transformer &pt, unsigned oidx, - expr * summary, bool must, const ptr_vector *aux_vars = nullptr); + derivation(pob &parent, datalog::rule const &rule, expr *trans, + app_ref_vector const &evars); + void add_premise(pred_transformer &pt, unsigned oidx, expr *summary, + bool must, const ptr_vector *aux_vars = nullptr); /// creates the first child. Must be called after all the premises /// are added. The model must be valid for the premises @@ -810,10 +952,10 @@ class derivation { pred_transformer &pt() const {return m_parent.pt();} }; - class pob_queue { - typedef std::priority_queue, pob_gt_proc> pob_queue_ty; + typedef std::priority_queue, pob_gt_proc> + pob_queue_ty; pob_ref m_root; unsigned m_max_level; unsigned m_min_depth; @@ -848,13 +990,13 @@ class pob_queue { size_t size() const {return m_data.size();} }; - /** * Generalizers (strengthens) a lemma */ class lemma_generalizer { protected: context& m_ctx; + public: lemma_generalizer(context& ctx): m_ctx(ctx) {} virtual ~lemma_generalizer() = default; @@ -863,7 +1005,6 @@ class lemma_generalizer { virtual void reset_statistics() {} }; - class spacer_callback { protected: context &m_context; @@ -890,7 +1031,6 @@ class spacer_callback { virtual inline bool propagate() { return false; } virtual void propagate_eh() {} - }; // order in which children are processed @@ -917,6 +1057,7 @@ class context { void reset() { memset(this, 0, sizeof(*this)); } }; + // clang-format off // stat watches stopwatch m_solve_watch; stopwatch m_propagate_watch; @@ -925,7 +1066,7 @@ class context { stopwatch m_create_children_watch; stopwatch m_init_rules_watch; - fp_params const& m_params; + fp_params const& m_params; ast_manager& m; datalog::context* m_context; manager m_pm; @@ -935,7 +1076,6 @@ class context { scoped_ptr m_pool1; scoped_ptr m_pool2; - random_gen m_random; spacer_children_order m_children_order; decl2rel m_rels; // Map from relation predicate to fp-operator. @@ -985,14 +1125,13 @@ class context { scoped_ptr_vector m_callbacks; json_marshaller m_json_marshaller; std::fstream* m_trace_stream; + // clang-format on // Solve using gpdr strategy lbool gpdr_solve_core(); bool gpdr_check_reachability(unsigned lvl, model_search &ms); - bool gpdr_create_split_children(pob &n, const datalog::rule &r, - expr *trans, - model &mdl, - pob_ref_buffer &out); + bool gpdr_create_split_children(pob &n, const datalog::rule &r, expr *trans, + model &mdl, pob_ref_buffer &out); // progress logging void log_enter_level(unsigned lvl); @@ -1008,8 +1147,7 @@ class context { unsigned full_prop_lvl); bool is_reachable(pob &n); lbool expand_pob(pob &n, pob_ref_buffer &out); - bool create_children(pob& n, const datalog::rule &r, - model &mdl, + bool create_children(pob &n, const datalog::rule &r, model &mdl, const bool_vector& reach_pred_used, pob_ref_buffer &out); @@ -1028,7 +1166,6 @@ class context { vector & rs, bool with_bg = false) const; - // Initialization void init_lemma_generalizers(); void reset_lemma_generalizers(); @@ -1056,13 +1193,13 @@ class context { public: /** - Initial values of predicates are stored in corresponding relations in dctx. - We check whether there is some reachable state of the relation checked_relation. + Initial values of predicates are stored in corresponding relations in + dctx. We check whether there is some reachable state of the relation + checked_relation. */ context(fp_params const& params, ast_manager& m); ~context(); - const fp_params &get_params() const { return m_params; } bool use_eq_prop() const {return m_use_eq_prop;} bool use_native_mbp() const {return m_use_native_mbp;} @@ -1075,7 +1212,9 @@ class context { bool simplify_pob() const {return m_simplify_pob;} bool use_ctp() const {return m_use_ctp;} bool use_inc_clause() const {return m_use_inc_clause;} - unsigned blast_term_ite_inflation() const {return m_blast_term_ite_inflation;} + unsigned blast_term_ite_inflation() const { + return m_blast_term_ite_inflation; + } bool elim_aux() const {return m_elim_aux;} bool reach_dnf() const {return m_reach_dnf;} bool use_bg_invs() const {return m_use_bg_invs;} @@ -1084,17 +1223,19 @@ class context { manager& get_manager() {return m_pm;} const manager & get_manager() const {return m_pm;} decl2rel const& get_pred_transformers() const {return m_rels;} - pred_transformer& get_pred_transformer(func_decl* p) const {return *m_rels.find(p);} + pred_transformer &get_pred_transformer(func_decl *p) const { + return *m_rels.find(p); + } datalog::context& get_datalog_context() const { - SASSERT(m_context); return *m_context; + SASSERT(m_context); + return *m_context; } void update_rules(datalog::rule_set& rules); lbool solve(unsigned from_lvl = 0); lbool solve_from_lvl (unsigned from_lvl); - expr_ref get_answer(); /** * get bottom-up (from query) sequence of ground predicate instances @@ -1136,7 +1277,6 @@ class context { bool is_inductive(); - // three different solvers with three different sets of parameters // different solvers are used for different types of queries in spacer solver* mk_solver0() {return m_pool0->mk_solver();} @@ -1145,5 +1285,5 @@ class context { }; inline bool pred_transformer::use_native_mbp () {return ctx.use_native_mbp ();} -} +} // namespace spacer From 7aacc8baece4a9d98acc9a71c487f3cba35b88bd Mon Sep 17 00:00:00 2001 From: Arie Gurfinkel Date: Wed, 4 May 2022 10:33:29 -0400 Subject: [PATCH 08/78] Custom clang-format pragmas for spacer_context spacer_context.(cpp|h) are large and have inconsistent formatting. Disable clang-format for them until merge with main z3 branch and re-format. --- src/muz/spacer/spacer_context.cpp | 1 + src/muz/spacer/spacer_context.h | 610 +++++++++++++++--------------- 2 files changed, 310 insertions(+), 301 deletions(-) diff --git a/src/muz/spacer/spacer_context.cpp b/src/muz/spacer/spacer_context.cpp index 42b366d9415..c88507fc49a 100644 --- a/src/muz/spacer/spacer_context.cpp +++ b/src/muz/spacer/spacer_context.cpp @@ -20,6 +20,7 @@ Module Name: --*/ +// clang-format off #include #include diff --git a/src/muz/spacer/spacer_context.h b/src/muz/spacer/spacer_context.h index b163f346f52..9acf225f862 100644 --- a/src/muz/spacer/spacer_context.h +++ b/src/muz/spacer/spacer_context.h @@ -21,6 +21,7 @@ Module Name: --*/ #pragma once +// clang-format off #include #include @@ -36,8 +37,8 @@ Module Name: #include "muz/base/fp_params.hpp" namespace datalog { - class rule_set; - class context; +class rule_set; +class context; }; // namespace datalog namespace spacer { @@ -51,8 +52,8 @@ class context; class lemma_cluster; class lemma_cluster_finder; -typedef obj_map rule2inst; -typedef obj_map decl2rel; +typedef obj_map rule2inst; +typedef obj_map decl2rel; class pob; typedef ref pob_ref; @@ -81,7 +82,7 @@ class reach_fact { bool m_init; -public: + public: reach_fact(ast_manager &m, const datalog::rule &rule, expr *fact, const ptr_vector &aux_vars, bool init = false) : m_ref_count(0), m_fact(fact, m), m_aux_vars(aux_vars), m_rule(rule), @@ -91,29 +92,29 @@ class reach_fact { : m_ref_count(0), m_fact(fact, m), m_rule(rule), m_tag(m), m_init(init) {} - bool is_init () {return m_init;} - const datalog::rule& get_rule () {return m_rule;} + bool is_init() { return m_init; } + const datalog::rule &get_rule() { return m_rule; } - void add_justification (reach_fact *f) {m_justification.push_back (f);} + void add_justification(reach_fact *f) { m_justification.push_back(f); } const reach_fact_ref_vector &get_justifications() { return m_justification; } - expr *get () {return m_fact.get ();} - const ptr_vector &aux_vars () {return m_aux_vars;} + expr *get() { return m_fact.get(); } + const ptr_vector &aux_vars() { return m_aux_vars; } app *tag() const { SASSERT(m_tag); return m_tag; } - void set_tag(app* tag) {m_tag = tag;} + void set_tag(app *tag) { m_tag = tag; } - void inc_ref () {++m_ref_count;} + void inc_ref() { ++m_ref_count; } void dec_ref() { - SASSERT (m_ref_count > 0); - --m_ref_count; - if(m_ref_count == 0) { dealloc(this); } - } + SASSERT(m_ref_count > 0); + --m_ref_count; + if (m_ref_count == 0) { dealloc(this); } + } }; // a lemma @@ -136,70 +137,71 @@ class lemma { unsigned m_blocked:1; // blocked by CTP unsigned m_background:1; // background assumed fact // clang-format on + // clang-format off void mk_expr_core(); void mk_cube_core(); -public: - lemma(ast_manager &manager, expr * fml, unsigned lvl); + public: + lemma(ast_manager &manager, expr *fml, unsigned lvl); lemma(pob_ref const &p); lemma(pob_ref const &p, expr_ref_vector &cube, unsigned lvl); -// 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();} + 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(); } - void bump() {m_bumped++;} - unsigned get_bumped() {return m_bumped;} + void bump() { m_bumped++; } + unsigned get_bumped() { return m_bumped; } expr *get_expr(); bool is_false(); expr_ref_vector const &get_cube(); void update_cube(pob_ref const &p, expr_ref_vector &cube); - bool has_pob() {return !!m_pob;} - pob_ref &get_pob() {return m_pob;} - unsigned weakness() {return m_weakness;} + bool has_pob() { return !!m_pob; } + pob_ref &get_pob() { return m_pob; } + unsigned weakness() { return m_weakness; } - void add_skolem(app *zk, app* b); + void add_skolem(app *zk, app *b); - void set_external(bool ext){m_external = ext;} - bool external() { return m_external;} + void set_external(bool ext) { m_external = ext; } + bool external() { return m_external; } - void set_background(bool v) {m_background = v;} - bool is_background() {return m_background;} + void set_background(bool v) { m_background = v; } + bool is_background() { return m_background; } - bool is_blocked() {return m_blocked;} - void set_blocked(bool v) {m_blocked=v;} + bool is_blocked() { return m_blocked; } + void set_blocked(bool v) { m_blocked = v; } - bool is_inductive() const {return is_infty_level(m_lvl);} - unsigned level () const {return m_lvl;} - unsigned init_level() const {return m_init_lvl;} - void set_level (unsigned lvl); - app_ref_vector& get_bindings() {return m_bindings;} + bool is_inductive() const { return is_infty_level(m_lvl); } + unsigned level() const { return m_lvl; } + unsigned init_level() const { return m_init_lvl; } + void set_level(unsigned lvl); + app_ref_vector &get_bindings() { return m_bindings; } bool has_binding(app_ref_vector const &binding); void add_binding(app_ref_vector const &binding); - void instantiate(expr * const * exprs, expr_ref &result, expr *e = nullptr); - void mk_insts(expr_ref_vector& inst, expr* e = nullptr); - bool is_ground () {return !is_quantifier (get_expr());} + void instantiate(expr *const *exprs, expr_ref &result, expr *e = nullptr); + void mk_insts(expr_ref_vector &inst, expr *e = nullptr); + bool is_ground() { return !is_quantifier(get_expr()); } - void inc_ref () {++m_ref_count;} - void dec_ref () { - SASSERT (m_ref_count > 0); + void inc_ref() { ++m_ref_count; } + void dec_ref() { + SASSERT(m_ref_count > 0); --m_ref_count; - if (m_ref_count == 0) {dealloc(this);} + if (m_ref_count == 0) { dealloc(this); } } }; struct lemma_lt_proc { - bool operator() (lemma *a, lemma *b) { - return (a->level () < b->level ()) || - (a->level () == b->level () && - ast_lt_proc() (a->get_expr (), b->get_expr ())); + bool operator()(lemma *a, lemma *b) { + return (a->level() < b->level()) || + (a->level() == b->level() && + ast_lt_proc()(a->get_expr(), b->get_expr())); } }; @@ -221,6 +223,7 @@ class pred_transformer { // expected unsigned m_num_reach_queries; // clang-format on + // clang-format off stats() { reset(); } void reset() { memset(this, 0, sizeof(*this)); } @@ -229,7 +232,7 @@ class pred_transformer { /// manager of the lemmas in all the frames #include "muz/spacer/spacer_legacy_frames.h" class frames { - private: + private: // clang-format off pred_transformer &m_pt; // parent pred_transformer lemma_ref_vector m_pinned_lemmas; // all created lemmas @@ -240,25 +243,26 @@ class pred_transformer { bool m_sorted; // true if m_lemmas is sorted by m_lt lemma_lt_proc m_lt; // sort order for m_lemmas // clang-format on + // clang-format off - void sort (); + void sort(); - public: + public: frames(pred_transformer &pt) : m_pt(pt), m_size(0), m_sorted(true) {} - void simplify_formulas (); + void simplify_formulas(); - pred_transformer& pt() const {return m_pt;} - const lemma_ref_vector &lemmas() const {return m_lemmas;} + pred_transformer &pt() const { return m_pt; } + const lemma_ref_vector &lemmas() const { return m_lemmas; } - void get_frame_lemmas (unsigned level, expr_ref_vector &out) const { + void get_frame_lemmas(unsigned level, expr_ref_vector &out) const { for (auto &lemma : m_lemmas) { if (lemma->level() == level) { out.push_back(lemma->get_expr()); } } } - void get_frame_geq_lemmas (unsigned level, expr_ref_vector &out, - bool with_bg = false) const { + void get_frame_geq_lemmas(unsigned level, expr_ref_vector &out, + bool with_bg = false) const { for (auto &lemma : m_lemmas) { if (lemma->level() >= level) { out.push_back(lemma->get_expr()); @@ -276,13 +280,13 @@ class pred_transformer { for (auto &lemma : m_bg_invs) out.push_back(lemma); } } - const lemma_ref_vector& get_bg_invs() const {return m_bg_invs;} - unsigned size() const {return m_size;} - unsigned lemma_size() const {return m_lemmas.size ();} - unsigned bg_invs_size() const {return m_bg_invs.size();} + const lemma_ref_vector &get_bg_invs() const { return m_bg_invs; } + unsigned size() const { return m_size; } + unsigned lemma_size() const { return m_lemmas.size(); } + unsigned bg_invs_size() const { return m_bg_invs.size(); } - void add_frame() {m_size++;} - void inherit_frames (frames &other) { + void add_frame() { m_size++; } + void inherit_frames(frames &other) { for (auto &other_lemma : other.m_lemmas) { lemma_ref new_lemma = alloc(lemma, m_pt.get_ast_manager(), @@ -294,9 +298,9 @@ class pred_transformer { m_bg_invs.append(other.m_bg_invs); } - bool add_lemma (lemma *new_lemma); - void propagate_to_infinity (unsigned level); - bool propagate_to_next_level (unsigned level); + bool add_lemma(lemma *new_lemma); + void propagate_to_infinity(unsigned level); + bool propagate_to_next_level(unsigned level); }; /** @@ -313,7 +317,7 @@ class pred_transformer { // Type for the map from post-conditions to pobs. The common // case is that each post-condition corresponds to a single // pob. Other cases are handled by expanding the buffer - typedef obj_map expr2pob_buffer; + typedef obj_map expr2pob_buffer; // parent predicate transformer pred_transformer &m_pt; @@ -324,18 +328,18 @@ class pred_transformer { // a store pob_ref_vector m_pinned; - public: + public: pob_manager(pred_transformer &pt) : m_pt(pt) {} pob *mk_pob(pob *parent, unsigned level, unsigned depth, expr *post, app_ref_vector const &b); pob *mk_pob(pob *parent, unsigned level, unsigned depth, expr *post) { app_ref_vector b(m_pt.get_ast_manager()); - return mk_pob (parent, level, depth, post, b); + return mk_pob(parent, level, depth, post, b); } - unsigned size() const {return m_pinned.size();} + unsigned size() const { return m_pinned.size(); } - pob* find_pob(pob* parent, expr *post); + pob *find_pob(pob *parent, expr *post); }; class pt_rule { @@ -346,20 +350,21 @@ class pred_transformer { app_ref_vector m_reps; // map from fv in m_rule to ground constants app_ref m_tag; // a unique tag for the rule // clang-format on + // clang-format off - public: + public: pt_rule(ast_manager &m, const datalog::rule &r) : m_rule(r), m_trans(m), m_reps(m), m_tag(m) {} - const datalog::rule &rule() const {return m_rule;} + const datalog::rule &rule() const { return m_rule; } void set_tag(expr *tag) { SASSERT(is_app(tag)); set_tag(to_app(tag)); } - void set_tag(app* tag) {m_tag = tag;} - app* tag() const {return m_tag;} - ptr_vector &auxs() {return m_auxs;} + void set_tag(app *tag) { m_tag = tag; } + app *tag() const { return m_tag; } + ptr_vector &auxs() { return m_auxs; } void set_auxs(ptr_vector &v) { m_auxs.reset(); m_auxs.append(v); @@ -369,45 +374,45 @@ class pred_transformer { m_reps.append(v); } - void set_trans(expr_ref &v) {m_trans=v;} - expr* trans() const {return m_trans;} + void set_trans(expr_ref &v) { m_trans = v; } + expr *trans() const { return m_trans; } bool is_init() const { return m_rule.get_uninterpreted_tail_size() == 0; } }; class pt_rules { - typedef obj_map rule2ptrule; - typedef obj_map tag2ptrule; + typedef obj_map rule2ptrule; + typedef obj_map tag2ptrule; typedef rule2ptrule::iterator iterator; rule2ptrule m_rules; tag2ptrule m_tags; - public: + public: ~pt_rules() { for (auto &kv : m_rules) { dealloc(kv.m_value); } } - bool find_by_rule(const datalog::rule &r, pt_rule* &ptr) { + bool find_by_rule(const datalog::rule &r, pt_rule *&ptr) { return m_rules.find(&r, ptr); } - bool find_by_tag(const expr* tag, pt_rule* &ptr) { + bool find_by_tag(const expr *tag, pt_rule *&ptr) { return m_tags.find(tag, ptr); } pt_rule &mk_rule(ast_manager &m, const datalog::rule &r) { return mk_rule(pt_rule(m, r)); } pt_rule &mk_rule(const pt_rule &v); - void set_tag(expr* tag, pt_rule &v) { + void set_tag(expr *tag, pt_rule &v) { pt_rule *p; VERIFY(find_by_rule(v.rule(), p)); p->set_tag(tag); m_tags.insert(tag, p); } - bool empty() {return m_rules.empty();} - iterator begin() {return m_rules.begin();} - iterator end() {return m_rules.end();} + bool empty() { return m_rules.empty(); } + iterator begin() { return m_rules.begin(); } + iterator end() { return m_rules.end(); } }; /// Clusters of lemmas @@ -505,28 +510,29 @@ class pred_transformer { bool m_has_quantified_frame; // True when a quantified lemma is in the frame cluster_db m_cluster_db; // clang-format on + // clang-format off void init_sig(); app_ref mk_extend_lit(); void ensure_level(unsigned level); - void add_lemma_core (lemma *lemma, bool ground_only = false); - void add_lemma_from_child (pred_transformer &child, lemma *lemma, - unsigned lvl, bool ground_only = false); + void add_lemma_core(lemma *lemma, bool ground_only = false); + void add_lemma_from_child(pred_transformer &child, lemma *lemma, + unsigned lvl, bool ground_only = false); - void mk_assumptions(func_decl* head, expr* fml, expr_ref_vector& result); + void mk_assumptions(func_decl *head, expr *fml, expr_ref_vector &result); // Initialization - void init_rules(decl2rel const& pts); - void init_rule(decl2rel const& pts, datalog::rule const& rule); - void init_atom(decl2rel const& pts, app * atom, app_ref_vector& var_reprs, - expr_ref_vector& side, unsigned tail_idx); + void init_rules(decl2rel const &pts); + void init_rule(decl2rel const &pts, datalog::rule const &rule); + void init_atom(decl2rel const &pts, app *atom, app_ref_vector &var_reprs, + expr_ref_vector &side, unsigned tail_idx); - void simplify_formulas(tactic& tac, expr_ref_vector& fmls); + void simplify_formulas(tactic &tac, expr_ref_vector &fmls); void add_premises(decl2rel const &pts, unsigned lvl, datalog::rule &rule, expr_ref_vector &r); - app_ref mk_fresh_rf_tag (); + app_ref mk_fresh_rf_tag(); // get tagged formulae of all of the background invariants for all of the // predecessors of the current transformer @@ -535,69 +541,69 @@ class pred_transformer { return m_frames.get_bg_invs(); } -public: - pred_transformer(context& ctx, manager& pm, func_decl* head); + public: + pred_transformer(context &ctx, manager &pm, func_decl *head); - inline bool use_native_mbp (); + inline bool use_native_mbp(); bool mk_mdl_rf_consistent(const datalog::rule *r, model &mdl); - reach_fact *get_rf (expr *v) { + reach_fact *get_rf(expr *v) { for (auto *rf : m_reach_facts) { - if (v == rf->get()) {return rf;} + if (v == rf->get()) { return rf; } } return nullptr; } void find_predecessors(datalog::rule const &r, ptr_vector &predicates) const; - void add_rule(datalog::rule* r) {m_rules.push_back(r);} + void add_rule(datalog::rule *r) { m_rules.push_back(r); } void add_use(pred_transformer *pt) { if (!m_use.contains(pt)) { m_use.insert(pt); } } - void initialize(decl2rel const& pts); - - func_decl* head() const {return m_head;} - ptr_vector const& rules() const {return m_rules;} - func_decl* sig(unsigned i) const {return m_sig[i];} // signature - func_decl* const* sig() {return m_sig.data();} - unsigned sig_size() const {return m_sig.size();} - expr* transition() const {return m_transition;} - expr* init() const {return m_init;} - expr* rule2tag(datalog::rule const* r) { + void initialize(decl2rel const &pts); + + func_decl *head() const { return m_head; } + ptr_vector const &rules() const { return m_rules; } + func_decl *sig(unsigned i) const { return m_sig[i]; } // signature + func_decl *const *sig() { return m_sig.data(); } + unsigned sig_size() const { return m_sig.size(); } + expr *transition() const { return m_transition; } + expr *init() const { return m_init; } + expr *rule2tag(datalog::rule const *r) { pt_rule *p; return m_pt_rules.find_by_rule(*r, p) ? p->tag() : nullptr; } - unsigned get_num_levels() const {return m_frames.size ();} - expr_ref get_cover_delta(func_decl* p_orig, int level); - void add_cover(unsigned level, expr* property, bool bg = false); + unsigned get_num_levels() const { return m_frames.size(); } + expr_ref get_cover_delta(func_decl *p_orig, int level); + void add_cover(unsigned level, expr *property, bool bg = false); expr_ref get_reachable(); - std::ostream& display(std::ostream& strm) const; + std::ostream &display(std::ostream &strm) const; - void collect_statistics(statistics& st) const; + void collect_statistics(statistics &st) const; void reset_statistics(); - bool is_must_reachable(expr* state, model_ref* model = nullptr); + bool is_must_reachable(expr *state, model_ref *model = nullptr); /// \brief Returns reachability fact active in the given model /// all determines whether initial reachability facts are included as well - reach_fact *get_used_rf(model& mdl, bool all = true); + reach_fact *get_used_rf(model &mdl, bool all = true); /// \brief Returns reachability fact active in the origin of the given model - reach_fact* get_used_origin_rf(model &mdl, unsigned oidx); + reach_fact *get_used_origin_rf(model &mdl, unsigned oidx); /// \brief Collects all the reachable facts used in mdl - void get_all_used_rf(model &mdl, unsigned oidx, reach_fact_ref_vector& res); + void get_all_used_rf(model &mdl, unsigned oidx, reach_fact_ref_vector &res); void get_all_used_rf(model &mdl, reach_fact_ref_vector &res); expr_ref get_origin_summary(model &mdl, unsigned level, unsigned oidx, bool must, const ptr_vector **aux); bool is_ctp_blocked(lemma *lem); const datalog::rule *find_rule(model &mdl); - const datalog::rule *find_rule(model &mev, bool& is_concrete, - bool_vector& reach_pred_used, - unsigned& num_reuse_reach); - expr* get_transition(datalog::rule const& r) { + const datalog::rule *find_rule(model &mev, bool &is_concrete, + bool_vector &reach_pred_used, + unsigned &num_reuse_reach); + expr *get_transition(datalog::rule const &r) { pt_rule *p; return m_pt_rules.find_by_rule(r, p) ? p->trans() : nullptr; } - ptr_vector& get_aux_vars(datalog::rule const& r) { + ptr_vector &get_aux_vars(datalog::rule const &r) { pt_rule *p = nullptr; VERIFY(m_pt_rules.find_by_rule(r, p)); return p->auxs(); @@ -606,23 +612,23 @@ class pred_transformer { bool propagate_to_next_level(unsigned level); void propagate_to_infinity(unsigned level); /// \brief Add a lemma to the current context and all users - bool add_lemma(expr * e, unsigned lvl, bool bg); - bool add_lemma(lemma* lem) {return m_frames.add_lemma(lem);} - expr* get_reach_case_var (unsigned idx) const; - bool has_rfs () const { return !m_reach_facts.empty () ;} + bool add_lemma(expr *e, unsigned lvl, bool bg); + bool add_lemma(lemma *lem) { return m_frames.add_lemma(lem); } + expr *get_reach_case_var(unsigned idx) const; + bool has_rfs() const { return !m_reach_facts.empty(); } /// initialize reachability facts using initial rules - void init_rfs (); + void init_rfs(); reach_fact *mk_rf(pob &n, model &mdl, const datalog::rule &r); - void add_rf (reach_fact *fact, bool force = false); // add reachability fact - reach_fact* get_last_rf () const { return m_reach_facts.back (); } - expr* get_last_rf_tag () const; + void add_rf(reach_fact *fact, bool force = false); // add reachability fact + reach_fact *get_last_rf() const { return m_reach_facts.back(); } + expr *get_last_rf_tag() const; pob *mk_pob(pob *parent, unsigned level, unsigned depth, expr *post, app_ref_vector const &b) { return m_pobs.mk_pob(parent, level, depth, post, b); } - pob* find_pob(pob *parent, expr *post) { + pob *find_pob(pob *parent, expr *post) { return m_pobs.find_pob(parent, post); } @@ -630,12 +636,12 @@ class pred_transformer { return m_pobs.mk_pob(parent, level, depth, post); } - lbool is_reachable(pob& n, expr_ref_vector* core, model_ref *model, - unsigned& uses_level, bool& is_concrete, + lbool is_reachable(pob &n, expr_ref_vector *core, model_ref *model, + unsigned &uses_level, bool &is_concrete, datalog::rule const *&r, bool_vector &reach_pred_used, - unsigned& num_reuse_reach); + unsigned &num_reuse_reach); bool is_invariant(unsigned level, lemma *lem, unsigned &solver_level, - expr_ref_vector* core = nullptr); + expr_ref_vector *core = nullptr); bool is_invariant(unsigned level, expr *lem, unsigned &solver_level, expr_ref_vector *core = nullptr) { @@ -644,33 +650,33 @@ class pred_transformer { return false; } - bool check_inductive(unsigned level, expr_ref_vector& state, - unsigned& assumes_level, unsigned weakness = UINT_MAX); + bool check_inductive(unsigned level, expr_ref_vector &state, + unsigned &assumes_level, unsigned weakness = UINT_MAX); expr_ref get_formulas(unsigned level, bool bg = false) const; void simplify_formulas(); - context& get_context () const {return ctx;} - manager& get_manager() const {return pm;} - ast_manager& get_ast_manager() const {return m;} + context &get_context() const { return ctx; } + manager &get_manager() const { return pm; } + ast_manager &get_ast_manager() const { return m; } - void add_premises(decl2rel const& pts, unsigned lvl, expr_ref_vector& r); + void add_premises(decl2rel const &pts, unsigned lvl, expr_ref_vector &r); - void inherit_lemmas(pred_transformer& other); + void inherit_lemmas(pred_transformer &other); void ground_free_vars(expr *e, app_ref_vector &vars, ptr_vector &aux_vars, bool is_init); /// \brief Adds a given expression to the set of initial rules - app* extend_initial (expr *e); + app *extend_initial(expr *e); /// \brief Returns true if the obligation is already blocked by current /// lemmas - bool is_blocked (pob &n, unsigned &uses_level); + bool is_blocked(pob &n, unsigned &uses_level); /// \brief Returns true if the obligation is already blocked by current /// quantified lemmas - bool is_qblocked (pob &n); + bool is_qblocked(pob &n); /// \brief interface to Model Based Projection void mbp(app_ref_vector &vars, expr_ref &fml, model &mdl, @@ -687,7 +693,7 @@ class pred_transformer { m_frames.get_frame_all_lemmas(out, with_bg); } void update_solver_with_rfs(prop_solver *solver, const pred_transformer &pt, - app *rule_tag, unsigned pos); + app *rule_tag, unsigned pos); lemma_cluster *mk_cluster(const expr_ref &pattern) { return m_cluster_db.mk_cluster(pattern); @@ -699,7 +705,7 @@ class pred_transformer { } /// The number of clusters with pattern \p pattern - unsigned clstr_count(const expr_ref & pattern) { + unsigned clstr_count(const expr_ref &pattern) { return m_cluster_db.clstr_count(pattern); } @@ -760,9 +766,10 @@ class pob { // depth -> watch std::map m_expand_watches; unsigned m_blocked_lvl; - // clang-format on + // clang-format on + // clang-format off -public: + public: pob(pob *parent, pred_transformer &pt, unsigned level, unsigned depth = 0, bool add_to_parent = true); @@ -774,11 +781,11 @@ class pob { void set_post(expr *post, app_ref_vector const &binding); void set_post(expr *post); - unsigned weakness() {return m_weakness;} - void bump_weakness() {m_weakness++;} - void reset_weakness() {m_weakness=0;} + unsigned weakness() { return m_weakness; } + void bump_weakness() { m_weakness++; } + void reset_weakness() { m_weakness = 0; } - void inc_level () { + void inc_level() { SASSERT(!is_in_queue()); m_level++; m_depth++; @@ -786,79 +793,79 @@ class pob { } void inherit(pob const &p); - void set_derivation (derivation *d) {m_derivation = d;} - bool has_derivation () const {return (bool)m_derivation;} - derivation &get_derivation() const {return *m_derivation.get ();} - void reset_derivation () {set_derivation (nullptr);} + void set_derivation(derivation *d) { m_derivation = d; } + bool has_derivation() const { return (bool)m_derivation; } + derivation &get_derivation() const { return *m_derivation.get(); } + void reset_derivation() { set_derivation(nullptr); } /// detaches derivation from the node without deallocating - derivation* detach_derivation () {return m_derivation.detach ();} + derivation *detach_derivation() { return m_derivation.detach(); } - pob* parent () const { return m_parent.get (); } + pob *parent() const { return m_parent.get(); } - pred_transformer& pt () const { return m_pt; } - ast_manager& get_ast_manager () const { return m_pt.get_ast_manager (); } - manager& get_manager () const { return m_pt.get_manager (); } - context& get_context () const {return m_pt.get_context ();} + pred_transformer &pt() const { return m_pt; } + ast_manager &get_ast_manager() const { return m_pt.get_ast_manager(); } + manager &get_manager() const { return m_pt.get_manager(); } + context &get_context() const { return m_pt.get_context(); } - unsigned level () const { return m_level; } - unsigned depth () const {return m_depth;} - unsigned width () const {return m_kids.size();} - unsigned blocked_at(unsigned lvl=0){ + unsigned level() const { return m_level; } + unsigned depth() const { return m_depth; } + unsigned width() const { return m_kids.size(); } + unsigned blocked_at(unsigned lvl = 0) { return (m_blocked_lvl = std::max(lvl, m_blocked_lvl)); } - bool is_in_queue() const {return m_in_queue;} - void set_in_queue(bool v) {m_in_queue = v;} - bool use_farkas_generalizer () const {return m_use_farkas;} - void set_farkas_generalizer (bool v) {m_use_farkas = v;} + bool is_in_queue() const { return m_in_queue; } + void set_in_queue(bool v) { m_in_queue = v; } + bool use_farkas_generalizer() const { return m_use_farkas; } + void set_farkas_generalizer(bool v) { m_use_farkas = v; } - expr* post() const { return m_post.get (); } + expr *post() const { return m_post.get(); } - bool is_closed () const { return !m_open; } + bool is_closed() const { return !m_open; } void close(); - const ptr_vector &children() const {return m_kids;} - void add_child (pob &v) {m_kids.push_back (&v);} - void erase_child (pob &v) {m_kids.erase (&v);} + const ptr_vector &children() const { return m_kids; } + void add_child(pob &v) { m_kids.push_back(&v); } + void erase_child(pob &v) { m_kids.erase(&v); } - const ptr_vector &lemmas() const {return m_lemmas;} - void add_lemma(lemma* new_lemma) {m_lemmas.push_back(new_lemma);} + const ptr_vector &lemmas() const { return m_lemmas; } + void add_lemma(lemma *new_lemma) { m_lemmas.push_back(new_lemma); } - bool is_ground () const { return m_binding.empty (); } + bool is_ground() const { return m_binding.empty(); } unsigned get_free_vars_size() const { return m_binding.size(); } - app_ref_vector const &get_binding() const {return m_binding;} + app_ref_vector const &get_binding() const { return m_binding; } /* * Returns a map from variable id to skolems that implicitly * represent them in the pob. Note that only some (or none) of the * skolems returned actually appear in the post of the pob. */ - void get_skolems(app_ref_vector& v); + void get_skolems(app_ref_vector &v); void on_expand() { m_expand_watches[m_depth].start(); - if (m_parent.get()){m_parent.get()->on_expand();} + if (m_parent.get()) { m_parent.get()->on_expand(); } } void off_expand() { m_expand_watches[m_depth].stop(); - if (m_parent.get()){m_parent.get()->off_expand();} + if (m_parent.get()) { m_parent.get()->off_expand(); } } double get_expand_time(unsigned depth) { return m_expand_watches[depth].get_seconds(); } - void inc_ref () {++m_ref_count;} - void dec_ref () { + void inc_ref() { ++m_ref_count; } + void dec_ref() { --m_ref_count; - if (m_ref_count == 0) {dealloc(this);} + if (m_ref_count == 0) { dealloc(this); } } std::ostream &display(std::ostream &out, bool full = false) const; class on_expand_event { pob &m_p; - public: - on_expand_event(pob &p) : m_p(p) {m_p.on_expand();} - ~on_expand_event() {m_p.off_expand();} + public: + on_expand_event(pob &p) : m_p(p) { m_p.on_expand(); } + ~on_expand_event() { m_p.off_expand(); } }; }; @@ -867,11 +874,11 @@ inline std::ostream &operator<<(std::ostream &out, pob const &p) { } struct pob_lt_proc { - bool operator() (const pob *pn1, const pob *pn2) const; + bool operator()(const pob *pn1, const pob *pn2) const; }; struct pob_gt_proc { - bool operator() (const pob *n1, const pob *n2) const { + bool operator()(const pob *n1, const pob *n2) const { return pob_lt_proc()(n2, n1); } }; @@ -890,19 +897,19 @@ class derivation { bool m_must; app_ref_vector m_ovars; - public: - premise (pred_transformer &pt, unsigned oidx, expr *summary, bool must, - const ptr_vector *aux_vars = nullptr); + public: + premise(pred_transformer &pt, unsigned oidx, expr *summary, bool must, + const ptr_vector *aux_vars = nullptr); - bool is_must() {return m_must;} - expr * get_summary() {return m_summary.get ();} - app_ref_vector &get_ovars() {return m_ovars;} - unsigned get_oidx() {return m_oidx;} - pred_transformer &pt() {return m_pt;} + bool is_must() { return m_must; } + expr *get_summary() { return m_summary.get(); } + app_ref_vector &get_ovars() { return m_ovars; } + unsigned get_oidx() { return m_oidx; } + pred_transformer &pt() { return m_pt; } /// \brief Updated the summary. /// The new summary is over n-variables. - void set_summary(expr * summary, bool must, + void set_summary(expr *summary, bool must, const ptr_vector *aux_vars = nullptr); }; @@ -922,14 +929,15 @@ class derivation { // implicitly existentially quantified variables in m_trans app_ref_vector m_evars; // clang-format on + // clang-format off /// -- create next child using given model as the guide /// -- returns NULL if there is no next child - pob* create_next_child (model &mdl); + pob *create_next_child(model &mdl); /// existentially quantify vars and skolemize the result void exist_skolemize(expr *fml, app_ref_vector &vars, expr_ref &res); -public: + public: derivation(pob &parent, datalog::rule const &rule, expr *trans, app_ref_vector const &evars); void add_premise(pred_transformer &pt, unsigned oidx, expr *summary, @@ -938,40 +946,40 @@ class derivation { /// creates the first child. Must be called after all the premises /// are added. The model must be valid for the premises /// Returns NULL if no child exits - pob *create_first_child (model &mdl); + pob *create_first_child(model &mdl); /// Create the next child. Must summary of the currently active /// premise must be consistent with the transition relation - pob *create_next_child (); - - datalog::rule const& get_rule () const { return m_rule; } - pob& get_parent () const { return m_parent; } - ast_manager &get_ast_manager () const {return m_parent.get_ast_manager ();} - manager &get_manager () const {return m_parent.get_manager ();} - context &get_context() const {return m_parent.get_context();} - pred_transformer &pt() const {return m_parent.pt();} + pob *create_next_child(); + + datalog::rule const &get_rule() const { return m_rule; } + pob &get_parent() const { return m_parent; } + ast_manager &get_ast_manager() const { return m_parent.get_ast_manager(); } + manager &get_manager() const { return m_parent.get_manager(); } + context &get_context() const { return m_parent.get_context(); } + pred_transformer &pt() const { return m_parent.pt(); } }; class pob_queue { typedef std::priority_queue, pob_gt_proc> pob_queue_ty; - pob_ref m_root; + pob_ref m_root; unsigned m_max_level; unsigned m_min_depth; - pob_queue_ty m_data; + pob_queue_ty m_data; -public: - pob_queue(): m_root(nullptr), m_max_level(0), m_min_depth(0) {} + public: + pob_queue() : m_root(nullptr), m_max_level(0), m_min_depth(0) {} void reset(); - pob* top(); + pob *top(); void pop(); - void push (pob &n); + void push(pob &n); - void inc_level () { - SASSERT (!m_data.empty () || m_root); + void inc_level() { + SASSERT(!m_data.empty() || m_root); m_max_level++; m_min_depth++; if (m_root && m_data.empty()) { @@ -981,35 +989,35 @@ class pob_queue { } } - pob& get_root() const {return *m_root.get ();} - void set_root(pob& n); - bool is_root(pob& n) const {return m_root.get () == &n;} + pob &get_root() const { return *m_root.get(); } + void set_root(pob &n); + bool is_root(pob &n) const { return m_root.get() == &n; } - unsigned max_level() const {return m_max_level;} - unsigned min_depth() const {return m_min_depth;} - size_t size() const {return m_data.size();} + unsigned max_level() const { return m_max_level; } + unsigned min_depth() const { return m_min_depth; } + size_t size() const { return m_data.size(); } }; /** * Generalizers (strengthens) a lemma */ class lemma_generalizer { -protected: - context& m_ctx; + protected: + context &m_ctx; -public: - lemma_generalizer(context& ctx): m_ctx(ctx) {} + public: + lemma_generalizer(context &ctx) : m_ctx(ctx) {} virtual ~lemma_generalizer() = default; virtual void operator()(lemma_ref &lemma) = 0; - virtual void collect_statistics(statistics& st) const {} + virtual void collect_statistics(statistics &st) const {} virtual void reset_statistics() {} }; class spacer_callback { -protected: + protected: context &m_context; -public: + public: spacer_callback(context &context) : m_context(context) {} virtual ~spacer_callback() = default; @@ -1035,9 +1043,9 @@ class spacer_callback { // order in which children are processed enum spacer_children_order { - CO_RULE, // same order as in the rule - CO_REV_RULE, // reverse order of the rule - CO_RANDOM // random shuffle + CO_RULE, // same order as in the rule + CO_REV_RULE, // reverse order of the rule + CO_RANDOM // random shuffle }; class context { @@ -1126,6 +1134,7 @@ class context { json_marshaller m_json_marshaller; std::fstream* m_trace_stream; // clang-format on + // clang-format off // Solve using gpdr strategy lbool gpdr_solve_core(); @@ -1137,7 +1146,7 @@ class context { void log_enter_level(unsigned lvl); void log_propagate(); void log_expand_pob(pob &); - void log_add_lemma(pred_transformer &, lemma&); + void log_add_lemma(pred_transformer &, lemma &); // Functions used by search. lbool solve_core(unsigned from_lvl = 0); @@ -1148,7 +1157,7 @@ class context { bool is_reachable(pob &n); lbool expand_pob(pob &n, pob_ref_buffer &out); bool create_children(pob &n, const datalog::rule &r, model &mdl, - const bool_vector& reach_pred_used, + const bool_vector &reach_pred_used, pob_ref_buffer &out); /** @@ -1159,25 +1168,25 @@ class context { return expr_ref(pr.get(), pr.get_manager()); } expr_ref mk_unsat_answer() const; - unsigned get_cex_depth (); + unsigned get_cex_depth(); // Generate inductive property - void get_level_property(unsigned lvl, expr_ref_vector& res, - vector & rs, + void get_level_property(unsigned lvl, expr_ref_vector &res, + vector &rs, bool with_bg = false) const; // Initialization void init_lemma_generalizers(); void reset_lemma_generalizers(); - void inherit_lemmas(const decl2rel& rels); + void inherit_lemmas(const decl2rel &rels); void init_global_smt_params(); - void init_rules(datalog::rule_set& rules, decl2rel& transformers); + void init_rules(datalog::rule_set &rules, decl2rel &transformers); // (re)initialize context with new relations void init(const decl2rel &rels); bool validate(); bool check_invariant(unsigned lvl); - bool check_invariant(unsigned lvl, func_decl* fn); + bool check_invariant(unsigned lvl, func_decl *fn); void checkpoint(); @@ -1191,86 +1200,86 @@ class context { lbool handle_unknown(pob &n, const datalog::rule *r, model &model); bool mk_mdl_rf_consistent(model &mdl); -public: + public: /** Initial values of predicates are stored in corresponding relations in dctx. We check whether there is some reachable state of the relation checked_relation. */ - context(fp_params const& params, ast_manager& m); + context(fp_params const ¶ms, ast_manager &m); ~context(); const fp_params &get_params() const { return m_params; } - bool use_eq_prop() const {return m_use_eq_prop;} - bool use_native_mbp() const {return m_use_native_mbp;} - bool use_ground_pob() const {return m_ground_pob;} - bool use_instantiate() const {return m_instantiate;} - bool weak_abs() const {return m_weak_abs;} - bool use_qlemmas() const {return m_use_qlemmas;} - bool use_euf_gen() const {return m_use_euf_gen;} - bool use_lim_num_gen() const {return m_use_lim_num_gen;} - bool simplify_pob() const {return m_simplify_pob;} - bool use_ctp() const {return m_use_ctp;} - bool use_inc_clause() const {return m_use_inc_clause;} + bool use_eq_prop() const { return m_use_eq_prop; } + bool use_native_mbp() const { return m_use_native_mbp; } + bool use_ground_pob() const { return m_ground_pob; } + bool use_instantiate() const { return m_instantiate; } + bool weak_abs() const { return m_weak_abs; } + bool use_qlemmas() const { return m_use_qlemmas; } + bool use_euf_gen() const { return m_use_euf_gen; } + bool use_lim_num_gen() const { return m_use_lim_num_gen; } + bool simplify_pob() const { return m_simplify_pob; } + bool use_ctp() const { return m_use_ctp; } + bool use_inc_clause() const { return m_use_inc_clause; } unsigned blast_term_ite_inflation() const { return m_blast_term_ite_inflation; } - bool elim_aux() const {return m_elim_aux;} - bool reach_dnf() const {return m_reach_dnf;} - bool use_bg_invs() const {return m_use_bg_invs;} - - ast_manager& get_ast_manager() const {return m;} - manager& get_manager() {return m_pm;} - const manager & get_manager() const {return m_pm;} - decl2rel const& get_pred_transformers() const {return m_rels;} + bool elim_aux() const { return m_elim_aux; } + bool reach_dnf() const { return m_reach_dnf; } + bool use_bg_invs() const { return m_use_bg_invs; } + + ast_manager &get_ast_manager() const { return m; } + manager &get_manager() { return m_pm; } + const manager &get_manager() const { return m_pm; } + decl2rel const &get_pred_transformers() const { return m_rels; } pred_transformer &get_pred_transformer(func_decl *p) const { return *m_rels.find(p); } - datalog::context& get_datalog_context() const { + datalog::context &get_datalog_context() const { SASSERT(m_context); return *m_context; } - void update_rules(datalog::rule_set& rules); + void update_rules(datalog::rule_set &rules); lbool solve(unsigned from_lvl = 0); - lbool solve_from_lvl (unsigned from_lvl); + lbool solve_from_lvl(unsigned from_lvl); - expr_ref get_answer(); + expr_ref get_answer(); /** * get bottom-up (from query) sequence of ground predicate instances * (for e.g. P(0,1,0,0,3)) that together form a ground derivation to query */ - expr_ref get_ground_sat_answer () const; + expr_ref get_ground_sat_answer() const; proof_ref get_ground_refutation() const; - void get_rules_along_trace (datalog::rule_ref_vector& rules); + void get_rules_along_trace(datalog::rule_ref_vector &rules); - void collect_statistics(statistics& st) const; + void collect_statistics(statistics &st) const; void reset_statistics(); void reset(); - std::ostream& display(std::ostream& out) const; - void display_certificate(std::ostream& out) const; + std::ostream &display(std::ostream &out) const; + void display_certificate(std::ostream &out) const; - pob& get_root() const {return m_pob_queue.get_root();} - void set_query(func_decl* q) {m_query_pred = q;} - void set_unsat() {m_last_result = l_false;} - void set_model_converter(model_converter_ref& mc) {m_mc = mc;} + pob &get_root() const { return m_pob_queue.get_root(); } + void set_query(func_decl *q) { m_query_pred = q; } + void set_unsat() { m_last_result = l_false; } + void set_model_converter(model_converter_ref &mc) { m_mc = mc; } model_converter_ref get_model_converter() { return m_mc; } - void set_proof_converter(proof_converter_ref& pc) { m_pc = pc; } - scoped_ptr_vector &callbacks() {return m_callbacks;} + void set_proof_converter(proof_converter_ref &pc) { m_pc = pc; } + scoped_ptr_vector &callbacks() { return m_callbacks; } - unsigned get_num_levels(func_decl* p); + unsigned get_num_levels(func_decl *p); - expr_ref get_cover_delta(int level, func_decl* p_orig, func_decl* p); - void add_cover(int level, func_decl* pred, expr* property, bool bg = false); - expr_ref get_reachable (func_decl* p); - void add_invariant (func_decl *pred, expr* property); + expr_ref get_cover_delta(int level, func_decl *p_orig, func_decl *p); + void add_cover(int level, func_decl *pred, expr *property, bool bg = false); + expr_ref get_reachable(func_decl *p); + void add_invariant(func_decl *pred, expr *property); model_ref get_model(); - proof_ref get_proof() const {return get_ground_refutation();} + proof_ref get_proof() const { return get_ground_refutation(); } - expr_ref get_constraints (unsigned lvl); - void add_constraint (expr *c, unsigned lvl); + expr_ref get_constraints(unsigned lvl); + void add_constraint(expr *c, unsigned lvl); void new_lemma_eh(pred_transformer &pt, lemma *lem); void new_pob_eh(pob *p); @@ -1279,11 +1288,10 @@ class context { // three different solvers with three different sets of parameters // different solvers are used for different types of queries in spacer - solver* mk_solver0() {return m_pool0->mk_solver();} - solver* mk_solver1() {return m_pool1->mk_solver();} - solver* mk_solver2() {return m_pool2->mk_solver();} + solver *mk_solver0() { return m_pool0->mk_solver(); } + solver *mk_solver1() { return m_pool1->mk_solver(); } + solver *mk_solver2() { return m_pool2->mk_solver(); } }; -inline bool pred_transformer::use_native_mbp () {return ctx.use_native_mbp ();} +inline bool pred_transformer::use_native_mbp() { return ctx.use_native_mbp(); } } // namespace spacer - From 7db923a45c889c328ebc58b4674299d1984787fa Mon Sep 17 00:00:00 2001 From: hgvk94 Date: Tue, 16 Feb 2021 10:44:35 -0500 Subject: [PATCH 09/78] Computation of convex closure and matrix kernel Various LA functions. The implementations are somewhat preliminary. Convex closure is simplemented via syntactic convex closure procedure. Kernel computation considers many common cases. spacer_arith_kernel_sage implements kernel computation by call external Sage binary. It is used only for debugging and experiments. There is no link dependence on Sage. If desired, it can be removed. --- src/muz/spacer/CMakeLists.txt | 3 + src/muz/spacer/spacer_arith_kernel.cpp | 41 +++ src/muz/spacer/spacer_arith_kernel.h | 90 +++++ src/muz/spacer/spacer_arith_kernel_sage.cpp | 370 +++++++++++++++++++ src/muz/spacer/spacer_convex_closure.cpp | 375 ++++++++++++++++++++ src/muz/spacer/spacer_convex_closure.h | 198 +++++++++++ src/muz/spacer/spacer_matrix.cpp | 258 ++++++++------ src/muz/spacer/spacer_matrix.h | 52 ++- src/muz/spacer/spacer_unsat_core_plugin.cpp | 2 +- 9 files changed, 1256 insertions(+), 133 deletions(-) create mode 100644 src/muz/spacer/spacer_arith_kernel.cpp create mode 100644 src/muz/spacer/spacer_arith_kernel.h create mode 100644 src/muz/spacer/spacer_arith_kernel_sage.cpp create mode 100644 src/muz/spacer/spacer_convex_closure.cpp create mode 100644 src/muz/spacer/spacer_convex_closure.h diff --git a/src/muz/spacer/CMakeLists.txt b/src/muz/spacer/CMakeLists.txt index c1ee41e95f3..a6d1eefb890 100644 --- a/src/muz/spacer/CMakeLists.txt +++ b/src/muz/spacer/CMakeLists.txt @@ -30,6 +30,9 @@ z3_add_component(spacer spacer_mbc.cpp spacer_pdr.cpp spacer_sat_answer.cpp + spacer_convex_closure.cpp + spacer_arith_kernel.cpp + spacer_arith_kernel_sage.cpp COMPONENT_DEPENDENCIES arith_tactics core_tactics diff --git a/src/muz/spacer/spacer_arith_kernel.cpp b/src/muz/spacer/spacer_arith_kernel.cpp new file mode 100644 index 00000000000..519fd40cba9 --- /dev/null +++ b/src/muz/spacer/spacer_arith_kernel.cpp @@ -0,0 +1,41 @@ +/**++ +Copyright (c) 2020 Arie Gurfinkel + +Module Name: + + spacer_arith_kernel.cpp + +Abstract: + + Compute kernel of a matrix + +Author: + + Hari Govind + Arie Gurfinkel + +Notes: + +--*/ + +#include "muz/spacer/spacer_arith_kernel.h" + +using namespace spacer; + +bool spacer_arith_kernel::compute_kernel() { + SASSERT(m_matrix.num_rows() > 1); + + if (m_matrix.compute_linear_deps(m_kernel)) { + // the matrix cannot be reduced further + if (m_matrix.num_cols() - m_kernel.num_rows() <= 1) return true; + + m_kernel.reset(m_kernel.num_cols()); + SASSERT(m_matrix.num_cols() > 2); + } + if (m_matrix.num_cols() > 2) m_st.m_failed++; + if (m_plugin && m_matrix.num_cols() > 2) { + return m_plugin->compute_kernel(m_matrix, m_kernel); + } + return false; +} + diff --git a/src/muz/spacer/spacer_arith_kernel.h b/src/muz/spacer/spacer_arith_kernel.h new file mode 100644 index 00000000000..ea4f2d061c3 --- /dev/null +++ b/src/muz/spacer/spacer_arith_kernel.h @@ -0,0 +1,90 @@ +#pragma once +/**++ +Copyright (c) 2020 Arie Gurfinkel + +Module Name: + + spacer_arith_kernel.cpp + +Abstract: + + Compute kernel of a matrix + +Author: + + Hari Govind + Arie Gurfinkel + +Notes: + +--*/ + +#include "spacer_matrix.h" +#include "util/statistics.h" +namespace spacer { + +/** + Computes a kernel of a matrix. +*/ +class spacer_arith_kernel { + public: + class plugin { + public: + virtual ~plugin() {} + virtual bool compute_kernel(const spacer_matrix &in_matrix, + spacer_matrix &out_kernel) = 0; + virtual void collect_statistics(statistics &st) const = 0; + virtual void reset_statistics() = 0; + virtual void reset() = 0; + }; + + protected: + struct stats { + unsigned m_failed; + stats() { reset(); } + void reset() { m_failed = 0; } + }; + stats m_st; + + /// Input matrix for which kernel is to be computed + const spacer_matrix &m_matrix; + + /// Output matrix representing the kernel + spacer_matrix m_kernel; + + scoped_ptr m_plugin; + + public: + spacer_arith_kernel(spacer_matrix &matrix) + : m_matrix(matrix), m_kernel(0, 0) {} + virtual ~spacer_arith_kernel() = default; + + void set_plugin(spacer_arith_kernel::plugin *plugin) { m_plugin = plugin; } + + /// Computes kernel of a matrix + /// returns true if the computation was successful + /// use \p spacer_arith_kernel::get_kernel() to get the kernel + bool compute_kernel(); + bool operator()() { return compute_kernel(); } + + const spacer_matrix &get_kernel() const { return m_kernel; } + + void reset() { + m_kernel = spacer_matrix(0, 0); + if (m_plugin) m_plugin->reset(); + } + + virtual void collect_statistics(statistics &st) const { + st.update("SPACER arith kernel failed", m_st.m_failed); + if (m_plugin) { m_plugin->collect_statistics(st); } + } + virtual void reset_statistics() { + m_st.reset(); + if (m_plugin) m_plugin->reset_statistics(); + } +}; + +/// \brief Kernel computation using Sage package +spacer_arith_kernel::plugin *mk_sage_plugin(); + +} // namespace spacer diff --git a/src/muz/spacer/spacer_arith_kernel_sage.cpp b/src/muz/spacer/spacer_arith_kernel_sage.cpp new file mode 100644 index 00000000000..f815fa6422e --- /dev/null +++ b/src/muz/spacer/spacer_arith_kernel_sage.cpp @@ -0,0 +1,370 @@ +/**++ +Copyright (c) 2020 Arie Gurfinkel + +Module Name: + + spacer_arith_kernel_sage.cpp + +Abstract: + + Matrix kernel computation using Sage + +Author: + + Hari Govind + Arie Gurfinkel + +Notes: + + USED FOR DEBUGGING AND PROTOTYPING ONLY!!! +--*/ + +#include "muz/spacer/spacer_arith_kernel.h" +#include "util/stopwatch.h" +#include "util/util.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace spacer; + +namespace { +/** +Abstracts interface to Sage. + +Supports initialization and writing to Sage. + +To get output from Sage, write to file and read from the file. + +HG: Could not find standard methods to convert file descriptors to streams. +*/ +class Sage { + FILE *m_out; + FILE *m_in; + std::string tmp_name; + pid_t child_pid; + + /// Test sage communication + bool test(); + + public: + /// Start sage interface + Sage(); + ~Sage() { + kill(child_pid, SIGKILL); + int status; + waitpid(child_pid, &status, 0); + } + FILE *get_ostream() const { return m_out; } + FILE *get_istream() const { return m_in; } +}; + +Sage::Sage() { + int to_sage_pipe[2]; + int from_sage_pipe[2]; + int ok = pipe(to_sage_pipe); + if (ok) { + perror("sage pipe1"); + exit(1); + } + ok = pipe(from_sage_pipe); + if (ok) { + perror("sage pipe2"); + exit(1); + } + + pid_t pid = fork(); + if (pid) { + m_out = fdopen(to_sage_pipe[1], "w"); + m_in = fdopen(from_sage_pipe[0], "r"); + + /* parent */ + close(to_sage_pipe[0]); + close(from_sage_pipe[1]); + + child_pid = pid; + if (!test()) { + TRACE("sage-interface", tout << "Sage test failed \n";); + } + } else if (pid == 0) { + /* child */ + + // setup file descriptor + close(to_sage_pipe[1]); + close(from_sage_pipe[0]); + dup2(to_sage_pipe[0], STDIN_FILENO); + dup2(from_sage_pipe[1], STDOUT_FILENO); + + // setup arguments, assume sage is in PATH + char *const argv[3] = {(char *)"sage", NULL, NULL}; + // XXX: sage complains that it can't find $HOME, but works regardless + execvp("sage", argv); + perror("execvpe for sage"); + } else { + perror("fork"); + exit(1); + } +} + +bool Sage::test() { + char temp_name[] = "/tmp/spacersage.XXXXXX"; + int tmp_fd = mkstemp(temp_name); + if (tmp_fd == -1) { + // Error: failed to create temp file + TRACE("sage-interface", tout << "failed to create temp file\n";); + return false; + } + TRACE("sage-interface", + tout << "writing test output to " << temp_name << "\n";); + fprintf(m_out, "f = open (\"\%s\", 'w')\n", temp_name); + // Do stuff + fprintf(m_out, "print >>f, 2 + 2\n"); + fprintf(m_out, "f.close()\n"); + fflush(m_out); + // indicate that sage is done by printing to pipe + fprintf(m_out, "print \"\\nok\\n\"\n"); + fprintf(m_out, "sys.stdout.flush()\n"); + fflush(m_out); + + // first wait for sage to write ok + char *std_ok = nullptr; + size_t n = 0; + ssize_t t = 0; + // read all the lines printed by sage until we get okay + // will block if Sage not found :( + do { + t = getline(&std_ok, &n, m_in); + if (t == -1 || feof(m_in) || ferror(m_in)) { + TRACE("sage-interface", + tout << "error while reading from sage pipe \n";); + return false; + } + CTRACE("sage-interface-verb", t > 0, + tout << "got sage std output " << std_ok << "\n";); + } while (strcmp(std_ok, "ok\n") != 0); + // delete object allocated by getline + delete std_ok; + TRACE("sage-interface", tout << "got ok from sage \n";); + + // read output from file + std::ifstream ifs(temp_name); + int ok = -1; + if (!ifs.is_open()) { + TRACE("sage-interface", tout << "failed to open file\n";); + return false; + } + + ifs >> ok; + + if (ifs.bad()) { + TRACE("sage-interface", tout << "error when reading from file\n";); + ifs.close(); + close(tmp_fd); + std::remove(temp_name); + return false; + } + + TRACE("sage-interface", tout << "got sage output " << ok << "\n";); + ifs.close(); + close(tmp_fd); + // TODO: remove file even if sage/spacer terminates before reaching here + std::remove(temp_name); + return ok == 4; +} + +class sage_arith_kernel_plugin : public spacer_arith_kernel::plugin { + struct stats { + stopwatch watch; + unsigned m_sage_calls; + stats() { reset(); } + void reset() { + watch.reset(); + m_sage_calls = 0; + } + }; + stats m_st; + + scoped_ptr m_sage; + bool compute_kernel(const spacer_matrix &in_matrix, + spacer_matrix &out_kernel) override; + std::string matrix_to_string(const spacer_matrix &matrix) const; + + public: + sage_arith_kernel_plugin() : m_sage(alloc(Sage)) {} + ~sage_arith_kernel_plugin() {} + + virtual void collect_statistics(statistics &st) const override { + st.update("time.spacer.sage", m_st.watch.get_seconds()); + st.update("SPACER sage calls", m_st.m_sage_calls); + } + virtual void reset_statistics() override { m_st.reset(); } + virtual void reset() override { m_sage = alloc(Sage); } +}; + +std::string +sage_arith_kernel_plugin::matrix_to_string(const spacer_matrix &matrix) const { + std::stringstream ss; + ss << "[\n"; + for (unsigned i = 0; i < matrix.num_rows(); i++) { + ss << "("; + for (unsigned j = 0; j < matrix.num_cols() - 1; j++) { + ss << matrix.get(i, j).to_string(); + ss << ", "; + } + ss << matrix.get(i, matrix.num_cols() - 1).to_string(); + ss << "),\n"; + } + ss << "]\n"; + return ss.str(); +} + +bool sage_arith_kernel_plugin::compute_kernel(const spacer_matrix &in_matrix, + spacer_matrix &out_kernel) { + scoped_watch _w_(m_st.watch); + + char temp_name[] = "/tmp/spacersage.XXXXXX"; + int tmp_fd = mkstemp(temp_name); + if (tmp_fd == -1) { + // Error: failed to create temp file + perror("temp file create"); + exit(1); + } + m_st.m_sage_calls++; + TRACE("sage-interface", tout << temp_name << "\n";); + unsigned n_cols = in_matrix.num_cols(); + unsigned n_rows = in_matrix.num_rows(); + TRACE("sage-interface", tout << "Going to compute kernel of " << n_rows + << " by " << n_cols << " matrix \n" + << matrix_to_string(in_matrix) << "\n";); + + auto out = m_sage->get_ostream(); + fprintf(out, "f = open (\"\%s\", 'w')\n", temp_name); + + // construct matrix in sage + std::stringstream ss_stream; + ss_stream << " a = matrix(ZZ,"; + ss_stream << n_rows; + ss_stream << (", "); + ss_stream << (n_cols + 1); + ss_stream << (", ["); + for (unsigned i = 0; i < n_rows; i++) { + ss_stream << ("["); + for (unsigned j = 0; j < n_cols; j++) { + ss_stream << in_matrix.get(i, j).to_string(); + ss_stream << (", "); + } + ss_stream << ("1"); + ss_stream << ("], "); + } + ss_stream << ("]);\n"); + fprintf(out, "%s", ss_stream.str().c_str()); + fprintf(out, "c = a.right_kernel().basis();\n"); + fflush(out); + fprintf(out, "print >> f, len(c);\n"); + fprintf(out, "print >> f, c;\n"); + fprintf(out, "f.close()\n"); + fflush(out); + fprintf(out, "print \"\\nok\\n\"\n"); + fprintf(out, "sys.stdout.flush()\n"); + fflush(out); + + // first wait for sage to write ok + char *std_ok = nullptr; + size_t n = 0; + ssize_t t = 0; + // read all the lines printed by sage until we get okay + auto in = m_sage->get_istream(); + do { + t = getline(&std_ok, &n, in); + if (t == -1 || feof(in) || ferror(in)) { + TRACE("sage-interface", + tout << "error while reading from sage pipe \n";); + return false; + } + CTRACE("sage-interface-verb", t > 0, + tout << "got sage std output " << std_ok << "\n";); + } while (strcmp(std_ok, "ok\n") != 0); + // delete object allocated by getline + delete std_ok; + TRACE("sage-interface", tout << "got ok from sage \n";); + + // read output from file + std::ifstream ifs(temp_name); + if (!ifs.is_open()) { + TRACE("sage-interface", tout << "failed to open file\n";); + return false; + } + + int num; + unsigned total_rows = 0, row = 0, col = 0; + char misc_char; + ifs >> std::skipws; + ifs >> total_rows; + if (total_rows == 0) { + ifs.close(); + close(tmp_fd); + std::remove(temp_name); + TRACE("sage-interface", tout << "Rank of kernel is zero\n";); + return false; + } + out_kernel = spacer_matrix(total_rows, n_cols + 1); + ifs >> misc_char; + SASSERT(misc_char == '['); + while (true) { + while (misc_char != '(' && misc_char != ']') ifs >> misc_char; + if (misc_char == ']') break; + SASSERT(misc_char == '('); + col = 0; + while (!ifs.bad() && !ifs.eof()) { + ifs >> num; + if (ifs.fail() || ifs.bad()) { + TRACE("sage-interface", + tout << "Woops!!! Couldn't read sage output propertly. " + "Abording\n";); + ifs.close(); + close(tmp_fd); + std::remove(temp_name); + out_kernel.reset(0); + SASSERT(false); + return false; + } + ifs >> misc_char; + out_kernel.set(row, col, rational(num)); + col++; + if (misc_char == ')') { break; } + SASSERT(misc_char == ','); + } + row++; + } + SASSERT(row == total_rows); + if (ifs.bad()) { + TRACE("sage-interface", tout << "error when reading from file\n";); + ifs.close(); + close(tmp_fd); + std::remove(temp_name); + return false; + } + + TRACE("sage-interface", tout << "finished reading sage output\n";); + ifs.close(); + + TRACE("sage-interface", + tout << "Kernel is " << matrix_to_string(out_kernel) << "\n";); + + close(tmp_fd); + // TODO: remove file even if sage/spacer terminates before reaching here + std::remove(temp_name); + return true; +} + +} // namespace + +namespace spacer { +spacer_arith_kernel::plugin *mk_sage_plugin() { return nullptr; } +} // namespace spacer diff --git a/src/muz/spacer/spacer_convex_closure.cpp b/src/muz/spacer/spacer_convex_closure.cpp new file mode 100644 index 00000000000..b666f13670c --- /dev/null +++ b/src/muz/spacer/spacer_convex_closure.cpp @@ -0,0 +1,375 @@ +/**++ +Copyright (c) 2020 Arie Gurfinkel + +Module Name: + + spacer_convex_closure.cpp + +Abstract: + + Compute convex closure of polyhedra + +Author: + + Hari Govind + Arie Gurfinkel + +Notes: + +--*/ + +#include "muz/spacer/spacer_convex_closure.h" + +namespace { +bool is_int_matrix(const spacer::spacer_matrix &matrix) { + rational val; + for (unsigned i = 0, rows = matrix.num_rows(); i < rows; i++) { + for (unsigned j = 0, cols = matrix.num_cols(); j < cols; j++) + if (!matrix.get(i, j).is_int()) return false; + } + return true; +} + +bool is_sorted(const vector &data) { + for (unsigned i = 0; i < data.size() - 1; i++) { + if (!(data[i] >= data[i + 1])) return false; + } + return true; +} + +/// Check whether all elements of \p data are congruent modulo \p m +bool is_congruent_mod(const vector &data, rational m) { + SASSERT(data.size() > 0); + rational p = data[0] % m; + for (auto k : data) + if (k % m != p) return false; + return true; +} + +app *mk_bvadd(ast_manager &m, unsigned num, expr *const *args) { + if (num == 0) return nullptr; + if (num == 1) return is_app(args[0]) ? to_app(args[0]) : nullptr; + + bv_util bv(m); + if (num == 2) { return bv.mk_bv_add(args[0], args[1]); } + + /// XXX no mk_bv_add for n-ary bv_add + return m.mk_app(bv.get_fid(), OP_BADD, num, args); +} +} // namespace + +namespace spacer { + +void convex_closure::reset(unsigned n_cols) { + m_kernel.reset(); + m_data.reset(n_cols); + m_dim_vars.reset(); + m_dim = n_cols; + m_dim_vars.reserve(m_dim); + m_new_vars.reset(); + m_bv_sz = 0; + m_enable_syntactic_cc = true; +} + +void convex_closure::collect_statistics(statistics &st) const { + st.update("time.spacer.solve.reach.gen.global.cc", + m_st.watch.get_seconds()); + st.update("SPACER cc num dim reduction success", m_st.m_num_reductions); + st.update("SPACER cc max reduced dim", m_st.m_max_dim); + m_kernel.collect_statistics(st); +} + +// call m_kernel to reduce dimensions of m_data +// return the rank of m_data +unsigned convex_closure::reduce_dim() { + if (m_dim <= 1) return m_dim; + + bool has_kernel = m_kernel.compute_kernel(); + if (!has_kernel) { + TRACE("cvx_dbg", + tout << "No linear dependencies between pattern vars\n";); + return m_dim; + } + + const spacer_matrix &ker = m_kernel.get_kernel(); + SASSERT(ker.num_rows() > 0); + SASSERT(ker.num_rows() <= m_dim); + SASSERT(ker.num_cols() == m_dim + 1); + // m_dim - ker.num_rows() is the number of variables that have no linear + // dependencies + return m_dim - ker.num_rows(); +} + +// For row \p row in m_kernel, construct the equality: +// +// row * m_dim_vars = 0 +// +// In the equality, exactly one variable from m_dim_vars is on the lhs +void convex_closure::generate_equality_for_row(const vector &row, + expr_ref &out) { + // contains the right hand side of an equality + expr_ref_buffer rhs(m); + // index of first non zero element in row + int pv = -1; + // are we constructing rhs or lhs + bool is_lhs = true; + // coefficient of m_dim_vars[pv] + rational coeff(1); + + // the elements in row are the coefficients of m_dim_vars + // some elements should go to the rhs, in which case the signs are + // changed + for (unsigned j = 0, sz = row.size(); j < sz; j++) { + rational val = row.get(j); + SASSERT(val.is_int()); + if (val.is_zero()) continue; + if (is_lhs) { + // Cannot re-write the last element + if (j == row.size() - 1) continue; + SASSERT(pv == -1); + pv = j; + is_lhs = false; + // In integer echelon form, the pivot need not be 1 + coeff = val; + } else { + expr_ref prod(m); + if (j != row.size() - 1) { + prod = m_dim_vars.get(j); + mul_by_rat(prod, -1 * val * m_lcm); + } else { + if (m_arith.is_int(m_dim_vars.get(pv))) { + prod = m_arith.mk_int(-1 * val); + } else if (m_arith.is_real(m_dim_vars.get(pv))) { + prod = m_arith.mk_real(-1 * val); + } else if (m_bv.is_bv(m_dim_vars.get(pv))) { + prod = m_bv.mk_numeral(-1 * val, m_bv_sz); + } + } + SASSERT(prod.get()); + rhs.push_back(prod); + } + } + + // make sure that there is a non-zero entry + SASSERT(pv != -1); + + if (rhs.size() == 0) { + expr_ref _rhs(m); + if (m_arith.is_int(m_dim_vars.get(pv))) + _rhs = m_arith.mk_int(rational::zero()); + else if (m_arith.is_real(m_dim_vars.get(pv))) + _rhs = m_arith.mk_real(rational::zero()); + else if (m_bv.is_bv(m_dim_vars.get(pv))) + _rhs = m_bv.mk_numeral(rational::zero(), m_bv_sz); + out = m_arith.mk_eq(m_dim_vars.get(pv), _rhs); + return; + } + + out = m_is_arith ? m_arith.mk_add(rhs.size(), rhs.data()) + : mk_bvadd(m, rhs.size(), rhs.data()); + expr_ref pv_var(m); + pv_var = m_dim_vars.get(pv); + mul_by_rat(pv_var, coeff * m_lcm); + + out = m.mk_eq(pv_var, out); + TRACE("cvx_dbg", tout << "rewrote " << mk_pp(m_dim_vars.get(pv), m) + << " into " << out << "\n";); +} + +/// Generates linear equalities implied by m_data +/// +/// the linear equalities are m_kernel * m_dim_vars = 0 (where * is matrix +/// multiplication) the new equalities are stored in m_dim_vars for each row [0, +/// 1, 0, 1 , 1] in m_kernel, the equality m_lcm*v1 = -1*m_lcm*v3 + -1*1 is +/// constructed and stored at index 1 of m_dim_vars +void convex_closure::generate_implied_equalities(expr_ref_vector &out) { + // assume kernel has been computed already + const spacer_matrix &kern = m_kernel.get_kernel(); + SASSERT(kern.num_rows() > 0); + + expr_ref eq(m); + for (unsigned i = kern.num_rows(); i > 0; i--) { + auto &row = kern.get_row(i - 1); + generate_equality_for_row(row, eq); + out.push_back(eq); + } +} + +/// Construct the equality ((m_new_vars . m_data[*][i]) = m_dim_vars[i]) +/// +/// Where . is the dot product, m_data[*][i] is +/// the ith column of m_data. Add the result to res_vec. +void convex_closure::add_sum_cnstr(unsigned i, expr_ref_vector &out) { + expr_ref_buffer sum(m); + expr_ref prod(m), v(m); + for (unsigned j = 0, sz = m_new_vars.size(); j < sz; j++) { + prod = m_new_vars.get(j); + mul_by_rat(prod, m_data.get(j, i)); + sum.push_back(prod); + } + v = m_arith.mk_to_real(m_dim_vars.get(i)); + mul_by_rat(v, m_lcm); + if (m_is_arith) + out.push_back(m.mk_eq(m_arith.mk_add(sum.size(), sum.data()), v)); + else + out.push_back(m.mk_eq(mk_bvadd(m, sum.size(), sum.data()), v)); +} + +void convex_closure::syntactic_convex_closure(expr_ref_vector &out) { + for (unsigned i = 0; i < m_data.num_rows(); i++) { + var *v = m.mk_var(i + dims(), m_arith.mk_real()); + m_new_vars.push_back(v); + } + + // forall j :: m_new_vars[j] >= 0 + for (auto v : m_new_vars) { + out.push_back(m_arith.mk_ge(v, m_arith.mk_real(rational::zero()))); + } + + for (unsigned i = 0, sz = m_dim_vars.size(); i < sz; i++) { + if (is_var(m_dim_vars.get(i))) add_sum_cnstr(i, out); + } + + //(\Sum j . m_new_vars[j]) = 1 + out.push_back(m.mk_eq( + m_arith.mk_add(m_new_vars.size(), + reinterpret_cast(m_new_vars.data())), + m_arith.mk_real(rational::one()))); +} + +#define MAX_DIV_BOUND 101 +// check whether \exists m, d s.t data[i] mod m = d. Returns the largest m and +// corresponding d +// TODO: find the largest divisor, not the smallest. +// TODO: improve efficiency +bool convex_closure::compute_div_constraint(const vector &data, + rational &m, rational &d) { + TRACE("cvx_dbg_verb", { + tout << "computing div constraints for "; + for (rational r : data) tout << r << " "; + tout << "\n"; + }); + SASSERT(data.size() > 1); + SASSERT(is_sorted(data)); + + m = rational(2); + // hard cut off to save time + rational bnd(MAX_DIV_BOUND); + rational big = data.back(); + for (; m < big && m < bnd; m++) { + if (is_congruent_mod(data, m)) break; + } + if (m >= big) return false; + if (m == bnd) return false; + + d = data[0] % m; + // work around for z3::rational::rem returning negative numbers. + d = (m + d) % m; + SASSERT(d >= rational::zero()); + + TRACE("cvx_dbg_verb", tout << "div constraint generated. cf " << m + << " and off " << d << "\n";); + return true; +} + +/// Compute the convex closure of points in m_data +/// +/// Returns true if the convex closure is syntactic +bool convex_closure::closure(expr_ref_vector &out) { + scoped_watch _w_(m_st.watch); + SASSERT(is_int_matrix(m_data)); + + unsigned red_dim = reduce_dim(); + + // store dim var before rewrite + expr_ref var(m_dim_vars.get(0), m); + if (red_dim < dims()) { + m_st.m_num_reductions++; + generate_implied_equalities(out); + TRACE("cvx_dbg", tout << "Linear equalities true of the matrix " + << mk_and(out) << "\n";); + } + + if (red_dim > m_st.m_max_dim) m_st.m_max_dim = red_dim; + + if (red_dim > 1) { + // there is no alternative to syntactic convex closure right now + // syntactic convex closure does not support BV + if (m_enable_syntactic_cc) { + SASSERT(m_new_vars.size() == 0); + TRACE("subsume", tout << "Computing syntactic convex closure\n";); + syntactic_convex_closure(out); + } else { + out.reset(); + return false; + } + return true; + } + + // zero dimensional convex closure + if (red_dim == 0) { return false; } + + SASSERT(red_dim == 1); + do_1dim_convex_closure(var, out); + return false; +} + +// construct the formula result_var <= bnd or result_var >= bnd +expr *convex_closure::mk_ineq(expr_ref result_var, rational bnd, bool is_le) { + if (m_is_arith) { + // The resulting expr is of sort Real if result_var is of sort Real. + // Otherwise, the resulting expr is of sort Int + if (is_le) return m_arith.mk_le(result_var, m_arith.mk_int(bnd)); + return m_arith.mk_ge(result_var, m_arith.mk_int(bnd)); + } + // TODO figure out whether we need signed versions or unsigned versions. + if (is_le) return m_bv.mk_ule(result_var, m_bv.mk_numeral(bnd, m_bv_sz)); + return m_bv.mk_ule(m_bv.mk_numeral(bnd, m_bv_sz), result_var); +} + +void convex_closure::do_1dim_convex_closure(const expr_ref &var, + expr_ref_vector &out) { + // The convex closure over one dimension is just a bound + vector data; + m_data.get_col(0, data); + auto gt_proc = [](rational const &x, rational const &y) -> bool { + return x > y; + }; + std::sort(data.begin(), data.end(), gt_proc); + + // -- compute LB <= var <= UB + expr_ref res(m); + res = var; + mul_by_rat(res, m_lcm); + // upper-bound + out.push_back(mk_ineq(res, data[0], true)); + // lower-bound + out.push_back(mk_ineq(res, data.back(), false)); + + // -- compute divisibility constraints + rational cr, off; + // add div constraints for all variables. + for (unsigned j = 0; j < m_data.num_cols(); j++) { + auto *v = m_dim_vars.get(j); + if (is_var(v) && (m_arith.is_int(v) || m_bv.is_bv(v))) { + data.reset(); + m_data.get_col(j, data); + std::sort(data.begin(), data.end(), gt_proc); + if (compute_div_constraint(data, cr, off)) { + res = v; + mul_by_rat(res, m_lcm); + if (m_is_arith) { + res = m.mk_eq(m_arith.mk_mod(res, m_arith.mk_int(cr)), + m_arith.mk_int(off)); + } else { + res = m.mk_eq( + m_bv.mk_bv_urem(res, m_bv.mk_numeral(cr, m_bv_sz)), + m_bv.mk_numeral(off, m_bv_sz)); + } + out.push_back(res); + } + } + } +} + +} // namespace spacer diff --git a/src/muz/spacer/spacer_convex_closure.h b/src/muz/spacer/spacer_convex_closure.h new file mode 100644 index 00000000000..b543f990df6 --- /dev/null +++ b/src/muz/spacer/spacer_convex_closure.h @@ -0,0 +1,198 @@ +#pragma once +/**++ +Copyright (c) 2020 Arie Gurfinkel + +Module Name: + + spacer_convex_closure.h + +Abstract: + + Compute convex closure of polyhedra + +Author: + + Hari Govind + Arie Gurfinkel + +Notes: + +--*/ + +#include "ast/arith_decl_plugin.h" +#include "ast/ast.h" +#include "ast/ast_util.h" +#include "muz/spacer/spacer_arith_kernel.h" +#include "muz/spacer/spacer_matrix.h" +#include "muz/spacer/spacer_util.h" +#include "util/statistics.h" + +namespace spacer { + +/// Computes a convex closure of a set of points +class convex_closure { + struct stats { + unsigned m_num_reductions; + unsigned m_max_dim; + stopwatch watch; + stats() { reset(); } + void reset() { + m_num_reductions = 0; + m_max_dim = 0; + watch.reset(); + } + }; + stats m_st; + + ast_manager &m; + arith_util m_arith; + bv_util m_bv; + + // size of all bit vectors in m_dim_vars + unsigned m_bv_sz; + + // Compute syntactic convex closure + bool m_enable_syntactic_cc; + + // true if \p m_dim_vars are arithmetic sort (i.e., Real or Int) + bool m_is_arith; + + // size of \p m_dim_vars + unsigned m_dim; + + // A vector of rational valued points + spacer_matrix m_data; + + // Variables naming dimensions in `m_data` + // \p m_dim_vars[i] is variable naming dimension \p i + var_ref_vector m_dim_vars; + + // Kernel of \p m_data + // Set at the end of computation + spacer_arith_kernel m_kernel; + + // Free variables introduced by syntactic convex closure + // These variables are always of sort Real + var_ref_vector m_new_vars; + + // m_lcm is a hack to allow convex_closure computation of rational matrices + // as well. Let A be a real matrix. m_lcm is the lcm of all denominators in + // A m_data = m_lcm * A, is always an integer matrix + // TODO: m_lcm should be maintained by the client + rational m_lcm; + + /// Reduces dimension of \p m_data and returns its rank + unsigned reduce_dim(); + + /// Constructs an equality corresponding to a given row in the kernel + /// + /// The equality is conceptually corresponds to + /// row * m_dim_vars = 0 + /// where row is a row vector and m_dim_vars is a column vector. + /// However, the equality is put in a form so that exactly one variable from + /// \p m_dim_vars is on the LHS + void generate_equality_for_row(const vector &row, expr_ref &out); + + /// Construct all linear equations implied by points in \p m_data + /// This is defined by \p m_kernel * m_dim_vars = 0 + void generate_implied_equalities(expr_ref_vector &out); + + /// Compute syntactic convex closure of \p m_data + void syntactic_convex_closure(expr_ref_vector &out); + + /// Construct the equality ((m_nw_vars . m_data[*][j]) = m_dim_vars[j]) + /// + /// \p m_data[*][j] is the jth column of m_data + /// The equality is added to \p out. + void add_sum_cnstr(unsigned j, expr_ref_vector &out); + + /// Compute one dimensional convex closure over \p var + /// + /// \p var is the dimension over which convex closure is computed + /// Result is stored in \p out + void do_1dim_convex_closure(const expr_ref &var, expr_ref_vector &out); + + /// Computes div constraint implied by a set of data points + /// + /// Finds the largest numbers \p m, \p d such that \p m_data[i] mod m = d + /// Returns true if successful + bool compute_div_constraint(const vector &data, rational &m, + rational &d); + + /// Constructs a formula \p var ~ bnd, where ~ = is_le ? <= : >= + expr *mk_ineq(expr_ref var, rational bnd, bool is_le); + + public: + convex_closure(ast_manager &manager, bool use_sage) + : m(manager), m_arith(m), m_bv(m), m_bv_sz(0), m_enable_syntactic_cc(true), + m_is_arith(true), m_dim(0), m_data(0, 0), m_dim_vars(m), + m_kernel(m_data), m_new_vars(m) { + + if (use_sage) m_kernel.set_plugin(mk_sage_plugin()); + } + + /// Resets all data points + /// + /// n_cols is the number of dimensions of new expected data points + void reset(unsigned n_cols); + + /// Turn support for fixed sized bit-vectors of size \p sz + /// + /// Disables syntactic convex closure as a side-effect + void set_bv(unsigned sz) { + SASSERT(sz > 0); + m_is_arith = false; + m_bv_sz = sz; + m_enable_syntactic_cc = false; + } + + /// \brief Name dimension \p i with a variable \p v. + void set_dimension(unsigned i, var *v) { + SASSERT(i < dims()); + SASSERT(m_dim_vars[i] == nullptr); + m_dim_vars[i] = v; + } + + /// \brief Return number of dimensions of each point + unsigned dims() const { return m_dim; } + + /// \brief Return variables introduced by the syntactic convex closure + const var_ref_vector &get_new_vars() const { return m_new_vars; } + + /// \brief Add a one-dimensional point to convex closure + void push_back(rational x) { + SASSERT(dims() == 1); + vector row; + row.reserve(1, x); + m_data.add_row(row); + } + + /// \brief Add a two-dimensional point to convex closure + void push_back(rational x, rational y) { + SASSERT(dims() == 2); + vector row; + row.reserve(2); + row[0] = x; + row[1] = y; + m_data.add_row(row); + } + + /// \brief Add an n-dimensional point to convex closure + void push_back(const vector &point) { + SASSERT(point.size() == dims()); + m_data.add_row(point); + }; + + /// \brief Compute convex closure of the current set of points + /// + /// Returns true if successful and \p out is an exact convex closure + /// Returns false if \p out is an over-approximation + bool closure(expr_ref_vector &out); + + void collect_statistics(statistics &st) const; + void reset_statistics() { m_st.reset(); } + + /// Set the least common multiple of \p m_data + void set_lcm(rational l) { m_lcm = l; } +}; +} // namespace spacer diff --git a/src/muz/spacer/spacer_matrix.cpp b/src/muz/spacer/spacer_matrix.cpp index ea9b3cb320e..02d411950c6 100644 --- a/src/muz/spacer/spacer_matrix.cpp +++ b/src/muz/spacer/spacer_matrix.cpp @@ -17,143 +17,169 @@ Revision History: --*/ #include "muz/spacer/spacer_matrix.h" -namespace spacer -{ - spacer_matrix::spacer_matrix(unsigned m, unsigned n) : m_num_rows(m), m_num_cols(n) - { - for (unsigned i=0; i < m; ++i) - { - vector v; - for (unsigned j=0; j < n; ++j) - { - v.push_back(rational(0)); - } - m_matrix.push_back(v); - } +namespace spacer { +spacer_matrix::spacer_matrix(unsigned m, unsigned n) + : m_num_rows(m), m_num_cols(n) { + m_matrix.reserve(m_num_rows); + for (unsigned i = 0; i < m_num_rows; ++i) { + m_matrix[i].reserve(m_num_cols, rational(0)); } +} - unsigned spacer_matrix::num_rows() - { - return m_num_rows; - } - - unsigned spacer_matrix::num_cols() - { - return m_num_cols; - } - - const rational& spacer_matrix::get(unsigned int i, unsigned int j) - { - SASSERT(i < m_num_rows); - SASSERT(j < m_num_cols); - - return m_matrix[i][j]; - } +void spacer_matrix::get_col(unsigned i, vector &row) const { + SASSERT(i < m_num_cols); + row.reset(); + row.reserve(m_num_rows); + unsigned j = 0; + for (auto &v : m_matrix) { row[j++] = (v.get(i)); } + SASSERT(row.size() == m_num_rows); +} - void spacer_matrix::set(unsigned int i, unsigned int j, const rational& v) - { - SASSERT(i < m_num_rows); - SASSERT(j < m_num_cols); +void spacer_matrix::add_row(const vector &row) { + SASSERT(row.size() == m_num_cols); + m_matrix.push_back(row); + m_num_rows = m_matrix.size(); +} - m_matrix[i][j] = v; - } +unsigned spacer_matrix::perform_gaussian_elimination() { + unsigned i = 0; + unsigned j = 0; + while (i < m_matrix.size() && j < m_matrix[0].size()) { + // find maximal element in column with row index bigger or equal i + rational max = m_matrix[i][j]; + unsigned max_index = i; + + for (unsigned k = i + 1; k < m_matrix.size(); ++k) { + if (max < m_matrix[k][j]) { + max = m_matrix[k][j]; + max_index = k; + } + } - unsigned spacer_matrix::perform_gaussian_elimination() - { - unsigned i=0; - unsigned j=0; - while(i < m_matrix.size() && j < m_matrix[0].size()) + if (max.is_zero()) // skip this column { - // find maximal element in column with row index bigger or equal i - rational max = m_matrix[i][j]; - unsigned max_index = i; - - for (unsigned k=i+1; k < m_matrix.size(); ++k) - { - if (max < m_matrix[k][j]) - { - max = m_matrix[k][j]; - max_index = k; + ++j; + } else { + // reorder rows if necessary + vector tmp = m_matrix[i]; + m_matrix[i] = m_matrix[max_index]; + m_matrix[max_index] = m_matrix[i]; + + // normalize row + rational pivot = m_matrix[i][j]; + if (!pivot.is_one()) { + for (unsigned k = 0; k < m_matrix[i].size(); ++k) { + m_matrix[i][k] = m_matrix[i][k] / pivot; } } - if (max.is_zero()) // skip this column - { - ++j; - } - else - { - // reorder rows if necessary - vector tmp = m_matrix[i]; - m_matrix[i] = m_matrix[max_index]; - m_matrix[max_index] = m_matrix[i]; - - // normalize row - rational pivot = m_matrix[i][j]; - if (!pivot.is_one()) - { - for (unsigned k=0; k < m_matrix[i].size(); ++k) - { - m_matrix[i][k] = m_matrix[i][k] / pivot; - } - } - - // subtract row from all other rows - for (unsigned k=1; k < m_matrix.size(); ++k) - { - if (k != i) - { - rational factor = m_matrix[k][j]; - for (unsigned l=0; l < m_matrix[k].size(); ++l) - { - m_matrix[k][l] = m_matrix[k][l] - (factor * m_matrix[i][l]); - } + // subtract row from all other rows + for (unsigned k = 1; k < m_matrix.size(); ++k) { + if (k != i) { + rational factor = m_matrix[k][j]; + for (unsigned l = 0; l < m_matrix[k].size(); ++l) { + m_matrix[k][l] = + m_matrix[k][l] - (factor * m_matrix[i][l]); } } - - ++i; - ++j; } - } - if (get_verbosity_level() >= 1) - { - SASSERT(m_matrix.size() > 0); + ++i; + ++j; } + } + + if (get_verbosity_level() >= 1) { SASSERT(m_matrix.size() > 0); } + + return i; // i points to the row after the last row which is non-zero +} - return i; //i points to the row after the last row which is non-zero +std::ostream &spacer_matrix::display(std::ostream &out) const { + out << "Matrix\n"; + for (const auto &row : m_matrix) { + for (const auto &element : row) { out << element << ", "; } + out << "\n"; } + out << "\n"; + return out; +} - void spacer_matrix::print_matrix() - { - verbose_stream() << "\nMatrix\n"; - for (const auto& row : m_matrix) - { - for (const auto& element : row) - { - verbose_stream() << element << ", "; - } - verbose_stream() << "\n"; +void spacer_matrix::normalize() { + rational den = rational::one(); + for (unsigned i = 0; i < m_num_rows; ++i) { + for (unsigned j = 0; j < m_num_cols; ++j) { + den = lcm(den, denominator(m_matrix[i][j])); } - verbose_stream() << "\n"; } - void spacer_matrix::normalize() - { - rational den = rational::one(); - for (unsigned i=0; i < m_num_rows; ++i) - { - for (unsigned j=0; j < m_num_cols; ++j) - { - den = lcm(den, denominator(m_matrix[i][j])); - } + + for (unsigned i = 0; i < m_num_rows; ++i) { + for (unsigned j = 0; j < m_num_cols; ++j) { + m_matrix[i][j] = den * m_matrix[i][j]; + SASSERT(m_matrix[i][j].is_int()); } - for (unsigned i=0; i < m_num_rows; ++i) - { - for (unsigned j=0; j < m_num_cols; ++j) - { - m_matrix[i][j] = den * m_matrix[i][j]; - SASSERT(m_matrix[i][j].is_int()); + } +} + +// attempt to guess that all rows of the matrix are linearly dependent +bool spacer_matrix::is_lin_reltd(unsigned i, unsigned j, rational &coeff1, + rational &coeff2, rational &off) const { + SASSERT(m_num_rows > 1); + coeff1 = m_matrix[0][j] - m_matrix[1][j]; + coeff2 = m_matrix[1][i] - m_matrix[0][i]; + off = (m_matrix[0][i] * m_matrix[1][j]) - (m_matrix[1][i] * m_matrix[0][j]); + + for (unsigned k = 0; k < m_num_rows; k++) { + if (((coeff1 * m_matrix[k][i]) + (coeff2 * m_matrix[k][j]) + off) != + rational::zero()) { + TRACE("cvx_dbg_verb", + tout << "Didn't work for " << m_matrix[k][i] << " and " + << m_matrix[k][j] << " with coefficients " << coeff1 + << " , " << coeff2 << " and offset " << off << "\n";); + return false; + } + } + + rational div = gcd(coeff1, gcd(coeff2, off)); + if (div == 0) return false; + coeff1 = coeff1 / div; + coeff2 = coeff2 / div; + off = off / div; + return true; +} + +bool spacer_matrix::compute_linear_deps(spacer_matrix &eq) const { + SASSERT(m_num_rows > 1); + + eq.reset(m_num_cols + 1); + + rational coeff1, coeff2, off; + vector lin_dep; + lin_dep.reserve(m_num_cols + 1); + + for (unsigned i = 0; i < m_num_cols; i++) { + for (unsigned j = i + 1; j < m_num_cols; j++) { + if (is_lin_reltd(i, j, coeff1, coeff2, off)) { + SASSERT(!(coeff1 == 0 && coeff2 == 0 && off == 0)); + lin_dep[i] = coeff1; + lin_dep[j] = coeff2; + lin_dep[m_num_cols] = off; + eq.add_row(lin_dep); + + TRACE("cvx_dbg_verb", { + tout << "Adding row "; + for (rational r : lin_dep) tout << r << " "; + tout << "\n"; + }); + // reset everything + lin_dep[i] = rational::zero(); + lin_dep[j] = rational::zero(); + lin_dep[m_num_cols] = 0; + // Found a dependency for this row, move on. + // sound because of transitivity of is_lin_reltd + break; } } } + return eq.num_rows() > 0; } +} // namespace spacer diff --git a/src/muz/spacer/spacer_matrix.h b/src/muz/spacer/spacer_matrix.h index 334433ad7b7..d69b4946674 100644 --- a/src/muz/spacer/spacer_matrix.h +++ b/src/muz/spacer/spacer_matrix.h @@ -22,24 +22,44 @@ Revision History: namespace spacer { - class spacer_matrix { - public: - spacer_matrix(unsigned m, unsigned n); // m rows, n columns +class spacer_matrix { + private: + unsigned m_num_rows; + unsigned m_num_cols; + vector> m_matrix; - unsigned num_rows(); - unsigned num_cols(); + bool is_lin_reltd(unsigned i, unsigned j, rational &coeff1, + rational &coeff2, rational &off) const; - const rational& get(unsigned i, unsigned j); - void set(unsigned i, unsigned j, const rational& v); + public: + spacer_matrix(unsigned m, unsigned n); // m rows, n columns - unsigned perform_gaussian_elimination(); + unsigned num_rows() const { return m_num_rows; } + unsigned num_cols() const { return m_num_cols; } - void print_matrix(); - void normalize(); - private: - unsigned m_num_rows; - unsigned m_num_cols; - vector> m_matrix; - }; -} + const rational &get(unsigned i, unsigned j) const { return m_matrix[i][j]; } + void set(unsigned i, unsigned j, const rational &v) { m_matrix[i][j] = v; } + const vector &get_row(unsigned i) const { + SASSERT(i < num_rows()); + return m_matrix.get(i); + } + + /// Returns a copy of row \p i + void get_col(unsigned i, vector &row) const; + + void add_row(const vector &row); + + void reset(unsigned n_cols) { + m_num_rows = 0; + m_num_cols = n_cols; + m_matrix.reset(); + } + + std::ostream &display(std::ostream &out) const; + void normalize(); + unsigned perform_gaussian_elimination(); + + bool compute_linear_deps(spacer_matrix &eq) const; +}; +} // namespace spacer diff --git a/src/muz/spacer/spacer_unsat_core_plugin.cpp b/src/muz/spacer/spacer_unsat_core_plugin.cpp index e0c5a39f5d2..5523326f481 100644 --- a/src/muz/spacer/spacer_unsat_core_plugin.cpp +++ b/src/muz/spacer/spacer_unsat_core_plugin.cpp @@ -396,8 +396,8 @@ namespace spacer { matrix.set(i, map[pair.second], pair.first); } } - matrix.print_matrix(); + IF_VERBOSE(10, matrix.display(verbose_stream());); // 3. normalize matrix to integer values matrix.normalize(); From 61bdaea3b54a4cbd932e4412be1da9d10f817794 Mon Sep 17 00:00:00 2001 From: hgvk94 Date: Tue, 16 Feb 2021 10:50:57 -0500 Subject: [PATCH 10/78] Add spacer_concretize --- src/muz/spacer/CMakeLists.txt | 1 + src/muz/spacer/spacer_concretize.cpp | 208 +++++++++++++++++++++++++++ src/muz/spacer/spacer_concretize.h | 77 ++++++++++ 3 files changed, 286 insertions(+) create mode 100644 src/muz/spacer/spacer_concretize.cpp create mode 100644 src/muz/spacer/spacer_concretize.h diff --git a/src/muz/spacer/CMakeLists.txt b/src/muz/spacer/CMakeLists.txt index a6d1eefb890..b844d7f0106 100644 --- a/src/muz/spacer/CMakeLists.txt +++ b/src/muz/spacer/CMakeLists.txt @@ -30,6 +30,7 @@ z3_add_component(spacer spacer_mbc.cpp spacer_pdr.cpp spacer_sat_answer.cpp + spacer_concretize.cpp spacer_convex_closure.cpp spacer_arith_kernel.cpp spacer_arith_kernel_sage.cpp diff --git a/src/muz/spacer/spacer_concretize.cpp b/src/muz/spacer/spacer_concretize.cpp new file mode 100644 index 00000000000..809e9a97160 --- /dev/null +++ b/src/muz/spacer/spacer_concretize.cpp @@ -0,0 +1,208 @@ +/*++ +Copyright (c) 2020 Arie Gurfinkel + +Module Name: + + spacer_concretize.cpp + +Abstract: + + Concretize a pob + +Author: + + Hari Govind V K + Arie Gurfinkel + + +--*/ +#include "spacer_concretize.h" + +namespace pattern_var_marker_ns { +struct proc { + arith_util &m_arith; + expr_fast_mark2 &m_marks; + proc(arith_util &arith, expr_fast_mark2 &marks) + : m_arith(arith), m_marks(marks) {} + void operator()(var *n) const {} + void operator()(quantifier *q) const {} + void operator()(app const *n) const { + expr *e1, *e2; + if (m_arith.is_mul(n, e1, e2)) { + if (is_var(e1) && !is_var(e2)) + m_marks.mark(e2); + else if (is_var(e2) && !is_var(e1)) + m_marks.mark(e1); + } + } +}; +}; // namespace pattern_var_marker_ns +namespace spacer { +void pob_concretizer::mark_pattern_vars() { + pattern_var_marker_ns::proc proc(m_arith, m_var_marks); + quick_for_each_expr(proc, const_cast(m_pattern)); +} + +bool pob_concretizer::push_out(expr_ref_vector &out, const expr_ref &e) { + // using m_var_marks to mark both variables and expressions sent to out + // the two sets are distinct so we can reuse the same marks + if (!m_var_marks.is_marked(e)) { + m_var_marks.mark(e); + out.push_back(e); + return true; + } + return false; +} + +bool pob_concretizer::apply(const expr_ref_vector &cube, expr_ref_vector &out) { + // mark variables that are being split out + mark_pattern_vars(); + + for (auto *lit : cube) { + if (!apply_lit(lit, out)) { + out.reset(); + m_var_marks.reset(); + return false; + } + } + + m_var_marks.reset(); + return true; +} + +bool pob_concretizer::is_split_var(expr *e, expr *&var, bool &pos) { + expr *e1, *e2; + rational n; + + if (m_var_marks.is_marked(e)) { + var = e; + pos = true; + return true; + } else if (m_arith.is_mul(e, e1, e2) && m_arith.is_numeral(e1, n) && + m_var_marks.is_marked(e2)) { + var = e2; + pos = !n.is_neg(); + return true; + } + + return false; +} + +void pob_concretizer::split_lit_le_lt(expr *_lit, expr_ref_vector &out) { + expr *e1, *e2; + + expr *lit = _lit; + m.is_not(_lit, lit); + VERIFY(m_arith.is_le(lit, e1, e2) || m_arith.is_gt(lit, e1, e2) || + m_arith.is_lt(lit, e1, e2) || m_arith.is_ge(lit, e1, e2)); + + ptr_buffer kids; + expr *var; + bool pos; + expr_ref val(m); + for (auto *arg : *to_app(e1)) { + if (is_split_var(arg, var, pos)) { + val = m_model->operator()(var); + + // reuse val to keep the new literal + val = pos ? m_arith.mk_le(var, val) : m_arith.mk_ge(var, val); + push_out(out, val); + } else { + kids.push_back(arg); + } + } + + if (kids.empty()) return; + + // -- nothing was changed in the literal, move it out as is + if (kids.size() == to_app(e1)->get_num_args()) { + push_out(out, {_lit, m}); + return; + } + + // create new leftover literal using remaining arguments + expr_ref lhs(m); + if (kids.size() == 1) { + lhs = kids.get(0); + } else + lhs = m_arith.mk_add(kids.size(), kids.data()); + + expr_ref rhs = m_model->operator()(lhs); + expr_ref new_lit(m_arith.mk_le(lhs, rhs), m); + push_out(out, new_lit); +} + +void pob_concretizer::split_lit_ge_gt(expr *_lit, expr_ref_vector &out) { + expr *e1, *e2; + + expr *lit = _lit; + m.is_not(_lit, lit); + VERIFY(m_arith.is_le(lit, e1, e2) || m_arith.is_gt(lit, e1, e2) || + m_arith.is_lt(lit, e1, e2) || m_arith.is_ge(lit, e1, e2)); + + ptr_buffer kids; + expr *var; + bool pos; + expr_ref val(m); + for (auto *arg : *to_app(e1)) { + if (is_split_var(arg, var, pos)) { + val = m_model->operator()(var); + + // reuse val to keep the new literal + val = pos ? m_arith.mk_ge(var, val) : m_arith.mk_le(var, val); + push_out(out, val); + } else { + kids.push_back(arg); + } + } + + if (kids.empty()) return; + + // -- nothing was changed in the literal, move it out as is + if (kids.size() == to_app(e1)->get_num_args()) { + push_out(out, {_lit, m}); + return; + } + + // create new leftover literal using remaining arguments + expr_ref lhs(m); + if (kids.size() == 1) { + lhs = kids.get(0); + } else + lhs = m_arith.mk_add(kids.size(), kids.data()); + + expr_ref rhs = m_model->operator()(lhs); + expr_ref new_lit(m_arith.mk_ge(lhs, rhs), m); + push_out(out, new_lit); +} + +bool pob_concretizer::apply_lit(expr *_lit, expr_ref_vector &out) { + expr *lit = _lit; + bool is_neg = m.is_not(_lit, lit); + + // split literals of the form a1*x1 + ... + an*xn ~ c, where c is a + // constant, ~ is <, <=, >, or >=, and the top level operator of LHS is + + expr *e1, *e2; + if ((m_arith.is_lt(lit, e1, e2) || m_arith.is_le(lit, e1, e2)) && + m_arith.is_add(e1)) { + SASSERT(m_arith.is_numeral(e2)); + if (!is_neg) { + split_lit_le_lt(_lit, out); + } else { + split_lit_ge_gt(_lit, out); + } + } else if ((m_arith.is_gt(lit, e1, e2) || m_arith.is_ge(lit, e1, e2)) && + m_arith.is_add(e1)) { + SASSERT(m_arith.is_numeral(e2)); + if (!is_neg) { + split_lit_ge_gt(_lit, out); + } else { + split_lit_le_lt(_lit, out); + } + } else { + out.push_back(_lit); + } + return true; +} +} // namespace spacer + diff --git a/src/muz/spacer/spacer_concretize.h b/src/muz/spacer/spacer_concretize.h new file mode 100644 index 00000000000..6cefb58db8e --- /dev/null +++ b/src/muz/spacer/spacer_concretize.h @@ -0,0 +1,77 @@ +#pragma once +/*++ +Copyright (c) 2020 Arie Gurfinkel + +Module Name: + + spacer_concretize.h + +Abstract: + + Concretize a pob + +Author: + + Hari Govind V K + Arie Gurfinkel + + +--*/ + +#include "ast/ast.h" +#include "ast/rewriter/expr_safe_replace.h" +#include "muz/spacer/spacer_context.h" +#include "muz/spacer/spacer_util.h" +#include "tactic/core/ctx_simplify_tactic.h" +#include "util/obj_ref_hashtable.h" +#include "util/rational.h" + +namespace spacer { + +class pob_concretizer { + ast_manager &m; + arith_util m_arith; + + model_ref &m_model; + + const expr *m_pattern; + + expr_fast_mark2 m_var_marks; + + // Marks all constants to be split in the pattern + void mark_pattern_vars(); + + // Adds a literal to \p out (unless it is already there) + bool push_out(expr_ref_vector &out, const expr_ref &e); + // Determines whether \p e is a*var, for some variable in \p m_var_marks + // Sets \p pos to sign(a) + bool is_split_var(expr *e, expr *&var, bool &pos); + // Splits a < or <= literal using the model + void split_lit_le_lt(expr *lit, expr_ref_vector &out); + // See split_lit_le_lt + void split_lit_ge_gt(expr *lit, expr_ref_vector &out); + + public: + pob_concretizer(ast_manager &_m, model_ref &model, const expr *pattern) + : m(_m), m_arith(m), m_model(model), m_pattern(pattern) {} + + /// Concretize \p cube into conjunction of simpler literals + /// + /// Returns true on success and adds new literals to out + /// ensures: mk_and(out) ==> cube + bool apply(expr *cube, expr_ref_vector &out) { + expr_ref_vector flat(m); + flatten_and(cube, flat); + return apply(flat, out); + } + + /// Concretizes a vector of literals + bool apply(const expr_ref_vector &cube, expr_ref_vector &out); + + /// Concretizes a single literal + /// + /// Returns true on success, new literals are added to \p out + bool apply_lit(expr *lit, expr_ref_vector &out); +}; + +} // namespace spacer From 45532fae05c8aec6600f454e075901174c297bfa Mon Sep 17 00:00:00 2001 From: hgvk94 Date: Tue, 16 Feb 2021 08:41:35 -0500 Subject: [PATCH 11/78] Utility methods for spacer conjecture rule --- src/muz/spacer/CMakeLists.txt | 1 + src/muz/spacer/spacer_conjecture.cpp | 92 ++++++++++++++++++++++++++++ src/muz/spacer/spacer_util.h | 4 +- 3 files changed, 95 insertions(+), 2 deletions(-) create mode 100644 src/muz/spacer/spacer_conjecture.cpp diff --git a/src/muz/spacer/CMakeLists.txt b/src/muz/spacer/CMakeLists.txt index b844d7f0106..11259b2956a 100644 --- a/src/muz/spacer/CMakeLists.txt +++ b/src/muz/spacer/CMakeLists.txt @@ -32,6 +32,7 @@ z3_add_component(spacer spacer_sat_answer.cpp spacer_concretize.cpp spacer_convex_closure.cpp + spacer_conjecture.cpp spacer_arith_kernel.cpp spacer_arith_kernel_sage.cpp COMPONENT_DEPENDENCIES diff --git a/src/muz/spacer/spacer_conjecture.cpp b/src/muz/spacer/spacer_conjecture.cpp new file mode 100644 index 00000000000..e3925c69aff --- /dev/null +++ b/src/muz/spacer/spacer_conjecture.cpp @@ -0,0 +1,92 @@ +/** + Copyright (c) 2019 Microsoft Corporation and Arie Gurfinkel + + Module Name: + + spacer_conjecture.cpp + + Abstract: + + Methods to implement conjecture rule in gspacer + + Author: + + Arie Gurfinkel + Hari Govind + + + Notes: + +--*/ + +#include "ast/for_each_expr.h" + +#include "muz/spacer/spacer_context.h" +#include "muz/spacer/spacer_util.h" + +namespace spacer { +/// Returns true if \p lit is LA/BV inequality with a single free variable +bool is_mono_var_lit(expr *lit, ast_manager &m) { + expr *e; + bv_util bv(m); + arith_util a_util(m); + if (m.is_not(lit, e)) return is_mono_var_lit(e, m); + if (a_util.is_arith_expr((lit)) || bv.is_bv_ule(lit) || + bv.is_bv_sle(lit)) { + return get_num_vars(lit) == 1 && !has_nonlinear_var_mul(lit, m); + } + return false; +} + +/// Returns true if \p pattern contains a single mono-var literal +/// +/// That is, \p pattern contains a single suitable literal. +/// The literal is returned in \p res +bool find_unique_mono_var_lit(const expr_ref &pattern, expr_ref &res) { + if (get_num_vars(pattern) != 1) return false; + ast_manager &m = res.m(); + + // if the pattern has multiple literals, check whether exactly one of them + // is leq + expr_ref_vector conj(m); + conj.push_back(pattern); + flatten_and(conj); + unsigned count = 0; + for (auto *lit : conj) { + if (is_mono_var_lit(lit, m)) { + if (count) return false; + res = lit; + count++; + } + } + SASSERT(count <= 1); + return count == 1; +} + +/// Filter out a given literal \p lit from a list of literals +/// +/// Returns true if at least one literal was filtered out +/// \p out contains all the remaining (not filtered) literals +/// \p out holds the result. Returns true if any literal has been dropped +bool filter_out_lit(const expr_ref_vector &vec, const expr_ref &lit, expr_ref_vector &out) { + ast_manager &m = vec.get_manager(); + bool dirty = false, pos = false; + sem_matcher m_matcher(m); + substitution sub(m); + + out.reset(); + sub.reserve(1, get_num_vars(lit.get())); + SASSERT(!(m.is_not(lit) && m.is_eq(to_app(lit)->get_arg(0)))); + for (auto &c : vec) { + m_matcher.reset(); + if (m_matcher(lit, c, sub, pos) && pos) { + if (is_numeric_sub(sub)) { + dirty = true; + continue; + } + } + out.push_back(c); + } + return dirty; +} +} // namespace spacer diff --git a/src/muz/spacer/spacer_util.h b/src/muz/spacer/spacer_util.h index 4f07265fd31..8d8e1c3340e 100644 --- a/src/muz/spacer/spacer_util.h +++ b/src/muz/spacer/spacer_util.h @@ -155,12 +155,12 @@ bool is_mono_var(expr *lit, ast_manager &m, arith_util &a_util); // a mono_var_pattern has only one variable in the whole expression and is // linear. lit is the literal with the variable -bool should_conjecture(const expr_ref &p, expr_ref &lit); +bool find_unique_mono_var_lit(const expr_ref &p, expr_ref &lit); /// Drop all literals that numerically match \p lit, from \p fml_vec. /// /// \p abs_fml holds the result. Returns true if any literal has been dropped -bool drop_lit(expr_ref_vector &in, expr_ref &lit, expr_ref_vector &out); +bool filter_out_lit(const expr_ref_vector &in, const expr_ref &lit, expr_ref_vector &out); /// Returns true if range of s is numeric bool is_numeric_sub(const substitution &s); From a601cc10713ea11d00eca1648e2247b7cce475ba Mon Sep 17 00:00:00 2001 From: Arie Gurfinkel Date: Thu, 28 Apr 2022 14:32:36 -0400 Subject: [PATCH 12/78] Add spacer_expand_bnd_generalizer Generalizes arithmetic inequality literals of the form x <= c, by changing constant c to other constants found in the problem. --- src/muz/spacer/CMakeLists.txt | 1 + .../spacer/spacer_expand_bnd_generalizer.cpp | 229 ++++++++++++++++++ .../spacer/spacer_expand_bnd_generalizer.h | 68 ++++++ src/muz/spacer/spacer_generalizers.h | 1 + 4 files changed, 299 insertions(+) create mode 100644 src/muz/spacer/spacer_expand_bnd_generalizer.cpp create mode 100644 src/muz/spacer/spacer_expand_bnd_generalizer.h diff --git a/src/muz/spacer/CMakeLists.txt b/src/muz/spacer/CMakeLists.txt index 11259b2956a..255ca0c1f48 100644 --- a/src/muz/spacer/CMakeLists.txt +++ b/src/muz/spacer/CMakeLists.txt @@ -23,6 +23,7 @@ z3_add_component(spacer spacer_sem_matcher.cpp spacer_quant_generalizer.cpp spacer_arith_generalizers.cpp + spacer_expand_bnd_generalizer.cpp spacer_cluster.cpp spacer_callback.cpp spacer_json.cpp diff --git a/src/muz/spacer/spacer_expand_bnd_generalizer.cpp b/src/muz/spacer/spacer_expand_bnd_generalizer.cpp new file mode 100644 index 00000000000..6639bc843fc --- /dev/null +++ b/src/muz/spacer/spacer_expand_bnd_generalizer.cpp @@ -0,0 +1,229 @@ +/*++ + Copyright (c) 2020 Arie Gurfinkel + + Module Name: + + spacer_expand_bnd_generalizer.cpp + + Abstract: + + Strengthen lemmas by changing numeral constants inside arithmetic literals + + Author: + + Hari Govind V K + Arie Gurfinkel + +--*/ +#include "muz/spacer/spacer_expand_bnd_generalizer.h" +#include "ast/for_each_expr.h" +#include "ast/rewriter/expr_safe_replace.h" + +namespace { +/// Returns true if \p e is arithmetic comparison +/// +/// Returns true if \p e is of the form \p lhs op rhs, where +/// op in {<=, <, >, >=}, and rhs is a numeric value +bool is_arith_comp(const expr *e, expr *&lhs, rational &rhs, bool &is_int, + ast_manager &m) { + arith_util arith(m); + expr *e1, *e2; + if (m.is_not(e, e1)) return is_arith_comp(e1, lhs, rhs, is_int, m); + if (arith.is_le(e, lhs, e2) || arith.is_lt(e, lhs, e2) || + arith.is_ge(e, lhs, e2) || arith.is_gt(e, lhs, e2)) + return arith.is_numeral(e2, rhs, is_int); + return false; +} + +bool is_arith_comp(const expr *e, expr *&lhs, rational &rhs, ast_manager &m) { + bool is_int; + return is_arith_comp(e, lhs, rhs, is_int, m); +} +bool is_arith_comp(const expr *e, rational &rhs, ast_manager &m) { + expr *lhs; + return is_arith_comp(e, lhs, rhs, m); +} +/// If \p lit is of the form (x op v), replace v with num +/// +/// Supports arithmetic literals where op is <, <=, >, >=, or negation +bool update_bound(const expr *lit, rational num, expr_ref &res, + bool negate = false) { + SASSERT(is_app(lit)); + ast_manager &m = res.get_manager(); + expr *e1; + if (m.is_not(lit, e1)) { return update_bound(e1, num, res, !negate); } + + arith_util arith(m); + expr *lhs; + rational val; + bool is_int; + if (!is_arith_comp(lit, lhs, val, is_int, m)) return false; + + res = m.mk_app(to_app(lit)->get_decl(), lhs, arith.mk_numeral(num, is_int)); + if (negate) { m.mk_not(res); } + return true; +} + +} // namespace +namespace spacer { + +namespace collect_rationals_ns { + +/// Finds rationals in an expression +struct proc { + ast_manager &m; + arith_util m_arith; + + vector &m_res; + proc(ast_manager &a_m, vector &res) + : m(a_m), m_arith(m), m_res(res) {} + void operator()(expr *n) const {} + void operator()(app *n) { + rational val; + if (m_arith.is_numeral(n, val)) m_res.push_back(val); + } +}; +} // namespace collect_rationals_ns + +/// Extract all numerals from an expression +void collect_rationals(expr *e, vector &res, ast_manager &m) { + collect_rationals_ns::proc proc(m, res); + quick_for_each_expr(proc, e); +} + +lemma_expand_bnd_generalizer::lemma_expand_bnd_generalizer(context &ctx) + : lemma_generalizer(ctx), m(ctx.get_ast_manager()), m_arith(m) { + // -- collect rationals from initial condition and transition relation + for (auto &kv : ctx.get_pred_transformers()) { + collect_rationals(kv.m_value->init(), m_values, m); + collect_rationals(kv.m_value->transition(), m_values, m); + } + + // remove duplicates + std::sort(m_values.begin(), m_values.end()); + auto last = std::unique(m_values.begin(), m_values.end()); + for (unsigned i = 0, sz = std::distance(last, m_values.end()); i < sz; ++i) + m_values.pop_back(); +} + +void lemma_expand_bnd_generalizer::operator()(lemma_ref &lemma) { + scoped_watch _w_(m_st.watch); + if (!lemma->get_pob()->expand_bnd()) return; + + expr_ref_vector cube(lemma->get_cube()); + + // -- temporary stores a core + expr_ref_vector core(m); + + expr_ref lit(m), new_lit(m); + rational bnd; + // for every literal + for (unsigned i = 0, sz = cube.size(); i < sz; i++) { + lit = cube.get(i); + if (m.is_true(lit)) continue; + if (!is_arith_comp(lit, bnd, m)) continue; + + TRACE("expand_bnd", tout << "Attempting to expand " << lit << " inside " + << cube << "\n";); + + // for every value + for (rational n : m_values) { + if (!is_interesting(lit, bnd, n)) continue; + m_st.atmpts++; + TRACE("expand_bnd", tout << "Attempting to expand " << lit + << " with numeral " << n << "\n";); + + // -- update bound on lit + VERIFY(update_bound(lit, n, new_lit)); + // -- update lit to new_lit for a new candidate lemma + cube[i] = new_lit; + + core.reset(); + core.append(cube); + // -- check that candidate is inductive + if (check_inductive(lemma, core)) { + expr_fast_mark1 in_core; + for (auto *e : core) in_core.mark(e); + for (unsigned i = 0, sz = cube.size(); i < sz; ++i) { + if (!in_core.is_marked(cube.get(i))) cube[i] = m.mk_true(); + } + // move to next literal if the current has been removed + if (!in_core.is_marked(new_lit)) break; + } else { + // -- candidate not inductive, restore original lit + cube[i] = lit; + } + } + } + + // Currently, we allow for only one round of expand bound per lemma + // Mark lemma as already expanded so that it is not generalized in this way + // again + lemma->get_pob()->stop_expand_bnd(); +} + +/// Check whether \p candidate is a possible generalization for \p lemma. +/// Side-effect: update \p lemma with the new candidate +bool lemma_expand_bnd_generalizer::check_inductive(lemma_ref &lemma, + expr_ref_vector &candidate) { + TRACE("expand_bnd_verb", + tout << "Attempting to update lemma with " << candidate << "\n";); + + unsigned uses_level = 0; + auto &pt = lemma->get_pob()->pt(); + bool res = pt.check_inductive(lemma->level(), candidate, uses_level, + lemma->weakness()); + if (res) { + m_st.success++; + lemma->update_cube(lemma->get_pob(), candidate); + lemma->set_level(uses_level); + TRACE("expand_bnd", tout << "expand_bnd succeeded with " + << mk_and(candidate) << " at level " + << uses_level << "\n";); + } + return res; +} + +/// Check whether lit ==> lit[val |--> n] (barring special cases). That is, +/// whether \p lit becomes weaker if \p val is replaced with \p n +/// +/// \p lit has to be of the form t <= v where v is a numeral. +/// Special cases: +/// In the trivial case in which \p val == \p n, return false. +/// if lit is an equality or the negation of an equality, return true. +bool lemma_expand_bnd_generalizer::is_interesting(const expr *lit, rational val, + rational new_val) { + SASSERT(lit); + // the only case in which negation and non negation agree + if (val == new_val) return false; + + if (m.is_eq(lit)) return true; + + // negation is the actual negation modulo val == n + expr *e1; + if (m.is_not(lit, e1)) { + return m.is_eq(lit) || !is_interesting(e1, val, new_val); + } + + SASSERT(val != new_val); + SASSERT(is_app(lit)); + + if (to_app(lit)->get_family_id() != m_arith.get_family_id()) return false; + switch (to_app(lit)->get_decl_kind()) { + case OP_LE: + case OP_LT: + return new_val > val; + case OP_GT: + case OP_GE: + return new_val < val; + default: + return false; + } +} + +void lemma_expand_bnd_generalizer::collect_statistics(statistics &st) const { + st.update("time.spacer.solve.reach.gen.expand", m_st.watch.get_seconds()); + st.update("SPACER expand_bnd attmpts", m_st.atmpts); + st.update("SPACER expand_bnd success", m_st.success); +} +} // namespace spacer diff --git a/src/muz/spacer/spacer_expand_bnd_generalizer.h b/src/muz/spacer/spacer_expand_bnd_generalizer.h new file mode 100644 index 00000000000..91ba3b41700 --- /dev/null +++ b/src/muz/spacer/spacer_expand_bnd_generalizer.h @@ -0,0 +1,68 @@ +#pragma once +/*++ + Copyright (c) 2020 Arie Gurfinkel + + Module Name: + + spacer_expand_bnd_generalizer.h + + Abstract: + + Strengthen lemmas by changing numeral constants inside arithmetic literals + + Author: + + Hari Govind V K + Arie Gurfinkel + + +--*/ + +#include "muz/spacer/spacer_context.h" + +namespace spacer { + +class lemma_expand_bnd_generalizer : public lemma_generalizer { + struct stats { + unsigned atmpts; + unsigned success; + stopwatch watch; + stats() { reset(); } + void reset() { + watch.reset(); + atmpts = 0; + success = 0; + } + }; + stats m_st; + ast_manager &m; + arith_util m_arith; + + /// A set of numeral values that can be used to expand bound + vector m_values; + + public: + lemma_expand_bnd_generalizer(context &ctx); + ~lemma_expand_bnd_generalizer() override {} + + void operator()(lemma_ref &lemma) override; + + void collect_statistics(statistics &st) const override; + void reset_statistics() override { m_st.reset(); } + + private: + + /// Check whether lit ==> lit[val |--> n] (barring special cases). That is, + /// whether \p lit becomes weaker if \p val is replaced with \p n + /// + /// \p lit has to be of the form t <= v where v is a numeral. + /// Special cases: + /// In the trivial case in which \p val == \p n, return false. + /// if lit is an equality or the negation of an equality, return true. + bool is_interesting(const expr *lit, rational val, rational n); + + /// check whether \p conj is a possible generalization for \p lemma. + /// update \p lemma if it is. + bool check_inductive(lemma_ref &lemma, expr_ref_vector &candiate); +}; +} // namespace spacer diff --git a/src/muz/spacer/spacer_generalizers.h b/src/muz/spacer/spacer_generalizers.h index ae1e532591e..2b41252ad9b 100644 --- a/src/muz/spacer/spacer_generalizers.h +++ b/src/muz/spacer/spacer_generalizers.h @@ -21,6 +21,7 @@ Revision History: #include "ast/arith_decl_plugin.h" #include "muz/spacer/spacer_context.h" +#include "muz/spacer/spacer_expand_bnd_generalizer.h" namespace spacer { From f6d0fc316b809895e891c73c7f65434aeab0e179 Mon Sep 17 00:00:00 2001 From: Arie Gurfinkel Date: Wed, 4 May 2022 10:26:45 -0400 Subject: [PATCH 13/78] Add spacer_global_generalizer Global generalizer checks every new lemma against a cluster of previously learned lemmas, and, if possible, conjectures a new pob, that, when blocked, generalizes multiple existing lemmas. --- src/muz/base/fp_params.pyg | 7 + src/muz/spacer/CMakeLists.txt | 1 + src/muz/spacer/spacer_context.cpp | 333 ++++-- src/muz/spacer/spacer_context.h | 123 ++- .../spacer/spacer_expand_bnd_generalizer.cpp | 4 +- src/muz/spacer/spacer_global_generalizer.cpp | 945 ++++++++++++++++++ src/muz/spacer/spacer_global_generalizer.h | 185 ++++ 7 files changed, 1510 insertions(+), 88 deletions(-) create mode 100644 src/muz/spacer/spacer_global_generalizer.cpp create mode 100644 src/muz/spacer/spacer_global_generalizer.h diff --git a/src/muz/base/fp_params.pyg b/src/muz/base/fp_params.pyg index 098922b1b54..94b146386d6 100644 --- a/src/muz/base/fp_params.pyg +++ b/src/muz/base/fp_params.pyg @@ -181,4 +181,11 @@ def_module_params('fp', ('spacer.use_lim_num_gen', BOOL, False, 'Enable limit numbers generalizer to get smaller numbers'), ('spacer.logic', SYMBOL, '', 'SMT-LIB logic to configure internal SMT solvers'), ('spacer.arith.solver', UINT, 2, 'arithmetic solver: 0 - no solver, 1 - bellman-ford based solver (diff. logic only), 2 - simplex based solver, 3 - floyd-warshall based solver (diff. logic only) and no theory combination 4 - utvpi, 5 - infinitary lra, 6 - lra solver'), + ('spacer.global', BOOL, False, 'Enable global guidance'), + ('spacer.gg.concretize', BOOL, True, 'Enable global guidance concretize'), + ('spacer.gg.conjecture', BOOL, True, 'Enable global guidance conjecture'), + ('spacer.gg.subsume', BOOL, True, 'Enable global guidance subsume'), + ('spacer.gg.use_sage', BOOL, False, '(Debug) Use SAGE for kernel computation in gg.subsume'), + ('spacer.use_iuc', BOOL, True, 'Enable Interpolating Unsat Core(IUC) for lemma generalization'), + ('spacer.expand_bnd', BOOL, False, 'Enable expand-bound lemma generalization'), )) diff --git a/src/muz/spacer/CMakeLists.txt b/src/muz/spacer/CMakeLists.txt index 255ca0c1f48..a0b6954b145 100644 --- a/src/muz/spacer/CMakeLists.txt +++ b/src/muz/spacer/CMakeLists.txt @@ -23,6 +23,7 @@ z3_add_component(spacer spacer_sem_matcher.cpp spacer_quant_generalizer.cpp spacer_arith_generalizers.cpp + spacer_global_generalizer.cpp spacer_expand_bnd_generalizer.cpp spacer_cluster.cpp spacer_callback.cpp diff --git a/src/muz/spacer/spacer_context.cpp b/src/muz/spacer/spacer_context.cpp index c88507fc49a..46d90f747e3 100644 --- a/src/muz/spacer/spacer_context.cpp +++ b/src/muz/spacer/spacer_context.cpp @@ -52,30 +52,38 @@ Module Name: #include "muz/transforms/dl_mk_rule_inliner.h" #include "muz/spacer/spacer_qe_project.h" #include "muz/spacer/spacer_sat_answer.h" +#include "muz/spacer/spacer_concretize.h" +#include "muz/spacer/spacer_global_generalizer.h" #define WEAKNESS_MAX 65535 namespace spacer { /// pob -- proof obligation -pob::pob (pob* parent, pred_transformer& pt, - unsigned level, unsigned depth, bool add_to_parent): - m_ref_count (0), - m_parent (parent), m_pt (pt), - m_post (m_pt.get_ast_manager ()), - m_binding(m_pt.get_ast_manager()), - m_new_post (m_pt.get_ast_manager ()), - m_level (level), m_depth (depth), - m_open (true), m_use_farkas (true), m_in_queue(false), - m_weakness(0), m_blocked_lvl(0) { +pob::pob(pob *parent, pred_transformer &pt, unsigned level, unsigned depth, + bool add_to_parent) + : m_ref_count(0), m_parent(parent), m_pt(pt), + m_post(m_pt.get_ast_manager()), m_binding(m_pt.get_ast_manager()), + m_new_post(m_pt.get_ast_manager()), m_level(level), m_depth(depth), + m_open(true), m_use_farkas(true), m_in_queue(false), m_is_conjecture(false), + m_enable_local_gen(true), m_enable_concretize(false), m_is_subsume(false), + m_enable_expand_bnd_gen(false), m_weakness(0), m_blocked_lvl(0), + m_conjecture_pat(m_pt.get_ast_manager()), + m_concretize_pat(m_pt.get_ast_manager()), + m_subsume_post(m_pt.get_ast_manager()), + m_subsume_bindings(m_pt.get_ast_manager()), m_gas(0) { if (add_to_parent && m_parent) { m_parent->add_child(*this); } + if (m_parent) { + m_is_conjecture = m_parent->is_conjecture(); + m_is_subsume = m_parent->is_subsume(); + m_gas = m_parent->get_gas(); + } } void pob::set_post(expr* post) { - app_ref_vector empty_binding(get_ast_manager()); - set_post(post, empty_binding); + set_post(post, {get_ast_manager()}); } void pob::set_post(expr* post, app_ref_vector const &binding) { @@ -757,6 +765,8 @@ void pred_transformer::collect_statistics(statistics& st) const m_must_reachable_watch.get_seconds ()); st.update("time.spacer.ctp", m_ctp_watch.get_seconds()); st.update("time.spacer.mbp", m_mbp_watch.get_seconds()); + // -- Max cluster size can decrease during run + st.update("SPACER max cluster size", m_cluster_db.get_max_cluster_size()); } void pred_transformer::reset_statistics() @@ -1259,12 +1269,11 @@ void pred_transformer::get_pred_bg_invs(expr_ref_vector& out) { /// \brief Returns true if the obligation is already blocked by current lemmas -bool pred_transformer::is_blocked (pob &n, unsigned &uses_level) -{ +bool pred_transformer::is_blocked(pob &n, unsigned &uses_level, model_ref *model) { ensure_level (n.level ()); prop_solver::scoped_level _sl (*m_solver, n.level ()); m_solver->set_core (nullptr); - m_solver->set_model (nullptr); + m_solver->set_model(model); expr_ref_vector post(m), _aux(m); post.push_back (n.post ()); @@ -1336,7 +1345,7 @@ lbool pred_transformer::is_reachable(pob& n, expr_ref_vector* core, model_ref* model, unsigned& uses_level, bool& is_concrete, datalog::rule const*& r, bool_vector& reach_pred_used, - unsigned& num_reuse_reach) + unsigned& num_reuse_reach, bool use_iuc) { TRACE("spacer", tout << "is-reachable: " << head()->get_name() << " level: " @@ -1350,7 +1359,8 @@ lbool pred_transformer::is_reachable(pob& n, expr_ref_vector* core, // prepare the solver prop_solver::scoped_level _sl(*m_solver, n.level()); - prop_solver::scoped_subset_core _sc (*m_solver, !n.use_farkas_generalizer ()); + prop_solver::scoped_subset_core _sc( + *m_solver, !(use_iuc && n.use_farkas_generalizer())); prop_solver::scoped_weakness _sw(*m_solver, 0, ctx.weak_abs() ? n.weakness() : UINT_MAX); m_solver->set_core(core); @@ -1407,9 +1417,10 @@ lbool pred_transformer::is_reachable(pob& n, expr_ref_vector* core, r = find_rule(**model, is_concrete, reach_pred_used, num_reuse_reach); TRACE("spacer", tout << "reachable is_sat: " << is_sat << " " - << r << " is_concrete " << is_concrete << " rused: " << reach_pred_used << "\n"; - ctx.get_datalog_context().get_rule_manager().display_smt2(*r, tout) << "\n"; - ); + << r << " is_concrete " << is_concrete << " rused: " << reach_pred_used << "\n";); + CTRACE("spacer", r, + ctx.get_datalog_context().get_rule_manager().display_smt2(*r, tout); + tout << "\n";); TRACE("spacer_sat", tout << "model is:\n" << **model << "\n";); } @@ -2273,6 +2284,8 @@ context::context(fp_params const& params, ast_manager& m) : m_last_result(l_undef), m_inductive_lvl(0), m_expanded_lvl(0), + m_global_gen(nullptr), + m_expand_bnd_gen(nullptr), m_json_marshaller(this), m_trace_stream(nullptr) { @@ -2291,6 +2304,7 @@ context::context(fp_params const& params, ast_manager& m) : m_pool1 = alloc(solver_pool, pool1_base.get(), max_num_contexts); m_pool2 = alloc(solver_pool, pool2_base.get(), max_num_contexts); + m_lmma_cluster = alloc(lemma_cluster_finder, m); updt_params(); if (m_params.spacer_trace_file().is_non_empty_string()) { @@ -2303,6 +2317,7 @@ context::context(fp_params const& params, ast_manager& m) : context::~context() { reset_lemma_generalizers(); + dealloc(m_lmma_cluster); reset(); if (m_trace_stream) { @@ -2348,7 +2363,14 @@ void context::updt_params() { m_restart_initial_threshold = m_params.spacer_restart_initial_threshold(); m_pdr_bfs = m_params.spacer_gpdr_bfs(); m_use_bg_invs = m_params.spacer_use_bg_invs(); - + m_global = m_params.spacer_global(); + m_expand_bnd = m_params.spacer_expand_bnd(); + m_gg_conjecture = m_params.spacer_gg_conjecture(); + m_gg_subsume = m_params.spacer_gg_subsume(); + m_gg_use_sage = m_params.spacer_gg_use_sage(); + m_gg_concretize = m_params.spacer_gg_concretize(); + + m_use_iuc = m_params.spacer_use_iuc(); if (m_use_gpdr) { // set options to be compatible with GPDR m_weak_abs = false; @@ -2679,6 +2701,15 @@ void context::init_lemma_generalizers() m_lemma_generalizers.push_back(alloc(lemma_array_eq_generalizer, *this)); } + if (m_global) { + m_global_gen = alloc(lemma_global_generalizer, *this); + m_lemma_generalizers.push_back(m_global_gen); + } + + if (m_expand_bnd) { + m_expand_bnd_gen = alloc(lemma_expand_bnd_generalizer, *this); + m_lemma_generalizers.push_back(m_expand_bnd_gen); + } if (m_validate_lemmas) { m_lemma_generalizers.push_back(alloc(lemma_sanity_checker, *this)); } @@ -3071,6 +3102,8 @@ void context::log_expand_pob(pob &n) { if (n.parent()) pob_id = std::to_string(n.parent()->post()->get_id()); *m_trace_stream << "** expand-pob: " << n.pt().head()->get_name() + << (n.is_conjecture() ? "CONJ" : "") + << (n.is_subsume() ? " SUBS" : "") << " level: " << n.level() << " depth: " << (n.depth() - m_pob_queue.min_depth()) << " exprID: " << n.post()->get_id() << " pobID: " << pob_id << "\n" @@ -3078,13 +3111,18 @@ void context::log_expand_pob(pob &n) { } TRACE("spacer", tout << "expand-pob: " << n.pt().head()->get_name() + << (n.is_conjecture() ? "CONJ" : "") + << (n.is_subsume() ? " SUBS" : "") << " level: " << n.level() << " depth: " << (n.depth() - m_pob_queue.min_depth()) - << " fvsz: " << n.get_free_vars_size() << "\n" + << " fvsz: " << n.get_free_vars_size() + << " gas: " << n.get_gas() << "\n" << mk_pp(n.post(), m) << "\n";); STRACE("spacer_progress", tout << "** expand-pob: " << n.pt().head()->get_name() + << (n.is_conjecture() ? "CONJ" : "") + << (n.is_subsume() ? " SUBS" : "") << " level: " << n.level() << " depth: " << (n.depth() - m_pob_queue.min_depth()) << "\n" << mk_epp(n.post(), m) << "\n\n";); @@ -3209,14 +3247,22 @@ bool context::check_reachability () break; case l_false: SASSERT(m_pob_queue.size() == old_sz); + // re-queue all pobs introduced by global gen and any pobs that can be blocked at a higher level for (auto pob : new_pobs) { - if (is_requeue(*pob)) {m_pob_queue.push(*pob);} + if ((pob->is_may_pob() && pob->post() != node->post()) || is_requeue(*pob)) { + m_pob_queue.push(*pob); + } } if (m_pob_queue.is_root(*node)) {return false;} break; case l_undef: SASSERT(m_pob_queue.size() == old_sz); + // collapse may pobs if the reachability of one of them cannot + // be estimated + if ((node->is_may_pob()) && new_pobs.size() == 0) { + close_all_may_parents(node); + } for (auto pob : new_pobs) {m_pob_queue.push(*pob);} break; } @@ -3271,9 +3317,9 @@ bool context::is_reachable(pob &n) unsigned saved = n.level (); // TBD: don't expose private field n.m_level = infty_level (); - lbool res = n.pt().is_reachable(n, nullptr, &mdl, - uses_level, is_concrete, r, - reach_pred_used, num_reuse_reach); + lbool res = + n.pt().is_reachable(n, nullptr, &mdl, uses_level, is_concrete, r, + reach_pred_used, num_reuse_reach, m_use_iuc); n.m_level = saved; if (res != l_true || !is_concrete) { @@ -3447,14 +3493,15 @@ lbool context::expand_pob(pob& n, pob_ref_buffer &out) log_expand_pob(n); stopwatch watch; - IF_VERBOSE (1, verbose_stream () << "expand: " << n.pt ().head ()->get_name () - << " (" << n.level () << ", " + IF_VERBOSE(1, verbose_stream() + << "expand: " << n.pt().head()->get_name() << " (" + << n.level() << ", " << (n.depth () - m_pob_queue.min_depth ()) << ") " << (n.use_farkas_generalizer () ? "FAR " : "SUB ") - << " w(" << n.weakness() << ") " - << n.post ()->get_id (); - verbose_stream().flush (); - watch.start ();); + << (n.is_conjecture() ? "CONJ " : "") + << (n.is_subsume() ? " SUBS" : "") << " w(" + << n.weakness() << ") " << n.post()->get_id(); + verbose_stream().flush(); watch.start();); // used in case n is unreachable unsigned uses_level = infty_level (); @@ -3473,21 +3520,54 @@ lbool context::expand_pob(pob& n, pob_ref_buffer &out) // if (!m_pob_queue.is_root (n)) n.close (); IF_VERBOSE (1, verbose_stream () << " K " << std::fixed << std::setprecision(2) - << watch.get_seconds () << "\n";); - n.inc_level(); - out.push_back(&n); - return l_false; - } - - if (/* XXX noop */ n.pt().is_qblocked(n)) { - STRACE("spacer_progress", - tout << "This pob can be blocked by instantiation\n";); - } - + << watch.get_seconds () << "\n";); + n.inc_level(); + out.push_back(&n); + return l_false; + } + + if (/* XXX noop */ n.pt().is_qblocked(n)) { + STRACE("spacer_progress", + tout << "This pob can be blocked by instantiation\n";); + } + if ((n.is_may_pob()) && n.get_gas() == 0) { + TRACE("global", tout << "Cant prove may pob. Collapsing " + << mk_pp(n.post(), m) << "\n";); + m_stats.m_num_pob_ofg++; + return l_undef; + } + // Decide whether to concretize pob + // get a model that satisfies the pob and the current set of lemmas + // TODO: if push_pob is enabled, avoid calling is_blocked twice + if (m_gg_concretize && n.is_concretize_enabled() && + !n.pt().is_blocked(n, uses_level, &model)) { + TRACE("global", + tout << "Concretizing: " << mk_pp(n.post(), m) << "\n" + << "\t" << n.get_gas() << " attempts left\n";); + + SASSERT(m_global_gen); + if (pob *new_pob = m_global_gen->mk_concretize_pob(n, model)) { + m_stats.m_num_concretize++; + out.push_back(new_pob); + out.push_back(&n); + IF_VERBOSE(1, verbose_stream() + << " C " << std::fixed << std::setprecision(2) + << watch.get_seconds() << "\n";); + unsigned gas = n.get_gas(); + SASSERT(gas > 0); + // dec gas for orig pob to limit number of concretizations + new_pob->set_gas(gas--); + n.set_gas(gas); + return l_undef; + } + } + + model = nullptr; predecessor_eh(); - lbool res = n.pt ().is_reachable (n, &cube, &model, uses_level, is_concrete, r, - reach_pred_used, num_reuse_reach); + lbool res = + n.pt().is_reachable(n, &cube, &model, uses_level, is_concrete, r, + reach_pred_used, num_reuse_reach, m_use_iuc); if (model) model->set_model_completion(false); if (res == l_undef && model) res = handle_unknown(n, r, *model); @@ -3535,7 +3615,18 @@ lbool context::expand_pob(pob& n, pob_ref_buffer &out) out.push_back (next); } } + if(n.is_subsume()) + m_stats.m_num_subsume_pob_reachable++; + if(n.is_conjecture()) + m_stats.m_num_conj_failed++; + CTRACE("global", n.is_conjecture(), + tout << "Failed to block conjecture " + << n.post()->get_id() << "\n";); + + CTRACE("global", n.is_subsume(), + tout << "Failed to block subsume generalization " + << mk_pp(n.post(), m) << "\n";); IF_VERBOSE(1, verbose_stream () << (next ? " X " : " T ") << std::fixed << std::setprecision(2) @@ -3566,7 +3657,7 @@ lbool context::expand_pob(pob& n, pob_ref_buffer &out) throw unknown_exception(); } case l_false: { - // n is unreachable, create new summary facts + // n is unreachable, create a new lemma timeit _timer (is_trace_enabled("spacer_timeit"), "spacer::expand_pob::false", verbose_stream ()); @@ -3574,40 +3665,69 @@ lbool context::expand_pob(pob& n, pob_ref_buffer &out) // -- only update expanded level when new lemmas are generated at it. if (n.level() < m_expanded_lvl) { m_expanded_lvl = n.level(); } - TRACE("spacer", tout << "cube:\n"; - for (unsigned j = 0; j < cube.size(); ++j) - tout << mk_pp(cube[j].get(), m) << "\n";); + TRACE("spacer", tout << "cube:\n" << cube << "\n";); + if(n.is_conjecture()) m_stats.m_num_conj_success++; + if(n.is_subsume()) m_stats.m_num_subsume_pob_blckd++; pob_ref nref(&n); - // -- create lemma from a pob and last unsat core - lemma_ref lemma = alloc(class lemma, pob_ref(&n), cube, uses_level); - // -- run all lemma generalizers - for (unsigned i = 0; - // -- only generalize if lemma was constructed using farkas - n.use_farkas_generalizer () && !lemma->is_false() && - i < m_lemma_generalizers.size(); ++i) { - checkpoint (); - (*m_lemma_generalizers[i])(lemma); + // -- create lemma from a pob and last unsat core + lemma_ref lemma_pob; + if (n.is_local_gen_enabled()) { + lemma_pob = alloc(class lemma, nref, cube, uses_level); + // -- run all lemma generalizers + for (unsigned i = 0; + // -- only generalize if lemma was constructed using farkas + n.use_farkas_generalizer() && !lemma_pob->is_false() && + i < m_lemma_generalizers.size(); + ++i) { + checkpoint (); + (*m_lemma_generalizers[i])(lemma_pob); + } + } else if (m_global_gen || m_expand_bnd_gen) { + m_stats.m_non_local_gen++; + + expr_ref_vector pob_cube(m); + n.get_post_simplified(pob_cube); + + lemma_pob = alloc(class lemma, nref, pob_cube, n.level()); + TRACE("global", tout << " stopped local gen on pob " + << mk_pp(n.post(), m) << " with id " + << n.post()->get_id() << "\n lemma learned " + << mk_and(lemma_pob->get_cube()) << "\n";); + if (m_global_gen) (*m_global_gen)(lemma_pob); + if (m_expand_bnd_gen) (*m_expand_bnd_gen)(lemma_pob); + } else { + lemma_pob = alloc(class lemma, nref, cube, uses_level); } - DEBUG_CODE( - lemma_sanity_checker sanity_checker(*this); - sanity_checker(lemma); - ); + CTRACE("global", n.is_conjecture() || n.is_subsume(), + tout << " Blocked " + << (n.is_conjecture() ? "conjecture" : "subsume") + << " pob " << mk_pp(n.post(), m) + << " using lemma " << mk_pp(lemma_pob->get_expr(), m) + << " Level " << lemma_pob->level() << " id " + << n.post()->get_id() << "\n";); - TRACE("spacer", tout << "invariant state: " - << (is_infty_level(lemma->level())?"(inductive)":"") - << mk_pp(lemma->get_expr(), m) << "\n";); + DEBUG_CODE(lemma_sanity_checker sanity_checker(*this); + sanity_checker(lemma_pob);); - bool v = n.pt().add_lemma (lemma.get()); - if (v) { m_stats.m_num_lemmas++; } + TRACE("spacer", + tout << "invariant state: " + << (is_infty_level(lemma_pob->level()) ? "(inductive)" : "") + << mk_pp(lemma_pob->get_expr(), m) << "\n";); + + bool v = n.pt().add_lemma(lemma_pob.get()); + if (v) { + if (m_global) m_lmma_cluster->cluster(lemma_pob); + m_stats.m_num_lemmas++; + } // Optionally update the node to be the negation of the lemma if (v && m_use_lemma_as_pob) { expr_ref c(m); - c = mk_and(lemma->get_cube()); + c = mk_and(lemma_pob->get_cube()); // check that the post condition is different if (c != n.post()) { pob *f = n.pt().find_pob(n.parent(), c); @@ -3623,6 +3743,31 @@ lbool context::expand_pob(pob& n, pob_ref_buffer &out) } } + if (m_global_gen) { + // if global gen is enabled, post-process the pob to create new subsume or conjecture pob + if (pob* new_pob = m_global_gen->mk_subsume_pob(n)) { + new_pob->set_gas(n.get_gas() - 1); + n.set_gas(n.get_gas() - 1); + out.push_back(new_pob); + TRACE("global", + tout << "Attempting to block pob " << mk_pp(n.post(), m) + << " using generalization " << mk_pp(new_pob->post(), m) + << " with gas " << new_pob->get_gas() << "\n";); + out.push_back(new_pob); + m_stats.m_num_subsume_pobs++; + } else if (pob* new_pob = m_gg_conjecture ? m_global_gen->mk_conjecture_pob(n) : nullptr) { + new_pob->set_gas(n.get_gas() - 1); + n.set_gas(n.get_gas() - 1); + out.push_back(new_pob); + TRACE("global", tout << " conjecture " << mk_pp(n.post(), m) + << " id is " << n.post()->get_id() + << "\n into pob " << new_pob->post() + << " id is " << new_pob->post()->get_id() + << "\n";); + m_stats.m_num_conj++; + } + } + // schedule the node to be placed back in the queue n.inc_level(); out.push_back(&n); @@ -3638,6 +3783,12 @@ lbool context::expand_pob(pob& n, pob_ref_buffer &out) } case l_undef: // something went wrong + // if the pob is a may pob, bail out + if (n.is_may_pob()) { + n.close(); + m_stats.m_expand_pob_undef++; + return l_undef; + } if (n.weakness() < 10 /* MAX_WEAKENSS */) { bool has_new_child = false; SASSERT(m_weak_abs); @@ -3934,6 +4085,11 @@ bool context::create_children(pob& n, datalog::rule const& r, !mdl.is_true(n.post()))) { kid->reset_derivation(); } + if (kid->is_may_pob()) { + SASSERT(n.get_gas() > 0); + n.set_gas(n.get_gas() - 1); + kid->set_gas(n.get_gas() - 1); + } out.push_back(kid); m_stats.m_num_queries++; return true; @@ -3972,6 +4128,17 @@ void context::collect_statistics(statistics& st) const st.update("SPACER num lemmas", m_stats.m_num_lemmas); // -- number of restarts taken st.update("SPACER restarts", m_stats.m_num_restarts); + // -- number of time pob abstraction was invoked + st.update("SPACER conj", m_stats.m_num_conj); + st.update("SPACER conj success", m_stats.m_num_conj_success); + st.update("SPACER conj failed", + m_stats.m_num_conj_failed); + st.update("SPACER pob out of gas", m_stats.m_num_pob_ofg); + st.update("SPACER subsume pob", m_stats.m_num_subsume_pobs); + st.update("SPACER subsume success", m_stats.m_num_subsume_pob_reachable); + st.update("SPACER subsume failed", m_stats.m_num_subsume_pob_blckd); + st.update("SPACER concretize", m_stats.m_num_concretize); + st.update("SPACER non local gen", m_stats.m_non_local_gen); // -- time to initialize the rules st.update ("time.spacer.init_rules", m_init_rules_watch.get_seconds ()); @@ -3992,6 +4159,7 @@ void context::collect_statistics(statistics& st) const for (unsigned i = 0; i < m_lemma_generalizers.size(); ++i) { m_lemma_generalizers[i]->collect_statistics(st); } + m_lmma_cluster->collect_statistics(st); } void context::reset_statistics() @@ -4009,6 +4177,7 @@ void context::reset_statistics() m_lemma_generalizers[i]->reset_statistics(); } + m_lmma_cluster->reset_statistics(); m_init_rules_watch.reset (); m_solve_watch.reset (); m_propagate_watch.reset (); @@ -4141,6 +4310,10 @@ inline bool pob_lt_proc::operator() (const pob *pn1, const pob *pn2) const if (n1.depth() != n2.depth()) { return n1.depth() < n2.depth(); } + if (n1.is_subsume() != n2.is_subsume()) { return n1.is_subsume(); } + if (n1.is_conjecture() != n2.is_conjecture()) { return n1.is_conjecture(); } + + if (n1.get_gas() != n2.get_gas()) { return n1.get_gas() > n2.get_gas(); } // -- a more deterministic order of proof obligations in a queue // if (!n1.get_context ().get_params ().spacer_nondet_tie_break ()) { @@ -4194,5 +4367,27 @@ inline bool pob_lt_proc::operator() (const pob *pn1, const pob *pn2) const } - +// set gas of each may parent to 0 +// TODO: close siblings as well. kids of a pob are not stored in the pob +void context::close_all_may_parents(pob_ref node) { + pob_ref_vector to_do; + to_do.push_back(node.get()); + while (to_do.size() != 0) { + pob_ref t = to_do.back(); + t->set_gas(0); + if (t->is_may_pob()) { + t->close(); + } else + break; + to_do.pop_back(); + to_do.push_back(t->parent()); + } +} +// construct a simplified version of the post +void pob::get_post_simplified(expr_ref_vector &pob_cube) { + pob_cube.reset(); + pob_cube.push_back(m_post); + flatten_and(pob_cube); + simplify_bounds(pob_cube); +} } diff --git a/src/muz/spacer/spacer_context.h b/src/muz/spacer/spacer_context.h index 9acf225f862..1b15060401e 100644 --- a/src/muz/spacer/spacer_context.h +++ b/src/muz/spacer/spacer_context.h @@ -51,6 +51,7 @@ class pob_queue; class context; class lemma_cluster; class lemma_cluster_finder; +class lemma_global_generalizer; typedef obj_map rule2inst; typedef obj_map decl2rel; @@ -636,12 +637,12 @@ class pred_transformer { return m_pobs.mk_pob(parent, level, depth, post); } - lbool is_reachable(pob &n, expr_ref_vector *core, model_ref *model, - unsigned &uses_level, bool &is_concrete, + lbool is_reachable(pob& n, expr_ref_vector* core, model_ref *model, + unsigned& uses_level, bool& is_concrete, datalog::rule const *&r, bool_vector &reach_pred_used, - unsigned &num_reuse_reach); + unsigned& num_reuse_reach, bool use_iuc = true); bool is_invariant(unsigned level, lemma *lem, unsigned &solver_level, - expr_ref_vector *core = nullptr); + expr_ref_vector* core = nullptr); bool is_invariant(unsigned level, expr *lem, unsigned &solver_level, expr_ref_vector *core = nullptr) { @@ -673,7 +674,7 @@ class pred_transformer { /// \brief Returns true if the obligation is already blocked by current /// lemmas - bool is_blocked(pob &n, unsigned &uses_level); + bool is_blocked(pob &n, unsigned &uses_level, model_ref *model = nullptr); /// \brief Returns true if the obligation is already blocked by current /// quantified lemmas bool is_qblocked(pob &n); @@ -752,6 +753,16 @@ class pob { unsigned m_use_farkas:1; /// true if this pob is in pob_queue unsigned m_in_queue:1; + // true if this pob is a conjecture + unsigned m_is_conjecture:1; + // should do local generalizations on pob + unsigned m_enable_local_gen:1; + // should concretize cube + unsigned m_enable_concretize:1; + // is a subsume pob + unsigned m_is_subsume:1; + // should apply expand bnd generalization on pob + unsigned m_enable_expand_bnd_gen:1; unsigned m_weakness; /// derivation representing the position of this node in the parent's rule @@ -769,7 +780,24 @@ class pob { // clang-format on // clang-format off - public: + // pattern with which conjecture was created + expr_ref_vector m_conjecture_pat; + + // pattern identified for one of its lemmas + expr_ref m_concretize_pat; + + // a post that subsumes all lemmas that block this pob + expr_ref_vector m_subsume_post; + + // bindings for subsume post + app_ref_vector m_subsume_bindings; + + // level at which may pob is to be added + unsigned m_may_lvl; + + // gas decides how much time is spent in blocking this (may) pob + unsigned m_gas; +public: pob(pob *parent, pred_transformer &pt, unsigned level, unsigned depth = 0, bool add_to_parent = true); @@ -781,11 +809,24 @@ class pob { void set_post(expr *post, app_ref_vector const &binding); void set_post(expr *post); + void set_subsume_pob(const expr_ref_vector &expr) { + m_may_lvl = 0; + m_subsume_post.reset(); + m_subsume_post.append(expr); + } + void set_subsume_bindings(app_ref_vector& vars) { + m_subsume_bindings.reset(); + m_subsume_bindings.append(vars); + } + void set_may_pob_lvl(unsigned l) { m_may_lvl = l; } + unsigned get_may_pob_lvl() { return m_may_lvl; } + expr_ref_vector const &get_subsume_pob() const { return m_subsume_post; } + app_ref_vector const &get_subsume_bindings() const { return m_subsume_bindings; } unsigned weakness() { return m_weakness; } void bump_weakness() { m_weakness++; } void reset_weakness() { m_weakness = 0; } - void inc_level() { + void inc_level () { SASSERT(!is_in_queue()); m_level++; m_depth++; @@ -795,17 +836,41 @@ class pob { void inherit(pob const &p); void set_derivation(derivation *d) { m_derivation = d; } bool has_derivation() const { return (bool)m_derivation; } - derivation &get_derivation() const { return *m_derivation.get(); } - void reset_derivation() { set_derivation(nullptr); } + derivation &get_derivation() const { return *m_derivation.get (); } + void reset_derivation() { set_derivation (nullptr); } /// detaches derivation from the node without deallocating - derivation *detach_derivation() { return m_derivation.detach(); } - - pob *parent() const { return m_parent.get(); } - - pred_transformer &pt() const { return m_pt; } - ast_manager &get_ast_manager() const { return m_pt.get_ast_manager(); } - manager &get_manager() const { return m_pt.get_manager(); } - context &get_context() const { return m_pt.get_context(); } + derivation* detach_derivation() { return m_derivation.detach (); } + + pob* parent() const { return m_parent.get (); } + + bool is_conjecture() const { return m_is_conjecture; } + void set_conjecture(bool v = true) { m_is_conjecture = v; } + + void disable_expand_bnd_gen() { m_enable_expand_bnd_gen = false; } + bool is_expand_bnd_enabled() { return m_enable_expand_bnd_gen; } + void set_expand_bnd(bool v = true) { m_enable_expand_bnd_gen = v; } + void set_concretize_pattern(const expr_ref &pattern) { m_concretize_pat = pattern; } + const expr_ref &get_concretize_pattern() const { return m_concretize_pat; } + const expr_ref_vector &get_conjecture_pattern() const { return m_conjecture_pat; } + void set_conjecture_pattern(const expr_ref_vector &pattern) { + m_conjecture_pat.reset(); + m_conjecture_pat.append(pattern); + } + bool is_subsume() const { return m_is_subsume; } + void set_subsume(bool v = true) { m_is_subsume = v; } + bool is_may_pob() const { return is_subsume() || is_conjecture(); } + unsigned get_gas() const { return m_gas; } + void set_gas(unsigned n) { m_gas = n; } + + bool is_local_gen_enabled() const { return m_enable_local_gen; } + void disable_local_gen() { m_enable_local_gen = false; } + void get_post_simplified(expr_ref_vector &res); + bool is_concretize_enabled() const { return m_enable_concretize && m_gas > 0; } + void set_concretize(bool v = true) { m_enable_concretize = v; } + pred_transformer& pt() const { return m_pt; } + ast_manager& get_ast_manager() const { return m_pt.get_ast_manager(); } + manager& get_manager() const { return m_pt.get_manager(); } + context& get_context() const { return m_pt.get_context(); } unsigned level() const { return m_level; } unsigned depth() const { return m_depth; } @@ -1061,6 +1126,15 @@ class context { unsigned m_num_restarts; unsigned m_num_lemmas_imported; unsigned m_num_lemmas_discarded; + unsigned m_num_conj; + unsigned m_num_conj_success; + unsigned m_num_conj_failed; + unsigned m_num_subsume_pobs; + unsigned m_num_subsume_pob_reachable; + unsigned m_num_subsume_pob_blckd; + unsigned m_num_concretize; + unsigned m_num_pob_ofg; + unsigned m_non_local_gen; stats() { reset(); } void reset() { memset(this, 0, sizeof(*this)); } }; @@ -1094,6 +1168,9 @@ class context { unsigned m_inductive_lvl; unsigned m_expanded_lvl; ptr_buffer m_lemma_generalizers; + lemma_global_generalizer *m_global_gen; + lemma_generalizer *m_expand_bnd_gen; + lemma_cluster_finder *m_lmma_cluster; stats m_stats; model_converter_ref m_mc; proof_converter_ref m_pc; @@ -1126,6 +1203,13 @@ class context { bool m_simplify_formulas_post; bool m_pdr_bfs; bool m_use_bg_invs; + bool m_global; + bool m_expand_bnd; + bool m_gg_conjecture; + bool m_gg_subsume; + bool m_gg_use_sage; + bool m_gg_concretize; + bool m_use_iuc; unsigned m_push_pob_max_depth; unsigned m_max_level; unsigned m_restart_initial_threshold; @@ -1227,6 +1311,7 @@ class context { bool elim_aux() const { return m_elim_aux; } bool reach_dnf() const { return m_reach_dnf; } bool use_bg_invs() const { return m_use_bg_invs; } + bool do_subsume() const { return m_gg_subsume; } ast_manager &get_ast_manager() const { return m; } manager &get_manager() { return m_pm; } @@ -1286,6 +1371,10 @@ class context { bool is_inductive(); + bool use_sage() { return m_gg_use_sage; } + // close all parents of may pob when gas runs out + void close_all_may_parents(pob_ref node); + // three different solvers with three different sets of parameters // different solvers are used for different types of queries in spacer solver *mk_solver0() { return m_pool0->mk_solver(); } diff --git a/src/muz/spacer/spacer_expand_bnd_generalizer.cpp b/src/muz/spacer/spacer_expand_bnd_generalizer.cpp index 6639bc843fc..a3e88f8d537 100644 --- a/src/muz/spacer/spacer_expand_bnd_generalizer.cpp +++ b/src/muz/spacer/spacer_expand_bnd_generalizer.cpp @@ -108,7 +108,7 @@ lemma_expand_bnd_generalizer::lemma_expand_bnd_generalizer(context &ctx) void lemma_expand_bnd_generalizer::operator()(lemma_ref &lemma) { scoped_watch _w_(m_st.watch); - if (!lemma->get_pob()->expand_bnd()) return; + if (!lemma->get_pob()->is_expand_bnd_enabled()) return; expr_ref_vector cube(lemma->get_cube()); @@ -159,7 +159,7 @@ void lemma_expand_bnd_generalizer::operator()(lemma_ref &lemma) { // Currently, we allow for only one round of expand bound per lemma // Mark lemma as already expanded so that it is not generalized in this way // again - lemma->get_pob()->stop_expand_bnd(); + lemma->get_pob()->disable_expand_bnd_gen(); } /// Check whether \p candidate is a possible generalization for \p lemma. diff --git a/src/muz/spacer/spacer_global_generalizer.cpp b/src/muz/spacer/spacer_global_generalizer.cpp new file mode 100644 index 00000000000..73fea662e02 --- /dev/null +++ b/src/muz/spacer/spacer_global_generalizer.cpp @@ -0,0 +1,945 @@ +/*++ +Copyright (c) 2020 Arie Gurfinkel + +Module Name: + + spacer_global_generalizer.cpp + +Abstract: + + Global Guidance for Spacer + +Author: + + Hari Govind V K + Arie Gurfinkel + + +--*/ +#include "muz/spacer/spacer_global_generalizer.h" +#include "ast/arith_decl_plugin.h" +#include "ast/ast_util.h" +#include "ast/for_each_expr.h" +#include "ast/rewriter/expr_safe_replace.h" +#include "muz/spacer/spacer_cluster.h" +#include "muz/spacer/spacer_concretize.h" +#include "muz/spacer/spacer_context.h" +#include "muz/spacer/spacer_manager.h" +#include "muz/spacer/spacer_matrix.h" +#include "muz/spacer/spacer_util.h" +#include "smt/smt_solver.h" + +using namespace spacer; + +namespace { + +// LOCAL HELPER FUNCTIONS IN ANONYMOUS NAMESPACE + +struct compute_lcm_proc { + ast_manager &m; + arith_util m_arith; + rational m_val; + compute_lcm_proc(ast_manager &a_m) : m(a_m), m_arith(m), m_val(1) {} + void operator()(expr *n) const {} + void operator()(app *n) { + rational val; + if (m_arith.is_numeral(n, val)) { + m_val = lcm(denominator(abs(val)), m_val); + } + } +}; + +/// Check whether there are Real constants in \p c +bool contains_reals(const app_ref_vector &c) { + arith_util m_arith(c.get_manager()); + for (auto *f : c) { + if (m_arith.is_real(f)) return true; + } + return false; +} + +// Check whether there are Int constants in \p c +bool contains_ints(const app_ref_vector &c) { + arith_util m_arith(c.get_manager()); + for (auto *f : c) { + if (m_arith.is_int(f)) return true; + } + return false; +} + +// Check whether \p sub contains a mapping to a bv_numeral. +// return bv_size of the bv_numeral in the first such mapping. +bool contains_bv(ast_manager &m, const substitution &sub, unsigned &sz) { + bv_util m_bv(m); + std::pair v; + expr_offset r; + rational num; + for (unsigned j = 0, sz = sub.get_num_bindings(); j < sz; j++) { + sub.get_binding(j, v, r); + if (m_bv.is_numeral(r.get_expr(), num, sz)) return true; + } + return false; +} + +// Check whether 1) all expressions in the range of \p sub are bv_numerals 2) +// all bv_numerals in range are of size sz +bool all_same_sz(ast_manager &m, const substitution &sub, unsigned sz) { + bv_util m_bv(m); + std::pair v; + expr_offset r; + rational num; + unsigned n_sz; + for (unsigned j = 0; j < sub.get_num_bindings(); j++) { + sub.get_binding(j, v, r); + if (!m_bv.is_numeral(r.get_expr(), num, n_sz) || n_sz != sz) + return false; + } + return true; +} + +/// Check whether there is an equivalent of function \p f in LRA +bool has_lra_equiv(const expr_ref &f) { + ast_manager &m = f.m(); + arith_util a(m); + + // uninterpreted constants do not have arguments. So equivalent function + // exists. + if (is_uninterp_const(f)) return true; + return is_app(f) && + to_app(f)->get_decl()->get_family_id() == a.get_family_id(); +} + +/// Get lcm of all the denominators of all the rational values in e +rational compute_lcm(expr *e, ast_manager &m) { + compute_lcm_proc g(m); + for_each_expr(g, e); + TRACE("subsume_verb", + tout << "lcm of " << mk_pp(e, m) << " is " << g.m_val << "\n";); + return g.m_val; +} + +// clang-format off +/// Removes all occurrences of (to_real t) from \p fml where t is a constant +/// +/// Applies the following rewrites upto depth \p depth +/// v:Real --> mk_int(v) where v is a real value +/// (to_real v:Int) --> v +/// (to_int v) --> (strip_to_real v) +/// (store A (to_int (to_real i0)) ... (to_int (to_real iN)) k) --> (store A i0 ... iN +/// (strip_to_real k)) +/// (select A (to_int (to_real i0)) ... (to_int (to_real iN))) --> (select A i0 ... iN) +/// (op (to_real a0) ... (to_real aN)) --> (op a0 ... aN) where op is an +/// arithmetic operation +/// on all other formulas, do nothing +/// NOTE: cannot use a rewriter since we change the sort of fml +// clang-format on +void strip_to_real(expr_ref &fml, unsigned depth = 3) { + ast_manager &m = fml.get_manager(); + arith_util arith(m); + rational r; + + if (arith.is_numeral(fml, r)) { + SASSERT(denominator(r).is_one()); + fml = arith.mk_int(r); + return; + } + + if (depth == 0) { return; } + + if (arith.is_to_real(fml)) { + fml = to_app(fml)->get_arg(0); + return; + } + if (arith.is_to_int(fml) && arith.is_to_real(to_app(fml)->get_arg(0))) { + expr *arg = to_app(fml)->get_arg(0); + fml = to_app(arg)->get_arg(0); + return; + } + + if (!is_app(fml)) return; + + app *f_app = to_app(fml); + expr_ref_buffer new_args(m); + expr_ref child(m); + for (unsigned i = 0, sz = f_app->get_num_args(); i < sz; i++) { + child = f_app->get_arg(i); + strip_to_real(child, depth - 1); + new_args.push_back(child); + } + fml = m.mk_app(f_app->get_family_id(), f_app->get_decl_kind(), + new_args.size(), new_args.data()); + return; +} + +/// Coerces a rational inequality to a semantically equivalent inequality with +/// integer coefficients +/// +/// Works on arithmetic (in)equalities +/// if fml contains a mod, fml is not normalized +/// otherwise, lcm of fml is computed and lcm * fml is computed +void to_int_term(expr_ref &fml) { + ast_manager &m = fml.get_manager(); + arith_util arith(m); + + if (!(arith.is_arith_expr(fml) || m.is_eq(fml))) return; + if (!contains_real(fml)) return; + + app *fml_app = to_app(fml); + SASSERT(fml_app->get_num_args() == 2); + expr_ref lhs(fml_app->get_arg(0), m); + expr_ref rhs(fml_app->get_arg(1), m); + + // mod not supported + SASSERT(!contains_mod(fml)); + + rational lcm = compute_lcm(fml, m); + SASSERT(lcm != rational::zero()); + + mul_by_rat(lhs, lcm); + mul_by_rat(rhs, lcm); + + strip_to_real(lhs); + strip_to_real(rhs); + fml = + m.mk_app(fml_app->get_family_id(), fml_app->get_decl_kind(), lhs, rhs); +} + +/// Convert all fractional constants in \p fml to integers +void to_int(expr_ref &fml) { + ast_manager &m = fml.get_manager(); + arith_util arith(m); + expr_ref_vector fml_vec(m), new_fml(m); + expr_ref new_lit(m); + + flatten_and(fml, fml_vec); + for (auto *lit : fml_vec) { + new_lit = lit; + to_int_term(new_lit); + new_fml.push_back(new_lit); + } + fml = mk_and(new_fml); +} + +// clang-format off +/// Wrap integer uninterpreted constants in expression \p fml with (to_real) +/// +/// only supports arithmetic expressions +/// Applies the following rewrite rules upto depth \p depth +/// (to_real_term c) --> (c:Real) where c is a numeral +/// (to_real_term i:Int) --> (to_real i) where i is a constant/var +/// (to_real_term (select A i0:Int ... iN:Int)) --> (select A (to_int (to_real i0)) ... +/// (to_int (to_real iN))) +/// (to_real_term (store A i0:Int ... iN:Int k)) --> (store A (to_int (to_real i0)) ... +/// (to_int (to_real iN)) +/// (to_real_term k)) +/// (to_real_term (op (a0:Int) ... (aN:Int))) --> (op (to_real a0) ... (to_real aN)) +/// where op is +/// an arithmetic +/// operation +/// on all other formulas, do nothing +/// NOTE: cannot use a rewriter since we change the sort of fml +// clang-format on +static void to_real_term(expr_ref &fml, unsigned depth = 3) { + ast_manager &m = fml.get_manager(); + arith_util arith(m); + datatype_util datatype(m); + if (!arith.is_int_real(fml)) return; + rational r; + if (arith.is_numeral(fml, r)) { + fml = arith.mk_real(r); + return; + } + if (is_uninterp_const(fml)) { + if (arith.is_int(fml)) fml = arith.mk_to_real(fml); + return; + } + if (arith.is_to_real(fml)) { + expr *arg = to_app(fml)->get_arg(0); + if (arith.is_numeral(arg, r)) fml = arith.mk_real(r); + return; + } + + if (is_uninterp(fml)) return; + if (depth == 0) return; + SASSERT(is_app(fml)); + app *fml_app = to_app(fml); + expr *const *args = fml_app->get_args(); + unsigned num_args = fml_app->get_num_args(); + expr_ref_buffer new_args(m); + expr_ref child(m); + + if (!has_lra_equiv(fml)) { + new_args.push_back(args[0]); + for (unsigned i = 1; i < num_args; i++) { + child = args[i]; + to_real_term(child, depth - 1); + if (arith.is_int(args[i])) child = arith.mk_to_int(child); + SASSERT(args[i]->get_sort() == child->get_sort()); + new_args.push_back(child); + } + fml = m.mk_app(fml_app->get_decl(), new_args); + } else { + for (unsigned i = 0; i < num_args; i++) { + child = args[i]; + to_real_term(child, depth - 1); + new_args.push_back(child); + } + // The mk_app method selects the function sort based on the sort of + // new_args + fml = m.mk_app(fml_app->get_family_id(), fml_app->get_decl_kind(), + new_args.size(), new_args.data()); + } + return; +} + +/// Wrap integer uninterpreted constants in conjunction \p fml with +/// (to_real) +void to_real(expr_ref &fml) { + ast_manager &m = fml.get_manager(); + + // cannot use an expr_ref_buffer since flatten_and operates on + // expr_ref_vector + expr_ref_vector fml_vec(m), new_fml(m); + flatten_and(fml, fml_vec); + + expr_ref_buffer new_args(m); + expr_ref kid(m), new_f(m); + for (auto *lit : fml_vec) { + new_args.reset(); + app *lit_app = to_app(lit); + for (auto *arg : *lit_app) { + kid = arg; + to_real_term(kid); + new_args.push_back(kid); + } + // Uninterpreted functions cannot be created using the mk_app api that + // is being used. + if (is_uninterp(lit)) new_f = lit; + // use this api to change sorts in domain of f + else + new_f = m.mk_app(lit_app->get_family_id(), lit_app->get_decl_kind(), + new_args.size(), new_args.data()); + new_fml.push_back(new_f); + } + fml = mk_and(new_fml); +} + +} // namespace + +namespace spacer { +lemma_global_generalizer::subsumer::subsumer(ast_manager &a_m, bool use_sage, + bool ground_pob) + : m(a_m), m_arith(m), m_bv(m), m_cvx_cls(m, use_sage), m_dim_frsh_cnsts(m), + m_dim_vars(m), m_ground_pob(ground_pob) { + scoped_ptr factory( + mk_smt_strategic_solver_factory(symbol::null)); + m_solver = (*factory)(m, params_ref::get_empty(), false, true, false, + symbol::null); +} + +lemma_global_generalizer::lemma_global_generalizer(context &ctx) + : lemma_generalizer(ctx), m(ctx.get_ast_manager()), + m_subsumer(m, ctx.use_sage(), ctx.use_ground_pob()), + m_do_subsume(ctx.do_subsume()) {} + +void lemma_global_generalizer::operator()(lemma_ref &lemma) { + scoped_watch _w_(m_st.watch); + generalize(lemma); +} + +void lemma_global_generalizer::subsumer::add_dim_vars(const lemma_cluster &lc) { + expr_offset r; + std::pair v; + + unsigned n_vars = get_num_vars(lc.get_pattern()); + + auto &lemmas = lc.get_lemmas(); + const substitution &sub = lemmas.get(0).get_sub(); + + for (unsigned j = 0; j < n_vars; j++) { + // get var id + sub.get_binding(j, v, r); + auto *sort = r.get_expr()->get_sort(); + + // create a variable for jth dimension, and register with convex closure + var *var = m.mk_var(v.first, sort); + m_dim_vars[j] = var; + m_cvx_cls.set_dimension(j, var); + + // create a fresh skolem constant for the jth variable + m_dim_frsh_cnsts[j] = m.mk_fresh_const("mrg_cvx", sort); + } +} + +// Populate m_cvx_cls by 1) collecting all substitutions in the cluster \p lc +// 2) normalizing them to integer numerals +void lemma_global_generalizer::subsumer::populate_cvx_cls( + const lemma_cluster &lc) { + expr_offset r; + std::pair v; + + unsigned n_vars = get_num_vars(lc.get_pattern()); + const lemma_info_vector &lemmas = lc.get_lemmas(); + + // compute LCM of all denominators numbers in all lemma instances + rational lemma_lcm = rational::one(), num; + for (const auto &lemma : lemmas) { + const substitution &sub = lemma.get_sub(); + for (unsigned j = 0; j < n_vars; j++) { + sub.get_binding(j, v, r); + if (m_arith.is_numeral(r.get_expr(), num) || + m_bv.is_numeral(r.get_expr(), num)) { + lemma_lcm = lcm(lemma_lcm, abs(denominator(num))); + } + } + } + + m_cvx_cls.set_lcm(lemma_lcm); + + // Populate m_cvx_cls by normalized points corresponding to the + // substitutions Each point is normalized by multiplying by LCM of all + // denominators + vector point; + for (const auto &lemma : lemmas) { + point.reset(); + + const substitution &sub = lemma.get_sub(); + for (unsigned j = 0; j < n_vars; j++) { + sub.get_binding(j, v, r); + if (m_arith.is_numeral(r.get_expr(), num) || + m_bv.is_numeral(r.get_expr(), num)) { + point.push_back(lemma_lcm * num); + } + } + // -- add normalized point to convex closure + m_cvx_cls.push_back(point); + } +} + +/// Find a representative for \p c +// TODO: replace with a symbolic representative +expr *lemma_global_generalizer::subsumer::find_repr(const model_ref &mdl, + const app *c) { + return mdl->get_const_interp(c->get_decl()); +} + +/// Skolemize implicitly existentially quantified constants +/// +/// Constants in \p m_dim_frsh_cnsts are existentially quantified in \p f. They +/// are replaced by specific skolem constants. The \p out vector is populated +/// with corresponding instantiations. Currently, instantiations are values +/// chosen from the model +void lemma_global_generalizer::subsumer::skolemize(expr_ref &f, + const model_ref &mdl, + app_ref_vector &out) { + unsigned idx = out.size(); + app_ref sk(m); + expr_ref eval(m); + expr_safe_replace sub(m); + + expr_ref_vector f_cnsts(m); + spacer::collect_uninterp_consts(f, f_cnsts); + + expr_fast_mark2 marks; + for (auto *c : f_cnsts) { marks.mark(c); } + + for (unsigned i = 0, sz = m_dim_frsh_cnsts.size(); i < sz; i++) { + app *c = m_dim_frsh_cnsts.get(i); + if (!marks.is_marked(c)) continue; + + SASSERT(m_arith.is_int(c)); + // Make skolem constants for ground pob + sk = mk_zk_const(m, i + idx, c->get_sort()); + eval = find_repr(mdl, c); + SASSERT(is_app(eval)); + out.push_back(to_app(eval)); + sub.insert(c, sk); + } + sub(f.get(), f); + TRACE("subsume", tout << "skolemized into " << f << "\n";); + m_dim_frsh_cnsts.reset(); +} + +///\p hard is a hard constraint and \p soft is a soft constraint that have to be +/// satisfied by mdl +bool lemma_global_generalizer::subsumer::maxsat_with_model( + const expr_ref &hard, const expr_ref &soft, model_ref &out_model) { + TRACE("subsume_verb", + tout << "maxsat with model " << hard << " " << soft << "\n";); + SASSERT(is_ground(hard)); + + solver::scoped_push _sp(*m_solver); + m_solver->assert_expr(hard); + + expr_ref_buffer tags(m); + lbool res; + if (is_ground(soft)) { + tags.push_back(m.mk_fresh_const("get_mdl_assump", m.mk_bool_sort())); + m_solver->assert_expr(m.mk_implies(tags.back(), soft)); + } + + res = m_solver->check_sat(tags.size(), tags.data()); + + // best-effort -- flip one of the soft constraints + // in the current use, this is guaranteed to be sat + if (res != l_true && !tags.empty()) { + unsigned sz = tags.size(); + tags.set(sz - 1, m.mk_not(tags[sz - 1])); + res = m_solver->check_sat(tags.size(), tags.data()); + } + + if (res != l_true) { return false; } + + m_solver->get_model(out_model); + return true; +} + +/// Returns false if subsumption is not supported for \p lc +bool lemma_global_generalizer::subsumer::is_handled(const lemma_cluster &lc) { + // check whether all substitutions are to bv_numerals + unsigned sz = 0; + bool bv_clus = contains_bv(m, lc.get_lemmas()[0].get_sub(), sz); + // If there are no BV numerals, cases are handled. + // TODO: put restriction on Arrays, non linear arithmetic etc + if (!bv_clus) return true; + if (!all_same_sz(m, lc.get_lemmas()[0].get_sub(), sz)) { + TRACE("subsume", + tout << "cannot compute cvx cls of different size variables\n";); + return false; + } + return true; +} + +void lemma_global_generalizer::subsumer::setup(const lemma_cluster &lc) { + unsigned n_vars = get_num_vars(lc.get_pattern()); + m_cvx_cls.reset(n_vars); + + m_dim_vars.reset(); + m_dim_vars.reserve(n_vars); + + m_dim_frsh_cnsts.reset(); + m_dim_frsh_cnsts.reserve(n_vars); + + unsigned sz = 0; + if (contains_bv(m, lc.get_lemmas()[0].get_sub(), sz)) { + m_cvx_cls.set_bv(sz); + } + + // create variables and corresponding skolems for each dimension in the + // input space + add_dim_vars(lc); + + // Add all vectors corresponding to the substitutions of lemmas in the + // cluster to convex closure computation + populate_cvx_cls(lc); +} + +/// Add variables introduced by cvx_cls to the list of variables +void lemma_global_generalizer::subsumer::add_cvx_cls_vars() { + for (auto v : m_cvx_cls.get_new_vars()) { + m_dim_vars.push_back(v); + m_dim_frsh_cnsts.push_back( + m.mk_fresh_const("mrg_syn_cvx", v->get_sort())); + } +} + +bool lemma_global_generalizer::subsumer::subsume(const lemma_cluster &lc, + expr_ref_vector &new_post, + app_ref_vector &bindings) { + if (!is_handled(lc)) return false; + + setup(lc); + + // compute convex closure + expr_ref_vector cls(m); + bool is_syntactic = m_cvx_cls.closure(cls); + + CTRACE("subsume_verb", is_syntactic, + tout << "Convex closure introduced new variables. Closure is " + << mk_and(cls) << "\n";); + + // If convex closure introduced new variables, add them to + // m_dim_frsh_cnsts + if (is_syntactic) { + m_st.m_num_syn_cls++; + add_cvx_cls_vars(); + } + + cls.push_back(lc.get_pattern()); + + // Ground syntactic CC by skolemizing variables + expr_ref ground_cls(m); + ground_free_vars(mk_and(cls), ground_cls); + TRACE("subsume_verb", tout << "Rewrote all vars into u_consts\n" + << mk_and(cls) << "\n" + << " into " + << "\n" + << ground_cls << "\n";); + + // Skolemized syntactic cc + expr_ref syn_cls(ground_cls, m); + + // Attempt to eliminate variables introduced by syntactic cc + if (!eliminate_vars(ground_cls, lc, + is_syntactic && contains_ints(m_dim_frsh_cnsts), + bindings)) { + // something failed, bail out + return false; + } + + // at this point ground_cls might be stronger than original syntactic + // closure weaken it + flatten_and(ground_cls, new_post); + return over_approximate(new_post, syn_cls); +} + +/// Eliminate m_dim_frsh_cnsts from \p cvx_cls +/// +/// Uses \p lc to get a model for mbp. +/// \p mlir indicates whether \p cvx_cls contains both ints and reals. +/// all vars that could not be eliminated are skolemized and added to \p +/// bindings +bool lemma_global_generalizer::subsumer::eliminate_vars( + expr_ref &cvx_pattern, const lemma_cluster &lc, bool mlir, + app_ref_vector &out_bindings) { + if (mlir) { + // coerce to real + to_real(cvx_pattern); + // coerce skolem constants to real + to_real_cnsts(); + + TRACE("subsume_verb", + tout << "To real produced " << cvx_pattern << "\n";); + } + + // Get a model to guide MBP. Attempt to get a model that does not satisfy + // any of the cubes in the cluster + + model_ref mdl; + expr_ref neg_cubes(m); + lc.get_conj_lemmas(neg_cubes); + if (!maxsat_with_model(cvx_pattern, neg_cubes, mdl)) { + TRACE("subsume", + tout << "Convex closure is unsat " << cvx_pattern << "\n";); + return false; + } + + SASSERT(mdl.get() != nullptr); + TRACE("subsume", tout << "calling mbp with " << cvx_pattern << " and\n" + << *mdl << "\n";); + + // MBP to eliminate existentially quantified variables + qe_project(m, m_dim_frsh_cnsts, cvx_pattern, *mdl.get(), true, true, + !m_ground_pob); + + TRACE("subsume", tout << "Pattern after mbp of computing cvx cls: " + << cvx_pattern << "\n";); + + if (!m_ground_pob && contains_reals(m_dim_frsh_cnsts)) { + TRACE("subsume", { + tout << "Could not eliminate non-integer variables\n"; + for (auto *e : m_dim_vars) tout << mk_pp(e, m); + tout << "\n"; + }); + return false; + } + + SASSERT(!m_ground_pob || m_dim_frsh_cnsts.empty()); + + if (mlir) { to_int(cvx_pattern); } + + // If not all variables have been eliminated, skolemize and add bindings + // This creates quantified proof obligation that will be handled by QUIC3 + if (!m_dim_frsh_cnsts.empty()) { + SASSERT(!m_ground_pob); + skolemize(cvx_pattern, mdl, out_bindings); + } + + return true; +} + +/// Find a weakening of \p a such that \p b ==> a +/// +/// Returns true on success and sets \p a to the result +bool lemma_global_generalizer::subsumer::over_approximate(expr_ref_vector &a, + const expr_ref b) { + + // B && !(A1 && A2 && A3) is encoded as + // B && ((tag1 && !A1) || (tag2 && !A2) || (tag3 && !A3)) + // iterate and check tags + expr_ref_vector tags(m), tagged_a(m); + std::string tag_prefix = "o"; + for (auto *lit : a) { + // AG: use local constants instead of global mk_fresh_const + tags.push_back(m.mk_fresh_const(symbol(tag_prefix), m.mk_bool_sort())); + tagged_a.push_back(m.mk_implies(tags.back(), lit)); + } + + TRACE("subsume_verb", tout << "weakening " << mk_and(a) + << " to over approximate " << b << "\n";); + solver::scoped_push _sp(*m_solver); + m_solver->assert_expr(b); + m_solver->assert_expr(push_not(mk_and(tagged_a))); + + while (true) { + lbool res = m_solver->check_sat(tags.size(), tags.data()); + if (res == l_false) { + break; + } else if (res == l_undef) { + break; + } + + // flip tags for all satisfied literals of !A + model_ref mdl; + m_solver->get_model(mdl); + + for (unsigned i = 0, sz = a.size(); i < sz; ++i) { + if (!m.is_not(tags.get(i)) && mdl->is_false(a.get(i))) { + tags[i] = m.mk_not(tags.get(i)); + } + } + } + + expr_ref_buffer res(m); + // remove all expressions whose tags are false + for (unsigned i = 0, sz = tags.size(); i < sz; i++) { + if (!m.is_not(tags.get(i))) { res.push_back(a.get(i)); } + } + a.reset(); + a.append(res.size(), res.data()); + + if (a.empty()) { + // could not find an over approximation + TRACE("subsume", + tout << "mbp did not over-approximate convex closure\n";); + m_st.m_num_no_ovr_approx++; + return false; + } + + TRACE("subsume", + tout << "over approximate produced " << mk_and(a) << "\n";); + return true; +} + +/// Attempt to set a conjecture on pob \p n. +/// +/// Done by dropping literal \p lit from +/// post of \p n. \p lvl is level for conjecture pob. \p gas is the gas for +/// the conjecture pob returns true if conjecture is set +bool lemma_global_generalizer::do_conjecture(pob_ref &n, const expr_ref &lit, + unsigned lvl, unsigned gas) { + expr_ref_vector fml_vec(m); + expr_ref n_pob(n->post(), m); + normalize_order(n_pob, n_pob); + fml_vec.push_back(n_pob); + flatten_and(fml_vec); + + expr_ref_vector conj(m); + bool is_filtered = filter_out_lit(fml_vec, lit, conj); + SASSERT(0 < gas && gas < UINT_MAX); + if (conj.empty()) { + // If the pob cannot be abstracted, stop using generalization on + // it + TRACE("global", tout << "stop local generalization on pob " << n_pob + << " id is " << n_pob->get_id() << "\n";); + n->disable_local_gen(); + return false; + } else if (!is_filtered) { + // The literal to be abstracted is not in the pob + TRACE("global", tout << "cannot conjecture on " << n_pob << " with lit " + << lit << "\n";); + n->disable_local_gen(); + m_st.m_num_cant_abs++; + return false; + } + + // There is enough gas to conjecture on pob + n->set_conjecture_pattern(conj); + n->set_expand_bnd(); + n->set_may_pob_lvl(lvl); + n->set_gas(gas); + n->disable_local_gen(); + TRACE("global", tout << "set conjecture " << conj << " at level " + << n->get_may_pob_lvl() << "\n";); + return true; +} + +// Decide global guidance based on lemma +void lemma_global_generalizer::generalize(lemma_ref &lemma) { + // -- pob that the lemma blocks + pob_ref &pob = lemma->get_pob(); + // -- cluster that the lemma belongs to + lemma_cluster *cluster = pob->pt().clstr_match(lemma); + + /// Lemma does not belong to any cluster. return + if (!cluster) return; + + // if the cluster does not have enough gas, stop local generalization + // and return + if (cluster->get_gas() == 0) { + m_st.m_num_cls_ofg++; + pob->disable_local_gen(); + TRACE("global", tout << "stop local generalization on pob " + << mk_pp(pob->post(), m) << " id is " + << pob->post()->get_id() << "\n";); + return; + } + + // -- local cluster that includes the new lemma + lemma_cluster lc(*cluster); + lc.add_lemma(lemma, true); + + const expr_ref &pat = lc.get_pattern(); + + TRACE("global", { + tout << "Global generalization of: " << mk_and(lemma->get_cube()) + << "\n" + << "Cluster pattern: " << pat << "\n" + << "Other lemmas:\n"; + for (const auto &lemma : lc.get_lemmas()) { + tout << mk_and(lemma.get_lemma()->get_cube()) << "\n"; + } + }); + + // Concretize + if (has_nonlinear_var_mul(pat, m)) { + m_st.m_num_non_lin++; + + TRACE("global", + tout << "Found non linear pattern. Marked to concretize \n";); + // not constructing the concrete pob here since we need a model for + // n->post() + pob->set_concretize_pattern(pat); + pob->set_concretize(true); + pob->set_gas(cluster->get_pob_gas()); + cluster->dec_gas(); + return; + } + + // Conjecture + expr_ref lit(m); + if (find_unique_mono_var_lit(pat, lit)) { + // Create a conjecture by dropping literal from pob. + TRACE("global", tout << "Conjecture with pattern " << mk_pp(pat, m) + << " with gas " << cluster->get_gas() << "\n";); + unsigned gas = cluster->get_pob_gas(); + unsigned lvl = cluster->get_min_lvl(); + if (do_conjecture(pob, lit, lvl, gas)) { + // decrease the number of times this cluster is going to be used + // for conjecturing + cluster->dec_gas(); + return; + } + } + + // if subsumption removed all the other lemmas, there is nothing to + // generalize + if (lc.get_size() < 2) return; + + if (!m_do_subsume) return; + // -- new pob that is blocked by generalized lemma + expr_ref_vector new_pob(m); + // -- bindings for free variables of new_pob + // -- subsumer might introduce extra free variables + app_ref_vector bindings(lemma->get_bindings()); + + if (m_subsumer.subsume(lc, new_pob, bindings)) { + pob->set_subsume_pob(new_pob); + pob->set_subsume_bindings(bindings); + pob->set_may_pob_lvl(cluster->get_min_lvl()); + pob->set_gas(cluster->get_pob_gas() + 1); + pob->set_expand_bnd(); + TRACE("global", tout << "subsume pob " << mk_and(new_pob) + << " at level " << cluster->get_min_lvl() + << " set on pob " << mk_pp(pob->post(), m) + << "\n";); + // -- stop local generalization + // -- maybe not the best choice in general. Helped with one instance + // on + // -- our benchmarks + pob->disable_local_gen(); + cluster->dec_gas(); + } +} + +/// Replace bound vars in \p fml with uninterpreted constants +void lemma_global_generalizer::subsumer::ground_free_vars(expr *pat, + expr_ref &out) { + SASSERT(!is_ground(pat)); + expr_safe_replace sub(m); + for (unsigned i = 0; i < m_dim_vars.size(); i++) { + sub.insert(m_dim_vars.get(i), m_dim_frsh_cnsts.get(i)); + } + sub(pat, out); + SASSERT(is_ground(out)); + return; +} + +// convert all LIA constants in m_dim_frsh_cnsts to LRA constants using +// to_real +void lemma_global_generalizer::subsumer::to_real_cnsts() { + for (unsigned i = 0, sz = m_dim_frsh_cnsts.size(); i < sz; i++) { + auto *c = m_dim_frsh_cnsts.get(i); + if (m_arith.is_real(c)) continue; + m_dim_frsh_cnsts.set(i, m_arith.mk_to_real(c)); + } +} + +pob *lemma_global_generalizer::mk_concretize_pob(pob &n, model_ref &model) { + expr_ref_vector new_post(m); + spacer::pob_concretizer proc(m, model, n.get_concretize_pattern()); + if (proc.apply(n.post(), new_post)) { + pob *new_pob = n.pt().mk_pob(n.parent(), n.level(), n.depth(), + mk_and(new_post), n.get_binding()); + + TRACE("concretize", tout << "pob:\n" << mk_pp(n.post(), m) + << " is concretized into:\n" + << mk_pp(new_pob->post(), m) << "\n";); + return new_pob; + } + return nullptr; +} + +pob *lemma_global_generalizer::mk_subsume_pob(pob &n) { + if (n.get_subsume_pob().empty() || n.get_gas() <= 0) return nullptr; + + expr_ref post = mk_and(n.get_subsume_pob()); + pob *f = n.pt().find_pob(n.parent(), post); + if (f && f->is_in_queue()) return nullptr; + + auto level = n.get_may_pob_lvl(); + f = n.pt().mk_pob(n.parent(), level, n.depth(), post, + n.get_subsume_bindings()); + f->set_subsume(); + return f; +} + +pob *lemma_global_generalizer::mk_conjecture_pob(pob &n) { + if (n.get_conjecture_pattern().empty() || n.get_gas() <= 0) return nullptr; + + expr_ref post = mk_and(n.get_conjecture_pattern()); + pob *f = n.pt().find_pob(n.parent(), post); + if (f && f->is_in_queue()) return nullptr; + + auto level = n.get_may_pob_lvl(); + f = n.pt().mk_pob(n.parent(), level, n.depth(), post, {m}); + f->set_conjecture(); + return f; +} + +void lemma_global_generalizer::subsumer::collect_statistics( + statistics &st) const { + st.update("SPACER num no over approximate", m_st.m_num_no_ovr_approx); + st.update("SPACER num sync cvx cls", m_st.m_num_syn_cls); + st.update("SPACER num mbp failed", m_st.m_num_mbp_failed); + m_cvx_cls.collect_statistics(st); +} + +void lemma_global_generalizer::collect_statistics(statistics &st) const { + st.update("time.spacer.solve.reach.gen.global", m_st.watch.get_seconds()); + st.update("SPACER cluster out of gas", m_st.m_num_cls_ofg); + st.update("SPACER num non lin", m_st.m_num_non_lin); + st.update("SPACER num cant abstract", m_st.m_num_cant_abs); +} + +} // namespace spacer diff --git a/src/muz/spacer/spacer_global_generalizer.h b/src/muz/spacer/spacer_global_generalizer.h new file mode 100644 index 00000000000..25ef3567984 --- /dev/null +++ b/src/muz/spacer/spacer_global_generalizer.h @@ -0,0 +1,185 @@ +#pragma once +/*++ +Copyright (c) 2020 Arie Gurfinkel + +Module Name: + + spacer_global_generalizer.h + +Abstract: + + Global Guidance for Spacer + +Author: + + Hari Govind V K + Arie Gurfinkel + + +--*/ + +#include "muz/spacer/spacer_context.h" +#include "muz/spacer/spacer_convex_closure.h" + +namespace spacer { + +/// Global guided generalization +/// +/// See Hari Govind et al. Global Guidance for Local Generalization in Model +/// Checking. CAV 2020 +class lemma_global_generalizer : public lemma_generalizer { + /// Subsumption strategy + class subsumer { + struct stats { + unsigned m_num_syn_cls; + unsigned m_num_mbp_failed; + unsigned m_num_no_ovr_approx; + + stopwatch watch; + stats() { reset(); } + void reset() { + watch.reset(); + m_num_syn_cls = 0; + m_num_mbp_failed = 0; + m_num_no_ovr_approx = 0; + } + }; + stats m_st; + + ast_manager &m; + arith_util m_arith; + bv_util m_bv; + + // convex closure interface + convex_closure m_cvx_cls; + + // save fresh constants for mbp + app_ref_vector m_dim_frsh_cnsts; + + // save vars from cluster pattern + var_ref_vector m_dim_vars; + + // create pob without free vars + bool m_ground_pob; + + // Local solver to get model for computing mbp and to check whether + // cvx_cls ==> mbp + ref m_solver; + + /// Prepare internal state for computing subsumption + void setup(const lemma_cluster &lc); + + /// Returns false if subsumption is not supported for given cluster + bool is_handled(const lemma_cluster &lc); + + /// Find a representative for \p c + expr *find_repr(const model_ref &mdl, const app *c); + + /// Skolemize m_dim_frsh_cnsts in \p f + /// + /// \p cnsts is appended with ground terms from \p mdl + void skolemize(expr_ref &f, const model_ref &mdl, + app_ref_vector &cnsts); + + /// Create new vars to compute convex cls + void add_dim_vars(const lemma_cluster &lc); + + /// Coerce LIA constants in \p m_dim_frsh_cnsts to LRA constants + void to_real_cnsts(); + + /// Populate \p m_cvx_cls + /// + /// 1. Collect all substitutions in the cluster \p lc + /// 2. Convert all substitutions to integer numerals + void populate_cvx_cls(const lemma_cluster &lc); + + /// Make \p fml ground using m_dim_frsh_cnsts. Store result in \p out + void ground_free_vars(expr *fml, expr_ref &out); + + /// Weaken \p a such that (and a) overapproximates \p b + bool over_approximate(expr_ref_vector &a, const expr_ref b); + + /// \p a is a hard constraint and \p b is a soft constraint that have to + /// be satisfied by \p mdl + bool maxsat_with_model(const expr_ref &hard, const expr_ref &soft, + model_ref &out_model); + + /// Eliminate m_dim_frsh_cnsts from \p cvx_cls + /// + /// Uses \p lc to get a model for mbp. + /// \p mlir indicates whether \p cvx_cls contains both ints and reals. + /// all vars that could not be eliminated are skolemized and added to \p + /// bindings + bool eliminate_vars(expr_ref &cvx_cls, const lemma_cluster &lc, + bool mlir, app_ref_vector &bindings); + + /// Add variables introduced by m_cvx_cls to the list of variables to be + /// eliminated + void add_cvx_cls_vars(); + + public: + subsumer(ast_manager &m, bool use_sage, bool ground_pob); + + void collect_statistics(statistics &st) const; + + /// Compute a cube \p res such that \neg p subsumes all the lemmas in \p + /// lc + /// + /// \p cnsts is a set of constants that can be used to make \p res + /// ground + bool subsume(const lemma_cluster &lc, expr_ref_vector &res, + app_ref_vector &cnsts); + }; + + struct stats { + unsigned m_num_cls_ofg; + unsigned m_num_syn_cls; + unsigned m_num_mbp_failed; + unsigned m_num_non_lin; + unsigned m_num_no_ovr_approx; + unsigned m_num_cant_abs; + + stopwatch watch; + stats() { reset(); } + void reset() { + watch.reset(); + m_num_cls_ofg = 0; + m_num_non_lin = 0; + m_num_syn_cls = 0; + m_num_mbp_failed = 0; + m_num_no_ovr_approx = 0; + m_num_cant_abs = 0; + } + }; + stats m_st; + ast_manager &m; + subsumer m_subsumer; + + /// Decide global guidance based on lemma + void generalize(lemma_ref &lemma); + + /// Attempt to set a conjecture on pob \p n. + /// + /// Done by dropping literal \p lit from + /// post of \p n. \p lvl is level for conjecture pob. \p gas is the gas for + /// the conjecture pob returns true if conjecture is set + bool do_conjecture(pob_ref &n, const expr_ref &lit, unsigned lvl, unsigned gas); + + /// Enable/disable subsume rule + bool m_do_subsume; + + public: + lemma_global_generalizer(context &ctx); + ~lemma_global_generalizer() override {} + + void operator()(lemma_ref &lemma) override; + + void collect_statistics(statistics &st) const override; + void reset_statistics() override { m_st.reset(); } + + // post-actions for pobs produced during generalization + pob *mk_concretize_pob(pob &n, model_ref &model); + pob *mk_subsume_pob(pob &n); + pob *mk_conjecture_pob(pob &n); +}; +} // namespace spacer From bd7e6c39b497c85b81147803edef1a03508c5733 Mon Sep 17 00:00:00 2001 From: Arie Gurfinkel Date: Wed, 27 Apr 2022 11:05:02 -0400 Subject: [PATCH 14/78] Remove fp.spacer.print_json option The option is used to dump state of spacer into json for debugging. It has been replaced by `fp.spacer.trace_file` that allows dumping an execution of spacer. The json file can be reconstructed from the trace file elsewhere. --- src/muz/base/fp_params.pyg | 1 - src/muz/spacer/CMakeLists.txt | 1 - src/muz/spacer/spacer_context.cpp | 22 +--- src/muz/spacer/spacer_context.h | 11 -- src/muz/spacer/spacer_json.cpp | 191 ------------------------------ src/muz/spacer/spacer_json.h | 59 --------- 6 files changed, 2 insertions(+), 283 deletions(-) delete mode 100644 src/muz/spacer/spacer_json.cpp delete mode 100644 src/muz/spacer/spacer_json.h diff --git a/src/muz/base/fp_params.pyg b/src/muz/base/fp_params.pyg index 94b146386d6..e965788ed90 100644 --- a/src/muz/base/fp_params.pyg +++ b/src/muz/base/fp_params.pyg @@ -169,7 +169,6 @@ def_module_params('fp', ('spacer.p3.share_lemmas', BOOL, False, 'Share frame lemmas'), ('spacer.p3.share_invariants', BOOL, False, "Share invariants lemmas"), ('spacer.min_level', UINT, 0, 'Minimal level to explore'), - ('spacer.print_json', SYMBOL, '', 'Print pobs tree in JSON format to a given file'), ('spacer.trace_file', SYMBOL, '', 'Log file for progress events'), ('spacer.ctp', BOOL, True, 'Enable counterexample-to-pushing'), ('spacer.use_inc_clause', BOOL, True, 'Use incremental clause to represent trans'), diff --git a/src/muz/spacer/CMakeLists.txt b/src/muz/spacer/CMakeLists.txt index a0b6954b145..048f5d460e1 100644 --- a/src/muz/spacer/CMakeLists.txt +++ b/src/muz/spacer/CMakeLists.txt @@ -27,7 +27,6 @@ z3_add_component(spacer spacer_expand_bnd_generalizer.cpp spacer_cluster.cpp spacer_callback.cpp - spacer_json.cpp spacer_iuc_proof.cpp spacer_mbc.cpp spacer_pdr.cpp diff --git a/src/muz/spacer/spacer_context.cpp b/src/muz/spacer/spacer_context.cpp index 46d90f747e3..a4735ce3dc3 100644 --- a/src/muz/spacer/spacer_context.cpp +++ b/src/muz/spacer/spacer_context.cpp @@ -2286,7 +2286,6 @@ context::context(fp_params const& params, ast_manager& m) : m_expanded_lvl(0), m_global_gen(nullptr), m_expand_bnd_gen(nullptr), - m_json_marshaller(this), m_trace_stream(nullptr) { params_ref p; @@ -3052,9 +3051,7 @@ lbool context::solve_core (unsigned from_lvl) if (check_reachability()) { return l_true; } if (lvl > 0 && m_use_propagate) - if (propagate(m_expanded_lvl, lvl, UINT_MAX)) { dump_json(); return l_false; } - - dump_json(); + if (propagate(m_expanded_lvl, lvl, UINT_MAX)) { return l_false; } if (is_inductive()){ return l_false; @@ -3373,16 +3370,6 @@ bool context::is_reachable(pob &n) return next ? is_reachable(*next) : true; } -void context::dump_json() -{ - if (m_params.spacer_print_json().is_non_empty_string()) { - std::ofstream of; - of.open(m_params.spacer_print_json().bare_str()); - m_json_marshaller.marshal(of); - of.close(); - } -} - void context::predecessor_eh() { for (unsigned i = 0; i < m_callbacks.size(); i++) { @@ -4263,8 +4250,6 @@ void context::add_constraint (expr *c, unsigned level) } void context::new_lemma_eh(pred_transformer &pt, lemma *lem) { - if (m_params.spacer_print_json().is_non_empty_string()) - m_json_marshaller.register_lemma(lem); bool handle=false; for (unsigned i = 0; i < m_callbacks.size(); i++) { handle|=m_callbacks[i]->new_lemma(); @@ -4286,10 +4271,7 @@ void context::new_lemma_eh(pred_transformer &pt, lemma *lem) { } } -void context::new_pob_eh(pob *p) { - if (m_params.spacer_print_json().is_non_empty_string()) - m_json_marshaller.register_pob(p); -} +void context::new_pob_eh(pob *p) { } bool context::is_inductive() { // check that inductive level (F infinity) of the query predicate diff --git a/src/muz/spacer/spacer_context.h b/src/muz/spacer/spacer_context.h index 1b15060401e..763663a49f6 100644 --- a/src/muz/spacer/spacer_context.h +++ b/src/muz/spacer/spacer_context.h @@ -28,7 +28,6 @@ Module Name: #include #include "muz/spacer/spacer_cluster.h" -#include "muz/spacer/spacer_json.h" #include "muz/spacer/spacer_manager.h" #include "muz/spacer/spacer_prop_solver.h" #include "muz/spacer/spacer_sem_matcher.h" @@ -774,8 +773,6 @@ class pob { // lemmas created to block this pob (at any time, not necessarily active) ptr_vector m_lemmas; - // depth -> watch - std::map m_expand_watches; unsigned m_blocked_lvl; // clang-format on // clang-format off @@ -907,16 +904,11 @@ class pob { void get_skolems(app_ref_vector &v); void on_expand() { - m_expand_watches[m_depth].start(); if (m_parent.get()) { m_parent.get()->on_expand(); } } void off_expand() { - m_expand_watches[m_depth].stop(); if (m_parent.get()) { m_parent.get()->off_expand(); } } - double get_expand_time(unsigned depth) { - return m_expand_watches[depth].get_seconds(); - } void inc_ref() { ++m_ref_count; } void dec_ref() { @@ -1215,7 +1207,6 @@ class context { unsigned m_restart_initial_threshold; unsigned m_blast_term_ite_inflation; scoped_ptr_vector m_callbacks; - json_marshaller m_json_marshaller; std::fstream* m_trace_stream; // clang-format on // clang-format off @@ -1276,8 +1267,6 @@ class context { void simplify_formulas(); - void dump_json(); - void predecessor_eh(); void updt_params(); diff --git a/src/muz/spacer/spacer_json.cpp b/src/muz/spacer/spacer_json.cpp deleted file mode 100644 index a99a8f298f4..00000000000 --- a/src/muz/spacer/spacer_json.cpp +++ /dev/null @@ -1,191 +0,0 @@ -/**++ -Copyright (c) 2017 Microsoft Corporation and Matteo Marescotti - -Module Name: - - spacer_json.cpp - -Abstract: - - SPACER json marshalling support - -Author: - - Matteo Marescotti - -Notes: - ---*/ - -#include -#include "spacer_context.h" -#include "spacer_json.h" -#include "spacer_util.h" - -namespace spacer { - -static std::ostream &json_marshal(std::ostream &out, ast *t, ast_manager &m) { - - mk_epp pp = mk_epp(t, m); - std::ostringstream ss; - ss << pp; - out << "\""; - for (auto &c:ss.str()) { - switch (c) { - case '"': - out << "\\\""; - break; - case '\\': - out << "\\\\"; - break; - case '\b': - out << "\\b"; - break; - case '\f': - out << "\\f"; - break; - case '\n': - out << "\\n"; - break; - case '\r': - out << "\\r"; - break; - case '\t': - out << "\\t"; - break; - default: - if ('\x00' <= c && c <= '\x1f') { - out << "\\u" - << std::hex << std::setw(4) << std::setfill('0') << (int) c; - } else { - out << c; - } - } - } - out << "\""; - return out; -} - -static std::ostream &json_marshal(std::ostream &out, lemma *l) { - out << "{" - << R"("init_level":")" << l->init_level() - << R"(", "level":")" << l->level() - << R"(", "expr":)"; - json_marshal(out, l->get_expr(), l->get_ast_manager()); - out << "}"; - return out; -} - -static std::ostream &json_marshal(std::ostream &out, const lemma_ref_vector &lemmas) { - - std::ostringstream ls; - for (auto l:lemmas) { - ls << ((unsigned)ls.tellp() == 0 ? "" : ","); - json_marshal(ls, l); - } - out << "[" << ls.str() << "]"; - return out; - } - - -void json_marshaller::register_lemma(lemma *l) { - if (l->has_pob()) { - m_relations[&*l->get_pob()][l->get_pob()->depth()].push_back(l); - } -} - -void json_marshaller::register_pob(pob *p) { - m_relations[p]; -} - -void json_marshaller::marshal_lemmas_old(std::ostream &out) const { - unsigned pob_id = 0; - for (auto &pob_map:m_relations) { - std::ostringstream pob_lemmas; - for (auto &depth_lemmas : pob_map.second) { - pob_lemmas << ((unsigned)pob_lemmas.tellp() == 0 ? "" : ",") - << "\"" << depth_lemmas.first << "\":"; - json_marshal(pob_lemmas, depth_lemmas.second); - } - if (pob_lemmas.tellp()) { - out << ((unsigned)out.tellp() == 0 ? "" : ",\n"); - out << "\"" << pob_id << "\":{" << pob_lemmas.str() << "}"; - } - pob_id++; - } -} -void json_marshaller::marshal_lemmas_new(std::ostream &out) const { - unsigned pob_id = 0; - for (auto &pob_map:m_relations) { - std::ostringstream pob_lemmas; - pob *n = pob_map.first; - unsigned i = 0; - for (auto *l : n->lemmas()) { - pob_lemmas << ((unsigned)pob_lemmas.tellp() == 0 ? "" : ",") - << "\"" << i++ << "\":"; - lemma_ref_vector lemmas_vec; - lemmas_vec.push_back(l); - json_marshal(pob_lemmas, lemmas_vec); - } - - if (pob_lemmas.tellp()) { - out << ((unsigned)out.tellp() == 0 ? "" : ",\n"); - out << "\"" << pob_id << "\":{" << pob_lemmas.str() << "}"; - } - pob_id++; - } -} - -std::ostream &json_marshaller::marshal(std::ostream &out) const { - std::ostringstream nodes; - std::ostringstream edges; - std::ostringstream lemmas; - - if (m_old_style) - marshal_lemmas_old(lemmas); - else - marshal_lemmas_new(lemmas); - - unsigned pob_id = 0; - unsigned depth = 0; - while (true) { - double root_expand_time = m_ctx->get_root().get_expand_time(depth); - bool a = false; - pob_id = 0; - for (auto &pob_map:m_relations) { - pob *n = pob_map.first; - double expand_time = n->get_expand_time(depth); - if (expand_time > 0) { - a = true; - std::ostringstream pob_expr; - json_marshal(pob_expr, n->post(), n->get_ast_manager()); - - nodes << ((unsigned)nodes.tellp() == 0 ? "" : ",\n") << - "{\"id\":\"" << depth << n << - "\",\"relative_time\":\"" << expand_time / root_expand_time << - "\",\"absolute_time\":\"" << std::setprecision(2) << expand_time << - "\",\"predicate\":\"" << n->pt().head()->get_name() << - "\",\"expr_id\":\"" << n->post()->get_id() << - "\",\"pob_id\":\"" << pob_id << - "\",\"depth\":\"" << depth << - "\",\"expr\":" << pob_expr.str() << "}"; - if (n->parent()) { - edges << ((unsigned)edges.tellp() == 0 ? "" : ",\n") << - "{\"from\":\"" << depth << n->parent() << - "\",\"to\":\"" << depth << n << "\"}"; - } - } - pob_id++; - } - if (!a) { - break; - } - depth++; - } - out << "{\n\"nodes\":[\n" << nodes.str() << "\n],\n"; - out << "\"edges\":[\n" << edges.str() << "\n],\n"; - out << "\"lemmas\":{\n" << lemmas.str() << "\n}\n}\n"; - return out; -} - -} diff --git a/src/muz/spacer/spacer_json.h b/src/muz/spacer/spacer_json.h deleted file mode 100644 index bb330cc039c..00000000000 --- a/src/muz/spacer/spacer_json.h +++ /dev/null @@ -1,59 +0,0 @@ -/**++ -Copyright (c) 2017 Microsoft Corporation and Matteo Marescotti - -Module Name: - - spacer_json.h - -Abstract: - - SPACER json marshalling support - -Author: - - Matteo Marescotti - -Notes: - ---*/ - -#pragma once - -#include -#include -#include "util/ref.h" -#include "util/ref_vector.h" - -class ast; - -class ast_manager; - -namespace spacer { - -class lemma; -typedef sref_vector lemma_ref_vector; -class context; -class pob; - - -class json_marshaller { - context *m_ctx; - bool m_old_style; - std::map> m_relations; - - void marshal_lemmas_old(std::ostream &out) const; - void marshal_lemmas_new(std::ostream &out) const; -public: - json_marshaller(context *ctx, bool old_style = false) : - m_ctx(ctx), m_old_style(old_style) {} - - void register_lemma(lemma *l); - - void register_pob(pob *p); - - std::ostream &marshal(std::ostream &out) const; -}; - -} - - From e8bb6f99834dbcbd10841603a11059a89c74180f Mon Sep 17 00:00:00 2001 From: hgvk94 Date: Fri, 12 Feb 2021 16:34:38 -0500 Subject: [PATCH 15/78] Workaround for segfault in spacer_proof_utils Issue #3 in hgvk94/z3 Segfault in some proof reduction. Avoid by bailing out on reduction. --- src/muz/spacer/spacer_proof_utils.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/muz/spacer/spacer_proof_utils.cpp b/src/muz/spacer/spacer_proof_utils.cpp index a3ebb028a86..800a6329160 100644 --- a/src/muz/spacer/spacer_proof_utils.cpp +++ b/src/muz/spacer/spacer_proof_utils.cpp @@ -284,6 +284,11 @@ namespace spacer { ptr_buffer const &parents, unsigned num_params, parameter const *params) { + if(num_params != parents.size() + 1) { + //TODO: fix bug + TRACE("spacer.fkab", tout << "UNEXPECTED INPUT TO FUNCTION. Bailing out\n";); + return proof_ref(m); + } SASSERT(num_params == parents.size() + 1 /* one param is missing */); arith_util a(m); th_rewriter rw(m); From c3cb7d59ce4ae27ee31512e8daa5a0722408250f Mon Sep 17 00:00:00 2001 From: Arie Gurfinkel Date: Thu, 28 Apr 2022 13:28:41 -0400 Subject: [PATCH 16/78] Revert bug for incomplete models --- src/muz/spacer/spacer_context.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/muz/spacer/spacer_context.cpp b/src/muz/spacer/spacer_context.cpp index a4735ce3dc3..cb1e41bf100 100644 --- a/src/muz/spacer/spacer_context.cpp +++ b/src/muz/spacer/spacer_context.cpp @@ -1204,6 +1204,7 @@ expr_ref pred_transformer::get_origin_summary (model &mdl, for (auto* s : summary) { if (!is_quantifier(s) && !mdl.is_true(s)) { TRACE("spacer", tout << "Summary not true in the model: " << mk_pp(s, m) << "\n";); + return expr_ref(m); } } From 62c2158ad613e44a0dbeb49bd5131567932707b9 Mon Sep 17 00:00:00 2001 From: Arie Gurfinkel Date: Mon, 9 May 2022 15:31:18 -0700 Subject: [PATCH 17/78] Use local fresh variables in spacer_global_generalizer --- src/muz/spacer/spacer_global_generalizer.cpp | 29 ++++++++++++++++---- src/muz/spacer/spacer_global_generalizer.h | 11 +++++++- 2 files changed, 33 insertions(+), 7 deletions(-) diff --git a/src/muz/spacer/spacer_global_generalizer.cpp b/src/muz/spacer/spacer_global_generalizer.cpp index 73fea662e02..41a03f3fd1d 100644 --- a/src/muz/spacer/spacer_global_generalizer.cpp +++ b/src/muz/spacer/spacer_global_generalizer.cpp @@ -329,14 +329,28 @@ void to_real(expr_ref &fml) { namespace spacer { lemma_global_generalizer::subsumer::subsumer(ast_manager &a_m, bool use_sage, bool ground_pob) - : m(a_m), m_arith(m), m_bv(m), m_cvx_cls(m, use_sage), m_dim_frsh_cnsts(m), - m_dim_vars(m), m_ground_pob(ground_pob) { + : m(a_m), m_arith(m), m_bv(m), m_tags(m), m_used_tags(0), + m_cvx_cls(m, use_sage), m_dim_frsh_cnsts(m), m_dim_vars(m), + m_ground_pob(ground_pob) { scoped_ptr factory( mk_smt_strategic_solver_factory(symbol::null)); m_solver = (*factory)(m, params_ref::get_empty(), false, true, false, symbol::null); } +app *lemma_global_generalizer::subsumer::mk_fresh_tag() { + if (m_used_tags == m_tags.size()) { + auto *bool_sort = m.mk_bool_sort(); + // -- create 4 new tags + m_tags.push_back(m.mk_fresh_const(symbol("t"), bool_sort)); + m_tags.push_back(m.mk_fresh_const(symbol("t"), bool_sort)); + m_tags.push_back(m.mk_fresh_const(symbol("t"), bool_sort)); + m_tags.push_back(m.mk_fresh_const(symbol("t"), bool_sort)); + } + + return m_tags.get(m_used_tags++); +} + lemma_global_generalizer::lemma_global_generalizer(context &ctx) : lemma_generalizer(ctx), m(ctx.get_ast_manager()), m_subsumer(m, ctx.use_sage(), ctx.use_ground_pob()), @@ -474,7 +488,7 @@ bool lemma_global_generalizer::subsumer::maxsat_with_model( expr_ref_buffer tags(m); lbool res; if (is_ground(soft)) { - tags.push_back(m.mk_fresh_const("get_mdl_assump", m.mk_bool_sort())); + tags.push_back(mk_fresh_tag()); m_solver->assert_expr(m.mk_implies(tags.back(), soft)); } @@ -511,6 +525,9 @@ bool lemma_global_generalizer::subsumer::is_handled(const lemma_cluster &lc) { } void lemma_global_generalizer::subsumer::setup(const lemma_cluster &lc) { + + m_used_tags = 0; + unsigned n_vars = get_num_vars(lc.get_pattern()); m_cvx_cls.reset(n_vars); @@ -670,8 +687,7 @@ bool lemma_global_generalizer::subsumer::over_approximate(expr_ref_vector &a, expr_ref_vector tags(m), tagged_a(m); std::string tag_prefix = "o"; for (auto *lit : a) { - // AG: use local constants instead of global mk_fresh_const - tags.push_back(m.mk_fresh_const(symbol(tag_prefix), m.mk_bool_sort())); + tags.push_back(mk_fresh_tag()); tagged_a.push_back(m.mk_implies(tags.back(), lit)); } @@ -892,7 +908,8 @@ pob *lemma_global_generalizer::mk_concretize_pob(pob &n, model_ref &model) { pob *new_pob = n.pt().mk_pob(n.parent(), n.level(), n.depth(), mk_and(new_post), n.get_binding()); - TRACE("concretize", tout << "pob:\n" << mk_pp(n.post(), m) + TRACE("concretize", tout << "pob:\n" + << mk_pp(n.post(), m) << " is concretized into:\n" << mk_pp(new_pob->post(), m) << "\n";); return new_pob; diff --git a/src/muz/spacer/spacer_global_generalizer.h b/src/muz/spacer/spacer_global_generalizer.h index 25ef3567984..f05e3ffc3cc 100644 --- a/src/muz/spacer/spacer_global_generalizer.h +++ b/src/muz/spacer/spacer_global_generalizer.h @@ -50,6 +50,11 @@ class lemma_global_generalizer : public lemma_generalizer { arith_util m_arith; bv_util m_bv; + // boolean variables used as local tags + app_ref_vector m_tags; + // number of tags currently used + unsigned m_used_tags; + // convex closure interface convex_closure m_cvx_cls; @@ -66,6 +71,9 @@ class lemma_global_generalizer : public lemma_generalizer { // cvx_cls ==> mbp ref m_solver; + /// Return a fresh boolean variable + app *mk_fresh_tag(); + /// Prepare internal state for computing subsumption void setup(const lemma_cluster &lc); @@ -163,7 +171,8 @@ class lemma_global_generalizer : public lemma_generalizer { /// Done by dropping literal \p lit from /// post of \p n. \p lvl is level for conjecture pob. \p gas is the gas for /// the conjecture pob returns true if conjecture is set - bool do_conjecture(pob_ref &n, const expr_ref &lit, unsigned lvl, unsigned gas); + bool do_conjecture(pob_ref &n, const expr_ref &lit, unsigned lvl, + unsigned gas); /// Enable/disable subsume rule bool m_do_subsume; From c873a85f4c8c05f84a9637e0f27e66288e5e5566 Mon Sep 17 00:00:00 2001 From: Arie Gurfinkel Date: Tue, 10 May 2022 20:40:33 -0700 Subject: [PATCH 18/78] Cleanup of spacer_convex_closure --- src/muz/spacer/spacer_convex_closure.cpp | 133 +++++++++++-------- src/muz/spacer/spacer_convex_closure.h | 48 +++---- src/muz/spacer/spacer_global_generalizer.cpp | 2 +- 3 files changed, 101 insertions(+), 82 deletions(-) diff --git a/src/muz/spacer/spacer_convex_closure.cpp b/src/muz/spacer/spacer_convex_closure.cpp index b666f13670c..7bb1bdc26ae 100644 --- a/src/muz/spacer/spacer_convex_closure.cpp +++ b/src/muz/spacer/spacer_convex_closure.cpp @@ -63,10 +63,10 @@ namespace spacer { void convex_closure::reset(unsigned n_cols) { m_kernel.reset(); m_data.reset(n_cols); - m_dim_vars.reset(); + m_col_vars.reset(); m_dim = n_cols; - m_dim_vars.reserve(m_dim); - m_new_vars.reset(); + m_col_vars.reserve(m_dim); + m_alphas.reset(); m_bv_sz = 0; m_enable_syntactic_cc = true; } @@ -102,21 +102,20 @@ unsigned convex_closure::reduce_dim() { // For row \p row in m_kernel, construct the equality: // -// row * m_dim_vars = 0 +// row * m_col_vars = 0 // -// In the equality, exactly one variable from m_dim_vars is on the lhs -void convex_closure::generate_equality_for_row(const vector &row, - expr_ref &out) { +// In the equality, exactly one variable from m_col_vars is on the lhs +void convex_closure::mk_row_eq(const vector &row, expr_ref &out) { // contains the right hand side of an equality expr_ref_buffer rhs(m); // index of first non zero element in row int pv = -1; // are we constructing rhs or lhs bool is_lhs = true; - // coefficient of m_dim_vars[pv] + // coefficient of m_col_vars[pv] rational coeff(1); - // the elements in row are the coefficients of m_dim_vars + // the elements in row are the coefficients of m_col_vars // some elements should go to the rhs, in which case the signs are // changed for (unsigned j = 0, sz = row.size(); j < sz; j++) { @@ -134,18 +133,19 @@ void convex_closure::generate_equality_for_row(const vector &row, } else { expr_ref prod(m); if (j != row.size() - 1) { - prod = m_dim_vars.get(j); + prod = m_col_vars.get(j); mul_by_rat(prod, -1 * val * m_lcm); } else { - if (m_arith.is_int(m_dim_vars.get(pv))) { - prod = m_arith.mk_int(-1 * val); - } else if (m_arith.is_real(m_dim_vars.get(pv))) { - prod = m_arith.mk_real(-1 * val); - } else if (m_bv.is_bv(m_dim_vars.get(pv))) { + auto *col_v = m_col_vars.get(pv); + if (m_arith.is_int_real(col_v)) { + prod = m_arith.mk_numeral(-1 * val, m_arith.is_int(col_v)); + } else if (m_bv.is_bv(col_v)) { prod = m_bv.mk_numeral(-1 * val, m_bv_sz); + } else { + SASSERT(false); } } - SASSERT(prod.get()); + SASSERT(prod); rhs.push_back(prod); } } @@ -155,33 +155,33 @@ void convex_closure::generate_equality_for_row(const vector &row, if (rhs.size() == 0) { expr_ref _rhs(m); - if (m_arith.is_int(m_dim_vars.get(pv))) - _rhs = m_arith.mk_int(rational::zero()); - else if (m_arith.is_real(m_dim_vars.get(pv))) - _rhs = m_arith.mk_real(rational::zero()); - else if (m_bv.is_bv(m_dim_vars.get(pv))) + auto *col_var = m_col_vars.get(pv); + if (m_arith.is_int_real(col_var)) + _rhs = + m_arith.mk_numeral(rational::zero(), m_arith.is_int(col_var)); + else if (m_bv.is_bv(col_var)) _rhs = m_bv.mk_numeral(rational::zero(), m_bv_sz); - out = m_arith.mk_eq(m_dim_vars.get(pv), _rhs); + out = m_arith.mk_eq(col_var, _rhs); return; } out = m_is_arith ? m_arith.mk_add(rhs.size(), rhs.data()) : mk_bvadd(m, rhs.size(), rhs.data()); expr_ref pv_var(m); - pv_var = m_dim_vars.get(pv); + pv_var = m_col_vars.get(pv); mul_by_rat(pv_var, coeff * m_lcm); out = m.mk_eq(pv_var, out); - TRACE("cvx_dbg", tout << "rewrote " << mk_pp(m_dim_vars.get(pv), m) + TRACE("cvx_dbg", tout << "rewrote " << mk_pp(m_col_vars.get(pv), m) << " into " << out << "\n";); } /// Generates linear equalities implied by m_data /// -/// the linear equalities are m_kernel * m_dim_vars = 0 (where * is matrix -/// multiplication) the new equalities are stored in m_dim_vars for each row [0, +/// the linear equalities are m_kernel * m_col_vars = 0 (where * is matrix +/// multiplication) the new equalities are stored in m_col_vars for each row [0, /// 1, 0, 1 , 1] in m_kernel, the equality m_lcm*v1 = -1*m_lcm*v3 + -1*1 is -/// constructed and stored at index 1 of m_dim_vars +/// constructed and stored at index 1 of m_col_vars void convex_closure::generate_implied_equalities(expr_ref_vector &out) { // assume kernel has been computed already const spacer_matrix &kern = m_kernel.get_kernel(); @@ -190,50 +190,69 @@ void convex_closure::generate_implied_equalities(expr_ref_vector &out) { expr_ref eq(m); for (unsigned i = kern.num_rows(); i > 0; i--) { auto &row = kern.get_row(i - 1); - generate_equality_for_row(row, eq); + mk_row_eq(row, eq); out.push_back(eq); } } -/// Construct the equality ((m_new_vars . m_data[*][i]) = m_dim_vars[i]) +/// Construct the equality ((m_alphas . m_data[*][i]) = m_col_vars[i]) /// /// Where . is the dot product, m_data[*][i] is /// the ith column of m_data. Add the result to res_vec. -void convex_closure::add_sum_cnstr(unsigned i, expr_ref_vector &out) { +void convex_closure::mk_col_sum(unsigned col, expr_ref_vector &out) { + SASSERT(m_is_arith); + expr_ref_buffer sum(m); - expr_ref prod(m), v(m); - for (unsigned j = 0, sz = m_new_vars.size(); j < sz; j++) { - prod = m_new_vars.get(j); - mul_by_rat(prod, m_data.get(j, i)); - sum.push_back(prod); + for (unsigned row = 0, sz = m_alphas.size(); row < sz; row++) { + expr_ref alpha(m); + auto n = m_data.get(row, col); + if (n.is_zero()) { + ; // noop + } else { + alpha = m_alphas.get(row); + if (!n.is_one()) { + alpha = m_arith.mk_mul( + m_arith.mk_numeral(n, false /* is_int */), alpha); + } + } + if (alpha) sum.push_back(alpha); } - v = m_arith.mk_to_real(m_dim_vars.get(i)); - mul_by_rat(v, m_lcm); - if (m_is_arith) - out.push_back(m.mk_eq(m_arith.mk_add(sum.size(), sum.data()), v)); - else - out.push_back(m.mk_eq(mk_bvadd(m, sum.size(), sum.data()), v)); + SASSERT(!sum.empty()); + expr_ref s(m); + if (sum.size() == 1) { + s = sum[0]; + } else if (sum.size() > 1) { + s = m_arith.mk_add(sum.size(), sum.data()); + } + + expr_ref v(m); + expr *vi = m_col_vars.get(col); + v = m_arith.is_int(vi) ? m_arith.mk_to_real(vi) : vi; + if (!m_lcm.is_one()) { + v = m_arith.mk_mul(m_arith.mk_numeral(m_lcm, false /* is_int */), v); + } + + out.push_back(m.mk_eq(s, v)); } void convex_closure::syntactic_convex_closure(expr_ref_vector &out) { - for (unsigned i = 0; i < m_data.num_rows(); i++) { - var *v = m.mk_var(i + dims(), m_arith.mk_real()); - m_new_vars.push_back(v); + sort_ref real_sort(m_arith.mk_real(), m); + for (unsigned row = 0; row < m_data.num_rows(); row++) { + m_alphas.push_back(m.mk_var(dims() + row, real_sort)); } + expr_ref zero(m_arith.mk_real(rational::zero()), m); // forall j :: m_new_vars[j] >= 0 - for (auto v : m_new_vars) { - out.push_back(m_arith.mk_ge(v, m_arith.mk_real(rational::zero()))); - } + for (auto v : m_alphas) { out.push_back(m_arith.mk_ge(v, zero)); } - for (unsigned i = 0, sz = m_dim_vars.size(); i < sz; i++) { - if (is_var(m_dim_vars.get(i))) add_sum_cnstr(i, out); + for (unsigned k = 0, sz = m_col_vars.size(); k < sz; k++) { + if (is_var(m_col_vars.get(k))) mk_col_sum(k, out); } //(\Sum j . m_new_vars[j]) = 1 out.push_back(m.mk_eq( - m_arith.mk_add(m_new_vars.size(), - reinterpret_cast(m_new_vars.data())), + m_arith.mk_add(m_alphas.size(), + reinterpret_cast(m_alphas.data())), m_arith.mk_real(rational::one()))); } @@ -242,8 +261,8 @@ void convex_closure::syntactic_convex_closure(expr_ref_vector &out) { // corresponding d // TODO: find the largest divisor, not the smallest. // TODO: improve efficiency -bool convex_closure::compute_div_constraint(const vector &data, - rational &m, rational &d) { +bool convex_closure::generate_div_constraint(const vector &data, + rational &m, rational &d) { TRACE("cvx_dbg_verb", { tout << "computing div constraints for "; for (rational r : data) tout << r << " "; @@ -282,7 +301,7 @@ bool convex_closure::closure(expr_ref_vector &out) { unsigned red_dim = reduce_dim(); // store dim var before rewrite - expr_ref var(m_dim_vars.get(0), m); + expr_ref var(m_col_vars.get(0), m); if (red_dim < dims()) { m_st.m_num_reductions++; generate_implied_equalities(out); @@ -296,7 +315,7 @@ bool convex_closure::closure(expr_ref_vector &out) { // there is no alternative to syntactic convex closure right now // syntactic convex closure does not support BV if (m_enable_syntactic_cc) { - SASSERT(m_new_vars.size() == 0); + SASSERT(m_alphas.size() == 0); TRACE("subsume", tout << "Computing syntactic convex closure\n";); syntactic_convex_closure(out); } else { @@ -350,12 +369,12 @@ void convex_closure::do_1dim_convex_closure(const expr_ref &var, rational cr, off; // add div constraints for all variables. for (unsigned j = 0; j < m_data.num_cols(); j++) { - auto *v = m_dim_vars.get(j); + auto *v = m_col_vars.get(j); if (is_var(v) && (m_arith.is_int(v) || m_bv.is_bv(v))) { data.reset(); m_data.get_col(j, data); std::sort(data.begin(), data.end(), gt_proc); - if (compute_div_constraint(data, cr, off)) { + if (generate_div_constraint(data, cr, off)) { res = v; mul_by_rat(res, m_lcm); if (m_is_arith) { diff --git a/src/muz/spacer/spacer_convex_closure.h b/src/muz/spacer/spacer_convex_closure.h index b543f990df6..53da3482c5e 100644 --- a/src/muz/spacer/spacer_convex_closure.h +++ b/src/muz/spacer/spacer_convex_closure.h @@ -48,24 +48,24 @@ class convex_closure { arith_util m_arith; bv_util m_bv; - // size of all bit vectors in m_dim_vars + // size of all bit vectors in m_col_vars unsigned m_bv_sz; // Compute syntactic convex closure bool m_enable_syntactic_cc; - // true if \p m_dim_vars are arithmetic sort (i.e., Real or Int) + // true if \p m_col_vars are arithmetic sort (i.e., Real or Int) bool m_is_arith; - // size of \p m_dim_vars + // number of columns in \p m_data unsigned m_dim; // A vector of rational valued points spacer_matrix m_data; - // Variables naming dimensions in `m_data` - // \p m_dim_vars[i] is variable naming dimension \p i - var_ref_vector m_dim_vars; + // Variables naming columns in `m_data` + // \p m_col_vars[k] is a var for column \p k + var_ref_vector m_col_vars; // Kernel of \p m_data // Set at the end of computation @@ -73,7 +73,7 @@ class convex_closure { // Free variables introduced by syntactic convex closure // These variables are always of sort Real - var_ref_vector m_new_vars; + var_ref_vector m_alphas; // m_lcm is a hack to allow convex_closure computation of rational matrices // as well. Let A be a real matrix. m_lcm is the lcm of all denominators in @@ -87,24 +87,24 @@ class convex_closure { /// Constructs an equality corresponding to a given row in the kernel /// /// The equality is conceptually corresponds to - /// row * m_dim_vars = 0 - /// where row is a row vector and m_dim_vars is a column vector. + /// row * m_col_vars = 0 + /// where row is a row vector and m_col_vars is a column vector. /// However, the equality is put in a form so that exactly one variable from - /// \p m_dim_vars is on the LHS - void generate_equality_for_row(const vector &row, expr_ref &out); + /// \p m_col_vars is on the LHS + void mk_row_eq(const vector &row, expr_ref &out); /// Construct all linear equations implied by points in \p m_data - /// This is defined by \p m_kernel * m_dim_vars = 0 + /// This is defined by \p m_kernel * m_col_vars = 0 void generate_implied_equalities(expr_ref_vector &out); /// Compute syntactic convex closure of \p m_data void syntactic_convex_closure(expr_ref_vector &out); - /// Construct the equality ((m_nw_vars . m_data[*][j]) = m_dim_vars[j]) + /// Construct the equality ((m_alphas . m_data[*][k]) = m_col_vars[k]) /// - /// \p m_data[*][j] is the jth column of m_data + /// \p m_data[*][k] is the kth column of m_data /// The equality is added to \p out. - void add_sum_cnstr(unsigned j, expr_ref_vector &out); + void mk_col_sum(unsigned k, expr_ref_vector &out); /// Compute one dimensional convex closure over \p var /// @@ -116,17 +116,17 @@ class convex_closure { /// /// Finds the largest numbers \p m, \p d such that \p m_data[i] mod m = d /// Returns true if successful - bool compute_div_constraint(const vector &data, rational &m, - rational &d); + bool generate_div_constraint(const vector &data, rational &m, + rational &d); /// Constructs a formula \p var ~ bnd, where ~ = is_le ? <= : >= expr *mk_ineq(expr_ref var, rational bnd, bool is_le); public: convex_closure(ast_manager &manager, bool use_sage) - : m(manager), m_arith(m), m_bv(m), m_bv_sz(0), m_enable_syntactic_cc(true), - m_is_arith(true), m_dim(0), m_data(0, 0), m_dim_vars(m), - m_kernel(m_data), m_new_vars(m) { + : m(manager), m_arith(m), m_bv(m), m_bv_sz(0), + m_enable_syntactic_cc(true), m_is_arith(true), m_dim(0), m_data(0, 0), + m_col_vars(m), m_kernel(m_data), m_alphas(m) { if (use_sage) m_kernel.set_plugin(mk_sage_plugin()); } @@ -147,17 +147,17 @@ class convex_closure { } /// \brief Name dimension \p i with a variable \p v. - void set_dimension(unsigned i, var *v) { + void set_col_var(unsigned i, var *v) { SASSERT(i < dims()); - SASSERT(m_dim_vars[i] == nullptr); - m_dim_vars[i] = v; + SASSERT(m_col_vars[i] == nullptr); + m_col_vars[i] = v; } /// \brief Return number of dimensions of each point unsigned dims() const { return m_dim; } /// \brief Return variables introduced by the syntactic convex closure - const var_ref_vector &get_new_vars() const { return m_new_vars; } + const var_ref_vector &get_new_vars() const { return m_alphas; } /// \brief Add a one-dimensional point to convex closure void push_back(rational x) { diff --git a/src/muz/spacer/spacer_global_generalizer.cpp b/src/muz/spacer/spacer_global_generalizer.cpp index 41a03f3fd1d..2b154156f99 100644 --- a/src/muz/spacer/spacer_global_generalizer.cpp +++ b/src/muz/spacer/spacer_global_generalizer.cpp @@ -378,7 +378,7 @@ void lemma_global_generalizer::subsumer::add_dim_vars(const lemma_cluster &lc) { // create a variable for jth dimension, and register with convex closure var *var = m.mk_var(v.first, sort); m_dim_vars[j] = var; - m_cvx_cls.set_dimension(j, var); + m_cvx_cls.set_col_var(j, var); // create a fresh skolem constant for the jth variable m_dim_frsh_cnsts[j] = m.mk_fresh_const("mrg_cvx", sort); From 6791848d4e9e2164bf411e029173646c1d2ed22c Mon Sep 17 00:00:00 2001 From: Arie Gurfinkel Date: Tue, 10 May 2022 22:03:37 -0700 Subject: [PATCH 19/78] Allow arbitrary expressions to name cols in convex_closure --- src/muz/spacer/spacer_convex_closure.h | 8 ++++---- src/muz/spacer/spacer_global_generalizer.cpp | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/muz/spacer/spacer_convex_closure.h b/src/muz/spacer/spacer_convex_closure.h index 53da3482c5e..d0998cd5795 100644 --- a/src/muz/spacer/spacer_convex_closure.h +++ b/src/muz/spacer/spacer_convex_closure.h @@ -65,7 +65,7 @@ class convex_closure { // Variables naming columns in `m_data` // \p m_col_vars[k] is a var for column \p k - var_ref_vector m_col_vars; + expr_ref_vector m_col_vars; // Kernel of \p m_data // Set at the end of computation @@ -73,7 +73,7 @@ class convex_closure { // Free variables introduced by syntactic convex closure // These variables are always of sort Real - var_ref_vector m_alphas; + expr_ref_vector m_alphas; // m_lcm is a hack to allow convex_closure computation of rational matrices // as well. Let A be a real matrix. m_lcm is the lcm of all denominators in @@ -147,7 +147,7 @@ class convex_closure { } /// \brief Name dimension \p i with a variable \p v. - void set_col_var(unsigned i, var *v) { + void set_col_var(unsigned i, expr *v) { SASSERT(i < dims()); SASSERT(m_col_vars[i] == nullptr); m_col_vars[i] = v; @@ -157,7 +157,7 @@ class convex_closure { unsigned dims() const { return m_dim; } /// \brief Return variables introduced by the syntactic convex closure - const var_ref_vector &get_new_vars() const { return m_alphas; } + const expr_ref_vector &get_alphas() const { return m_alphas; } /// \brief Add a one-dimensional point to convex closure void push_back(rational x) { diff --git a/src/muz/spacer/spacer_global_generalizer.cpp b/src/muz/spacer/spacer_global_generalizer.cpp index 2b154156f99..d0e819949a3 100644 --- a/src/muz/spacer/spacer_global_generalizer.cpp +++ b/src/muz/spacer/spacer_global_generalizer.cpp @@ -553,8 +553,9 @@ void lemma_global_generalizer::subsumer::setup(const lemma_cluster &lc) { /// Add variables introduced by cvx_cls to the list of variables void lemma_global_generalizer::subsumer::add_cvx_cls_vars() { - for (auto v : m_cvx_cls.get_new_vars()) { - m_dim_vars.push_back(v); + for (auto v : m_cvx_cls.get_alphas()) { + SASSERT(is_var(v)); + m_dim_vars.push_back(to_var(v)); m_dim_frsh_cnsts.push_back( m.mk_fresh_const("mrg_syn_cvx", v->get_sort())); } @@ -896,8 +897,7 @@ void lemma_global_generalizer::subsumer::ground_free_vars(expr *pat, void lemma_global_generalizer::subsumer::to_real_cnsts() { for (unsigned i = 0, sz = m_dim_frsh_cnsts.size(); i < sz; i++) { auto *c = m_dim_frsh_cnsts.get(i); - if (m_arith.is_real(c)) continue; - m_dim_frsh_cnsts.set(i, m_arith.mk_to_real(c)); + if (!m_arith.is_real(c)) m_dim_frsh_cnsts.set(i, m_arith.mk_to_real(c)); } } From 98efe6e4a76bd9303b99deadf03bd13632e49612 Mon Sep 17 00:00:00 2001 From: Arie Gurfinkel Date: Wed, 11 May 2022 17:05:46 -0700 Subject: [PATCH 20/78] WIP: convex closure --- src/muz/spacer/spacer_convex_closure.cpp | 54 ++- src/muz/spacer/spacer_convex_closure.h | 50 ++- src/muz/spacer/spacer_global_generalizer.cpp | 374 ++++++++++++------- src/muz/spacer/spacer_global_generalizer.h | 37 +- 4 files changed, 337 insertions(+), 178 deletions(-) diff --git a/src/muz/spacer/spacer_convex_closure.cpp b/src/muz/spacer/spacer_convex_closure.cpp index 7bb1bdc26ae..87adcf4ad66 100644 --- a/src/muz/spacer/spacer_convex_closure.cpp +++ b/src/muz/spacer/spacer_convex_closure.cpp @@ -134,7 +134,7 @@ void convex_closure::mk_row_eq(const vector &row, expr_ref &out) { expr_ref prod(m); if (j != row.size() - 1) { prod = m_col_vars.get(j); - mul_by_rat(prod, -1 * val * m_lcm); + mul_by_rat(prod, -1 * val); } else { auto *col_v = m_col_vars.get(pv); if (m_arith.is_int_real(col_v)) { @@ -169,7 +169,7 @@ void convex_closure::mk_row_eq(const vector &row, expr_ref &out) { : mk_bvadd(m, rhs.size(), rhs.data()); expr_ref pv_var(m); pv_var = m_col_vars.get(pv); - mul_by_rat(pv_var, coeff * m_lcm); + mul_by_rat(pv_var, coeff); out = m.mk_eq(pv_var, out); TRACE("cvx_dbg", tout << "rewrote " << mk_pp(m_col_vars.get(pv), m) @@ -180,7 +180,7 @@ void convex_closure::mk_row_eq(const vector &row, expr_ref &out) { /// /// the linear equalities are m_kernel * m_col_vars = 0 (where * is matrix /// multiplication) the new equalities are stored in m_col_vars for each row [0, -/// 1, 0, 1 , 1] in m_kernel, the equality m_lcm*v1 = -1*m_lcm*v3 + -1*1 is +/// 1, 0, 1 , 1] in m_kernel, the equality v1 = -1*v3 + -1*1 is /// constructed and stored at index 1 of m_col_vars void convex_closure::generate_implied_equalities(expr_ref_vector &out) { // assume kernel has been computed already @@ -228,17 +228,13 @@ void convex_closure::mk_col_sum(unsigned col, expr_ref_vector &out) { expr_ref v(m); expr *vi = m_col_vars.get(col); v = m_arith.is_int(vi) ? m_arith.mk_to_real(vi) : vi; - if (!m_lcm.is_one()) { - v = m_arith.mk_mul(m_arith.mk_numeral(m_lcm, false /* is_int */), v); - } - out.push_back(m.mk_eq(s, v)); } void convex_closure::syntactic_convex_closure(expr_ref_vector &out) { sort_ref real_sort(m_arith.mk_real(), m); for (unsigned row = 0; row < m_data.num_rows(); row++) { - m_alphas.push_back(m.mk_var(dims() + row, real_sort)); + m_alphas.push_back(m.mk_fresh_const("alpha!!scc", real_sort)); } expr_ref zero(m_arith.mk_real(rational::zero()), m); @@ -246,7 +242,7 @@ void convex_closure::syntactic_convex_closure(expr_ref_vector &out) { for (auto v : m_alphas) { out.push_back(m_arith.mk_ge(v, zero)); } for (unsigned k = 0, sz = m_col_vars.size(); k < sz; k++) { - if (is_var(m_col_vars.get(k))) mk_col_sum(k, out); + if (m_col_vars.get(k)) mk_col_sum(k, out); } //(\Sum j . m_new_vars[j]) = 1 @@ -291,6 +287,40 @@ bool convex_closure::generate_div_constraint(const vector &data, return true; } +bool convex_closure::compute() { + scoped_watch _w_(m_st.watch); + SASSERT(is_int_matrix(m_data)); + + unsigned rank = reduce_dim(); + // store dim var before rewrite + expr_ref var(m_col_vars.get(0), m); + if (rank < dims()) { + m_st.m_num_reductions++; + generate_implied_equalities(m_explicit_cc); + TRACE("cvx_dbg", tout << "Linear equalities true of the matrix " + << mk_and(m_explicit_cc) << "\n";); + } + + m_st.m_max_dim = std::max(m_st.m_max_dim, rank); + + if (rank == 0) { + // AG: Is this possible? + return false; + } else if (rank > 1) { + if (m_enable_syntactic_cc) { + SASSERT(m_alphas.size() == 0); + TRACE("subsume", tout << "Computing syntactic convex closure\n";); + syntactic_convex_closure(m_implicit_cc); + } else { + return false; + } + return true; + } + + SASSERT(rank == 1); + do_1dim_convex_closure(var, m_explicit_cc); + return true; +} /// Compute the convex closure of points in m_data /// /// Returns true if the convex closure is syntactic @@ -309,7 +339,7 @@ bool convex_closure::closure(expr_ref_vector &out) { << mk_and(out) << "\n";); } - if (red_dim > m_st.m_max_dim) m_st.m_max_dim = red_dim; + m_st.m_max_dim = std::max(m_st.m_max_dim, red_dim); if (red_dim > 1) { // there is no alternative to syntactic convex closure right now @@ -359,7 +389,6 @@ void convex_closure::do_1dim_convex_closure(const expr_ref &var, // -- compute LB <= var <= UB expr_ref res(m); res = var; - mul_by_rat(res, m_lcm); // upper-bound out.push_back(mk_ineq(res, data[0], true)); // lower-bound @@ -370,13 +399,12 @@ void convex_closure::do_1dim_convex_closure(const expr_ref &var, // add div constraints for all variables. for (unsigned j = 0; j < m_data.num_cols(); j++) { auto *v = m_col_vars.get(j); - if (is_var(v) && (m_arith.is_int(v) || m_bv.is_bv(v))) { + if (v && (m_arith.is_int(v) || m_bv.is_bv(v))) { data.reset(); m_data.get_col(j, data); std::sort(data.begin(), data.end(), gt_proc); if (generate_div_constraint(data, cr, off)) { res = v; - mul_by_rat(res, m_lcm); if (m_is_arith) { res = m.mk_eq(m_arith.mk_mod(res, m_arith.mk_int(cr)), m_arith.mk_int(off)); diff --git a/src/muz/spacer/spacer_convex_closure.h b/src/muz/spacer/spacer_convex_closure.h index d0998cd5795..70f12539fb3 100644 --- a/src/muz/spacer/spacer_convex_closure.h +++ b/src/muz/spacer/spacer_convex_closure.h @@ -75,11 +75,8 @@ class convex_closure { // These variables are always of sort Real expr_ref_vector m_alphas; - // m_lcm is a hack to allow convex_closure computation of rational matrices - // as well. Let A be a real matrix. m_lcm is the lcm of all denominators in - // A m_data = m_lcm * A, is always an integer matrix - // TODO: m_lcm should be maintained by the client - rational m_lcm; + expr_ref_vector m_implicit_cc; + expr_ref_vector m_explicit_cc; /// Reduces dimension of \p m_data and returns its rank unsigned reduce_dim(); @@ -126,7 +123,8 @@ class convex_closure { convex_closure(ast_manager &manager, bool use_sage) : m(manager), m_arith(m), m_bv(m), m_bv_sz(0), m_enable_syntactic_cc(true), m_is_arith(true), m_dim(0), m_data(0, 0), - m_col_vars(m), m_kernel(m_data), m_alphas(m) { + m_col_vars(m), m_kernel(m_data), m_alphas(m), m_implicit_cc(m), + m_explicit_cc(m) { if (use_sage) m_kernel.set_plugin(mk_sage_plugin()); } @@ -149,18 +147,15 @@ class convex_closure { /// \brief Name dimension \p i with a variable \p v. void set_col_var(unsigned i, expr *v) { SASSERT(i < dims()); - SASSERT(m_col_vars[i] == nullptr); + SASSERT(m_col_vars.get(i) == nullptr); m_col_vars[i] = v; } /// \brief Return number of dimensions of each point unsigned dims() const { return m_dim; } - /// \brief Return variables introduced by the syntactic convex closure - const expr_ref_vector &get_alphas() const { return m_alphas; } - /// \brief Add a one-dimensional point to convex closure - void push_back(rational x) { + void add_row(rational x) { SASSERT(dims() == 1); vector row; row.reserve(1, x); @@ -168,7 +163,7 @@ class convex_closure { } /// \brief Add a two-dimensional point to convex closure - void push_back(rational x, rational y) { + void add_row(rational x, rational y) { SASSERT(dims() == 2); vector row; row.reserve(2); @@ -178,7 +173,7 @@ class convex_closure { } /// \brief Add an n-dimensional point to convex closure - void push_back(const vector &point) { + void add_row(const vector &point) { SASSERT(point.size() == dims()); m_data.add_row(point); }; @@ -189,10 +184,33 @@ class convex_closure { /// Returns false if \p out is an over-approximation bool closure(expr_ref_vector &out); + bool operator()(expr_ref_vector &out) { return this->closure(out); } + + bool operator()() { return this->compute(); } + bool compute(); + bool has_implicit() { return !m_implicit_cc.empty(); } + bool has_explicit() { return !m_explicit_cc.empty(); } + + /// Returns the implicit component of convex closure (if available) + /// + /// Implicit component contains constants from get_alphas() that are + /// implicitly existentially quantified + const expr_ref_vector &get_implicit() { return m_implicit_cc; } + + /// \brief Return implicit constants in implicit convex closure + const expr_ref_vector &get_alphas() const { return m_alphas; } + + /// Returns the explicit component of convex closure (if available) + /// + /// The explicit component is in term of column variables + const expr_ref_vector &get_explicit() { return m_explicit_cc; } + + /// Returns constants used to name columns + /// + /// Explicit convex closure is in terms of these variables + const expr_ref_vector &get_col_vars() { return m_col_vars; } + void collect_statistics(statistics &st) const; void reset_statistics() { m_st.reset(); } - - /// Set the least common multiple of \p m_data - void set_lcm(rational l) { m_lcm = l; } }; } // namespace spacer diff --git a/src/muz/spacer/spacer_global_generalizer.cpp b/src/muz/spacer/spacer_global_generalizer.cpp index d0e819949a3..6eab2e1e116 100644 --- a/src/muz/spacer/spacer_global_generalizer.cpp +++ b/src/muz/spacer/spacer_global_generalizer.cpp @@ -35,6 +35,65 @@ namespace { // LOCAL HELPER FUNCTIONS IN ANONYMOUS NAMESPACE +class to_real_stripper { + ast_manager &m; + arith_util m_arith; + + public: + to_real_stripper(ast_manager &_m) : m(_m), m_arith(m) {} + bool operator()(expr_ref &e, unsigned depth = 8) { + rational num; + if (m_arith.is_int(e)) return true; + if (depth == 0) return false; + if (!is_app(e)) return false; + + if (m_arith.is_to_real(e)) { + // strip to_real() + e = to_app(e)->get_arg(0); + return true; + } else if (m_arith.is_numeral(e, num)) { + // convert number to an integer + if (denominator(num).is_one()) { + e = m_arith.mk_int(num); + return true; + } else { + return false; + } + } + + app *e_app = to_app(e); + expr_ref_buffer args(m); + expr_ref kid(m); + bool dirty = false; + for (unsigned i = 0, sz = e_app->get_num_args(); i < sz; ++i) { + auto *arg = e_app->get_arg(i); + kid = arg; + if (this->operator()(kid, depth - 1)) { + dirty |= (kid.get() != arg); + args.push_back(std::move(kid)); + } else { + return false; + } + } + + if (dirty) + e = m.mk_app(e_app->get_family_id(), e_app->get_decl_kind(), + args.size(), args.data()); + + return true; + } + + bool operator()(expr_ref_vector &vec, unsigned depth = 8) { + bool res = true; + expr_ref e(m); + for (unsigned i = 0, sz = vec.size(); res && i < sz; ++i) { + res = this->operator()(e, depth); + if (res) { vec[i] = e; } + } + return res; + } +}; + struct compute_lcm_proc { ast_manager &m; arith_util m_arith; @@ -329,8 +388,7 @@ void to_real(expr_ref &fml) { namespace spacer { lemma_global_generalizer::subsumer::subsumer(ast_manager &a_m, bool use_sage, bool ground_pob) - : m(a_m), m_arith(m), m_bv(m), m_tags(m), m_used_tags(0), - m_cvx_cls(m, use_sage), m_dim_frsh_cnsts(m), m_dim_vars(m), + : m(a_m), m_arith(m), m_bv(m), m_tags(m), m_used_tags(0), m_col_names(m), m_ground_pob(ground_pob) { scoped_ptr factory( mk_smt_strategic_solver_factory(symbol::null)); @@ -361,72 +419,88 @@ void lemma_global_generalizer::operator()(lemma_ref &lemma) { generalize(lemma); } -void lemma_global_generalizer::subsumer::add_dim_vars(const lemma_cluster &lc) { +void lemma_global_generalizer::subsumer::mk_col_names(const lemma_cluster &lc) { + expr_offset r; std::pair v; - unsigned n_vars = get_num_vars(lc.get_pattern()); - auto &lemmas = lc.get_lemmas(); + SASSERT(!lemmas.empty()); const substitution &sub = lemmas.get(0).get_sub(); - for (unsigned j = 0; j < n_vars; j++) { + m_col_names.reserve(sub.get_num_bindings()); + for (unsigned j = 0, sz = sub.get_num_bindings(); j < sz; j++) { // get var id sub.get_binding(j, v, r); auto *sort = r.get_expr()->get_sort(); - // create a variable for jth dimension, and register with convex closure - var *var = m.mk_var(v.first, sort); - m_dim_vars[j] = var; - m_cvx_cls.set_col_var(j, var); - - // create a fresh skolem constant for the jth variable - m_dim_frsh_cnsts[j] = m.mk_fresh_const("mrg_cvx", sort); + if (!m_col_names.get(j) || m_col_names.get(j)->get_sort() != sort) { + // create a fresh skolem constant for the jth variable + // reuse variables if they are already here and have matching sort + m_col_names[j] = m.mk_fresh_const("mrg_cvx!!", sort); + } } + + // -- lcm corresponds to a column, reset them since names have potentially + // changed + // -- this is a just-in-case + m_col_lcm.reset(); } // Populate m_cvx_cls by 1) collecting all substitutions in the cluster \p lc // 2) normalizing them to integer numerals -void lemma_global_generalizer::subsumer::populate_cvx_cls( - const lemma_cluster &lc) { +void lemma_global_generalizer::subsumer::setup_cvx_closure( + convex_closure &cc, const lemma_cluster &lc) { expr_offset r; std::pair v; - unsigned n_vars = get_num_vars(lc.get_pattern()); + mk_col_names(lc); const lemma_info_vector &lemmas = lc.get_lemmas(); - // compute LCM of all denominators numbers in all lemma instances - rational lemma_lcm = rational::one(), num; + m_col_lcm.reset(); + + unsigned n_vars = 0; + rational num; + bool is_first = true; for (const auto &lemma : lemmas) { const substitution &sub = lemma.get_sub(); + if (is_first) { + n_vars = sub.get_num_bindings(); + m_col_lcm.reserve(n_vars, rational::one()); + is_first = false; + } + for (unsigned j = 0; j < n_vars; j++) { sub.get_binding(j, v, r); - if (m_arith.is_numeral(r.get_expr(), num) || - m_bv.is_numeral(r.get_expr(), num)) { - lemma_lcm = lcm(lemma_lcm, abs(denominator(num))); + if (is_numeral(r.get_expr(), num)) { + m_col_lcm[j] = lcm(m_col_lcm.get(j), abs(denominator(num))); } } } - m_cvx_cls.set_lcm(lemma_lcm); + cc.reset(n_vars); + + unsigned bv_width; + if (contains_bv(m, lc.get_lemmas()[0].get_sub(), bv_width)) { + cc.set_bv(bv_width); + } + + for (unsigned j = 0; j < n_vars; ++j) + cc.set_col_var(j, mk_rat_mul(m_col_lcm.get(j), m_col_names.get(j))); - // Populate m_cvx_cls by normalized points corresponding to the - // substitutions Each point is normalized by multiplying by LCM of all - // denominators - vector point; + vector row; for (const auto &lemma : lemmas) { - point.reset(); + row.reset(); const substitution &sub = lemma.get_sub(); - for (unsigned j = 0; j < n_vars; j++) { + for (unsigned j = 0, sz = sub.get_num_bindings(); j < sz; j++) { sub.get_binding(j, v, r); - if (m_arith.is_numeral(r.get_expr(), num) || - m_bv.is_numeral(r.get_expr(), num)) { - point.push_back(lemma_lcm * num); - } + VERIFY(is_numeral(r.get_expr(), num)); + row.push_back(m_col_lcm.get(j) * num); } - // -- add normalized point to convex closure - m_cvx_cls.push_back(point); + + // -- add normalized row to convex closure + cc.add_row(row); } } @@ -443,9 +517,8 @@ expr *lemma_global_generalizer::subsumer::find_repr(const model_ref &mdl, /// are replaced by specific skolem constants. The \p out vector is populated /// with corresponding instantiations. Currently, instantiations are values /// chosen from the model -void lemma_global_generalizer::subsumer::skolemize(expr_ref &f, - const model_ref &mdl, - app_ref_vector &out) { +void lemma_global_generalizer::subsumer::skolemize_for_quic3( + expr_ref &f, const model_ref &mdl, app_ref_vector &out) { unsigned idx = out.size(); app_ref sk(m); expr_ref eval(m); @@ -457,8 +530,8 @@ void lemma_global_generalizer::subsumer::skolemize(expr_ref &f, expr_fast_mark2 marks; for (auto *c : f_cnsts) { marks.mark(c); } - for (unsigned i = 0, sz = m_dim_frsh_cnsts.size(); i < sz; i++) { - app *c = m_dim_frsh_cnsts.get(i); + for (unsigned i = 0, sz = m_col_names.size(); i < sz; i++) { + app *c = m_col_names.get(i); if (!marks.is_marked(c)) continue; SASSERT(m_arith.is_int(c)); @@ -471,10 +544,55 @@ void lemma_global_generalizer::subsumer::skolemize(expr_ref &f, } sub(f.get(), f); TRACE("subsume", tout << "skolemized into " << f << "\n";); - m_dim_frsh_cnsts.reset(); + m_col_names.reset(); +} + +bool lemma_global_generalizer::subsumer::find_model( + const expr_ref_vector &cc, const expr_ref_vector &alphas, expr *bg, + model_ref &out_model) { + + // push because we re-use the solver + solver::scoped_push _sp(*m_solver); + if (bg) m_solver->assert_expr(bg); + + // if there are alphas, we have syntactic convex closure + if (!alphas.empty()) { + SASSERT(alphas.size() >= 2); + // -- assert syntactic convex closure constraints + m_solver->assert_expr(cc); + + // try to get an interior point in convex closure that also satisfies bg + { + // push because this might be unsat + solver::scoped_push _sp2(*m_solver); + expr_ref zero(m_arith.mk_real(0), m); + + for (auto *alpha : alphas) { + m_solver->assert_expr(m_arith.mk_gt(alpha, zero)); + } + + auto res = m_solver->check_sat(); + if (res == l_true) { + m_solver->get_model(out_model); + return true; + } + } + } + + // failed, try to get any point in convex closure + auto res = m_solver->check_sat(); + + if (res == l_true) { + m_solver->get_model(out_model); + return true; + } + + // something went wrong and there is no model, even though one was expected + return false; } -///\p hard is a hard constraint and \p soft is a soft constraint that have to be +///\p hard is a hard constraint and \p soft is a soft constraint that have +/// to be /// satisfied by mdl bool lemma_global_generalizer::subsumer::maxsat_with_model( const expr_ref &hard, const expr_ref &soft, model_ref &out_model) { @@ -524,41 +642,9 @@ bool lemma_global_generalizer::subsumer::is_handled(const lemma_cluster &lc) { return true; } -void lemma_global_generalizer::subsumer::setup(const lemma_cluster &lc) { - +void lemma_global_generalizer::subsumer::reset() { m_used_tags = 0; - - unsigned n_vars = get_num_vars(lc.get_pattern()); - m_cvx_cls.reset(n_vars); - - m_dim_vars.reset(); - m_dim_vars.reserve(n_vars); - - m_dim_frsh_cnsts.reset(); - m_dim_frsh_cnsts.reserve(n_vars); - - unsigned sz = 0; - if (contains_bv(m, lc.get_lemmas()[0].get_sub(), sz)) { - m_cvx_cls.set_bv(sz); - } - - // create variables and corresponding skolems for each dimension in the - // input space - add_dim_vars(lc); - - // Add all vectors corresponding to the substitutions of lemmas in the - // cluster to convex closure computation - populate_cvx_cls(lc); -} - -/// Add variables introduced by cvx_cls to the list of variables -void lemma_global_generalizer::subsumer::add_cvx_cls_vars() { - for (auto v : m_cvx_cls.get_alphas()) { - SASSERT(is_var(v)); - m_dim_vars.push_back(to_var(v)); - m_dim_frsh_cnsts.push_back( - m.mk_fresh_const("mrg_syn_cvx", v->get_sort())); - } + m_col_lcm.reset(); } bool lemma_global_generalizer::subsumer::subsume(const lemma_cluster &lc, @@ -566,49 +652,79 @@ bool lemma_global_generalizer::subsumer::subsume(const lemma_cluster &lc, app_ref_vector &bindings) { if (!is_handled(lc)) return false; - setup(lc); + convex_closure cvx_closure(m, false); + + reset(); + setup_cvx_closure(cvx_closure, lc); // compute convex closure - expr_ref_vector cls(m); - bool is_syntactic = m_cvx_cls.closure(cls); + if (!cvx_closure.compute()) { return false; } + bool is_syntactic = cvx_closure.has_implicit(); + if (is_syntactic) { m_st.m_num_syn_cls++; } CTRACE("subsume_verb", is_syntactic, - tout << "Convex closure introduced new variables. Closure is " - << mk_and(cls) << "\n";); - - // If convex closure introduced new variables, add them to - // m_dim_frsh_cnsts - if (is_syntactic) { - m_st.m_num_syn_cls++; - add_cvx_cls_vars(); - } - - cls.push_back(lc.get_pattern()); - - // Ground syntactic CC by skolemizing variables - expr_ref ground_cls(m); - ground_free_vars(mk_and(cls), ground_cls); - TRACE("subsume_verb", tout << "Rewrote all vars into u_consts\n" - << mk_and(cls) << "\n" - << " into " - << "\n" - << ground_cls << "\n";); - - // Skolemized syntactic cc - expr_ref syn_cls(ground_cls, m); - - // Attempt to eliminate variables introduced by syntactic cc - if (!eliminate_vars(ground_cls, lc, - is_syntactic && contains_ints(m_dim_frsh_cnsts), - bindings)) { - // something failed, bail out - return false; + tout << "Convex closure introduced new variables. Implicit part of " + "closure is: " + << mk_and(cvx_closure.get_implicit()) << "\n";); + + expr_ref grounded(m); + ground_free_vars(lc.get_pattern(), grounded); + + expr_ref_vector vec(m); + auto &implicit_cc = cvx_closure.get_implicit(); + auto &explicit_cc = cvx_closure.get_explicit(); + vec.append(implicit_cc.size(), implicit_cc.data()); + vec.append(explicit_cc.size(), explicit_cc.data()); + + // get a model for mbp + model_ref mdl; + auto &alphas = cvx_closure.get_alphas(); + find_model(vec, alphas, grounded, mdl); + + app_ref_vector vars(m); + expr_ref conj(m); + vec.reset(); + + // eliminate real-valued alphas from syntactic convex closure + if (!implicit_cc.empty()) { + vec.append(implicit_cc.size(), implicit_cc.data()); + conj = mk_and(vec); + vars.append(alphas.size(), + reinterpret_cast(alphas.data())); + qe_project(m, vars, conj, *mdl.get(), true, true, !m_ground_pob); + + // mbp failed, not expected, bail out + if (!vars.empty()) return false; } - // at this point ground_cls might be stronger than original syntactic - // closure weaken it - flatten_and(ground_cls, new_post); - return over_approximate(new_post, syn_cls); + // vec = [implicit_cc] + // store full cc, this is what we want to over-approximate explicitly + vec.append(explicit_cc.size(), explicit_cc.data()); + vec.push_back(grounded); + // vec = [implicit_cc(alpha_j, v_i), explicit_cc(v_i), phi(v_i)] + expr_ref full_cc(mk_and(vec), m); + + // conj is the result of mbp, ensure it has no to_real() conversions + to_real_stripper stripper(m); + vec.reset(); + flatten_and(conj, vec); + stripper(vec); + + // vec is [cc(v_i), phi(v_i)], and we need to eliminate v_i from it + vec.push_back(grounded); + + vars.reset(); + vars.append(m_col_names.size(), + reinterpret_cast(m_col_names.data())); + conj = mk_and(vec); + qe_project(m, vars, conj, *mdl.get(), true, true, !m_ground_pob); + + // failed + if (!vars.empty()) return false; + + // at the end, new_post must over-approximate the implicit convex closure + flatten_and(conj, new_post); + return over_approximate(new_post, full_cc); } /// Eliminate m_dim_frsh_cnsts from \p cvx_cls @@ -647,30 +763,27 @@ bool lemma_global_generalizer::subsumer::eliminate_vars( << *mdl << "\n";); // MBP to eliminate existentially quantified variables - qe_project(m, m_dim_frsh_cnsts, cvx_pattern, *mdl.get(), true, true, + qe_project(m, m_col_names, cvx_pattern, *mdl.get(), true, true, !m_ground_pob); TRACE("subsume", tout << "Pattern after mbp of computing cvx cls: " << cvx_pattern << "\n";); - if (!m_ground_pob && contains_reals(m_dim_frsh_cnsts)) { - TRACE("subsume", { - tout << "Could not eliminate non-integer variables\n"; - for (auto *e : m_dim_vars) tout << mk_pp(e, m); - tout << "\n"; - }); + if (!m_ground_pob && contains_reals(m_col_names)) { + TRACE("subsume", tout << "Could not eliminate non-integer variables\n" + << m_col_names << "\n";); return false; } - SASSERT(!m_ground_pob || m_dim_frsh_cnsts.empty()); + SASSERT(!m_ground_pob || m_col_names.empty()); if (mlir) { to_int(cvx_pattern); } // If not all variables have been eliminated, skolemize and add bindings // This creates quantified proof obligation that will be handled by QUIC3 - if (!m_dim_frsh_cnsts.empty()) { + if (!m_col_names.empty()) { SASSERT(!m_ground_pob); - skolemize(cvx_pattern, mdl, out_bindings); + skolemize_for_quic3(cvx_pattern, mdl, out_bindings); } return true; @@ -883,21 +996,18 @@ void lemma_global_generalizer::generalize(lemma_ref &lemma) { void lemma_global_generalizer::subsumer::ground_free_vars(expr *pat, expr_ref &out) { SASSERT(!is_ground(pat)); - expr_safe_replace sub(m); - for (unsigned i = 0; i < m_dim_vars.size(); i++) { - sub.insert(m_dim_vars.get(i), m_dim_frsh_cnsts.get(i)); - } - sub(pat, out); + var_subst vs(m, false); + out = vs(pat, m_col_names.size(), + reinterpret_cast(m_col_names.data())); SASSERT(is_ground(out)); - return; } // convert all LIA constants in m_dim_frsh_cnsts to LRA constants using // to_real void lemma_global_generalizer::subsumer::to_real_cnsts() { - for (unsigned i = 0, sz = m_dim_frsh_cnsts.size(); i < sz; i++) { - auto *c = m_dim_frsh_cnsts.get(i); - if (!m_arith.is_real(c)) m_dim_frsh_cnsts.set(i, m_arith.mk_to_real(c)); + for (unsigned i = 0, sz = m_col_names.size(); i < sz; i++) { + auto *c = m_col_names.get(i); + if (!m_arith.is_real(c)) m_col_names.set(i, m_arith.mk_to_real(c)); } } @@ -949,7 +1059,7 @@ void lemma_global_generalizer::subsumer::collect_statistics( st.update("SPACER num no over approximate", m_st.m_num_no_ovr_approx); st.update("SPACER num sync cvx cls", m_st.m_num_syn_cls); st.update("SPACER num mbp failed", m_st.m_num_mbp_failed); - m_cvx_cls.collect_statistics(st); + // m_cvx_closure.collect_statistics(st); } void lemma_global_generalizer::collect_statistics(statistics &st) const { diff --git a/src/muz/spacer/spacer_global_generalizer.h b/src/muz/spacer/spacer_global_generalizer.h index f05e3ffc3cc..c09dd875a9e 100644 --- a/src/muz/spacer/spacer_global_generalizer.h +++ b/src/muz/spacer/spacer_global_generalizer.h @@ -55,14 +55,9 @@ class lemma_global_generalizer : public lemma_generalizer { // number of tags currently used unsigned m_used_tags; - // convex closure interface - convex_closure m_cvx_cls; - // save fresh constants for mbp - app_ref_vector m_dim_frsh_cnsts; - - // save vars from cluster pattern - var_ref_vector m_dim_vars; + app_ref_vector m_col_names; + vector m_col_lcm; // create pob without free vars bool m_ground_pob; @@ -74,8 +69,7 @@ class lemma_global_generalizer : public lemma_generalizer { /// Return a fresh boolean variable app *mk_fresh_tag(); - /// Prepare internal state for computing subsumption - void setup(const lemma_cluster &lc); + void reset(); /// Returns false if subsumption is not supported for given cluster bool is_handled(const lemma_cluster &lc); @@ -86,20 +80,16 @@ class lemma_global_generalizer : public lemma_generalizer { /// Skolemize m_dim_frsh_cnsts in \p f /// /// \p cnsts is appended with ground terms from \p mdl - void skolemize(expr_ref &f, const model_ref &mdl, - app_ref_vector &cnsts); + void skolemize_for_quic3(expr_ref &f, const model_ref &mdl, + app_ref_vector &cnsts); /// Create new vars to compute convex cls - void add_dim_vars(const lemma_cluster &lc); + void mk_col_names(const lemma_cluster &lc); /// Coerce LIA constants in \p m_dim_frsh_cnsts to LRA constants void to_real_cnsts(); - /// Populate \p m_cvx_cls - /// - /// 1. Collect all substitutions in the cluster \p lc - /// 2. Convert all substitutions to integer numerals - void populate_cvx_cls(const lemma_cluster &lc); + void setup_cvx_closure(convex_closure &cc, const lemma_cluster &lc); /// Make \p fml ground using m_dim_frsh_cnsts. Store result in \p out void ground_free_vars(expr *fml, expr_ref &out); @@ -112,6 +102,10 @@ class lemma_global_generalizer : public lemma_generalizer { bool maxsat_with_model(const expr_ref &hard, const expr_ref &soft, model_ref &out_model); + bool find_model(const expr_ref_vector &cc, + const expr_ref_vector &alphas, expr *bg, + model_ref &out_model); + /// Eliminate m_dim_frsh_cnsts from \p cvx_cls /// /// Uses \p lc to get a model for mbp. @@ -125,6 +119,15 @@ class lemma_global_generalizer : public lemma_generalizer { /// eliminated void add_cvx_cls_vars(); + bool is_numeral(const expr *e, rational &n) { + return m_arith.is_numeral(e, n) || m_bv.is_numeral(e, n); + } + + expr *mk_rat_mul(rational n, expr *v) { + if (n.is_one()) return v; + return m_arith.mk_mul(m_arith.mk_numeral(n, m_arith.is_int(v)), v); + } + public: subsumer(ast_manager &m, bool use_sage, bool ground_pob); From 9c7a80a2c2ef338b32862b6e627a052272a5c79a Mon Sep 17 00:00:00 2001 From: Arie Gurfinkel Date: Wed, 11 May 2022 17:32:57 -0700 Subject: [PATCH 21/78] WIP: convex closure --- src/muz/spacer/spacer_convex_closure.cpp | 63 ++++++++++++++---------- src/muz/spacer/spacer_convex_closure.h | 26 +++++----- 2 files changed, 49 insertions(+), 40 deletions(-) diff --git a/src/muz/spacer/spacer_convex_closure.cpp b/src/muz/spacer/spacer_convex_closure.cpp index 87adcf4ad66..1533658d97e 100644 --- a/src/muz/spacer/spacer_convex_closure.cpp +++ b/src/muz/spacer/spacer_convex_closure.cpp @@ -68,7 +68,7 @@ void convex_closure::reset(unsigned n_cols) { m_col_vars.reserve(m_dim); m_alphas.reset(); m_bv_sz = 0; - m_enable_syntactic_cc = true; + m_enable_implicit = true; } void convex_closure::collect_statistics(statistics &st) const { @@ -165,8 +165,8 @@ void convex_closure::mk_row_eq(const vector &row, expr_ref &out) { return; } - out = m_is_arith ? m_arith.mk_add(rhs.size(), rhs.data()) - : mk_bvadd(m, rhs.size(), rhs.data()); + out = !has_bv() ? m_arith.mk_add(rhs.size(), rhs.data()) + : mk_bvadd(m, rhs.size(), rhs.data()); expr_ref pv_var(m); pv_var = m_col_vars.get(pv); mul_by_rat(pv_var, coeff); @@ -200,7 +200,7 @@ void convex_closure::generate_implied_equalities(expr_ref_vector &out) { /// Where . is the dot product, m_data[*][i] is /// the ith column of m_data. Add the result to res_vec. void convex_closure::mk_col_sum(unsigned col, expr_ref_vector &out) { - SASSERT(m_is_arith); + SASSERT(!has_bv()); expr_ref_buffer sum(m); for (unsigned row = 0, sz = m_alphas.size(); row < sz; row++) { @@ -307,7 +307,7 @@ bool convex_closure::compute() { // AG: Is this possible? return false; } else if (rank > 1) { - if (m_enable_syntactic_cc) { + if (m_enable_implicit) { SASSERT(m_alphas.size() == 0); TRACE("subsume", tout << "Computing syntactic convex closure\n";); syntactic_convex_closure(m_implicit_cc); @@ -344,7 +344,7 @@ bool convex_closure::closure(expr_ref_vector &out) { if (red_dim > 1) { // there is no alternative to syntactic convex closure right now // syntactic convex closure does not support BV - if (m_enable_syntactic_cc) { + if (m_enable_implicit) { SASSERT(m_alphas.size() == 0); TRACE("subsume", tout << "Computing syntactic convex closure\n";); syntactic_convex_closure(out); @@ -364,20 +364,25 @@ bool convex_closure::closure(expr_ref_vector &out) { } // construct the formula result_var <= bnd or result_var >= bnd -expr *convex_closure::mk_ineq(expr_ref result_var, rational bnd, bool is_le) { - if (m_is_arith) { - // The resulting expr is of sort Real if result_var is of sort Real. - // Otherwise, the resulting expr is of sort Int - if (is_le) return m_arith.mk_le(result_var, m_arith.mk_int(bnd)); - return m_arith.mk_ge(result_var, m_arith.mk_int(bnd)); +expr *convex_closure::mk_le_ge(expr *v, rational n, bool is_le) { + if (m_arith.is_int_real(v)) { + expr *en = m_arith.mk_numeral(n, m_arith.is_int(v)); + return is_le ? m_arith.mk_le(v, en) : m_arith.mk_ge(v, en); + } else if (m_bv.is_bv(v)) { + expr *en = m_bv.mk_numeral(n, m_bv.get_bv_size(v->get_sort())); + return is_le ? m_bv.mk_ule(v, en) : m_bv.mk_ule(en, v); + } else { + UNREACHABLE(); } - // TODO figure out whether we need signed versions or unsigned versions. - if (is_le) return m_bv.mk_ule(result_var, m_bv.mk_numeral(bnd, m_bv_sz)); - return m_bv.mk_ule(m_bv.mk_numeral(bnd, m_bv_sz), result_var); + + return nullptr; } void convex_closure::do_1dim_convex_closure(const expr_ref &var, expr_ref_vector &out) { + + // XXX assumes that var corresponds to col 0 + // The convex closure over one dimension is just a bound vector data; m_data.get_col(0, data); @@ -390,9 +395,9 @@ void convex_closure::do_1dim_convex_closure(const expr_ref &var, expr_ref res(m); res = var; // upper-bound - out.push_back(mk_ineq(res, data[0], true)); + out.push_back(mk_le_ge(res, data[0], true)); // lower-bound - out.push_back(mk_ineq(res, data.back(), false)); + out.push_back(mk_le_ge(res, data.back(), false)); // -- compute divisibility constraints rational cr, off; @@ -404,19 +409,23 @@ void convex_closure::do_1dim_convex_closure(const expr_ref &var, m_data.get_col(j, data); std::sort(data.begin(), data.end(), gt_proc); if (generate_div_constraint(data, cr, off)) { - res = v; - if (m_is_arith) { - res = m.mk_eq(m_arith.mk_mod(res, m_arith.mk_int(cr)), - m_arith.mk_int(off)); - } else { - res = m.mk_eq( - m_bv.mk_bv_urem(res, m_bv.mk_numeral(cr, m_bv_sz)), - m_bv.mk_numeral(off, m_bv_sz)); - } - out.push_back(res); + out.push_back(mk_mod_eq(v, cr, off)); } } } } +expr *convex_closure::mk_mod_eq(expr *v, rational d, rational r) { + expr *res = nullptr; + if (!m_arith.is_int(v)) { + res = m.mk_eq(m_arith.mk_mod(v, m_arith.mk_int(d)), m_arith.mk_int(r)); + } else if (m_bv.is_bv(v)) { + res = m.mk_eq(m_bv.mk_bv_urem(v, m_bv.mk_numeral(d, m_bv_sz)), + m_bv.mk_numeral(r, m_bv_sz)); + } else { + UNREACHABLE(); + } + return res; +} + } // namespace spacer diff --git a/src/muz/spacer/spacer_convex_closure.h b/src/muz/spacer/spacer_convex_closure.h index 70f12539fb3..ed0e9e85510 100644 --- a/src/muz/spacer/spacer_convex_closure.h +++ b/src/muz/spacer/spacer_convex_closure.h @@ -51,11 +51,8 @@ class convex_closure { // size of all bit vectors in m_col_vars unsigned m_bv_sz; - // Compute syntactic convex closure - bool m_enable_syntactic_cc; - - // true if \p m_col_vars are arithmetic sort (i.e., Real or Int) - bool m_is_arith; + // Enable computation of implicit syntactic convex closure + bool m_enable_implicit; // number of columns in \p m_data unsigned m_dim; @@ -116,15 +113,19 @@ class convex_closure { bool generate_div_constraint(const vector &data, rational &m, rational &d); - /// Constructs a formula \p var ~ bnd, where ~ = is_le ? <= : >= - expr *mk_ineq(expr_ref var, rational bnd, bool is_le); + /// Constructs a formula \p var ~ n , where ~ = is_le ? <= : >= + expr *mk_le_ge(expr* var, rational n, bool is_le); + + /// Returns (v % d == r) + expr *mk_mod_eq(expr *v, rational d, rational r); + + bool has_bv() { return m_bv_sz > 0; } public: convex_closure(ast_manager &manager, bool use_sage) - : m(manager), m_arith(m), m_bv(m), m_bv_sz(0), - m_enable_syntactic_cc(true), m_is_arith(true), m_dim(0), m_data(0, 0), - m_col_vars(m), m_kernel(m_data), m_alphas(m), m_implicit_cc(m), - m_explicit_cc(m) { + : m(manager), m_arith(m), m_bv(m), m_bv_sz(0), m_enable_implicit(true), + m_dim(0), m_data(0, 0), m_col_vars(m), m_kernel(m_data), m_alphas(m), + m_implicit_cc(m), m_explicit_cc(m) { if (use_sage) m_kernel.set_plugin(mk_sage_plugin()); } @@ -139,9 +140,8 @@ class convex_closure { /// Disables syntactic convex closure as a side-effect void set_bv(unsigned sz) { SASSERT(sz > 0); - m_is_arith = false; m_bv_sz = sz; - m_enable_syntactic_cc = false; + m_enable_implicit = false; } /// \brief Name dimension \p i with a variable \p v. From b0d5d826027bb9f39f27a860c572c1da491d8408 Mon Sep 17 00:00:00 2001 From: Arie Gurfinkel Date: Thu, 12 May 2022 10:37:45 -0700 Subject: [PATCH 22/78] Fix bindings order in spacer_global_generalizer The matcher creates substitution using std_order, which is reverse of expected order (variable 0 is last). Adjust the code appropriately for that. --- src/muz/spacer/spacer_cluster.h | 2 ++ src/muz/spacer/spacer_global_generalizer.cpp | 8 ++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/muz/spacer/spacer_cluster.h b/src/muz/spacer/spacer_cluster.h index 2ed0c9333f6..fb5ab1d28d1 100644 --- a/src/muz/spacer/spacer_cluster.h +++ b/src/muz/spacer/spacer_cluster.h @@ -47,6 +47,8 @@ class lemma_cluster { // a lemma lemma_ref m_lemma; // a substitution such that for some pattern, \p m_lemma is an instance + // substitution is stored in std_order for quantifiers (i.e., reverse of + // expected) substitution m_sub; public: diff --git a/src/muz/spacer/spacer_global_generalizer.cpp b/src/muz/spacer/spacer_global_generalizer.cpp index 6eab2e1e116..b4419fcc978 100644 --- a/src/muz/spacer/spacer_global_generalizer.cpp +++ b/src/muz/spacer/spacer_global_generalizer.cpp @@ -430,8 +430,8 @@ void lemma_global_generalizer::subsumer::mk_col_names(const lemma_cluster &lc) { m_col_names.reserve(sub.get_num_bindings()); for (unsigned j = 0, sz = sub.get_num_bindings(); j < sz; j++) { - // get var id - sub.get_binding(j, v, r); + // get var id (sub is in reverse order) + sub.get_binding(sz - 1 - j, v, r); auto *sort = r.get_expr()->get_sort(); if (!m_col_names.get(j) || m_col_names.get(j)->get_sort() != sort) { @@ -471,7 +471,7 @@ void lemma_global_generalizer::subsumer::setup_cvx_closure( } for (unsigned j = 0; j < n_vars; j++) { - sub.get_binding(j, v, r); + sub.get_binding(n_vars - 1 - j, v, r); if (is_numeral(r.get_expr(), num)) { m_col_lcm[j] = lcm(m_col_lcm.get(j), abs(denominator(num))); } @@ -494,7 +494,7 @@ void lemma_global_generalizer::subsumer::setup_cvx_closure( const substitution &sub = lemma.get_sub(); for (unsigned j = 0, sz = sub.get_num_bindings(); j < sz; j++) { - sub.get_binding(j, v, r); + sub.get_binding(sz - 1 - j, v, r); VERIFY(is_numeral(r.get_expr(), num)); row.push_back(m_col_lcm.get(j) * num); } From 0104caeb2bea23297da2018f96aa73d761d6f8bf Mon Sep 17 00:00:00 2001 From: Arie Gurfinkel Date: Thu, 12 May 2022 10:38:51 -0700 Subject: [PATCH 23/78] Increase verbosity level for smt_context stats --- src/smt/smt_context_pp.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/smt/smt_context_pp.cpp b/src/smt/smt_context_pp.cpp index 77b42c6c22c..24bfb3355fe 100644 --- a/src/smt/smt_context_pp.cpp +++ b/src/smt/smt_context_pp.cpp @@ -766,11 +766,11 @@ namespace smt { for (; p2 + 2 < str.size(); ++p2) l2 << " "; l1 << ")\n"; l2 << ")\n"; - IF_VERBOSE(1, verbose_stream() << l1.str() << l2.str()); + IF_VERBOSE(2, verbose_stream() << l1.str() << l2.str()); m_last_positions.reset(); m_last_positions.append(offsets); } - IF_VERBOSE(1, verbose_stream() << str); + IF_VERBOSE(2, verbose_stream() << str); } }; From 8efc9b929e4fc7aef090fcb873942290fe6070fd Mon Sep 17 00:00:00 2001 From: Arie Gurfinkel Date: Thu, 12 May 2022 10:39:09 -0700 Subject: [PATCH 24/78] Dead code in qe_mbp --- src/qe/qe_mbp.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/qe/qe_mbp.cpp b/src/qe/qe_mbp.cpp index efadc59e6b3..6b0e3cf32d4 100644 --- a/src/qe/qe_mbp.cpp +++ b/src/qe/qe_mbp.cpp @@ -269,8 +269,6 @@ class mbproj::impl { } bool validate_model(model& model, expr_ref_vector const& fmls) { - expr_ref val(m); - model_evaluator eval(model); for (expr* f : fmls) { VERIFY(!model.is_false(f)); } From d6f1754773385de61863f6de39d2c9b92ebbc89f Mon Sep 17 00:00:00 2001 From: Arie Gurfinkel Date: Thu, 12 May 2022 10:39:45 -0700 Subject: [PATCH 25/78] bug fixes in spacer_global_generalizer::subsumer --- src/muz/spacer/spacer_global_generalizer.cpp | 32 ++++++++++++++------ 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/src/muz/spacer/spacer_global_generalizer.cpp b/src/muz/spacer/spacer_global_generalizer.cpp index b4419fcc978..035e8280ae0 100644 --- a/src/muz/spacer/spacer_global_generalizer.cpp +++ b/src/muz/spacer/spacer_global_generalizer.cpp @@ -87,6 +87,7 @@ class to_real_stripper { bool res = true; expr_ref e(m); for (unsigned i = 0, sz = vec.size(); res && i < sz; ++i) { + e = vec.get(i); res = this->operator()(e, depth); if (res) { vec[i] = e; } } @@ -555,11 +556,12 @@ bool lemma_global_generalizer::subsumer::find_model( solver::scoped_push _sp(*m_solver); if (bg) m_solver->assert_expr(bg); + // -- assert syntactic convex closure constraints + m_solver->assert_expr(cc); + // if there are alphas, we have syntactic convex closure if (!alphas.empty()) { SASSERT(alphas.size() >= 2); - // -- assert syntactic convex closure constraints - m_solver->assert_expr(cc); // try to get an interior point in convex closure that also satisfies bg { @@ -587,6 +589,8 @@ bool lemma_global_generalizer::subsumer::find_model( return true; } + UNREACHABLE(); + // something went wrong and there is no model, even though one was expected return false; } @@ -700,18 +704,22 @@ bool lemma_global_generalizer::subsumer::subsume(const lemma_cluster &lc, // vec = [implicit_cc] // store full cc, this is what we want to over-approximate explicitly vec.append(explicit_cc.size(), explicit_cc.data()); - vec.push_back(grounded); + flatten_and(grounded, vec); // vec = [implicit_cc(alpha_j, v_i), explicit_cc(v_i), phi(v_i)] expr_ref full_cc(mk_and(vec), m); - // conj is the result of mbp, ensure it has no to_real() conversions - to_real_stripper stripper(m); vec.reset(); - flatten_and(conj, vec); - stripper(vec); + if (conj) { + // if explicit version of implicit cc was successfully computed + // conj is it, but need to ensure it has no to_real() + to_real_stripper stripper(m); + flatten_and(conj, vec); + stripper(vec); + } + vec.append(explicit_cc.size(), explicit_cc.data()); - // vec is [cc(v_i), phi(v_i)], and we need to eliminate v_i from it - vec.push_back(grounded); + flatten_and(grounded, vec); + // here vec is [cc(v_i), phi(v_i)], and we need to eliminate v_i from it vars.reset(); vars.append(m_col_names.size(), @@ -997,7 +1005,11 @@ void lemma_global_generalizer::subsumer::ground_free_vars(expr *pat, expr_ref &out) { SASSERT(!is_ground(pat)); var_subst vs(m, false); - out = vs(pat, m_col_names.size(), + // m_col_names might be bigger since it contains previously used constants + // relying on the fact that m_col_lcm was just set. Better to compute free + // vars of pat + SASSERT(m_col_lcm.size() <= m_col_names.size()); + out = vs(pat, m_col_lcm.size(), reinterpret_cast(m_col_names.data())); SASSERT(is_ground(out)); } From 790ddadcaedda83f263e14e7ed97d0f012628fe4 Mon Sep 17 00:00:00 2001 From: Arie Gurfinkel Date: Thu, 12 May 2022 11:39:48 -0700 Subject: [PATCH 26/78] Partially remove dependence of size of m_alphas I want m_alphas to potentially be greater than currently used alpha variables. This is helpful for reusing them across multiple calls to convex closure --- src/muz/spacer/spacer_convex_closure.cpp | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/muz/spacer/spacer_convex_closure.cpp b/src/muz/spacer/spacer_convex_closure.cpp index 1533658d97e..94919c7d218 100644 --- a/src/muz/spacer/spacer_convex_closure.cpp +++ b/src/muz/spacer/spacer_convex_closure.cpp @@ -203,7 +203,7 @@ void convex_closure::mk_col_sum(unsigned col, expr_ref_vector &out) { SASSERT(!has_bv()); expr_ref_buffer sum(m); - for (unsigned row = 0, sz = m_alphas.size(); row < sz; row++) { + for (unsigned row = 0, sz = m_data.num_rows(); row < sz; row++) { expr_ref alpha(m); auto n = m_data.get(row, col); if (n.is_zero()) { @@ -233,13 +233,16 @@ void convex_closure::mk_col_sum(unsigned col, expr_ref_vector &out) { void convex_closure::syntactic_convex_closure(expr_ref_vector &out) { sort_ref real_sort(m_arith.mk_real(), m); - for (unsigned row = 0; row < m_data.num_rows(); row++) { - m_alphas.push_back(m.mk_fresh_const("alpha!!scc", real_sort)); - } - expr_ref zero(m_arith.mk_real(rational::zero()), m); - // forall j :: m_new_vars[j] >= 0 - for (auto v : m_alphas) { out.push_back(m_arith.mk_ge(v, zero)); } + + for (unsigned row = 0, sz = m_data.num_rows(); row < sz; row++) { + if (row >= m_alphas.size()) { + m_alphas.push_back(m.mk_fresh_const("a!cc", real_sort)); + } + SASSERT(row < m_alphas.size()); + // forall j :: alpha_j >= 0 + out.push_back(m_arith.mk_ge(m_alphas.get(row), zero)); + } for (unsigned k = 0, sz = m_col_vars.size(); k < sz; k++) { if (m_col_vars.get(k)) mk_col_sum(k, out); @@ -247,7 +250,7 @@ void convex_closure::syntactic_convex_closure(expr_ref_vector &out) { //(\Sum j . m_new_vars[j]) = 1 out.push_back(m.mk_eq( - m_arith.mk_add(m_alphas.size(), + m_arith.mk_add(m_data.num_rows(), reinterpret_cast(m_alphas.data())), m_arith.mk_real(rational::one()))); } @@ -308,7 +311,6 @@ bool convex_closure::compute() { return false; } else if (rank > 1) { if (m_enable_implicit) { - SASSERT(m_alphas.size() == 0); TRACE("subsume", tout << "Computing syntactic convex closure\n";); syntactic_convex_closure(m_implicit_cc); } else { @@ -345,7 +347,6 @@ bool convex_closure::closure(expr_ref_vector &out) { // there is no alternative to syntactic convex closure right now // syntactic convex closure does not support BV if (m_enable_implicit) { - SASSERT(m_alphas.size() == 0); TRACE("subsume", tout << "Computing syntactic convex closure\n";); syntactic_convex_closure(out); } else { From 42eeee11f2f6388995b6be79fadfa0e4dd7f5b14 Mon Sep 17 00:00:00 2001 From: Arie Gurfinkel Date: Thu, 12 May 2022 20:38:20 -0400 Subject: [PATCH 27/78] Subtle bug in kernel computation Coefficient was being passed by reference and, therefore, was being changed indirectly. In the process, updated the code to be more generic to avoid rational computation in the middle of matrix manipulation. --- src/math/simplex/sparse_matrix_ops.h | 75 +++++++++++++++------------- 1 file changed, 40 insertions(+), 35 deletions(-) diff --git a/src/math/simplex/sparse_matrix_ops.h b/src/math/simplex/sparse_matrix_ops.h index 039d2fedeaf..edc28f70a51 100644 --- a/src/math/simplex/sparse_matrix_ops.h +++ b/src/math/simplex/sparse_matrix_ops.h @@ -7,7 +7,7 @@ Module Name: Abstract: - + Author: Nikolaj Bjorner (nbjorner) 2014-01-15 @@ -23,13 +23,13 @@ Module Name: namespace simplex { -class sparse_matrix_ops { - public: + class sparse_matrix_ops { + public: template static void kernel(sparse_matrix &M, vector> &K) { using scoped_numeral = typename Ext::scoped_numeral; - vector d, c; + vector d, c; unsigned n_vars = M.num_vars(), n_rows = M.num_rows(); c.resize(n_rows, 0u); d.resize(n_vars, 0u); @@ -39,47 +39,52 @@ class sparse_matrix_ops { scoped_numeral D(m); for (unsigned k = 0; k < n_vars; ++k) { - d[k] = 0; - for (auto [row, row_entry] : M.get_rows(k)) { - if (c[row.id()] != 0) continue; - auto &m_jk = row_entry->m_coeff; - if (mpq_manager::is_zero(m_jk)) continue; - - // D = rational(-1) / m_jk; - m.set(D, m_jk); - m.inv(D); - m.neg(D); - - M.mul(row, D); - for (auto [row_i, row_i_entry] : M.get_rows(k)) { - if (row_i.id() == row.id()) continue; + d[k] = 0; + for (auto [row, row_entry] : M.get_rows(k)) { + if (c[row.id()] != 0) + continue; + auto& m_jk = row_entry->m_coeff; + if (mpq_manager::is_zero(m_jk)) + continue; + + // D = rational(-1) / m_jk; + m.set(D, m_jk); + m.inv(D); + m.neg(D); + + M.mul(row, D); + for (auto [row_i, row_i_entry] : M.get_rows(k)) { + if (row_i.id() == row.id()) + continue; m.set(m_ik, row_i_entry->m_coeff); - // row_i += m_ik * row - M.add(row_i, m_ik, row); + // row_i += m_ik * row + M.add(row_i, m_ik, row); + } + c[row.id()] = k + 1; + d[k] = row.id() + 1; + break; } - c[row.id()] = k + 1; - d[k] = row.id() + 1; - break; } - } for (unsigned k = 0; k < n_vars; ++k) { - if (d[k] != 0) continue; - K.push_back(vector()); + if (d[k] != 0) + continue; + K.push_back(vector()); for (unsigned i = 0; i < n_vars; ++i) { - if (d[i] > 0) { - auto r = sparse_matrix::row(d[i] - 1); - K.back().push_back(rational(M.get_coeff(r, k))); + if (d[i] > 0) { + auto r = sparse_matrix::row(d[i]-1); + K.back().push_back(rational(M.get_coeff(r, k))); } else if (i == k) - K.back().push_back(rational(1)); - else - K.back().push_back(rational(0)); + K.back().push_back(rational(1)); + else + K.back().push_back(rational(0)); + } } - } } static void kernel(sparse_matrix &M, vector> &K) { kernel(M, K); - } -}; + } + }; } // namespace simplex + From 9f21c78e265de57efa7c13e4fe3230d1879585dd Mon Sep 17 00:00:00 2001 From: Arie Gurfinkel Date: Thu, 12 May 2022 20:45:39 -0400 Subject: [PATCH 28/78] another test for sparse_matrix_ops::kernel --- src/test/simplex.cpp | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/test/simplex.cpp b/src/test/simplex.cpp index 2e59b41719d..793687b1c36 100644 --- a/src/test/simplex.cpp +++ b/src/test/simplex.cpp @@ -157,6 +157,22 @@ static void test5() { } +static void test6() { + unsynch_mpq_manager m; + qmatrix M(m); + add(M, vec(-1, 2, 1)); + add(M, vec(0, 1, 1)); + M.display(std::cout); + vector> K; + kernel(M, K); + std::cout << "Kernel:\n"; + for (auto const &v : K) + std::cout << v << "\n"; + std::cout << "matrix after\n"; + M.display(std::cout); + +} + void tst_simplex() { reslimit rl; Simplex S(rl); @@ -193,4 +209,5 @@ void tst_simplex() { test3(); test4(); test5(); + test6(); } From 7baeb20c8950eebe8168064e6f378bae5235645d Mon Sep 17 00:00:00 2001 From: Arie Gurfinkel Date: Fri, 13 May 2022 18:03:38 -0400 Subject: [PATCH 29/78] Implementation of matrix kernel using Fraction Free Elimination Ensures that the kernel is int for int matrices. All divisions are exact. --- src/math/simplex/simplex.cpp | 4 + src/math/simplex/simplex.h | 1 + src/math/simplex/sparse_matrix.h | 1 + src/math/simplex/sparse_matrix_def.h | 19 +++ src/math/simplex/sparse_matrix_ops.h | 176 ++++++++++++++++++++------- src/test/simplex.cpp | 22 +++- 6 files changed, 174 insertions(+), 49 deletions(-) diff --git a/src/math/simplex/simplex.cpp b/src/math/simplex/simplex.cpp index 643db9c5bff..6174b81b353 100644 --- a/src/math/simplex/simplex.cpp +++ b/src/math/simplex/simplex.cpp @@ -41,6 +41,10 @@ namespace simplex { sparse_matrix_ops::kernel(M, K); } + void kernel_ffe(sparse_matrix &M, vector> &K) { + sparse_matrix_ops::kernel_ffe(M, K); + } + void ensure_rational_solution(simplex& S) { rational delta(1); for (unsigned i = 0; i < S.get_num_vars(); ++i) { diff --git a/src/math/simplex/simplex.h b/src/math/simplex/simplex.h index 8c470a242b8..0405a59d0ba 100644 --- a/src/math/simplex/simplex.h +++ b/src/math/simplex/simplex.h @@ -203,5 +203,6 @@ namespace simplex { void ensure_rational_solution(simplex& s); void kernel(sparse_matrix& s, vector>& K); + void kernel_ffe(sparse_matrix &s, vector> &K); }; diff --git a/src/math/simplex/sparse_matrix.h b/src/math/simplex/sparse_matrix.h index ecba27af5e2..9333e9ddb3c 100644 --- a/src/math/simplex/sparse_matrix.h +++ b/src/math/simplex/sparse_matrix.h @@ -168,6 +168,7 @@ namespace simplex { void add_var(row r, numeral const& n, var_t var); void add(row r, numeral const& n, row src); void mul(row r, numeral const& n); + void div(row r, numeral const& n); void neg(row r); void del(row r); diff --git a/src/math/simplex/sparse_matrix_def.h b/src/math/simplex/sparse_matrix_def.h index 255e62f1185..914ab1d942e 100644 --- a/src/math/simplex/sparse_matrix_def.h +++ b/src/math/simplex/sparse_matrix_def.h @@ -432,6 +432,25 @@ namespace simplex { } } + /** + \brief Set row <- n/row + */ + template + void sparse_matrix::div(row r, numeral const &n) { + SASSERT(!m.is_zero(n)); + if (m.is_one(n)) { + // no op + } else if (m.is_minus_one(n)) { + neg(r); + } else { + row_iterator it = row_begin(r); + row_iterator end = row_end(r); + for (; it != end; ++it) { + m.div(it->m_coeff, n, it->m_coeff); + } + } + } + /** \brief Delete row. */ diff --git a/src/math/simplex/sparse_matrix_ops.h b/src/math/simplex/sparse_matrix_ops.h index edc28f70a51..02fdfec5491 100644 --- a/src/math/simplex/sparse_matrix_ops.h +++ b/src/math/simplex/sparse_matrix_ops.h @@ -23,28 +23,28 @@ Module Name: namespace simplex { - class sparse_matrix_ops { - public: - template - static void kernel(sparse_matrix &M, vector> &K) { - using scoped_numeral = typename Ext::scoped_numeral; - - vector d, c; - unsigned n_vars = M.num_vars(), n_rows = M.num_rows(); - c.resize(n_rows, 0u); - d.resize(n_vars, 0u); - - auto &m = M.get_manager(); - scoped_numeral m_ik(m); - scoped_numeral D(m); - - for (unsigned k = 0; k < n_vars; ++k) { - d[k] = 0; - for (auto [row, row_entry] : M.get_rows(k)) { +class sparse_matrix_ops { +public: + template + static void kernel(sparse_matrix &M, vector> &K) { + using scoped_numeral = typename Ext::scoped_numeral; + + vector d, c; + unsigned n_vars = M.num_vars(), n_rows = M.num_rows(); + c.resize(n_rows, 0u); + d.resize(n_vars, 0u); + + auto &m = M.get_manager(); + scoped_numeral m_ik(m); + scoped_numeral D(m); + + for (unsigned k = 0; k < n_vars; ++k) { + d[k] = 0; + for (auto [row, row_entry] : M.get_rows(k)) { if (c[row.id()] != 0) continue; - auto& m_jk = row_entry->m_coeff; - if (mpq_manager::is_zero(m_jk)) + auto &m_jk = row_entry->m_coeff; + if (m.is_zero(m_jk)) continue; // D = rational(-1) / m_jk; @@ -53,38 +53,120 @@ namespace simplex { m.neg(D); M.mul(row, D); - for (auto [row_i, row_i_entry] : M.get_rows(k)) { + for (auto [row_i, row_i_entry] : M.get_rows(k)) { if (row_i.id() == row.id()) continue; - m.set(m_ik, row_i_entry->m_coeff); - // row_i += m_ik * row - M.add(row_i, m_ik, row); - } - c[row.id()] = k + 1; - d[k] = row.id() + 1; - break; - } - } - - for (unsigned k = 0; k < n_vars; ++k) { + m.set(m_ik, row_i_entry->m_coeff); + // row_i += m_ik * row + M.add(row_i, m_ik, row); + } + c[row.id()] = k + 1; + d[k] = row.id() + 1; + break; + } + } + + for (unsigned k = 0; k < n_vars; ++k) { if (d[k] != 0) continue; - K.push_back(vector()); - for (unsigned i = 0; i < n_vars; ++i) { - if (d[i] > 0) { - auto r = sparse_matrix::row(d[i]-1); - K.back().push_back(rational(M.get_coeff(r, k))); - } else if (i == k) - K.back().push_back(rational(1)); - else - K.back().push_back(rational(0)); - } - } + K.push_back(vector()); + for (unsigned i = 0; i < n_vars; ++i) { + if (d[i] > 0) { + auto r = typename sparse_matrix::row(d[i] - 1); + K.back().push_back(rational(M.get_coeff(r, k))); + } else if (i == k) + K.back().push_back(rational(1)); + else + K.back().push_back(rational(0)); + } } + } + + static void kernel(sparse_matrix &M, vector> &K) { + kernel(M, K); + } + + /// \brief Kernel computation using fraction-free-elimination + /// + template + static void kernel_ffe(sparse_matrix &M, vector> &K) { + using scoped_numeral = typename Ext::scoped_numeral; + + /// Based on George Nakos, Peter R. Turner, Robert M. Williams: + /// Fraction-free algorithms for linear and polynomial equations. SIGSAM + /// Bull. 31(3): 11-19 (1997) + vector d, c; + unsigned n_vars = M.num_vars(), n_rows = M.num_rows(); + c.resize(n_rows, 0u); + d.resize(n_vars, 0u); + + auto &m = M.get_manager(); + scoped_numeral m_ik(m); + scoped_numeral m_jk(m); + scoped_numeral last_pv(m); + + m.set(last_pv, 1); + + for (unsigned k = 0; k < n_vars; ++k) { + d[k] = 0; + for (auto [row, row_entry] : M.get_rows(k)) { + if (c[row.id()] != 0) + continue; + auto &m_jk_ref = row_entry->m_coeff; + if (m.is_zero(m_jk_ref)) + // XXX: should not happen, the matrix is sparse + continue; + + // this a pivot column + m.set(m_jk, m_jk_ref); - static void kernel(sparse_matrix &M, vector> &K) { - kernel(M, K); + // ensure that pivot is negative + if (m.is_pos(m_jk_ref)) { + M.neg(row); + } else { + m.neg(m_jk); } - }; -} // namespace simplex + // m_jk is abs(M[j]][k]) + for (auto row_i : M.get_rows()) { + if (row_i.id() == row.id()) + continue; + + m.set(m_ik, M.get_coeff(row_i, k)); + // row_i *= m_jk + M.mul(row_i, m_jk); + if (!m.is_zero(m_ik)) { + // row_i += m_ik * row + M.add(row_i, m_ik, row); + } + M.div(row_i, last_pv); + } + c[row.id()] = k + 1; + d[k] = row.id() + 1; + m.set(last_pv, m_jk); + break; + } + } + + for (unsigned k = 0; k < n_vars; ++k) { + if (d[k] != 0) + continue; + K.push_back(vector()); + for (unsigned i = 0; i < n_vars; ++i) { + if (d[i] > 0) { + auto r = typename sparse_matrix::row(d[i] - 1); + K.back().push_back(rational(M.get_coeff(r, k))); + } else if (i == k) + K.back().push_back(rational(last_pv)); + else + K.back().push_back(rational(0)); + } + } +} + + static void kernel_ffe(sparse_matrix &M, + vector> &K) { + kernel_ffe(M, K); + } +}; +} // namespace simplex diff --git a/src/test/simplex.cpp b/src/test/simplex.cpp index 793687b1c36..3c20956fb62 100644 --- a/src/test/simplex.cpp +++ b/src/test/simplex.cpp @@ -149,7 +149,7 @@ static void test5() { add(M, vec(0, 0, 0, 0, 0, 0)); M.display(std::cout); vector> K; - kernel(M, K); + kernel_ffe(M, K); std::cout << "after\n"; for (auto const& v : K) std::cout << v << "\n"; @@ -164,7 +164,7 @@ static void test6() { add(M, vec(0, 1, 1)); M.display(std::cout); vector> K; - kernel(M, K); + kernel_ffe(M, K); std::cout << "Kernel:\n"; for (auto const &v : K) std::cout << v << "\n"; @@ -173,6 +173,23 @@ static void test6() { } +static void test7() { + unsynch_mpq_manager m; + qmatrix M(m); + add(M, vec(1, 2, 3, 4, 10)); + add(M, vec(2, 2, 3, 4, 11)); + add(M, vec(3, 3, 3, 4, 13)); + add(M, vec(9, 8, 7, 6, 30)); + M.display(std::cout); + vector> K; + kernel_ffe(M, K); + std::cout << "Kernel:\n"; + for (auto const &v : K) + std::cout << v << "\n"; + std::cout << "matrix after\n"; + M.display(std::cout); +} + void tst_simplex() { reslimit rl; Simplex S(rl); @@ -210,4 +227,5 @@ void tst_simplex() { test4(); test5(); test6(); + test7(); } From f42ca709b157a7ac8b20eb5d261be86e47f40071 Mon Sep 17 00:00:00 2001 From: Arie Gurfinkel Date: Fri, 13 May 2022 20:42:20 -0400 Subject: [PATCH 30/78] clang-format sparse_matrix_ops.h --- src/math/simplex/sparse_matrix_ops.h | 264 +++++++++++++-------------- 1 file changed, 128 insertions(+), 136 deletions(-) diff --git a/src/math/simplex/sparse_matrix_ops.h b/src/math/simplex/sparse_matrix_ops.h index 02fdfec5491..0d817d86831 100644 --- a/src/math/simplex/sparse_matrix_ops.h +++ b/src/math/simplex/sparse_matrix_ops.h @@ -7,7 +7,7 @@ Module Name: Abstract: - + Author: Nikolaj Bjorner (nbjorner) 2014-01-15 @@ -24,149 +24,141 @@ Module Name: namespace simplex { class sparse_matrix_ops { -public: - template - static void kernel(sparse_matrix &M, vector> &K) { - using scoped_numeral = typename Ext::scoped_numeral; - - vector d, c; - unsigned n_vars = M.num_vars(), n_rows = M.num_rows(); - c.resize(n_rows, 0u); - d.resize(n_vars, 0u); - - auto &m = M.get_manager(); - scoped_numeral m_ik(m); - scoped_numeral D(m); - - for (unsigned k = 0; k < n_vars; ++k) { - d[k] = 0; - for (auto [row, row_entry] : M.get_rows(k)) { - if (c[row.id()] != 0) - continue; - auto &m_jk = row_entry->m_coeff; - if (m.is_zero(m_jk)) - continue; - - // D = rational(-1) / m_jk; - m.set(D, m_jk); - m.inv(D); - m.neg(D); - - M.mul(row, D); - for (auto [row_i, row_i_entry] : M.get_rows(k)) { - if (row_i.id() == row.id()) - continue; - m.set(m_ik, row_i_entry->m_coeff); - // row_i += m_ik * row - M.add(row_i, m_ik, row); + public: + template + static void kernel(sparse_matrix &M, vector> &K) { + using scoped_numeral = typename Ext::scoped_numeral; + + vector d, c; + unsigned n_vars = M.num_vars(), n_rows = M.num_rows(); + c.resize(n_rows, 0u); + d.resize(n_vars, 0u); + + auto &m = M.get_manager(); + scoped_numeral m_ik(m); + scoped_numeral D(m); + + for (unsigned k = 0; k < n_vars; ++k) { + d[k] = 0; + for (auto [row, row_entry] : M.get_rows(k)) { + if (c[row.id()] != 0) continue; + auto &m_jk = row_entry->m_coeff; + if (m.is_zero(m_jk)) continue; + + // D = rational(-1) / m_jk; + m.set(D, m_jk); + m.inv(D); + m.neg(D); + + M.mul(row, D); + for (auto [row_i, row_i_entry] : M.get_rows(k)) { + if (row_i.id() == row.id()) continue; + m.set(m_ik, row_i_entry->m_coeff); + // row_i += m_ik * row + M.add(row_i, m_ik, row); + } + c[row.id()] = k + 1; + d[k] = row.id() + 1; + break; + } + } + + for (unsigned k = 0; k < n_vars; ++k) { + if (d[k] != 0) continue; + K.push_back(vector()); + for (unsigned i = 0; i < n_vars; ++i) { + if (d[i] > 0) { + auto r = typename sparse_matrix::row(d[i] - 1); + K.back().push_back(rational(M.get_coeff(r, k))); + } + else if (i == k) + K.back().push_back(rational(1)); + else + K.back().push_back(rational(0)); + } } - c[row.id()] = k + 1; - d[k] = row.id() + 1; - break; - } } - for (unsigned k = 0; k < n_vars; ++k) { - if (d[k] != 0) - continue; - K.push_back(vector()); - for (unsigned i = 0; i < n_vars; ++i) { - if (d[i] > 0) { - auto r = typename sparse_matrix::row(d[i] - 1); - K.back().push_back(rational(M.get_coeff(r, k))); - } else if (i == k) - K.back().push_back(rational(1)); - else - K.back().push_back(rational(0)); - } + static void kernel(sparse_matrix &M, vector> &K) { + kernel(M, K); } - } - - static void kernel(sparse_matrix &M, vector> &K) { - kernel(M, K); - } - - /// \brief Kernel computation using fraction-free-elimination - /// - template - static void kernel_ffe(sparse_matrix &M, vector> &K) { - using scoped_numeral = typename Ext::scoped_numeral; - - /// Based on George Nakos, Peter R. Turner, Robert M. Williams: - /// Fraction-free algorithms for linear and polynomial equations. SIGSAM - /// Bull. 31(3): 11-19 (1997) - vector d, c; - unsigned n_vars = M.num_vars(), n_rows = M.num_rows(); - c.resize(n_rows, 0u); - d.resize(n_vars, 0u); - - auto &m = M.get_manager(); - scoped_numeral m_ik(m); - scoped_numeral m_jk(m); - scoped_numeral last_pv(m); - - m.set(last_pv, 1); - - for (unsigned k = 0; k < n_vars; ++k) { - d[k] = 0; - for (auto [row, row_entry] : M.get_rows(k)) { - if (c[row.id()] != 0) - continue; - auto &m_jk_ref = row_entry->m_coeff; - if (m.is_zero(m_jk_ref)) - // XXX: should not happen, the matrix is sparse - continue; - - // this a pivot column - m.set(m_jk, m_jk_ref); - - // ensure that pivot is negative - if (m.is_pos(m_jk_ref)) { - M.neg(row); - } else { - m.neg(m_jk); + + /// \brief Kernel computation using fraction-free-elimination + /// + template + static void kernel_ffe(sparse_matrix &M, vector> &K) { + using scoped_numeral = typename Ext::scoped_numeral; + + /// Based on George Nakos, Peter R. Turner, Robert M. Williams: + /// Fraction-free algorithms for linear and polynomial equations. SIGSAM + /// Bull. 31(3): 11-19 (1997) + vector d, c; + unsigned n_vars = M.num_vars(), n_rows = M.num_rows(); + c.resize(n_rows, 0u); + d.resize(n_vars, 0u); + + auto &m = M.get_manager(); + scoped_numeral m_ik(m); + scoped_numeral m_jk(m); + scoped_numeral last_pv(m); + + m.set(last_pv, 1); + + for (unsigned k = 0; k < n_vars; ++k) { + d[k] = 0; + for (auto [row, row_entry] : M.get_rows(k)) { + if (c[row.id()] != 0) continue; + auto &m_jk_ref = row_entry->m_coeff; + if (m.is_zero(m_jk_ref)) + // XXX: should not happen, the matrix is sparse + continue; + + // this a pivot column + m.set(m_jk, m_jk_ref); + + // ensure that pivot is negative + if (m.is_pos(m_jk_ref)) { M.neg(row); } + else { m.neg(m_jk); } + // m_jk is abs(M[j]][k]) + + for (auto row_i : M.get_rows()) { + if (row_i.id() == row.id()) continue; + + m.set(m_ik, M.get_coeff(row_i, k)); + // row_i *= m_jk + M.mul(row_i, m_jk); + if (!m.is_zero(m_ik)) { + // row_i += m_ik * row + M.add(row_i, m_ik, row); + } + M.div(row_i, last_pv); + } + c[row.id()] = k + 1; + d[k] = row.id() + 1; + m.set(last_pv, m_jk); + break; + } } - // m_jk is abs(M[j]][k]) - - for (auto row_i : M.get_rows()) { - if (row_i.id() == row.id()) - continue; - - m.set(m_ik, M.get_coeff(row_i, k)); - // row_i *= m_jk - M.mul(row_i, m_jk); - if (!m.is_zero(m_ik)) { - // row_i += m_ik * row - M.add(row_i, m_ik, row); - } - M.div(row_i, last_pv); + + for (unsigned k = 0; k < n_vars; ++k) { + if (d[k] != 0) continue; + K.push_back(vector()); + for (unsigned i = 0; i < n_vars; ++i) { + if (d[i] > 0) { + auto r = typename sparse_matrix::row(d[i] - 1); + K.back().push_back(rational(M.get_coeff(r, k))); + } + else if (i == k) + K.back().push_back(rational(last_pv)); + else + K.back().push_back(rational(0)); + } } - c[row.id()] = k + 1; - d[k] = row.id() + 1; - m.set(last_pv, m_jk); - break; - } } - for (unsigned k = 0; k < n_vars; ++k) { - if (d[k] != 0) - continue; - K.push_back(vector()); - for (unsigned i = 0; i < n_vars; ++i) { - if (d[i] > 0) { - auto r = typename sparse_matrix::row(d[i] - 1); - K.back().push_back(rational(M.get_coeff(r, k))); - } else if (i == k) - K.back().push_back(rational(last_pv)); - else - K.back().push_back(rational(0)); - } + static void kernel_ffe(sparse_matrix &M, + vector> &K) { + kernel_ffe(M, K); } -} - - static void kernel_ffe(sparse_matrix &M, - vector> &K) { - kernel_ffe(M, K); - } }; } // namespace simplex From aff6a4388a7cc8a70e3c8bfcc04fccd58c575578 Mon Sep 17 00:00:00 2001 From: Arie Gurfinkel Date: Sat, 14 May 2022 09:23:23 -0400 Subject: [PATCH 31/78] another implementation of ffe kernel in sparse_matrix_ops --- src/math/simplex/sparse_matrix_ops.h | 74 ++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/src/math/simplex/sparse_matrix_ops.h b/src/math/simplex/sparse_matrix_ops.h index 0d817d86831..8cc1fdd3e46 100644 --- a/src/math/simplex/sparse_matrix_ops.h +++ b/src/math/simplex/sparse_matrix_ops.h @@ -160,5 +160,79 @@ class sparse_matrix_ops { vector> &K) { kernel_ffe(M, K); } + + + template + static void kernel_ffe(sparse_matrix &M, sparse_matrix &K, + vector &basics) { + using scoped_numeral = typename Ext::scoped_numeral; + + /// Based on George Nakos, Peter R. Turner, Robert M. Williams: + /// Fraction-free algorithms for linear and polynomial equations. SIGSAM + /// Bull. 31(3): 11-19 (1997) + vector d, c; + unsigned n_vars = M.num_vars(), n_rows = M.num_rows(); + c.resize(n_rows, 0u); + d.resize(n_vars, 0u); + + auto &m = M.get_manager(); + scoped_numeral m_ik(m); + scoped_numeral m_jk(m); + scoped_numeral last_pv(m); + + m.set(last_pv, 1); + + for (unsigned k = 0; k < n_vars; ++k) { + d[k] = 0; + for (auto [row, row_entry] : M.get_rows(k)) { + if (c[row.id()] != 0) continue; + auto &m_jk_ref = row_entry->m_coeff; + if (m.is_zero(m_jk_ref)) + // XXX: should not happen, the matrix is sparse + continue; + + // this a pivot column + m.set(m_jk, m_jk_ref); + + // ensure that pivot is negative + if (m.is_pos(m_jk_ref)) { M.neg(row); } + else { m.neg(m_jk); } + // m_jk is abs(M[j]][k]) + + for (auto row_i : M.get_rows()) { + if (row_i.id() == row.id()) continue; + + m.set(m_ik, M.get_coeff(row_i, k)); + // row_i *= m_jk + M.mul(row_i, m_jk); + if (!m.is_zero(m_ik)) { + // row_i += m_ik * row + M.add(row_i, m_ik, row); + } + M.div(row_i, last_pv); + } + c[row.id()] = k + 1; + d[k] = row.id() + 1; + m.set(last_pv, m_jk); + break; + } + } + + K.ensure_var(n_vars - 1); + for (unsigned k = 0; k < n_vars; ++k) { + if (d[k] != 0) continue; + auto row = K.mk_row(); + basics.push_back(k); + for (unsigned i = 0; i < n_vars; ++i) { + if (d[i] > 0) { + auto r = typename sparse_matrix::row(d[i] - 1); + K.add_var(row, M.get_coeff(r, k), i); + } + else if (i == k) + K.add_var(row, last_pv, i); + } + } + } + }; } // namespace simplex From 7ff6c7704b85a495f946d1744bd9a7af053b33cb Mon Sep 17 00:00:00 2001 From: Arie Gurfinkel Date: Sat, 14 May 2022 09:22:30 -0400 Subject: [PATCH 32/78] Re-do arith_kernel and convex_closure --- src/muz/spacer/spacer_arith_kernel.cpp | 71 ++++++- src/muz/spacer/spacer_arith_kernel.h | 7 +- src/muz/spacer/spacer_arith_kernel_sage.cpp | 6 +- src/muz/spacer/spacer_convex_closure.cpp | 216 ++++++++------------ src/muz/spacer/spacer_convex_closure.h | 59 ++---- 5 files changed, 176 insertions(+), 183 deletions(-) diff --git a/src/muz/spacer/spacer_arith_kernel.cpp b/src/muz/spacer/spacer_arith_kernel.cpp index 519fd40cba9..7f33b243cae 100644 --- a/src/muz/spacer/spacer_arith_kernel.cpp +++ b/src/muz/spacer/spacer_arith_kernel.cpp @@ -20,12 +20,15 @@ Module Name: #include "muz/spacer/spacer_arith_kernel.h" +#include "math/simplex/sparse_matrix_def.h" +#include "math/simplex/sparse_matrix_ops.h" + using namespace spacer; bool spacer_arith_kernel::compute_kernel() { SASSERT(m_matrix.num_rows() > 1); - if (m_matrix.compute_linear_deps(m_kernel)) { + if (false && m_matrix.compute_linear_deps(m_kernel)) { // the matrix cannot be reduced further if (m_matrix.num_cols() - m_kernel.num_rows() <= 1) return true; @@ -33,9 +36,71 @@ bool spacer_arith_kernel::compute_kernel() { SASSERT(m_matrix.num_cols() > 2); } if (m_matrix.num_cols() > 2) m_st.m_failed++; - if (m_plugin && m_matrix.num_cols() > 2) { - return m_plugin->compute_kernel(m_matrix, m_kernel); + if (m_plugin /* && m_matrix.num_cols() > 2 */) { + return m_plugin->compute_kernel(m_matrix, m_kernel, m_basic_vars); } return false; } +namespace { +class simplex_arith_kernel_plugin : public spacer_arith_kernel::plugin { + public: + simplex_arith_kernel_plugin() {} + + bool compute_kernel(const spacer_matrix &in, spacer_matrix &out, + vector &basics) override { + using qmatrix = simplex::sparse_matrix; + unsynch_mpq_manager m; + qmatrix qmat(m); + + // extra column for column of 1 + qmat.ensure_var(in.num_cols()); + + for (unsigned i = 0, n_rows = in.num_rows(); i < n_rows; ++i) { + auto row_id = qmat.mk_row(); + unsigned j, n_cols; + for (j = 0, n_cols = in.num_cols(); j < n_cols; ++j) { + qmat.add_var(row_id, in.get(i, j).to_mpq(), j); + } + qmat.add_var(row_id, rational::one().to_mpq(), n_cols); + } + TRACE("gg", qmat.display(tout);); + + qmatrix kern(m); + simplex::sparse_matrix_ops::kernel_ffe(qmat, kern, + basics); + + out.reset(kern.num_vars()); + vector vec; + for (auto row : kern.get_rows()) { + vec.reserve(kern.num_vars(), rational(0)); + for (auto &[coeff, v] : kern.get_row(row)) { + vec[v] = rational(coeff); + } + out.add_row(vec); + } + + TRACE("gg", { + tout << "Computed kernel\n"; + qmat.display(tout); + tout << "\n"; + kern.display(tout); + tout << "\n"; + tout << "basics: " << basics << "\n"; + }); + return out.num_rows() > 0; + } + + void collect_statistics(statistics &st) const override {} + void reset_statistics() override {} + void reset() override {} +}; + +} // namespace + +namespace spacer { + +spacer_arith_kernel::plugin *mk_simplex_kernel_plugin() { + return alloc(simplex_arith_kernel_plugin); +} +} // namespace spacer diff --git a/src/muz/spacer/spacer_arith_kernel.h b/src/muz/spacer/spacer_arith_kernel.h index ea4f2d061c3..4d81cbaa1ad 100644 --- a/src/muz/spacer/spacer_arith_kernel.h +++ b/src/muz/spacer/spacer_arith_kernel.h @@ -32,7 +32,8 @@ class spacer_arith_kernel { public: virtual ~plugin() {} virtual bool compute_kernel(const spacer_matrix &in_matrix, - spacer_matrix &out_kernel) = 0; + spacer_matrix &out_kernel, + vector &basics) = 0; virtual void collect_statistics(statistics &st) const = 0; virtual void reset_statistics() = 0; virtual void reset() = 0; @@ -51,6 +52,8 @@ class spacer_arith_kernel { /// Output matrix representing the kernel spacer_matrix m_kernel; + /// columns in the kernel that correspond to basic vars + vector m_basic_vars; scoped_ptr m_plugin; @@ -68,6 +71,7 @@ class spacer_arith_kernel { bool operator()() { return compute_kernel(); } const spacer_matrix &get_kernel() const { return m_kernel; } + const vector &get_basic_vars() const { return m_basic_vars; } void reset() { m_kernel = spacer_matrix(0, 0); @@ -86,5 +90,6 @@ class spacer_arith_kernel { /// \brief Kernel computation using Sage package spacer_arith_kernel::plugin *mk_sage_plugin(); +spacer_arith_kernel::plugin *mk_simplex_kernel_plugin(); } // namespace spacer diff --git a/src/muz/spacer/spacer_arith_kernel_sage.cpp b/src/muz/spacer/spacer_arith_kernel_sage.cpp index f815fa6422e..fadadad3605 100644 --- a/src/muz/spacer/spacer_arith_kernel_sage.cpp +++ b/src/muz/spacer/spacer_arith_kernel_sage.cpp @@ -192,7 +192,8 @@ class sage_arith_kernel_plugin : public spacer_arith_kernel::plugin { scoped_ptr m_sage; bool compute_kernel(const spacer_matrix &in_matrix, - spacer_matrix &out_kernel) override; + spacer_matrix &out_kernel, + vector &basics) override; std::string matrix_to_string(const spacer_matrix &matrix) const; public: @@ -225,7 +226,8 @@ sage_arith_kernel_plugin::matrix_to_string(const spacer_matrix &matrix) const { } bool sage_arith_kernel_plugin::compute_kernel(const spacer_matrix &in_matrix, - spacer_matrix &out_kernel) { + spacer_matrix &out_kernel, + vector &basics) { scoped_watch _w_(m_st.watch); char temp_name[] = "/tmp/spacersage.XXXXXX"; diff --git a/src/muz/spacer/spacer_convex_closure.cpp b/src/muz/spacer/spacer_convex_closure.cpp index 94919c7d218..099a479327f 100644 --- a/src/muz/spacer/spacer_convex_closure.cpp +++ b/src/muz/spacer/spacer_convex_closure.cpp @@ -19,6 +19,7 @@ Module Name: --*/ #include "muz/spacer/spacer_convex_closure.h" +#include "ast/rewriter/th_rewriter.h" namespace { bool is_int_matrix(const spacer::spacer_matrix &matrix) { @@ -46,26 +47,34 @@ bool is_congruent_mod(const vector &data, rational m) { return true; } -app *mk_bvadd(ast_manager &m, unsigned num, expr *const *args) { +app *mk_bvadd(bv_util &bv, unsigned num, expr *const *args) { if (num == 0) return nullptr; if (num == 1) return is_app(args[0]) ? to_app(args[0]) : nullptr; - bv_util bv(m); if (num == 2) { return bv.mk_bv_add(args[0], args[1]); } /// XXX no mk_bv_add for n-ary bv_add - return m.mk_app(bv.get_fid(), OP_BADD, num, args); + return bv.get_manager().mk_app(bv.get_fid(), OP_BADD, num, args); } } // namespace namespace spacer { +convex_closure::convex_closure(ast_manager &_m) + : m(_m), m_arith(m), m_bv(m), m_bv_sz(0), m_enable_implicit(true), m_dim(0), + m_data(0, 0), m_col_vars(m), m_kernel(m_data), m_alphas(m), + m_implicit_cc(m), m_explicit_cc(m) { + + m_kernel.set_plugin(mk_simplex_kernel_plugin()); +} void convex_closure::reset(unsigned n_cols) { + m_dim = n_cols; m_kernel.reset(); - m_data.reset(n_cols); + m_data.reset(m_dim); m_col_vars.reset(); - m_dim = n_cols; m_col_vars.reserve(m_dim); + m_dead_cols.reset(); + m_dead_cols.reserve(m_dim, false); m_alphas.reset(); m_bv_sz = 0; m_enable_implicit = true; @@ -81,7 +90,7 @@ void convex_closure::collect_statistics(statistics &st) const { // call m_kernel to reduce dimensions of m_data // return the rank of m_data -unsigned convex_closure::reduce_dim() { +unsigned convex_closure::reduce() { if (m_dim <= 1) return m_dim; bool has_kernel = m_kernel.compute_kernel(); @@ -97,6 +106,11 @@ unsigned convex_closure::reduce_dim() { SASSERT(ker.num_cols() == m_dim + 1); // m_dim - ker.num_rows() is the number of variables that have no linear // dependencies + + for (auto v : m_kernel.get_basic_vars()) + // XXX sometimes a constant can be basic, need to find a way to + // switch it to var + if (v < m_dead_cols.size()) m_dead_cols[v] = true; return m_dim - ker.num_rows(); } @@ -105,84 +119,45 @@ unsigned convex_closure::reduce_dim() { // row * m_col_vars = 0 // // In the equality, exactly one variable from m_col_vars is on the lhs -void convex_closure::mk_row_eq(const vector &row, expr_ref &out) { - // contains the right hand side of an equality - expr_ref_buffer rhs(m); - // index of first non zero element in row - int pv = -1; - // are we constructing rhs or lhs - bool is_lhs = true; - // coefficient of m_col_vars[pv] - rational coeff(1); - - // the elements in row are the coefficients of m_col_vars - // some elements should go to the rhs, in which case the signs are - // changed - for (unsigned j = 0, sz = row.size(); j < sz; j++) { - rational val = row.get(j); - SASSERT(val.is_int()); - if (val.is_zero()) continue; - if (is_lhs) { - // Cannot re-write the last element - if (j == row.size() - 1) continue; - SASSERT(pv == -1); - pv = j; - is_lhs = false; - // In integer echelon form, the pivot need not be 1 - coeff = val; +void convex_closure::kernel_row2eq(const vector &row, expr_ref &out) { + expr_ref_buffer lhs(m); + expr_ref e1(m); + + bool is_int = false; + for (unsigned i = 0, sz = row.size(); i < sz; ++i) { + rational val_i = row.get(i); + if (val_i.is_zero()) continue; + SASSERT(val_i.is_int()); + + if (i < sz - 1) { + e1 = m_col_vars.get(i); + is_int |= m_arith.is_int(e1); + mul_by_rat(e1, val_i); } else { - expr_ref prod(m); - if (j != row.size() - 1) { - prod = m_col_vars.get(j); - mul_by_rat(prod, -1 * val); - } else { - auto *col_v = m_col_vars.get(pv); - if (m_arith.is_int_real(col_v)) { - prod = m_arith.mk_numeral(-1 * val, m_arith.is_int(col_v)); - } else if (m_bv.is_bv(col_v)) { - prod = m_bv.mk_numeral(-1 * val, m_bv_sz); - } else { - SASSERT(false); - } - } - SASSERT(prod); - rhs.push_back(prod); + e1 = mk_numeral(rational::one(), is_int); } + lhs.push_back(e1); } - // make sure that there is a non-zero entry - SASSERT(pv != -1); - - if (rhs.size() == 0) { - expr_ref _rhs(m); - auto *col_var = m_col_vars.get(pv); - if (m_arith.is_int_real(col_var)) - _rhs = - m_arith.mk_numeral(rational::zero(), m_arith.is_int(col_var)); - else if (m_bv.is_bv(col_var)) - _rhs = m_bv.mk_numeral(rational::zero(), m_bv_sz); - out = m_arith.mk_eq(col_var, _rhs); - return; - } - - out = !has_bv() ? m_arith.mk_add(rhs.size(), rhs.data()) - : mk_bvadd(m, rhs.size(), rhs.data()); - expr_ref pv_var(m); - pv_var = m_col_vars.get(pv); - mul_by_rat(pv_var, coeff); + e1 = !has_bv() ? mk_add(lhs) : mk_bvadd(m_bv, lhs.size(), lhs.data()); + e1 = m.mk_eq(e1, mk_numeral(rational::zero(), is_int)); - out = m.mk_eq(pv_var, out); - TRACE("cvx_dbg", tout << "rewrote " << mk_pp(m_col_vars.get(pv), m) - << " into " << out << "\n";); + // revisit this simplification step, it is here only to prevent/simplify + // formula construction everywhere else + params_ref params; + params.set_bool("som", true); + params.set_bool("flat", true); + th_rewriter rw(m, params); + rw(e1, out); } /// Generates linear equalities implied by m_data /// /// the linear equalities are m_kernel * m_col_vars = 0 (where * is matrix -/// multiplication) the new equalities are stored in m_col_vars for each row [0, -/// 1, 0, 1 , 1] in m_kernel, the equality v1 = -1*v3 + -1*1 is +/// multiplication) the new equalities are stored in m_col_vars for each row +/// [0, 1, 0, 1 , 1] in m_kernel, the equality v1 = -1*v3 + -1*1 is /// constructed and stored at index 1 of m_col_vars -void convex_closure::generate_implied_equalities(expr_ref_vector &out) { +void convex_closure::kernel2fmls(expr_ref_vector &out) { // assume kernel has been computed already const spacer_matrix &kern = m_kernel.get_kernel(); SASSERT(kern.num_rows() > 0); @@ -190,16 +165,36 @@ void convex_closure::generate_implied_equalities(expr_ref_vector &out) { expr_ref eq(m); for (unsigned i = kern.num_rows(); i > 0; i--) { auto &row = kern.get_row(i - 1); - mk_row_eq(row, eq); + kernel_row2eq(row, eq); out.push_back(eq); } } +expr *convex_closure::mk_add(const expr_ref_buffer &vec) { + SASSERT(!vec.empty()); + expr_ref s(m); + if (vec.size() == 1) { + return vec[0]; + } else if (vec.size() > 1) { + return m_arith.mk_add(vec.size(), vec.data()); + } + + UNREACHABLE(); + return nullptr; +} + +expr *convex_closure::mk_numeral(const rational &n, bool is_int) { + if (!has_bv()) + return m_arith.mk_numeral(n, is_int); + else + return m_bv.mk_numeral(n, m_bv_sz); +} + /// Construct the equality ((m_alphas . m_data[*][i]) = m_col_vars[i]) /// /// Where . is the dot product, m_data[*][i] is /// the ith column of m_data. Add the result to res_vec. -void convex_closure::mk_col_sum(unsigned col, expr_ref_vector &out) { +void convex_closure::cc_col2eq(unsigned col, expr_ref_vector &out) { SASSERT(!has_bv()); expr_ref_buffer sum(m); @@ -219,11 +214,7 @@ void convex_closure::mk_col_sum(unsigned col, expr_ref_vector &out) { } SASSERT(!sum.empty()); expr_ref s(m); - if (sum.size() == 1) { - s = sum[0]; - } else if (sum.size() > 1) { - s = m_arith.mk_add(sum.size(), sum.data()); - } + s = mk_add(sum); expr_ref v(m); expr *vi = m_col_vars.get(col); @@ -231,7 +222,7 @@ void convex_closure::mk_col_sum(unsigned col, expr_ref_vector &out) { out.push_back(m.mk_eq(s, v)); } -void convex_closure::syntactic_convex_closure(expr_ref_vector &out) { +void convex_closure::cc2fmls(expr_ref_vector &out) { sort_ref real_sort(m_arith.mk_real(), m); expr_ref zero(m_arith.mk_real(rational::zero()), m); @@ -245,7 +236,7 @@ void convex_closure::syntactic_convex_closure(expr_ref_vector &out) { } for (unsigned k = 0, sz = m_col_vars.size(); k < sz; k++) { - if (m_col_vars.get(k)) mk_col_sum(k, out); + if (m_col_vars.get(k) && !m_dead_cols[k]) cc_col2eq(k, out); } //(\Sum j . m_new_vars[j]) = 1 @@ -260,8 +251,8 @@ void convex_closure::syntactic_convex_closure(expr_ref_vector &out) { // corresponding d // TODO: find the largest divisor, not the smallest. // TODO: improve efficiency -bool convex_closure::generate_div_constraint(const vector &data, - rational &m, rational &d) { +bool convex_closure::infer_div_pred(const vector &data, rational &m, + rational &d) { TRACE("cvx_dbg_verb", { tout << "computing div constraints for "; for (rational r : data) tout << r << " "; @@ -294,12 +285,12 @@ bool convex_closure::compute() { scoped_watch _w_(m_st.watch); SASSERT(is_int_matrix(m_data)); - unsigned rank = reduce_dim(); + unsigned rank = reduce(); // store dim var before rewrite expr_ref var(m_col_vars.get(0), m); if (rank < dims()) { m_st.m_num_reductions++; - generate_implied_equalities(m_explicit_cc); + kernel2fmls(m_explicit_cc); TRACE("cvx_dbg", tout << "Linear equalities true of the matrix " << mk_and(m_explicit_cc) << "\n";); } @@ -312,7 +303,7 @@ bool convex_closure::compute() { } else if (rank > 1) { if (m_enable_implicit) { TRACE("subsume", tout << "Computing syntactic convex closure\n";); - syntactic_convex_closure(m_implicit_cc); + cc2fmls(m_implicit_cc); } else { return false; } @@ -320,49 +311,9 @@ bool convex_closure::compute() { } SASSERT(rank == 1); - do_1dim_convex_closure(var, m_explicit_cc); + cc_1dim(var, m_explicit_cc); return true; } -/// Compute the convex closure of points in m_data -/// -/// Returns true if the convex closure is syntactic -bool convex_closure::closure(expr_ref_vector &out) { - scoped_watch _w_(m_st.watch); - SASSERT(is_int_matrix(m_data)); - - unsigned red_dim = reduce_dim(); - - // store dim var before rewrite - expr_ref var(m_col_vars.get(0), m); - if (red_dim < dims()) { - m_st.m_num_reductions++; - generate_implied_equalities(out); - TRACE("cvx_dbg", tout << "Linear equalities true of the matrix " - << mk_and(out) << "\n";); - } - - m_st.m_max_dim = std::max(m_st.m_max_dim, red_dim); - - if (red_dim > 1) { - // there is no alternative to syntactic convex closure right now - // syntactic convex closure does not support BV - if (m_enable_implicit) { - TRACE("subsume", tout << "Computing syntactic convex closure\n";); - syntactic_convex_closure(out); - } else { - out.reset(); - return false; - } - return true; - } - - // zero dimensional convex closure - if (red_dim == 0) { return false; } - - SASSERT(red_dim == 1); - do_1dim_convex_closure(var, out); - return false; -} // construct the formula result_var <= bnd or result_var >= bnd expr *convex_closure::mk_le_ge(expr *v, rational n, bool is_le) { @@ -379,8 +330,7 @@ expr *convex_closure::mk_le_ge(expr *v, rational n, bool is_le) { return nullptr; } -void convex_closure::do_1dim_convex_closure(const expr_ref &var, - expr_ref_vector &out) { +void convex_closure::cc_1dim(const expr_ref &var, expr_ref_vector &out) { // XXX assumes that var corresponds to col 0 @@ -409,14 +359,14 @@ void convex_closure::do_1dim_convex_closure(const expr_ref &var, data.reset(); m_data.get_col(j, data); std::sort(data.begin(), data.end(), gt_proc); - if (generate_div_constraint(data, cr, off)) { - out.push_back(mk_mod_eq(v, cr, off)); + if (infer_div_pred(data, cr, off)) { + out.push_back(mk_eq_mod(v, cr, off)); } } } } -expr *convex_closure::mk_mod_eq(expr *v, rational d, rational r) { +expr *convex_closure::mk_eq_mod(expr *v, rational d, rational r) { expr *res = nullptr; if (!m_arith.is_int(v)) { res = m.mk_eq(m_arith.mk_mod(v, m_arith.mk_int(d)), m_arith.mk_int(r)); diff --git a/src/muz/spacer/spacer_convex_closure.h b/src/muz/spacer/spacer_convex_closure.h index ed0e9e85510..c3676ddf518 100644 --- a/src/muz/spacer/spacer_convex_closure.h +++ b/src/muz/spacer/spacer_convex_closure.h @@ -63,6 +63,7 @@ class convex_closure { // Variables naming columns in `m_data` // \p m_col_vars[k] is a var for column \p k expr_ref_vector m_col_vars; + vector m_dead_cols; // Kernel of \p m_data // Set at the end of computation @@ -76,7 +77,7 @@ class convex_closure { expr_ref_vector m_explicit_cc; /// Reduces dimension of \p m_data and returns its rank - unsigned reduce_dim(); + unsigned reduce(); /// Constructs an equality corresponding to a given row in the kernel /// @@ -85,50 +86,46 @@ class convex_closure { /// where row is a row vector and m_col_vars is a column vector. /// However, the equality is put in a form so that exactly one variable from /// \p m_col_vars is on the LHS - void mk_row_eq(const vector &row, expr_ref &out); + void kernel_row2eq(const vector &row, expr_ref &out); /// Construct all linear equations implied by points in \p m_data /// This is defined by \p m_kernel * m_col_vars = 0 - void generate_implied_equalities(expr_ref_vector &out); + void kernel2fmls(expr_ref_vector &out); /// Compute syntactic convex closure of \p m_data - void syntactic_convex_closure(expr_ref_vector &out); + void cc2fmls(expr_ref_vector &out); /// Construct the equality ((m_alphas . m_data[*][k]) = m_col_vars[k]) /// /// \p m_data[*][k] is the kth column of m_data /// The equality is added to \p out. - void mk_col_sum(unsigned k, expr_ref_vector &out); + void cc_col2eq(unsigned k, expr_ref_vector &out); /// Compute one dimensional convex closure over \p var /// /// \p var is the dimension over which convex closure is computed /// Result is stored in \p out - void do_1dim_convex_closure(const expr_ref &var, expr_ref_vector &out); + void cc_1dim(const expr_ref &var, expr_ref_vector &out); /// Computes div constraint implied by a set of data points /// /// Finds the largest numbers \p m, \p d such that \p m_data[i] mod m = d /// Returns true if successful - bool generate_div_constraint(const vector &data, rational &m, - rational &d); + bool infer_div_pred(const vector &data, rational &m, rational &d); /// Constructs a formula \p var ~ n , where ~ = is_le ? <= : >= - expr *mk_le_ge(expr* var, rational n, bool is_le); + expr *mk_le_ge(expr *var, rational n, bool is_le); - /// Returns (v % d == r) - expr *mk_mod_eq(expr *v, rational d, rational r); + expr *mk_add(const expr_ref_buffer &vec); + expr *mk_numeral(const rational &n, bool is_int); + + /// Returns equality (v = r mod d) + expr *mk_eq_mod(expr *v, rational d, rational r); bool has_bv() { return m_bv_sz > 0; } public: - convex_closure(ast_manager &manager, bool use_sage) - : m(manager), m_arith(m), m_bv(m), m_bv_sz(0), m_enable_implicit(true), - m_dim(0), m_data(0, 0), m_col_vars(m), m_kernel(m_data), m_alphas(m), - m_implicit_cc(m), m_explicit_cc(m) { - - if (use_sage) m_kernel.set_plugin(mk_sage_plugin()); - } + convex_closure(ast_manager &_m); /// Resets all data points /// @@ -154,38 +151,12 @@ class convex_closure { /// \brief Return number of dimensions of each point unsigned dims() const { return m_dim; } - /// \brief Add a one-dimensional point to convex closure - void add_row(rational x) { - SASSERT(dims() == 1); - vector row; - row.reserve(1, x); - m_data.add_row(row); - } - - /// \brief Add a two-dimensional point to convex closure - void add_row(rational x, rational y) { - SASSERT(dims() == 2); - vector row; - row.reserve(2); - row[0] = x; - row[1] = y; - m_data.add_row(row); - } - /// \brief Add an n-dimensional point to convex closure void add_row(const vector &point) { SASSERT(point.size() == dims()); m_data.add_row(point); }; - /// \brief Compute convex closure of the current set of points - /// - /// Returns true if successful and \p out is an exact convex closure - /// Returns false if \p out is an over-approximation - bool closure(expr_ref_vector &out); - - bool operator()(expr_ref_vector &out) { return this->closure(out); } - bool operator()() { return this->compute(); } bool compute(); bool has_implicit() { return !m_implicit_cc.empty(); } From 0bbb723f38172fedb4e33b1557a07c60b419587c Mon Sep 17 00:00:00 2001 From: Arie Gurfinkel Date: Sat, 14 May 2022 09:24:57 -0400 Subject: [PATCH 33/78] update spacer_global_generalization for new subsumer --- src/muz/spacer/spacer_global_generalizer.cpp | 16 +++------------- src/muz/spacer/spacer_global_generalizer.h | 2 +- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/src/muz/spacer/spacer_global_generalizer.cpp b/src/muz/spacer/spacer_global_generalizer.cpp index 035e8280ae0..7e7673de3e3 100644 --- a/src/muz/spacer/spacer_global_generalizer.cpp +++ b/src/muz/spacer/spacer_global_generalizer.cpp @@ -118,15 +118,6 @@ bool contains_reals(const app_ref_vector &c) { return false; } -// Check whether there are Int constants in \p c -bool contains_ints(const app_ref_vector &c) { - arith_util m_arith(c.get_manager()); - for (auto *f : c) { - if (m_arith.is_int(f)) return true; - } - return false; -} - // Check whether \p sub contains a mapping to a bv_numeral. // return bv_size of the bv_numeral in the first such mapping. bool contains_bv(ast_manager &m, const substitution &sub, unsigned &sz) { @@ -387,8 +378,7 @@ void to_real(expr_ref &fml) { } // namespace namespace spacer { -lemma_global_generalizer::subsumer::subsumer(ast_manager &a_m, bool use_sage, - bool ground_pob) +lemma_global_generalizer::subsumer::subsumer(ast_manager &a_m, bool ground_pob) : m(a_m), m_arith(m), m_bv(m), m_tags(m), m_used_tags(0), m_col_names(m), m_ground_pob(ground_pob) { scoped_ptr factory( @@ -412,7 +402,7 @@ app *lemma_global_generalizer::subsumer::mk_fresh_tag() { lemma_global_generalizer::lemma_global_generalizer(context &ctx) : lemma_generalizer(ctx), m(ctx.get_ast_manager()), - m_subsumer(m, ctx.use_sage(), ctx.use_ground_pob()), + m_subsumer(m, ctx.use_ground_pob()), m_do_subsume(ctx.do_subsume()) {} void lemma_global_generalizer::operator()(lemma_ref &lemma) { @@ -656,7 +646,7 @@ bool lemma_global_generalizer::subsumer::subsume(const lemma_cluster &lc, app_ref_vector &bindings) { if (!is_handled(lc)) return false; - convex_closure cvx_closure(m, false); + convex_closure cvx_closure(m); reset(); setup_cvx_closure(cvx_closure, lc); diff --git a/src/muz/spacer/spacer_global_generalizer.h b/src/muz/spacer/spacer_global_generalizer.h index c09dd875a9e..e4dacaaadda 100644 --- a/src/muz/spacer/spacer_global_generalizer.h +++ b/src/muz/spacer/spacer_global_generalizer.h @@ -129,7 +129,7 @@ class lemma_global_generalizer : public lemma_generalizer { } public: - subsumer(ast_manager &m, bool use_sage, bool ground_pob); + subsumer(ast_manager &m, bool ground_pob); void collect_statistics(statistics &st) const; From 717cf1d81402576eaf8f2fab6872fe9684aa736b Mon Sep 17 00:00:00 2001 From: Arie Gurfinkel Date: Sat, 14 May 2022 09:25:12 -0400 Subject: [PATCH 34/78] remove spacer.gg.use_sage parameter --- src/muz/base/fp_params.pyg | 1 - src/muz/spacer/spacer_context.cpp | 1 - src/muz/spacer/spacer_context.h | 2 -- 3 files changed, 4 deletions(-) diff --git a/src/muz/base/fp_params.pyg b/src/muz/base/fp_params.pyg index e965788ed90..57a264768cd 100644 --- a/src/muz/base/fp_params.pyg +++ b/src/muz/base/fp_params.pyg @@ -184,7 +184,6 @@ def_module_params('fp', ('spacer.gg.concretize', BOOL, True, 'Enable global guidance concretize'), ('spacer.gg.conjecture', BOOL, True, 'Enable global guidance conjecture'), ('spacer.gg.subsume', BOOL, True, 'Enable global guidance subsume'), - ('spacer.gg.use_sage', BOOL, False, '(Debug) Use SAGE for kernel computation in gg.subsume'), ('spacer.use_iuc', BOOL, True, 'Enable Interpolating Unsat Core(IUC) for lemma generalization'), ('spacer.expand_bnd', BOOL, False, 'Enable expand-bound lemma generalization'), )) diff --git a/src/muz/spacer/spacer_context.cpp b/src/muz/spacer/spacer_context.cpp index cb1e41bf100..f5fe248fc31 100644 --- a/src/muz/spacer/spacer_context.cpp +++ b/src/muz/spacer/spacer_context.cpp @@ -2367,7 +2367,6 @@ void context::updt_params() { m_expand_bnd = m_params.spacer_expand_bnd(); m_gg_conjecture = m_params.spacer_gg_conjecture(); m_gg_subsume = m_params.spacer_gg_subsume(); - m_gg_use_sage = m_params.spacer_gg_use_sage(); m_gg_concretize = m_params.spacer_gg_concretize(); m_use_iuc = m_params.spacer_use_iuc(); diff --git a/src/muz/spacer/spacer_context.h b/src/muz/spacer/spacer_context.h index 763663a49f6..658232e4063 100644 --- a/src/muz/spacer/spacer_context.h +++ b/src/muz/spacer/spacer_context.h @@ -1199,7 +1199,6 @@ class context { bool m_expand_bnd; bool m_gg_conjecture; bool m_gg_subsume; - bool m_gg_use_sage; bool m_gg_concretize; bool m_use_iuc; unsigned m_push_pob_max_depth; @@ -1360,7 +1359,6 @@ class context { bool is_inductive(); - bool use_sage() { return m_gg_use_sage; } // close all parents of may pob when gas runs out void close_all_may_parents(pob_ref node); From f27c34602d68cb04c238b8a6e71773c24211b8be Mon Sep 17 00:00:00 2001 From: Arie Gurfinkel Date: Sat, 14 May 2022 10:08:40 -0400 Subject: [PATCH 35/78] cleanup of spacer_global_generalizer --- src/muz/spacer/spacer_global_generalizer.cpp | 359 +------------------ src/muz/spacer/spacer_global_generalizer.h | 21 -- 2 files changed, 1 insertion(+), 379 deletions(-) diff --git a/src/muz/spacer/spacer_global_generalizer.cpp b/src/muz/spacer/spacer_global_generalizer.cpp index 7e7673de3e3..e4ab4a22841 100644 --- a/src/muz/spacer/spacer_global_generalizer.cpp +++ b/src/muz/spacer/spacer_global_generalizer.cpp @@ -95,29 +95,6 @@ class to_real_stripper { } }; -struct compute_lcm_proc { - ast_manager &m; - arith_util m_arith; - rational m_val; - compute_lcm_proc(ast_manager &a_m) : m(a_m), m_arith(m), m_val(1) {} - void operator()(expr *n) const {} - void operator()(app *n) { - rational val; - if (m_arith.is_numeral(n, val)) { - m_val = lcm(denominator(abs(val)), m_val); - } - } -}; - -/// Check whether there are Real constants in \p c -bool contains_reals(const app_ref_vector &c) { - arith_util m_arith(c.get_manager()); - for (auto *f : c) { - if (m_arith.is_real(f)) return true; - } - return false; -} - // Check whether \p sub contains a mapping to a bv_numeral. // return bv_size of the bv_numeral in the first such mapping. bool contains_bv(ast_manager &m, const substitution &sub, unsigned &sz) { @@ -148,233 +125,6 @@ bool all_same_sz(ast_manager &m, const substitution &sub, unsigned sz) { return true; } -/// Check whether there is an equivalent of function \p f in LRA -bool has_lra_equiv(const expr_ref &f) { - ast_manager &m = f.m(); - arith_util a(m); - - // uninterpreted constants do not have arguments. So equivalent function - // exists. - if (is_uninterp_const(f)) return true; - return is_app(f) && - to_app(f)->get_decl()->get_family_id() == a.get_family_id(); -} - -/// Get lcm of all the denominators of all the rational values in e -rational compute_lcm(expr *e, ast_manager &m) { - compute_lcm_proc g(m); - for_each_expr(g, e); - TRACE("subsume_verb", - tout << "lcm of " << mk_pp(e, m) << " is " << g.m_val << "\n";); - return g.m_val; -} - -// clang-format off -/// Removes all occurrences of (to_real t) from \p fml where t is a constant -/// -/// Applies the following rewrites upto depth \p depth -/// v:Real --> mk_int(v) where v is a real value -/// (to_real v:Int) --> v -/// (to_int v) --> (strip_to_real v) -/// (store A (to_int (to_real i0)) ... (to_int (to_real iN)) k) --> (store A i0 ... iN -/// (strip_to_real k)) -/// (select A (to_int (to_real i0)) ... (to_int (to_real iN))) --> (select A i0 ... iN) -/// (op (to_real a0) ... (to_real aN)) --> (op a0 ... aN) where op is an -/// arithmetic operation -/// on all other formulas, do nothing -/// NOTE: cannot use a rewriter since we change the sort of fml -// clang-format on -void strip_to_real(expr_ref &fml, unsigned depth = 3) { - ast_manager &m = fml.get_manager(); - arith_util arith(m); - rational r; - - if (arith.is_numeral(fml, r)) { - SASSERT(denominator(r).is_one()); - fml = arith.mk_int(r); - return; - } - - if (depth == 0) { return; } - - if (arith.is_to_real(fml)) { - fml = to_app(fml)->get_arg(0); - return; - } - if (arith.is_to_int(fml) && arith.is_to_real(to_app(fml)->get_arg(0))) { - expr *arg = to_app(fml)->get_arg(0); - fml = to_app(arg)->get_arg(0); - return; - } - - if (!is_app(fml)) return; - - app *f_app = to_app(fml); - expr_ref_buffer new_args(m); - expr_ref child(m); - for (unsigned i = 0, sz = f_app->get_num_args(); i < sz; i++) { - child = f_app->get_arg(i); - strip_to_real(child, depth - 1); - new_args.push_back(child); - } - fml = m.mk_app(f_app->get_family_id(), f_app->get_decl_kind(), - new_args.size(), new_args.data()); - return; -} - -/// Coerces a rational inequality to a semantically equivalent inequality with -/// integer coefficients -/// -/// Works on arithmetic (in)equalities -/// if fml contains a mod, fml is not normalized -/// otherwise, lcm of fml is computed and lcm * fml is computed -void to_int_term(expr_ref &fml) { - ast_manager &m = fml.get_manager(); - arith_util arith(m); - - if (!(arith.is_arith_expr(fml) || m.is_eq(fml))) return; - if (!contains_real(fml)) return; - - app *fml_app = to_app(fml); - SASSERT(fml_app->get_num_args() == 2); - expr_ref lhs(fml_app->get_arg(0), m); - expr_ref rhs(fml_app->get_arg(1), m); - - // mod not supported - SASSERT(!contains_mod(fml)); - - rational lcm = compute_lcm(fml, m); - SASSERT(lcm != rational::zero()); - - mul_by_rat(lhs, lcm); - mul_by_rat(rhs, lcm); - - strip_to_real(lhs); - strip_to_real(rhs); - fml = - m.mk_app(fml_app->get_family_id(), fml_app->get_decl_kind(), lhs, rhs); -} - -/// Convert all fractional constants in \p fml to integers -void to_int(expr_ref &fml) { - ast_manager &m = fml.get_manager(); - arith_util arith(m); - expr_ref_vector fml_vec(m), new_fml(m); - expr_ref new_lit(m); - - flatten_and(fml, fml_vec); - for (auto *lit : fml_vec) { - new_lit = lit; - to_int_term(new_lit); - new_fml.push_back(new_lit); - } - fml = mk_and(new_fml); -} - -// clang-format off -/// Wrap integer uninterpreted constants in expression \p fml with (to_real) -/// -/// only supports arithmetic expressions -/// Applies the following rewrite rules upto depth \p depth -/// (to_real_term c) --> (c:Real) where c is a numeral -/// (to_real_term i:Int) --> (to_real i) where i is a constant/var -/// (to_real_term (select A i0:Int ... iN:Int)) --> (select A (to_int (to_real i0)) ... -/// (to_int (to_real iN))) -/// (to_real_term (store A i0:Int ... iN:Int k)) --> (store A (to_int (to_real i0)) ... -/// (to_int (to_real iN)) -/// (to_real_term k)) -/// (to_real_term (op (a0:Int) ... (aN:Int))) --> (op (to_real a0) ... (to_real aN)) -/// where op is -/// an arithmetic -/// operation -/// on all other formulas, do nothing -/// NOTE: cannot use a rewriter since we change the sort of fml -// clang-format on -static void to_real_term(expr_ref &fml, unsigned depth = 3) { - ast_manager &m = fml.get_manager(); - arith_util arith(m); - datatype_util datatype(m); - if (!arith.is_int_real(fml)) return; - rational r; - if (arith.is_numeral(fml, r)) { - fml = arith.mk_real(r); - return; - } - if (is_uninterp_const(fml)) { - if (arith.is_int(fml)) fml = arith.mk_to_real(fml); - return; - } - if (arith.is_to_real(fml)) { - expr *arg = to_app(fml)->get_arg(0); - if (arith.is_numeral(arg, r)) fml = arith.mk_real(r); - return; - } - - if (is_uninterp(fml)) return; - if (depth == 0) return; - SASSERT(is_app(fml)); - app *fml_app = to_app(fml); - expr *const *args = fml_app->get_args(); - unsigned num_args = fml_app->get_num_args(); - expr_ref_buffer new_args(m); - expr_ref child(m); - - if (!has_lra_equiv(fml)) { - new_args.push_back(args[0]); - for (unsigned i = 1; i < num_args; i++) { - child = args[i]; - to_real_term(child, depth - 1); - if (arith.is_int(args[i])) child = arith.mk_to_int(child); - SASSERT(args[i]->get_sort() == child->get_sort()); - new_args.push_back(child); - } - fml = m.mk_app(fml_app->get_decl(), new_args); - } else { - for (unsigned i = 0; i < num_args; i++) { - child = args[i]; - to_real_term(child, depth - 1); - new_args.push_back(child); - } - // The mk_app method selects the function sort based on the sort of - // new_args - fml = m.mk_app(fml_app->get_family_id(), fml_app->get_decl_kind(), - new_args.size(), new_args.data()); - } - return; -} - -/// Wrap integer uninterpreted constants in conjunction \p fml with -/// (to_real) -void to_real(expr_ref &fml) { - ast_manager &m = fml.get_manager(); - - // cannot use an expr_ref_buffer since flatten_and operates on - // expr_ref_vector - expr_ref_vector fml_vec(m), new_fml(m); - flatten_and(fml, fml_vec); - - expr_ref_buffer new_args(m); - expr_ref kid(m), new_f(m); - for (auto *lit : fml_vec) { - new_args.reset(); - app *lit_app = to_app(lit); - for (auto *arg : *lit_app) { - kid = arg; - to_real_term(kid); - new_args.push_back(kid); - } - // Uninterpreted functions cannot be created using the mk_app api that - // is being used. - if (is_uninterp(lit)) new_f = lit; - // use this api to change sorts in domain of f - else - new_f = m.mk_app(lit_app->get_family_id(), lit_app->get_decl_kind(), - new_args.size(), new_args.data()); - new_fml.push_back(new_f); - } - fml = mk_and(new_fml); -} - } // namespace namespace spacer { @@ -402,8 +152,7 @@ app *lemma_global_generalizer::subsumer::mk_fresh_tag() { lemma_global_generalizer::lemma_global_generalizer(context &ctx) : lemma_generalizer(ctx), m(ctx.get_ast_manager()), - m_subsumer(m, ctx.use_ground_pob()), - m_do_subsume(ctx.do_subsume()) {} + m_subsumer(m, ctx.use_ground_pob()), m_do_subsume(ctx.do_subsume()) {} void lemma_global_generalizer::operator()(lemma_ref &lemma) { scoped_watch _w_(m_st.watch); @@ -585,41 +334,6 @@ bool lemma_global_generalizer::subsumer::find_model( return false; } -///\p hard is a hard constraint and \p soft is a soft constraint that have -/// to be -/// satisfied by mdl -bool lemma_global_generalizer::subsumer::maxsat_with_model( - const expr_ref &hard, const expr_ref &soft, model_ref &out_model) { - TRACE("subsume_verb", - tout << "maxsat with model " << hard << " " << soft << "\n";); - SASSERT(is_ground(hard)); - - solver::scoped_push _sp(*m_solver); - m_solver->assert_expr(hard); - - expr_ref_buffer tags(m); - lbool res; - if (is_ground(soft)) { - tags.push_back(mk_fresh_tag()); - m_solver->assert_expr(m.mk_implies(tags.back(), soft)); - } - - res = m_solver->check_sat(tags.size(), tags.data()); - - // best-effort -- flip one of the soft constraints - // in the current use, this is guaranteed to be sat - if (res != l_true && !tags.empty()) { - unsigned sz = tags.size(); - tags.set(sz - 1, m.mk_not(tags[sz - 1])); - res = m_solver->check_sat(tags.size(), tags.data()); - } - - if (res != l_true) { return false; } - - m_solver->get_model(out_model); - return true; -} - /// Returns false if subsumption is not supported for \p lc bool lemma_global_generalizer::subsumer::is_handled(const lemma_cluster &lc) { // check whether all substitutions are to bv_numerals @@ -725,68 +439,6 @@ bool lemma_global_generalizer::subsumer::subsume(const lemma_cluster &lc, return over_approximate(new_post, full_cc); } -/// Eliminate m_dim_frsh_cnsts from \p cvx_cls -/// -/// Uses \p lc to get a model for mbp. -/// \p mlir indicates whether \p cvx_cls contains both ints and reals. -/// all vars that could not be eliminated are skolemized and added to \p -/// bindings -bool lemma_global_generalizer::subsumer::eliminate_vars( - expr_ref &cvx_pattern, const lemma_cluster &lc, bool mlir, - app_ref_vector &out_bindings) { - if (mlir) { - // coerce to real - to_real(cvx_pattern); - // coerce skolem constants to real - to_real_cnsts(); - - TRACE("subsume_verb", - tout << "To real produced " << cvx_pattern << "\n";); - } - - // Get a model to guide MBP. Attempt to get a model that does not satisfy - // any of the cubes in the cluster - - model_ref mdl; - expr_ref neg_cubes(m); - lc.get_conj_lemmas(neg_cubes); - if (!maxsat_with_model(cvx_pattern, neg_cubes, mdl)) { - TRACE("subsume", - tout << "Convex closure is unsat " << cvx_pattern << "\n";); - return false; - } - - SASSERT(mdl.get() != nullptr); - TRACE("subsume", tout << "calling mbp with " << cvx_pattern << " and\n" - << *mdl << "\n";); - - // MBP to eliminate existentially quantified variables - qe_project(m, m_col_names, cvx_pattern, *mdl.get(), true, true, - !m_ground_pob); - - TRACE("subsume", tout << "Pattern after mbp of computing cvx cls: " - << cvx_pattern << "\n";); - - if (!m_ground_pob && contains_reals(m_col_names)) { - TRACE("subsume", tout << "Could not eliminate non-integer variables\n" - << m_col_names << "\n";); - return false; - } - - SASSERT(!m_ground_pob || m_col_names.empty()); - - if (mlir) { to_int(cvx_pattern); } - - // If not all variables have been eliminated, skolemize and add bindings - // This creates quantified proof obligation that will be handled by QUIC3 - if (!m_col_names.empty()) { - SASSERT(!m_ground_pob); - skolemize_for_quic3(cvx_pattern, mdl, out_bindings); - } - - return true; -} - /// Find a weakening of \p a such that \p b ==> a /// /// Returns true on success and sets \p a to the result @@ -1004,15 +656,6 @@ void lemma_global_generalizer::subsumer::ground_free_vars(expr *pat, SASSERT(is_ground(out)); } -// convert all LIA constants in m_dim_frsh_cnsts to LRA constants using -// to_real -void lemma_global_generalizer::subsumer::to_real_cnsts() { - for (unsigned i = 0, sz = m_col_names.size(); i < sz; i++) { - auto *c = m_col_names.get(i); - if (!m_arith.is_real(c)) m_col_names.set(i, m_arith.mk_to_real(c)); - } -} - pob *lemma_global_generalizer::mk_concretize_pob(pob &n, model_ref &model) { expr_ref_vector new_post(m); spacer::pob_concretizer proc(m, model, n.get_concretize_pattern()); diff --git a/src/muz/spacer/spacer_global_generalizer.h b/src/muz/spacer/spacer_global_generalizer.h index e4dacaaadda..bff95d2980e 100644 --- a/src/muz/spacer/spacer_global_generalizer.h +++ b/src/muz/spacer/spacer_global_generalizer.h @@ -86,9 +86,6 @@ class lemma_global_generalizer : public lemma_generalizer { /// Create new vars to compute convex cls void mk_col_names(const lemma_cluster &lc); - /// Coerce LIA constants in \p m_dim_frsh_cnsts to LRA constants - void to_real_cnsts(); - void setup_cvx_closure(convex_closure &cc, const lemma_cluster &lc); /// Make \p fml ground using m_dim_frsh_cnsts. Store result in \p out @@ -97,28 +94,10 @@ class lemma_global_generalizer : public lemma_generalizer { /// Weaken \p a such that (and a) overapproximates \p b bool over_approximate(expr_ref_vector &a, const expr_ref b); - /// \p a is a hard constraint and \p b is a soft constraint that have to - /// be satisfied by \p mdl - bool maxsat_with_model(const expr_ref &hard, const expr_ref &soft, - model_ref &out_model); - bool find_model(const expr_ref_vector &cc, const expr_ref_vector &alphas, expr *bg, model_ref &out_model); - /// Eliminate m_dim_frsh_cnsts from \p cvx_cls - /// - /// Uses \p lc to get a model for mbp. - /// \p mlir indicates whether \p cvx_cls contains both ints and reals. - /// all vars that could not be eliminated are skolemized and added to \p - /// bindings - bool eliminate_vars(expr_ref &cvx_cls, const lemma_cluster &lc, - bool mlir, app_ref_vector &bindings); - - /// Add variables introduced by m_cvx_cls to the list of variables to be - /// eliminated - void add_cvx_cls_vars(); - bool is_numeral(const expr *e, rational &n) { return m_arith.is_numeral(e, n) || m_bv.is_numeral(e, n); } From 31b0fd643163b279b0ef80eeb92d2cc4037ba11d Mon Sep 17 00:00:00 2001 From: Arie Gurfinkel Date: Sat, 14 May 2022 10:14:59 -0400 Subject: [PATCH 36/78] Removed dependency on sage --- src/muz/spacer/CMakeLists.txt | 1 - src/muz/spacer/spacer_arith_kernel.h | 2 - src/muz/spacer/spacer_arith_kernel_sage.cpp | 372 -------------------- 3 files changed, 375 deletions(-) delete mode 100644 src/muz/spacer/spacer_arith_kernel_sage.cpp diff --git a/src/muz/spacer/CMakeLists.txt b/src/muz/spacer/CMakeLists.txt index 048f5d460e1..d05a9d43514 100644 --- a/src/muz/spacer/CMakeLists.txt +++ b/src/muz/spacer/CMakeLists.txt @@ -35,7 +35,6 @@ z3_add_component(spacer spacer_convex_closure.cpp spacer_conjecture.cpp spacer_arith_kernel.cpp - spacer_arith_kernel_sage.cpp COMPONENT_DEPENDENCIES arith_tactics core_tactics diff --git a/src/muz/spacer/spacer_arith_kernel.h b/src/muz/spacer/spacer_arith_kernel.h index 4d81cbaa1ad..683fed2ba02 100644 --- a/src/muz/spacer/spacer_arith_kernel.h +++ b/src/muz/spacer/spacer_arith_kernel.h @@ -88,8 +88,6 @@ class spacer_arith_kernel { } }; -/// \brief Kernel computation using Sage package -spacer_arith_kernel::plugin *mk_sage_plugin(); spacer_arith_kernel::plugin *mk_simplex_kernel_plugin(); } // namespace spacer diff --git a/src/muz/spacer/spacer_arith_kernel_sage.cpp b/src/muz/spacer/spacer_arith_kernel_sage.cpp deleted file mode 100644 index fadadad3605..00000000000 --- a/src/muz/spacer/spacer_arith_kernel_sage.cpp +++ /dev/null @@ -1,372 +0,0 @@ -/**++ -Copyright (c) 2020 Arie Gurfinkel - -Module Name: - - spacer_arith_kernel_sage.cpp - -Abstract: - - Matrix kernel computation using Sage - -Author: - - Hari Govind - Arie Gurfinkel - -Notes: - - USED FOR DEBUGGING AND PROTOTYPING ONLY!!! ---*/ - -#include "muz/spacer/spacer_arith_kernel.h" -#include "util/stopwatch.h" -#include "util/util.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace spacer; - -namespace { -/** -Abstracts interface to Sage. - -Supports initialization and writing to Sage. - -To get output from Sage, write to file and read from the file. - -HG: Could not find standard methods to convert file descriptors to streams. -*/ -class Sage { - FILE *m_out; - FILE *m_in; - std::string tmp_name; - pid_t child_pid; - - /// Test sage communication - bool test(); - - public: - /// Start sage interface - Sage(); - ~Sage() { - kill(child_pid, SIGKILL); - int status; - waitpid(child_pid, &status, 0); - } - FILE *get_ostream() const { return m_out; } - FILE *get_istream() const { return m_in; } -}; - -Sage::Sage() { - int to_sage_pipe[2]; - int from_sage_pipe[2]; - int ok = pipe(to_sage_pipe); - if (ok) { - perror("sage pipe1"); - exit(1); - } - ok = pipe(from_sage_pipe); - if (ok) { - perror("sage pipe2"); - exit(1); - } - - pid_t pid = fork(); - if (pid) { - m_out = fdopen(to_sage_pipe[1], "w"); - m_in = fdopen(from_sage_pipe[0], "r"); - - /* parent */ - close(to_sage_pipe[0]); - close(from_sage_pipe[1]); - - child_pid = pid; - if (!test()) { - TRACE("sage-interface", tout << "Sage test failed \n";); - } - } else if (pid == 0) { - /* child */ - - // setup file descriptor - close(to_sage_pipe[1]); - close(from_sage_pipe[0]); - dup2(to_sage_pipe[0], STDIN_FILENO); - dup2(from_sage_pipe[1], STDOUT_FILENO); - - // setup arguments, assume sage is in PATH - char *const argv[3] = {(char *)"sage", NULL, NULL}; - // XXX: sage complains that it can't find $HOME, but works regardless - execvp("sage", argv); - perror("execvpe for sage"); - } else { - perror("fork"); - exit(1); - } -} - -bool Sage::test() { - char temp_name[] = "/tmp/spacersage.XXXXXX"; - int tmp_fd = mkstemp(temp_name); - if (tmp_fd == -1) { - // Error: failed to create temp file - TRACE("sage-interface", tout << "failed to create temp file\n";); - return false; - } - TRACE("sage-interface", - tout << "writing test output to " << temp_name << "\n";); - fprintf(m_out, "f = open (\"\%s\", 'w')\n", temp_name); - // Do stuff - fprintf(m_out, "print >>f, 2 + 2\n"); - fprintf(m_out, "f.close()\n"); - fflush(m_out); - // indicate that sage is done by printing to pipe - fprintf(m_out, "print \"\\nok\\n\"\n"); - fprintf(m_out, "sys.stdout.flush()\n"); - fflush(m_out); - - // first wait for sage to write ok - char *std_ok = nullptr; - size_t n = 0; - ssize_t t = 0; - // read all the lines printed by sage until we get okay - // will block if Sage not found :( - do { - t = getline(&std_ok, &n, m_in); - if (t == -1 || feof(m_in) || ferror(m_in)) { - TRACE("sage-interface", - tout << "error while reading from sage pipe \n";); - return false; - } - CTRACE("sage-interface-verb", t > 0, - tout << "got sage std output " << std_ok << "\n";); - } while (strcmp(std_ok, "ok\n") != 0); - // delete object allocated by getline - delete std_ok; - TRACE("sage-interface", tout << "got ok from sage \n";); - - // read output from file - std::ifstream ifs(temp_name); - int ok = -1; - if (!ifs.is_open()) { - TRACE("sage-interface", tout << "failed to open file\n";); - return false; - } - - ifs >> ok; - - if (ifs.bad()) { - TRACE("sage-interface", tout << "error when reading from file\n";); - ifs.close(); - close(tmp_fd); - std::remove(temp_name); - return false; - } - - TRACE("sage-interface", tout << "got sage output " << ok << "\n";); - ifs.close(); - close(tmp_fd); - // TODO: remove file even if sage/spacer terminates before reaching here - std::remove(temp_name); - return ok == 4; -} - -class sage_arith_kernel_plugin : public spacer_arith_kernel::plugin { - struct stats { - stopwatch watch; - unsigned m_sage_calls; - stats() { reset(); } - void reset() { - watch.reset(); - m_sage_calls = 0; - } - }; - stats m_st; - - scoped_ptr m_sage; - bool compute_kernel(const spacer_matrix &in_matrix, - spacer_matrix &out_kernel, - vector &basics) override; - std::string matrix_to_string(const spacer_matrix &matrix) const; - - public: - sage_arith_kernel_plugin() : m_sage(alloc(Sage)) {} - ~sage_arith_kernel_plugin() {} - - virtual void collect_statistics(statistics &st) const override { - st.update("time.spacer.sage", m_st.watch.get_seconds()); - st.update("SPACER sage calls", m_st.m_sage_calls); - } - virtual void reset_statistics() override { m_st.reset(); } - virtual void reset() override { m_sage = alloc(Sage); } -}; - -std::string -sage_arith_kernel_plugin::matrix_to_string(const spacer_matrix &matrix) const { - std::stringstream ss; - ss << "[\n"; - for (unsigned i = 0; i < matrix.num_rows(); i++) { - ss << "("; - for (unsigned j = 0; j < matrix.num_cols() - 1; j++) { - ss << matrix.get(i, j).to_string(); - ss << ", "; - } - ss << matrix.get(i, matrix.num_cols() - 1).to_string(); - ss << "),\n"; - } - ss << "]\n"; - return ss.str(); -} - -bool sage_arith_kernel_plugin::compute_kernel(const spacer_matrix &in_matrix, - spacer_matrix &out_kernel, - vector &basics) { - scoped_watch _w_(m_st.watch); - - char temp_name[] = "/tmp/spacersage.XXXXXX"; - int tmp_fd = mkstemp(temp_name); - if (tmp_fd == -1) { - // Error: failed to create temp file - perror("temp file create"); - exit(1); - } - m_st.m_sage_calls++; - TRACE("sage-interface", tout << temp_name << "\n";); - unsigned n_cols = in_matrix.num_cols(); - unsigned n_rows = in_matrix.num_rows(); - TRACE("sage-interface", tout << "Going to compute kernel of " << n_rows - << " by " << n_cols << " matrix \n" - << matrix_to_string(in_matrix) << "\n";); - - auto out = m_sage->get_ostream(); - fprintf(out, "f = open (\"\%s\", 'w')\n", temp_name); - - // construct matrix in sage - std::stringstream ss_stream; - ss_stream << " a = matrix(ZZ,"; - ss_stream << n_rows; - ss_stream << (", "); - ss_stream << (n_cols + 1); - ss_stream << (", ["); - for (unsigned i = 0; i < n_rows; i++) { - ss_stream << ("["); - for (unsigned j = 0; j < n_cols; j++) { - ss_stream << in_matrix.get(i, j).to_string(); - ss_stream << (", "); - } - ss_stream << ("1"); - ss_stream << ("], "); - } - ss_stream << ("]);\n"); - fprintf(out, "%s", ss_stream.str().c_str()); - fprintf(out, "c = a.right_kernel().basis();\n"); - fflush(out); - fprintf(out, "print >> f, len(c);\n"); - fprintf(out, "print >> f, c;\n"); - fprintf(out, "f.close()\n"); - fflush(out); - fprintf(out, "print \"\\nok\\n\"\n"); - fprintf(out, "sys.stdout.flush()\n"); - fflush(out); - - // first wait for sage to write ok - char *std_ok = nullptr; - size_t n = 0; - ssize_t t = 0; - // read all the lines printed by sage until we get okay - auto in = m_sage->get_istream(); - do { - t = getline(&std_ok, &n, in); - if (t == -1 || feof(in) || ferror(in)) { - TRACE("sage-interface", - tout << "error while reading from sage pipe \n";); - return false; - } - CTRACE("sage-interface-verb", t > 0, - tout << "got sage std output " << std_ok << "\n";); - } while (strcmp(std_ok, "ok\n") != 0); - // delete object allocated by getline - delete std_ok; - TRACE("sage-interface", tout << "got ok from sage \n";); - - // read output from file - std::ifstream ifs(temp_name); - if (!ifs.is_open()) { - TRACE("sage-interface", tout << "failed to open file\n";); - return false; - } - - int num; - unsigned total_rows = 0, row = 0, col = 0; - char misc_char; - ifs >> std::skipws; - ifs >> total_rows; - if (total_rows == 0) { - ifs.close(); - close(tmp_fd); - std::remove(temp_name); - TRACE("sage-interface", tout << "Rank of kernel is zero\n";); - return false; - } - out_kernel = spacer_matrix(total_rows, n_cols + 1); - ifs >> misc_char; - SASSERT(misc_char == '['); - while (true) { - while (misc_char != '(' && misc_char != ']') ifs >> misc_char; - if (misc_char == ']') break; - SASSERT(misc_char == '('); - col = 0; - while (!ifs.bad() && !ifs.eof()) { - ifs >> num; - if (ifs.fail() || ifs.bad()) { - TRACE("sage-interface", - tout << "Woops!!! Couldn't read sage output propertly. " - "Abording\n";); - ifs.close(); - close(tmp_fd); - std::remove(temp_name); - out_kernel.reset(0); - SASSERT(false); - return false; - } - ifs >> misc_char; - out_kernel.set(row, col, rational(num)); - col++; - if (misc_char == ')') { break; } - SASSERT(misc_char == ','); - } - row++; - } - SASSERT(row == total_rows); - if (ifs.bad()) { - TRACE("sage-interface", tout << "error when reading from file\n";); - ifs.close(); - close(tmp_fd); - std::remove(temp_name); - return false; - } - - TRACE("sage-interface", tout << "finished reading sage output\n";); - ifs.close(); - - TRACE("sage-interface", - tout << "Kernel is " << matrix_to_string(out_kernel) << "\n";); - - close(tmp_fd); - // TODO: remove file even if sage/spacer terminates before reaching here - std::remove(temp_name); - return true; -} - -} // namespace - -namespace spacer { -spacer_arith_kernel::plugin *mk_sage_plugin() { return nullptr; } -} // namespace spacer From 44f27b15715bd80be458c386320ae97d3e2e839a Mon Sep 17 00:00:00 2001 From: Arie Gurfinkel Date: Sat, 14 May 2022 12:58:32 -0400 Subject: [PATCH 37/78] fix in spacer_convex_closure --- src/muz/spacer/spacer_convex_closure.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/muz/spacer/spacer_convex_closure.cpp b/src/muz/spacer/spacer_convex_closure.cpp index 099a479327f..52b2a1a2e7d 100644 --- a/src/muz/spacer/spacer_convex_closure.cpp +++ b/src/muz/spacer/spacer_convex_closure.cpp @@ -134,7 +134,7 @@ void convex_closure::kernel_row2eq(const vector &row, expr_ref &out) { is_int |= m_arith.is_int(e1); mul_by_rat(e1, val_i); } else { - e1 = mk_numeral(rational::one(), is_int); + e1 = mk_numeral(val_i, is_int); } lhs.push_back(e1); } From e1ddc8a74dc45af40d7a15a3cd460ee414a08486 Mon Sep 17 00:00:00 2001 From: Arie Gurfinkel Date: Thu, 4 Aug 2022 17:51:15 -0400 Subject: [PATCH 38/78] spacer_sem_matcher: consider an additional semantic matching disabled until it is shown useful --- src/muz/spacer/spacer_sem_matcher.cpp | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/muz/spacer/spacer_sem_matcher.cpp b/src/muz/spacer/spacer_sem_matcher.cpp index f3c2af8a993..b0eeb51a363 100644 --- a/src/muz/spacer/spacer_sem_matcher.cpp +++ b/src/muz/spacer/spacer_sem_matcher.cpp @@ -84,7 +84,7 @@ bool sem_matcher::operator()(expr * e1, expr * e2, substitution & s, bool &pos) top = false; if (n1->get_decl() != n2->get_decl()) { - expr *e1 = nullptr, *e2 = nullptr; + expr *e1 = nullptr, *e2 = nullptr, *e3 = nullptr, *e4 = nullptr, *e5 = nullptr; rational val1, val2; // x<=y == !(x>y) @@ -120,6 +120,26 @@ bool sem_matcher::operator()(expr * e1, expr * e2, substitution & s, bool &pos) else { return false; } +#if 0 + // x >= var and !(y <= n) + // match (x, y) and (var, n+1) + if (m_arith.is_ge(n1, e1, e2) && is_var(e2) && + m.is_not(n2, e3) && m_arith.is_le(e3, e4, e5) && + m_arith.is_int(e5) && + m_arith.is_numeral(e5, val2)) { + + expr* num2 = m_arith.mk_numeral(val2 + 1, true); + m_pinned.push_back(num2); + + if (!match_var(to_var(e2), num2)) return false; + + m_todo.pop_back(); + + m_todo.push_back(expr_pair(e1, e4)); + + continue; + } +#endif } unsigned num_args1 = n1->get_num_args(); From 9a2da38156debb09331cfae369d9ef1387cea62f Mon Sep 17 00:00:00 2001 From: Arie Gurfinkel Date: Thu, 4 Aug 2022 17:52:37 -0400 Subject: [PATCH 39/78] spacer_global_generalizer: improve do_conjecture - if conjecture does not apply to pob, use lemma instead - better normalization - improve debug prints --- src/muz/spacer/spacer_global_generalizer.cpp | 58 +++++++++++++------- src/muz/spacer/spacer_global_generalizer.h | 2 +- 2 files changed, 38 insertions(+), 22 deletions(-) diff --git a/src/muz/spacer/spacer_global_generalizer.cpp b/src/muz/spacer/spacer_global_generalizer.cpp index e4ab4a22841..c7cda4a6de5 100644 --- a/src/muz/spacer/spacer_global_generalizer.cpp +++ b/src/muz/spacer/spacer_global_generalizer.cpp @@ -506,28 +506,43 @@ bool lemma_global_generalizer::subsumer::over_approximate(expr_ref_vector &a, /// Done by dropping literal \p lit from /// post of \p n. \p lvl is level for conjecture pob. \p gas is the gas for /// the conjecture pob returns true if conjecture is set -bool lemma_global_generalizer::do_conjecture(pob_ref &n, const expr_ref &lit, - unsigned lvl, unsigned gas) { +bool lemma_global_generalizer::do_conjecture(pob_ref &n, lemma_ref &lemma, + const expr_ref &lit, unsigned lvl, + unsigned gas) { expr_ref_vector fml_vec(m); - expr_ref n_pob(n->post(), m); - normalize_order(n_pob, n_pob); - fml_vec.push_back(n_pob); + expr_ref n_post(n->post(), m); + normalize(n_post, n_post, false, false); + // normalize_order(n_post, n_post); + fml_vec.push_back(n_post); flatten_and(fml_vec); expr_ref_vector conj(m); bool is_filtered = filter_out_lit(fml_vec, lit, conj); + if (!is_filtered) { + // -- try using the corresponding lemma instead + conj.reset(); + n_post = mk_and(lemma->get_cube()); + normalize_order(n_post, n_post); + fml_vec.reset(); + fml_vec.push_back(n_post); + flatten_and(fml_vec); + is_filtered = filter_out_lit(fml_vec, lit, conj); + } + SASSERT(0 < gas && gas < UINT_MAX); if (conj.empty()) { // If the pob cannot be abstracted, stop using generalization on // it - TRACE("global", tout << "stop local generalization on pob " << n_pob - << " id is " << n_pob->get_id() << "\n";); + TRACE("global", tout << "stop local generalization on pob " << n_post + << " id is " << n_post->get_id() << "\n";); n->disable_local_gen(); return false; } else if (!is_filtered) { // The literal to be abstracted is not in the pob - TRACE("global", tout << "cannot conjecture on " << n_pob << " with lit " - << lit << "\n";); + TRACE("global", tout << "Conjecture failed:\n" + << lit << "\n" + << n_post << "\n" + << "conj:" << conj << "\n";); n->disable_local_gen(); m_st.m_num_cant_abs++; return false; @@ -572,11 +587,12 @@ void lemma_global_generalizer::generalize(lemma_ref &lemma) { const expr_ref &pat = lc.get_pattern(); TRACE("global", { - tout << "Global generalization of: " << mk_and(lemma->get_cube()) - << "\n" - << "Cluster pattern: " << pat << "\n" - << "Other lemmas:\n"; - for (const auto &lemma : lc.get_lemmas()) { + tout << "Global generalization of:\n" + << mk_and(lemma->get_cube()) << "\n" + << "Using cluster:\n" + << pat << "\n" + << "Existing lemmas in the cluster:\n"; + for (const auto &lemma : cluster->get_lemmas()) { tout << mk_and(lemma.get_lemma()->get_cube()) << "\n"; } }); @@ -600,11 +616,12 @@ void lemma_global_generalizer::generalize(lemma_ref &lemma) { expr_ref lit(m); if (find_unique_mono_var_lit(pat, lit)) { // Create a conjecture by dropping literal from pob. - TRACE("global", tout << "Conjecture with pattern " << mk_pp(pat, m) - << " with gas " << cluster->get_gas() << "\n";); + TRACE("global", tout << "Conjecture with pattern\n" + << mk_pp(pat, m) << "\n" + << "with gas " << cluster->get_gas() << "\n";); unsigned gas = cluster->get_pob_gas(); unsigned lvl = cluster->get_min_lvl(); - if (do_conjecture(pob, lit, lvl, gas)) { + if (do_conjecture(pob, lemma, lit, lvl, gas)) { // decrease the number of times this cluster is going to be used // for conjecturing cluster->dec_gas(); @@ -629,10 +646,9 @@ void lemma_global_generalizer::generalize(lemma_ref &lemma) { pob->set_may_pob_lvl(cluster->get_min_lvl()); pob->set_gas(cluster->get_pob_gas() + 1); pob->set_expand_bnd(); - TRACE("global", tout << "subsume pob " << mk_and(new_pob) - << " at level " << cluster->get_min_lvl() - << " set on pob " << mk_pp(pob->post(), m) - << "\n";); + TRACE("global", tout << "Create subsume pob at level " + << cluster->get_min_lvl() << "\n" + << mk_and(new_pob) << "\n";); // -- stop local generalization // -- maybe not the best choice in general. Helped with one instance // on diff --git a/src/muz/spacer/spacer_global_generalizer.h b/src/muz/spacer/spacer_global_generalizer.h index bff95d2980e..00b85ecd5af 100644 --- a/src/muz/spacer/spacer_global_generalizer.h +++ b/src/muz/spacer/spacer_global_generalizer.h @@ -153,7 +153,7 @@ class lemma_global_generalizer : public lemma_generalizer { /// Done by dropping literal \p lit from /// post of \p n. \p lvl is level for conjecture pob. \p gas is the gas for /// the conjecture pob returns true if conjecture is set - bool do_conjecture(pob_ref &n, const expr_ref &lit, unsigned lvl, + bool do_conjecture(pob_ref &n, lemma_ref &lemma, const expr_ref &lit, unsigned lvl, unsigned gas); /// Enable/disable subsume rule From 0f534d040e26c317d6afcfff534620e2cad81c2b Mon Sep 17 00:00:00 2001 From: Arie Gurfinkel Date: Thu, 4 Aug 2022 17:54:27 -0400 Subject: [PATCH 40/78] spacer_conjecture: formatting --- src/muz/spacer/spacer_conjecture.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/muz/spacer/spacer_conjecture.cpp b/src/muz/spacer/spacer_conjecture.cpp index e3925c69aff..7279047c094 100644 --- a/src/muz/spacer/spacer_conjecture.cpp +++ b/src/muz/spacer/spacer_conjecture.cpp @@ -31,7 +31,7 @@ bool is_mono_var_lit(expr *lit, ast_manager &m) { bv_util bv(m); arith_util a_util(m); if (m.is_not(lit, e)) return is_mono_var_lit(e, m); - if (a_util.is_arith_expr((lit)) || bv.is_bv_ule(lit) || + if (a_util.is_arith_expr(lit) || bv.is_bv_ule(lit) || bv.is_bv_sle(lit)) { return get_num_vars(lit) == 1 && !has_nonlinear_var_mul(lit, m); } From be1e9c0d9e4f1bc3f0195841ac5fd607c0d80a7c Mon Sep 17 00:00:00 2001 From: Arie Gurfinkel Date: Thu, 4 Aug 2022 17:55:05 -0400 Subject: [PATCH 41/78] spacer_cluster: improve debug prints --- src/muz/spacer/spacer_cluster.cpp | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/muz/spacer/spacer_cluster.cpp b/src/muz/spacer/spacer_cluster.cpp index 4e3f843fdd2..59dce2783a9 100644 --- a/src/muz/spacer/spacer_cluster.cpp +++ b/src/muz/spacer/spacer_cluster.cpp @@ -199,9 +199,8 @@ bool lemma_cluster::add_lemma(const lemma_ref &lemma, bool subsume) { if (rm.get_lemma() == li.get_lemma()) return false; } } - TRACE("cluster_stats", tout << "Added lemma " << mk_and(lemma->get_cube()) - << " to existing cluster " << m_pattern - << "\n";); + TRACE("cluster_stats", tout << "Added lemma\n" << mk_and(lemma->get_cube()) << "\n" + << "to existing cluster\n" << m_pattern << "\n";); return true; } @@ -279,7 +278,7 @@ bool lemma_cluster_finder::anti_unify_n_intrp(const expr_ref &cube, if (is_general_pattern) { SASSERT(e != nullptr); TRACE("cluster_stats", - tout << "Found a general pattern " << mk_pp(e, m) << "\n";); + tout << "Found a general pattern\n" << mk_pp(e, m) << "\n";); // found a good pattern res = expr_ref(e, m); return true; @@ -365,8 +364,8 @@ void lemma_cluster_finder::cluster(lemma_ref &lemma) { lemma_cluster *cluster = pt.mk_cluster(pattern); TRACE("cluster_stats", - tout << "created new cluster with pattern: " << pattern << "\n" - << " and lemma cube: " << lcube << "\n";); + tout << "created new cluster with pattern:\n" << pattern << "\n" + << " and lemma cube:\n" << lcube << "\n";); IF_VERBOSE(2, verbose_stream() << "\ncreated new cluster with pattern: " << pattern << "\n" @@ -374,9 +373,9 @@ void lemma_cluster_finder::cluster(lemma_ref &lemma) { for (const lemma_ref &l : neighbours) { SASSERT(cluster->can_contain(l)); - cluster->add_lemma(l, false); - TRACE("cluster_stats", - tout << "Added lemma " << mk_and(l->get_cube()) << "\n";); + bool added = cluster->add_lemma(l, false); + CTRACE("cluster_stats", added, + tout << "Added neighbour lemma\n" << mk_and(l->get_cube()) << "\n";); } // finally add the lemma and do subsumption check From 7eb3aea8a62ac713fad1f22af20b79dead9986d2 Mon Sep 17 00:00:00 2001 From: Arie Gurfinkel Date: Thu, 4 Aug 2022 17:56:52 -0400 Subject: [PATCH 42/78] spacer_context: improve debug prints --- src/muz/spacer/spacer_context.cpp | 30 ++++++++++++------------------ 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/src/muz/spacer/spacer_context.cpp b/src/muz/spacer/spacer_context.cpp index f5fe248fc31..9d59d5f2389 100644 --- a/src/muz/spacer/spacer_context.cpp +++ b/src/muz/spacer/spacer_context.cpp @@ -3099,7 +3099,7 @@ void context::log_expand_pob(pob &n) { if (n.parent()) pob_id = std::to_string(n.parent()->post()->get_id()); *m_trace_stream << "** expand-pob: " << n.pt().head()->get_name() - << (n.is_conjecture() ? "CONJ" : "") + << (n.is_conjecture() ? " CONJ" : "") << (n.is_subsume() ? " SUBS" : "") << " level: " << n.level() << " depth: " << (n.depth() - m_pob_queue.min_depth()) @@ -3108,7 +3108,7 @@ void context::log_expand_pob(pob &n) { } TRACE("spacer", tout << "expand-pob: " << n.pt().head()->get_name() - << (n.is_conjecture() ? "CONJ" : "") + << (n.is_conjecture() ? " CONJ" : "") << (n.is_subsume() ? " SUBS" : "") << " level: " << n.level() << " depth: " << (n.depth() - m_pob_queue.min_depth()) @@ -3118,7 +3118,7 @@ void context::log_expand_pob(pob &n) { STRACE("spacer_progress", tout << "** expand-pob: " << n.pt().head()->get_name() - << (n.is_conjecture() ? "CONJ" : "") + << (n.is_conjecture() ? " CONJ" : "") << (n.is_subsume() ? " SUBS" : "") << " level: " << n.level() << " depth: " << (n.depth() - m_pob_queue.min_depth()) << "\n" @@ -3690,12 +3690,10 @@ lbool context::expand_pob(pob& n, pob_ref_buffer &out) } CTRACE("global", n.is_conjecture() || n.is_subsume(), - tout << " Blocked " - << (n.is_conjecture() ? "conjecture" : "subsume") - << " pob " << mk_pp(n.post(), m) - << " using lemma " << mk_pp(lemma_pob->get_expr(), m) - << " Level " << lemma_pob->level() << " id " - << n.post()->get_id() << "\n";); + tout << "Blocked " + << (n.is_conjecture() ? "conjecture " : "subsume ") << n.post()->get_id() + << " at level " << n.level() + << " using lemma\n" << mk_pp(lemma_pob->get_expr(), m) << "\n";); DEBUG_CODE(lemma_sanity_checker sanity_checker(*this); sanity_checker(lemma_pob);); @@ -3736,21 +3734,17 @@ lbool context::expand_pob(pob& n, pob_ref_buffer &out) new_pob->set_gas(n.get_gas() - 1); n.set_gas(n.get_gas() - 1); out.push_back(new_pob); - TRACE("global", - tout << "Attempting to block pob " << mk_pp(n.post(), m) - << " using generalization " << mk_pp(new_pob->post(), m) - << " with gas " << new_pob->get_gas() << "\n";); + TRACE("global_verbose", + tout << "New subsume pob\n" << mk_pp(new_pob->post(), m) << "\n" + << "gas:" << new_pob->get_gas() << "\n";); out.push_back(new_pob); m_stats.m_num_subsume_pobs++; } else if (pob* new_pob = m_gg_conjecture ? m_global_gen->mk_conjecture_pob(n) : nullptr) { new_pob->set_gas(n.get_gas() - 1); n.set_gas(n.get_gas() - 1); out.push_back(new_pob); - TRACE("global", tout << " conjecture " << mk_pp(n.post(), m) - << " id is " << n.post()->get_id() - << "\n into pob " << new_pob->post() - << " id is " << new_pob->post()->get_id() - << "\n";); + TRACE("global", + tout << "New conjecture pob\n" << mk_pp(new_pob->post(), m) << "\n";); m_stats.m_num_conj++; } } From ab44f86c04e6e9bc6a65fdba08dd8a16d4a9dd06 Mon Sep 17 00:00:00 2001 From: Arie Gurfinkel Date: Thu, 4 Aug 2022 17:58:22 -0400 Subject: [PATCH 43/78] spacer_context: re-queue may pobs enabled even if global re-queue is disabled --- src/muz/spacer/spacer_context.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/muz/spacer/spacer_context.cpp b/src/muz/spacer/spacer_context.cpp index 9d59d5f2389..e2862b21ea5 100644 --- a/src/muz/spacer/spacer_context.cpp +++ b/src/muz/spacer/spacer_context.cpp @@ -77,7 +77,7 @@ pob::pob(pob *parent, pred_transformer &pt, unsigned level, unsigned depth, } if (m_parent) { m_is_conjecture = m_parent->is_conjecture(); - m_is_subsume = m_parent->is_subsume(); + // m_is_subsume = m_parent->is_subsume(); m_gas = m_parent->get_gas(); } } @@ -3246,7 +3246,13 @@ bool context::check_reachability () SASSERT(m_pob_queue.size() == old_sz); // re-queue all pobs introduced by global gen and any pobs that can be blocked at a higher level for (auto pob : new_pobs) { - if ((pob->is_may_pob() && pob->post() != node->post()) || is_requeue(*pob)) { + TRACE("gg", tout << "pob: is_may_pob " << pob->is_may_pob() << "\n";); + //if ((pob->is_may_pob() && pob->post() != node->post()) || is_requeue(*pob)) { + if (is_requeue(*pob)) { + TRACE("gg", + tout << "Adding back blocked pob at level " + << pob->level() + << " and depth " << pob->depth() << "\n"); m_pob_queue.push(*pob); } } @@ -3272,7 +3278,7 @@ bool context::check_reachability () /// returns true if the given pob can be re-scheduled bool context::is_requeue(pob &n) { - if (!m_push_pob) {return false;} + if (!n.is_may_pob() && !m_push_pob) { return false; } unsigned max_depth = m_push_pob_max_depth; return (n.level() >= m_pob_queue.max_level() || m_pob_queue.max_level() - n.level() <= max_depth); From bc05b82574901eaa139bc26c64d4e01ef135c68b Mon Sep 17 00:00:00 2001 From: Arie Gurfinkel Date: Thu, 9 Jun 2022 09:57:42 -0400 Subject: [PATCH 44/78] spacer_cluster print formatting --- src/muz/spacer/spacer_cluster.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/muz/spacer/spacer_cluster.cpp b/src/muz/spacer/spacer_cluster.cpp index 59dce2783a9..eb58b7458f1 100644 --- a/src/muz/spacer/spacer_cluster.cpp +++ b/src/muz/spacer/spacer_cluster.cpp @@ -305,8 +305,8 @@ void lemma_cluster_finder::cluster(lemma_ref &lemma) { lemma_cluster *clstr = pt.clstr_match(lemma); if (clstr && clstr->get_size() <= MAX_CLUSTER_SIZE) { TRACE("cluster_stats_verb", { - tout << "Trying to add lemma " << lemma->get_cube() - << " to an existing cluster "; + tout << "Trying to add lemma\n" << lemma->get_cube() + << " to an existing cluster\n"; for (auto lem : clstr->get_lemmas()) tout << lem.get_lemma()->get_cube() << "\n"; }); From 78d95bd49587f9b707ca2be07725f3946403eadf Mon Sep 17 00:00:00 2001 From: Arie Gurfinkel Date: Thu, 9 Jun 2022 09:58:51 -0400 Subject: [PATCH 45/78] reset methods on pob --- src/muz/spacer/spacer_context.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/muz/spacer/spacer_context.h b/src/muz/spacer/spacer_context.h index 658232e4063..aa4b620ff28 100644 --- a/src/muz/spacer/spacer_context.h +++ b/src/muz/spacer/spacer_context.h @@ -811,6 +811,7 @@ class pob { m_subsume_post.reset(); m_subsume_post.append(expr); } + void reset_subsume_post() { m_subsume_post.reset(); } void set_subsume_bindings(app_ref_vector& vars) { m_subsume_bindings.reset(); m_subsume_bindings.append(vars); @@ -853,6 +854,7 @@ class pob { m_conjecture_pat.reset(); m_conjecture_pat.append(pattern); } + void reset_conjecture_pattern() { m_conjecture_pat.reset() ; } bool is_subsume() const { return m_is_subsume; } void set_subsume(bool v = true) { m_is_subsume = v; } bool is_may_pob() const { return is_subsume() || is_conjecture(); } From 0d15b29582e554be58ef2dc13c73e98a60195f2c Mon Sep 17 00:00:00 2001 From: Arie Gurfinkel Date: Thu, 9 Jun 2022 09:59:20 -0400 Subject: [PATCH 46/78] cleanup of print and local variable names --- src/muz/spacer/spacer_context.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/muz/spacer/spacer_context.cpp b/src/muz/spacer/spacer_context.cpp index e2862b21ea5..373b8c55391 100644 --- a/src/muz/spacer/spacer_context.cpp +++ b/src/muz/spacer/spacer_context.cpp @@ -3685,9 +3685,10 @@ lbool context::expand_pob(pob& n, pob_ref_buffer &out) n.get_post_simplified(pob_cube); lemma_pob = alloc(class lemma, nref, pob_cube, n.level()); - TRACE("global", tout << " stopped local gen on pob " - << mk_pp(n.post(), m) << " with id " - << n.post()->get_id() << "\n lemma learned " + TRACE("global", tout << "Disabled local gen on pob (id: " + << n.post()->get_id() << ")\n" + << mk_pp(n.post(), m) << "\n" + << "Lemma:\n" << mk_and(lemma_pob->get_cube()) << "\n";); if (m_global_gen) (*m_global_gen)(lemma_pob); if (m_expand_bnd_gen) (*m_expand_bnd_gen)(lemma_pob); @@ -3709,14 +3710,14 @@ lbool context::expand_pob(pob& n, pob_ref_buffer &out) << (is_infty_level(lemma_pob->level()) ? "(inductive)" : "") << mk_pp(lemma_pob->get_expr(), m) << "\n";); - bool v = n.pt().add_lemma(lemma_pob.get()); - if (v) { + bool is_new = n.pt().add_lemma(lemma_pob.get()); + if (is_new) { if (m_global) m_lmma_cluster->cluster(lemma_pob); m_stats.m_num_lemmas++; } // Optionally update the node to be the negation of the lemma - if (v && m_use_lemma_as_pob) { + if (is_new && m_use_lemma_as_pob) { expr_ref c(m); c = mk_and(lemma_pob->get_cube()); // check that the post condition is different From 1ca2918db8d5b750c1f3c9f9c12af4db6a40e093 Mon Sep 17 00:00:00 2001 From: Arie Gurfinkel Date: Thu, 9 Jun 2022 10:00:08 -0400 Subject: [PATCH 47/78] formatting --- src/muz/spacer/spacer_global_generalizer.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/muz/spacer/spacer_global_generalizer.cpp b/src/muz/spacer/spacer_global_generalizer.cpp index c7cda4a6de5..14d39567c1f 100644 --- a/src/muz/spacer/spacer_global_generalizer.cpp +++ b/src/muz/spacer/spacer_global_generalizer.cpp @@ -519,14 +519,14 @@ bool lemma_global_generalizer::do_conjecture(pob_ref &n, lemma_ref &lemma, expr_ref_vector conj(m); bool is_filtered = filter_out_lit(fml_vec, lit, conj); if (!is_filtered) { - // -- try using the corresponding lemma instead - conj.reset(); - n_post = mk_and(lemma->get_cube()); - normalize_order(n_post, n_post); - fml_vec.reset(); - fml_vec.push_back(n_post); - flatten_and(fml_vec); - is_filtered = filter_out_lit(fml_vec, lit, conj); + // -- try using the corresponding lemma instead + conj.reset(); + n_post = mk_and(lemma->get_cube()); + normalize_order(n_post, n_post); + fml_vec.reset(); + fml_vec.push_back(n_post); + flatten_and(fml_vec); + is_filtered = filter_out_lit(fml_vec, lit, conj); } SASSERT(0 < gas && gas < UINT_MAX); From 30131ba54d21ded9389d76b8233146ea4269f79f Mon Sep 17 00:00:00 2001 From: Arie Gurfinkel Date: Thu, 9 Jun 2022 10:01:01 -0400 Subject: [PATCH 48/78] reset generalization data once it has been used --- src/muz/spacer/spacer_global_generalizer.cpp | 24 +++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/src/muz/spacer/spacer_global_generalizer.cpp b/src/muz/spacer/spacer_global_generalizer.cpp index 14d39567c1f..3177dcc3d93 100644 --- a/src/muz/spacer/spacer_global_generalizer.cpp +++ b/src/muz/spacer/spacer_global_generalizer.cpp @@ -691,27 +691,39 @@ pob *lemma_global_generalizer::mk_concretize_pob(pob &n, model_ref &model) { pob *lemma_global_generalizer::mk_subsume_pob(pob &n) { if (n.get_subsume_pob().empty() || n.get_gas() <= 0) return nullptr; + pob *root = n.parent(); + while (root->parent()) root = root->parent(); + expr_ref post = mk_and(n.get_subsume_pob()); - pob *f = n.pt().find_pob(n.parent(), post); - if (f && f->is_in_queue()) return nullptr; + pob *f = n.pt().find_pob(root, post); + if (f && f->is_in_queue()) { + n.reset_subsume_post(); + return nullptr; + } auto level = n.get_may_pob_lvl(); - f = n.pt().mk_pob(n.parent(), level, n.depth(), post, - n.get_subsume_bindings()); + f = n.pt().mk_pob(root, level, n.depth(), post, n.get_subsume_bindings()); f->set_subsume(); + + n.reset_subsume_post(); return f; } pob *lemma_global_generalizer::mk_conjecture_pob(pob &n) { if (n.get_conjecture_pattern().empty() || n.get_gas() <= 0) return nullptr; + pob *root = n.parent(); + while (root->parent()) root = root->parent(); + expr_ref post = mk_and(n.get_conjecture_pattern()); - pob *f = n.pt().find_pob(n.parent(), post); + pob *f = n.pt().find_pob(root, post); if (f && f->is_in_queue()) return nullptr; + auto level = n.get_may_pob_lvl(); - f = n.pt().mk_pob(n.parent(), level, n.depth(), post, {m}); + f = n.pt().mk_pob(root, level, n.depth(), post, {m}); f->set_conjecture(); + n.reset_conjecture_pattern(); return f; } From bf58d4dd0d1b019d93f3392536d5d6fb97a52a5f Mon Sep 17 00:00:00 2001 From: Arie Gurfinkel Date: Fri, 10 Jun 2022 13:17:06 -0400 Subject: [PATCH 49/78] refactored extra pob creation during global guidance --- src/muz/spacer/spacer_context.cpp | 13 ++- src/muz/spacer/spacer_context.h | 46 +++------- src/muz/spacer/spacer_global_generalizer.cpp | 89 ++++++++++++-------- 3 files changed, 77 insertions(+), 71 deletions(-) diff --git a/src/muz/spacer/spacer_context.cpp b/src/muz/spacer/spacer_context.cpp index 373b8c55391..eebe72db588 100644 --- a/src/muz/spacer/spacer_context.cpp +++ b/src/muz/spacer/spacer_context.cpp @@ -68,10 +68,8 @@ pob::pob(pob *parent, pred_transformer &pt, unsigned level, unsigned depth, m_open(true), m_use_farkas(true), m_in_queue(false), m_is_conjecture(false), m_enable_local_gen(true), m_enable_concretize(false), m_is_subsume(false), m_enable_expand_bnd_gen(false), m_weakness(0), m_blocked_lvl(0), - m_conjecture_pat(m_pt.get_ast_manager()), m_concretize_pat(m_pt.get_ast_manager()), - m_subsume_post(m_pt.get_ast_manager()), - m_subsume_bindings(m_pt.get_ast_manager()), m_gas(0) { + m_gas(0) { if (add_to_parent && m_parent) { m_parent->add_child(*this); } @@ -110,9 +108,18 @@ void pob::inherit(pob const &p) { m_depth = p.m_depth; m_open = p.m_open; m_use_farkas = p.m_use_farkas; + + m_is_conjecture = p.m_is_conjecture; + m_enable_local_gen = p.m_enable_local_gen; + m_enable_concretize = p.m_enable_concretize; + m_is_subsume = p.m_is_subsume; + m_enable_expand_bnd_gen = p.m_enable_expand_bnd_gen; + m_weakness = p.m_weakness; m_derivation = nullptr; + + m_gas = p.m_gas; } void pob::close () { diff --git a/src/muz/spacer/spacer_context.h b/src/muz/spacer/spacer_context.h index aa4b620ff28..5414cb4d46f 100644 --- a/src/muz/spacer/spacer_context.h +++ b/src/muz/spacer/spacer_context.h @@ -777,49 +777,37 @@ class pob { // clang-format on // clang-format off - // pattern with which conjecture was created - expr_ref_vector m_conjecture_pat; - // pattern identified for one of its lemmas expr_ref m_concretize_pat; - // a post that subsumes all lemmas that block this pob - expr_ref_vector m_subsume_post; - - // bindings for subsume post - app_ref_vector m_subsume_bindings; - - // level at which may pob is to be added - unsigned m_may_lvl; - // gas decides how much time is spent in blocking this (may) pob unsigned m_gas; + + // additional data used by global (and other) generalizations + scoped_ptr m_data; public: pob(pob *parent, pred_transformer &pt, unsigned level, unsigned depth = 0, bool add_to_parent = true); + // no copy constructor + pob(const pob&) = delete; + // no move constructor + pob(pob &&) = delete; + ~pob() { if (m_parent) { m_parent->erase_child(*this); } } + + void set_data(pob* v) { m_data = v; } + void reset_data() { set_data(nullptr); } + pob* get_data() { return m_data.get(); } + bool has_data() { return m_data; } + // TBD: move into constructor and make private void set_post(expr *post, app_ref_vector const &binding); void set_post(expr *post); - void set_subsume_pob(const expr_ref_vector &expr) { - m_may_lvl = 0; - m_subsume_post.reset(); - m_subsume_post.append(expr); - } - void reset_subsume_post() { m_subsume_post.reset(); } - void set_subsume_bindings(app_ref_vector& vars) { - m_subsume_bindings.reset(); - m_subsume_bindings.append(vars); - } - void set_may_pob_lvl(unsigned l) { m_may_lvl = l; } - unsigned get_may_pob_lvl() { return m_may_lvl; } - expr_ref_vector const &get_subsume_pob() const { return m_subsume_post; } - app_ref_vector const &get_subsume_bindings() const { return m_subsume_bindings; } unsigned weakness() { return m_weakness; } void bump_weakness() { m_weakness++; } void reset_weakness() { m_weakness = 0; } @@ -849,12 +837,6 @@ class pob { void set_expand_bnd(bool v = true) { m_enable_expand_bnd_gen = v; } void set_concretize_pattern(const expr_ref &pattern) { m_concretize_pat = pattern; } const expr_ref &get_concretize_pattern() const { return m_concretize_pat; } - const expr_ref_vector &get_conjecture_pattern() const { return m_conjecture_pat; } - void set_conjecture_pattern(const expr_ref_vector &pattern) { - m_conjecture_pat.reset(); - m_conjecture_pat.append(pattern); - } - void reset_conjecture_pattern() { m_conjecture_pat.reset() ; } bool is_subsume() const { return m_is_subsume; } void set_subsume(bool v = true) { m_is_subsume = v; } bool is_may_pob() const { return is_subsume() || is_conjecture(); } diff --git a/src/muz/spacer/spacer_global_generalizer.cpp b/src/muz/spacer/spacer_global_generalizer.cpp index 3177dcc3d93..b663c9a9c54 100644 --- a/src/muz/spacer/spacer_global_generalizer.cpp +++ b/src/muz/spacer/spacer_global_generalizer.cpp @@ -548,14 +548,23 @@ bool lemma_global_generalizer::do_conjecture(pob_ref &n, lemma_ref &lemma, return false; } - // There is enough gas to conjecture on pob - n->set_conjecture_pattern(conj); + pob *root = n->parent(); + while (root->parent()) root = root->parent(); + scoped_ptr new_pob = alloc(pob, root, n->pt(), lvl, n->depth(), false); + if (!new_pob) return false; + + new_pob->set_post(mk_and(conj)); + new_pob->set_conjecture(); + + // -- register with current pob + n->set_data(new_pob.detach()); + + // -- update properties of the current pob itself n->set_expand_bnd(); - n->set_may_pob_lvl(lvl); n->set_gas(gas); n->disable_local_gen(); - TRACE("global", tout << "set conjecture " << conj << " at level " - << n->get_may_pob_lvl() << "\n";); + TRACE("global", tout << "set conjecture " << n->get_data()->post() + << " at level " << n->get_data()->level() << "\n";); return true; } @@ -635,26 +644,34 @@ void lemma_global_generalizer::generalize(lemma_ref &lemma) { if (!m_do_subsume) return; // -- new pob that is blocked by generalized lemma - expr_ref_vector new_pob(m); + expr_ref_vector new_post(m); // -- bindings for free variables of new_pob // -- subsumer might introduce extra free variables app_ref_vector bindings(lemma->get_bindings()); - if (m_subsumer.subsume(lc, new_pob, bindings)) { - pob->set_subsume_pob(new_pob); - pob->set_subsume_bindings(bindings); - pob->set_may_pob_lvl(cluster->get_min_lvl()); + if (m_subsumer.subsume(lc, new_post, bindings)) { + class pob *root = pob->parent(); + while (root->parent()) root = root->parent(); + scoped_ptr new_pob = + alloc(class pob, root, pob->pt(), cluster->get_min_lvl(), + pob->depth(), false); + if (!new_pob) return; + + new_pob->set_post(mk_and(new_post), bindings); + new_pob->set_subsume(); + pob->set_data(new_pob.detach()); + + // -- update properties of the pob itself pob->set_gas(cluster->get_pob_gas() + 1); pob->set_expand_bnd(); - TRACE("global", tout << "Create subsume pob at level " - << cluster->get_min_lvl() << "\n" - << mk_and(new_pob) << "\n";); - // -- stop local generalization - // -- maybe not the best choice in general. Helped with one instance - // on - // -- our benchmarks + // Stop local generalization. Perhaps not the best choice in general. + // Helped with one instance on our benchmarks pob->disable_local_gen(); cluster->dec_gas(); + + TRACE("global", tout << "Create subsume pob at level " + << cluster->get_min_lvl() << "\n" + << mk_and(new_post) << "\n";); } } @@ -689,41 +706,41 @@ pob *lemma_global_generalizer::mk_concretize_pob(pob &n, model_ref &model) { } pob *lemma_global_generalizer::mk_subsume_pob(pob &n) { - if (n.get_subsume_pob().empty() || n.get_gas() <= 0) return nullptr; + if (!(n.get_gas() >= 0 && n.has_data() && n.get_data()->is_subsume())) + return nullptr; - pob *root = n.parent(); - while (root->parent()) root = root->parent(); + pob *data = n.get_data(); - expr_ref post = mk_and(n.get_subsume_pob()); - pob *f = n.pt().find_pob(root, post); + pob *f = n.pt().find_pob(data->parent(), data->post()); if (f && f->is_in_queue()) { - n.reset_subsume_post(); + n.reset_data(); return nullptr; } - auto level = n.get_may_pob_lvl(); - f = n.pt().mk_pob(root, level, n.depth(), post, n.get_subsume_bindings()); + f = n.pt().mk_pob(data->parent(), data->level(), data->depth(), + data->post(), n.get_binding()); f->set_subsume(); + f->inherit(*data); - n.reset_subsume_post(); + n.reset_data(); return f; } pob *lemma_global_generalizer::mk_conjecture_pob(pob &n) { - if (n.get_conjecture_pattern().empty() || n.get_gas() <= 0) return nullptr; - - pob *root = n.parent(); - while (root->parent()) root = root->parent(); + if (!(n.has_data() && n.get_data()->is_conjecture() && n.get_gas() > 0)) + return nullptr; - expr_ref post = mk_and(n.get_conjecture_pattern()); - pob *f = n.pt().find_pob(root, post); + pob *data = n.get_data(); + pob *f = n.pt().find_pob(data->parent(), data->post()); if (f && f->is_in_queue()) return nullptr; + f = n.pt().mk_pob(data->parent(), data->level(), data->depth(), + data->post(), {m}); + + // inherit all metadata from new_pob + f->inherit(*data); - auto level = n.get_may_pob_lvl(); - f = n.pt().mk_pob(root, level, n.depth(), post, {m}); - f->set_conjecture(); - n.reset_conjecture_pattern(); + n.reset_data(); return f; } From dfdc3feeb4760bbb6da0a1607abc58926675a032 Mon Sep 17 00:00:00 2001 From: Arie Gurfinkel Date: Fri, 10 Jun 2022 16:17:49 -0400 Subject: [PATCH 50/78] fix bug copying sparse matrix into spacer matrix --- src/muz/spacer/spacer_arith_kernel.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/muz/spacer/spacer_arith_kernel.cpp b/src/muz/spacer/spacer_arith_kernel.cpp index 7f33b243cae..851c1e24f22 100644 --- a/src/muz/spacer/spacer_arith_kernel.cpp +++ b/src/muz/spacer/spacer_arith_kernel.cpp @@ -73,6 +73,7 @@ class simplex_arith_kernel_plugin : public spacer_arith_kernel::plugin { out.reset(kern.num_vars()); vector vec; for (auto row : kern.get_rows()) { + vec.reset(); vec.reserve(kern.num_vars(), rational(0)); for (auto &[coeff, v] : kern.get_row(row)) { vec[v] = rational(coeff); From 8d6cd33bed482c7e3055f9c95acd28dc9245b49b Mon Sep 17 00:00:00 2001 From: Arie Gurfinkel Date: Thu, 16 Jun 2022 14:47:23 -0400 Subject: [PATCH 51/78] bug fix in spacer_convex_closure --- src/muz/spacer/spacer_convex_closure.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/muz/spacer/spacer_convex_closure.cpp b/src/muz/spacer/spacer_convex_closure.cpp index 52b2a1a2e7d..4c2e1fcfd41 100644 --- a/src/muz/spacer/spacer_convex_closure.cpp +++ b/src/muz/spacer/spacer_convex_closure.cpp @@ -368,7 +368,7 @@ void convex_closure::cc_1dim(const expr_ref &var, expr_ref_vector &out) { expr *convex_closure::mk_eq_mod(expr *v, rational d, rational r) { expr *res = nullptr; - if (!m_arith.is_int(v)) { + if (m_arith.is_int(v)) { res = m.mk_eq(m_arith.mk_mod(v, m_arith.mk_int(d)), m_arith.mk_int(r)); } else if (m_bv.is_bv(v)) { res = m.mk_eq(m_bv.mk_bv_urem(v, m_bv.mk_numeral(d, m_bv_sz)), From 36818b1df201e037d35b6784848a3420a2aaa9d4 Mon Sep 17 00:00:00 2001 From: Arie Gurfinkel Date: Thu, 16 Jun 2022 14:50:02 -0400 Subject: [PATCH 52/78] formatting change in spacer_context --- src/muz/spacer/spacer_context.h | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/muz/spacer/spacer_context.h b/src/muz/spacer/spacer_context.h index 5414cb4d46f..a230e2cf040 100644 --- a/src/muz/spacer/spacer_context.h +++ b/src/muz/spacer/spacer_context.h @@ -780,7 +780,7 @@ class pob { // pattern identified for one of its lemmas expr_ref m_concretize_pat; - // gas decides how much time is spent in blocking this (may) pob + // gas decides how much time is spent in blocking this (may) pob unsigned m_gas; // additional data used by global (and other) generalizations @@ -798,7 +798,6 @@ class pob { if (m_parent) { m_parent->erase_child(*this); } } - void set_data(pob* v) { m_data = v; } void reset_data() { set_data(nullptr); } pob* get_data() { return m_data.get(); } @@ -812,7 +811,7 @@ class pob { void bump_weakness() { m_weakness++; } void reset_weakness() { m_weakness = 0; } - void inc_level () { + void inc_level() { SASSERT(!is_in_queue()); m_level++; m_depth++; From 57525a3c3bdfb04145d5005d7ff68da224c7555c Mon Sep 17 00:00:00 2001 From: Arie Gurfinkel Date: Mon, 11 Jul 2022 14:10:01 -0400 Subject: [PATCH 53/78] spacer_cluster: get_min_lvl chose level based on pob as well as lemmas --- src/muz/spacer/spacer_cluster.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/muz/spacer/spacer_cluster.cpp b/src/muz/spacer/spacer_cluster.cpp index eb58b7458f1..13ef17c26c3 100644 --- a/src/muz/spacer/spacer_cluster.cpp +++ b/src/muz/spacer/spacer_cluster.cpp @@ -72,6 +72,13 @@ unsigned lemma_cluster::get_min_lvl() { if (m_lemma_vec.empty()) return 0; unsigned lvl = m_lemma_vec[0].get_lemma()->level(); for (auto l : m_lemma_vec) { lvl = std::min(lvl, l.get_lemma()->level()); } + // if all lemmas are at infinity, use the level of the lowest pob + if (is_infty_level(lvl)) { + for (auto l : m_lemma_vec) { + if (l.get_lemma()->has_pob()) + lvl = std::min(lvl, l.get_lemma()->get_pob()->level()); + } + } return lvl; } From c3178396035d1c2f7862299b054100fbfea4349e Mon Sep 17 00:00:00 2001 From: Arie Gurfinkel Date: Mon, 11 Jul 2022 14:11:51 -0400 Subject: [PATCH 54/78] spacer_context: add desired_level to pob desired_level indicates at which level pob should be proved. A pob will be pushed to desired_level if necessary --- src/muz/spacer/spacer_context.cpp | 8 ++++++-- src/muz/spacer/spacer_context.h | 4 ++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/muz/spacer/spacer_context.cpp b/src/muz/spacer/spacer_context.cpp index eebe72db588..af2a6458036 100644 --- a/src/muz/spacer/spacer_context.cpp +++ b/src/muz/spacer/spacer_context.cpp @@ -65,7 +65,7 @@ pob::pob(pob *parent, pred_transformer &pt, unsigned level, unsigned depth, : m_ref_count(0), m_parent(parent), m_pt(pt), m_post(m_pt.get_ast_manager()), m_binding(m_pt.get_ast_manager()), m_new_post(m_pt.get_ast_manager()), m_level(level), m_depth(depth), - m_open(true), m_use_farkas(true), m_in_queue(false), m_is_conjecture(false), + m_desired_level(0), m_open(true), m_use_farkas(true), m_in_queue(false), m_is_conjecture(false), m_enable_local_gen(true), m_enable_concretize(false), m_is_subsume(false), m_enable_expand_bnd_gen(false), m_weakness(0), m_blocked_lvl(0), m_concretize_pat(m_pt.get_ast_manager()), @@ -106,6 +106,7 @@ void pob::inherit(pob const &p) { m_level = p.m_level; m_depth = p.m_depth; + m_desired_level = std::max(m_desired_level, p.m_desired_level); m_open = p.m_open; m_use_farkas = p.m_use_farkas; @@ -3285,7 +3286,10 @@ bool context::check_reachability () /// returns true if the given pob can be re-scheduled bool context::is_requeue(pob &n) { - if (!n.is_may_pob() && !m_push_pob) { return false; } + // if have not reached desired level, then requeue + if (n.level() <= n.desired_level()) { return true; } + if (!m_push_pob) { return false; } + unsigned max_depth = m_push_pob_max_depth; return (n.level() >= m_pob_queue.max_level() || m_pob_queue.max_level() - n.level() <= max_depth); diff --git a/src/muz/spacer/spacer_context.h b/src/muz/spacer/spacer_context.h index a230e2cf040..1283a525f7f 100644 --- a/src/muz/spacer/spacer_context.h +++ b/src/muz/spacer/spacer_context.h @@ -745,6 +745,8 @@ class pob { unsigned m_level:16; unsigned m_depth:16; + unsigned m_desired_level:16; + /// whether a concrete answer to the post is found unsigned m_open:1; /// whether to use farkas generalizer to construct a lemma blocking this @@ -854,6 +856,8 @@ class pob { unsigned level() const { return m_level; } unsigned depth() const { return m_depth; } + unsigned desired_level() const { return m_desired_level; } + void set_desired_level(unsigned v) { m_desired_level = v; } unsigned width() const { return m_kids.size(); } unsigned blocked_at(unsigned lvl = 0) { return (m_blocked_lvl = std::max(lvl, m_blocked_lvl)); From 1a7def538aed950258c3c9cbc52e1d208ec415b7 Mon Sep 17 00:00:00 2001 From: Arie Gurfinkel Date: Mon, 11 Jul 2022 14:12:26 -0400 Subject: [PATCH 55/78] spacer_context: renamed subsume stats the name of success/failed was switched --- src/muz/spacer/spacer_context.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/muz/spacer/spacer_context.cpp b/src/muz/spacer/spacer_context.cpp index af2a6458036..8860b147e67 100644 --- a/src/muz/spacer/spacer_context.cpp +++ b/src/muz/spacer/spacer_context.cpp @@ -4134,8 +4134,8 @@ void context::collect_statistics(statistics& st) const m_stats.m_num_conj_failed); st.update("SPACER pob out of gas", m_stats.m_num_pob_ofg); st.update("SPACER subsume pob", m_stats.m_num_subsume_pobs); - st.update("SPACER subsume success", m_stats.m_num_subsume_pob_reachable); - st.update("SPACER subsume failed", m_stats.m_num_subsume_pob_blckd); + st.update("SPACER subsume failed", m_stats.m_num_subsume_pob_reachable); + st.update("SPACER subsume success", m_stats.m_num_subsume_pob_blckd); st.update("SPACER concretize", m_stats.m_num_concretize); st.update("SPACER non local gen", m_stats.m_non_local_gen); From 4b2a9b3563c6e7be49baf112125a6401ebd12bb1 Mon Sep 17 00:00:00 2001 From: Arie Gurfinkel Date: Mon, 11 Jul 2022 14:13:52 -0400 Subject: [PATCH 56/78] spacer_convex_closure: fix prototype of is_congruent_mod() --- src/muz/spacer/spacer_convex_closure.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/muz/spacer/spacer_convex_closure.cpp b/src/muz/spacer/spacer_convex_closure.cpp index 4c2e1fcfd41..b0ee18f64b8 100644 --- a/src/muz/spacer/spacer_convex_closure.cpp +++ b/src/muz/spacer/spacer_convex_closure.cpp @@ -39,7 +39,7 @@ bool is_sorted(const vector &data) { } /// Check whether all elements of \p data are congruent modulo \p m -bool is_congruent_mod(const vector &data, rational m) { +bool is_congruent_mod(const vector &data, const rational &m) { SASSERT(data.size() > 0); rational p = data[0] % m; for (auto k : data) From 1e39629b03cb888eadb0c50b2e6dd42ae5d0c874 Mon Sep 17 00:00:00 2001 From: Arie Gurfinkel Date: Mon, 11 Jul 2022 14:14:30 -0400 Subject: [PATCH 57/78] spacer_convex_closure: hacks in infer_div_pred() --- src/muz/spacer/spacer_convex_closure.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/muz/spacer/spacer_convex_closure.cpp b/src/muz/spacer/spacer_convex_closure.cpp index b0ee18f64b8..47682164301 100644 --- a/src/muz/spacer/spacer_convex_closure.cpp +++ b/src/muz/spacer/spacer_convex_closure.cpp @@ -162,6 +162,7 @@ void convex_closure::kernel2fmls(expr_ref_vector &out) { const spacer_matrix &kern = m_kernel.get_kernel(); SASSERT(kern.num_rows() > 0); + TRACE("cvx_dbg", kern.display(tout);); expr_ref eq(m); for (unsigned i = kern.num_rows(); i > 0; i--) { auto &row = kern.get_row(i - 1); @@ -262,18 +263,24 @@ bool convex_closure::infer_div_pred(const vector &data, rational &m, SASSERT(is_sorted(data)); m = rational(2); + + // special handling for even/odd + if (is_congruent_mod(data, m)) { + mod(data.back(), m, d); + return true; + } + // hard cut off to save time rational bnd(MAX_DIV_BOUND); rational big = data.back(); + // AG: why (m < big)? Note that 'big' is the smallest element of data for (; m < big && m < bnd; m++) { if (is_congruent_mod(data, m)) break; } if (m >= big) return false; if (m == bnd) return false; - d = data[0] % m; - // work around for z3::rational::rem returning negative numbers. - d = (m + d) % m; + mod(data[0], m, d); SASSERT(d >= rational::zero()); TRACE("cvx_dbg_verb", tout << "div constraint generated. cf " << m From d556eff413c84dfc34cb7409d545cae1cb55fbac Mon Sep 17 00:00:00 2001 From: Arie Gurfinkel Date: Mon, 11 Jul 2022 14:15:04 -0400 Subject: [PATCH 58/78] spacer_util: do not expand literals with mod By default, equality literal t=p is expanded into t<=p && t>=p Disable the expansion in case t contains 'mod' operator since such expansion is usually not helpful for divisibility --- src/muz/spacer/spacer_util.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/muz/spacer/spacer_util.cpp b/src/muz/spacer/spacer_util.cpp index 40e02551fca..155ab618f13 100644 --- a/src/muz/spacer/spacer_util.cpp +++ b/src/muz/spacer/spacer_util.cpp @@ -308,7 +308,8 @@ void expand_literals(ast_manager &m, expr_ref_vector &conjs) { for (unsigned i = 0; i < conjs.size(); ++i) { expr *e = conjs[i].get(); - if (m.is_eq(e, e1, e2) && arith.is_int_real(e1)) { + if (m.is_eq(e, e1, e2) && arith.is_int_real(e1) && !arith.is_mod(e1) && + !arith.is_mod(e2)) { conjs[i] = arith.mk_le(e1, e2); if (i + 1 == conjs.size()) { conjs.push_back(arith.mk_ge(e1, e2)); From dc29e8fe64728118bc551f6783403272d7a01100 Mon Sep 17 00:00:00 2001 From: Arie Gurfinkel Date: Mon, 11 Jul 2022 14:16:51 -0400 Subject: [PATCH 59/78] spacer_util: rename m_util into m_arith --- src/muz/spacer/spacer_util.cpp | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/muz/spacer/spacer_util.cpp b/src/muz/spacer/spacer_util.cpp index 155ab618f13..7ac5453ce83 100644 --- a/src/muz/spacer/spacer_util.cpp +++ b/src/muz/spacer/spacer_util.cpp @@ -590,12 +590,12 @@ void simplify_bounds(expr_ref_vector &cube) { simplify_bounds_new(cube); } /// Adhoc rewriting of arithmetic expressions struct adhoc_rewriter_cfg : public default_rewriter_cfg { ast_manager &m; - arith_util m_util; + arith_util m_arith; - adhoc_rewriter_cfg(ast_manager &manager) : m(manager), m_util(m) {} + adhoc_rewriter_cfg(ast_manager &manager) : m(manager), m_arith(m) {} - bool is_le(func_decl const *n) const { return m_util.is_le(n); } - bool is_ge(func_decl const *n) const { return m_util.is_ge(n); } + bool is_le(func_decl const *n) const { return m_arith.is_le(n); } + bool is_ge(func_decl const *n) const { return m_arith.is_ge(n); } br_status reduce_app(func_decl *f, unsigned num, expr *const *args, expr_ref &result, proof_ref &result_pr) { @@ -611,31 +611,32 @@ struct adhoc_rewriter_cfg : public default_rewriter_cfg { br_status mk_le_core(expr *arg1, expr *arg2, expr_ref &result) { // t <= -1 ==> t < 0 ==> !(t >= 0) - if (m_util.is_int(arg1) && m_util.is_minus_one(arg2)) { - result = m.mk_not(m_util.mk_ge(arg1, mk_zero())); + if (m_arith.is_int(arg1) && m_arith.is_minus_one(arg2)) { + result = m.mk_not(m_arith.mk_ge(arg1, mk_zero())); return BR_DONE; } return BR_FAILED; } br_status mk_ge_core(expr *arg1, expr *arg2, expr_ref &result) { // t >= 1 ==> t > 0 ==> !(t <= 0) - if (m_util.is_int(arg1) && is_one(arg2)) { + if (m_arith.is_int(arg1) && is_one(arg2)) { - result = m.mk_not(m_util.mk_le(arg1, mk_zero())); + result = m.mk_not(m_arith.mk_le(arg1, mk_zero())); return BR_DONE; } return BR_FAILED; } - expr *mk_zero() { return m_util.mk_numeral(rational(0), true); } + expr *mk_zero() { return m_arith.mk_numeral(rational(0), true); } bool is_one(expr const *n) const { rational val; - return m_util.is_numeral(n, val) && val.is_one(); + return m_arith.is_numeral(n, val) && val.is_one(); } }; void normalize(expr *e, expr_ref &out, bool use_simplify_bounds, bool use_factor_eqs) { + ast_manager &m = out.m(); params_ref params; // arith_rewriter params.set_bool("sort_sums", true); From 69a5ba47ea9e60c252c9b9425565087c4db60cef Mon Sep 17 00:00:00 2001 From: Arie Gurfinkel Date: Mon, 11 Jul 2022 14:17:34 -0400 Subject: [PATCH 60/78] spacer_util: cleanup normalize() --- src/muz/spacer/spacer_util.cpp | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/muz/spacer/spacer_util.cpp b/src/muz/spacer/spacer_util.cpp index 7ac5453ce83..b9b025a0d44 100644 --- a/src/muz/spacer/spacer_util.cpp +++ b/src/muz/spacer/spacer_util.cpp @@ -647,15 +647,15 @@ void normalize(expr *e, expr_ref &out, bool use_simplify_bounds, params.set_bool("flat", true); // apply rewriter - th_rewriter rw(out.m(), params); + th_rewriter rw(m, params); rw(e, out); - adhoc_rewriter_cfg adhoc_cfg(out.m()); - rewriter_tpl adhoc_rw(out.m(), false, adhoc_cfg); + adhoc_rewriter_cfg adhoc_cfg(m); + rewriter_tpl adhoc_rw(m, false, adhoc_cfg); adhoc_rw(out.get(), out); - if (out.m().is_and(out)) { - expr_ref_vector v(out.m()); + if (m.is_and(out)) { + expr_ref_vector v(m); flatten_and(out, v); if (v.size() > 1) { @@ -677,11 +677,11 @@ void normalize(expr *e, expr_ref &out, bool use_simplify_bounds, << out << "\n" << "to\n" << mk_and(v) << "\n";); - TRACE("spacer_normalize", mbp::term_graph egraph(out.m()); - for (expr *e - : v) egraph.add_lit(to_app(e)); - tout << "Reduced app:\n" - << mk_pp(egraph.to_expr(), out.m()) << "\n";); + TRACE("spacer_normalize", { + mbp::term_graph egraph(m); + for (expr *e : v) egraph.add_lit(to_app(e)); + tout << "Reduced app:\n" << mk_pp(egraph.to_expr(), m) << "\n"; + }); out = mk_and(v); } } From 46ae6efcd2dd09b50179f0a5b22cca923803280a Mon Sep 17 00:00:00 2001 From: Arie Gurfinkel Date: Mon, 11 Jul 2022 14:17:58 -0400 Subject: [PATCH 61/78] spacer_util: formatting --- src/muz/spacer/spacer_util.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/muz/spacer/spacer_util.cpp b/src/muz/spacer/spacer_util.cpp index b9b025a0d44..c6f53197ea3 100644 --- a/src/muz/spacer/spacer_util.cpp +++ b/src/muz/spacer/spacer_util.cpp @@ -979,7 +979,7 @@ struct proc { void operator()(app const *n) const { expr *e1, *e2; if (is_mul(n, e1, e2) && ((is_var(e1) && !is_numeral(e2)) || - (is_var(e2) && !is_numeral(e1)))) + (is_var(e2) && !is_numeral(e1)))) throw found(); } }; @@ -1016,7 +1016,9 @@ bool contains_mod(expr *e, ast_manager &m) { return false; } -bool contains_mod(const expr_ref &e) { return contains_mod(e.get(), e.get_manager()); } +bool contains_mod(const expr_ref &e) { + return contains_mod(e.get(), e.get_manager()); +} namespace contains_real_ns { struct found {}; From 2fd703836c49bf8ec2ec7258d8f102d2336968b5 Mon Sep 17 00:00:00 2001 From: Arie Gurfinkel Date: Mon, 11 Jul 2022 14:19:04 -0400 Subject: [PATCH 62/78] spacer_context: formatting cleanup on subsume and conjecture --- src/muz/spacer/spacer_context.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/muz/spacer/spacer_context.cpp b/src/muz/spacer/spacer_context.cpp index 8860b147e67..053b58ff0f2 100644 --- a/src/muz/spacer/spacer_context.cpp +++ b/src/muz/spacer/spacer_context.cpp @@ -3752,18 +3752,19 @@ lbool context::expand_pob(pob& n, pob_ref_buffer &out) new_pob->set_gas(n.get_gas() - 1); n.set_gas(n.get_gas() - 1); out.push_back(new_pob); + m_stats.m_num_subsume_pobs++; + TRACE("global_verbose", tout << "New subsume pob\n" << mk_pp(new_pob->post(), m) << "\n" << "gas:" << new_pob->get_gas() << "\n";); - out.push_back(new_pob); - m_stats.m_num_subsume_pobs++; } else if (pob* new_pob = m_gg_conjecture ? m_global_gen->mk_conjecture_pob(n) : nullptr) { new_pob->set_gas(n.get_gas() - 1); n.set_gas(n.get_gas() - 1); out.push_back(new_pob); + m_stats.m_num_conj++; + TRACE("global", tout << "New conjecture pob\n" << mk_pp(new_pob->post(), m) << "\n";); - m_stats.m_num_conj++; } } From 002b3a1947cddb2cd187e71d7059bce32e601dab Mon Sep 17 00:00:00 2001 From: Arie Gurfinkel Date: Mon, 11 Jul 2022 14:20:06 -0400 Subject: [PATCH 63/78] spacer_context: fix handling may pobs when abs_weakness is enabled A pob might be undef, so weakness must be bumped up --- src/muz/spacer/spacer_context.cpp | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/muz/spacer/spacer_context.cpp b/src/muz/spacer/spacer_context.cpp index 053b58ff0f2..e82d1e8ee1d 100644 --- a/src/muz/spacer/spacer_context.cpp +++ b/src/muz/spacer/spacer_context.cpp @@ -3782,13 +3782,24 @@ lbool context::expand_pob(pob& n, pob_ref_buffer &out) return l_false; } case l_undef: - // something went wrong - // if the pob is a may pob, bail out + // if the pob is a may pob, handle specially if (n.is_may_pob()) { + // do not create children, but bump weakness + // bail out if this does not help + // AG: do not know why this is a good strategy + if (n.weakness() < 10) { + SASSERT(out.empty()); + n.bump_weakness(); + return expand_pob(n, out); + } n.close(); m_stats.m_expand_pob_undef++; + IF_VERBOSE(1, verbose_stream() << " UNDEF " + << std::fixed << std::setprecision(2) + << watch.get_seconds () << "\n";); return l_undef; } + if (n.weakness() < 10 /* MAX_WEAKENSS */) { bool has_new_child = false; SASSERT(m_weak_abs); From 71a5669d8cab6397d3d6a72b1cf328686c4c067c Mon Sep 17 00:00:00 2001 From: Arie Gurfinkel Date: Mon, 11 Jul 2022 14:21:01 -0400 Subject: [PATCH 64/78] spacer_arith_kernel: enhance debug print --- src/muz/spacer/spacer_arith_kernel.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/muz/spacer/spacer_arith_kernel.cpp b/src/muz/spacer/spacer_arith_kernel.cpp index 851c1e24f22..fdcb6b0d650 100644 --- a/src/muz/spacer/spacer_arith_kernel.cpp +++ b/src/muz/spacer/spacer_arith_kernel.cpp @@ -88,6 +88,7 @@ class simplex_arith_kernel_plugin : public spacer_arith_kernel::plugin { kern.display(tout); tout << "\n"; tout << "basics: " << basics << "\n"; + out.display(tout); }); return out.num_rows() > 0; } From 5c3b2979f8bd3001468c93b33773ac20a61c8f81 Mon Sep 17 00:00:00 2001 From: Arie Gurfinkel Date: Mon, 11 Jul 2022 14:21:44 -0400 Subject: [PATCH 65/78] spacer_global_generalizer: improve matching on conjecture --- src/muz/spacer/spacer_global_generalizer.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/muz/spacer/spacer_global_generalizer.cpp b/src/muz/spacer/spacer_global_generalizer.cpp index b663c9a9c54..2a4efb2b84f 100644 --- a/src/muz/spacer/spacer_global_generalizer.cpp +++ b/src/muz/spacer/spacer_global_generalizer.cpp @@ -509,6 +509,7 @@ bool lemma_global_generalizer::subsumer::over_approximate(expr_ref_vector &a, bool lemma_global_generalizer::do_conjecture(pob_ref &n, lemma_ref &lemma, const expr_ref &lit, unsigned lvl, unsigned gas) { + arith_util arith(m); expr_ref_vector fml_vec(m); expr_ref n_post(n->post(), m); normalize(n_post, n_post, false, false); @@ -518,6 +519,15 @@ bool lemma_global_generalizer::do_conjecture(pob_ref &n, lemma_ref &lemma, expr_ref_vector conj(m); bool is_filtered = filter_out_lit(fml_vec, lit, conj); + expr *e1 = nullptr, *e2 = nullptr; + if (!is_filtered && + (arith.is_le(lit, e1, e2) || arith.is_ge(lit, e1, e2))) { + + // if lit is '<=' or '>=', try matching '==' + is_filtered = + filter_out_lit(fml_vec, expr_ref(m.mk_eq(e1, e2), m), conj); + } + if (!is_filtered) { // -- try using the corresponding lemma instead conj.reset(); From 84cb6e62c044aeb2b799e14dc79dfd131eccc892 Mon Sep 17 00:00:00 2001 From: Arie Gurfinkel Date: Mon, 11 Jul 2022 14:22:15 -0400 Subject: [PATCH 66/78] spacer_global_generalizer: set desired level on conjecture pob --- src/muz/spacer/spacer_global_generalizer.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/muz/spacer/spacer_global_generalizer.cpp b/src/muz/spacer/spacer_global_generalizer.cpp index 2a4efb2b84f..0ea3ce02878 100644 --- a/src/muz/spacer/spacer_global_generalizer.cpp +++ b/src/muz/spacer/spacer_global_generalizer.cpp @@ -563,6 +563,8 @@ bool lemma_global_generalizer::do_conjecture(pob_ref &n, lemma_ref &lemma, scoped_ptr new_pob = alloc(pob, root, n->pt(), lvl, n->depth(), false); if (!new_pob) return false; + new_pob->set_desired_level(n->level()); + new_pob->set_post(mk_and(conj)); new_pob->set_conjecture(); @@ -573,7 +575,7 @@ bool lemma_global_generalizer::do_conjecture(pob_ref &n, lemma_ref &lemma, n->set_expand_bnd(); n->set_gas(gas); n->disable_local_gen(); - TRACE("global", tout << "set conjecture " << n->get_data()->post() + TRACE("global", tout << "set conjecture " << mk_pp(n->get_data()->post(), m) << " at level " << n->get_data()->level() << "\n";); return true; } From 02224c616ab50305f8d1243eb6ff4b3f39782732 Mon Sep 17 00:00:00 2001 From: Arie Gurfinkel Date: Mon, 11 Jul 2022 14:23:04 -0400 Subject: [PATCH 67/78] spacer_global_generalizer: debug print --- src/muz/spacer/spacer_global_generalizer.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/muz/spacer/spacer_global_generalizer.cpp b/src/muz/spacer/spacer_global_generalizer.cpp index 0ea3ce02878..6843880278a 100644 --- a/src/muz/spacer/spacer_global_generalizer.cpp +++ b/src/muz/spacer/spacer_global_generalizer.cpp @@ -603,18 +603,24 @@ void lemma_global_generalizer::generalize(lemma_ref &lemma) { // -- local cluster that includes the new lemma lemma_cluster lc(*cluster); - lc.add_lemma(lemma, true); + // XXX most of the time lemma clustering happens before generalization + // XXX so `add_lemma` is likely to return false, but this does not mean + // XXX that the lemma is not new + bool is_new = lc.add_lemma(lemma, true); + (void)is_new; const expr_ref &pat = lc.get_pattern(); TRACE("global", { tout << "Global generalization of:\n" << mk_and(lemma->get_cube()) << "\n" + << (is_new ? "new" : "old") << "\n" << "Using cluster:\n" << pat << "\n" << "Existing lemmas in the cluster:\n"; - for (const auto &lemma : cluster->get_lemmas()) { - tout << mk_and(lemma.get_lemma()->get_cube()) << "\n"; + for (const auto &li : cluster->get_lemmas()) { + tout << mk_and(li.get_lemma()->get_cube()) + << " lvl:" << li.get_lemma()->level() << "\n"; } }); From 7deadf2f7a08d6593d02a199e045453560dd338a Mon Sep 17 00:00:00 2001 From: Arie Gurfinkel Date: Mon, 11 Jul 2022 14:23:41 -0400 Subject: [PATCH 68/78] spacer_global_generalizer: set min level on new pobs the new level should not be higher than the pob that was generalized --- src/muz/spacer/spacer_global_generalizer.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/muz/spacer/spacer_global_generalizer.cpp b/src/muz/spacer/spacer_global_generalizer.cpp index 6843880278a..a26a3430116 100644 --- a/src/muz/spacer/spacer_global_generalizer.cpp +++ b/src/muz/spacer/spacer_global_generalizer.cpp @@ -647,7 +647,8 @@ void lemma_global_generalizer::generalize(lemma_ref &lemma) { << mk_pp(pat, m) << "\n" << "with gas " << cluster->get_gas() << "\n";); unsigned gas = cluster->get_pob_gas(); - unsigned lvl = cluster->get_min_lvl(); + unsigned lvl = lc.get_min_lvl(); + if (pob) lvl = std::min(lvl, pob->level()); if (do_conjecture(pob, lemma, lit, lvl, gas)) { // decrease the number of times this cluster is going to be used // for conjecturing @@ -670,11 +671,14 @@ void lemma_global_generalizer::generalize(lemma_ref &lemma) { if (m_subsumer.subsume(lc, new_post, bindings)) { class pob *root = pob->parent(); while (root->parent()) root = root->parent(); + + unsigned new_lvl = lc.get_min_lvl(); + if (pob) new_lvl = std::min(new_lvl, pob->level()); scoped_ptr new_pob = - alloc(class pob, root, pob->pt(), cluster->get_min_lvl(), - pob->depth(), false); + alloc(class pob, root, pob->pt(), new_lvl, pob->depth(), false); if (!new_pob) return; + new_pob->set_desired_level(pob->level()); new_pob->set_post(mk_and(new_post), bindings); new_pob->set_subsume(); pob->set_data(new_pob.detach()); @@ -687,8 +691,8 @@ void lemma_global_generalizer::generalize(lemma_ref &lemma) { pob->disable_local_gen(); cluster->dec_gas(); - TRACE("global", tout << "Create subsume pob at level " - << cluster->get_min_lvl() << "\n" + TRACE("global", tout << "Create subsume pob at level " << new_lvl + << "\n" << mk_and(new_post) << "\n";); } } From 99af59ddcb3b87804b70ab73e6607ddf0c56fca6 Mon Sep 17 00:00:00 2001 From: Arie Gurfinkel Date: Mon, 11 Jul 2022 14:24:28 -0400 Subject: [PATCH 69/78] spacer_global_generalizer: do no re-create closed pobs If a generalized pob exist and closed, do not re-create it. --- src/muz/spacer/spacer_global_generalizer.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/muz/spacer/spacer_global_generalizer.cpp b/src/muz/spacer/spacer_global_generalizer.cpp index a26a3430116..83da38a82ba 100644 --- a/src/muz/spacer/spacer_global_generalizer.cpp +++ b/src/muz/spacer/spacer_global_generalizer.cpp @@ -734,11 +734,14 @@ pob *lemma_global_generalizer::mk_subsume_pob(pob &n) { pob *data = n.get_data(); pob *f = n.pt().find_pob(data->parent(), data->post()); - if (f && f->is_in_queue()) { + if (f && (f->is_in_queue() || f->is_closed())) { n.reset_data(); return nullptr; } + TRACE("global", tout << "mk_subsume_pob at level " << data->level() + << " with post state:\n" + << mk_pp(data->post(), m) << "\n";); f = n.pt().mk_pob(data->parent(), data->level(), data->depth(), data->post(), n.get_binding()); f->set_subsume(); @@ -754,7 +757,10 @@ pob *lemma_global_generalizer::mk_conjecture_pob(pob &n) { pob *data = n.get_data(); pob *f = n.pt().find_pob(data->parent(), data->post()); - if (f && f->is_in_queue()) return nullptr; + if (f && (f->is_in_queue() || f->is_closed())) { + n.reset_data(); + return nullptr; + } f = n.pt().mk_pob(data->parent(), data->level(), data->depth(), data->post(), {m}); From 0dbb22c678fc887e61534cdcf31530a2ebd21c46 Mon Sep 17 00:00:00 2001 From: Arie Gurfinkel Date: Tue, 12 Jul 2022 09:26:56 -0400 Subject: [PATCH 70/78] spacer_context: normalize twice --- src/muz/spacer/spacer_context.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/muz/spacer/spacer_context.cpp b/src/muz/spacer/spacer_context.cpp index e82d1e8ee1d..b1676520b9a 100644 --- a/src/muz/spacer/spacer_context.cpp +++ b/src/muz/spacer/spacer_context.cpp @@ -98,6 +98,11 @@ void pob::inherit(pob const &p) { SASSERT(!is_in_queue()); SASSERT(m_parent == p.m_parent); SASSERT(&m_pt == &p.m_pt); + + // -- HACK: normalize second time because th_rewriter is not idempotent + if (m_post != p.m_post) { + normalize(m_post, m_post, false, false); + } SASSERT(m_post == p.m_post); SASSERT(!m_new_post); From 21d14bcdaec861c69c38b5e89c90764fe7f360b1 Mon Sep 17 00:00:00 2001 From: Arie Gurfinkel Date: Thu, 4 Aug 2022 13:35:47 -0400 Subject: [PATCH 71/78] spacer_context: forward propagate only same kind of pobs --- src/muz/spacer/spacer_context.cpp | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/src/muz/spacer/spacer_context.cpp b/src/muz/spacer/spacer_context.cpp index b1676520b9a..5b66d1b63d3 100644 --- a/src/muz/spacer/spacer_context.cpp +++ b/src/muz/spacer/spacer_context.cpp @@ -3200,13 +3200,21 @@ bool context::check_reachability () node = last_reachable; last_reachable = nullptr; if (m_pob_queue.is_root(*node)) { return true; } - if (is_reachable (*node->parent())) { - last_reachable = node->parent (); + + // do not check the parent if its may pob status is different + if (node->parent()->is_may_pob() != node->is_may_pob()) + { + last_reachable = nullptr; + break; + } + + if (is_reachable(*node->parent())) { + last_reachable = node->parent(); SASSERT(last_reachable->is_closed()); - last_reachable->close (); + last_reachable->close(); } else if (!node->parent()->is_closed()) { /* bump node->parent */ - node->parent ()->bump_weakness(); + node->parent()->bump_weakness(); } } @@ -3251,15 +3259,18 @@ bool context::check_reachability () case l_true: SASSERT(m_pob_queue.size() == old_sz); SASSERT(new_pobs.empty()); + node->close(); last_reachable = node; - last_reachable->close (); - if (m_pob_queue.is_root(*node)) {return true;} + if (m_pob_queue.is_root(*node)) { return true; } break; case l_false: SASSERT(m_pob_queue.size() == old_sz); // re-queue all pobs introduced by global gen and any pobs that can be blocked at a higher level for (auto pob : new_pobs) { - TRACE("gg", tout << "pob: is_may_pob " << pob->is_may_pob() << "\n";); + TRACE("gg", tout << "pob: is_may_pob " << pob->is_may_pob() + << " with post:\n" + << mk_pp(pob->post(), m) + << "\n";); //if ((pob->is_may_pob() && pob->post() != node->post()) || is_requeue(*pob)) { if (is_requeue(*pob)) { TRACE("gg", From e4844318b329bf1fea68ae6af5da2cefaf071d6b Mon Sep 17 00:00:00 2001 From: Arie Gurfinkel Date: Thu, 28 Jul 2022 17:20:44 -0400 Subject: [PATCH 72/78] sketch of inductive generalizer A better implementation of inductive generalizer that in addition to dropping literals also attempts to weaken them. Current implementation is a sketch to be extended based on examples/requirements. --- src/muz/spacer/CMakeLists.txt | 1 + src/muz/spacer/spacer_context.cpp | 3 +- src/muz/spacer/spacer_generalizers.h | 7 +- .../spacer/spacer_ind_lemma_generalizer.cpp | 303 ++++++++++++++++++ 4 files changed, 312 insertions(+), 2 deletions(-) create mode 100644 src/muz/spacer/spacer_ind_lemma_generalizer.cpp diff --git a/src/muz/spacer/CMakeLists.txt b/src/muz/spacer/CMakeLists.txt index d05a9d43514..28fbddc7b0d 100644 --- a/src/muz/spacer/CMakeLists.txt +++ b/src/muz/spacer/CMakeLists.txt @@ -24,6 +24,7 @@ z3_add_component(spacer spacer_quant_generalizer.cpp spacer_arith_generalizers.cpp spacer_global_generalizer.cpp + spacer_ind_lemma_generalizer.cpp spacer_expand_bnd_generalizer.cpp spacer_cluster.cpp spacer_callback.cpp diff --git a/src/muz/spacer/spacer_context.cpp b/src/muz/spacer/spacer_context.cpp index 5b66d1b63d3..fe7d025ac26 100644 --- a/src/muz/spacer/spacer_context.cpp +++ b/src/muz/spacer/spacer_context.cpp @@ -2700,7 +2700,8 @@ void context::init_lemma_generalizers() //m_lemma_generalizers.push_back (alloc (unsat_core_generalizer, *this)); if (m_use_ind_gen) { - m_lemma_generalizers.push_back(alloc(lemma_bool_inductive_generalizer, *this, 0)); + // m_lemma_generalizers.push_back(alloc(lemma_bool_inductive_generalizer, *this, 0)); + m_lemma_generalizers.push_back(alloc_lemma_inductive_generalizer(*this)); } // after the lemma is minimized (maybe should also do before) diff --git a/src/muz/spacer/spacer_generalizers.h b/src/muz/spacer/spacer_generalizers.h index 2b41252ad9b..fb26c756c0b 100644 --- a/src/muz/spacer/spacer_generalizers.h +++ b/src/muz/spacer/spacer_generalizers.h @@ -175,5 +175,10 @@ class limit_num_generalizer : public lemma_generalizer { void collect_statistics(statistics &st) const override; void reset_statistics() override { m_st.reset(); } }; -} // namespace spacer +lemma_generalizer * +alloc_lemma_inductive_generalizer(spacer::context &ctx, + bool only_array_eligible = false, + bool enable_literal_weakening = true); + +} // namespace spacer diff --git a/src/muz/spacer/spacer_ind_lemma_generalizer.cpp b/src/muz/spacer/spacer_ind_lemma_generalizer.cpp new file mode 100644 index 00000000000..e139e743e5c --- /dev/null +++ b/src/muz/spacer/spacer_ind_lemma_generalizer.cpp @@ -0,0 +1,303 @@ +#include "ast/expr_functors.h" +#include "muz/spacer/spacer_context.h" + +using namespace spacer; + +namespace { + +class contains_array_op_proc : public i_expr_pred { + ast_manager &m; + family_id m_array_fid; + + public: + contains_array_op_proc(ast_manager &manager) + : m(manager), m_array_fid(array_util(m).get_family_id()) {} + bool operator()(expr *e) override { + return is_app(e) && to_app(e)->get_family_id() == m_array_fid; + } +}; + +class lemma_inductive_generalizer : public lemma_generalizer { + struct stats { + unsigned count; + unsigned weaken_success; + unsigned weaken_fail; + stopwatch watch; + stats() { reset(); } + void reset() { + count = 0; + weaken_success = 0; + weaken_fail = 0; + watch.reset(); + } + }; + + ast_manager &m; + expr_ref m_true; + stats m_st; + bool m_only_array_eligible; + bool m_enable_litweak; + + contains_array_op_proc m_contains_array_op; + check_pred m_contains_array_pred; + + expr_ref_vector m_pinned; + lemma *m_lemma = nullptr; + spacer::pred_transformer *m_pt = nullptr; + unsigned m_weakness = 0; + unsigned m_level = 0; + ptr_vector m_cube; + + // temporary vector + expr_ref_vector m_core; + + public: + lemma_inductive_generalizer(spacer::context &ctx, + bool only_array_eligible = false, + bool enable_literal_weakening = true) + : lemma_generalizer(ctx), m(ctx.get_ast_manager()), + m_true(m.mk_true(), m), m_only_array_eligible(only_array_eligible), + m_enable_litweak(enable_literal_weakening), m_contains_array_op(m), + m_contains_array_pred(m_contains_array_op, m), + + m_pinned(m), m_core(m) {} + + private: + // -- true if literal \p lit is eligible to be generalized + bool is_eligible(expr *lit) { + return !m_only_array_eligible || has_arrays(lit); + } + + bool has_arrays(expr *lit) { return m_contains_array_op(lit); } + + void reset() { + m_cube.reset(); + m_weakness = 0; + m_level = 0; + m_pt = nullptr; + m_pinned.reset(); + m_core.reset(); + } + + void setup(lemma_ref &lemma) { + // check that we start in uninitialized state + SASSERT(m_pt == nullptr); + m_lemma = lemma.get(); + m_pt = &lemma->get_pob()->pt(); + m_weakness = lemma->weakness(); + m_level = lemma->level(); + auto &cube = lemma->get_cube(); + m_cube.reset(); + for (auto *lit : cube) { m_cube.push_back(lit); } + } + + // loads current generalization from m_cube to m_core + void load_cube_to_core() { + m_core.reset(); + for (unsigned i = 0, sz = m_cube.size(); i < sz; ++i) { + auto *lit = m_cube.get(i); + if (lit == m_true) continue; + m_core.push_back(lit); + } + } + + // returns true if m_cube is inductive + bool is_cube_inductive() { + load_cube_to_core(); + if (m_core.empty()) return false; + + unsigned used_level; + if (m_pt->check_inductive(m_level, m_core, used_level, m_weakness)) { + m_level = std::max(m_level, used_level); + return true; + } + return false; + } + + // intersect m_cube with m_core + unsigned update_cube_by_core(unsigned from = 0) { + // generalize away all literals in m_cube that are not in m_core + // do not assume anything about order of literals in m_core + + unsigned success = 0; + // mark core + ast_fast_mark2 marked_core; + for (auto *v : m_core) { marked_core.mark(v); } + + // replace unmarked literals by m_true in m_cube + for (unsigned i = from, sz = m_cube.size(); i < sz; ++i) { + auto *lit = m_cube.get(i); + if (lit == m_true) continue; + if (!marked_core.is_marked(lit)) { + m_cube[i] = m_true; + success++; + } + } + return success; + } + // generalizes m_core and removes from m_cube all generalized literals + unsigned generalize_core(unsigned from = 0) { + unsigned success = 0; + unsigned used_level; + + // -- while it is possible that a single literal can be generalized to + // false, + // -- it is fairly unlikely. Thus, we give up generalizing in this case. + if (m_core.empty()) return 0; + + // -- check whether candidate in m_core is inductive + if (m_pt->check_inductive(m_level, m_core, used_level, m_weakness)) { + success += update_cube_by_core(from); + // update m_level to the largest level at which the the current + // candidate in m_cube is inductive + m_level = std::max(m_level, used_level); + } + + return success; + } + + // generalizes (i.e., drops) a specific literal of m_cube + unsigned generalize1(unsigned lit_idx) { + + if (!is_eligible(m_cube.get(lit_idx))) return 0; + + // -- populate m_core with all literals except the one being generalized + m_core.reset(); + for (unsigned i = 0, sz = m_cube.size(); i < sz; ++i) { + auto *lit = m_cube.get(i); + if (lit == m_true || i == lit_idx) continue; + m_core.push_back(lit); + } + + return generalize_core(lit_idx); + } + + // generalizes all literals of m_cube in a given range + unsigned generalize_range(unsigned from, unsigned to) { + unsigned success = 0; + for (unsigned i = from; i < to; ++i) { success += generalize1(i); } + return success; + } + + // weakens a given literal of m_cube + // weakening replaces a literal by a weaker literal(s) + // for example, x=y might get weakened into one of x<=y or y<=x + unsigned weaken1(unsigned lit_idx) { + if (!is_eligible(m_cube.get(lit_idx))) return 0; + if (m_cube.get(lit_idx) == m_true) return 0; + + unsigned success = 0; + unsigned cube_sz = m_cube.size(); + + // -- save literal to be generalized, and replace it by true + expr *saved_lit = m_cube.get(lit_idx); + m_cube[lit_idx] = m_true; + + // -- add new weaker literals to end of m_cube and attempt to generalize + expr_ref_vector weakening(m); + weakening.push_back(saved_lit); + expand_literals(m, weakening); + if (weakening.get(0) != saved_lit) { + for (auto *lit : weakening) { + m_cube.push_back(lit); + m_pinned.push_back(lit); + } + + if (m_cube.size() - cube_sz >= 2) { + // normal case: generalize new weakening + success += generalize_range(cube_sz, m_cube.size()); + } else { + // special case -- weaken literal by another literal, check that + // cube is still inductive + success += (is_cube_inductive() ? 1 : 0); + } + } + + // -- failed to generalize, restore removed literal and m_cube + if (success == 0) { + m_cube[lit_idx] = saved_lit; + m_cube.shrink(cube_sz); + m_st.weaken_fail++; + } else { + m_st.weaken_success++; + } + + return success; + } + + // weakens literals of m_cube in a given range + unsigned weaken_range(unsigned from, unsigned to) { + unsigned success = 0; + for (unsigned i = from; i < to; ++i) { success += weaken1(i); } + return success; + } + + public: + // entry point for generalization + void operator()(lemma_ref &lemma) override { + if (lemma->get_cube().empty()) return; + + m_st.count++; + scoped_watch _w_(m_st.watch); + + setup(lemma); + + unsigned num_gens = 0; + + // -- first round -- generalize by dropping literals + num_gens += generalize_range(0, m_cube.size()); + + // -- if weakening is enabled, start next round + if (m_enable_litweak) { + unsigned cube_sz = m_cube.size(); + // -- second round -- weaken literals that cannot be dropped + num_gens += weaken_range(0, cube_sz); + + // -- third round -- weaken literals produced in prev round + if (cube_sz < m_cube.size()) + num_gens += weaken_range(cube_sz, m_cube.size()); + } + + // if there is at least one generalization, update lemma + if (num_gens > 0) { + TRACE("indgen", + tout << "Generalized " << num_gens << " literals\n";); + + // reuse m_core since it is not needed for anything else + m_core.reset(); + for (auto *lit : m_cube) { + if (lit != m_true) m_core.push_back(lit); + } + + TRACE("indgen", tout << "Original: " << lemma->get_cube() << "\n" + << "Generalized: " << m_core << "\n";); + + lemma->update_cube(lemma->get_pob(), m_core); + lemma->set_level(m_level); + } + + reset(); + + return; + } + + void collect_statistics(statistics &st) const override { + st.update("time.spacer.solve.reach.gen.ind", m_st.watch.get_seconds()); + st.update("SPACER inductive gen", m_st.count); + st.update("SPACER inductive gen weaken success", m_st.weaken_success); + st.update("SPACER inductive gen weaken fail", m_st.weaken_fail); + } + void reset_statistics() override { m_st.reset(); } +}; +} // namespace + +namespace spacer { +lemma_generalizer * +alloc_lemma_inductive_generalizer(spacer::context &ctx, + bool only_array_eligible, + bool enable_literal_weakening) { + return alloc(lemma_inductive_generalizer, ctx, only_array_eligible, + enable_literal_weakening); +} + +} // namespace spacer From a62fc725124dbf89fddedbc308c14f02610d0b08 Mon Sep 17 00:00:00 2001 From: Arie Gurfinkel Date: Tue, 30 Aug 2022 13:42:28 -0400 Subject: [PATCH 73/78] fix ordering in spacer_cluster_util --- src/muz/spacer/spacer_cluster_util.cpp | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/muz/spacer/spacer_cluster_util.cpp b/src/muz/spacer/spacer_cluster_util.cpp index cafb40e3139..72d1d226906 100644 --- a/src/muz/spacer/spacer_cluster_util.cpp +++ b/src/muz/spacer/spacer_cluster_util.cpp @@ -83,6 +83,16 @@ struct bool_and_less_proc { if (e1 == e2) return false; + if (e1->get_kind() != e2->get_kind()) return e1->get_kind() < e2->get_kind(); + if (!is_app(e1)) return ast_lt(e1, e2); + + app *a1 = to_app(e1), *a2 = to_app(e2); + + if (a1->get_family_id() != a2->get_family_id()) + return a1->get_family_id() < a2->get_family_id(); + if (a1->get_decl_kind() != a2->get_decl_kind()) + return a1->get_decl_kind() < a2->get_decl_kind(); + if (!(m_arith.is_le(e1, t1, k1) || m_arith.is_lt(e1, t1, k1) || m_arith.is_ge(e1, t1, k1) || m_arith.is_gt(e1, t1, k1))) { t1 = e1; @@ -98,10 +108,10 @@ struct bool_and_less_proc { if (t1 == t2) return ast_lt(k1, k2); - if (!(is_app(t1) && is_app(t2))) { - return is_app(t1) == is_app(t2) ? ast_lt(t1, t2) - : is_app(t1) < is_app(t2); - } + if (t1->get_kind() != t2->get_kind()) + return t1->get_kind() < t2->get_kind(); + + if (!is_app(t1)) return ast_lt(t1, t2); unsigned d1 = to_app(t1)->get_depth(); unsigned d2 = to_app(t2)->get_depth(); From 5a8eb94787ef54de76c99085d5e138f05abd50bd Mon Sep 17 00:00:00 2001 From: Arie Gurfinkel Date: Tue, 30 Aug 2022 13:43:10 -0400 Subject: [PATCH 74/78] fix resetting of substitution matcher in spacer_conjecture Old code would forget to reset the substitution provided to the sem_matcher. Thus, if the substitution was matched once (i.e., one literal of interest is found), no other literal would be matched. --- src/muz/spacer/spacer_conjecture.cpp | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/muz/spacer/spacer_conjecture.cpp b/src/muz/spacer/spacer_conjecture.cpp index 7279047c094..e95f0c7b3d9 100644 --- a/src/muz/spacer/spacer_conjecture.cpp +++ b/src/muz/spacer/spacer_conjecture.cpp @@ -71,15 +71,18 @@ bool find_unique_mono_var_lit(const expr_ref &pattern, expr_ref &res) { bool filter_out_lit(const expr_ref_vector &vec, const expr_ref &lit, expr_ref_vector &out) { ast_manager &m = vec.get_manager(); bool dirty = false, pos = false; - sem_matcher m_matcher(m); + sem_matcher matcher(m); substitution sub(m); out.reset(); - sub.reserve(1, get_num_vars(lit.get())); + unsigned lit_num_vars = get_num_vars(lit.get()); SASSERT(!(m.is_not(lit) && m.is_eq(to_app(lit)->get_arg(0)))); for (auto &c : vec) { - m_matcher.reset(); - if (m_matcher(lit, c, sub, pos) && pos) { + sub.reset(); + sub.reserve(1, lit_num_vars); + matcher.reset(); + + if (matcher(lit, c, sub, pos) && pos) { if (is_numeric_sub(sub)) { dirty = true; continue; @@ -87,6 +90,9 @@ bool filter_out_lit(const expr_ref_vector &vec, const expr_ref &lit, expr_ref_ve } out.push_back(c); } + + CTRACE("global", dirty, + tout << "Filtered " << lit << " from " << vec << "\n got " << out << "\n";); return dirty; } } // namespace spacer From d14f0124f9fa9dcd1bb18c582eae8efaecfbc805 Mon Sep 17 00:00:00 2001 From: Arie Gurfinkel Date: Tue, 30 Aug 2022 13:44:41 -0400 Subject: [PATCH 75/78] add spacer_util is_normalized() method used for debugging only --- src/muz/spacer/spacer_util.cpp | 15 +++++++++++++++ src/muz/spacer/spacer_util.h | 6 +++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/muz/spacer/spacer_util.cpp b/src/muz/spacer/spacer_util.cpp index c6f53197ea3..f8f71954061 100644 --- a/src/muz/spacer/spacer_util.cpp +++ b/src/muz/spacer/spacer_util.cpp @@ -633,6 +633,21 @@ struct adhoc_rewriter_cfg : public default_rewriter_cfg { } }; +bool is_normalized(expr_ref e, bool use_simplify_bounds, bool use_factor_eqs) { + expr_ref out(e.m()); + normalize(e, out, use_simplify_bounds, use_factor_eqs); + + expr_ref out0 = out; + if (e != out) { normalize(out, out, use_simplify_bounds, use_factor_eqs); } + + CTRACE("inherit_bug", e != out, + tout << "e==out0: " << (e == out0) << " e==out: " << (e == out) + << " out0==out: " << (out0 == out) << "\n"; + tout << "e: " << e << "\n" + << "out0: " << out0 << "\n" + << "out: " << out << "\n";); + return e == out; +} void normalize(expr *e, expr_ref &out, bool use_simplify_bounds, bool use_factor_eqs) { diff --git a/src/muz/spacer/spacer_util.h b/src/muz/spacer/spacer_util.h index 8d8e1c3340e..dbc3083a212 100644 --- a/src/muz/spacer/spacer_util.h +++ b/src/muz/spacer/spacer_util.h @@ -104,6 +104,9 @@ void expand_literals(ast_manager &m, expr_ref_vector &conjs); expr_ref_vector compute_implicant_literals(model &mdl, expr_ref_vector &formula); void simplify_bounds(expr_ref_vector &lemmas); +bool is_normalized(expr_ref e, bool use_simplify_bounds = true, + bool factor_eqs = false); + void normalize(expr *e, expr_ref &out, bool use_simplify_bounds = true, bool factor_eqs = false); @@ -160,7 +163,8 @@ bool find_unique_mono_var_lit(const expr_ref &p, expr_ref &lit); /// Drop all literals that numerically match \p lit, from \p fml_vec. /// /// \p abs_fml holds the result. Returns true if any literal has been dropped -bool filter_out_lit(const expr_ref_vector &in, const expr_ref &lit, expr_ref_vector &out); +bool filter_out_lit(const expr_ref_vector &in, const expr_ref &lit, + expr_ref_vector &out); /// Returns true if range of s is numeric bool is_numeric_sub(const substitution &s); From a715d930c9c4a7b57e84d2c9dad3d214dace79ae Mon Sep 17 00:00:00 2001 From: Arie Gurfinkel Date: Tue, 30 Aug 2022 13:45:14 -0400 Subject: [PATCH 76/78] simplify normalization of pob expressions pob expressions are normalized to increase syntactic matching. Some of the normalization rules seem out of place, so removing them for now. --- src/muz/spacer/spacer_util.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/muz/spacer/spacer_util.cpp b/src/muz/spacer/spacer_util.cpp index f8f71954061..bc9224771b2 100644 --- a/src/muz/spacer/spacer_util.cpp +++ b/src/muz/spacer/spacer_util.cpp @@ -656,7 +656,8 @@ void normalize(expr *e, expr_ref &out, bool use_simplify_bounds, // arith_rewriter params.set_bool("sort_sums", true); params.set_bool("gcd_rounding", true); - params.set_bool("arith_lhs", true); + // params.set_bool("arith_lhs", true); + params.set_bool("arith_ineq_lhs", true); // poly_rewriter params.set_bool("som", true); params.set_bool("flat", true); @@ -665,9 +666,9 @@ void normalize(expr *e, expr_ref &out, bool use_simplify_bounds, th_rewriter rw(m, params); rw(e, out); - adhoc_rewriter_cfg adhoc_cfg(m); - rewriter_tpl adhoc_rw(m, false, adhoc_cfg); - adhoc_rw(out.get(), out); + // adhoc_rewriter_cfg adhoc_cfg(m); + // rewriter_tpl adhoc_rw(m, false, adhoc_cfg); + // adhoc_rw(out.get(), out); if (m.is_and(out)) { expr_ref_vector v(m); @@ -700,6 +701,8 @@ void normalize(expr *e, expr_ref &out, bool use_simplify_bounds, out = mk_and(v); } } + + // normalize_order(out, out); } // rewrite term such that the pretty printing is easier to read From dc2a51d14bbec7d97944d329b17be60af7488653 Mon Sep 17 00:00:00 2001 From: Arie Gurfinkel Date: Tue, 30 Aug 2022 13:46:09 -0400 Subject: [PATCH 77/78] fix in spacer_global_generalizer If conjecture fails, do not try other generalization strategies -- they will not apply. --- src/muz/spacer/spacer_global_generalizer.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/muz/spacer/spacer_global_generalizer.cpp b/src/muz/spacer/spacer_global_generalizer.cpp index 83da38a82ba..8c0d7aa6fbc 100644 --- a/src/muz/spacer/spacer_global_generalizer.cpp +++ b/src/muz/spacer/spacer_global_generalizer.cpp @@ -614,6 +614,7 @@ void lemma_global_generalizer::generalize(lemma_ref &lemma) { TRACE("global", { tout << "Global generalization of:\n" << mk_and(lemma->get_cube()) << "\n" + << "at lvl: " << lemma->level() << "\n" << (is_new ? "new" : "old") << "\n" << "Using cluster:\n" << pat << "\n" @@ -654,6 +655,11 @@ void lemma_global_generalizer::generalize(lemma_ref &lemma) { // for conjecturing cluster->dec_gas(); return; + } else { + // -- if conjecture failed, there is nothing else to do. + // -- the pob matched pre-condition for conjecture, so it should not + // be subsumed + return; } } From 2d664ba86c83d4a732aa557668d90b68df47d496 Mon Sep 17 00:00:00 2001 From: Arie Gurfinkel Date: Tue, 30 Aug 2022 13:47:10 -0400 Subject: [PATCH 78/78] fix in spacer_context do not check that may pob is blocked by existing lemmas. It is likely to be blocked. Our goal is to block it again and generalize to a new lemma. This can be further improved by moving directly to generalization when pob is blocked by existing lemmas... --- src/muz/spacer/spacer_context.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/muz/spacer/spacer_context.cpp b/src/muz/spacer/spacer_context.cpp index fe7d025ac26..4c5c13e3428 100644 --- a/src/muz/spacer/spacer_context.cpp +++ b/src/muz/spacer/spacer_context.cpp @@ -3537,7 +3537,7 @@ lbool context::expand_pob(pob& n, pob_ref_buffer &out) unsigned num_reuse_reach = 0; - if (m_push_pob && n.pt().is_blocked(n, uses_level)) { + if (!n.is_may_pob() && m_push_pob && n.pt().is_blocked(n, uses_level)) { // if (!m_pob_queue.is_root (n)) n.close (); IF_VERBOSE (1, verbose_stream () << " K " << std::fixed << std::setprecision(2) @@ -3551,6 +3551,7 @@ lbool context::expand_pob(pob& n, pob_ref_buffer &out) STRACE("spacer_progress", tout << "This pob can be blocked by instantiation\n";); } + if ((n.is_may_pob()) && n.get_gas() == 0) { TRACE("global", tout << "Cant prove may pob. Collapsing " << mk_pp(n.post(), m) << "\n";);