From d02b0cde7ad221f44aaa232b2e4a4fdc4fd66335 Mon Sep 17 00:00:00 2001 From: Nikolaj Bjorner Date: Mon, 7 Sep 2020 20:35:32 -0700 Subject: [PATCH] running updates to bv_solver (#4674) * na Signed-off-by: Nikolaj Bjorner * na Signed-off-by: Nikolaj Bjorner * na Signed-off-by: Nikolaj Bjorner * na Signed-off-by: Nikolaj Bjorner * na Signed-off-by: Nikolaj Bjorner * na Signed-off-by: Nikolaj Bjorner * na Signed-off-by: Nikolaj Bjorner * na Signed-off-by: Nikolaj Bjorner * dbg Signed-off-by: Nikolaj Bjorner * bv Signed-off-by: Nikolaj Bjorner * drat and fresh Signed-off-by: Nikolaj Bjorner * move ackerman functionality Signed-off-by: Nikolaj Bjorner * na Signed-off-by: Nikolaj Bjorner * debugability Signed-off-by: Nikolaj Bjorner * towards debugability Signed-off-by: Nikolaj Bjorner * missing file Signed-off-by: Nikolaj Bjorner * na Signed-off-by: Nikolaj Bjorner * na Signed-off-by: Nikolaj Bjorner * remove csp Signed-off-by: Nikolaj Bjorner --- src/ast/CMakeLists.txt | 1 - src/ast/ast_smt2_pp.cpp | 13 + src/ast/ast_smt2_pp.h | 8 + src/ast/bv_decl_plugin.cpp | 14 +- src/ast/bv_decl_plugin.h | 4 + src/ast/csp_decl_plugin.cpp | 386 -------- src/ast/csp_decl_plugin.h | 163 ---- src/ast/euf/euf_egraph.cpp | 231 ++--- src/ast/euf/euf_egraph.h | 81 +- src/ast/euf/euf_enode.h | 15 +- src/ast/pattern/expr_pattern_match.h | 16 +- src/cmd_context/cmd_context.cpp | 3 - src/sat/dimacs.cpp | 217 ++++- src/sat/dimacs.h | 86 ++ src/sat/sat_aig_cuts.h | 10 +- src/sat/sat_binspr.h | 8 +- src/sat/sat_drat.cpp | 257 +++--- src/sat/sat_drat.h | 39 +- src/sat/sat_elim_eqs.cpp | 1 - src/sat/sat_extension.h | 3 +- src/sat/sat_simplifier.cpp | 3 +- src/sat/sat_solver.cpp | 124 ++- src/sat/sat_solver.h | 18 +- src/sat/sat_solver/inc_sat_solver.cpp | 2 +- src/sat/sat_solver_core.h | 3 + src/sat/sat_types.h | 6 +- src/sat/smt/CMakeLists.txt | 2 + src/sat/smt/ba_internalize.cpp | 4 + src/sat/smt/ba_solver.cpp | 8 +- src/sat/smt/ba_solver.h | 6 +- src/sat/smt/bv_ackerman.cpp | 153 ++++ src/sat/smt/bv_ackerman.h | 77 ++ src/sat/smt/bv_internalize.cpp | 674 ++++++++------ src/sat/smt/bv_solver.cpp | 649 ++++++++++++-- src/sat/smt/bv_solver.h | 188 ++-- src/sat/smt/euf_ackerman.cpp | 58 +- src/sat/smt/euf_ackerman.h | 9 +- src/sat/smt/euf_internalize.cpp | 82 +- src/sat/smt/euf_invariant.cpp | 48 + src/sat/smt/euf_model.cpp | 13 +- src/sat/smt/euf_proof.cpp | 65 +- src/sat/smt/euf_solver.cpp | 258 ++++-- src/sat/smt/euf_solver.h | 111 ++- src/sat/smt/sat_smt.h | 3 + src/sat/smt/sat_th.cpp | 46 +- src/sat/smt/sat_th.h | 30 +- src/sat/tactic/goal2sat.cpp | 146 ++- src/sat/tactic/sat_tactic.cpp | 12 +- src/shell/CMakeLists.txt | 1 + src/shell/drat_frontend.cpp | 197 ++++ src/shell/drat_frontend.h | 8 + src/shell/main.cpp | 19 +- src/shell/opt_frontend.cpp | 1 - src/smt/CMakeLists.txt | 1 - src/smt/smt_setup.cpp | 10 - src/smt/theory_jobscheduler.cpp | 1183 ------------------------- src/smt/theory_jobscheduler.h | 247 ------ src/test/egraph.cpp | 2 + src/util/dlist.h | 50 ++ src/util/scoped_limit_trail.h | 36 + src/util/statistics.cpp | 6 +- src/util/statistics.h | 6 +- src/util/union_find.h | 12 +- 63 files changed, 3049 insertions(+), 3084 deletions(-) delete mode 100644 src/ast/csp_decl_plugin.cpp delete mode 100644 src/ast/csp_decl_plugin.h create mode 100644 src/sat/smt/bv_ackerman.cpp create mode 100644 src/sat/smt/bv_ackerman.h create mode 100644 src/sat/smt/euf_invariant.cpp create mode 100644 src/shell/drat_frontend.cpp create mode 100644 src/shell/drat_frontend.h delete mode 100644 src/smt/theory_jobscheduler.cpp delete mode 100644 src/smt/theory_jobscheduler.h create mode 100644 src/util/scoped_limit_trail.h diff --git a/src/ast/CMakeLists.txt b/src/ast/CMakeLists.txt index aad9e1826c1..d4c7291f446 100644 --- a/src/ast/CMakeLists.txt +++ b/src/ast/CMakeLists.txt @@ -14,7 +14,6 @@ z3_add_component(ast ast_translation.cpp ast_util.cpp bv_decl_plugin.cpp - csp_decl_plugin.cpp datatype_decl_plugin.cpp decl_collector.cpp display_dimacs.cpp diff --git a/src/ast/ast_smt2_pp.cpp b/src/ast/ast_smt2_pp.cpp index 4ef42d62cbf..e2767a687e5 100644 --- a/src/ast/ast_smt2_pp.cpp +++ b/src/ast/ast_smt2_pp.cpp @@ -1342,6 +1342,17 @@ std::ostream& operator<<(std::ostream& out, mk_ismt2_pp const & p) { return out; } +std::ostream& operator<<(std::ostream& out, mk_ismt2_func const& p) { + smt2_pp_environment_dbg env(p.m); + format_ref r(fm(p.m)); + unsigned len = 0; + r = env.pp_fdecl(p.m_fn, len); + params_ref pa; + pp(out, r.get(), p.m, pa); + return out; +} + + std::ostream& operator<<(std::ostream& out, expr_ref const& e) { return out << mk_ismt2_pp(e.get(), e.get_manager()); } @@ -1388,6 +1399,8 @@ std::ostream& operator<<(std::ostream& out, sort_ref_vector const& e) { return out; } + + #ifdef Z3DEBUG void pp(expr const * n, ast_manager & m) { std::cout << mk_ismt2_pp(const_cast(n), m) << std::endl; diff --git a/src/ast/ast_smt2_pp.h b/src/ast/ast_smt2_pp.h index d9a1aff5702..6b4d221af99 100644 --- a/src/ast/ast_smt2_pp.h +++ b/src/ast/ast_smt2_pp.h @@ -122,6 +122,7 @@ struct mk_ismt2_pp { mk_ismt2_pp(ast * t, ast_manager & m, unsigned indent = 0, unsigned num_vars = 0, char const * var_prefix = nullptr); }; + std::ostream& operator<<(std::ostream& out, mk_ismt2_pp const & p); std::ostream& operator<<(std::ostream& out, expr_ref const& e); @@ -136,3 +137,10 @@ std::ostream& operator<<(std::ostream& out, var_ref_vector const& e); std::ostream& operator<<(std::ostream& out, func_decl_ref_vector const& e); std::ostream& operator<<(std::ostream& out, sort_ref_vector const& e); +struct mk_ismt2_func { + func_decl* m_fn; + ast_manager &m; + mk_ismt2_func(func_decl* f, ast_manager& m): m_fn(f), m(m) {} +}; + +std::ostream& operator<<(std::ostream& out, mk_ismt2_func const& f); diff --git a/src/ast/bv_decl_plugin.cpp b/src/ast/bv_decl_plugin.cpp index dfcb7053764..cf53d59bf09 100644 --- a/src/ast/bv_decl_plugin.cpp +++ b/src/ast/bv_decl_plugin.cpp @@ -131,12 +131,8 @@ void bv_decl_plugin::finalize() { DEC_REF(m_int2bv); DEC_REF(m_bv2int); - vector >::iterator it = m_bit2bool.begin(); - vector >::iterator end = m_bit2bool.end(); - for (; it != end; ++it) { - ptr_vector & ds = *it; + for (auto& ds : m_bit2bool) DEC_REF(ds); - } DEC_REF(m_mkbv); } @@ -829,6 +825,14 @@ bool bv_recognizers::is_bv2int(expr const* e, expr*& r) const { return true; } +bool bv_recognizers::is_bit2bool(expr* e, expr*& bv, unsigned& idx) const { + if (!is_bit2bool(e)) + return false; + bv = to_app(e)->get_arg(0); + idx = to_app(e)->get_parameter(0).get_int(); + return true; +} + bool bv_recognizers::mult_inverse(rational const & n, unsigned bv_size, rational & result) { if (n.is_one()) { result = n; diff --git a/src/ast/bv_decl_plugin.h b/src/ast/bv_decl_plugin.h index 95982cf9fa4..2f3242dff24 100644 --- a/src/ast/bv_decl_plugin.h +++ b/src/ast/bv_decl_plugin.h @@ -368,6 +368,9 @@ class bv_recognizers { MATCH_BINARY(is_bv_sdivi); MATCH_BINARY(is_bv_udivi); MATCH_BINARY(is_bv_smodi); + MATCH_UNARY(is_bit2bool); + MATCH_UNARY(is_int2bv); + bool is_bit2bool(expr* e, expr*& bv, unsigned& idx) const; rational norm(rational const & val, unsigned bv_size, bool is_signed) const ; rational norm(rational const & val, unsigned bv_size) const { return norm(val, bv_size, false); } @@ -432,6 +435,7 @@ class bv_util : public bv_recognizers { app * mk_bvsmul_no_ovfl(expr* m, expr* n) { return m_manager.mk_app(get_fid(), OP_BSMUL_NO_OVFL, n, m); } app * mk_bvsmul_no_udfl(expr* m, expr* n) { return m_manager.mk_app(get_fid(), OP_BSMUL_NO_UDFL, n, m); } app * mk_bvumul_no_ovfl(expr* m, expr* n) { return m_manager.mk_app(get_fid(), OP_BUMUL_NO_OVFL, n, m); } + app * mk_bit2bool(expr* e, unsigned idx) { parameter p(idx); return m_manager.mk_app(get_fid(), OP_BIT2BOOL, 1, &p, 1, &e); } private: void log_bv_from_exprs(app * r, unsigned n, expr* const* es) { diff --git a/src/ast/csp_decl_plugin.cpp b/src/ast/csp_decl_plugin.cpp deleted file mode 100644 index 0e7688969f6..00000000000 --- a/src/ast/csp_decl_plugin.cpp +++ /dev/null @@ -1,386 +0,0 @@ -/*++ -Copyright (c) 2018 Microsoft Corporation - -Module Name: - - csp_decl_plugin.h - -Abstract: - - Declarations used for a job-shop scheduling domain. - -Author: - - Nikolaj Bjorner (nbjorner) 2018-8-9 - -Revision History: - - ---*/ -#include "ast/csp_decl_plugin.h" -#include "ast/arith_decl_plugin.h" - -void csp_decl_plugin::set_manager(ast_manager* m, family_id fid) { - decl_plugin::set_manager(m, fid); - m_int_sort = nullptr; - m_alist_sort = nullptr; - m_job_sort = nullptr; - m_resource_sort = nullptr; -} - -void csp_decl_plugin::init() { - if (!m_int_sort) { - m_int_sort = m_manager->mk_sort(m_manager->mk_family_id("arith"), INT_SORT); - m_alist_sort = m_manager->mk_sort(symbol("AList"), sort_info(m_family_id, ALIST_SORT)); - m_job_sort = m_manager->mk_sort(symbol("Job"), sort_info(m_family_id, JOB_SORT)); - m_resource_sort = m_manager->mk_sort(symbol("Resource"), sort_info(m_family_id, RESOURCE_SORT)); - m_manager->inc_ref(m_int_sort); - m_manager->inc_ref(m_resource_sort); - m_manager->inc_ref(m_job_sort); - m_manager->inc_ref(m_alist_sort); - } -} - -void csp_decl_plugin::finalize() { - m_manager->dec_ref(m_alist_sort); - m_manager->dec_ref(m_job_sort); - m_manager->dec_ref(m_resource_sort); - m_manager->dec_ref(m_int_sort); -} - -sort * csp_decl_plugin::mk_sort(decl_kind k, unsigned num_parameters, parameter const * parameters) { - init(); - if (num_parameters != 0) { - m_manager->raise_exception("no parameters expected with job-shop sort"); - } - switch (static_cast(k)) { - case JOB_SORT: return m_job_sort; - case RESOURCE_SORT: return m_resource_sort; - case ALIST_SORT: return m_alist_sort; - default: UNREACHABLE(); return nullptr; - } -} - -func_decl * csp_decl_plugin::mk_func_decl( - decl_kind k, unsigned num_parameters, parameter const * parameters, unsigned arity, sort * const * domain, sort *) { - init(); - symbol name; - sort* rng = nullptr; - switch (static_cast(k)) { - case OP_JS_JOB: - check_arity(arity); - check_index1(num_parameters, parameters); - name = symbol("job"); - rng = m_job_sort; - break; - case OP_JS_RESOURCE: - check_arity(arity); - check_index1(num_parameters, parameters); - name = symbol("resource"); - rng = m_resource_sort; - break; - case OP_JS_RESOURCE_MAKESPAN: - if (arity != 1 || domain[0] != m_resource_sort) m_manager->raise_exception("makespan expects a resource argument"); - name = symbol("makespan"); - rng = m_int_sort; - break; - case OP_JS_START: - if (arity != 1 || domain[0] != m_job_sort) m_manager->raise_exception("start expects a job argument"); - if (num_parameters > 0) m_manager->raise_exception("no parameters"); - name = symbol("job-start"); - rng = m_int_sort; - break; - case OP_JS_END: - if (arity != 1 || domain[0] != m_job_sort) m_manager->raise_exception("resource expects a job argument"); - if (num_parameters > 0) m_manager->raise_exception("no parameters"); - name = symbol("job-end"); - rng = m_int_sort; - break; - case OP_JS_JOB2RESOURCE: - if (arity != 1 || domain[0] != m_job_sort) m_manager->raise_exception("job2resource expects a job argument"); - if (num_parameters > 0) m_manager->raise_exception("no parameters"); - name = symbol("job2resource"); - rng = m_resource_sort; - break; - case OP_JS_MODEL: - // has no parameters - // all arguments are of sort alist - name = symbol("js-model"); - rng = m_manager->mk_bool_sort(); - break; - case OP_JS_JOB_RESOURCE: - if (arity != 6) m_manager->raise_exception("add-job-resource expects 6 arguments"); - if (domain[0] != m_job_sort) m_manager->raise_exception("first argument of add-job-resource expects should be a job"); - if (domain[1] != m_resource_sort) m_manager->raise_exception("second argument of add-job-resource expects should be a resource"); - if (domain[2] != m_int_sort) m_manager->raise_exception("3rd argument of add-job-resource expects should be an integer"); - if (domain[3] != m_int_sort) m_manager->raise_exception("4th argument of add-job-resource expects should be an integer"); - if (domain[4] != m_int_sort) m_manager->raise_exception("5th argument of add-job-resource expects should be an integer"); - if (domain[5] != m_alist_sort) m_manager->raise_exception("6th argument of add-job-resource should be a list of properties"); - name = symbol("add-job-resource"); - rng = m_alist_sort; - break; - case OP_JS_RESOURCE_AVAILABLE: - if (arity != 6) m_manager->raise_exception("add-resource-available expects 6 arguments"); - if (domain[0] != m_resource_sort) m_manager->raise_exception("first argument of add-resource-available expects should be a resource"); - if (domain[1] != m_int_sort) m_manager->raise_exception("2nd argument of add-resource-available expects should be an integer"); - if (domain[2] != m_int_sort) m_manager->raise_exception("3rd argument of add-resource-available expects should be an integer"); - if (domain[3] != m_int_sort) m_manager->raise_exception("4th argument of add-resource-available expects should be an integer"); - if (domain[4] != m_int_sort) m_manager->raise_exception("5th argument of add-resource-available expects should be an integer"); - if (domain[5] != m_alist_sort) m_manager->raise_exception("6th argument of add-resource-available should be a list of properties"); - name = symbol("add-resource-available"); - rng = m_alist_sort; - break; - case OP_JS_JOB_PREEMPTABLE: - if (arity != 1 || domain[0] != m_job_sort) - m_manager->raise_exception("set-preemptable expects one argument, which is a job"); - name = symbol("set-preemptable"); - rng = m_alist_sort; - break; - case OP_JS_PROPERTIES: - if (arity != 0) m_manager->raise_exception("js-properties takes no arguments"); - for (unsigned i = 0; i < num_parameters; ++i) { - if (!parameters[i].is_symbol()) m_manager->raise_exception("js-properties expects a list of keyword parameters"); - } - name = symbol("js-properties"); - rng = m_alist_sort; - break; - case OP_JS_JOB_GOAL: - if (arity != 1 || domain[0] != m_job_sort) - m_manager->raise_exception("add-job-goal expects one argument, which is a job"); - if (num_parameters != 2 || !parameters[0].is_symbol() || !parameters[1].is_int()) - m_manager->raise_exception("add-job-goal expects one symbol and one integer parameter"); - name = symbol("add-job-goal"); - rng = m_alist_sort; - break; - case OP_JS_OBJECTIVE: - if (arity != 0) - m_manager->raise_exception("add-optimization-objective expects no arguments"); - if (num_parameters != 1 || !parameters[0].is_symbol()) - m_manager->raise_exception("add-optimization-objective expects one symbol parameter"); - name = symbol("add-optimization-objective"); - rng = m_alist_sort; - break; - default: - UNREACHABLE(); - return nullptr; - } - return m_manager->mk_func_decl(name, arity, domain, rng, func_decl_info(m_family_id, k, num_parameters, parameters)); - -} - -void csp_decl_plugin::check_arity(unsigned arity) { - if (arity > 0) - m_manager->raise_exception("csp variables use parameters only and take no arguments"); -} - -void csp_decl_plugin::check_index1(unsigned num_parameters, parameter const* ps) { - if (num_parameters != 1 || !ps[0].is_int()) - m_manager->raise_exception("csp variable expects a single integer parameter"); -} - -void csp_decl_plugin::check_index2(unsigned num_parameters, parameter const* ps) { - if (num_parameters != 2 || !ps[0].is_int() || !ps[1].is_int()) - m_manager->raise_exception("csp variable expects two integer parameters"); -} - - -bool csp_decl_plugin::is_value(app * e) const { - return is_app_of(e, m_family_id, OP_JS_JOB) || is_app_of(e, m_family_id, OP_JS_RESOURCE); -} - -void csp_decl_plugin::get_op_names(svector & op_names, symbol const & logic) { - if (logic == symbol("CSP")) { - op_names.push_back(builtin_name("job", OP_JS_JOB)); - op_names.push_back(builtin_name("resource", OP_JS_RESOURCE)); - op_names.push_back(builtin_name("makespan", OP_JS_RESOURCE_MAKESPAN)); - op_names.push_back(builtin_name("job-start", OP_JS_START)); - op_names.push_back(builtin_name("job-end", OP_JS_END)); - op_names.push_back(builtin_name("job2resource", OP_JS_JOB2RESOURCE)); - op_names.push_back(builtin_name("js-model", OP_JS_MODEL)); - op_names.push_back(builtin_name("add-job-resource", OP_JS_JOB_RESOURCE)); - op_names.push_back(builtin_name("add-resource-available", OP_JS_RESOURCE_AVAILABLE)); - op_names.push_back(builtin_name("set-preemptable", OP_JS_JOB_PREEMPTABLE)); - op_names.push_back(builtin_name("js-properties", OP_JS_PROPERTIES)); - op_names.push_back(builtin_name("add-job-goal", OP_JS_JOB_GOAL)); - op_names.push_back(builtin_name("add-optimization-objective", OP_JS_OBJECTIVE)); - } -} - -void csp_decl_plugin::get_sort_names(svector & sort_names, symbol const & logic) { - if (logic == symbol("CSP")) { - sort_names.push_back(builtin_name("Job", JOB_SORT)); - sort_names.push_back(builtin_name("Resource", RESOURCE_SORT)); - } -} - -expr * csp_decl_plugin::get_some_value(sort * s) { - init(); - parameter p(0); - if (is_sort_of(s, m_family_id, JOB_SORT)) - return m_manager->mk_const(mk_func_decl(OP_JS_JOB, 1, &p, 0, nullptr, nullptr)); - if (is_sort_of(s, m_family_id, RESOURCE_SORT)) - return m_manager->mk_const(mk_func_decl(OP_JS_RESOURCE, 1, &p, 0, nullptr, nullptr)); - UNREACHABLE(); - return nullptr; -} - - -csp_util::csp_util(ast_manager& m): m(m) { - m_fid = m.mk_family_id("csp"); - m_plugin = static_cast(m.get_plugin(m_fid)); -} - -sort* csp_util::mk_job_sort() { - return m_plugin->mk_job_sort(); -} - -sort* csp_util::mk_resource_sort() { - return m_plugin->mk_resource_sort(); -} - -app* csp_util::mk_job(unsigned j) { - parameter p(j); - return m.mk_const(m.mk_func_decl(m_fid, OP_JS_JOB, 1, &p, 0, (sort*const*)nullptr, nullptr)); -} - -unsigned csp_util::job2id(expr* j) { - if (is_app_of(j, m_fid, OP_JS_JOB)) { - return to_app(j)->get_decl()->get_parameter(0).get_int(); - } - SASSERT(is_app_of(j, m_fid, OP_JS_START) || - is_app_of(j, m_fid, OP_JS_END) || - is_app_of(j, m_fid, OP_JS_JOB2RESOURCE)); - return job2id(to_app(j)->get_arg(0)); -} - -app* csp_util::mk_resource(unsigned r) { - parameter p(r); - return m.mk_const(m.mk_func_decl(m_fid, OP_JS_RESOURCE, 1, &p, 0, (sort*const*)nullptr, nullptr)); -} - -unsigned csp_util::resource2id(expr* r) { - SASSERT(is_app_of(r, m_fid, OP_JS_RESOURCE)); - return to_app(r)->get_decl()->get_parameter(0).get_int(); -} - -app* csp_util::mk_start(unsigned j) { - app_ref job(mk_job(j), m); - sort* js = m.get_sort(job); - return m.mk_app(m.mk_func_decl(m_fid, OP_JS_START, 0, nullptr, 1, &js, nullptr), job); -} - -app* csp_util::mk_end(unsigned j) { - app_ref job(mk_job(j), m); - sort* js = m.get_sort(job); - return m.mk_app(m.mk_func_decl(m_fid, OP_JS_END, 0, nullptr, 1, &js, nullptr), job); -} - -app* csp_util::mk_job2resource(unsigned j) { - app_ref job(mk_job(j), m); - sort* js = m.get_sort(job); - return m.mk_app(m.mk_func_decl(m_fid, OP_JS_JOB2RESOURCE, 0, nullptr, 1, &js, nullptr), job); -} - -app* csp_util::mk_makespan(unsigned r) { - app_ref resource(mk_resource(r), m); - sort* rs = m.get_sort(resource); - return m.mk_app(m.mk_func_decl(m_fid, OP_JS_RESOURCE_MAKESPAN, 0, nullptr, 1, &rs, nullptr), resource); -} - -bool csp_util::is_resource(expr* e, unsigned& r) { - return is_app_of(e, m_fid, OP_JS_RESOURCE) && (r = resource2id(e), true); -} - -bool csp_util::is_makespan(expr * e, unsigned& r) { - return is_app_of(e, m_fid, OP_JS_RESOURCE_MAKESPAN) && is_resource(to_app(e)->get_arg(0), r); -} - -bool csp_util::is_job(expr* e, unsigned& j) { - return is_app_of(e, m_fid, OP_JS_JOB) && (j = job2id(e), true); -} - -bool csp_util::is_job2resource(expr* e, unsigned& j) { - return is_app_of(e, m_fid, OP_JS_JOB2RESOURCE) && (j = job2id(e), true); -} - -bool csp_util::is_add_resource_available(expr * e, expr *& res, unsigned& loadpct, unsigned& cap_time, uint64_t& start, uint64_t& end, svector& properties) { - if (!is_app_of(e, m_fid, OP_JS_RESOURCE_AVAILABLE)) return false; - res = to_app(e)->get_arg(0); - arith_util a(m); - rational r; - if (!a.is_numeral(to_app(e)->get_arg(1), r) || !r.is_unsigned()) return false; - loadpct = r.get_unsigned(); - if (!a.is_numeral(to_app(e)->get_arg(2), r) || !r.is_unsigned()) return false; - cap_time = r.get_unsigned(); - if (!a.is_numeral(to_app(e)->get_arg(3), r) || !r.is_uint64()) return false; - start = r.get_uint64(); - if (!a.is_numeral(to_app(e)->get_arg(4), r) || !r.is_uint64()) return false; - end = r.get_uint64(); - if (!is_js_properties(to_app(e)->get_arg(5), properties)) return false; - return true; -} - -bool csp_util::is_add_job_resource(expr * e, expr *& job, expr*& res, unsigned& loadpct, uint64_t& capacity, uint64_t& end, svector& properties) { - if (!is_app_of(e, m_fid, OP_JS_JOB_RESOURCE)) return false; - job = to_app(e)->get_arg(0); - res = to_app(e)->get_arg(1); - arith_util a(m); - rational r; - if (!a.is_numeral(to_app(e)->get_arg(2), r) || !r.is_unsigned()) return false; - loadpct = r.get_unsigned(); - if (!a.is_numeral(to_app(e)->get_arg(3), r) || !r.is_uint64()) return false; - capacity = r.get_uint64(); - if (!a.is_numeral(to_app(e)->get_arg(4), r) || !r.is_uint64()) return false; - end = r.get_uint64(); - if (!is_js_properties(to_app(e)->get_arg(5), properties)) return false; - return true; -} - -bool csp_util::is_set_preemptable(expr* e, expr *& job) { - if (!is_app_of(e, m_fid, OP_JS_JOB_PREEMPTABLE)) return false; - job = to_app(e)->get_arg(0); - return true; -} - -bool csp_util::is_js_properties(expr* e, svector& properties) { - if (!is_app_of(e, m_fid, OP_JS_PROPERTIES)) - return false; - unsigned sz = to_app(e)->get_decl()->get_num_parameters(); - for (unsigned i = 0; i < sz; ++i) { - properties.push_back(to_app(e)->get_decl()->get_parameter(i).get_symbol()); - } - return true; -} - -bool csp_util::is_job_goal(expr* e, js_job_goal& goal, unsigned& level, expr*& job) { - if (!is_app_of(e, m_fid, OP_JS_JOB_GOAL)) - return false; - SASSERT(2 == to_app(e)->get_decl()->get_num_parameters()); - SASSERT(1 == to_app(e)->get_num_args()); - symbol g = to_app(e)->get_decl()->get_parameter(0).get_symbol(); - level = to_app(e)->get_decl()->get_parameter(1).get_int(); - if (g == ":earliest-end-time" || g == "earliest-end-time") - goal = JS_JOB_GOAL_EARLIEST_END_TIME; - else if (g == ":latest-start-time" || g == "latest-start-time") - goal = JS_JOB_GOAL_LATEST_START_TIME; - else - return false; - job = to_app(e)->get_arg(0); - return true; -} - -bool csp_util::is_objective(expr* e, js_optimization_objective& objective) { - if (!is_app_of(e, m_fid, OP_JS_OBJECTIVE)) - return false; - SASSERT(1 == to_app(e)->get_decl()->get_num_parameters()); - symbol obj = to_app(e)->get_decl()->get_parameter(0).get_symbol(); - if (obj == ":duration" || obj == "duration") - objective = JS_OBJECTIVE_DURATION; - else if (obj == ":priority" || obj == "priority") - objective = JS_OBJECTIVE_PRIORITY; - else - return false; - return true; -} - - diff --git a/src/ast/csp_decl_plugin.h b/src/ast/csp_decl_plugin.h deleted file mode 100644 index fcbe4a443ef..00000000000 --- a/src/ast/csp_decl_plugin.h +++ /dev/null @@ -1,163 +0,0 @@ -/*++ -Copyright (c) 2018 Microsoft Corporation - -Module Name: - - csp_decl_plugin.h - -Abstract: - - Declarations used for a job-shop scheduling domain. - - The job-shop domain comprises of constants job(j), resource(r) - - It finds values to variables: - - start(j), end(j), job2resource(j) - - It assumes a background of: - - resources : Job -> Resource -> Int * LoadPct - time to run job j on resource r assuming LoadPct - - runtime : Job -> Int - time to run job j if not associated with any resource - - capacity : Resource -> Int -> LoadPct - capacity of resource r at time t, given as sequence of time intervals - // assume each job has at least one resource associated with it. - // introduce a dummy resource if needed. - - // Theory: - end(j) - start(j) = time-to-execute(j) - time-to-execute(j) := time-to-execute(j, resource(j)) otherwise - - time-to-execute(j, r) := (T - start(j)) - where capacity(j,r) = sum_{t = start(j)}^{T} load(loadpct(j,r), r, t) - - capacity(j, r) := cap where (cap, loadpct) = resources j r - loadpct(j, r) := loadpct where (cap, loadpct) = resources j r - - load(loadpct, r, t) := min(capacity r t, loadpct) / loadpct - - capacity(r, t) >= sum_{j | job-on-resource(j, r, t) } min(capacity r t, loadpct(j, r)) - - // Macros: - job-on-resource(j, r) := r = resource(j); - job-on-resource(j, r, t) := (job-on-resource(j, r) & start(j) <= t <= end(j)); - start_min(j, t) := start(j) >= t; - end_max(j, t) := end(j) <= t; - job_link(j1, j2, startstart, hard) := start(j1) = start(j2); - job_link(j1, j2, startstart, soft) := start(j1) <= start(j2); - job_link(j1, j2, endend, hard) := end(j1) = end(j2); - job_link(j1, j2, endend, soft) := end(j2) <= end(j1); - job_link(j1, j2, endstart, hard) := end(j1) = start(j2); - job_link(j1, j2, endstart, soft) := end(j2) <= start(j1); - job_link(j1, j2, startend, hard) := end(j2) = start(j1); - job_link(j1, j2, startend, soft) := end(j1) <= start(j2); - job_delay(j1, j2, t) := end(j1) + t <= end(j2); - job_on_same_resource(j1, j2) := resource(j1) = resource(j2); - job_not_on_same_resource(j1, j2) := resource(j1) != resource(j2); - job_time_intersect(j1, j2) := start(j1) <= end(j2) <= end(j1) || start(j2) <= end(j1) <= end(j2); - - job-on-resource(j, r, t) => job-property(j) = null or job_property(j) in working_time_property(r, t); - -Author: - - Nikolaj Bjorner (nbjorner) 2018-8-9 - -Revision History: - - ---*/ -#pragma once -#include "ast/ast.h" - -enum js_sort_kind { - JOB_SORT, - RESOURCE_SORT, - ALIST_SORT -}; - -enum js_op_kind { - OP_JS_JOB, // value of type job - OP_JS_RESOURCE, // value of type resource - OP_JS_RESOURCE_MAKESPAN, // makespan of resource: the minimal resource time required for assigned jobs. - OP_JS_START, // start time of a job - OP_JS_END, // end time of a job - OP_JS_JOB2RESOURCE, // resource associated with job - OP_JS_MODEL, // jobscheduler model - OP_JS_JOB_RESOURCE, // model declaration for job assignment to resource - OP_JS_JOB_PREEMPTABLE, // model declaration for whether job is pre-emptable - OP_JS_RESOURCE_AVAILABLE, // model declaration for availability intervals of resource - OP_JS_PROPERTIES, // model declaration of a set of properties. Each property is a keyword. - OP_JS_JOB_GOAL, // job goal objective :earliest-end-time or :latest-start-time - OP_JS_OBJECTIVE // duration or completion-time -}; - -enum js_job_goal { - JS_JOB_GOAL_EARLIEST_END_TIME, - JS_JOB_GOAL_LATEST_START_TIME -}; - -enum js_optimization_objective { - JS_OBJECTIVE_DURATION, - JS_OBJECTIVE_PRIORITY -}; - -class csp_decl_plugin : public decl_plugin { - void init(); -public: - csp_decl_plugin() {} - ~csp_decl_plugin() override {} - void finalize() override; - void set_manager(ast_manager* m, family_id fid) override; - decl_plugin * mk_fresh() override { return alloc(csp_decl_plugin); } - sort * mk_sort(decl_kind k, unsigned num_parameters, parameter const * parameters) override; - func_decl * mk_func_decl(decl_kind k, unsigned num_parameters, parameter const * parameters, - unsigned arity, sort * const * domain, sort * range) override; - bool is_value(app * e) const override; - bool is_unique_value(app * e) const override { return is_value(e); } - void get_op_names(svector & op_names, symbol const & logic) override; - void get_sort_names(svector & sort_names, symbol const & logic) override; - expr * get_some_value(sort * s) override; - sort * mk_job_sort() const { return m_job_sort; } - sort * mk_resource_sort() const { return m_resource_sort; } - sort * mk_alist_sort() const { return m_alist_sort; } -private: - sort* m_job_sort; - sort* m_resource_sort; - sort* m_alist_sort; - sort* m_int_sort; - - void check_arity(unsigned arity); - void check_index1(unsigned n, parameter const* ps); - void check_index2(unsigned n, parameter const* ps); -}; - -class csp_util { - ast_manager& m; - family_id m_fid; - csp_decl_plugin* m_plugin; -public: - csp_util(ast_manager& m); - sort* mk_job_sort(); - sort* mk_resource_sort(); - - app* mk_job(unsigned j); - app* mk_resource(unsigned r); - app* mk_start(unsigned j); - app* mk_end(unsigned j); - app* mk_job2resource(unsigned j); - app* mk_makespan(unsigned r); - - bool is_job(expr* e, unsigned& j); - bool is_job2resource(expr* e, unsigned& j); - bool is_resource(expr* e, unsigned& r); - bool is_makespan(expr* e, unsigned& r); - bool is_add_resource_available(expr * e, expr *& res, unsigned& loadpct, unsigned& cap_time, uint64_t& start, uint64_t& end, svector& properites); - bool is_add_job_resource(expr * e, expr *& job, expr*& res, unsigned& loadpct, uint64_t& capacity, uint64_t& finite_capacity_end, svector& properites); - bool is_set_preemptable(expr* e, expr *& job); - bool is_model(expr* e) const { return is_app_of(e, m_fid, OP_JS_MODEL); } - bool is_js_properties(expr* e, svector& properties); - bool is_job_goal(expr* e, js_job_goal& goal, unsigned& level, expr*& job); - bool is_objective(expr* e, js_optimization_objective& objective); - -private: - unsigned job2id(expr* j); - unsigned resource2id(expr* r); - -}; diff --git a/src/ast/euf/euf_egraph.cpp b/src/ast/euf/euf_egraph.cpp index 4f1e3d10979..74c0c0b0f4c 100644 --- a/src/ast/euf/euf_egraph.cpp +++ b/src/ast/euf/euf_egraph.cpp @@ -17,52 +17,11 @@ Module Name: #include "ast/euf/euf_egraph.h" #include "ast/ast_pp.h" +#include "ast/ast_ll_pp.h" #include "ast/ast_translation.h" namespace euf { - /** - \brief Trail for add_th_var - */ - class add_th_var_trail : public trail { - enode * m_enode; - theory_id m_th_id; - public: - add_th_var_trail(enode * n, theory_id th_id): - m_enode(n), - m_th_id(th_id) { - } - - void undo(egraph & ctx) override { - theory_var v = m_enode->get_th_var(m_th_id); - SASSERT(v != null_theory_var); - m_enode->del_th_var(m_th_id); - enode * root = m_enode->get_root(); - if (root != m_enode && root->get_th_var(m_th_id) == v) - root->del_th_var(m_th_id); - } - }; - - /** - \brief Trail for replace_th_var - */ - class replace_th_var_trail : public trail { - enode * m_enode; - unsigned m_th_id:8; - unsigned m_old_th_var:24; - public: - replace_th_var_trail(enode * n, theory_id th_id, theory_var old_var): - m_enode(n), - m_th_id(th_id), - m_old_th_var(old_var) { - } - - void undo(egraph & ctx) override { - SASSERT(m_enode->get_th_var(m_th_id) != null_theory_var); - m_enode->replace_th_var(m_old_th_var, m_th_id); - } - }; - void egraph::undo_eq(enode* r1, enode* n1, unsigned r2_num_parents) { enode* r2 = r1->get_root(); r2->dec_class_size(r1->class_size()); @@ -82,6 +41,9 @@ namespace euf { enode* n = enode::mk(m_region, f, num_args, args); m_nodes.push_back(n); m_exprs.push_back(f); + push_node(n); + for (unsigned i = 0; i < num_args; ++i) + set_merge_enabled(args[i], true); return n; } @@ -104,8 +66,7 @@ namespace euf { void egraph::reinsert_equality(enode* p) { SASSERT(is_equality(p)); if (p->get_arg(0)->get_root() == p->get_arg(1)->get_root()) { - m_new_lits.push_back(enode_bool_pair(p, true)); - ++m_stats.m_num_eqs; + add_literal(p, true); } } @@ -114,19 +75,14 @@ namespace euf { } void egraph::force_push() { + if (m_num_scopes == 0) + return; for (; m_num_scopes > 0; --m_num_scopes) { - scope s; - s.m_inconsistent = m_inconsistent; - s.m_num_eqs = m_eqs.size(); - s.m_num_nodes = m_nodes.size(); - s.m_trail_sz = m_trail.size(); - s.m_new_lits_sz = m_new_lits.size(); - s.m_new_th_eqs_sz = m_new_th_eqs.size(); - s.m_new_lits_qhead = m_new_lits_qhead; - s.m_new_th_eqs_qhead = m_new_th_eqs_qhead; - m_scopes.push_back(s); + m_scopes.push_back(m_updates.size()); m_region.push_scope(); } + m_updates.push_back(update_record(m_new_lits_qhead, update_record::new_lits_qhead())); + m_updates.push_back(update_record(m_new_th_eqs_qhead, update_record::new_th_eq_qhead())); } void egraph::update_children(enode* n) { @@ -147,6 +103,7 @@ namespace euf { return n; if (is_equality(n)) { update_children(n); + reinsert_equality(n); return n; } enode_bool_pair p = m_table.insert(n); @@ -170,6 +127,18 @@ namespace euf { n->m_parents.finalize(); } + void egraph::add_th_eq(theory_id id, theory_var v1, theory_var v2, enode* c, enode* r) { + m_new_th_eqs.push_back(th_eq(id, v1, v2, c, r)); + m_updates.push_back(update_record(update_record::new_th_eq())); + ++m_stats.m_num_th_eqs; + } + + void egraph::add_literal(enode* n, bool is_eq) { + m_new_lits.push_back(enode_bool_pair(n, is_eq)); + m_updates.push_back(update_record(update_record::new_lit())); + if (is_eq) ++m_stats.m_num_eqs; else ++m_stats.m_num_lits; + } + void egraph::add_th_var(enode* n, theory_var v, theory_id id) { force_push(); theory_var w = n->get_th_var(id); @@ -177,21 +146,37 @@ namespace euf { if (w == null_theory_var) { n->add_th_var(v, id, m_region); - m_trail.push_back(new (m_region) add_th_var_trail(n, id)); + m_updates.push_back(update_record(n, id, update_record::add_th_var())); if (r != n) { theory_var u = r->get_th_var(id); if (u == null_theory_var) r->add_th_var(v, id, m_region); else - m_new_th_eqs.push_back(th_eq(id, v, u, n, r)); + add_th_eq(id, v, u, n, r); } } else { theory_var u = r->get_th_var(id); SASSERT(u != v && u != null_theory_var); n->replace_th_var(v, id); - m_trail.push_back(new (m_region) replace_th_var_trail(n, id, u)); - m_new_th_eqs.push_back(th_eq(id, v, u, n, r)); + m_updates.push_back(update_record(n, id, u, update_record::replace_th_var())); + add_th_eq(id, v, u, n, r); + } + } + + void egraph::undo_add_th_var(enode* n, theory_id tid) { + theory_var v = n->get_th_var(tid); + SASSERT(v != null_theory_var); + n->del_th_var(tid); + enode* root = n->get_root(); + if (root != n && root->get_th_var(tid) == v) + root->del_th_var(tid); + } + + void egraph::set_merge_enabled(enode* n, bool enable_merge) { + if (enable_merge != n->merge_enabled()) { + m_updates.push_back(update_record(n, update_record::toggle_merge())); + n->set_merge_enabled(enable_merge); } } @@ -202,29 +187,59 @@ namespace euf { } num_scopes -= m_num_scopes; unsigned old_lim = m_scopes.size() - num_scopes; - scope s = m_scopes[old_lim]; - for (unsigned i = m_eqs.size(); i-- > s.m_num_eqs; ) { - auto const& p = m_eqs[i]; - undo_eq(p.r1, p.n1, p.r2_num_parents); - } - for (unsigned i = m_nodes.size(); i-- > s.m_num_nodes; ) { - enode* n = m_nodes[i]; + unsigned num_updates = m_scopes[old_lim]; + auto undo_node = [&](enode* n) { if (n->num_args() > 1) m_table.erase(n); m_expr2enode[n->get_owner_id()] = nullptr; n->~enode(); - } - undo_trail_stack(*this, m_trail, s.m_trail_sz); - m_inconsistent = s.m_inconsistent; - m_new_lits_qhead = s.m_new_lits_qhead; - m_new_th_eqs_qhead = s.m_new_th_eqs_qhead; - m_eqs.shrink(s.m_num_eqs); - m_nodes.shrink(s.m_num_nodes); - m_exprs.shrink(s.m_num_nodes); - m_new_lits.shrink(s.m_new_lits_sz); - m_new_th_eqs.shrink(s.m_new_th_eqs_sz); + m_nodes.pop_back(); + m_exprs.pop_back(); + }; + for (unsigned i = m_updates.size(); i-- > num_updates; ) { + auto const& p = m_updates[i]; + switch (p.tag) { + case update_record::tag_t::is_add_node: + undo_node(p.r1); + break; + case update_record::tag_t::is_toggle_merge: + p.r1->set_merge_enabled(!p.r1->merge_enabled()); + break; + case update_record::tag_t::is_set_parent: + undo_eq(p.r1, p.n1, p.r2_num_parents); + break; + case update_record::tag_t::is_add_th_var: + undo_add_th_var(p.r1, p.r2_num_parents); + break; + case update_record::tag_t::is_replace_th_var: + SASSERT(p.r1->get_th_var(p.m_th_id) != null_theory_var); + p.r1->replace_th_var(p.m_old_th_var, p.m_th_id); + break; + case update_record::tag_t::is_new_lit: + m_new_lits.pop_back(); + break; + case update_record::tag_t::is_new_th_eq: + m_new_th_eqs.pop_back(); + break; + case update_record::tag_t::is_new_th_eq_qhead: + m_new_th_eqs_qhead = p.qhead; + break; + case update_record::tag_t::is_new_lits_qhead: + m_new_lits_qhead = p.qhead; + break; + case update_record::tag_t::is_inconsistent: + m_inconsistent = p.m_inconsistent; + break; + default: + UNREACHABLE(); + break; + } + } + + m_updates.shrink(num_updates); m_scopes.shrink(old_lim); m_region.pop_scope(num_scopes); + m_worklist.reset(); } void egraph::merge(enode* n1, enode* n2, justification j) { @@ -245,8 +260,7 @@ namespace euf { std::swap(n1, n2); } if ((m.is_true(r2->get_owner()) || m.is_false(r2->get_owner())) && j.is_congruence()) { - m_new_lits.push_back(enode_bool_pair(n1, false)); - ++m_stats.m_num_lits; + add_literal(n1, false); } for (enode* p : enode_parents(n1)) m_table.erase(p); @@ -268,13 +282,13 @@ namespace euf { for (auto iv : enode_th_vars(n)) { theory_id id = iv.get_id(); theory_var v = root->get_th_var(id); - if (v == null_theory_var) { - root->add_th_var(iv.get_var(), id, m_region); - m_trail.push_back(new (m_region) add_th_var_trail(root, id)); + if (v == null_theory_var) { + root->add_th_var(iv.get_var(), id, m_region); + m_updates.push_back(update_record(root, id, update_record::add_th_var())); } else { SASSERT(v != iv.get_var()); - m_new_th_eqs.push_back(th_eq(id, v, iv.get_var(), n, root)); + add_th_eq(id, v, iv.get_var(), n, root); } } } @@ -308,6 +322,7 @@ namespace euf { if (m_inconsistent) return; m_inconsistent = true; + m_updates.push_back(update_record(false, update_record::inconsistent())); m_n1 = n1; m_n2 = n2; m_justification = j; @@ -387,10 +402,19 @@ namespace euf { } } + void egraph::begin_explain() { + SASSERT(m_todo.empty()); + } + + void egraph::end_explain() { + for (enode* n : m_todo) + n->unmark1(); + m_todo.reset(); + } + template void egraph::explain(ptr_vector& justifications) { SASSERT(m_inconsistent); - SASSERT(m_todo.empty()); push_todo(m_n1); push_todo(m_n2); explain_eq(justifications, m_n1, m_n2, m_justification); @@ -399,7 +423,6 @@ namespace euf { template void egraph::explain_eq(ptr_vector& justifications, enode* a, enode* b) { - SASSERT(m_todo.empty()); SASSERT(a->get_root() == b->get_root()); enode* lca = find_lca(a, b); push_to_lca(a, lca); @@ -418,9 +441,6 @@ namespace euf { explain_eq(justifications, n, n->m_target, n->m_justification); } } - for (enode* n : m_todo) - n->unmark1(); - m_todo.reset(); } void egraph::invariant() { @@ -429,25 +449,29 @@ namespace euf { } std::ostream& egraph::display(std::ostream& out, unsigned max_args, enode* n) const { - out << std::setw(5) - << n->get_owner_id() << " := "; + out << n->get_owner_id() << " := "; if (!n->is_root()) out << "[" << n->get_root()->get_owner_id() << "] "; expr* f = n->get_owner(); if (is_app(f)) - out << to_app(f)->get_decl()->get_name() << " "; + out << mk_bounded_pp(f, m, 1); else if (is_quantifier(f)) - out << "q "; + out << "q:" << f->get_id(); else - out << "v "; - for (enode* arg : enode_args(n)) - out << arg->get_owner_id() << " "; - for (unsigned i = n->num_args(); i < max_args; ++i) - out << " "; - out << "\t"; - for (enode* p : enode_parents(n)) - out << p->get_owner_id() << " "; + out << "v:" << f->get_id(); out << "\n"; + if (!n->m_parents.empty()) { + out << " "; + for (enode* p : enode_parents(n)) + out << p->get_owner_id() << " "; + out << "\n"; + } + if (n->has_th_vars()) { + out << " "; + for (auto v : enode_th_vars(n)) + out << v.get_id() << ":" << v.get_var() << " "; + out << "\n"; + } return out; } @@ -464,8 +488,9 @@ namespace euf { void egraph::collect_statistics(statistics& st) const { st.update("euf merge", m_stats.m_num_merge); st.update("euf conflicts", m_stats.m_num_conflicts); - st.update("euf eq prop", m_stats.m_num_eqs); - st.update("euf lit prop", m_stats.m_num_lits); + st.update("euf equality propagations", m_stats.m_num_eqs); + st.update("euf theory equality propagations", m_stats.m_num_th_eqs); + st.update("euf literal propagations", m_stats.m_num_lits); } void egraph::copy_from(egraph const& src, std::function& copy_justification) { @@ -504,7 +529,7 @@ template void euf::egraph::explain(ptr_vector& justifications); template void euf::egraph::explain_todo(ptr_vector& justifications); template void euf::egraph::explain_eq(ptr_vector& justifications, enode* a, enode* b); -template void euf::egraph::explain(ptr_vector& justifications); -template void euf::egraph::explain_todo(ptr_vector& justifications); -template void euf::egraph::explain_eq(ptr_vector& justifications, enode* a, enode* b); +template void euf::egraph::explain(ptr_vector& justifications); +template void euf::egraph::explain_todo(ptr_vector& justifications); +template void euf::egraph::explain_eq(ptr_vector& justifications, enode* a, enode* b); diff --git a/src/ast/euf/euf_egraph.h b/src/ast/euf/euf_egraph.h index 0fd5c418970..63265605de5 100644 --- a/src/ast/euf/euf_egraph.h +++ b/src/ast/euf/euf_egraph.h @@ -31,14 +31,6 @@ Module Name: namespace euf { - struct add_eq_record { - enode* r1; - enode* n1; - unsigned r2_num_parents; - add_eq_record(enode* r1, enode* n1, unsigned r2_num_parents): - r1(r1), n1(n1), r2_num_parents(r2_num_parents) {} - }; - /*** \brief store derived theory equalities. Theory 'id' is notified with the equality of theory variables v1, v2 @@ -58,31 +50,66 @@ namespace euf { class egraph { typedef ptr_vector > trail_stack; - struct scope { - bool m_inconsistent; - unsigned m_num_eqs; - unsigned m_num_nodes; - unsigned m_trail_sz; - unsigned m_new_lits_sz; - unsigned m_new_th_eqs_sz; - unsigned m_new_lits_qhead; - unsigned m_new_th_eqs_qhead; - }; struct stats { unsigned m_num_merge; + unsigned m_num_th_eqs; unsigned m_num_lits; unsigned m_num_eqs; unsigned m_num_conflicts; stats() { reset(); } void reset() { memset(this, 0, sizeof(*this)); } }; + struct update_record { + struct toggle_merge {}; + struct add_th_var {}; + struct replace_th_var {}; + struct new_lit {}; + struct new_th_eq {}; + struct new_th_eq_qhead {}; + struct new_lits_qhead {}; + struct inconsistent {}; + enum tag_t { is_set_parent, is_add_node, is_toggle_merge, + is_add_th_var, is_replace_th_var, is_new_lit, is_new_th_eq, + is_new_th_eq_qhead, is_new_lits_qhead, is_inconsistent }; + tag_t tag; + enode* r1; + enode* n1; + union { + unsigned r2_num_parents; + struct { + unsigned m_th_id : 8; + unsigned m_old_th_var : 24; + }; + unsigned qhead; + bool m_inconsistent; + }; + update_record(enode* r1, enode* n1, unsigned r2_num_parents) : + tag(tag_t::is_set_parent), r1(r1), n1(n1), r2_num_parents(r2_num_parents) {} + update_record(enode* n) : + tag(tag_t::is_add_node), r1(n), n1(nullptr), r2_num_parents(UINT_MAX) {} + update_record(enode* n, toggle_merge) : + tag(tag_t::is_toggle_merge), r1(n), n1(nullptr), r2_num_parents(UINT_MAX) {} + update_record(enode* n, unsigned id, add_th_var) : + tag(tag_t::is_add_th_var), r1(n), n1(nullptr), r2_num_parents(id) {} + update_record(enode* n, theory_id id, theory_var v, replace_th_var) : + tag(tag_t::is_replace_th_var), r1(n), n1(nullptr), m_th_id(id), m_old_th_var(v) {} + update_record(new_lit) : + tag(tag_t::is_new_lit), r1(nullptr), n1(nullptr), r2_num_parents(0) {} + update_record(new_th_eq) : + tag(tag_t::is_new_th_eq), r1(nullptr), n1(nullptr), r2_num_parents(0) {} + update_record(unsigned qh, new_th_eq_qhead): + tag(tag_t::is_new_th_eq_qhead), r1(nullptr), n1(nullptr), qhead(qh) {} + update_record(unsigned qh, new_lits_qhead): + tag(tag_t::is_new_lits_qhead), r1(nullptr), n1(nullptr), qhead(qh) {} + update_record(bool inc, inconsistent) : + tag(tag_t::is_inconsistent), m_inconsistent(inc) {} + }; ast_manager& m; - trail_stack m_trail; - region m_region; enode_vector m_worklist; etable m_table; - svector m_eqs; - svector m_scopes; + region m_region; + svector m_updates; + unsigned_vector m_scopes; enode_vector m_expr2enode; enode_vector m_nodes; expr_ref_vector m_exprs; @@ -101,9 +128,14 @@ namespace euf { std::function m_used_cc; void push_eq(enode* r1, enode* n1, unsigned r2_num_parents) { - m_eqs.push_back(add_eq_record(r1, n1, r2_num_parents)); + m_updates.push_back(update_record(r1, n1, r2_num_parents)); } + void push_node(enode* n) { m_updates.push_back(update_record(n)); } + + void add_th_eq(theory_id id, theory_var v1, theory_var v2, enode* c, enode* r); + void add_literal(enode* n, bool is_eq); void undo_eq(enode* r1, enode* n1, unsigned r2_num_parents); + void undo_add_th_var(enode* n, theory_id id); enode* mk_enode(expr* f, unsigned num_args, enode * const* args); void reinsert(enode* n); void force_push(); @@ -173,10 +205,13 @@ namespace euf { void add_th_var(enode* n, theory_var v, theory_id id); + void set_merge_enabled(enode* n, bool enable_merge); void set_used_eq(std::function& used_eq) { m_used_eq = used_eq; } void set_used_cc(std::function& used_cc) { m_used_cc = used_cc; } + void begin_explain(); + void end_explain(); template void explain(ptr_vector& justifications); template diff --git a/src/ast/euf/euf_enode.h b/src/ast/euf/euf_enode.h index 0645761196a..41bab679e7d 100644 --- a/src/ast/euf/euf_enode.h +++ b/src/ast/euf/euf_enode.h @@ -36,21 +36,22 @@ namespace euf { const theory_id null_theory_id = -1; class enode { - expr* m_owner; + expr* m_owner{ nullptr }; bool m_mark1 { false }; bool m_mark2 { false }; bool m_commutative { false }; bool m_update_children { false }; bool m_interpreted { false }; + bool m_merge_enabled { true }; unsigned m_class_size { 1 }; unsigned m_table_id { UINT_MAX }; enode_vector m_parents; - enode* m_next; - enode* m_root; + enode* m_next{ nullptr }; + enode* m_root{ nullptr }; enode* m_target { nullptr }; th_var_list m_th_vars; justification m_justification; - unsigned m_num_args; + unsigned m_num_args { 0 }; enode* m_args[0]; friend class enode_args; @@ -73,6 +74,7 @@ namespace euf { n->m_root = n; n->m_commutative = num_args == 2 && is_app(f) && to_app(f)->get_decl()->is_commutative(); n->m_num_args = num_args; + n->m_merge_enabled = true; for (unsigned i = 0; i < num_args; ++i) { SASSERT(to_app(f)->get_arg(i) == args[i]->get_owner()); n->m_args[i] = args[i]; @@ -86,7 +88,8 @@ namespace euf { friend class replace_th_var_trail; void add_th_var(theory_var v, theory_id id, region & r) { m_th_vars.add_var(v, id, r); } void replace_th_var(theory_var v, theory_id id) { m_th_vars.replace(v, id); } - void del_th_var(theory_id id) { m_th_vars.del_var(id); } + void del_th_var(theory_id id) { m_th_vars.del_var(id); } + void set_merge_enabled(bool m) { m_merge_enabled = m; } public: ~enode() { @@ -106,6 +109,7 @@ namespace euf { bool interpreted() const { return m_interpreted; } bool commutative() const { return m_commutative; } void mark_interpreted() { SASSERT(num_args() == 0); m_interpreted = true; } + bool merge_enabled() { return m_merge_enabled; } enode* get_arg(unsigned i) const { SASSERT(i < num_args()); return m_args[i]; } unsigned hash() const { return m_owner->hash(); } @@ -143,6 +147,7 @@ namespace euf { unsigned get_owner_id() const { return m_owner->get_id(); } unsigned get_root_id() const { return m_root->m_owner->get_id(); } theory_var get_th_var(theory_id id) const { return m_th_vars.find(id); } + bool is_attached_to(theory_id id) const { return get_th_var(id) != null_theory_var; } bool has_th_vars() const { return !m_th_vars.empty(); } void inc_class_size(unsigned n) { m_class_size += n; } diff --git a/src/ast/pattern/expr_pattern_match.h b/src/ast/pattern/expr_pattern_match.h index 3fc226e03d1..272610d4e8e 100644 --- a/src/ast/pattern/expr_pattern_match.h +++ b/src/ast/pattern/expr_pattern_match.h @@ -45,14 +45,14 @@ class expr_pattern_match { m_kind(k), m_offset(o), m_next(next), m_app(app), m_count(count) {} instr_kind m_kind; - unsigned m_offset; - unsigned m_next; - app* m_app; - expr* m_pat; - unsigned m_reg; - unsigned m_other_reg; - unsigned m_count; - unsigned m_num_bound; + unsigned m_offset{ 0 }; + unsigned m_next{ 0 }; + app* m_app{ nullptr }; + expr* m_pat{ nullptr }; + unsigned m_reg{ 0 }; + unsigned m_other_reg{ 0 }; + unsigned m_count{ 0 }; + unsigned m_num_bound{ 0 }; }; typedef obj_map subst; diff --git a/src/cmd_context/cmd_context.cpp b/src/cmd_context/cmd_context.cpp index 387e10edd78..f86cc6cb606 100644 --- a/src/cmd_context/cmd_context.cpp +++ b/src/cmd_context/cmd_context.cpp @@ -30,7 +30,6 @@ Module Name: #include "ast/seq_decl_plugin.h" #include "ast/pb_decl_plugin.h" #include "ast/fpa_decl_plugin.h" -#include "ast/csp_decl_plugin.h" #include "ast/special_relations_decl_plugin.h" #include "ast/ast_pp.h" #include "ast/rewriter/var_subst.h" @@ -698,7 +697,6 @@ void cmd_context::init_manager_core(bool new_manager) { register_plugin(symbol("pb"), alloc(pb_decl_plugin), logic_has_pb()); register_plugin(symbol("fpa"), alloc(fpa_decl_plugin), logic_has_fpa()); register_plugin(symbol("datalog_relation"), alloc(datalog::dl_decl_plugin), !has_logic()); - register_plugin(symbol("csp"), alloc(csp_decl_plugin), smt_logics::logic_is_csp(m_logic)); register_plugin(symbol("specrels"), alloc(special_relations_decl_plugin), !has_logic()); } else { @@ -715,7 +713,6 @@ void cmd_context::init_manager_core(bool new_manager) { load_plugin(symbol("seq"), logic_has_seq(), fids); load_plugin(symbol("fpa"), logic_has_fpa(), fids); load_plugin(symbol("pb"), logic_has_pb(), fids); - load_plugin(symbol("csp"), smt_logics::logic_is_csp(m_logic), fids); for (family_id fid : fids) { decl_plugin * p = m_manager->get_plugin(fid); diff --git a/src/sat/dimacs.cpp b/src/sat/dimacs.cpp index ea8789958b7..027186fb706 100644 --- a/src/sat/dimacs.cpp +++ b/src/sat/dimacs.cpp @@ -20,43 +20,21 @@ Revision History: #undef max #undef min #include "sat/sat_solver.h" +#include "sat/sat_drat.h" -namespace { -struct lex_error {}; - -class stream_buffer { - std::istream & m_stream; - int m_val; - unsigned m_line; -public: - - stream_buffer(std::istream & s): - m_stream(s), - m_line(0) { - m_val = m_stream.get(); - } - - int operator *() const { - return m_val; - } - - void operator ++() { - m_val = m_stream.get(); - if (m_val == '\n') ++m_line; - } - - unsigned line() const { return m_line; } -}; +template +static bool is_whitespace(Buffer & in) { + return (*in >= 9 && *in <= 13) || *in == 32; +} template -void skip_whitespace(Buffer & in) { - while ((*in >= 9 && *in <= 13) || *in == 32) { +static void skip_whitespace(Buffer & in) { + while (is_whitespace(in)) ++in; - } } template -void skip_line(Buffer & in) { +static void skip_line(Buffer & in) { while(true) { if (*in == EOF) { return; @@ -70,7 +48,7 @@ void skip_line(Buffer & in) { } template -int parse_int(Buffer & in, std::ostream& err) { +static int parse_int(Buffer & in, std::ostream& err) { int val = 0; bool neg = false; skip_whitespace(in); @@ -84,8 +62,11 @@ int parse_int(Buffer & in, std::ostream& err) { } if (*in < '0' || *in > '9') { - err << "(error, \"unexpected char: " << *in << " line: " << in.line() << "\")\n"; - throw lex_error(); + if (20 <= *in && *in < 128) + err << "(error, \"unexpected char: " << ((char)*in) << " line: " << in.line() << "\")\n"; + else + err << "(error, \"unexpected char: " << *in << " line: " << in.line() << "\")\n"; + throw dimacs::lex_error(); } while (*in >= '0' && *in <= '9') { @@ -97,7 +78,7 @@ int parse_int(Buffer & in, std::ostream& err) { } template -void read_clause(Buffer & in, std::ostream& err, sat::solver & solver, sat::literal_vector & lits) { +static void read_clause(Buffer & in, std::ostream& err, sat::solver & solver, sat::literal_vector & lits) { int parsed_lit; int var; @@ -116,7 +97,24 @@ void read_clause(Buffer & in, std::ostream& err, sat::solver & solver, sat::lite } template -bool parse_dimacs_core(Buffer & in, std::ostream& err, sat::solver & solver) { +static void read_clause(Buffer & in, std::ostream& err, sat::literal_vector & lits) { + int parsed_lit; + int var; + + lits.reset(); + + while (true) { + parsed_lit = parse_int(in, err); + if (parsed_lit == 0) + break; + var = abs(parsed_lit); + SASSERT(var > 0); + lits.push_back(sat::literal(var, parsed_lit < 0)); + } +} + +template +static bool parse_dimacs_core(Buffer & in, std::ostream& err, sat::solver & solver) { sat::literal_vector lits; try { while (true) { @@ -133,15 +131,158 @@ bool parse_dimacs_core(Buffer & in, std::ostream& err, sat::solver & solver) { } } } - catch (lex_error) { + catch (dimacs::lex_error) { return false; } return true; } -} bool parse_dimacs(std::istream & in, std::ostream& err, sat::solver & solver) { - stream_buffer _in(in); + dimacs::stream_buffer _in(in); return parse_dimacs_core(_in, err, solver); } + + +namespace dimacs { + + std::ostream& operator<<(std::ostream& out, drat_record const& r) { + switch (r.m_tag) { + case drat_record::tag_t::is_clause: + return out << r.m_status << " " << r.m_lits << "\n"; + case drat_record::tag_t::is_node: + return out << "e " << r.m_node_id << " " << r.m_name << " " << r.m_args << "\n"; + case drat_record::is_bool_def: + return out << "b " << r.m_node_id << " " << r.m_args << "\n"; + } + return out; + } + + char const* drat_parser::parse_identifier() { + m_buffer.reset(); + while (!is_whitespace(in)) { + m_buffer.push_back(*in); + ++in; + } + m_buffer.push_back(0); + return m_buffer.c_ptr(); + } + + char const* drat_parser::parse_sexpr() { + m_buffer.reset(); + unsigned lp = 0; + while (!is_whitespace(in) || lp > 0) { + m_buffer.push_back(*in); + if (*in == '(') + ++lp; + else if (*in == ')') { + if (lp == 0) { + throw lex_error(); + } + else --lp; + } + ++in; + } + m_buffer.push_back(0); + return m_buffer.c_ptr(); + } + + int drat_parser::read_theory_id() { + skip_whitespace(in); + if ('a' <= *in && *in <= 'z') { + if (!m_read_theory_id) + throw lex_error(); + return m_read_theory_id(parse_identifier()); + } + else { + return -1; + } + } + + bool drat_parser::next() { + int n, b, e, theory_id; + try { + loop: + skip_whitespace(in); + switch (*in) { + case EOF: + return false; + case 'c': + case 'p': + skip_line(in); + goto loop; + case 'i': + ++in; + skip_whitespace(in); + read_clause(in, err, m_record.m_lits); + m_record.m_tag = drat_record::tag_t::is_clause; + m_record.m_status = sat::status::input(); + break; + case 'a': + ++in; + skip_whitespace(in); + theory_id = read_theory_id(); + skip_whitespace(in); + read_clause(in, err, m_record.m_lits); + m_record.m_tag = drat_record::tag_t::is_clause; + m_record.m_status = sat::status::th(false, theory_id); + break; + case 'e': + ++in; + skip_whitespace(in); + n = parse_int(in, err); + skip_whitespace(in); + m_record.m_name = parse_sexpr(); + m_record.m_tag = drat_record::tag_t::is_node; + m_record.m_node_id = n; + m_record.m_args.reset(); + while (true) { + n = parse_int(in, err); + if (n == 0) + break; + if (n < 0) + throw lex_error(); + m_record.m_args.push_back(n); + } + break; + case 'b': + ++in; + skip_whitespace(in); + b = parse_int(in, err); + n = parse_int(in, err); + e = parse_int(in, err); + if (e != 0) + throw lex_error(); + m_record.m_tag = drat_record::tag_t::is_bool_def; + m_record.m_node_id = b; + m_record.m_args.reset(); + m_record.m_args.push_back(n); + break; + case 'd': + ++in; + skip_whitespace(in); + read_clause(in, err, m_record.m_lits); + m_record.m_tag = drat_record::tag_t::is_clause; + m_record.m_status = sat::status::deleted(); + break; + case 'r': + ++in; + skip_whitespace(in); + theory_id = read_theory_id(); + read_clause(in, err, m_record.m_lits); + m_record.m_tag = drat_record::tag_t::is_clause; + m_record.m_status = sat::status::th(true, theory_id); + break; + default: + read_clause(in, err, m_record.m_lits); + m_record.m_tag = drat_record::tag_t::is_clause; + m_record.m_status = sat::status::redundant(); + break; + } + return true; + } + catch (lex_error) { + return false; + } + } +} diff --git a/src/sat/dimacs.h b/src/sat/dimacs.h index 06629d26d78..eed385556ee 100644 --- a/src/sat/dimacs.h +++ b/src/sat/dimacs.h @@ -15,6 +15,9 @@ Module Name: Revision History: + Nikolaj Bjorner (nbjorner) 2020-09-07 + Add parser to consume extended DRAT format. + --*/ #pragma once @@ -22,4 +25,87 @@ Revision History: bool parse_dimacs(std::istream & s, std::ostream& err, sat::solver & solver); +namespace dimacs { + struct lex_error {}; + + class stream_buffer { + std::istream & m_stream; + int m_val; + unsigned m_line; + public: + + stream_buffer(std::istream & s): + m_stream(s), + m_line(0) { + m_val = m_stream.get(); + } + + int operator *() const { + return m_val; + } + + void operator ++() { + m_val = m_stream.get(); + if (m_val == '\n') ++m_line; + } + + unsigned line() const { return m_line; } + }; + + struct drat_record { + enum tag_t { is_clause, is_node, is_bool_def }; + tag_t m_tag; + // a clause populates m_lits and m_status + // a node populates m_node_id, m_name, m_args + // a bool def populates m_node_id and one element in m_args + sat::literal_vector m_lits; + sat::status m_status; + unsigned m_node_id; + std::string m_name; + unsigned_vector m_args; + drat_record(): + m_tag(is_clause), + m_status(sat::status::redundant()) + {} + }; + + std::ostream& operator<<(std::ostream& out, drat_record const& r); + + class drat_parser { + dimacs::stream_buffer in; + std::ostream& err; + drat_record m_record; + std::function m_read_theory_id; + svector m_buffer; + + char const* parse_sexpr(); + char const* parse_identifier(); + int read_theory_id(); + bool next(); + + public: + drat_parser(std::istream & _in, std::ostream& err): + in(_in), err(err) + {} + + class iterator { + drat_parser& p; + bool m_eof; + public: + iterator(drat_parser& p, bool is_eof):p(p), m_eof(is_eof) { if (!m_eof) m_eof = !p.next(); } + drat_record const& operator*() { return p.m_record; } + iterator& operator++() { if (!p.next()) m_eof = true; return *this;} + bool operator==(iterator const& other) const { return m_eof == other.m_eof; } + bool operator!=(iterator const& other) const { return m_eof != other.m_eof; } + }; + + iterator begin() { return iterator(*this, false); }; + iterator end() { return iterator(*this, true); } + + void set_read_theory(std::function& r) { m_read_theory_id = r; } + + }; +}; + + diff --git a/src/sat/sat_aig_cuts.h b/src/sat/sat_aig_cuts.h index 9ae00f7d077..9e60ce9ecde 100644 --- a/src/sat/sat_aig_cuts.h +++ b/src/sat/sat_aig_cuts.h @@ -69,11 +69,11 @@ namespace sat { // encodes one of var, and, !and, xor, !xor, ite, !ite. class node { - bool m_sign; - bool_op m_op; - uint64_t m_lut; - unsigned m_size; - unsigned m_offset; + bool m_sign{ false }; + bool_op m_op{ no_op }; + uint64_t m_lut{ 0 }; + unsigned m_size{ 0 }; + unsigned m_offset{ 0 }; public: node(): m_sign(false), m_op(no_op), m_size(UINT_MAX), m_offset(UINT_MAX) {} diff --git a/src/sat/sat_binspr.h b/src/sat/sat_binspr.h index 8dab8305eec..5b4f9a0d091 100644 --- a/src/sat/sat_binspr.h +++ b/src/sat/sat_binspr.h @@ -38,13 +38,13 @@ namespace sat { solver& m_solver; scoped_ptr s; - unsigned m_bin_clauses; - unsigned m_stopped_at; + unsigned m_bin_clauses{ 0 }; + unsigned m_stopped_at{ 0 }; vector m_use_list; - unsigned m_limit1, m_limit2; + unsigned m_limit1{ 0 }, m_limit2{ 0 }; bool_vector m_mark, m_mark2; literal_vector m_must_candidates, m_may_candidates; - unsigned m_state; + unsigned m_state{ 0 }; void init_g() { m_state = 0x7; } void g_add_binary(literal l1, literal l2, bool flip2); diff --git a/src/sat/sat_drat.cpp b/src/sat/sat_drat.cpp index b1de72147aa..b924f378ab5 100644 --- a/src/sat/sat_drat.cpp +++ b/src/sat/sat_drat.cpp @@ -6,10 +6,10 @@ Module Name: sat_drat.cpp Abstract: - + Produce DRAT proofs. - Check them using a very simple forward checker + Check them using a very simple forward checker that interacts with external plugins. Author: @@ -24,19 +24,18 @@ Module Name: namespace sat { - drat::drat(solver& s): + drat::drat(solver& s) : s(s), m_out(nullptr), m_bout(nullptr), m_inconsistent(false), - m_num_add(0), - m_num_del(0), m_check_unsat(false), m_check_sat(false), m_check(false), m_activity(false) { - if (s.get_config().m_drat && s.get_config().m_drat_file != symbol()) { + if (s.get_config().m_drat && s.get_config().m_drat_file.is_non_empty_string()) { + std::cout << "DRAT " << s.get_config().m_drat_file << "\n"; auto mode = s.get_config().m_drat_binary ? (std::ios_base::binary | std::ios_base::out | std::ios_base::trunc) : std::ios_base::out; m_out = alloc(std::ofstream, s.get_config().m_drat_file.str(), mode); if (s.get_config().m_drat_binary) { @@ -61,7 +60,7 @@ namespace sat { m_bout = nullptr; } - void drat::updt_config() { + void drat::updt_config() { m_check_unsat = s.get_config().m_drat_check_unsat; m_check_sat = s.get_config().m_drat_check_sat; m_check = m_check_unsat || m_check_sat; @@ -75,31 +74,41 @@ namespace sat { out << "d"; else if (st.is_asserted()) out << "a"; - + else if (st.is_input()) + out << "i"; + if (!st.is_sat()) out << " " << m_theory[st.get_th()]; - return out; + return out; } void drat::dump(unsigned n, literal const* c, status st) { if (st.is_asserted() && !s.m_ext) return; - if (m_activity && ((m_num_add % 1000) == 0)) + if (m_activity && ((m_stats.m_num_add % 1000) == 0)) dump_activity(); - + char buffer[10000]; char digits[20]; // enough for storing unsigned char* lastd = digits + sizeof(digits); - + unsigned len = 0; if (st.is_asserted()) { buffer[len++] = 'a'; - buffer[len++] = ' '; + buffer[len++] = ' '; } else if (st.is_deleted()) { buffer[len++] = 'd'; buffer[len++] = ' '; } + else if (st.is_input()) { + buffer[len++] = 'i'; + buffer[len++] = ' '; + } + else if (st.is_redundant() && !st.is_sat()) { + buffer[len++] = 'r'; + buffer[len++] = ' '; + } if (!st.is_sat()) { for (char ch : m_theory[st.get_th()]) @@ -108,28 +117,28 @@ namespace sat { } for (unsigned i = 0; i < n; ++i) { literal lit = c[i]; - unsigned v = lit.var(); + unsigned v = lit.var(); if (lit.sign()) buffer[len++] = '-'; char* d = lastd; SASSERT(v > 0); - while (v > 0) { + while (v > 0) { d--; *d = (v % 10) + '0'; v /= 10; SASSERT(d > digits); } - SASSERT(len + lastd < sizeof(buffer) + d); - memcpy(buffer + len, d, lastd - d); - len += static_cast(lastd - d); - buffer[len++] = ' '; - if (len + 50 > sizeof(buffer)) { - m_out->write(buffer, len); - len = 0; + SASSERT(len + lastd < sizeof(buffer) + d); + memcpy(buffer + len, d, lastd - d); + len += static_cast(lastd - d); + buffer[len++] = ' '; + if (len + 50 > sizeof(buffer)) { + m_out->write(buffer, len); + len = 0; } - } - buffer[len++] = '0'; - buffer[len++] = '\n'; - m_out->write(buffer, len); + } + buffer[len++] = '0'; + buffer[len++] = '\n'; + m_out->write(buffer, len); } @@ -144,9 +153,9 @@ namespace sat { void drat::bdump(unsigned n, literal const* c, status st) { unsigned char ch = 0; if (st.is_redundant()) - ch = 'a'; + ch = 'a'; else if (st.is_deleted()) - ch = 'd'; + ch = 'd'; else return; char buffer[10000]; int len = 0; @@ -164,8 +173,7 @@ namespace sat { m_bout->write(buffer, len); len = 0; } - } - while (v); + } while (v); } buffer[len++] = 0; m_bout->write(buffer, len); @@ -188,7 +196,7 @@ namespace sat { if (c[i] != last) { out << c[i] << " "; last = c[i]; - } + } } out << "\n"; } @@ -198,7 +206,7 @@ namespace sat { declare(l); IF_VERBOSE(20, trace(verbose_stream(), 1, &l, st);); - if (st.is_redundant()) { + if (st.is_redundant() && st.is_sat()) { verify(1, &l); } if (st.is_deleted()) { @@ -213,17 +221,17 @@ namespace sat { void drat::append(literal l1, literal l2, status st) { TRACE("sat_drat", pp(tout, st) << " " << l1 << " " << l2 << "\n";); - declare(l1); + declare(l1); declare(l2); literal lits[2] = { l1, l2 }; - + IF_VERBOSE(20, trace(verbose_stream(), 2, lits, st);); if (st.is_deleted()) { // noop // don't record binary as deleted. } else { - if (st.is_redundant()) { + if (st.is_redundant() && st.is_sat()) { verify(2, lits); } clause* c = m_alloc.mk_clause(2, lits, st.is_redundant()); @@ -252,18 +260,18 @@ namespace sat { (*m_out) << "b " << v << " " << n << " 0\n"; } - void drat::def_begin(unsigned n, symbol const& name) { - if (m_out) - (*m_out) << "n " << n << " " << name; + void drat::def_begin(unsigned n, std::string const& name) { + if (m_out) + (*m_out) << "e " << n << " " << name; } void drat::def_add_arg(unsigned arg) { - if (m_out) + if (m_out) (*m_out) << " " << arg; } void drat::def_end() { - if (m_out) + if (m_out) (*m_out) << " 0\n"; } @@ -300,12 +308,12 @@ namespace sat { unsigned n = c.size(); IF_VERBOSE(20, trace(verbose_stream(), n, c.begin(), st);); - if (st.is_redundant()) { + if (st.is_redundant() && st.is_sat()) { verify(c); } - + m_status.push_back(st); - m_proof.push_back(&c); + m_proof.push_back(&c); if (st.is_deleted()) { if (n > 0) del_watch(c, c[0]); if (n > 1) del_watch(c, c[1]); @@ -327,11 +335,11 @@ namespace sat { } } switch (num_watch) { - case 0: - m_inconsistent = true; + case 0: + m_inconsistent = true; break; - case 1: - assign_propagate(l1); + case 1: + assign_propagate(l1); break; default: { SASSERT(num_watch == 2); @@ -345,7 +353,7 @@ namespace sat { } void drat::del_watch(clause& c, literal l) { - watch& w = m_watches[(~l).index()]; + watch& w = m_watches[(~l).index()]; for (unsigned i = 0; i < w.size(); ++i) { if (m_watched_clauses[w[i]].m_clause == &c) { w[i] = w.back(); @@ -368,7 +376,7 @@ namespace sat { bool drat::is_drup(unsigned n, literal const* c) { if (m_inconsistent || n == 0) return true; unsigned num_units = m_units.size(); - for (unsigned i = 0; !m_inconsistent && i < n; ++i) { + for (unsigned i = 0; !m_inconsistent && i < n; ++i) { assign_propagate(~c[i]); } if (!m_inconsistent) { @@ -417,7 +425,7 @@ namespace sat { } svector bin; s.collect_bin_clauses(bin, true); - for (auto & b : bin) { + for (auto& b : bin) { bool found = false; if (m_assignment[b.first.var()] != (b.first.sign() ? l_true : l_false)) found = true; if (m_assignment[b.second.var()] != (b.second.sign() ? l_true : l_false)) found = true; @@ -435,17 +443,16 @@ namespace sat { } m_units.shrink(num_units); bool ok = m_inconsistent; - // IF_VERBOSE(9, verbose_stream() << "is-drup " << m_inconsistent << "\n"); m_inconsistent = false; - return ok; } bool drat::is_drat(unsigned n, literal const* c) { - if (m_inconsistent || n == 0) return true; - for (unsigned i = 0; i < n; ++i) { - if (is_drat(n, c, i)) return true; - } + if (m_inconsistent || n == 0) + return true; + for (unsigned i = 0; i < n; ++i) + if (is_drat(n, c, i)) + return true; return false; } @@ -482,7 +489,7 @@ namespace sat { if (st.is_sat() && j != c.size()) { lits.append(j, c.begin()); lits.append(c.size() - j - 1, c.begin() + j + 1); - if (!is_drup(lits.size(), lits.c_ptr())) + if (!is_drup(lits.size(), lits.c_ptr())) return false; lits.resize(n); } @@ -496,26 +503,33 @@ namespace sat { if (!m_check_unsat) { return; } - for (unsigned i = 0; i < n; ++i) { + for (unsigned i = 0; i < n; ++i) { declare(c[i]); - } - if (!is_drup(n, c) && !is_drat(n, c)) { - literal_vector lits(n, c); - IF_VERBOSE(0, verbose_stream() << "Verification of " << lits << " failed\n"); - // s.display(std::cout); - std::string line; - std::getline(std::cin, line); - SASSERT(false); - INVOKE_DEBUGGER(); - exit(0); - UNREACHABLE(); - //display(std::cout); - TRACE("sat_drat", - tout << literal_vector(n, c) << "\n"; - display(tout); - s.display(tout);); - UNREACHABLE(); } + if (is_drup(n, c)) { + ++m_stats.m_num_drup; + return; + } + if (is_drat(n, c)) { + ++m_stats.m_num_drat; + return; + } + + literal_vector lits(n, c); + IF_VERBOSE(0, verbose_stream() << "Verification of " << lits << " failed\n"); + // s.display(std::cout); + std::string line; + std::getline(std::cin, line); + SASSERT(false); + INVOKE_DEBUGGER(); + exit(0); + UNREACHABLE(); + //display(std::cout); + TRACE("sat_drat", + tout << literal_vector(n, c) << "\n"; + display(tout); + s.display(tout);); + UNREACHABLE(); } bool drat::contains(literal c, justification const& j) { @@ -529,7 +543,7 @@ namespace sat { return contains(c, j.get_literal()); case justification::TERNARY: return contains(c, j.get_literal1(), j.get_literal2()); - case justification::CLAUSE: + case justification::CLAUSE: return contains(s.get_clause(j)); default: return true; @@ -546,7 +560,7 @@ namespace sat { if (match(n, lits, c)) { if (st.is_deleted()) { num_del++; - } + } else { num_add++; } @@ -578,7 +592,7 @@ namespace sat { void drat::display(std::ostream& out) const { out << "units: " << m_units << "\n"; for (unsigned i = 0; i < m_assignment.size(); ++i) { - lbool v = value(literal(i, false)); + lbool v = value(literal(i, false)); if (v != l_undef) out << i << ": " << v << "\n"; } for (unsigned i = 0; i < m_proof.size(); ++i) { @@ -599,12 +613,12 @@ namespace sat { if (num_true == 0 && num_undef == 1) { out << "Unit "; } - pp(out, m_status[i]) << " " << i << ": " << *c << "\n"; + pp(out, m_status[i]) << " " << i << ": " << *c << "\n"; } } for (unsigned i = 0; i < m_assignment.size(); ++i) { - watch const& w1 = m_watches[2*i]; - watch const& w2 = m_watches[2*i + 1]; + watch const& w1 = m_watches[2 * i]; + watch const& w2 = m_watches[2 * i + 1]; if (!w1.empty()) { out << i << " |-> "; for (unsigned i = 0; i < w1.size(); ++i) out << *(m_watched_clauses[w1[i]].m_clause) << " "; @@ -626,7 +640,7 @@ namespace sat { void drat::assign(literal l) { lbool new_value = l.sign() ? l_false : l_true; lbool old_value = value(l); -// TRACE("sat_drat", tout << "assign " << l << " := " << new_value << " from " << old_value << "\n";); + // TRACE("sat_drat", tout << "assign " << l << " := " << new_value << " from " << old_value << "\n";); switch (old_value) { case l_false: m_inconsistent = true; @@ -645,7 +659,7 @@ namespace sat { assign(l); for (unsigned i = num_units; !m_inconsistent && i < m_units.size(); ++i) { propagate(m_units[i]); - } + } } void drat::propagate(literal l) { @@ -676,10 +690,10 @@ namespace sat { wc.m_l2 = lit; m_watches[(~lit).index()].push_back(idx); done = true; - } + } } if (done) { - continue; + continue; } else if (value(wc.m_l1) == l_false) { m_inconsistent = true; @@ -693,19 +707,19 @@ namespace sat { } } end_process_watch: - for (; it != end; ++it, ++it2) - *it2 = *it; - clauses.set_end(it2); + for (; it != end; ++it, ++it2) + *it2 = *it; + clauses.set_end(it2); } status drat::get_status(bool learned) const { if (learned || s.m_searching) return status::redundant(); - return status::asserted(); + return status::asserted(); } void drat::add() { - ++m_num_add; + ++m_stats.m_num_add; if (m_out) (*m_out) << "0\n"; if (m_bout) bdump(0, nullptr, status::redundant()); if (m_check_unsat) { @@ -713,7 +727,7 @@ namespace sat { } } void drat::add(literal l, bool learned) { - ++m_num_add; + ++m_stats.m_num_add; status st = get_status(learned); if (m_out) dump(1, &l, st); if (m_bout) bdump(1, &l, st); @@ -721,19 +735,19 @@ namespace sat { } void drat::add(literal l1, literal l2, status st) { if (st.is_deleted()) - ++m_num_del; + ++m_stats.m_num_del; else - ++m_num_add; - literal ls[2] = {l1, l2}; + ++m_stats.m_num_add; + literal ls[2] = { l1, l2 }; if (m_out) dump(2, ls, st); if (m_bout) bdump(2, ls, st); if (m_check) append(l1, l2, st); } void drat::add(clause& c, status st) { if (st.is_deleted()) - ++m_num_del; + ++m_stats.m_num_del; else - ++m_num_add; + ++m_stats.m_num_add; if (m_out) dump(c.size(), c.begin(), st); if (m_bout) bdump(c.size(), c.begin(), st); if (m_check) { @@ -743,9 +757,9 @@ namespace sat { } void drat::add(literal_vector const& lits, status st) { if (st.is_deleted()) - ++m_num_del; - else - ++m_num_add; + ++m_stats.m_num_del; + else + ++m_stats.m_num_add; if (m_check) { switch (lits.size()) { case 0: add(); break; @@ -756,12 +770,12 @@ namespace sat { break; } } - } - if (m_out) + } + if (m_out) dump(lits.size(), lits.c_ptr(), st); } void drat::add(literal_vector const& c) { - ++m_num_add; + ++m_stats.m_num_add; if (m_out) dump(c.size(), c.begin(), status::redundant()); if (m_bout) bdump(c.size(), c.begin(), status::redundant()); if (m_check) { @@ -780,15 +794,15 @@ namespace sat { } void drat::del(literal l) { - ++m_num_del; + ++m_stats.m_num_del; if (m_out) dump(1, &l, status::deleted()); if (m_bout) bdump(1, &l, status::deleted()); if (m_check_unsat) append(l, status::deleted()); } void drat::del(literal l1, literal l2) { - ++m_num_del; - literal ls[2] = {l1, l2}; + ++m_stats.m_num_del; + literal ls[2] = { l1, l2 }; if (m_out) dump(2, ls, status::deleted()); if (m_bout) bdump(2, ls, status::deleted()); if (m_check) append(l1, l2, status::deleted()); @@ -806,26 +820,47 @@ namespace sat { m_seen[lit.index()] = false; } #endif - ++m_num_del; + ++m_stats.m_num_del; if (m_out) dump(c.size(), c.begin(), status::deleted()); if (m_bout) bdump(c.size(), c.begin(), status::deleted()); if (m_check) { - clause* c1 = m_alloc.mk_clause(c.size(), c.begin(), c.is_learned()); + clause* c1 = m_alloc.mk_clause(c.size(), c.begin(), c.is_learned()); append(*c1, status::deleted()); } } void drat::del(literal_vector const& c) { - ++m_num_del; + ++m_stats.m_num_del; if (m_out) dump(c.size(), c.begin(), status::deleted()); if (m_bout) bdump(c.size(), c.begin(), status::deleted()); if (m_check) { - clause* c1 = m_alloc.mk_clause(c.size(), c.begin(), true); + clause* c1 = m_alloc.mk_clause(c.size(), c.begin(), true); append(*c1, status::deleted()); } } - - void drat::check_model(model const& m) { + + void drat::check_model(model const& m) { + } + + std::ostream& operator<<(std::ostream& out, status const& st) { + if (st.is_deleted()) + out << "d"; + else if (st.is_input()) + out << "i"; + else if (st.is_asserted()) + out << "a"; + else if (st.is_redundant() && !st.is_sat()) + out << "r"; + if (!st.is_sat()) + out << " th" << st.m_orig; + return out; + } + + void drat::collect_statistics(statistics& st) const { + st.update("num-drup", m_stats.m_num_drup); + st.update("num-drat", m_stats.m_num_drat); + st.update("num-add", m_stats.m_num_add); + st.update("num-del", m_stats.m_num_del); } } diff --git a/src/sat/sat_drat.h b/src/sat/sat_drat.h index f41d5b103d8..57ffab47221 100644 --- a/src/sat/sat_drat.h +++ b/src/sat/sat_drat.h @@ -19,18 +19,24 @@ Module Name: For SMT extensions are as follows: - Assertion (trusted modulo internalizer): + Input assertion: + i * 0 + + Assertion (true modulo a theory): a [] * 0 - The optional theory id indicates the assertion is irredundant + The if no theory id is given, the assertion is a tautology + modulo Tseitin converison. Theory ids track whether the + tautology is modulo a theory. + Assertions are irredundant. Bridge from ast-node to boolean variable: b 0 - Definition of an ast node: - n * 0 + Definition of an expression (ast-node): + e * 0 - Theory lemma - * 0 + Redundant clause (theory lemma if theory id is given) + [r []] * 0 Available theories are: - euf The theory lemma should be a consequence of congruence closure. @@ -45,8 +51,16 @@ Module Name: #include "sat_types.h" namespace sat { + class justification; + class clause; + class drat { - private: + struct stats { + unsigned m_num_drup { 0 }; + unsigned m_num_drat { 0 }; + unsigned m_num_add { 0 }; + unsigned m_num_del { 0 }; + }; struct watched_clause { clause* m_clause; literal m_l1, m_l2; @@ -64,10 +78,10 @@ namespace sat { literal_vector m_units; vector m_watches; svector m_assignment; - svector m_theory; + vector m_theory; bool m_inconsistent; - unsigned m_num_add, m_num_del; bool m_check_unsat, m_check_sat, m_check, m_activity; + stats m_stats; void dump_activity(); void dump(unsigned n, literal const* c, status st); @@ -96,6 +110,7 @@ namespace sat { bool match(unsigned n, literal const* lits, clause const& c) const; public: + drat(solver& s); ~drat(); @@ -114,7 +129,7 @@ namespace sat { void bool_def(bool_var v, unsigned n); // declare AST node n with 'name' and arguments arg - void def_begin(unsigned n, symbol const& name); + void def_begin(unsigned n, std::string const& name); void def_add_arg(unsigned arg); void def_end(); @@ -139,7 +154,11 @@ namespace sat { bool contains(literal c, justification const& j); void check_model(model const& m); + + void collect_statistics(statistics& st) const; }; + std::ostream& operator<<(std::ostream& out, status const& st); + }; diff --git a/src/sat/sat_elim_eqs.cpp b/src/sat/sat_elim_eqs.cpp index baca88229d4..94aeea996b3 100644 --- a/src/sat/sat_elim_eqs.cpp +++ b/src/sat/sat_elim_eqs.cpp @@ -236,7 +236,6 @@ namespace sat { if (m_solver.is_assumption(v) || (m_solver.is_external(v) && (m_solver.is_incremental() || !root_ok))) { // cannot really eliminate v, since we have to notify extension of future assignments if (m_solver.m_config.m_drat && m_solver.m_config.m_drat_file.is_null()) { - std::cout << "DRAT\n"; m_solver.m_drat.add(~l, r, sat::status::redundant()); m_solver.m_drat.add(l, ~r, sat::status::redundant()); } diff --git a/src/sat/sat_extension.h b/src/sat/sat_extension.h index dac8ec8d159..e248ddbef2d 100644 --- a/src/sat/sat_extension.h +++ b/src/sat/sat_extension.h @@ -61,9 +61,10 @@ namespace sat { virtual void set_lookahead(lookahead* s) = 0; virtual void init_search() {} virtual bool propagate(literal l, ext_constraint_idx idx) = 0; + virtual bool unit_propagate() = 0; virtual bool is_external(bool_var v) = 0; virtual double get_reward(literal l, ext_constraint_idx idx, literal_occs_fun& occs) const = 0; - virtual void get_antecedents(literal l, ext_justification_idx idx, literal_vector & r) = 0; + virtual void get_antecedents(literal l, ext_justification_idx idx, literal_vector & r, bool probing) = 0; virtual bool is_extended_binary(ext_justification_idx idx, literal_vector & r) = 0; virtual void asserted(literal l) = 0; virtual check_result check() = 0; diff --git a/src/sat/sat_simplifier.cpp b/src/sat/sat_simplifier.cpp index 09dff101cf1..f3c09ffb7f8 100644 --- a/src/sat/sat_simplifier.cpp +++ b/src/sat/sat_simplifier.cpp @@ -1900,7 +1900,6 @@ namespace sat { } bool simplifier::try_eliminate(bool_var v) { - TRACE("sat_simplifier", tout << "processing: " << v << "\n";); if (value(v) != l_undef) return false; @@ -1963,7 +1962,7 @@ namespace sat { } } } - TRACE("sat_simplifier", tout << "found var to eliminate, before: " << before_clauses << " after: " << after_clauses << "\n";); + TRACE("sat_simplifier", tout << "eliminate " << v << ", before: " << before_clauses << " after: " << after_clauses << "\n";); m_elim_counter -= num_pos * num_neg + before_lits; m_elim_counter -= num_pos * num_neg + before_lits; diff --git a/src/sat/sat_solver.cpp b/src/sat/sat_solver.cpp index 3474fb2b707..d331b787ad1 100644 --- a/src/sat/sat_solver.cpp +++ b/src/sat/sat_solver.cpp @@ -86,7 +86,7 @@ namespace sat { m_cuber = nullptr; m_local_search = nullptr; m_mc.set_solver(this); - //mk_var(false, false); + mk_var(false, false); } solver::~solver() { @@ -138,7 +138,7 @@ namespace sat { m_qhead = 0; m_trail.reset(); m_scopes.reset(); - //mk_var(false, false); + mk_var(false, false); if (src.inconsistent()) { set_conflict(); @@ -353,7 +353,7 @@ namespace sat { clause * solver::mk_clause_core(unsigned num_lits, literal * lits, sat::status st) { bool redundant = st.is_redundant(); TRACE("sat", tout << "mk_clause: " << mk_lits_pp(num_lits, lits) << (redundant?" learned":" aux") << "\n";); - if (!redundant) { + if (!redundant || !st.is_sat()) { unsigned old_sz = num_lits; bool keep = simplify_clause(num_lits, lits); TRACE("sat_mk_clause", tout << "mk_clause (after simp), keep: " << keep << "\n" << mk_lits_pp(num_lits, lits) << "\n";); @@ -1103,6 +1103,11 @@ namespace sat { } } wlist.set_end(it2); + if (m_ext) { + m_ext->unit_propagate(); + if (inconsistent()) + return false; + } } SASSERT(m_qhead == m_trail.size()); SASSERT(!m_inconsistent); @@ -1205,9 +1210,9 @@ namespace sat { } try { init_search(); - if (inconsistent()) return l_false; + if (check_inconsistent()) return l_false; propagate(false); - if (inconsistent()) return l_false; + if (check_inconsistent()) return l_false; init_assumptions(num_lits, lits); propagate(false); if (check_inconsistent()) return l_false; @@ -1644,7 +1649,9 @@ namespace sat { bool solver::check_inconsistent() { if (inconsistent()) { - if (tracking_assumptions()) + if (tracking_assumptions() && at_search_lvl()) + resolve_conflict(); + else if (m_config.m_drat && at_base_lvl()) resolve_conflict(); return true; } @@ -2576,13 +2583,6 @@ namespace sat { if (m_step_size > m_config.m_step_size_min) { m_step_size -= m_config.m_step_size_dec; } - struct reset_cache { - solver& s; - bool active; - reset_cache(solver& s) :s(s), active(true) {} - ~reset_cache() { if (active) s.m_cached_antecedent_js = 0; } - }; - reset_cache _reset(*this); bool unique_max; m_conflict_lvl = get_max_lvl(m_not_l, m_conflict, unique_max); justification js = m_conflict; @@ -2609,7 +2609,6 @@ namespace sat { pop_reinit(m_scope_lvl - m_conflict_lvl + 1); m_force_conflict_analysis = true; ++m_stats.m_backtracks; - _reset.active = false; return l_undef; } m_force_conflict_analysis = false; @@ -2685,7 +2684,7 @@ namespace sat { break; } case justification::EXT_JUSTIFICATION: { - fill_ext_antecedents(consequent, js); + fill_ext_antecedents(consequent, js, false); for (literal l : m_ext_antecedents) process_antecedent(l, num_marks); break; @@ -2786,6 +2785,7 @@ namespace sat { if (m_par && lemma) { m_par->share_clause(*this, *lemma); } + m_lemma.reset(); TRACE("sat_conflict_detail", tout << "consistent " << (!m_inconsistent) << " scopes: " << scope_lvl() << " backtrack: " << backtrack_lvl << " backjump: " << backjump_lvl << "\n";); decay_activity(); updt_phase_counters(); @@ -2847,7 +2847,7 @@ namespace sat { break; } case justification::EXT_JUSTIFICATION: { - fill_ext_antecedents(consequent, js); + fill_ext_antecedents(consequent, js, true); for (literal l : m_ext_antecedents) { process_antecedent_for_unsat_core(l); } @@ -2975,7 +2975,7 @@ namespace sat { case justification::EXT_JUSTIFICATION: if (not_l != null_literal) not_l.neg(); - fill_ext_antecedents(not_l, js); + fill_ext_antecedents(not_l, js, true); for (literal l : m_ext_antecedents) level = update_max_level(l, level, unique_max); return level; @@ -3031,16 +3031,12 @@ namespace sat { /** \brief js is an external justification. Collect its antecedents and store at m_ext_antecedents. */ - void solver::fill_ext_antecedents(literal consequent, justification js) { + void solver::fill_ext_antecedents(literal consequent, justification js, bool probing) { SASSERT(js.is_ext_justification()); SASSERT(m_ext); auto idx = js.get_ext_justification_idx(); - if (consequent == m_cached_antecedent_consequent && idx == m_cached_antecedent_js) - return; m_ext_antecedents.reset(); - m_ext->get_antecedents(consequent, idx, m_ext_antecedents); - m_cached_antecedent_consequent = consequent; - m_cached_antecedent_js = idx; + m_ext->get_antecedents(consequent, idx, m_ext_antecedents, probing); } bool solver::is_two_phase() const { @@ -3354,7 +3350,7 @@ namespace sat { } case justification::EXT_JUSTIFICATION: { literal consequent(var, value(var) == l_false); - fill_ext_antecedents(consequent, js); + fill_ext_antecedents(consequent, js, false); for (literal l : m_ext_antecedents) { if (!process_antecedent_for_minimization(l)) { reset_unmark(old_size); @@ -3496,7 +3492,7 @@ namespace sat { break; } case justification::EXT_JUSTIFICATION: { - fill_ext_antecedents(~m_lemma[i], js); + fill_ext_antecedents(~m_lemma[i], js, true); for (literal l : m_ext_antecedents) { update_lrb_reasoned(l); } @@ -3657,6 +3653,7 @@ namespace sat { s.m_trail_lim = m_trail.size(); s.m_clauses_to_reinit_lim = m_clauses_to_reinit.size(); s.m_inconsistent = m_inconsistent; + // m_vars_lim.push(num_vars()); if (m_ext) m_ext->push(); } @@ -3668,11 +3665,46 @@ namespace sat { m_stats.m_units = init_trail_size(); } + void solver::pop_vars(unsigned num_scopes) { + unsigned old_num_vars = m_vars_lim.pop(num_scopes); + if (old_num_vars == num_vars()) + return; + IF_VERBOSE(0, verbose_stream() << "new variables created under scope\n";); + for (unsigned v = old_num_vars; v < num_vars(); ++v) { + + } + } + + void solver::shrink_vars(unsigned v) { + for (bool_var i = v; i < m_justification.size(); ++i) { + m_case_split_queue.del_var_eh(i); + m_probing.reset_cache(literal(i, true)); + m_probing.reset_cache(literal(i, false)); + } + m_watches.shrink(2*v); + m_assignment.shrink(2*v); + m_justification.shrink(v); + m_decision.shrink(v); + m_eliminated.shrink(v); + m_external.shrink(v); + m_touched.shrink(v); + m_activity.shrink(v); + m_mark.shrink(v); + m_lit_mark.shrink(2*v); + m_phase.shrink(v); + m_best_phase.shrink(v); + m_prev_phase.shrink(v); + m_assigned_since_gc.shrink(v); + m_simplifier.reset_todos(); + } + void solver::pop(unsigned num_scopes) { if (num_scopes == 0) return; - if (m_ext) + if (m_ext) { + // pop_vars(num_scopes); m_ext->pop(num_scopes); + } SASSERT(num_scopes <= scope_lvl()); unsigned new_lvl = scope_lvl() - num_scopes; scope & s = m_scopes[new_lvl]; @@ -3709,12 +3741,27 @@ namespace sat { m_qhead = m_trail.size(); if (!m_replay_assign.empty()) IF_VERBOSE(20, verbose_stream() << "replay assign: " << m_replay_assign.size() << "\n"); for (unsigned i = m_replay_assign.size(); i-- > 0; ) { - m_trail.push_back(m_replay_assign[i]); + literal lit = m_replay_assign[i]; + if (m_ext && m_external[lit.var()]) + m_ext->asserted(lit); + m_trail.push_back(lit); } m_replay_assign.reset(); } + void solver::get_reinit_literals(unsigned n, literal_vector& r) { + unsigned new_lvl = scope_lvl() - n; + unsigned old_sz = m_scopes[new_lvl].m_clauses_to_reinit_lim; + for (unsigned i = m_clauses_to_reinit.size(); i-- > old_sz; ) { + clause_wrapper cw = m_clauses_to_reinit[i]; + for (unsigned j = cw.size(); j-- > 0; ) + r.push_back(cw[j]); + } + for (literal lit : m_lemma) + r.push_back(lit); + } + void solver::reinit_clauses(unsigned old_sz) { unsigned sz = m_clauses_to_reinit.size(); SASSERT(old_sz <= sz); @@ -3834,26 +3881,7 @@ namespace sat { // v is an index of a variable that does not occur in solver state. if (v < m_justification.size()) { - for (bool_var i = v; i < m_justification.size(); ++i) { - m_case_split_queue.del_var_eh(i); - m_probing.reset_cache(literal(i, true)); - m_probing.reset_cache(literal(i, false)); - } - m_watches.shrink(2*v); - m_assignment.shrink(2*v); - m_justification.shrink(v); - m_decision.shrink(v); - m_eliminated.shrink(v); - m_external.shrink(v); - m_touched.shrink(v); - m_activity.shrink(v); - m_mark.shrink(v); - m_lit_mark.shrink(2*v); - m_phase.shrink(v); - m_best_phase.shrink(v); - m_prev_phase.shrink(v); - m_assigned_since_gc.shrink(v); - m_simplifier.reset_todos(); + shrink_vars(v); } } @@ -4766,7 +4794,7 @@ namespace sat { break; } case justification::EXT_JUSTIFICATION: { - fill_ext_antecedents(lit, js); + fill_ext_antecedents(lit, js, true); for (literal l : m_ext_antecedents) { if (check_domain(lit, l) && all_found) { s |= m_antecedents.find(l.var()); diff --git a/src/sat/sat_solver.h b/src/sat/sat_solver.h index 2fd9cbbc186..79eddffaca4 100644 --- a/src/sat/sat_solver.h +++ b/src/sat/sat_solver.h @@ -45,6 +45,7 @@ Revision History: #include "util/trace.h" #include "util/rlimit.h" #include "util/scoped_ptr_vector.h" +#include "util/scoped_limit_trail.h" namespace sat { @@ -172,6 +173,7 @@ namespace sat { bool m_inconsistent; }; svector m_scopes; + scoped_limit_trail m_vars_lim; stopwatch m_stopwatch; params_ref m_params; scoped_ptr m_clone; // for debugging purposes @@ -399,6 +401,7 @@ namespace sat { bool canceled() { return !m_rlimit.inc(); } config const& get_config() const { return m_config; } drat& get_drat() { return m_drat; } + drat* get_drat_ptr() override { return &m_drat; } void set_incremental(bool b) { m_config.m_incremental = b; } bool is_incremental() const { return m_config.m_incremental; } extension* get_extension() const override { return m_ext.get(); } @@ -565,8 +568,6 @@ namespace sat { unsigned m_conflict_lvl; literal_vector m_lemma; literal_vector m_ext_antecedents; - literal m_cached_antecedent_consequent; - ext_justification_idx m_cached_antecedent_js { 0 }; bool use_backjumping(unsigned num_scopes); bool resolve_conflict(); lbool resolve_conflict_core(); @@ -582,7 +583,7 @@ namespace sat { void resolve_conflict_for_unsat_core(); void process_antecedent_for_unsat_core(literal antecedent); void process_consequent_for_unsat_core(literal consequent, justification const& js); - void fill_ext_antecedents(literal consequent, justification js); + void fill_ext_antecedents(literal consequent, justification js, bool probing); unsigned skip_literals_above_conflict_level(); void updt_phase_of_vars(); void updt_phase_counters(); @@ -621,6 +622,8 @@ namespace sat { void push(); void pop(unsigned num_scopes); void pop_reinit(unsigned num_scopes); + void shrink_vars(unsigned v); + void pop_vars(unsigned num_scopes); void unassign_vars(unsigned old_sz, unsigned new_lvl); void reinit_clauses(unsigned old_sz); @@ -635,6 +638,14 @@ namespace sat { bool_var max_var(clause_vector& clauses, bool_var v); bool_var max_var(bool learned, bool_var v); + // ----------------------- + // + // External + // + // ----------------------- + public: + void set_should_simplify() { m_next_simplify = m_conflicts_since_init; } + void get_reinit_literals(unsigned num_scopes, literal_vector& r); public: void user_push() override; void user_pop(unsigned num_scopes) override; @@ -798,4 +809,3 @@ namespace sat { std::ostream & operator<<(std::ostream & out, mk_stat const & stat); }; - diff --git a/src/sat/sat_solver/inc_sat_solver.cpp b/src/sat/sat_solver/inc_sat_solver.cpp index bc8170183fe..d5de7f61989 100644 --- a/src/sat/sat_solver/inc_sat_solver.cpp +++ b/src/sat/sat_solver/inc_sat_solver.cpp @@ -126,7 +126,7 @@ class inc_sat_solver : public solver { auto* ext = dynamic_cast(m_solver.get_extension()); if (ext) { auto& si = result->m_goal2sat.si(dst_m, m_params, result->m_solver, result->m_map, result->m_dep2asm, is_incremental()); - euf::solver::scoped_set_translate st(*ext, dst_m, result->m_map, si); + euf::solver::scoped_set_translate st(*ext, dst_m, si); result->m_solver.copy(m_solver); } else { diff --git a/src/sat/sat_solver_core.h b/src/sat/sat_solver_core.h index 372b1308e04..f6beb72ec16 100644 --- a/src/sat/sat_solver_core.h +++ b/src/sat/sat_solver_core.h @@ -25,6 +25,7 @@ namespace sat { class cut_simplifier; class extension; + class drat; class solver_core { protected: @@ -95,6 +96,8 @@ namespace sat { virtual cut_simplifier* get_cut_simplifier() { return nullptr; } + virtual drat* get_drat_ptr() { return nullptr; } + // The following methods are used when converting the state from the SAT solver back // to a set of assertions. diff --git a/src/sat/sat_types.h b/src/sat/sat_types.h index c169892922f..67863c8f367 100644 --- a/src/sat/sat_types.h +++ b/src/sat/sat_types.h @@ -254,19 +254,22 @@ namespace sat { class status { public: - enum class st { asserted, redundant, deleted }; + enum class st { input, asserted, redundant, deleted }; st m_st; int m_orig; public: status(enum st s, int o) : m_st(s), m_orig(o) {}; status(status const& s) : m_st(s.m_st), m_orig(s.m_orig) {} status(status&& s) noexcept { m_st = st::asserted; m_orig = -1; std::swap(m_st, s.m_st); std::swap(m_orig, s.m_orig); } + status& operator=(status const& other) { m_st = other.m_st; m_orig = other.m_orig; return *this; } static status redundant() { return status(status::st::redundant, -1); } static status asserted() { return status(status::st::asserted, -1); } static status deleted() { return status(status::st::deleted, -1); } + static status input() { return status(status::st::input, -1); } static status th(bool redundant, int id) { return status(redundant ? st::redundant : st::asserted, id); } + bool is_input() const { return st::input == m_st; } bool is_redundant() const { return st::redundant == m_st; } bool is_asserted() const { return st::asserted == m_st; } bool is_deleted() const { return st::deleted == m_st; } @@ -275,6 +278,5 @@ namespace sat { int get_th() const { return m_orig; } }; - }; diff --git a/src/sat/smt/CMakeLists.txt b/src/sat/smt/CMakeLists.txt index 145c1a3fcb5..6555749d963 100644 --- a/src/sat/smt/CMakeLists.txt +++ b/src/sat/smt/CMakeLists.txt @@ -3,10 +3,12 @@ z3_add_component(sat_smt atom2bool_var.cpp ba_internalize.cpp ba_solver.cpp + bv_ackerman.cpp bv_internalize.cpp bv_solver.cpp euf_ackerman.cpp euf_internalize.cpp + euf_invariant.cpp euf_model.cpp euf_proof.cpp euf_solver.cpp diff --git a/src/sat/smt/ba_internalize.cpp b/src/sat/smt/ba_internalize.cpp index e28b1dd927f..2aed982a1ba 100644 --- a/src/sat/smt/ba_internalize.cpp +++ b/src/sat/smt/ba_internalize.cpp @@ -21,6 +21,10 @@ Module Name: namespace sat { + void ba_solver::internalize(expr* e, bool redundant) { + internalize(e, false, false, redundant); + } + literal ba_solver::internalize(expr* e, bool sign, bool root, bool redundant) { flet _redundant(m_is_redundant, redundant); if (m_pb.is_pb(e)) diff --git a/src/sat/smt/ba_solver.cpp b/src/sat/smt/ba_solver.cpp index ec2e2554cf0..482834b7645 100644 --- a/src/sat/smt/ba_solver.cpp +++ b/src/sat/smt/ba_solver.cpp @@ -2107,8 +2107,8 @@ namespace sat { // ---------------------------- // constraint generic methods - void ba_solver::get_antecedents(literal l, ext_justification_idx idx, literal_vector & r) { - get_antecedents(l, index2constraint(idx), r); + void ba_solver::get_antecedents(literal l, ext_justification_idx idx, literal_vector & r, bool probing) { + get_antecedents(l, index2constraint(idx), r, probing); } bool ba_solver::is_watched(literal lit, constraint const& c) const { @@ -2128,14 +2128,14 @@ namespace sat { get_wlist(~lit).push_back(w); } - void ba_solver::get_antecedents(literal l, constraint const& c, literal_vector& r) { + void ba_solver::get_antecedents(literal l, constraint const& c, literal_vector& r, bool probing) { switch (c.tag()) { case card_t: get_antecedents(l, c.to_card(), r); break; case pb_t: get_antecedents(l, c.to_pb(), r); break; case xr_t: get_antecedents(l, c.to_xr(), r); break; default: UNREACHABLE(); break; } - if (get_config().m_drat && m_solver) { + if (get_config().m_drat && m_solver && !probing) { literal_vector lits; for (literal lit : r) lits.push_back(~lit); diff --git a/src/sat/smt/ba_solver.h b/src/sat/smt/ba_solver.h index 38d682be342..0ae2210b5aa 100644 --- a/src/sat/smt/ba_solver.h +++ b/src/sat/smt/ba_solver.h @@ -361,7 +361,7 @@ namespace sat { void set_conflict(constraint& c, literal lit); void assign(constraint& c, literal lit); bool assigned_above(literal above, literal below); - void get_antecedents(literal l, constraint const& c, literal_vector & r); + void get_antecedents(literal l, constraint const& c, literal_vector & r, bool probing); bool validate_conflict(constraint const& c) const; bool validate_unit_propagation(constraint const& c, literal alit) const; void validate_eliminated(); @@ -576,8 +576,9 @@ namespace sat { bool is_external(bool_var v) override; bool propagate(literal l, ext_constraint_idx idx) override; + bool unit_propagate() override { return false; } lbool resolve_conflict() override; - void get_antecedents(literal l, ext_justification_idx idx, literal_vector & r) override; + void get_antecedents(literal l, ext_justification_idx idx, literal_vector & r, bool probing) override; void asserted(literal l) override; check_result check() override; void push() override; @@ -604,6 +605,7 @@ namespace sat { bool check_model(model const& m) const override; literal internalize(expr* e, bool sign, bool root, bool redundant) override; + void internalize(expr* e, bool redundant) override; bool to_formulas(std::function& l2e, expr_ref_vector& fmls) override; euf::th_solver* fresh(solver* s, euf::solver& ctx) override; diff --git a/src/sat/smt/bv_ackerman.cpp b/src/sat/smt/bv_ackerman.cpp new file mode 100644 index 00000000000..669ebf0b8a4 --- /dev/null +++ b/src/sat/smt/bv_ackerman.cpp @@ -0,0 +1,153 @@ +/*++ +Copyright (c) 2020 Microsoft Corporation + +Module Name: + + bv_ackerman.cpp + +Abstract: + + Ackerman reduction plugin for bit-vectors + +Author: + + Nikolaj Bjorner (nbjorner) 2020-08-28 + +--*/ + +#include "sat/smt/bv_solver.h" +#include "sat/smt/bv_ackerman.h" + +namespace bv { + + ackerman::ackerman(solver& s): s(s) { + new_tmp(); + m_propagate_low_watermark = s.get_config().m_dack_threshold; + } + + ackerman::~ackerman() { + reset(); + dealloc(m_tmp_vv); + } + + void ackerman::reset() { + m_table.reset(); + m_queue = nullptr; + } + + void ackerman::used_eq_eh(euf::theory_var v1, euf::theory_var v2) { + if (v1 == v2) + return; + if (v1 > v2) + std::swap(v1, v2); + vv* n = m_tmp_vv; + n->v1 = v1; + n->v2 = v2; + vv* other = m_table.insert_if_not_there(n); + other->m_count++; + update_glue(*other); + if (other->m_count > m_propagate_high_watermark || other->m_glue == 0) + s.s().set_should_simplify(); + vv::push_to_front(m_queue, other); + if (other == n) { + new_tmp(); + gc(); + } + } + + void ackerman::update_glue(vv& v) { + unsigned sz = s.m_bits[v.v1].size(); + m_diff_levels.reserve(s.s().scope_lvl() + 1, false); + unsigned glue = 0; + unsigned max_glue = v.m_glue; + auto const& bitsa = s.m_bits[v.v1]; + auto const& bitsb = s.m_bits[v.v2]; + unsigned i = 0; + for (; i < sz && i < max_glue; ++i) { + sat::literal a = bitsa[i]; + sat::literal b = bitsb[i]; + if (a == b) + continue; + SASSERT(s.s().value(a) != l_undef); + SASSERT(s.s().value(b) == s.s().value(a)); + unsigned lvl_a = s.s().lvl(a); + unsigned lvl_b = s.s().lvl(b); + if (!m_diff_levels[lvl_a]) { + m_diff_levels[lvl_a] = true; + ++glue; + } + if (!m_diff_levels[lvl_b]) { + m_diff_levels[lvl_b] = true; + ++glue; + } + } + for (; i-- > 0; ) { + sat::literal a = bitsa[i]; + sat::literal b = bitsb[i]; + if (a != b) { + m_diff_levels[s.s().lvl(a)] = false; + m_diff_levels[s.s().lvl(b)] = false; + } + } + + if (glue < max_glue) + v.m_glue = glue <= sz ? 0 : glue; + } + + void ackerman::remove(vv* p) { + vv::remove_from(m_queue, p); + m_table.erase(p); + dealloc(p); + } + + void ackerman::new_tmp() { + m_tmp_vv = alloc(vv); + m_tmp_vv->init(m_tmp_vv); + m_tmp_vv->m_count = 0; + m_tmp_vv->m_glue = UINT_MAX; + } + + void ackerman::gc() { + m_num_propagations_since_last_gc++; + if (m_num_propagations_since_last_gc <= s.get_config().m_dack_gc) + return; + m_num_propagations_since_last_gc = 0; + + while (m_table.size() > m_gc_threshold) + remove(m_queue->prev()); + + m_gc_threshold *= 110; + m_gc_threshold /= 100; + m_gc_threshold++; + } + + void ackerman::propagate() { + SASSERT(s.s().at_base_lvl()); + auto* n = m_queue; + vv* k = nullptr; + unsigned num_prop = static_cast(s.s().get_stats().m_conflict * s.get_config().m_dack_factor); + num_prop = std::min(num_prop, m_table.size()); + for (unsigned i = 0; i < num_prop; ++i, n = k) { + k = n->next(); + if (n->m_count < m_propagate_low_watermark && n->m_glue != 0) + continue; + add_cc(n->v1, n->v2); + remove(n); + } + } + + void ackerman::add_cc(euf::theory_var v1, euf::theory_var v2) { + SASSERT(v1 < v2); + if (static_cast(v2) >= s.get_num_vars()) + return; + if (!s.var2enode(v1) || !s.var2enode(v2)) + return; + sort* s1 = s.m.get_sort(s.var2expr(v1)); + sort* s2 = s.m.get_sort(s.var2expr(v2)); + if (s1 != s2 || !s.bv.is_bv_sort(s1)) + return; + IF_VERBOSE(0, verbose_stream() << "assert ackerman " << v1 << " " << v2 << "\n"); + s.assert_ackerman(v1, v2); + } + +} diff --git a/src/sat/smt/bv_ackerman.h b/src/sat/smt/bv_ackerman.h new file mode 100644 index 00000000000..0b5b28a8cc3 --- /dev/null +++ b/src/sat/smt/bv_ackerman.h @@ -0,0 +1,77 @@ +/*++ +Copyright (c) 2020 Microsoft Corporation + +Module Name: + + euf_ackerman.h + +Abstract: + + Ackerman reduction plugin for EUF + +Author: + + Nikolaj Bjorner (nbjorner) 2020-08-25 + +--*/ +#pragma once + +#include "util/dlist.h" +#include "sat/smt/atom2bool_var.h" +#include "sat/smt/sat_th.h" + +namespace bv { + + class solver; + + class ackerman { + + struct vv : dll_base { + euf::theory_var v1, v2; + unsigned m_count{ 0 }; + unsigned m_glue{ UINT_MAX }; + vv():v1(euf::null_theory_var), v2(euf::null_theory_var) {} + vv(euf::theory_var v1, euf::theory_var v2):v1(v1), v2(v2) {} + }; + + struct vv_eq { + bool operator()(vv const* a, vv const* b) const { + return a->v1 == b->v1 && a->v2 == b->v2; + } + }; + + struct vv_hash { + unsigned operator()(vv const* a) const { + return mk_mix(a->v1, a->v2, 0); + } + }; + + typedef hashtable table_t; + + solver& s; + table_t m_table; + vv* m_queue { nullptr }; + vv* m_tmp_vv { nullptr }; + unsigned m_gc_threshold { 1 }; + unsigned m_propagate_high_watermark { 10000 }; + unsigned m_propagate_low_watermark { 10 }; + unsigned m_num_propagations_since_last_gc { 0 }; + bool_vector m_diff_levels; + + void update_glue(vv& v); + void reset(); + void new_tmp(); + void remove(vv* inf); + void gc(); + void add_cc(euf::theory_var v1, euf::theory_var v2); + + public: + ackerman(solver& s); + ~ackerman(); + + void used_eq_eh(euf::theory_var v1, euf::theory_var v2); + + void propagate(); + }; + +}; diff --git a/src/sat/smt/bv_internalize.cpp b/src/sat/smt/bv_internalize.cpp index 0c9cfac03f3..9029c30823c 100644 --- a/src/sat/smt/bv_internalize.cpp +++ b/src/sat/smt/bv_internalize.cpp @@ -22,23 +22,33 @@ Module Name: namespace bv { - class add_var_pos_trail : public trail { - solver::bit_atom * m_atom; + solver::bit_atom& solver::atom::to_bit() { + SASSERT(is_bit()); + return dynamic_cast(*this); + } + + solver::def_atom& solver::atom::to_def() { + SASSERT(!is_bit()); + return dynamic_cast(*this); + } + + class solver::add_var_pos_trail : public trail { + solver::bit_atom* m_atom; public: - add_var_pos_trail(solver::bit_atom * a):m_atom(a) {} - void undo(euf::solver & euf) override { + add_var_pos_trail(solver::bit_atom* a) :m_atom(a) {} + void undo(euf::solver& euf) override { SASSERT(m_atom->m_occs); m_atom->m_occs = m_atom->m_occs->m_next; } }; - class mk_atom_trail : public trail { + class solver::mk_atom_trail : public trail { solver& th; sat::bool_var m_var; public: - mk_atom_trail(sat::bool_var v, solver& th):m_var(v), th(th) {} - void undo(euf::solver & euf) override { - solver::atom * a = th.get_bv2a(m_var); + mk_atom_trail(sat::bool_var v, solver& th) : th(th), m_var(v) {} + void undo(euf::solver& euf) override { + solver::atom* a = th.get_bv2a(m_var); a->~atom(); th.erase_bv2a(m_var); } @@ -49,162 +59,152 @@ namespace bv { m_find.mk_var(); m_bits.push_back(sat::literal_vector()); m_wpos.push_back(0); - m_zero_one_bits.push_back(zero_one_bits()); + m_zero_one_bits.push_back(zero_one_bits()); ctx.attach_th_var(n, this, r); + + TRACE("bv", tout << "mk-var: " << r << " " << n->get_owner_id() << " " << mk_pp(n->get_owner(), m) << "\n";); return r; } + void solver::apply_sort_cnstr(euf::enode * n, sort * s) { + get_var(n); + } + sat::literal solver::internalize(expr* e, bool sign, bool root, bool redundant) { + SASSERT(m.is_bool(e)); if (!visit_rec(m, e, sign, root, redundant)) return sat::null_literal; - return sat::null_literal; + return expr2literal(e); } - bool solver::visit(expr* e) { - if (!bv.is_bv(e)) { - ctx.internalize(e, false, false, m_is_redundant); + void solver::internalize(expr* e, bool redundant) { + visit_rec(m, e, false, false, redundant); + } + + bool solver::visit(expr* e) { + if (!is_app(e) || to_app(e)->get_family_id() != get_id()) { + ctx.internalize(e, m_is_redundant); return true; } - m_args.reset(); + m_stack.push_back(sat::eframe(e)); + return false; + } + + bool solver::visited(expr* e) { + euf::enode* n = expr2enode(e); + return n && n->is_attached_to(get_id()); + } + + bool solver::post_visit(expr* e, bool sign, bool root) { + euf::enode* n = expr2enode(e); app* a = to_app(e); - for (expr* arg : *a) { - euf::enode* n = get_enode(arg); - if (n) - m_args.push_back(n); - else - m_stack.push_back(sat::eframe(arg)); + + SASSERT(!n || !n->is_attached_to(get_id())); + if (!n) { + m_args.reset(); + if (get_config().m_bv_reflect || m.is_considered_uninterpreted(a->get_decl())) { + for (expr* arg : *a) + m_args.push_back(expr2enode(arg)); + } + DEBUG_CODE(for (auto* n : m_args) VERIFY(n);); + n = ctx.mk_enode(e, m_args.size(), m_args.c_ptr()); + SASSERT(!n->is_attached_to(get_id())); + ctx.attach_node(n); } - if (m_args.size() != a->get_num_args()) - return false; - if (!smt_params().m_bv_reflect) - m_args.reset(); - euf::enode* n = mk_enode(a, m_args); - SASSERT(n->interpreted()); - theory_var v = n->get_th_var(get_id()); + + SASSERT(!n->is_attached_to(get_id())); + theory_var v = mk_var(n); + SASSERT(n->is_attached_to(get_id())); + + std::function bin; + std::function ebin; + std::function un; + std::function pun; +#define internalize_bin(F) bin = [&](unsigned sz, expr* const* xs, expr* const* ys, expr_ref_vector& bits) { m_bb.F(sz, xs, ys, bits); }; internalize_binary(a, bin); +#define internalize_un(F) un = [&](unsigned sz, expr* const* xs, expr_ref_vector& bits) { m_bb.F(sz, xs, bits);}; internalize_unary(a, un); +#define internalize_ac(F) bin = [&](unsigned sz, expr* const* xs, expr* const* ys, expr_ref_vector& bits) { m_bb.F(sz, xs, ys, bits); }; internalize_ac_binary(a, bin); +#define internalize_pun(F) pun = [&](unsigned sz, expr* const* xs, unsigned p, expr_ref_vector& bits) { m_bb.F(sz, xs, p, bits);}; internalize_par_unary(a, pun); +#define internalize_nfl(F) ebin = [&](unsigned sz, expr* const* xs, expr* const* ys, expr_ref& out) { m_bb.F(sz, xs, ys, out);}; internalize_novfl(a, ebin); switch (a->get_decl_kind()) { - case OP_BV_NUM: internalize_num(a, v); break; - default: break; + case OP_BV_NUM: internalize_num(a, v); break; + case OP_BNOT: internalize_un(mk_not); break; + case OP_BREDAND: internalize_un(mk_redand); break; + case OP_BREDOR: internalize_un(mk_redor); break; + case OP_BSDIV_I: internalize_bin(mk_sdiv); break; + case OP_BUDIV_I: internalize_bin(mk_udiv); break; + case OP_BUREM_I: internalize_bin(mk_urem); break; + case OP_BSREM_I: internalize_bin(mk_srem); break; + case OP_BSMOD_I: internalize_bin(mk_smod); break; + case OP_BSHL: internalize_bin(mk_shl); break; + case OP_BLSHR: internalize_bin(mk_lshr); break; + case OP_BASHR: internalize_bin(mk_ashr); break; + case OP_EXT_ROTATE_LEFT: internalize_bin(mk_ext_rotate_left); break; + case OP_EXT_ROTATE_RIGHT: internalize_bin(mk_ext_rotate_right); break; + case OP_BADD: internalize_ac(mk_adder); break; + case OP_BMUL: internalize_ac(mk_multiplier); break; + case OP_BAND: internalize_ac(mk_and); break; + case OP_BOR: internalize_ac(mk_or); break; + case OP_BXOR: internalize_ac(mk_xor); break; + case OP_BNAND: internalize_bin(mk_nand); break; + case OP_BNOR: internalize_bin(mk_nor); break; + case OP_BXNOR: internalize_bin(mk_xnor); break; + case OP_BCOMP: internalize_bin(mk_comp); break; + case OP_SIGN_EXT: internalize_pun(mk_sign_extend); break; + case OP_ZERO_EXT: internalize_pun(mk_zero_extend); break; + case OP_ROTATE_LEFT: internalize_pun(mk_rotate_left); break; + case OP_ROTATE_RIGHT: internalize_pun(mk_rotate_right); break; + case OP_BUMUL_NO_OVFL: internalize_nfl(mk_umul_no_overflow); break; + case OP_BSMUL_NO_OVFL: internalize_nfl(mk_smul_no_overflow); break; + case OP_BSMUL_NO_UDFL: internalize_nfl(mk_smul_no_underflow); break; + case OP_BIT2BOOL: internalize_bit2bool(a); break; + case OP_ULEQ: internalize_le(a); break; + case OP_SLEQ: internalize_le(a); break; + case OP_XOR3: internalize_xor3(a); break; + case OP_CARRY: internalize_carry(a); break; + case OP_BSUB: internalize_sub(a); break; + case OP_CONCAT: internalize_concat(a); break; + case OP_EXTRACT: internalize_extract(a); break; + case OP_MKBV: internalize_mkbv(a); break; + case OP_INT2BV: internalize_int2bv(a); break; + case OP_BV2INT: internalize_bv2int(a); break; + case OP_BSDIV0: break; + case OP_BUDIV0: break; + case OP_BSREM0: break; + case OP_BUREM0: break; + case OP_BSMOD0: break; + default: + UNREACHABLE(); + break; } return true; } - bool solver::visited(expr* e) { - return get_enode(e) != nullptr; - } - void solver::mk_bits(theory_var v) { TRACE("bv", tout << "v" << v << "\n";); - expr* e = get_expr(v); + expr* e = var2expr(v); unsigned bv_size = get_bv_size(v); - literal_vector& bits = m_bits[v]; - bits.reset(); + m_bits[v].reset(); for (unsigned i = 0; i < bv_size; i++) { - expr_ref b2b = mk_bit2bool(e, i); - bits.push_back(ctx.internalize(b2b, false, false, m_is_redundant)); - } - } - - expr_ref solver::mk_bit2bool(expr* e, unsigned idx) { - parameter p(idx); - expr* args[1] = { e }; - return expr_ref(m.mk_app(get_id(), OP_BIT2BOOL, 1, &p, 1, args), m); - } - -#if 0 - SASSERT(!ctx.b_internalized(n)); - - TRACE("bv", tout << "bit2bool: " << mk_pp(n, ctx.get_manager()) << "\n";); - expr* first_arg = n->get_arg(0); - - if (!ctx.e_internalized(first_arg)) { - // This may happen if bit2bool(x) is in a conflict - // clause that is being reinitialized, and x was not reinitialized - // yet. - // So, we internalize x (i.e., arg) - ctx.internalize(first_arg, false); - SASSERT(ctx.e_internalized(first_arg)); - // In most cases, when x is internalized, its bits are created. - // They are created because x is a bit-vector operation or apply_sort_cnstr is invoked. - // However, there is an exception. The method apply_sort_cnstr is not invoked for ite-terms. - // So, I execute get_var on the enode attached to first_arg. - // This will force a theory variable to be created if it does not already exist. - // This will also force the creation of all bits for x. - enode* first_arg_enode = ctx.get_enode(first_arg); - get_var(first_arg_enode); - } - - enode* arg = ctx.get_enode(first_arg); - // The argument was already internalized, but it may not have a theory variable associated with it. - // For example, for ite-terms the method apply_sort_cnstr is not invoked. - // See comment in the then-branch. - theory_var v_arg = arg->get_th_var(get_id()); - if (v_arg == null_theory_var) { - // The method get_var will create a theory variable for arg. - // As a side-effect the bits for arg will also be created. - get_var(arg); - SASSERT(ctx.b_internalized(n)); - } - else if (!ctx.b_internalized(n)) { - SASSERT(v_arg != null_theory_var); - bool_var bv = ctx.mk_bool_var(n); - ctx.set_var_theory(bv, get_id()); - bit_atom* a = new (get_region()) bit_atom(); - insert_bv2a(bv, a); - m_trail_stack.push(mk_atom_trail(bv)); - unsigned idx = n->get_decl()->get_parameter(0).get_int(); - SASSERT(a->m_occs == 0); - a->m_occs = new (get_region()) var_pos_occ(v_arg, idx); - // Fix for #2182, and SPACER bit-vector - if (idx < m_bits[v_arg].size()) { - ctx.mk_th_axiom(get_id(), m_bits[v_arg][idx], literal(bv, true)); - ctx.mk_th_axiom(get_id(), ~m_bits[v_arg][idx], literal(bv, false)); - } - } - // axiomatize bit2bool on constants. - rational val; - unsigned sz; - if (m_util.is_numeral(first_arg, val, sz)) { - rational bit; - unsigned idx = n->get_decl()->get_parameter(0).get_int(); - div(val, rational::power_of_two(idx), bit); - mod(bit, rational(2), bit); - literal lit = ctx.get_literal(n); - if (bit.is_zero()) lit.neg(); - ctx.mark_as_relevant(lit); - ctx.mk_th_axiom(get_id(), 1, &lit); + expr_ref b2b(bv.mk_bit2bool(e, i), m); + sat::literal lit = ctx.internalize(b2b, false, false, m_is_redundant); + m_bits[v].push_back(lit); } - - } -#endif - - - euf::enode * solver::mk_enode(expr * n, ptr_vector const& args) { - euf::enode * e = ctx.get_enode(n); - if (!e) { - e = ctx.mk_enode(n, args.size(), args.c_ptr()); - mk_var(e); - } - return e; } euf::theory_var solver::get_var(euf::enode* n) { theory_var v = n->get_th_var(get_id()); if (v == euf::null_theory_var) { v = mk_var(n); - mk_bits(v); + if (bv.is_bv(n->get_owner())) + mk_bits(v); } return v; } euf::enode* solver::get_arg(euf::enode* n, unsigned idx) { - if (get_config().m_bv_reflect) { - return n->get_arg(idx); - } - else { - expr* arg = n->get_arg(idx)->get_owner(); - return ctx.get_enode(arg); - } + app* o = to_app(n->get_owner()); + return ctx.get_enode(o->get_arg(idx)); } inline euf::theory_var solver::get_arg_var(euf::enode* n, unsigned idx) { @@ -212,20 +212,14 @@ namespace bv { } void solver::get_bits(theory_var v, expr_ref_vector& r) { - literal_vector& bits = m_bits[v]; - for (literal lit : bits) { - r.push_back(get_expr(lit)); - } + for (literal lit : m_bits[v]) + r.push_back(literal2expr(lit)); } inline void solver::get_bits(euf::enode* n, expr_ref_vector& r) { get_bits(get_var(n), r); } - inline void solver::get_arg_bits(euf::enode* n, unsigned idx, expr_ref_vector& r) { - get_bits(get_arg_var(n, idx), r); - } - inline void solver::get_arg_bits(app* n, unsigned idx, expr_ref_vector& r) { app* arg = to_app(n->get_arg(idx)); get_bits(ctx.get_enode(arg), r); @@ -234,7 +228,7 @@ namespace bv { void solver::register_true_false_bit(theory_var v, unsigned idx) { SASSERT(s().value(m_bits[v][idx]) != l_undef); bool is_true = (s().value(m_bits[v][idx]) == l_true); - zero_one_bits & bits = m_zero_one_bits[v]; + zero_one_bits& bits = m_zero_one_bits[v]; bits.push_back(zero_one_bit(v, idx, is_true)); } @@ -242,18 +236,22 @@ namespace bv { \brief Add bit l to the given variable. */ void solver::add_bit(theory_var v, literal l) { - literal_vector & bits = m_bits[v]; - unsigned idx = bits.size(); - bits.push_back(l); - if (s().value(l) != l_undef && s().lvl(l) == 0) { + unsigned idx = m_bits[v].size(); + m_bits[v].push_back(l); + s().set_external(l.var()); + set_bit_eh(v, l, idx); + } + + void solver::set_bit_eh(theory_var v, literal l, unsigned idx) { + if (s().value(l) != l_undef && s().lvl(l) == 0) register_true_false_bit(v, idx); - } else { - atom * a = get_bv2a(l.var()); - SASSERT(!a || a->is_bit()); - if (a) { - bit_atom* b = static_cast(a); - find_new_diseq_axioms(b->m_occs, v, idx); + atom* a = get_bv2a(l.var()); + if (a && !a->is_bit()) + ; + else if (a) { + bit_atom* b = &a->to_bit(); + find_new_diseq_axioms(*b, v, idx); ctx.push(add_var_pos_trail(b)); b->m_occs = new (get_region()) var_pos_occ(v, idx, b->m_occs); } @@ -261,24 +259,23 @@ namespace bv { bit_atom* b = new (get_region()) bit_atom(); insert_bv2a(l.var(), b); ctx.push(mk_atom_trail(l.var(), *this)); - SASSERT(b->m_occs == 0); + SASSERT(!b->m_occs); b->m_occs = new (get_region()) var_pos_occ(v, idx); } } } - void solver::add_unit(sat::literal lit) { - s().add_clause(1, &lit, status()); - } - - void solver::init_bits(euf::enode * n, expr_ref_vector const & bits) { + void solver::init_bits(expr* e, expr_ref_vector const& bits) { + euf::enode* n = expr2enode(e); SASSERT(get_bv_size(n) == bits.size()); SASSERT(euf::null_theory_var != n->get_th_var(get_id())); theory_var v = n->get_th_var(get_id()); - unsigned sz = bits.size(); m_bits[v].reset(); + for (expr* bit : bits) + add_bit(v, ctx.internalize(bit, false, false, m_is_redundant)); for (expr* bit : bits) - add_bit(v, get_literal(bit)); + get_var(expr2enode(bit)); + SASSERT(get_bv_size(n) == bits.size()); find_wpos(v); } @@ -287,22 +284,23 @@ namespace bv { } unsigned solver::get_bv_size(theory_var v) { - return get_bv_size(get_enode(v)); + return get_bv_size(var2enode(v)); } void solver::internalize_num(app* n, theory_var v) { numeral val; unsigned sz = 0; + SASSERT(expr2enode(n)->interpreted()); VERIFY(bv.is_numeral(n, val, sz)); expr_ref_vector bits(m); - m_bb.num2bits(val, sz, bits); - literal_vector & c_bits = m_bits[v]; + m_bb.num2bits(val, sz, bits); SASSERT(bits.size() == sz); - SASSERT(c_bits.empty()); + SASSERT(m_bits[v].empty()); + sat::literal true_literal = ctx.internalize(m.mk_true(), false, false, false); for (unsigned i = 0; i < sz; i++) { - expr * l = bits.get(i); + expr* l = bits.get(i); SASSERT(m.is_true(l) || m.is_false(l)); - c_bits.push_back(m.is_true(l) ? true_literal : false_literal); + m_bits[v].push_back(m.is_true(l) ? true_literal : ~true_literal); register_true_false_bit(v, i); } fixed_var_eh(v); @@ -310,14 +308,12 @@ namespace bv { void solver::internalize_mkbv(app* n) { expr_ref_vector bits(m); - euf::enode * e = mk_enode(n, m_args); bits.append(n->get_num_args(), n->get_args()); - init_bits(e, bits); + init_bits(n, bits); } void solver::internalize_bv2int(app* n) { - mk_enode(n, m_args); - assert_bv2int_axiom(n); + assert_bv2int_axiom(n); } /** @@ -325,172 +321,270 @@ namespace bv { * n = bv2int(k) = ite(bit2bool(k[sz-1],2^{sz-1},0) + ... + ite(bit2bool(k[0],1,0)) */ - void solver::assert_bv2int_axiom(app * n) { - expr* k = nullptr; - sort * int_sort = m.get_sort(n); - SASSERT(bv.is_bv2int(n, k)); + void solver::assert_bv2int_axiom(app* n) { + expr* k = nullptr; + VERIFY(bv.is_bv2int(n, k)); SASSERT(bv.is_bv_sort(m.get_sort(k))); expr_ref_vector k_bits(m); - euf::enode * k_enode = mk_enode(k, m_args); + euf::enode* k_enode = expr2enode(k); get_bits(k_enode, k_bits); unsigned sz = bv.get_bv_size(k); expr_ref_vector args(m); - expr_ref zero(bv.mk_numeral(numeral(0), int_sort), m); - numeral num(1); - for (expr* b : k_bits) { - expr_ref n(m_autil.mk_numeral(num, int_sort), m); - args.push_back(m.mk_ite(b, n, zero)); - num *= numeral(2); - } + expr_ref zero(m_autil.mk_int(0), m); + unsigned i = 0; + for (expr* b : k_bits) + args.push_back(m.mk_ite(b, m_autil.mk_int(power2(i++)), zero)); expr_ref sum(m_autil.mk_add(sz, args.c_ptr()), m); expr_ref eq(m.mk_eq(n, sum), m); sat::literal lit = ctx.internalize(eq, false, false, m_is_redundant); add_unit(lit); } - void solver::internalize_int2bv(app* n) { SASSERT(bv.is_int2bv(n)); - euf::enode* e = mk_enode(n, m_args); - theory_var v = e->get_th_var(get_id()); - mk_bits(v); - assert_int2bv_axiom(n); + euf::enode* e = expr2enode(n); + mk_bits(e->get_th_var(get_id())); + assert_int2bv_axiom(n); } /** * create the axiom: - * bv2int(n) = e mod 2^bit_width + * bv2int(n) = e mod 2^bit_width * where n = int2bv(e) * * Create the axioms: * bit2bool(i,n) == ((e div 2^i) mod 2 != 0) * for i = 0,.., sz-1 */ - void solver::assert_int2bv_axiom(app* n) { - SASSERT(bv.is_int2bv(n)); - expr* e = n->get_arg(0); - euf::enode* n_enode = mk_enode(n, m_args); - parameter param(m_autil.mk_int()); - expr* n_expr = n; + void solver::assert_int2bv_axiom(app* n) { + expr* e = nullptr; + VERIFY(bv.is_int2bv(n, e)); + euf::enode* n_enode = expr2enode(n); expr_ref lhs(m), rhs(m); - lhs = m.mk_app(get_id(), OP_BV2INT, 1, ¶m, 1, &n_expr); + lhs = bv.mk_bv2int(n); unsigned sz = bv.get_bv_size(n); numeral mod = power(numeral(2), sz); - rhs = m_autil.mk_mod(e, m_autil.mk_numeral(mod, true)); + rhs = m_autil.mk_mod(e, m_autil.mk_int(mod)); expr_ref eq(m.mk_eq(lhs, rhs), m); - literal l = ctx.internalize(eq, false, false, m_is_redundant); - add_unit(l); - TRACE("bv", tout << eq << "\n";); - + add_unit(ctx.internalize(eq, false, false, m_is_redundant)); + expr_ref_vector n_bits(m); - get_bits(n_enode, n_bits); for (unsigned i = 0; i < sz; ++i) { - numeral div = power(numeral(2), i); - mod = numeral(2); - rhs = m_autil.mk_idiv(e, m_autil.mk_numeral(div, true)); - rhs = m_autil.mk_mod(rhs, m_autil.mk_numeral(mod, true)); - rhs = m.mk_eq(rhs, m_autil.mk_numeral(rational(1), true)); - lhs = n_bits.get(i); + numeral div = power2(i); + rhs = m_autil.mk_idiv(e, m_autil.mk_int(div)); + rhs = m_autil.mk_mod(rhs, m_autil.mk_int(2)); + rhs = m.mk_eq(rhs, m_autil.mk_int(1)); + lhs = n_bits.get(i); expr_ref eq(m.mk_eq(lhs, rhs), m); TRACE("bv", tout << eq << "\n";); - l = ctx.internalize(eq, false, false, m_is_redundant); - add_unit(l); + add_unit(ctx.internalize(eq, false, false, m_is_redundant)); } } + template + void solver::internalize_le(app* n) { + SASSERT(n->get_num_args() == 2); + expr_ref_vector arg1_bits(m), arg2_bits(m); + get_arg_bits(n, 0, arg1_bits); + get_arg_bits(n, 1, arg2_bits); + expr_ref le(m); + if (Signed) + m_bb.mk_sle(arg1_bits.size(), arg1_bits.c_ptr(), arg2_bits.c_ptr(), le); + else + m_bb.mk_ule(arg1_bits.size(), arg1_bits.c_ptr(), arg2_bits.c_ptr(), le); + literal def = ctx.internalize(le, false, false, m_is_redundant); + add_def(def, expr2literal(n)); + } - void solver::internalize_add(app* n) {} - void solver::internalize_sub(app* n) {} - void solver::internalize_mul(app* n) {} - void solver::internalize_udiv(app* n) {} - void solver::internalize_sdiv(app* n) {} - void solver::internalize_urem(app* n) {} - void solver::internalize_srem(app* n) {} - void solver::internalize_smod(app* n) {} - void solver::internalize_shl(app* n) {} - void solver::internalize_lshr(app* n) {} - void solver::internalize_ashr(app* n) {} - void solver::internalize_ext_rotate_left(app* n) {} - void solver::internalize_ext_rotate_right(app* n) {} - void solver::internalize_and(app* n) {} - void solver::internalize_or(app* n) {} - void solver::internalize_not(app* n) {} - void solver::internalize_nand(app* n) {} - void solver::internalize_nor(app* n) {} - void solver::internalize_xor(app* n) {} - void solver::internalize_xnor(app* n) {} - void solver::internalize_concat(app* n) {} - void solver::internalize_sign_extend(app* n) {} - void solver::internalize_zero_extend(app* n) {} - void solver::internalize_extract(app* n) {} - void solver::internalize_redand(app* n) {} - void solver::internalize_redor(app* n) {} - void solver::internalize_comp(app* n) {} - void solver::internalize_rotate_left(app* n) {} - void solver::internalize_rotate_right(app* n) {} - void solver::internalize_umul_no_overflow(app* n) {} - void solver::internalize_smul_no_overflow(app* n) {} - void solver::internalize_smul_no_underflow(app* n) {} + void solver::internalize_carry(app* n) { + SASSERT(n->get_num_args() == 3); + literal r = ctx.get_literal(n); + literal l1 = ctx.get_literal(n->get_arg(0)); + literal l2 = ctx.get_literal(n->get_arg(1)); + literal l3 = ctx.get_literal(n->get_arg(2)); + add_clause(~r, l1, l2); + add_clause(~r, l1, l3); + add_clause(~r, l2, l3); + add_clause(r, ~l1, ~l2); + add_clause(r, ~l1, ~l3); + add_clause(r, ~l2, ~l3); + } -} + void solver::internalize_xor3(app* n) { + SASSERT(n->get_num_args() == 3); + literal r = ctx.get_literal(n); + literal l1 = expr2literal(n->get_arg(0)); + literal l2 = expr2literal(n->get_arg(1)); + literal l3 = expr2literal(n->get_arg(2)); + add_clause(~r, l1, l2, l3); + add_clause(~r, ~l1, ~l2, l3); + add_clause(~r, ~l1, l2, ~l3); + add_clause(~r, l1, ~l2, ~l3); + add_clause(r, ~l1, l2, l3); + add_clause(r, l1, ~l2, l3); + add_clause(r, l1, l2, ~l3); + add_clause(r, ~l1, ~l2, ~l3); + } + void solver::internalize_unary(app* n, std::function& fn) { + SASSERT(n->get_num_args() == 1); + expr_ref_vector arg1_bits(m), bits(m); + get_arg_bits(n, 0, arg1_bits); + fn(arg1_bits.size(), arg1_bits.c_ptr(), bits); + init_bits(n, bits); + } + void solver::internalize_par_unary(app* n, std::function& fn) { + SASSERT(n->get_num_args() == 1); + expr_ref_vector arg1_bits(m), bits(m); + get_arg_bits(n, 0, arg1_bits); + unsigned param = n->get_decl()->get_parameter(0).get_int(); + fn(arg1_bits.size(), arg1_bits.c_ptr(), param, bits); + init_bits(n, bits); + } -#if 0 - - case OP_BADD: internalize_add(term); return true; - case OP_BSUB: internalize_sub(term); return true; - case OP_BMUL: internalize_mul(term); return true; - case OP_BSDIV_I: internalize_sdiv(term); return true; - case OP_BUDIV_I: internalize_udiv(term); return true; - case OP_BSREM_I: internalize_srem(term); return true; - case OP_BUREM_I: internalize_urem(term); return true; - case OP_BSMOD_I: internalize_smod(term); return true; - case OP_BAND: internalize_and(term); return true; - case OP_BOR: internalize_or(term); return true; - case OP_BNOT: internalize_not(term); return true; - case OP_BXOR: internalize_xor(term); return true; - case OP_BNAND: internalize_nand(term); return true; - case OP_BNOR: internalize_nor(term); return true; - case OP_BXNOR: internalize_xnor(term); return true; - case OP_CONCAT: internalize_concat(term); return true; - case OP_SIGN_EXT: internalize_sign_extend(term); return true; - case OP_ZERO_EXT: internalize_zero_extend(term); return true; - case OP_EXTRACT: internalize_extract(term); return true; - case OP_BREDOR: internalize_redor(term); return true; - case OP_BREDAND: internalize_redand(term); return true; - case OP_BCOMP: internalize_comp(term); return true; - case OP_BSHL: internalize_shl(term); return true; - case OP_BLSHR: internalize_lshr(term); return true; - case OP_BASHR: internalize_ashr(term); return true; - case OP_ROTATE_LEFT: internalize_rotate_left(term); return true; - case OP_ROTATE_RIGHT: internalize_rotate_right(term); return true; - case OP_EXT_ROTATE_LEFT: internalize_ext_rotate_left(term); return true; - case OP_EXT_ROTATE_RIGHT: internalize_ext_rotate_right(term); return true; - case OP_BSDIV0: return false; - case OP_BUDIV0: return false; - case OP_BSREM0: return false; - case OP_BUREM0: return false; - case OP_BSMOD0: return false; - case OP_MKBV: internalize_mkbv(term); return true; - case OP_INT2BV: - if (params().m_bv_enable_int2bv2int) { - internalize_int2bv(term); - } - return params().m_bv_enable_int2bv2int; - case OP_BV2INT: - if (params().m_bv_enable_int2bv2int) { - internalize_bv2int(term); - } - return params().m_bv_enable_int2bv2int; - default: - TRACE("bv_op", tout << "unsupported operator: " << mk_ll_pp(term, m) << "\n";); - UNREACHABLE(); - return false; + void solver::internalize_binary(app* n, std::function& fn) { + SASSERT(n->get_num_args() == 2); + expr_ref_vector arg1_bits(m), arg2_bits(m), bits(m); + get_arg_bits(n, 0, arg1_bits); + get_arg_bits(n, 1, arg2_bits); + SASSERT(arg1_bits.size() == arg2_bits.size()); + fn(arg1_bits.size(), arg1_bits.c_ptr(), arg2_bits.c_ptr(), bits); + init_bits(n, bits); + } + + void solver::internalize_ac_binary(app* n, std::function& fn) { + SASSERT(n->get_num_args() >= 2); + expr_ref_vector bits(m), new_bits(m), arg_bits(m); + unsigned i = n->get_num_args() - 1; + get_arg_bits(n, i, bits); + for (; i-- > 0; ) { + arg_bits.reset(); + get_arg_bits(n, i, arg_bits); + SASSERT(arg_bits.size() == bits.size()); + new_bits.reset(); + fn(arg_bits.size(), arg_bits.c_ptr(), bits.c_ptr(), new_bits); + bits.swap(new_bits); + } + init_bits(n, bits); + TRACE("bv_verbose", tout << arg_bits << " " << bits << " " << new_bits << "\n";); + } + + void solver::internalize_novfl(app* n, std::function& fn) { + SASSERT(n->get_num_args() == 2); + expr_ref_vector arg1_bits(m), arg2_bits(m); + get_arg_bits(n, 0, arg1_bits); + get_arg_bits(n, 1, arg2_bits); + expr_ref out(m); + fn(arg1_bits.size(), arg1_bits.c_ptr(), arg2_bits.c_ptr(), out); + sat::literal def = ctx.internalize(out, false, false, m_is_redundant); + add_def(def, ctx.get_literal(n)); + } + + void solver::add_def(sat::literal def, sat::literal l) { + def_atom* a = new (get_region()) def_atom(l, def); + insert_bv2a(l.var(), a); + ctx.push(mk_atom_trail(l.var(), *this)); + add_clause(l, ~def); + add_clause(def, ~l); } + void solver::internalize_concat(app* n) { + euf::enode* e = expr2enode(n); + theory_var v = e->get_th_var(get_id()); + m_bits[v].reset(); + for (unsigned i = n->get_num_args(); i-- > 0; ) + for (literal lit : m_bits[get_arg_var(e, i)]) + add_bit(v, lit); + find_wpos(v); + } + + void solver::internalize_sub(app* n) { + SASSERT(n->get_num_args() == 2); + expr_ref_vector arg1_bits(m), arg2_bits(m), bits(m); + get_arg_bits(n, 0, arg1_bits); + get_arg_bits(n, 1, arg2_bits); + SASSERT(arg1_bits.size() == arg2_bits.size()); + expr_ref carry(m); + m_bb.mk_subtracter(arg1_bits.size(), arg1_bits.c_ptr(), arg2_bits.c_ptr(), bits, carry); + init_bits(n, bits); + } + + void solver::internalize_extract(app* n) { + SASSERT(n->get_num_args() == 1); + euf::enode* e = expr2enode(n); + theory_var v = e->get_th_var(get_id()); + theory_var arg = get_arg_var(e, 0); + unsigned start = n->get_decl()->get_parameter(1).get_int(); + unsigned end = n->get_decl()->get_parameter(0).get_int(); + SASSERT(start <= end && end < get_bv_size(v)); + m_bits[v].reset(); + for (unsigned i = start; i <= end; ++i) + add_bit(v, m_bits[arg][i]); + find_wpos(v); + } + + void solver::internalize_bit2bool(app* n) { + unsigned idx = 0; + expr* arg = nullptr; + VERIFY(bv.is_bit2bool(n, arg, idx)); + euf::enode* argn = expr2enode(arg); + if (!argn->is_attached_to(get_id())) { + mk_var(argn); + } + theory_var v_arg = argn->get_th_var(get_id()); + sat::literal lit = expr2literal(n); + sat::bool_var b = lit.var(); + bit_atom* a = new (get_region()) bit_atom(); + SASSERT(idx < get_bv_size(v_arg)); + a->m_occs = new (get_region()) var_pos_occ(v_arg, idx); + insert_bv2a(b, a); + ctx.push(mk_atom_trail(b, *this)); + if (idx < m_bits[v_arg].size() && lit != m_bits[v_arg][idx]) { + add_clause(m_bits[v_arg][idx], ~lit); + add_clause(~m_bits[v_arg][idx], lit); + } + // axiomatize bit2bool on constants. + rational val; + unsigned sz; + if (bv.is_numeral(arg, val, sz)) { + rational bit; + div(val, rational::power_of_two(idx), bit); + mod(bit, rational(2), bit); + if (bit.is_zero()) lit.neg(); + add_unit(lit); + } + } + + void solver::assert_ackerman(theory_var v1, theory_var v2) { + flet _red(m_is_redundant, true); + ++m_stats.m_ackerman; + expr* o1 = var2expr(v1); + expr* o2 = var2expr(v2); + expr_ref oe(m.mk_eq(o1, o2), m); + literal oeq = ctx.internalize(oe, false, false, m_is_redundant); + unsigned sz = get_bv_size(v1); + TRACE("bv", tout << oe << "\n";); + literal_vector eqs; + for (unsigned i = 0; i < sz; ++i) { + literal l1 = m_bits[v1][i]; + literal l2 = m_bits[v2][i]; + expr_ref e1(m), e2(m); + e1 = bv.mk_bit2bool(o1, i); + e2 = bv.mk_bit2bool(o2, i); + expr_ref e(m.mk_eq(e1, e2), m); + literal eq = ctx.internalize(e, false, false, m_is_redundant); + add_clause(l1, ~l2, ~eq); + add_clause(~l1, l2, ~eq); + add_clause(l1, l2, eq); + add_clause(~l1, ~l2, eq); + add_clause(eq, ~oeq); + eqs.push_back(~eq); + } + eqs.push_back(oeq); + s().add_clause(eqs.size(), eqs.c_ptr(), sat::status::th(m_is_redundant, get_id())); + } } -#endif diff --git a/src/sat/smt/bv_solver.cpp b/src/sat/smt/bv_solver.cpp index 4555a37cba9..564d1db98cf 100644 --- a/src/sat/smt/bv_solver.cpp +++ b/src/sat/smt/bv_solver.cpp @@ -3,18 +3,20 @@ Copyright (c) 2020 Microsoft Corporation Module Name: - bv_internalize.cpp + bv_solver.cpp Abstract: - Internalize utilities for bit-vector solver plugin. + Solving utilities for bit-vectors. Author: Nikolaj Bjorner (nbjorner) 2020-09-02 + based on smt/theory_bv --*/ +#include "ast/ast_ll_pp.h" #include "sat/smt/bv_solver.h" #include "sat/smt/euf_solver.h" #include "sat/smt/sat_th.h" @@ -22,52 +24,110 @@ Module Name: namespace bv { - void solver::fixed_var_eh(theory_var v) { - + class solver::bit_trail : public trail { + solver& s; + solver::var_pos vp; + sat::literal lit; + public: + bit_trail(solver& s, var_pos vp) : s(s), vp(vp), lit(s.m_bits[vp.first][vp.second]) {} + + virtual void undo(euf::solver& euf) { + s.m_bits[vp.first][vp.second] = lit; + } + }; + + solver::solver(euf::solver& ctx, theory_id id) : + euf::th_euf_solver(ctx, id), + bv(m), + m_autil(m), + m_ackerman(*this), + m_bb(m, get_config()), + m_find(*this) { + } + + void solver::fixed_var_eh(theory_var v1) { + numeral val1, val2; + VERIFY(get_fixed_value(v1, val1)); + unsigned sz = m_bits[v1].size(); + value_sort_pair key(val1, sz); + theory_var v2; + bool is_current = + m_fixed_var_table.find(key, v2) && + v2 < static_cast(get_num_vars()) && + is_bv(v2) && + get_bv_size(v2) == sz && + get_fixed_value(v2, val2) && val1 == val2; + + if (!is_current) + m_fixed_var_table.insert(key, v1); + else if (var2enode(v1)->get_root() != var2enode(v2)->get_root()) { + SASSERT(get_bv_size(v1) == get_bv_size(v2)); + TRACE("bv", tout << "detected equality: v" << v1 << " = v" << v2 << "\n" << pp(v1) << pp(v2);); + m_stats.m_num_th2core_eq++; + add_fixed_eq(v1, v2); + ctx.propagate(var2enode(v1), var2enode(v2), mk_bit2bv_justification(v1, v2)); + } + } + + void solver::add_fixed_eq(theory_var v1, theory_var v2) { + if (!get_config().m_bv_eq_axioms) + return; + m_ackerman.used_eq_eh(v1, v2); + } + + bool solver::get_fixed_value(theory_var v, numeral& result) const { + result.reset(); + unsigned i = 0; + for (literal b : m_bits[v]) { + switch (ctx.s().value(b)) { + case l_false: + break; + case l_undef: + return false; + case l_true: + result += power2(i); + break; + } + ++i; + } + return true; } /** \brief Find an unassigned bit for m_wpos[v], if such bit cannot be found invoke fixed_var_eh */ void solver::find_wpos(theory_var v) { - literal_vector const & bits = m_bits[v]; - unsigned sz = bits.size(); - unsigned & wpos = m_wpos[v]; - unsigned init = wpos; - for (; wpos < sz; wpos++) { - TRACE("find_wpos", tout << "curr bit: " << bits[wpos] << "\n";); - if (s().value(bits[wpos]) == l_undef) { - TRACE("find_wpos", tout << "moved wpos of v" << v << " to " << wpos << "\n";); - return; - } - } - wpos = 0; - for (; wpos < init; wpos++) { - if (s().value(bits[wpos]) == l_undef) { - TRACE("find_wpos", tout << "moved wpos of v" << v << " to " << wpos << "\n";); + literal_vector const& bits = m_bits[v]; + unsigned sz = bits.size(); + unsigned& wpos = m_wpos[v]; + for (unsigned i = 0; i < sz; ++i) { + unsigned idx = (i + wpos) % sz; + if (s().value(bits[idx]) == l_undef) { + wpos = idx; + TRACE("bv", tout << "moved wpos of v" << v << " to " << wpos << "\n";); return; } } - TRACE("find_wpos", tout << "v" << v << " is a fixed variable.\n";); + TRACE("bv", tout << "v" << v << " is a fixed variable.\n";); fixed_var_eh(v); } /** - \brief v[idx] = ~v'[idx], then v /= v' is a theory axiom. -*/ - void solver::find_new_diseq_axioms(var_pos_occ* occs, theory_var v, unsigned idx) { + *\brief v[idx] = ~v'[idx], then v /= v' is a theory axiom. + */ + void solver::find_new_diseq_axioms(bit_atom& a, theory_var v, unsigned idx) { + if (!get_config().m_bv_eq_axioms) + return; literal l = m_bits[v][idx]; l.neg(); - while (occs) { - theory_var v2 = occs->m_var; - unsigned idx2 = occs->m_idx; + for (auto vp : a) { + theory_var v2 = vp.first; + unsigned idx2 = vp.second; if (idx == idx2 && m_bits[v2][idx2] == l && get_bv_size(v2) == get_bv_size(v)) mk_new_diseq_axiom(v, v2, idx); - occs = occs->m_next; } } - /** \brief v1[idx] = ~v2[idx], then v1 /= v2 is a theory axiom. */ @@ -78,60 +138,533 @@ namespace bv { // TBD: disabled until new literal creation is supported return; SASSERT(m_bits[v1][idx] == ~m_bits[v2][idx]); - TRACE("bv_solver", tout << "found new diseq axiom\n" << pp(v1) << pp(v2);); + TRACE("bv", tout << "found new diseq axiom\n" << pp(v1) << pp(v2);); m_stats.m_num_diseq_static++; - expr_ref eq(m.mk_eq(get_expr(v1), get_expr(v2)), m); - sat::literal neq = ctx.internalize(eq, true, false, m_is_redundant); - s().add_clause(1, &neq, sat::status::th(m_is_redundant, get_id())); + expr_ref eq(m.mk_eq(var2expr(v1), var2expr(v2)), m); + add_unit(~ctx.internalize(eq, false, false, m_is_redundant)); } std::ostream& solver::display(std::ostream& out, theory_var v) const { + expr* e = var2expr(v); out << "v"; out.width(4); out << std::left << v; - out << " #"; + out << " "; out.width(4); - out << get_enode(v)->get_owner_id() << " -> #"; + out << e->get_id() << " -> "; out.width(4); -#if 0 - out << get_enode(find(v))->get_owner_id(); - out << std::right << ", bits:"; - literal_vector const& bits = m_bits[v]; - for (literal lit : bits) { - out << " " << lit << ":"; - ctx.display_literal(out, lit); + out << var2enode(find(v))->get_owner_id(); + out << std::right; + out.flush(); + atom* a = nullptr; + if (is_bv(v)) { + numeral val; + if (get_fixed_value(v, val)) + out << " (= " << val << ")"; + for (literal lit : m_bits[v]) { + out << " " << lit << ":" << mk_bounded_pp(literal2expr(lit), m, 1); + } } - numeral val; - if (get_fixed_value(v, val)) - out << ", value: " << val; + else if (m.is_bool(e) && (a = m_bool_var2atom.get(expr2literal(e).var(), nullptr))) { + if (a->is_bit()) { + for (var_pos vp : a->to_bit()) + out << " " << var2enode(vp.first)->get_owner_id() << "[" << vp.second << "]"; + } + else + out << "def-atom"; + } + else + out << " " << mk_bounded_pp(e, m, 1); out << "\n"; -#endif return out; } + void solver::new_eq_eh(euf::th_eq const& eq) { + TRACE("bv", tout << "new eq " << eq.m_v1 << " == " << eq.m_v2 << "\n";); + if (is_bv(eq.m_v1)) + m_find.merge(eq.m_v1, eq.m_v2); + } + double solver::get_reward(literal l, sat::ext_constraint_idx idx, sat::literal_occs_fun& occs) const { return 0; } bool solver::is_extended_binary(sat::ext_justification_idx idx, literal_vector& r) { return false; } bool solver::is_external(bool_var v) { return true; } bool solver::propagate(literal l, sat::ext_constraint_idx idx) { return false; } - void solver::get_antecedents(literal l, sat::ext_justification_idx idx, literal_vector& r) {} - void solver::asserted(literal l) {} - sat::check_result solver::check() { return sat::check_result::CR_DONE; } - void solver::push() {} - void solver::pop(unsigned n) {} + + void solver::get_antecedents(literal l, sat::ext_justification_idx idx, literal_vector& r, bool probing) { + auto& c = bv_justification::from_index(idx); + TRACE("bv", display_constraint(tout, idx);); + switch (c.m_kind) { + case bv_justification::kind_t::bv2bit: + r.push_back(c.m_antecedent); + ctx.add_antecedent(var2enode(c.m_v1), var2enode(c.m_v2)); + break; + case bv_justification::kind_t::bit2bv: + SASSERT(m_bits[c.m_v1].size() == m_bits[c.m_v2].size()); + for (unsigned i = m_bits[c.m_v1].size(); i-- > 0; ) { + sat::literal a = m_bits[c.m_v1][i]; + sat::literal b = m_bits[c.m_v2][i]; + SASSERT(a == b || s().value(a) != l_undef); + SASSERT(s().value(a) == s().value(b)); + if (a == b) + continue; + if (s().value(a) == l_false) { + a.neg(); + b.neg(); + } + r.push_back(a); + r.push_back(b); + } + break; + } + if (!probing && ctx.use_drat()) + log_drat(c); + } + + void solver::log_drat(bv_justification const& c) { + // this has a side-effect so changes provability: + expr_ref eq(m.mk_eq(var2expr(c.m_v1), var2expr(c.m_v2)), m); + sat::literal leq = ctx.internalize(eq, false, false, false); + sat::literal_vector lits; + auto add_bit = [&](sat::literal lit) { + if (s().value(lit) == l_true) + lit.neg(); + lits.push_back(lit); + }; + switch (c.m_kind) { + case bv_justification::kind_t::bv2bit: + lits.push_back(~leq); + lits.push_back(~c.m_antecedent); + lits.push_back(c.m_consequent); + break; + case bv_justification::kind_t::bit2bv: + lits.push_back(leq); + for (unsigned i = m_bits[c.m_v1].size(); i-- > 0; ) { + sat::literal a = m_bits[c.m_v1][i]; + sat::literal b = m_bits[c.m_v2][i]; + if (a != b) { + add_bit(a); + add_bit(b); + } + } + break; + } + ctx.get_drat().add(lits, status()); + } + + void solver::asserted(literal l) { + atom* a = get_bv2a(l.var()); + TRACE("bv", tout << "asserted: " << l << "\n";); + if (a->is_bit()) + for (auto vp : a->to_bit()) + m_prop_queue.push_back(vp); + } + + bool solver::unit_propagate() { + if (m_prop_queue_head == m_prop_queue.size()) + return false; + ctx.push(value_trail(m_prop_queue_head)); + for (; m_prop_queue_head < m_prop_queue.size() && !s().inconsistent(); ++m_prop_queue_head) + propagate_bits(m_prop_queue[m_prop_queue_head]); + return true; + } + + void solver::propagate_bits(var_pos entry) { + theory_var v1 = entry.first; + unsigned idx = entry.second; + SASSERT(idx < m_bits[v1].size()); + if (m_wpos[v1] == idx) + find_wpos(v1); + + literal bit1 = m_bits[v1][idx]; + lbool val = s().value(bit1); + TRACE("bv", tout << "propagating v" << v1 << " #" << var2enode(v1)->get_owner_id() << "[" << idx << "] = " << val << "\n";); + if (val == l_undef) + return; + + if (val == l_false) + bit1.neg(); + + for (theory_var v2 = m_find.next(v1); v2 != v1 && !s().inconsistent(); v2 = m_find.next(v2)) { + literal bit2 = m_bits[v2][idx]; + SASSERT(m_bits[v1][idx] != ~m_bits[v2][idx]); + TRACE("bv", tout << "propagating #" << var2enode(v2)->get_owner_id() << "[" << idx << "] = " << s().value(bit2) << "\n";); + + if (val == l_false) + bit2.neg(); + if (l_true != s().value(bit2)) + assign_bit(bit2, v1, v2, idx, bit1, false); + } + } + + sat::check_result solver::check() { + SASSERT(m_prop_queue.size() == m_prop_queue_head); + return sat::check_result::CR_DONE; + } + + void solver::push() { + th_euf_solver::lazy_push(); + m_prop_queue_lim.push_back(m_prop_queue.size()); + } + + void solver::pop(unsigned n) { + unsigned old_sz = m_prop_queue_lim.size() - n; + m_prop_queue.shrink(m_prop_queue_lim[old_sz]); + m_prop_queue_lim.shrink(old_sz); + n = lazy_pop(n); + if (n > 0) { + old_sz = get_num_vars(); + m_bits.shrink(old_sz); + m_wpos.shrink(old_sz); + m_zero_one_bits.shrink(old_sz); + } + } + void solver::pre_simplify() {} - void solver::simplify() {} + + void solver::simplify() { + m_ackerman.propagate(); + } + + bool solver::set_root(literal l, literal r) { + atom* a = get_bv2a(l.var()); + if (!a || !a->is_bit()) + return true; + for (auto vp : a->to_bit()) { + sat::literal l2 = m_bits[vp.first][vp.second]; + sat::literal r2 = (l.sign() == l2.sign()) ? r : ~r; + SASSERT(l2.var() == l.var()); + ctx.push(bit_trail(*this, vp)); + m_bits[vp.first][vp.second] = r2; + set_bit_eh(vp.first, r2, vp.second); + } + return true; + } + + /** + * Instantiate Ackerman axioms for bit-vectors that have become equal after roots have been added. + */ + void solver::flush_roots() { + struct eq { + solver& s; + eq(solver& s) :s(s) {} + bool operator()(theory_var v1, theory_var v2) const { + return s.m_bits[v1] == s.m_bits[v2]; + } + }; + struct hash { + solver& s; + hash(solver& s) :s(s) {} + bool operator()(theory_var v) const { + literal_vector const& a = s.m_bits[v]; + return string_hash(reinterpret_cast(a.c_ptr()), a.size() * sizeof(sat::literal), 3); + } + }; + eq eq_proc(*this); + hash hash_proc(*this); + map table(hash_proc, eq_proc); + for (unsigned v = 0; v < get_num_vars(); ++v) { + if (!m_bits[v].empty()) { + theory_var w = table.insert_if_not_there(v, v); + if (v != w && m_find.find(v) != m_find.find(w)) + assert_ackerman(v, w); + } + } + TRACE("bv", tout << "infer new equations for bit-vectors that are now equal\n";); + } + void solver::clauses_modifed() {} lbool solver::get_phase(bool_var v) { return l_undef; } - std::ostream& solver::display(std::ostream& out) const { return out; } - std::ostream& solver::display_justification(std::ostream& out, sat::ext_justification_idx idx) const { return out; } - std::ostream& solver::display_constraint(std::ostream& out, sat::ext_constraint_idx idx) const { return out; } - void solver::collect_statistics(statistics& st) const {} - sat::extension* solver::copy(sat::solver* s) { return nullptr; } + std::ostream& solver::display(std::ostream& out) const { + unsigned num_vars = get_num_vars(); + if (num_vars > 0) + out << "bv-solver:\n"; + for (unsigned v = 0; v < num_vars; v++) + out << pp(v); + return out; + } + + std::ostream& solver::display_justification(std::ostream& out, sat::ext_justification_idx idx) const { + return display_constraint(out, idx); + } + + std::ostream& solver::display_constraint(std::ostream& out, sat::ext_constraint_idx idx) const { + auto& c = bv_justification::from_index(idx); + switch (c.m_kind) { + case bv_justification::kind_t::bv2bit: + return out << c.m_consequent << " <= " << c.m_antecedent << " v" << c.m_v1 << " == v" << c.m_v2 << "\n"; + case bv_justification::kind_t::bit2bv: + return out << m_bits[c.m_v1] << " == " << m_bits[c.m_v2] << " => v" << c.m_v1 << " == v" << c.m_v2 << "\n"; + default: + UNREACHABLE(); + break; + } + return out; + } + + void solver::collect_statistics(statistics& st) const { + st.update("bv conflicts", m_stats.m_num_conflicts); + st.update("bv diseqs", m_stats.m_num_diseq_static); + st.update("bv dynamic diseqs", m_stats.m_num_diseq_dynamic); + st.update("bv bit2core", m_stats.m_num_bit2core); + st.update("bv->core eq", m_stats.m_num_th2core_eq); + st.update("bv ackerman", m_stats.m_ackerman); + } + + sat::extension* solver::copy(sat::solver* s) { UNREACHABLE(); return nullptr; } + + euf::th_solver* solver::fresh(sat::solver* s, euf::solver& ctx) { + bv::solver* result = alloc(bv::solver, ctx, get_id()); + ast_translation tr(m, ctx.get_manager()); + for (unsigned i = 0; i < get_num_vars(); ++i) { + expr* e1 = var2expr(i); + expr* e2 = tr(e1); + euf::enode* n2 = ctx.get_enode(e2); + SASSERT(n2); + result->mk_var(n2); + result->m_bits[i].append(m_bits[i]); + result->m_zero_one_bits[i].append(m_zero_one_bits[i]); + } + for (unsigned i = 0; i < get_num_vars(); ++i) + if (find(i) != i) + result->m_find.merge(i, find(i)); + result->m_prop_queue.append(m_prop_queue); + for (unsigned i = 0; i < m_bool_var2atom.size(); ++i) { + atom* a = m_bool_var2atom[i]; + if (!a) + continue; + + if (a->is_bit()) { + bit_atom* new_a = new (result->get_region()) bit_atom(); + m_bool_var2atom.setx(i, new_a, nullptr); + for (auto vp : a->to_bit()) + new_a->m_occs = new (result->get_region()) var_pos_occ(vp.first, vp.second, new_a->m_occs); + } + else { + def_atom* new_a = new (result->get_region()) def_atom(a->to_def().m_var, a->to_def().m_def); + m_bool_var2atom.setx(i, new_a, nullptr); + } + } + return result; + } + void solver::pop_reinit() {} bool solver::validate() { return true; } void solver::init_use_list(sat::ext_use_list& ul) {} bool solver::is_blocked(literal l, sat::ext_constraint_idx) { return false; } bool solver::check_model(sat::model const& m) const { return true; } - unsigned solver::max_var(unsigned w) const { return 0;} - + unsigned solver::max_var(unsigned w) const { return w; } + + void solver::add_value(euf::enode* n, expr_ref_vector& values) { + SASSERT(bv.is_bv(n->get_owner())); + theory_var v = n->get_th_var(get_id()); + rational val; + unsigned i = 0; + for (auto l : m_bits[v]) { + switch (s().value(l)) { + case l_true: + val += power2(i); + break; + default: + break; + } + ++i; + } + values[n->get_root_id()] = bv.mk_numeral(val, m_bits[v].size()); + } + + trail_stack& solver::get_trail_stack() { + return ctx.get_trail_stack(); + } + + void solver::merge_eh(theory_var r1, theory_var r2, theory_var v1, theory_var v2) { + TRACE("bv", tout << "merging: v" << v1 << " #" << var2enode(v1)->get_owner_id() << " v" << v2 << " #" << var2enode(v2)->get_owner_id() << "\n";); + if (!merge_zero_one_bits(r1, r2)) { + TRACE("bv", tout << "conflict detected\n";); + return; // conflict was detected + } + SASSERT(m_bits[v1].size() == m_bits[v2].size()); + unsigned sz = m_bits[v1].size(); + for (unsigned idx = 0; !s().inconsistent() && idx < sz; idx++) { + literal bit1 = m_bits[v1][idx]; + literal bit2 = m_bits[v2][idx]; + CTRACE("bv", bit1 == ~bit2, tout << pp(v1) << pp(v2) << "idx: " << idx << "\n";); + SASSERT(bit1 != ~bit2); + lbool val1 = s().value(bit1); + lbool val2 = s().value(bit2); + TRACE("bv", tout << "merge v" << v1 << " " << bit1 << ":= " << val1 << " " << bit2 << ":= " << val2 << "\n";); + if (val1 == val2) + continue; + CTRACE("bv", (val1 != l_undef && val2 != l_undef), tout << "inconsistent "; tout << pp(v1) << pp(v2) << "idx: " << idx << "\n";); + if (val1 == l_false) + assign_bit(~bit2, v1, v2, idx, ~bit1, true); + else if (val1 == l_true) + assign_bit(bit2, v1, v2, idx, bit1, true); + else if (val2 == l_false) + assign_bit(~bit1, v2, v1, idx, ~bit2, true); + else if (val2 == l_true) + assign_bit(bit1, v2, v1, idx, bit2, true); + } + } + + sat::justification solver::mk_bv2bit_justification(theory_var v1, theory_var v2, sat::literal c, sat::literal a) { + void* mem = get_region().allocate(bv_justification::get_obj_size()); + sat::constraint_base::initialize(mem, this); + auto* constraint = new (sat::constraint_base::ptr2mem(mem)) bv_justification(v1, v2, c, a); + return sat::justification::mk_ext_justification(s().scope_lvl(), constraint->to_index()); + } + + sat::ext_justification_idx solver::mk_bit2bv_justification(theory_var v1, theory_var v2) { + void* mem = get_region().allocate(bv_justification::get_obj_size()); + sat::constraint_base::initialize(mem, this); + auto* constraint = new (sat::constraint_base::ptr2mem(mem)) bv_justification(v1, v2); + return constraint->to_index(); + } + + void solver::assign_bit(literal consequent, theory_var v1, theory_var v2, unsigned idx, literal antecedent, bool propagate_eqc) { + m_stats.m_num_bit2core++; + SASSERT(ctx.s().value(antecedent) == l_true); + SASSERT(m_bits[v2][idx].var() == consequent.var()); + SASSERT(consequent.var() != antecedent.var()); + s().assign(consequent, mk_bv2bit_justification(v1, v2, consequent, antecedent)); + if (s().value(consequent) == l_false) { + m_stats.m_num_conflicts++; + SASSERT(s().inconsistent()); + } + else { + if (get_config().m_bv_eq_axioms && false) { + // TODO - enable when pop_reinit is available + expr_ref eq(m.mk_eq(var2expr(v1), var2expr(v2)), m); + flet _is_redundant(m_is_redundant, true); + literal eq_lit = ctx.internalize(eq, false, false, m_is_redundant); + add_clause(~antecedent, ~eq_lit, consequent); + add_clause(antecedent, ~eq_lit, ~consequent); + } + + if (m_wpos[v2] == idx) + find_wpos(v2); + bool_var cv = consequent.var(); + atom* a = get_bv2a(cv); + if (a && a->is_bit()) + for (auto curr : a->to_bit()) + if (propagate_eqc || find(curr.first) != find(v2) || curr.second != idx) + m_prop_queue.push_back(curr); + } + } + + void solver::unmerge_eh(theory_var v1, theory_var v2) { + // v1 was the root of the equivalence class + // I must remove the zero_one_bits that are from v2. + zero_one_bits& bits = m_zero_one_bits[v1]; + if (bits.empty()) + return; + for (unsigned j = bits.size(); j-- > 0; ) { + zero_one_bit& bit = bits[j]; + if (find(bit.m_owner) == v1) { + bits.shrink(j + 1); + return; + } + } + bits.shrink(0); + } + + bool solver::merge_zero_one_bits(theory_var r1, theory_var r2) { + zero_one_bits& bits2 = m_zero_one_bits[r2]; + if (bits2.empty()) + return true; + zero_one_bits& bits1 = m_zero_one_bits[r1]; + unsigned bv_size = get_bv_size(r1); + SASSERT(bv_size == get_bv_size(r2)); + m_merge_aux[0].reserve(bv_size + 1, euf::null_theory_var); + m_merge_aux[1].reserve(bv_size + 1, euf::null_theory_var); + + struct scoped_reset { + solver& s; + zero_one_bits& bits1; + scoped_reset(solver& s, zero_one_bits& bits1) :s(s), bits1(bits1) {} + ~scoped_reset() { + for (auto& zo : bits1) + s.m_merge_aux[zo.m_is_true][zo.m_idx] = euf::null_theory_var; + } + }; + scoped_reset _sr(*this, bits1); + + DEBUG_CODE(for (unsigned i = 0; i < bv_size; i++) SASSERT(m_merge_aux[0][i] == euf::null_theory_var || m_merge_aux[1][i] == euf::null_theory_var);); + + // save info about bits1 + for (auto& zo : bits1) + m_merge_aux[zo.m_is_true][zo.m_idx] = zo.m_owner; + // check if bits2 is consistent with bits1, and copy new bits to bits1 + for (auto& zo : bits2) { + theory_var v2 = zo.m_owner; + theory_var v1 = m_merge_aux[!zo.m_is_true][zo.m_idx]; + if (v1 != euf::null_theory_var) { + // conflict was detected ... v1 and v2 have complementary bits + SASSERT(m_bits[v1][zo.m_idx] == ~(m_bits[v2][zo.m_idx])); + SASSERT(m_bits[v1].size() == m_bits[v2].size()); + mk_new_diseq_axiom(v1, v2, zo.m_idx); + return false; + } + // copy missing variable to bits1 + if (m_merge_aux[zo.m_is_true][zo.m_idx] == euf::null_theory_var) + bits1.push_back(zo); + } + // reset m_merge_aux vector + DEBUG_CODE(for (unsigned i = 0; i < bv_size; i++) { SASSERT(m_merge_aux[0][i] == euf::null_theory_var || m_merge_aux[1][i] == euf::null_theory_var); }); + return true; + } + + rational const& solver::power2(unsigned i) const { + while (m_power2.size() <= i) + m_power2.push_back(m_bb.power(m_power2.size())); + return m_power2[i]; + } + + /** + \brief Check whether m_zero_one_bits is an accurate summary of the bits in the + equivalence class rooted by v. + \remark The method does nothing if v is not the root of the equivalence class. + */ + bool solver::check_zero_one_bits(theory_var v) { + if (s().inconsistent()) + return true; // property is only valid if the context is not in a conflict. + if (!is_root(v) || !is_bv(v)) + return true; + bool_vector bits[2]; + unsigned num_bits = 0; + unsigned bv_sz = get_bv_size(v); + bits[0].resize(bv_sz, false); + bits[1].resize(bv_sz, false); + + theory_var curr = v; + do { + literal_vector const& lits = m_bits[curr]; + for (unsigned i = 0; i < lits.size(); i++) { + literal l = lits[i]; + if (s().value(l) != l_undef) { + unsigned is_true = s().value(l) == l_true; + if (bits[!is_true][i]) { + // expect a conflict later on. + return true; + } + if (!bits[is_true][i]) { + bits[is_true][i] = true; + num_bits++; + } + } + } + curr = m_find.next(curr); + } while (curr != v); + + zero_one_bits const& _bits = m_zero_one_bits[v]; + SASSERT(_bits.size() == num_bits); + bool_vector already_found; + already_found.resize(bv_sz, false); + for (auto& zo : _bits) { + SASSERT(find(zo.m_owner) == v); + SASSERT(bits[zo.m_is_true][zo.m_idx]); + SASSERT(!already_found[zo.m_idx]); + already_found[zo.m_idx] = true; + } + return true; + } } diff --git a/src/sat/smt/bv_solver.h b/src/sat/smt/bv_solver.h index 3c2df99fe90..179d96ba402 100644 --- a/src/sat/smt/bv_solver.h +++ b/src/sat/smt/bv_solver.h @@ -17,8 +17,13 @@ Module Name: #pragma once #include "sat/smt/sat_th.h" +#include "sat/smt/bv_ackerman.h" #include "ast/rewriter/bit_blaster/bit_blaster.h" +namespace euf { + class solver; +} + namespace bv { class solver : public euf::th_euf_solver { @@ -32,15 +37,42 @@ namespace bv { typedef std::pair value_sort_pair; typedef pair_hash, unsigned_hash> value_sort_pair_hash; typedef map > value2var; - typedef union_find th_union_find; + typedef union_find bv_union_find; + typedef std::pair var_pos; + + friend class ackerman; struct stats { unsigned m_num_diseq_static, m_num_diseq_dynamic, m_num_bit2core, m_num_th2core_eq, m_num_conflicts; - unsigned m_num_eq_dynamic; + unsigned m_ackerman; void reset() { memset(this, 0, sizeof(stats)); } stats() { reset(); } }; + struct bv_justification { + enum kind_t { bv2bit, bit2bv }; + kind_t m_kind; + theory_var m_v1; + theory_var m_v2; + sat::literal m_consequent; + sat::literal m_antecedent; + bv_justification(theory_var v1, theory_var v2, sat::literal c, sat::literal a) : + m_kind(kind_t::bv2bit), m_v1(v1), m_v2(v2), m_consequent(c), m_antecedent(a) {} + bv_justification(theory_var v1, theory_var v2): + m_kind(kind_t::bit2bv), m_v1(v1), m_v2(v2) {} + sat::ext_constraint_idx to_index() const { + return sat::constraint_base::mem2base(this); + } + static bv_justification& from_index(size_t idx) { + return *reinterpret_cast(sat::constraint_base::from_index(idx)->mem()); + } + static size_t get_obj_size() { return sat::constraint_base::obj_size(sizeof(bv_justification)); } + }; + + sat::justification mk_bv2bit_justification(theory_var v1, theory_var v2, sat::literal c, sat::literal a); + sat::ext_justification_idx mk_bit2bv_justification(theory_var v1, theory_var v2); + void log_drat(bv_justification const& c); + /** \brief Structure used to store the position of a bitvector variable that @@ -66,17 +98,31 @@ namespace bv { typedef svector zero_one_bits; + struct bit_atom; + struct def_atom; class atom { public: virtual ~atom() {} virtual bool is_bit() const = 0; + bit_atom& to_bit(); + def_atom& to_def(); }; struct var_pos_occ { - theory_var m_var; - unsigned m_idx; + var_pos m_vp; var_pos_occ * m_next; - var_pos_occ(theory_var v = euf::null_theory_var, unsigned idx = 0, var_pos_occ * next = nullptr):m_var(v), m_idx(idx), m_next(next) {} + var_pos_occ(theory_var v = euf::null_theory_var, unsigned idx = 0, var_pos_occ * next = nullptr):m_vp(v, idx), m_next(next) {} + }; + + class var_pos_it { + var_pos_occ* m_first; + public: + var_pos_it(var_pos_occ* c) : m_first(c) {} + var_pos operator*() { return m_first->m_vp; } + var_pos_it& operator++() { m_first = m_first->m_next; return *this; } + var_pos_it operator++(int) { var_pos_it tmp = *this; ++* this; return tmp; } + bool operator==(var_pos_it const& other) const { return m_first == other.m_first; } + bool operator!=(var_pos_it const& other) const { return !(*this == other); } }; struct bit_atom : public atom { @@ -84,44 +130,51 @@ namespace bv { bit_atom():m_occs(nullptr) {} ~bit_atom() override {} bool is_bit() const override { return true; } + var_pos_it begin() const { return var_pos_it(m_occs); } + var_pos_it end() const { return var_pos_it(nullptr); } }; - struct le_atom : public atom { + struct def_atom : public atom { literal m_var; literal m_def; - le_atom(literal v, literal d):m_var(v), m_def(d) {} - ~le_atom() override {} + def_atom(literal v, literal d):m_var(v), m_def(d) {} + ~def_atom() override {} bool is_bit() const override { return false; } }; - friend class add_var_pos_trail; - friend class mk_atom_trail; + class bit_trail; + class add_var_pos_trail; + class mk_atom_trail; typedef ptr_vector bool_var2atom; bv_util bv; arith_util m_autil; stats m_stats; + ackerman m_ackerman; bit_blaster m_bb; - th_union_find m_find; + bv_union_find m_find; vector m_bits; // per var, the bits of a given variable. - ptr_vector m_bits_expr; - svector m_wpos; // per var, watch position for fixed variable detection. + unsigned_vector m_wpos; // per var, watch position for fixed variable detection. vector m_zero_one_bits; // per var, see comment in the struct zero_one_bit bool_var2atom m_bool_var2atom; + value2var m_fixed_var_table; + mutable vector m_power2; + literal_vector m_tmp_literals; + svector m_prop_queue; + unsigned_vector m_prop_queue_lim; + unsigned m_prop_queue_head { 0 }; + + sat::solver* m_solver; sat::solver& s() { return *m_solver; } - // internalize: - + // internalize void insert_bv2a(bool_var bv, atom * a) { m_bool_var2atom.setx(bv, a, 0); } void erase_bv2a(bool_var bv) { m_bool_var2atom[bv] = 0; } atom * get_bv2a(bool_var bv) const { return m_bool_var2atom.get(bv, 0); } - - sat::literal false_literal; - sat::literal true_literal; bool visit(expr* e) override; bool visited(expr* e) override; - bool post_visit(expr* e, bool sign, bool root) override { return true; } + bool post_visit(expr* e, bool sign, bool root) override; unsigned get_bv_size(euf::enode* n); unsigned get_bv_size(theory_var v); theory_var get_var(euf::enode* n); @@ -129,66 +182,55 @@ namespace bv { inline theory_var get_arg_var(euf::enode* n, unsigned idx); void get_bits(theory_var v, expr_ref_vector& r); void get_bits(euf::enode* n, expr_ref_vector& r); - void get_arg_bits(euf::enode* n, unsigned idx, expr_ref_vector& r); void get_arg_bits(app* n, unsigned idx, expr_ref_vector& r); - euf::enode* mk_enode(expr* n, ptr_vector const& args); void fixed_var_eh(theory_var v); - + bool is_bv(theory_var v) const { return bv.is_bv(var2expr(v)); } sat::status status() const { return sat::status::th(m_is_redundant, get_id()); } - void add_unit(sat::literal lit); void register_true_false_bit(theory_var v, unsigned i); void add_bit(theory_var v, sat::literal lit); - void init_bits(euf::enode * n, expr_ref_vector const & bits); + void set_bit_eh(theory_var v, literal l, unsigned idx); + void init_bits(expr* e, expr_ref_vector const & bits); void mk_bits(theory_var v); - expr_ref mk_bit2bool(expr* b, unsigned idx); - void internalize_num(app * n, theory_var v); - void internalize_add(app * n); - void internalize_sub(app * n); - void internalize_mul(app * n); - void internalize_udiv(app * n); - void internalize_sdiv(app * n); - void internalize_urem(app * n); - void internalize_srem(app * n); - void internalize_smod(app * n); - void internalize_shl(app * n); - void internalize_lshr(app * n); - void internalize_ashr(app * n); - void internalize_ext_rotate_left(app * n); - void internalize_ext_rotate_right(app * n); - void internalize_and(app * n); - void internalize_or(app * n); - void internalize_not(app * n); - void internalize_nand(app * n); - void internalize_nor(app * n); - void internalize_xor(app * n); - void internalize_xnor(app * n); - void internalize_concat(app * n); - void internalize_sign_extend(app * n); - void internalize_zero_extend(app * n); - void internalize_extract(app * n); - void internalize_redand(app * n); - void internalize_redor(app * n); - void internalize_comp(app * n); - void internalize_rotate_left(app * n); - void internalize_rotate_right(app * n); + void add_def(sat::literal def, sat::literal l); + void internalize_unary(app* n, std::function& fn); + void internalize_binary(app* n, std::function& fn); + void internalize_ac_binary(app* n, std::function& fn); + void internalize_par_unary(app* n, std::function& fn); + void internalize_novfl(app* n, std::function& fn); + void internalize_num(app * n, theory_var v); + void internalize_concat(app * n); void internalize_bv2int(app* n); void internalize_int2bv(app* n); void internalize_mkbv(app* n); - void internalize_umul_no_overflow(app *n); - void internalize_smul_no_overflow(app *n); - void internalize_smul_no_underflow(app *n); - + void internalize_xor3(app* n); + void internalize_carry(app* n); + void internalize_sub(app* n); + void internalize_extract(app* n); + void internalize_bit2bool(app* n); + template + void internalize_le(app* n); void assert_bv2int_axiom(app * n); void assert_int2bv_axiom(app* n); + void assert_ackerman(theory_var v1, theory_var v2); // solving + theory_var find(theory_var v) const { return m_find.find(v); } void find_wpos(theory_var v); - void find_new_diseq_axioms(var_pos_occ* occs, theory_var v, unsigned idx); + void find_new_diseq_axioms(bit_atom& a, theory_var v, unsigned idx); void mk_new_diseq_axiom(theory_var v1, theory_var v2, unsigned idx); - - + bool get_fixed_value(theory_var v, numeral& result) const; + void add_fixed_eq(theory_var v1, theory_var v2); + svector m_merge_aux[2]; //!< auxiliary vector used in merge_zero_one_bits + bool merge_zero_one_bits(theory_var r1, theory_var r2); + void assign_bit(literal consequent, theory_var v1, theory_var v2, unsigned idx, literal antecedent, bool propagate_eqc); + void propagate_bits(var_pos entry); + numeral const& power2(unsigned i) const; + + // invariants + bool check_zero_one_bits(theory_var v); + public: - solver(euf::solver& ctx); + solver(euf::solver& ctx, theory_id id); ~solver() override {} void set_solver(sat::solver* s) override { m_solver = s; } void set_lookahead(sat::lookahead* s) override { } @@ -197,19 +239,22 @@ namespace bv { bool is_extended_binary(sat::ext_justification_idx idx, literal_vector& r) override; bool is_external(bool_var v) override; bool propagate(literal l, sat::ext_constraint_idx idx) override; - void get_antecedents(literal l, sat::ext_justification_idx idx, literal_vector & r) override; + void get_antecedents(literal l, sat::ext_justification_idx idx, literal_vector & r, bool probing) override; void asserted(literal l) override; sat::check_result check() override; void push() override; void pop(unsigned n) override; void pre_simplify() override; void simplify() override; + bool set_root(literal l, literal r) override; + void flush_roots() override; void clauses_modifed() override; lbool get_phase(bool_var v) override; std::ostream& display(std::ostream& out) const override; std::ostream& display_justification(std::ostream& out, sat::ext_justification_idx idx) const override; std::ostream& display_constraint(std::ostream& out, sat::ext_constraint_idx idx) const override; void collect_statistics(statistics& st) const override; + euf::th_solver* fresh(sat::solver* s, euf::solver& ctx) override; extension* copy(sat::solver* s) override; void find_mutexes(literal_vector& lits, vector & mutexes) override {} void gc() override {} @@ -220,18 +265,31 @@ namespace bv { bool check_model(sat::model const& m) const override; unsigned max_var(unsigned w) const override; + void new_eq_eh(euf::th_eq const& eq) override; + bool unit_propagate() override; + + void add_value(euf::enode* n, expr_ref_vector& values) override; + bool extract_pb(std::function& card, std::function& pb) override { return false; } bool to_formulas(std::function& l2e, expr_ref_vector& fmls) override { return false; } sat::literal internalize(expr* e, bool sign, bool root, bool learned) override; + void internalize(expr* e, bool redundant) override; euf::theory_var mk_var(euf::enode* n) override; + void apply_sort_cnstr(euf::enode * n, sort * s) override; + + void merge_eh(theory_var, theory_var, theory_var v1, theory_var v2); + void after_merge_eh(theory_var r1, theory_var r2, theory_var v1, theory_var v2) { SASSERT(check_zero_one_bits(r1)); } + void unmerge_eh(theory_var v1, theory_var v2); + trail_stack& get_trail_stack(); // disagnostics - std::ostream& display(std::ostream& out, theory_var v) const; + std::ostream& display(std::ostream& out, theory_var v) const; typedef std::pair pp_var; pp_var pp(theory_var v) const { return pp_var(this, v); } + }; inline std::ostream& operator<<(std::ostream& out, solver::pp_var const& p) { return p.first->display(out, p.second); } diff --git a/src/sat/smt/euf_ackerman.cpp b/src/sat/smt/euf_ackerman.cpp index 77d5866554d..a551db4de21 100644 --- a/src/sat/smt/euf_ackerman.cpp +++ b/src/sat/smt/euf_ackerman.cpp @@ -58,37 +58,10 @@ namespace euf { inf.b = b; inf.c = nullptr; inf.is_cc = true; + inf.m_count = 0; insert(); } - void ackerman::remove_from_queue(inference* inf) { - if (m_queue->m_next == m_queue) { - SASSERT(inf == m_queue); - m_queue = nullptr; - return; - } - if (m_queue == inf) - m_queue = inf->m_next; - auto* next = inf->m_next; - auto* prev = inf->m_prev; - prev->m_next = next; - next->m_prev = prev; - } - - void ackerman::push_to_front(inference* inf) { - if (!m_queue) { - m_queue = inf; - } - else if (m_queue != inf) { - auto* next = inf->m_next; - auto* prev = inf->m_prev; - prev->m_next = next; - next->m_prev = prev; - inf->m_prev = m_queue->m_prev; - inf->m_next = m_queue; - m_queue->m_prev = inf; - } - } void ackerman::insert() { inference* inf = m_tmp_inference; @@ -97,15 +70,16 @@ namespace euf { m.inc_ref(inf->a); m.inc_ref(inf->b); m.inc_ref(inf->c); - } - else new_tmp(); + } other->m_count++; - push_to_front(other); + if (other->m_count > m_high_watermark) + s.s().set_should_simplify(); + inference::push_to_front(m_queue, other); } void ackerman::remove(inference* inf) { - remove_from_queue(inf); + inference::remove_from(m_queue, inf); m_table.erase(inf); m.dec_ref(inf->a); m.dec_ref(inf->b); @@ -115,13 +89,10 @@ namespace euf { void ackerman::new_tmp() { m_tmp_inference = alloc(inference); - m_tmp_inference->m_next = m_tmp_inference->m_prev = m_tmp_inference; - m_tmp_inference->m_count = 0; + m_tmp_inference->init(m_tmp_inference); } void ackerman::cg_conflict_eh(expr * n1, expr * n2) { - if (s.m_config.m_dack != DACK_ROOT) - return; if (!is_app(n1) || !is_app(n2)) return; app* a = to_app(n1); @@ -133,8 +104,6 @@ namespace euf { } void ackerman::used_eq_eh(expr* a, expr* b, expr* c) { - if (!s.m_config.m_dack_eq) - return; if (a == b || a == c || b == c) return; insert(a, b, c); @@ -142,8 +111,6 @@ namespace euf { } void ackerman::used_cc_eh(app* a, app* b) { - if (s.m_config.m_dack != DACK_CR) - return; SASSERT(a->get_decl() == b->get_decl()); SASSERT(a->get_num_args() == b->get_num_args()); insert(a, b); @@ -157,8 +124,8 @@ namespace euf { m_num_propagations_since_last_gc = 0; while (m_table.size() > m_gc_threshold) - remove(m_queue->m_prev); - + remove(m_queue->prev()); + m_gc_threshold *= 110; m_gc_threshold /= 100; m_gc_threshold++; @@ -171,13 +138,16 @@ namespace euf { unsigned num_prop = static_cast(s.s().get_stats().m_conflict * s.m_config.m_dack_factor); num_prop = std::min(num_prop, m_table.size()); for (unsigned i = 0; i < num_prop; ++i, n = k) { - k = n->m_next; + k = n->next(); if (n->m_count < s.m_config.m_dack_threshold) continue; + if (n->m_count >= m_high_watermark && num_prop < m_table.size()) + ++num_prop; if (n->is_cc) add_cc(n->a, n->b); else - add_eq(n->a, n->b, n->c); + add_eq(n->a, n->b, n->c); + ++s.m_stats.m_ackerman; remove(n); } } diff --git a/src/sat/smt/euf_ackerman.h b/src/sat/smt/euf_ackerman.h index aa92d88ecee..d794d027632 100644 --- a/src/sat/smt/euf_ackerman.h +++ b/src/sat/smt/euf_ackerman.h @@ -16,6 +16,7 @@ Module Name: --*/ #pragma once +#include "util/dlist.h" #include "ast/euf/euf_egraph.h" #include "sat/smt/atom2bool_var.h" #include "sat/smt/sat_th.h" @@ -24,13 +25,12 @@ namespace euf { class solver; + class ackerman { - struct inference { + struct inference : dll_base{ bool is_cc; expr* a, *b, *c; - inference* m_next{ nullptr }; - inference* m_prev{ nullptr }; unsigned m_count{ 0 }; inference():is_cc(false), a(nullptr), b(nullptr), c(nullptr) {} inference(app* a, app* b):is_cc(true), a(a), b(b), c(nullptr) {} @@ -58,6 +58,7 @@ namespace euf { inference* m_queue { nullptr }; inference* m_tmp_inference { nullptr }; unsigned m_gc_threshold { 1 }; + unsigned m_high_watermark { 1000 }; unsigned m_num_propagations_since_last_gc { 0 }; void reset(); @@ -69,8 +70,6 @@ namespace euf { void add_cc(expr* a, expr* b); void add_eq(expr* a, expr* b, expr* c); void gc(); - void push_to_front(inference* inf); - void remove_from_queue(inference* inf); public: ackerman(solver& s, ast_manager& m); diff --git a/src/sat/smt/euf_internalize.cpp b/src/sat/smt/euf_internalize.cpp index 30d8abc9aad..0898f8847d6 100644 --- a/src/sat/smt/euf_internalize.cpp +++ b/src/sat/smt/euf_internalize.cpp @@ -21,17 +21,27 @@ Module Name: namespace euf { - sat::literal solver::internalize(expr* e, bool sign, bool root, bool redundant) { + void solver::internalize(expr* e, bool redundant) { if (si.is_bool_op(e)) - return si.internalize(e, redundant); - auto* ext = get_solver(e); - if (ext) - return ext->internalize(e, sign, root, redundant); + attach_lit(si.internalize(e, redundant), e); + else if (auto* ext = get_solver(e)) + ext->internalize(e, redundant); + else + visit_rec(m, e, false, false, redundant); + SASSERT(m_egraph.find(e)); + } + sat::literal solver::internalize(expr* e, bool sign, bool root, bool redundant) { + if (si.is_bool_op(e)) + return attach_lit(si.internalize(e, redundant), e); + if (auto* ext = get_solver(e)) + return ext->internalize(e, sign, root, redundant); if (!visit_rec(m, e, sign, root, redundant)) return sat::null_literal; SASSERT(m_egraph.find(e)); - return literal(m_expr2var.to_bool_var(e), sign); + if (m.is_bool(e)) + return literal(si.to_bool_var(e), sign); + return sat::null_literal; } bool solver::visit(expr* e) { @@ -39,15 +49,7 @@ namespace euf { if (n) return true; if (si.is_bool_op(e)) { - sat::literal lit = si.internalize(e, m_is_redundant); - n = m_var2node.get(lit.var(), nullptr); - if (n && !lit.sign()) - return n; - - n = m_egraph.mk(e, 0, nullptr); - attach_lit(lit, n); - if (!m.is_true(e) && !m.is_false(e)) - s().set_external(lit.var()); + attach_lit(si.internalize(e, m_is_redundant), e); return true; } if (is_app(e) && to_app(e)->get_num_args() > 0) { @@ -64,8 +66,12 @@ namespace euf { m_args.reset(); for (unsigned i = 0; i < num; ++i) m_args.push_back(m_egraph.find(to_app(e)->get_arg(i))); - if (root && internalize_root(to_app(e), sign)) + if (root && internalize_root(to_app(e), sign, m_args)) return false; + if (auto* s = get_solver(e)) { + s->internalize(e, m_is_redundant); + return true; + } enode* n = m_egraph.mk(e, num, m_args.c_ptr()); attach_node(n); return true; @@ -77,33 +83,45 @@ namespace euf { void solver::attach_node(euf::enode* n) { expr* e = n->get_owner(); - log_node(n); - if (m.is_bool(e)) { - sat::bool_var v = si.add_bool_var(e); - log_bool_var(v, n); - attach_lit(literal(v, false), n); + if (!m.is_bool(e)) + log_node(e); + else + attach_lit(literal(si.add_bool_var(e), false), e); + + if (!m.is_bool(e) && m.get_sort(e)->get_family_id() != null_family_id) { + auto* e_ext = get_solver(e); + auto* s_ext = get_solver(m.get_sort(e)); + if (s_ext && s_ext != e_ext) + s_ext->apply_sort_cnstr(n, m.get_sort(e)); } axiomatize_basic(n); } - void solver::attach_lit(literal lit, euf::enode* n) { + sat::literal solver::attach_lit(literal lit, expr* e) { if (lit.sign()) { - sat::bool_var v = si.add_bool_var(n->get_owner()); + sat::bool_var v = si.add_bool_var(e); + s().set_external(v); sat::literal lit2 = literal(v, false); - s().mk_clause(~lit, lit2, sat::status::th(false, m.get_basic_family_id())); - s().mk_clause(lit, ~lit2, sat::status::th(false, m.get_basic_family_id())); + s().mk_clause(~lit, lit2, sat::status::asserted()); + s().mk_clause(lit, ~lit2, sat::status::asserted()); lit = lit2; } sat::bool_var v = lit.var(); - m_var2node.reserve(v + 1, nullptr); - SASSERT(m_var2node[v] == nullptr); - m_var2node[v] = n; + m_var2expr.reserve(v + 1, nullptr); + SASSERT(m_var2expr[v] == nullptr); + m_var2expr[v] = e; m_var_trail.push_back(v); + s().set_external(v); + if (!m_egraph.find(e)) { + enode* n = m_egraph.mk(e, 0, nullptr); + m_egraph.set_merge_enabled(n, false); + } + return lit; } - bool solver::internalize_root(app* e, bool sign) { + bool solver::internalize_root(app* e, bool sign, enode_vector const& args) { if (m.is_distinct(e)) { - enode_vector _args(m_args); + enode_vector _args(args); if (sign) add_not_distinct_axiom(e, _args.c_ptr()); else @@ -202,7 +220,7 @@ namespace euf { expr* c = a->get_arg(0); expr* th = a->get_arg(1); expr* el = a->get_arg(2); - sat::bool_var v = m_expr2var.to_bool_var(c); + sat::bool_var v = si.to_bool_var(c); SASSERT(v != sat::null_bool_var); SASSERT(!m.is_bool(e)); expr_ref eq_th(m.mk_eq(a, th), m); @@ -224,7 +242,7 @@ namespace euf { } } expr_ref fml(m.mk_or(eqs), m); - sat::literal dist(m_expr2var.to_bool_var(e), false); + sat::literal dist(si.to_bool_var(e), false); sat::literal some_eq = si.internalize(fml, m_is_redundant); sat::literal lits1[2] = { ~dist, ~some_eq }; sat::literal lits2[2] = { dist, some_eq }; diff --git a/src/sat/smt/euf_invariant.cpp b/src/sat/smt/euf_invariant.cpp new file mode 100644 index 00000000000..bb5e0c68837 --- /dev/null +++ b/src/sat/smt/euf_invariant.cpp @@ -0,0 +1,48 @@ +/*++ +Copyright (c) 2020 Microsoft Corporation + +Module Name: + + euf_invariant.cpp + +Abstract: + + Check invariants for EUF solver. + +Author: + + Nikolaj Bjorner (nbjorner) 2020-09-07 + +--*/ + +#include "sat/smt/euf_solver.h" + +namespace euf { + + /** + \brief Check if expressions attached to bool_variables and enodes have a consistent assignment. + For all a, b. root(a) == root(b) ==> get_assignment(a) == get_assignment(b) + */ + + void solver::check_eqc_bool_assignment() const { + for (enode* n : m_egraph.nodes()) { + VERIFY(!m.is_bool(n->get_owner()) || + s().value(get_literal(n)) == s().value(get_literal(n->get_root()))); + } + } + + void solver::check_missing_bool_enode_propagation() const { + for (enode* n : m_egraph.nodes()) + if (m.is_bool(n->get_owner()) && l_undef == s().value(get_literal(n))) { + if (!n->is_root()) { + VERIFY(l_undef == s().value(get_literal(n->get_root()))); + } + else + for (enode* o : enode_class(n)) { + VERIFY(l_undef == s().value(get_literal(o))); + } + } + } + + +} diff --git a/src/sat/smt/euf_model.cpp b/src/sat/smt/euf_model.cpp index a56d8f4cbeb..26f6106d958 100644 --- a/src/sat/smt/euf_model.cpp +++ b/src/sat/smt/euf_model.cpp @@ -16,6 +16,7 @@ Module Name: --*/ #include "ast/ast_pp.h" +#include "ast/ast_ll_pp.h" #include "sat/smt/euf_solver.h" #include "model/value_factory.h" @@ -79,7 +80,7 @@ namespace euf { } if (is_app(e) && to_app(e)->get_family_id() == m.get_basic_family_id()) continue; - sat::bool_var v = m_expr2var.to_bool_var(e); + sat::bool_var v = si.to_bool_var(e); SASSERT(v != sat::null_bool_var); switch (s().value(v)) { case l_true: @@ -100,6 +101,8 @@ namespace euf { expr* v = user_sort.get_fresh_value(m.get_sort(e)); values.set(id, v); } + else if ((mb = get_solver(m.get_sort(e)))) + mb->add_value(n, values); else { IF_VERBOSE(1, verbose_stream() << "no model values created for " << mk_pp(e, m) << "\n"); } @@ -119,6 +122,9 @@ namespace euf { if (m.is_bool(e) && is_uninterp_const(e) && mdl->get_const_interp(f)) continue; expr* v = values.get(n->get_root_id()); + CTRACE("euf", !v, tout << "no value for " << mk_pp(e, m) << "\n";); + if (!v) + continue; unsigned arity = f->get_arity(); if (arity == 0) mdl->register_decl(f, v); @@ -129,8 +135,11 @@ namespace euf { mdl->register_decl(f, fi); } args.reset(); - for (enode* arg : enode_args(n)) + for (enode* arg : enode_args(n)) { args.push_back(values.get(arg->get_root_id())); + SASSERT(args.back()); + } + SASSERT(args.size() == arity); if (!fi->get_entry(args.c_ptr())) fi->insert_new_entry(args.c_ptr(), v); } diff --git a/src/sat/smt/euf_proof.cpp b/src/sat/smt/euf_proof.cpp index 930da2c3625..f591161d7e6 100644 --- a/src/sat/smt/euf_proof.cpp +++ b/src/sat/smt/euf_proof.cpp @@ -19,53 +19,60 @@ Module Name: namespace euf { - void solver::log_node(enode* n) { - if (m_drat) { - expr* e = n->get_owner(); - if (is_app(e)) { - symbol const& name = to_app(e)->get_name(); - s().get_drat().def_begin(n->get_owner_id(), name); - for (enode* arg : enode_args(n)) { - s().get_drat().def_add_arg(arg->get_owner_id()); - } - s().get_drat().def_end(); - } - else { - IF_VERBOSE(0, verbose_stream() << "logging binders is TBD\n"); - } - } + void solver::init_drat() { + if (!m_drat_initialized) + get_drat().add_theory(m.get_basic_family_id(), symbol("euf")); + m_drat_initialized = true; } - void solver::log_bool_var(sat::bool_var v, enode* n) { - if (m_drat) { - s().get_drat().bool_def(v, n->get_owner_id()); + void solver::log_node(expr* e) { + if (!use_drat()) + return; + if (is_app(e)) { + std::stringstream strm; + strm << mk_ismt2_func(to_app(e)->get_decl(), m); + get_drat().def_begin(e->get_id(), strm.str()); + for (expr* arg : *to_app(e)) + get_drat().def_add_arg(arg->get_id()); + get_drat().def_end(); + } + else { + IF_VERBOSE(0, verbose_stream() << "logging binders is TBD\n"); } } + /** + * \brief logs antecedents to a proof trail. + * + * NB with theories, this is not a pure EUF justification, + * It is true modulo EUF and previously logged certificates + * so it isn't necessarily an axiom over EUF, + * We will here leave it to the EUF checker to perform resolution steps. + */ void solver::log_antecedents(literal l, literal_vector const& r) { TRACE("euf", log_antecedents(tout, l, r);); - if (m_drat) { - literal_vector lits; - for (literal lit : r) lits.push_back(~lit); - if (l != sat::null_literal) - lits.push_back(l); - s().get_drat().add(lits, sat::status::th(true, m.get_basic_family_id())); - } + if (!use_drat()) + return; + literal_vector lits; + for (literal lit : r) lits.push_back(~lit); + if (l != sat::null_literal) + lits.push_back(l); + get_drat().add(lits, sat::status::th(true, m.get_basic_family_id())); } void solver::log_antecedents(std::ostream& out, literal l, literal_vector const& r) { for (sat::literal l : r) { - enode* n = m_var2node[l.var()]; + expr* n = m_var2expr[l.var()]; out << ~l << ": "; if (!l.sign()) out << "! "; - out << mk_pp(n->get_owner(), m) << "\n"; + out << mk_pp(n, m) << "\n"; SASSERT(s().value(l) == l_true); } if (l != sat::null_literal) { out << l << ": "; if (l.sign()) out << "! "; - enode* n = m_var2node[l.var()]; - out << mk_pp(n->get_owner(), m) << "\n"; + expr* n = m_var2expr[l.var()]; + out << mk_pp(n, m) << "\n"; } } diff --git a/src/sat/smt/euf_solver.cpp b/src/sat/smt/euf_solver.cpp index 4dfe1f1525a..171f329cafb 100644 --- a/src/sat/smt/euf_solver.cpp +++ b/src/sat/smt/euf_solver.cpp @@ -20,27 +20,25 @@ Module Name: #include "sat/sat_solver.h" #include "sat/smt/sat_smt.h" #include "sat/smt/ba_solver.h" +#include "sat/smt/bv_solver.h" #include "sat/smt/euf_solver.h" namespace euf { void solver::updt_params(params_ref const& p) { m_config.updt_params(p); - m_drat = m_solver && m_solver->get_config().m_drat; - if (m_drat) - m_solver->get_drat().add_theory(m.get_basic_family_id(), symbol("euf")); } /** * retrieve extension that is associated with Boolean variable. */ th_solver* solver::get_solver(sat::bool_var v) { - if (v >= m_var2node.size()) + if (v >= m_var2expr.size()) return nullptr; - euf::enode* n = m_var2node[v]; - if (!n) + expr* e = m_var2expr[v]; + if (!e) return nullptr; - return get_solver(n->get_owner()); + return get_solver(e); } th_solver* solver::get_solver(expr* e) { @@ -49,8 +47,7 @@ namespace euf { return nullptr; } - th_solver* solver::get_solver(func_decl* f) { - family_id fid = f->get_family_id(); + th_solver* solver::get_solver(family_id fid, func_decl* f) { if (fid == null_family_id) return nullptr; auto* ext = m_id2solver.get(fid, nullptr); @@ -59,19 +56,24 @@ namespace euf { if (fid == m.get_basic_family_id()) return nullptr; pb_util pb(m); + bv_util bvu(m); if (pb.get_family_id() == fid) { ext = alloc(sat::ba_solver, *this, fid); - if (m_drat) - m_solver->get_drat().add_theory(fid, symbol("ba")); + if (use_drat()) + s().get_drat().add_theory(fid, symbol("ba")); + } + else if (bvu.get_family_id() == fid) { + ext = alloc(bv::solver, *this, fid); + if (use_drat()) + s().get_drat().add_theory(fid, symbol("bv")); } if (ext) { ext->set_solver(m_solver); ext->push_scopes(s().num_scopes()); add_solver(fid, ext); } - else { + else if (f) unhandled_function(f); - } return ext; } @@ -84,7 +86,7 @@ namespace euf { if (m_unhandled_functions.contains(f)) return; m_unhandled_functions.push_back(f); - m_trail.push_back(new (m_region) push_back_vector(m_unhandled_functions)); + m_trail.push(push_back_vector(m_unhandled_functions)); IF_VERBOSE(0, verbose_stream() << mk_pp(f, m) << " not handled\n"); } @@ -93,7 +95,7 @@ namespace euf { } bool solver::is_external(bool_var v) { - if (nullptr != m_var2node.get(v, nullptr)) + if (nullptr != m_var2expr.get(v, nullptr)) return true; for (auto* s : m_solvers) if (s->is_external(v)) @@ -107,86 +109,125 @@ namespace euf { return ext->propagate(l, idx); } - void solver::get_antecedents(literal l, ext_justification_idx idx, literal_vector& r) { + void solver::get_antecedents(literal l, ext_justification_idx idx, literal_vector& r, bool probing) { + m_egraph.begin_explain(); + m_explain.reset(); auto* ext = sat::constraint_base::to_extension(idx); if (ext == this) - get_antecedents(l, constraint::from_idx(idx), r); + get_antecedents(l, constraint::from_idx(idx), r, probing); else - ext->get_antecedents(l, idx, r); + ext->get_antecedents(l, idx, r, probing); + for (unsigned qhead = 0; qhead < m_explain.size(); ++qhead) { + size_t* e = m_explain[qhead]; + if (is_literal(e)) + r.push_back(get_literal(e)); + else { + size_t idx = get_justification(e); + auto* ext = sat::constraint_base::to_extension(idx); + SASSERT(ext != this); + sat::literal lit = sat::null_literal; + ext->get_antecedents(lit, idx, r, probing); + } + } + m_egraph.end_explain(); + if (!probing) + log_antecedents(l, r); } - void solver::get_antecedents(literal l, constraint& j, literal_vector& r) { - m_explain.reset(); + void solver::add_antecedent(enode* a, enode* b) { + m_egraph.explain_eq(m_explain, a, b); + } + + void solver::propagate(enode* a, enode* b, ext_justification_idx idx) { + m_egraph.merge(a, b, to_ptr(idx)); + } + + + void solver::get_antecedents(literal l, constraint& j, literal_vector& r, bool probing) { + expr* e = nullptr; euf::enode* n = nullptr; - // init_ackerman(); + init_ackerman(); switch (j.kind()) { case constraint::kind_t::conflict: SASSERT(m_egraph.inconsistent()); - m_egraph.explain(m_explain); + m_egraph.explain(m_explain); break; case constraint::kind_t::eq: - n = m_var2node[l.var()]; + e = m_var2expr[l.var()]; + n = m_egraph.find(e); SASSERT(n); SASSERT(m_egraph.is_equality(n)); SASSERT(!l.sign()); - m_egraph.explain_eq(m_explain, n->get_arg(0), n->get_arg(1)); + m_egraph.explain_eq(m_explain, n->get_arg(0), n->get_arg(1)); break; case constraint::kind_t::lit: - n = m_var2node[l.var()]; + e = m_var2expr[l.var()]; + n = m_egraph.find(e); SASSERT(n); SASSERT(m.is_bool(n->get_owner())); - m_egraph.explain_eq(m_explain, n, (l.sign() ? mk_false() : mk_true())); + m_egraph.explain_eq(m_explain, n, (l.sign() ? mk_false() : mk_true())); break; default: IF_VERBOSE(0, verbose_stream() << (unsigned)j.kind() << "\n"); UNREACHABLE(); } - for (unsigned* idx : m_explain) - r.push_back(sat::to_literal((unsigned)(idx - base_ptr()))); - - log_antecedents(l, r); } void solver::asserted(literal l) { - auto* ext = get_solver(l.var()); - if (ext) { - ext->asserted(l); + expr* e = m_var2expr.get(l.var(), nullptr); + if (!e) { return; } - bool sign = l.sign(); - auto n = m_var2node.get(l.var(), nullptr); + bool sign = l.sign(); + TRACE("euf", tout << "asserted: " << l << "@" << s().scope_lvl() << " " << (sign ? "not ": " ") << e->get_id() << "\n";); + euf::enode* n = m_egraph.find(e); if (!n) return; - - expr* e = n->get_owner(); - if (m.is_eq(e) && !sign) { + for (auto th : enode_th_vars(n)) + m_id2solver[th.get_id()]->asserted(l); + if (!n->merge_enabled()) + return; + size_t* c = to_ptr(l); + SASSERT(is_literal(c)); + SASSERT(l == get_literal(c)); + if (m.is_eq(e) && !sign && n->num_args() == 2) { euf::enode* na = n->get_arg(0); euf::enode* nb = n->get_arg(1); - TRACE("euf", tout << mk_pp(e, m) << "\n";); - m_egraph.merge(na, nb, base_ptr() + l.index()); + m_egraph.merge(na, nb, c); } else { euf::enode* nb = sign ? mk_false() : mk_true(); - TRACE("euf", tout << (sign ? "not ": " ") << mk_pp(n->get_owner(), m) << "\n";); - m_egraph.merge(n, nb, base_ptr() + l.index()); + m_egraph.merge(n, nb, c); } - // TBD: delay propagation? - propagate(); } - void solver::propagate() { - while (m_egraph.propagate() && !s().inconsistent()) { + bool solver::unit_propagate() { + bool propagated = false; + while (!s().inconsistent()) { if (m_egraph.inconsistent()) { unsigned lvl = s().scope_lvl(); s().set_conflict(sat::justification::mk_ext_justification(lvl, conflict_constraint().to_index())); - return; + return true; + } + bool propagated1 = false; + if (m_egraph.propagate()) { + propagate_literals(); + propagate_th_eqs(); + propagated1 = true; + } + + for (auto* s : m_solvers) { + if (s->unit_propagate()) + propagated1 = true; } - propagate_literals(); - propagate_th_eqs(); + if (!propagated1) + break; + propagated = true; } + return propagated; } void solver::propagate_literals() { @@ -196,7 +237,7 @@ namespace euf { bool is_eq = p.second; expr* e = n->get_owner(); expr* a = nullptr, *b = nullptr; - bool_var v = m_expr2var.to_bool_var(e); + bool_var v = si.to_bool_var(e); SASSERT(m.is_bool(e)); size_t cnstr; literal lit; @@ -207,7 +248,7 @@ namespace euf { } else { a = e, b = n->get_root()->get_owner(); - SASSERT(m.is_true(a) || m.is_false(b)); + SASSERT(m.is_true(b) || m.is_false(b)); cnstr = lit_constraint().to_index(); lit = literal(v, m.is_false(b)); } @@ -222,7 +263,7 @@ namespace euf { void solver::propagate_th_eqs() { for (; m_egraph.has_th_eq() && !s().inconsistent() && !m_egraph.inconsistent(); m_egraph.next_th_eq()) { th_eq eq = m_egraph.get_th_eq(); - m_id2solver[eq.m_id]->new_eq_eh(eq); + m_id2solver[eq.m_id]->new_eq_eh(eq); } } @@ -246,6 +287,7 @@ namespace euf { } sat::check_result solver::check() { + TRACE("euf", s().display(tout);); bool give_up = false; bool cont = false; for (auto* e : m_solvers) @@ -262,37 +304,52 @@ namespace euf { } void solver::push() { + si.push(); scope s; s.m_var_lim = m_var_trail.size(); - s.m_trail_lim = m_trail.size(); m_scopes.push_back(s); - m_region.push_scope(); + m_trail.push_scope(); for (auto* e : m_solvers) e->push(); m_egraph.push(); } - void solver::force_push() { - for (; m_num_scopes > 0; --m_num_scopes) { - - } - } - void solver::pop(unsigned n) { m_egraph.pop(n); for (auto* e : m_solvers) e->pop(n); - scope const & s = m_scopes[m_scopes.size() - n]; - for (unsigned i = m_var_trail.size(); i-- > s.m_var_lim; ) - m_var2node[m_var_trail[i]] = nullptr; - m_var_trail.shrink(s.m_var_lim); - - undo_trail_stack(*this, m_trail, s.m_trail_lim); - - m_region.pop_scope(n); + m_var2expr[m_var_trail[i]] = nullptr; + m_var_trail.shrink(s.m_var_lim); + m_trail.pop_scope(n); m_scopes.shrink(m_scopes.size() - n); + si.pop(n); + } + + void solver::start_reinit(unsigned n) { + sat::literal_vector lits; + m_reinit_vars.reset(); + m_reinit_exprs.reset(); + s().get_reinit_literals(n, lits); + for (sat::literal lit : lits) { + sat::bool_var v = lit.var(); + expr* e = bool_var2expr(v); + if (m_reinit_vars.contains(v) || !e) + continue; + m_reinit_vars.push_back(v); + m_reinit_exprs.push_back(e); + } + } + + void solver::finish_reinit() { + unsigned sz = m_reinit_vars.size(); + for (unsigned i = 0; i < sz; ++i) { + euf::enode* n = get_enode(m_reinit_exprs.get(i)); + if (n) + continue; + + } } void solver::pre_simplify() { @@ -319,13 +376,33 @@ namespace euf { return l_undef; } + bool solver::set_root(literal l, literal r) { + bool ok = true; + for (auto* s : m_solvers) + if (!s->set_root(l, r)) + ok = false; + expr* e = bool_var2expr(l.var()); + if (e) { + if (m.is_eq(e) && !m.is_iff(e)) + ok = false; + euf::enode* n = get_enode(e); + if (n && n->merge_enabled()) + ok = false; + } + return ok; + } + + void solver::flush_roots() { + for (auto* s : m_solvers) + s->flush_roots(); + } + std::ostream& solver::display(std::ostream& out) const { m_egraph.display(out); - out << "bool-vars\n"; for (unsigned v : m_var_trail) { - euf::enode* n = m_var2node[v]; - out << v << ": " << n->get_owner_id() << " " << mk_bounded_pp(n->get_owner(), m, 1) << "\n"; + expr* e = m_var2expr[v]; + out << v << ": " << e->get_id() << " " << m_solver->value(v) << " " << mk_bounded_pp(e, m, 1) << "\n"; } for (auto* e : m_solvers) e->display(out); @@ -350,13 +427,19 @@ namespace euf { m_egraph.collect_statistics(st); for (auto* e : m_solvers) e->collect_statistics(st); - st.update("euf dynack", m_stats.m_num_dynack); + st.update("euf ackerman", m_stats.m_ackerman); } sat::extension* solver::copy(sat::solver* s) { - auto* r = alloc(solver, *m_to_m, *m_to_expr2var, *m_to_si); + auto* r = alloc(solver, *m_to_m, *m_to_si); r->m_config = m_config; - std::function copy_justification = [&](void* x) { return (void*)(r->base_ptr() + ((unsigned*)x - base_ptr())); }; + sat::literal true_lit = sat::null_literal; + if (s->init_trail_size() > 0) + true_lit = s->trail_literal(0); + std::function copy_justification = [&](void* x) { + SASSERT(true_lit != sat::null_literal); + return (void*)(r->to_ptr(true_lit)); + }; r->m_egraph.copy_from(m_egraph, copy_justification); r->set_solver(s); for (unsigned i = 0; i < m_id2solver.size(); ++i) { @@ -386,6 +469,9 @@ namespace euf { for (auto* e : m_solvers) if (!e->validate()) return false; + check_eqc_bool_assignment(); + check_missing_bool_enode_propagation(); + m_egraph.invariant(); return true; } @@ -411,9 +497,9 @@ namespace euf { unsigned solver::max_var(unsigned w) const { for (auto* e : m_solvers) w = e->max_var(w); - for (unsigned sz = m_var2node.size(); sz-- > 0; ) { - euf::enode* n = m_var2node[sz]; - if (n && m.is_bool(n->get_owner())) { + for (unsigned sz = m_var2expr.size(); sz-- > 0; ) { + expr* n = m_var2expr[sz]; + if (n && m.is_bool(n)) { w = std::max(w, sz); break; } @@ -422,21 +508,15 @@ namespace euf { } double solver::get_reward(literal l, ext_constraint_idx idx, sat::literal_occs_fun& occs) const { - double r = 0; - for (auto* e : m_solvers) { - r = e->get_reward(l, idx, occs); - if (r != 0) - return r; - } - return r; + auto* ext = sat::constraint_base::to_extension(idx); + SASSERT(ext); + return (ext == this) ? 0 : ext->get_reward(l, idx, occs); } bool solver::is_extended_binary(ext_justification_idx idx, literal_vector& r) { - for (auto* e : m_solvers) { - if (e->is_extended_binary(idx, r)) - return true; - } - return false; + auto* ext = sat::constraint_base::to_extension(idx); + SASSERT(ext); + return (ext != this) && ext->is_extended_binary(idx, r); } void solver::init_ackerman() { diff --git a/src/sat/smt/euf_solver.h b/src/sat/smt/euf_solver.h index d7ed6f81d7f..5480b6fada9 100644 --- a/src/sat/smt/euf_solver.h +++ b/src/sat/smt/euf_solver.h @@ -48,41 +48,52 @@ namespace euf { size_t to_index() const { return sat::constraint_base::mem2base(this); } }; + + class solver : public sat::extension, public th_internalizer, public th_decompile { typedef top_sort deps_t; friend class ackerman; // friend class sat::ba_solver; struct stats { - unsigned m_num_dynack; + unsigned m_ackerman; stats() { reset(); } void reset() { memset(this, 0, sizeof(*this)); } }; struct scope { unsigned m_var_lim; - unsigned m_trail_lim; }; - typedef ptr_vector > trail_stack; + typedef trail_stack euf_trail_stack; + + + size_t* to_ptr(sat::literal l) { return TAG(size_t*, reinterpret_cast((size_t)(l.index() << 4)), 1); } + size_t* to_ptr(size_t jst) { return TAG(size_t*, reinterpret_cast(jst), 2); } + bool is_literal(size_t* p) const { return GET_TAG(p) == 1; } + bool is_justification(size_t* p) const { return GET_TAG(p) == 2; } + sat::literal get_literal(size_t* p) { + unsigned idx = static_cast(reinterpret_cast(UNTAG(size_t*, p))); + return sat::to_literal(idx >> 4); + } + size_t get_justification(size_t* p) const { + return reinterpret_cast(UNTAG(size_t*, p)); + } ast_manager& m; - atom2bool_var& m_expr2var; sat::sat_internalizer& si; smt_params m_config; - bool m_drat { false }; euf::egraph m_egraph; + euf_trail_stack m_trail; stats m_stats; - region m_region; func_decl_ref_vector m_unhandled_functions; - trail_stack m_trail; + sat::solver* m_solver { nullptr }; sat::lookahead* m_lookahead { nullptr }; ast_manager* m_to_m; - atom2bool_var* m_to_expr2var; sat::sat_internalizer* m_to_si; scoped_ptr m_ackerman; - ptr_vector m_var2node; - ptr_vector m_explain; + ptr_vector m_var2expr; + ptr_vector m_explain; unsigned m_num_scopes { 0 }; unsigned_vector m_var_trail; svector m_scopes; @@ -93,26 +104,29 @@ namespace euf { constraint* m_eq { nullptr }; constraint* m_lit { nullptr }; - sat::solver& s() { return *m_solver; } - unsigned * base_ptr() { return reinterpret_cast(this); } - // internalization - bool visit(expr* e) override; bool visited(expr* e) override; - bool post_visit(expr* e, bool sign, bool root) override; - void attach_node(euf::enode* n); - void attach_lit(sat::literal lit, euf::enode* n); + bool post_visit(expr* e, bool sign, bool root) override; + sat::literal attach_lit(sat::literal lit, expr* e); void add_distinct_axiom(app* e, euf::enode* const* args); void add_not_distinct_axiom(app* e, euf::enode* const* args); void axiomatize_basic(enode* n); - bool internalize_root(app* e, bool sign); + bool internalize_root(app* e, bool sign, ptr_vector const& args); euf::enode* mk_true(); euf::enode* mk_false(); + + // replay + expr_ref_vector m_reinit_exprs; + sat::bool_var_vector m_reinit_vars; + void start_reinit(unsigned num_scopes); + void finish_reinit(); // extensions - th_solver* get_solver(func_decl* f); + th_solver* get_solver(family_id fid, func_decl* f); + th_solver* get_solver(sort* s) { return get_solver(s->get_family_id(), nullptr); } + th_solver* get_solver(func_decl* f) { return get_solver(f->get_family_id(), f); } th_solver* get_solver(expr* e); th_solver* get_solver(sat::bool_var v); void add_solver(family_id fid, th_solver* th); @@ -127,15 +141,20 @@ namespace euf { void values2model(deps_t const& deps, expr_ref_vector const& values, model_ref& mdl); // solving - void propagate(); void propagate_literals(); void propagate_th_eqs(); - void get_antecedents(literal l, constraint& j, literal_vector& r); - void force_push(); + void get_antecedents(literal l, constraint& j, literal_vector& r, bool probing); + + // proofs void log_antecedents(std::ostream& out, literal l, literal_vector const& r); void log_antecedents(literal l, literal_vector const& r); - void log_node(enode* n); - void log_bool_var(sat::bool_var v, enode* n); + void log_node(expr* n); + bool m_drat_initialized{ false }; + void init_drat(); + + // invariant + void check_eqc_bool_assignment() const; + void check_missing_bool_enode_propagation() const; constraint& mk_constraint(constraint*& c, constraint::kind_t k); constraint& conflict_constraint() { return mk_constraint(m_conflict, constraint::kind_t::conflict); } @@ -143,17 +162,17 @@ namespace euf { constraint& lit_constraint() { return mk_constraint(m_lit, constraint::kind_t::lit); } public: - solver(ast_manager& m, atom2bool_var& expr2var, sat::sat_internalizer& si, params_ref const& p = params_ref()): + solver(ast_manager& m, sat::sat_internalizer& si, params_ref const& p = params_ref()): m(m), - m_expr2var(expr2var), si(si), m_egraph(m), + m_trail(*this), m_unhandled_functions(m), m_solver(nullptr), m_lookahead(nullptr), m_to_m(&m), - m_to_expr2var(&expr2var), - m_to_si(&si) + m_to_si(&si), + m_reinit_exprs(m) { updt_params(p); } @@ -166,37 +185,45 @@ namespace euf { struct scoped_set_translate { solver& s; - scoped_set_translate(solver& s, ast_manager& m, atom2bool_var& a2b, sat::sat_internalizer& si) : + scoped_set_translate(solver& s, ast_manager& m, sat::sat_internalizer& si) : s(s) { s.m_to_m = &m; - s.m_to_expr2var = &a2b; s.m_to_si = &si; } ~scoped_set_translate() { s.m_to_m = &s.m; - s.m_to_expr2var = &s.m_expr2var; s.m_to_si = &s.si; } }; + sat::solver& s() { return *m_solver; } + sat::solver const& s() const { return *m_solver; } sat::sat_internalizer& get_si() { return si; } ast_manager& get_manager() { return m; } enode* get_enode(expr* e) { return m_egraph.find(e); } - sat::literal get_literal(expr* e) { return literal(m_expr2var.to_bool_var(e), false); } + sat::literal get_literal(expr* e) const { return literal(si.to_bool_var(e), false); } + sat::literal get_literal(enode* e) const { return get_literal(e->get_owner()); } smt_params const& get_config() { return m_config; } - region& get_region() { return m_region; } + region& get_region() { return m_trail.get_region(); } template - void push(C const& c) { m_trail.push_back(new (m_region) C(c)); } + void push(C const& c) { m_trail.push(c); } + euf_trail_stack& get_trail_stack() { return m_trail; } void updt_params(params_ref const& p); - void set_solver(sat::solver* s) override { m_solver = s; m_drat = s->get_config().m_drat; } + void set_solver(sat::solver* s) override { m_solver = s; } void set_lookahead(sat::lookahead* s) override { m_lookahead = s; } void init_search() override; double get_reward(literal l, ext_constraint_idx idx, sat::literal_occs_fun& occs) const override; bool is_extended_binary(ext_justification_idx idx, literal_vector& r) override; bool is_external(bool_var v) override; bool propagate(literal l, ext_constraint_idx idx) override; - void get_antecedents(literal l, ext_justification_idx idx, literal_vector & r) override; + bool unit_propagate() override; + void propagate(enode* a, enode* b, ext_justification_idx); + bool set_root(literal l, literal r) override; + void flush_roots() override; + + void get_antecedents(literal l, ext_justification_idx idx, literal_vector & r, bool probing) override; + void add_antecedent(enode* a, enode* b); void asserted(literal l) override; sat::check_result check() override; void push() override; @@ -220,6 +247,9 @@ namespace euf { bool check_model(sat::model const& m) const override; unsigned max_var(unsigned w) const override; + bool use_drat() { return s().get_config().m_drat && (init_drat(), true); } + sat::drat& get_drat() { return s().get_drat(); } + // decompile bool extract_pb(std::function& card, std::function& pb) override; @@ -228,11 +258,18 @@ namespace euf { // internalize sat::literal internalize(expr* e, bool sign, bool root, bool learned) override; + void internalize(expr* e, bool learned) override; void attach_th_var(enode* n, th_solver* th, theory_var v) { m_egraph.add_th_var(n, v, th->get_id()); } + void attach_node(euf::enode* n); euf::enode* mk_enode(expr* e, unsigned n, enode* const* args) { return m_egraph.mk(e, n, args); } + expr* bool_var2expr(sat::bool_var v) { return m_var2expr.get(v, nullptr); } void update_model(model_ref& mdl); func_decl_ref_vector const& unhandled_functions() { return m_unhandled_functions; } - }; + }; }; + +inline std::ostream& operator<<(std::ostream& out, euf::solver const& s) { + return s.display(out); +} diff --git a/src/sat/smt/sat_smt.h b/src/sat/smt/sat_smt.h index 82b1ab16a79..7b46f89b8a9 100644 --- a/src/sat/smt/sat_smt.h +++ b/src/sat/smt/sat_smt.h @@ -39,8 +39,11 @@ namespace sat { virtual ~sat_internalizer() {} virtual bool is_bool_op(expr* e) const = 0; virtual literal internalize(expr* e, bool learned) = 0; + virtual bool_var to_bool_var(expr* e) = 0; virtual bool_var add_bool_var(expr* e) = 0; virtual void cache(app* t, literal l) = 0; + virtual void push() = 0; + virtual void pop(unsigned n) = 0; }; class constraint_base { diff --git a/src/sat/smt/sat_th.cpp b/src/sat/smt/sat_th.cpp index 435d330f5d0..21b2a475171 100644 --- a/src/sat/smt/sat_th.cpp +++ b/src/sat/smt/sat_th.cpp @@ -65,15 +65,24 @@ namespace euf { return ctx.get_region(); } - enode* th_euf_solver::get_enode(expr* e) const { + trail_stack& th_euf_solver::get_trail_stack() { + return ctx.get_trail_stack(); + } + + enode* th_euf_solver::expr2enode(expr* e) const { return ctx.get_enode(e); } - sat::literal th_euf_solver::get_literal(expr* e) const { + sat::literal th_euf_solver::expr2literal(expr* e) const { return ctx.get_literal(e); } + expr* th_euf_solver::bool_var2expr(sat::bool_var v) const { + return ctx.bool_var2expr(v); + } + theory_var th_euf_solver::mk_var(enode * n) { + force_push(); SASSERT(!is_attached_to_var(n)); euf::theory_var v = m_var2enode.size(); m_var2enode.push_back(n); @@ -82,7 +91,7 @@ namespace euf { bool th_euf_solver::is_attached_to_var(enode* n) const { theory_var v = n->get_th_var(get_id()); - return v != null_theory_var && get_enode(v) == n; + return v != null_theory_var && var2enode(v) == n; } theory_var th_euf_solver::get_th_var(expr* e) const { @@ -99,4 +108,35 @@ namespace euf { m_var2enode_lim.shrink(new_lvl); } + unsigned th_euf_solver::lazy_pop(unsigned n) { + if (n <= m_num_scopes) { + m_num_scopes -= n; + return 0; + } + else { + n -= m_num_scopes; + pop(n); + return n; + } + } + + void th_euf_solver::add_unit(sat::literal lit) { + ctx.s().add_clause(1, &lit, sat::status::th(m_is_redundant, get_id())); + } + + void th_euf_solver::add_clause(sat::literal a, sat::literal b) { + sat::literal lits[2] = { a, b }; + ctx.s().add_clause(2, lits, sat::status::th(m_is_redundant, get_id())); + } + + void th_euf_solver::add_clause(sat::literal a, sat::literal b, sat::literal c) { + sat::literal lits[3] = { a, b, c }; + ctx.s().add_clause(3, lits, sat::status::th(m_is_redundant, get_id())); + } + + void th_euf_solver::add_clause(sat::literal a, sat::literal b, sat::literal c, sat::literal d) { + sat::literal lits[4] = { a, b, c, d }; + ctx.s().add_clause(4, lits, sat::status::th(m_is_redundant, get_id())); + } + } diff --git a/src/sat/smt/sat_th.h b/src/sat/smt/sat_th.h index 987bc8dc6c7..55c0793d970 100644 --- a/src/sat/smt/sat_th.h +++ b/src/sat/smt/sat_th.h @@ -36,11 +36,14 @@ namespace euf { virtual bool visit(expr* e) { return false; } virtual bool visited(expr* e) { return false; } virtual bool post_visit(expr* e, bool sign, bool root) { return false; } + public: virtual ~th_internalizer() {} virtual sat::literal internalize(expr* e, bool sign, bool root, bool redundant) = 0; + virtual void internalize(expr* e, bool redundant) = 0; + /** \brief Apply (interpreted) sort constraints on the given enode. */ @@ -69,7 +72,7 @@ namespace euf { /** \brief compute dependencies for node n */ - virtual void add_dep(euf::enode* n, top_sort& dep) {} + virtual void add_dep(euf::enode* n, top_sort& dep) { dep.insert(n, nullptr); } /** \brief should function be included in model. @@ -102,24 +105,39 @@ namespace euf { solver & ctx; euf::enode_vector m_var2enode; unsigned_vector m_var2enode_lim; + unsigned m_num_scopes{ 0 }; smt_params const& get_config() const; - sat::literal get_literal(expr* e) const; + sat::literal expr2literal(expr* e) const; region& get_region(); + + + void add_unit(sat::literal lit); + void add_clause(sat::literal a, sat::literal b); + void add_clause(sat::literal a, sat::literal b, sat::literal c); + void add_clause(sat::literal a, sat::literal b, sat::literal c, sat::literal d); + public: th_euf_solver(euf::solver& ctx, euf::theory_id id); virtual ~th_euf_solver() {} virtual theory_var mk_var(enode * n); unsigned get_num_vars() const { return m_var2enode.size();} - enode* get_enode(expr* e) const; - enode* get_enode(theory_var v) const { return m_var2enode[v]; } - expr* get_expr(theory_var v) const { return get_enode(v)->get_owner(); } - expr_ref get_expr(sat::literal lit) const { expr* e = get_expr(lit.var()); return lit.sign() ? expr_ref(m.mk_not(e), m) : expr_ref(e, m); } + enode* expr2enode(expr* e) const; + enode* var2enode(theory_var v) const { return m_var2enode[v]; } + expr* var2expr(theory_var v) const { return var2enode(v)->get_owner(); } + expr* bool_var2expr(sat::bool_var v) const; + expr_ref literal2expr(sat::literal lit) const { expr* e = bool_var2expr(lit.var()); return lit.sign() ? expr_ref(m.mk_not(e), m) : expr_ref(e, m); } theory_var get_th_var(enode* n) const { return n->get_th_var(get_id()); } theory_var get_th_var(expr* e) const; + trail_stack & get_trail_stack(); bool is_attached_to_var(enode* n) const; + bool is_root(theory_var v) const { return var2enode(v)->is_root(); } void push() override; void pop(unsigned n) override; + + void lazy_push() { ++m_num_scopes; } + void force_push() { for (; m_num_scopes > 0; --m_num_scopes) push(); } + unsigned lazy_pop(unsigned n); }; diff --git a/src/sat/tactic/goal2sat.cpp b/src/sat/tactic/goal2sat.cpp index 9549333793f..c1eb93ce9ad 100644 --- a/src/sat/tactic/goal2sat.cpp +++ b/src/sat/tactic/goal2sat.cpp @@ -29,6 +29,7 @@ Module Name: #include "util/ref_util.h" #include "ast/ast_smt2_pp.h" #include "ast/ast_pp.h" +#include "ast/ast_smt2_pp.h" #include "ast/ast_ll_pp.h" #include "ast/pb_decl_plugin.h" #include "ast/ast_util.h" @@ -38,6 +39,7 @@ Module Name: #include "tactic/tactic.h" #include "tactic/generic_model_converter.h" #include "sat/sat_cut_simplifier.h" +#include "sat/sat_drat.h" #include "sat/tactic/goal2sat.h" #include "sat/smt/ba_solver.h" #include "sat/smt/euf_solver.h" @@ -72,6 +74,7 @@ struct goal2sat::imp : public sat::sat_internalizer { bool m_default_external; bool m_xor_solver; bool m_euf; + bool m_drat; bool m_is_redundant { false }; sat::literal_vector aig_lits; @@ -98,6 +101,7 @@ struct goal2sat::imp : public sat::sat_internalizer { m_max_memory = megabytes_to_bytes(p.get_uint("max_memory", UINT_MAX)); m_xor_solver = p.get_bool("xor_solver", false); m_euf = sp.euf(); + m_drat = sp.drat_file() != symbol(); } void throw_op_not_handled(std::string const& s) { @@ -125,19 +129,71 @@ struct goal2sat::imp : public sat::sat_internalizer { m_solver.add_clause(num, lits, m_is_redundant ? sat::status::redundant() : sat::status::asserted()); } + void mk_root_clause(sat::literal l) { + TRACE("goal2sat", tout << "mk_clause: " << l << "\n";); + m_solver.add_clause(1, &l, m_is_redundant ? sat::status::redundant() : sat::status::input()); + } + + void mk_root_clause(sat::literal l1, sat::literal l2) { + TRACE("goal2sat", tout << "mk_clause: " << l1 << " " << l2 << "\n";); + m_solver.add_clause(l1, l2, m_is_redundant ? sat::status::redundant() : sat::status::input()); + } + + void mk_root_clause(sat::literal l1, sat::literal l2, sat::literal l3) { + TRACE("goal2sat", tout << "mk_clause: " << l1 << " " << l2 << " " << l3 << "\n";); + m_solver.add_clause(l1, l2, l3, m_is_redundant ? sat::status::redundant() : sat::status::input()); + } + + void mk_root_clause(unsigned num, sat::literal * lits) { + TRACE("goal2sat", tout << "mk_clause: "; for (unsigned i = 0; i < num; i++) tout << lits[i] << " "; tout << "\n";); + m_solver.add_clause(num, lits, m_is_redundant ? sat::status::redundant() : sat::status::input()); + } + + sat::bool_var add_var(bool is_ext, expr* n) { + auto v = m_solver.add_var(is_ext); + log_node(v, n); + return v; + } + + void log_node(sat::bool_var v, expr* n) { + if (m_drat && m_solver.get_drat_ptr()) { + sat::drat* drat = m_solver.get_drat_ptr(); + if (is_app(n)) { + app* a = to_app(n); + std::stringstream strm; + strm << mk_ismt2_func(a->get_decl(), m); + drat->def_begin(n->get_id(), strm.str()); + for (expr* arg : *a) + drat->def_add_arg(arg->get_id()); + drat->def_end(); + } + else { + IF_VERBOSE(0, verbose_stream() << "skipping DRAT of non-app\n"); + } + drat->bool_def(v, n->get_id()); + } + } + + + sat::literal mk_true() { if (m_true == sat::null_literal) { // create fake variable to represent true; - m_true = sat::literal(m_solver.add_var(false), false); + m_true = sat::literal(add_var(false, m.mk_true()), false); mk_clause(m_true); // v is true } return m_true; } + sat::bool_var to_bool_var(expr* e) override { + return m_map.to_bool_var(e); + } + sat::bool_var add_bool_var(expr* t) override { sat::bool_var v = m_map.to_bool_var(t); if (v == sat::null_bool_var) { - v = m_solver.add_var(true); + v = add_var(true, t); + force_push(); m_map.insert(t, v); } else { @@ -146,6 +202,28 @@ struct goal2sat::imp : public sat::sat_internalizer { return v; } + unsigned m_num_scopes{ 0 }; + + void force_push() { + for (; m_num_scopes > 0; --m_num_scopes) + m_map.push(); + } + + void push() override { + ++m_num_scopes; + } + + void pop(unsigned n) override { + if (n <= m_num_scopes) { + m_num_scopes -= n; + return; + } + n -= m_num_scopes; + m_num_scopes = 0; + m_cache.reset(); + m_map.pop(n); + } + void cache(app* t, sat::literal l) override { m_cache.insert(t, l); } @@ -166,12 +244,7 @@ struct goal2sat::imp : public sat::sat_internalizer { strm << mk_ismt2_pp(t, m); throw_op_not_handled(strm.str()); } - else { - bool ext = m_default_external || !is_uninterp_const(t) || m_interface_vars.contains(t); - v = m_solver.add_var(ext); - m_map.insert(t, v); - l = sat::literal(v, sign); - TRACE("sat", tout << "new_var: " << v << ": " << mk_bounded_pp(t, m, 2) << " " << is_uninterp_const(t) << "\n";); + else { if (!is_uninterp_const(t)) { if (m_euf) { convert_euf(t, root, sign); @@ -180,6 +253,12 @@ struct goal2sat::imp : public sat::sat_internalizer { else m_unhandled_funs.push_back(to_app(t)->get_decl()); } + bool ext = m_default_external || !is_uninterp_const(t) || m_interface_vars.contains(t); + v = add_var(ext, t); + force_push(); + m_map.insert(t, v); + l = sat::literal(v, sign); + TRACE("sat", tout << "new_var: " << v << ": " << mk_bounded_pp(t, m, 2) << " " << is_uninterp_const(t) << "\n";); } } else { @@ -191,7 +270,7 @@ struct goal2sat::imp : public sat::sat_internalizer { m_result_stack.reset(); SASSERT(l != sat::null_literal); if (root) - mk_clause(l); + mk_root_clause(l); else m_result_stack.push_back(l); } @@ -213,7 +292,7 @@ struct goal2sat::imp : public sat::sat_internalizer { if (sign) l.neg(); if (root) - mk_clause(l); + mk_root_clause(l); else m_result_stack.push_back(l); return true; @@ -275,17 +354,17 @@ struct goal2sat::imp : public sat::sat_internalizer { for (unsigned i = 0; i < num; i++) { sat::literal l = m_result_stack[i]; l.neg(); - mk_clause(l); + mk_root_clause(l); } } else { - mk_clause(m_result_stack.size(), m_result_stack.c_ptr()); + mk_root_clause(m_result_stack.size(), m_result_stack.c_ptr()); } m_result_stack.reset(); } else { SASSERT(num <= m_result_stack.size()); - sat::bool_var k = m_solver.add_var(false); + sat::bool_var k = add_var(false, t); sat::literal l(k, false); m_cache.insert(t, l); sat::literal * lits = m_result_stack.end() - num; @@ -321,18 +400,18 @@ struct goal2sat::imp : public sat::sat_internalizer { for (unsigned i = 0; i < num; ++i) { m_result_stack[i].neg(); } - mk_clause(m_result_stack.size(), m_result_stack.c_ptr()); + mk_root_clause(m_result_stack.size(), m_result_stack.c_ptr()); } else { for (unsigned i = 0; i < num; ++i) { - mk_clause(m_result_stack[i]); + mk_root_clause(m_result_stack[i]); } } m_result_stack.reset(); } else { SASSERT(num <= m_result_stack.size()); - sat::bool_var k = m_solver.add_var(false); + sat::bool_var k = add_var(false, t); sat::literal l(k, false); m_cache.insert(t, l); sat::literal * lits = m_result_stack.end() - num; @@ -373,17 +452,17 @@ struct goal2sat::imp : public sat::sat_internalizer { if (root) { SASSERT(sz == 3); if (sign) { - mk_clause(~c, ~t); - mk_clause(c, ~e); + mk_root_clause(~c, ~t); + mk_root_clause(c, ~e); } else { - mk_clause(~c, t); - mk_clause(c, e); + mk_root_clause(~c, t); + mk_root_clause(c, e); } m_result_stack.reset(); } else { - sat::bool_var k = m_solver.add_var(false); + sat::bool_var k = add_var(false, n); sat::literal l(k, false); m_cache.insert(n, l); mk_clause(~l, ~c, t); @@ -411,16 +490,16 @@ struct goal2sat::imp : public sat::sat_internalizer { if (root) { SASSERT(sz == 2); if (sign) { - mk_clause(l1); - mk_clause(~l2); + mk_root_clause(l1); + mk_root_clause(~l2); } else { - mk_clause(~l1, l2); + mk_root_clause(~l1, l2); } m_result_stack.reset(); } else { - sat::bool_var k = m_solver.add_var(false); + sat::bool_var k = add_var(false, t); sat::literal l(k, false); m_cache.insert(t, l); // l <=> (l1 => l2) @@ -443,17 +522,17 @@ struct goal2sat::imp : public sat::sat_internalizer { if (root) { SASSERT(sz == 2); if (sign) { - mk_clause(l1, l2); - mk_clause(~l1, ~l2); + mk_root_clause(l1, l2); + mk_root_clause(~l1, ~l2); } else { - mk_clause(l1, ~l2); - mk_clause(~l1, l2); + mk_root_clause(l1, ~l2); + mk_root_clause(~l1, l2); } m_result_stack.reset(); } else { - sat::bool_var k = m_solver.add_var(false); + sat::bool_var k = add_var(false, t); sat::literal l(k, false); m_cache.insert(t, l); mk_clause(~l, l1, ~l2); @@ -486,7 +565,7 @@ struct goal2sat::imp : public sat::sat_internalizer { sat::extension* ext = m_solver.get_extension(); euf::solver* euf = nullptr; if (!ext) { - euf = alloc(euf::solver, m, m_map, *this); + euf = alloc(euf::solver, m, *this); m_solver.set_extension(euf); for (unsigned i = m_solver.num_scopes(); i-- > 0; ) euf->push(); @@ -502,7 +581,7 @@ struct goal2sat::imp : public sat::sat_internalizer { if (lit == sat::null_literal) return; if (root) - mk_clause(lit); + mk_root_clause(lit); else m_result_stack.push_back(lit); } @@ -528,7 +607,7 @@ struct goal2sat::imp : public sat::sat_internalizer { if (lit == sat::null_literal) return; if (root) - mk_clause(lit); + mk_root_clause(lit); else m_result_stack.push_back(lit); } @@ -643,6 +722,7 @@ struct goal2sat::imp : public sat::sat_internalizer { sat::literal internalize(expr* n, bool redundant) override { unsigned sz = m_result_stack.size(); + (void)sz; process(n, false, redundant); SASSERT(m_result_stack.size() == sz + 1); sat::literal result = m_result_stack.back(); diff --git a/src/sat/tactic/sat_tactic.cpp b/src/sat/tactic/sat_tactic.cpp index 8249bd8654f..c9afc457825 100644 --- a/src/sat/tactic/sat_tactic.cpp +++ b/src/sat/tactic/sat_tactic.cpp @@ -64,7 +64,8 @@ class sat_tactic : public tactic { TRACE("sat_dimacs", m_solver->display_dimacs(tout);); dep2assumptions(dep2asm, assumptions); lbool r = m_solver->check(assumptions.size(), assumptions.c_ptr()); - TRACE("sat", tout << "result of checking: " << r << " " << m_solver->get_reason_unknown() << "\n";); + TRACE("sat", tout << "result of checking: " << r << " "; + if (r == l_undef) tout << m_solver->get_reason_unknown(); tout << "\n";); if (r == l_false) { expr_dependency * lcore = nullptr; if (produce_core) { @@ -88,13 +89,18 @@ class sat_tactic : public tactic { for (auto const& kv : map) { expr * n = kv.m_key; sat::bool_var v = kv.m_value; + if (!is_app(n)) + continue; + app* a = to_app(n); + if (!is_uninterp_const(a)) + continue; TRACE("sat_tactic", tout << "extracting value of " << mk_ismt2_pp(n, m) << "\nvar: " << v << "\n";); switch (sat::value_at(v, ll_m)) { case l_true: - md->register_decl(to_app(n)->get_decl(), m.mk_true()); + md->register_decl(a->get_decl(), m.mk_true()); break; case l_false: - md->register_decl(to_app(n)->get_decl(), m.mk_false()); + md->register_decl(a->get_decl(), m.mk_false()); break; default: break; diff --git a/src/shell/CMakeLists.txt b/src/shell/CMakeLists.txt index 2782463410f..d55626d05fb 100644 --- a/src/shell/CMakeLists.txt +++ b/src/shell/CMakeLists.txt @@ -20,6 +20,7 @@ endforeach() add_executable(shell datalog_frontend.cpp dimacs_frontend.cpp + drat_frontend.cpp "${CMAKE_CURRENT_BINARY_DIR}/gparams_register_modules.cpp" "${CMAKE_CURRENT_BINARY_DIR}/install_tactic.cpp" main.cpp diff --git a/src/shell/drat_frontend.cpp b/src/shell/drat_frontend.cpp new file mode 100644 index 00000000000..561c185e59a --- /dev/null +++ b/src/shell/drat_frontend.cpp @@ -0,0 +1,197 @@ +/*++ +Copyright (c) 2020 Microsoft Corporation + +--*/ + +#include +#include +#include "util/memory_manager.h" +#include "util/statistics.h" +#include "sat/dimacs.h" +#include "sat/sat_solver.h" +#include "sat/sat_drat.h" +#include "smt/smt_solver.h" +#include "shell/drat_frontend.h" +#include "parsers/smt2/smt2parser.h" +#include "cmd_context/cmd_context.h" + +class smt_checker { + ast_manager& m; + expr_ref_vector const& m_b2e; + expr_ref_vector m_fresh_exprs; + expr_ref_vector m_core; + params_ref m_params; + scoped_ptr m_solver; + + expr* fresh(expr* e) { + unsigned i = e->get_id(); + m_fresh_exprs.reserve(i + 1); + expr* r = m_fresh_exprs.get(i); + if (!r) { + r = m.mk_fresh_const("sk", m.get_sort(e)); + m_fresh_exprs[i] = r; + } + return r; + } + + expr_ref define(expr* e, unsigned depth) { + expr_ref r(fresh(e), m); + m_core.push_back(m.mk_eq(r, e)); + if (depth == 0) + return r; + r = e; + if (is_app(e)) { + expr_ref_vector args(m); + for (expr* arg : *to_app(e)) + args.push_back(define(arg, depth - 1)); + r = m.mk_app(to_app(e)->get_decl(), args.size(), args.c_ptr()); + } + return r; + } + + void unfold1(sat::literal_vector const& lits) { + m_core.reset(); + for (sat::literal lit : lits) { + expr* e = m_b2e[lit.var()]; + expr_ref fml = define(e, 2); + if (!lit.sign()) + fml = m.mk_not(fml); + m_core.push_back(fml); + } + } +public: + smt_checker(expr_ref_vector const& b2e): + m(b2e.m()), m_b2e(b2e), m_fresh_exprs(m), m_core(m) { + m_solver = mk_smt_solver(m, m_params, symbol()); + } + + void check_shallow(sat::literal_vector const& lits) { + unfold1(lits); + m_solver->push(); + for (auto* c : m_core) + m_solver->assert_expr(c); + lbool is_sat = m_solver->check_sat(); + m_solver->pop(1); + if (is_sat == l_true) { + std::cout << "did not verify: " << lits << "\n" << m_core << "\n"; + for (sat::literal lit : lits) { + expr_ref e(m_b2e[lit.var()], m); + if (lit.sign()) + e = m.mk_not(e); + std::cout << e << " "; + } + std::cout << "\n"; + } + } +}; + +static void verify_smt(char const* drat_file, char const* smt_file) { + cmd_context ctx; + ctx.set_ignore_check(true); + ctx.set_regular_stream(std::cerr); + ctx.set_solver_factory(mk_smt_strategic_solver_factory()); + if (smt_file) { + std::ifstream smt_in(smt_file); + if (!parse_smt2_commands(ctx, smt_in)) { + std::cerr << "could not read file " << smt_file << "\n"; + return; + } + } + + std::ifstream ins(drat_file); + dimacs::drat_parser drat(ins, std::cerr); + std::function read_theory = [&](char const* r) { + if (strcmp(r, "euf") == 0) + return 0; + return ctx.m().mk_family_id(symbol(r)); + }; + drat.set_read_theory(read_theory); + params_ref p; + reslimit lim; + p.set_bool("drat.check_unsat", true); + sat::solver solver(p, lim); + sat::drat drat_checker(solver); + drat_checker.updt_config(); + + expr_ref_vector bool_var2expr(ctx.m()); + expr_ref_vector exprs(ctx.m()), args(ctx.m()); + func_decl* f = nullptr; + ptr_vector sorts; + + smt_checker checker(bool_var2expr); + + auto check_smt = [&](dimacs::drat_record const& r) { + auto const& st = r.m_status; + if (st.is_input()) + ; + else if (st.is_sat() && st.is_asserted()) { + std::cout << "Tseitin tautology " << r; + checker.check_shallow(r.m_lits); + } + else if (st.is_sat()) + ; + else if (st.is_deleted()) + ; + else { + std::cout << "check smt " << r; + checker.check_shallow(r.m_lits); + // TBD: shallow check may fail because it doesn't include + // all RUP units, whish are sometimes required. + } + }; + + for (auto const& r : drat) { + std::cout << r; + std::cout.flush(); + switch (r.m_tag) { + case dimacs::drat_record::tag_t::is_clause: + for (sat::literal lit : r.m_lits) + while (lit.var() >= solver.num_vars()) + solver.mk_var(true); + drat_checker.add(r.m_lits, r.m_status); + check_smt(r); + break; + case dimacs::drat_record::tag_t::is_node: + args.reset(); + sorts.reset(); + for (auto n : r.m_args) { + args.push_back(exprs.get(n)); + sorts.push_back(ctx.m().get_sort(args.back())); + } + if (r.m_name[0] == '(') { + std::cout << "parsing sexprs is TBD\n"; + exit(0); + } + f = ctx.find_func_decl(symbol(r.m_name.c_str()), 0, nullptr, args.size(), sorts.c_ptr(), nullptr); + if (!f) { + std::cout << "could not find function\n"; + exit(0); + } + exprs.reserve(r.m_node_id+1); + exprs.set(r.m_node_id, ctx.m().mk_app(f, args.size(), args.c_ptr())); + break; + case dimacs::drat_record::is_bool_def: + bool_var2expr.reserve(r.m_node_id+1); + bool_var2expr.set(r.m_node_id, exprs.get(r.m_args[0])); + break; + default: + UNREACHABLE(); + break; + } + } + statistics st; + drat_checker.collect_statistics(st); + std::cout << st << "\n"; +} + + +unsigned read_drat(char const* drat_file, char const* problem_file) { + if (!problem_file) { + std::cerr << "No smt2 file provided to checker\n"; + return -1; + } + verify_smt(drat_file, problem_file); + return 0; +} + + diff --git a/src/shell/drat_frontend.h b/src/shell/drat_frontend.h new file mode 100644 index 00000000000..ef37fcfaa74 --- /dev/null +++ b/src/shell/drat_frontend.h @@ -0,0 +1,8 @@ +/*++ +Copyright (c) 2011 Microsoft Corporation +--*/ +#pragma once + +unsigned read_drat(char const * drat_file, char const* problem_file); + + diff --git a/src/shell/main.cpp b/src/shell/main.cpp index bee73fb25d6..81c06d387da 100644 --- a/src/shell/main.cpp +++ b/src/shell/main.cpp @@ -38,15 +38,17 @@ Revision History: #include "util/env_params.h" #include "util/file_path.h" #include "shell/lp_frontend.h" +#include "shell/drat_frontend.h" #if defined( _WINDOWS ) && defined( __MINGW32__ ) && ( defined( __GNUG__ ) || defined( __clang__ ) ) #include #endif -typedef enum { IN_UNSPECIFIED, IN_SMTLIB_2, IN_DATALOG, IN_DIMACS, IN_WCNF, IN_OPB, IN_LP, IN_Z3_LOG, IN_MPS } input_kind; +typedef enum { IN_UNSPECIFIED, IN_SMTLIB_2, IN_DATALOG, IN_DIMACS, IN_WCNF, IN_OPB, IN_LP, IN_Z3_LOG, IN_MPS, IN_DRAT } input_kind; static std::string g_aux_input_file; static char const * g_input_file = nullptr; +static char const * g_drat_input_file = nullptr; static bool g_standard_input = false; static input_kind g_input_kind = IN_UNSPECIFIED; bool g_display_statistics = false; @@ -301,12 +303,14 @@ static void parse_cmd_line_args(int argc, char ** argv) { gparams::set(key, value); } else { - if (g_input_file) { - warning_msg("input file was already specified."); + if (get_extension(arg) && strcmp(get_extension(arg), "drat") == 0) { + g_input_kind = IN_DRAT; + g_drat_input_file = arg; } - else { + else if (g_input_file) + warning_msg("input file was already specified."); + else g_input_file = arg; - } } i++; } @@ -361,7 +365,7 @@ int STD_CALL main(int argc, char ** argv) { g_input_kind = IN_MPS; } } - } + } switch (g_input_kind) { case IN_SMTLIB_2: memory::exit_when_out_of_memory(true, "(error \"out of memory\")"); @@ -388,6 +392,9 @@ int STD_CALL main(int argc, char ** argv) { case IN_MPS: return_value = read_mps_file(g_input_file); break; + case IN_DRAT: + return_value = read_drat(g_drat_input_file, g_input_file); + break; default: UNREACHABLE(); } diff --git a/src/shell/opt_frontend.cpp b/src/shell/opt_frontend.cpp index bf59b8cea76..897b411d18d 100644 --- a/src/shell/opt_frontend.cpp +++ b/src/shell/opt_frontend.cpp @@ -1,4 +1,3 @@ - /*++ Copyright (c) 2015 Microsoft Corporation diff --git a/src/smt/CMakeLists.txt b/src/smt/CMakeLists.txt index ba1c58cdbd9..f797a7f405b 100644 --- a/src/smt/CMakeLists.txt +++ b/src/smt/CMakeLists.txt @@ -68,7 +68,6 @@ z3_add_component(smt theory_dl.cpp theory_dummy.cpp theory_fpa.cpp - theory_jobscheduler.cpp theory_lra.cpp theory_opt.cpp theory_pb.cpp diff --git a/src/smt/smt_setup.cpp b/src/smt/smt_setup.cpp index c477dbeca5d..8cf2f244d30 100644 --- a/src/smt/smt_setup.cpp +++ b/src/smt/smt_setup.cpp @@ -37,7 +37,6 @@ Revision History: #include "smt/theory_pb.h" #include "smt/theory_fpa.h" #include "smt/theory_str.h" -#include "smt/theory_jobscheduler.h" namespace smt { @@ -123,8 +122,6 @@ namespace smt { setup_UFLRA(); else if (m_logic == "LRA") setup_LRA(); - else if (m_logic == "CSP") - setup_CSP(); else if (m_logic == "QF_FP") setup_QF_FP(); else if (m_logic == "QF_FPBV" || m_logic == "QF_BVFP") @@ -203,8 +200,6 @@ namespace smt { setup_QF_DT(); else if (m_logic == "LRA") setup_LRA(); - else if (m_logic == "CSP") - setup_CSP(); else setup_unknown(st); } @@ -932,11 +927,6 @@ namespace smt { m_context.register_plugin(alloc(smt::theory_seq, m_context)); } - void setup::setup_CSP() { - setup_unknown(); - m_context.register_plugin(alloc(smt::theory_jobscheduler, m_context)); - } - void setup::setup_special_relations() { m_context.register_plugin(alloc(smt::theory_special_relations, m_context, m_manager)); } diff --git a/src/smt/theory_jobscheduler.cpp b/src/smt/theory_jobscheduler.cpp deleted file mode 100644 index 8684e65d00f..00000000000 --- a/src/smt/theory_jobscheduler.cpp +++ /dev/null @@ -1,1183 +0,0 @@ -/*++ -Copyright (c) 2018 Microsoft Corporation - -Module Name: - - theory_jobscheduler.cpp - -Abstract: - - -Author: - - Nikolaj Bjorner (nbjorner) 2018-09-08. - -Revision History: - -TODO: - -- arithmetic interface - -- propagation queue: - - register bounds on start times to propagate energy constraints - - more general registration mechanism for arithmetic theory. -- interact with opt -- jobs without resources - - complain or add dummy resource? At which level. - -Features: -- properties -- priority - - try mss based from outside -- job-goal - - try optimization based on arithmetic solver. - - earliest start, latest start -- constraint level - - add constraints gradually -- resource groups - - resource groups like a resource - - resources bound to resource groups within time intervals - - job can require to use K resources from a resource group simultaneously. - ---*/ - -#include "ast/ast_pp.h" -#include "smt/theory_jobscheduler.h" -#include "smt/smt_context.h" -#include "smt/smt_arith_value.h" -#include "smt/smt_model_generator.h" - -namespace smt { - - theory_var theory_jobscheduler::mk_var(enode * n) { - theory_var v = theory::mk_var(n); - return v; - } - - bool theory_jobscheduler::internalize_term(app * term) { - if (ctx.e_internalized(term)) - return true; - for (expr* arg : *term) { - if (!ctx.e_internalized(arg)) - ctx.internalize(arg, false); - } - enode* e = ctx.mk_enode(term, false, false, true); - theory_var v = mk_var(e); - ctx.attach_th_var(e, this, v); - ctx.mark_as_relevant(e); - return true; - } - - bool theory_jobscheduler::internalize_atom(app * atom, bool gate_ctx) { - TRACE("csp", tout << mk_pp(atom, m) << "\n";); - SASSERT(u.is_model(atom)); - for (expr* arg : *atom) { - if (!ctx.e_internalized(arg)) ctx.internalize(arg, false); - internalize_cmd(arg); - } - add_done(); - bool_var bv = ctx.mk_bool_var(atom); - ctx.set_var_theory(bv, get_id()); - return true; - } - - struct symbol_cmp { - bool operator()(symbol const& s1, symbol const& s2) const { - return lt(s1, s2); - } - }; - - // TBD: stronger parameter validation - void theory_jobscheduler::internalize_cmd(expr* cmd) { - symbol key, val; - rational r; - expr* job, *resource; - unsigned j = 0, res = 0, cap = 0, loadpct = 100, level = 0; - time_t start = 0, end = std::numeric_limits::max(), capacity = 0; - js_job_goal goal; - js_optimization_objective obj; - properties ps; - if (u.is_set_preemptable(cmd, job) && u.is_job(job, j)) { - set_preemptable(j, true); - } - else if (u.is_add_resource_available(cmd, resource, loadpct, cap, start, end, ps) && u.is_resource(resource, res)) { - std::sort(ps.begin(), ps.end(), symbol_cmp()); - (void) cap; // TBD - add_resource_available(res, loadpct, start, end, ps); - } - else if (u.is_add_job_resource(cmd, job, resource, loadpct, capacity, end, ps) && u.is_job(job, j) && u.is_resource(resource, res)) { - std::sort(ps.begin(), ps.end(), symbol_cmp()); - add_job_resource(j, res, loadpct, capacity, end, ps); - } - else if (u.is_job_goal(cmd, goal, level, job) && u.is_job(job, j)) { - // skip - } - else if (u.is_objective(cmd, obj)) { - // skip - } - else { - invalid_argument("command not recognized ", cmd); - } - } - - void theory_jobscheduler::invalid_argument(char const* msg, expr* arg) { - std::ostringstream strm; - strm << msg << mk_pp(arg, m); - throw default_exception(strm.str()); - } - - void theory_jobscheduler::new_eq_eh(theory_var v1, theory_var v2) { - enode* e1 = get_enode(v1); - enode* root = e1->get_root(); - unsigned r; - if (u.is_resource(root->get_owner(), r)) { - enode* next = e1; - do { - unsigned j; - if (u.is_job2resource(next->get_owner(), j) && !m_jobs[j].m_is_bound) { - m_bound_jobs.push_back(j); - m_jobs[j].m_is_bound = true; - } - next = next->get_next(); - } - while (e1 != next); - } - } - - - void theory_jobscheduler::new_diseq_eh(theory_var v1, theory_var v2) { - - } - - void theory_jobscheduler::push_scope_eh() { - scope s; - s.m_bound_jobs_lim = m_bound_jobs.size(); - s.m_bound_qhead = m_bound_qhead; - m_scopes.push_back(s); - } - - void theory_jobscheduler::pop_scope_eh(unsigned num_scopes) { - unsigned new_lvl = m_scopes.size() - num_scopes; - scope const& s = m_scopes[new_lvl]; - for (unsigned i = s.m_bound_jobs_lim; i < m_bound_jobs.size(); ++i) { - unsigned j = m_bound_jobs[i]; - m_jobs[j].m_is_bound = false; - } - m_bound_jobs.shrink(s.m_bound_jobs_lim); - m_bound_qhead = s.m_bound_qhead; - m_scopes.shrink(new_lvl); - } - - final_check_status theory_jobscheduler::final_check_eh() { - TRACE("csp", tout << "\n";); - bool blocked = false; - for (unsigned j = 0; j < m_jobs.size(); ++j) { - if (split_job2resource(j)) { - return FC_CONTINUE; - } - } - for (unsigned r = 0; r < m_resources.size(); ++r) { - if (constrain_resource_energy(r)) { - blocked = true; - } - } - for (unsigned j = 0; j < m_jobs.size(); ++j) { - if (constrain_end_time_interval(j, resource(j))) { - blocked = true; - } - } - - if (blocked) return FC_CONTINUE; - return FC_DONE; - } - - bool theory_jobscheduler::can_propagate() { - return m_bound_qhead < m_bound_jobs.size(); - } - - literal theory_jobscheduler::mk_literal(expr * e) { - expr_ref _e(e, m); - if (!ctx.e_internalized(e)) { - ctx.internalize(e, false); - } - ctx.mark_as_relevant(ctx.get_enode(e)); - return ctx.get_literal(e); - } - - literal theory_jobscheduler::mk_ge(expr* e, time_t t) { - return mk_literal(mk_ge_expr(e, t)); - } - - expr* theory_jobscheduler::mk_ge_expr(expr* e, time_t t) { - return a.mk_ge(e, a.mk_int(rational(t, rational::ui64()))); - } - - literal theory_jobscheduler::mk_ge(enode* e, time_t t) { - return mk_ge(e->get_owner(), t); - } - - literal theory_jobscheduler::mk_le(expr* e, time_t t) { - return mk_literal(mk_le_expr(e, t)); - } - - expr* theory_jobscheduler::mk_le_expr(expr* e, time_t t) { - return a.mk_le(e, a.mk_int(rational(t, rational::ui64()))); - } - - literal theory_jobscheduler::mk_le(enode* l, enode* r) { - expr_ref le(a.mk_le(l->get_owner(), r->get_owner()), m); - ctx.get_rewriter()(le); - return mk_literal(le); - } - - literal theory_jobscheduler::mk_le(enode* e, time_t t) { - return mk_le(e->get_owner(), t); - } - - literal theory_jobscheduler::mk_eq_lit(expr * l, expr * r) { - literal lit = mk_eq(l, r, false); - ctx.mark_as_relevant(lit); - return lit; - } - - - /** - * iterator of job overlaps. - */ - theory_jobscheduler::job_overlap::job_overlap(vector& starts, vector& ends): - m_start(0), m_starts(starts), m_ends(ends), s_idx(0), e_idx(0) { - job_time::compare cmp; - std::sort(starts.begin(), starts.end(), cmp); - std::sort(ends.begin(), ends.end(), cmp); - } - - bool theory_jobscheduler::job_overlap::next() { - if (s_idx == m_starts.size()) { - return false; - } - do { - m_start = std::max(m_start, m_starts[s_idx].m_time); - - // add jobs that start before or at m_start - while (s_idx < m_starts.size() && m_starts[s_idx].m_time <= m_start) { - m_jobs.insert(m_starts[s_idx].m_job); - ++s_idx; - } - // remove jobs that end before m_start. - while (e_idx < m_ends.size() && m_ends[e_idx].m_time < m_start) { - m_jobs.remove(m_ends[e_idx].m_job); - ++e_idx; - } - } - // as long as set of jobs increments, add to m_start - while (s_idx < m_starts.size() && e_idx < m_ends.size() && - m_starts[s_idx].m_time <= m_ends[e_idx].m_time); - - return true; - } - - - /** - * r = resource(j) & start(j) >= slb => end(j) >= ect(j, r, slb) - * - * note: not used so far - * note: subsumed by constrain_end_time_interval used in final-check - */ - void theory_jobscheduler::propagate_end_time(unsigned j, unsigned r) { - time_t slb = est(j); - time_t clb = ect(j, r, slb); - - if (clb > end(j)) { - job_info const& ji = m_jobs[j]; - literal start_ge_lo = mk_ge(ji.m_start, slb); - if (ctx.get_assignment(start_ge_lo) != l_true) { - return; - } - enode_pair eq(ji.m_job2resource, m_resources[r].m_resource); - if (eq.first->get_root() != eq.second->get_root()) { - return; - } - - literal end_ge_lo = mk_ge(ji.m_end, clb); - // Initialization ensures that satisfiable states have completion time below end. - ast_manager& m = get_manager(); - if (m.has_trace_stream()) { - app_ref body(m); - body = m.mk_implies(m.mk_and(m.mk_eq(eq.first->get_owner(), eq.second->get_owner()), ctx.bool_var2expr(start_ge_lo.var())), ctx.bool_var2expr(end_ge_lo.var())); - log_axiom_instantiation(body); - m.trace_stream() << "[end-of-instance]\n"; - } - region& r = ctx.get_region(); - ctx.assign(end_ge_lo, - ctx.mk_justification( - ext_theory_propagation_justification(get_id(), r, 1, &start_ge_lo, 1, &eq, end_ge_lo, 0, nullptr))); - } - } - - /** - * For time interval [t0, t1] the end-time can be computed as a function - * of start time based on resource load availability. - * - * r = resource(j) & t1 >= start(j) >= t0 => end(j) = start(j) + ect(j, r, t0) - t0 - */ - bool theory_jobscheduler::constrain_end_time_interval(unsigned j, unsigned r) { - unsigned idx1 = 0, idx2 = 0; - if (!job_has_resource(j, r)) { - IF_VERBOSE(0, verbose_stream() << "job " << j << " assigned non-registered resource " << r << "\n"); - return false; - } - time_t s = start(j); - job_resource const& jr = get_job_resource(j, r); - TRACE("csp", tout << "job: " << j << " resource: " << r << " start: " << s << "\n";); - vector& available = m_resources[r].m_available; - if (!resource_available(r, s, idx1)) return false; - if (!resource_available(jr, available[idx1])) return false; - time_t e = ect(j, r, s); - TRACE("csp", tout << "job: " << j << " resource: " << r << " ect: " << e << "\n";); - if (!resource_available(r, e, idx2)) return false; // infeasible.. - if (!resource_available(jr, available[idx2])) return false; - time_t start1 = available[idx1].m_start; - time_t end1 = available[idx1].m_end; - unsigned cap1 = available[idx1].m_loadpct; - time_t start2 = available[idx2].m_start; - time_t end2 = available[idx2].m_end; - unsigned cap2 = available[idx2].m_loadpct; - // calculate minimal start1 <= t0 <= s, such that ect(j, r, t0) >= start2 - // calculate maximal s <= t1 <= end1, such that ect(j, r, t1) <= end2 - time_t delta1 = (s - start1)*cap1; - time_t delta2 = (e - start2)*cap2; - time_t t0, t1; - if (delta1 <= delta2) { - t0 = start1; - } - else { - // solve for t0: - // (s - t0)*cap1 = (e - start2)*cap2; - t0 = s - (delta2 / cap1); - } - delta1 = (end1 - s)*cap1; - delta2 = (end2 - e)*cap2; - if (delta1 <= delta2) { - t1 = end1; - } - else { - // solve for t1: - // (t1 - s)*cap1 = (end2 - e)*cap2 - t1 = s + (delta2 / cap1); - } - - time_t delta = ect(j, r, t0) - t0; - if (end(j) == start(j) + delta) { - return false; - } - TRACE("csp", tout << "job: " << j << " resource " << r << " t0: " << t0 << " t1: " << t1 << " delta: " << delta << "\n";); - literal_vector lits; - enode* start_e = m_jobs[j].m_start; - enode* end_e = m_jobs[j].m_end; - lits.push_back(~mk_eq_lit(m_jobs[j].m_job2resource, m_resources[r].m_resource)); - lits.push_back(~mk_ge(start_e, t0)); - lits.push_back(~mk_le(start_e, t1)); - expr_ref rhs(a.mk_add(start_e->get_owner(), a.mk_int(rational(delta, rational::ui64()))), m); - lits.push_back(mk_eq_lit(end_e->get_owner(), rhs)); - ctx.mk_clause(lits.size(), lits.c_ptr(), nullptr, CLS_TH_LEMMA, nullptr); - ast_manager& m = get_manager(); - if (m.has_trace_stream()) { - app_ref body(m); - body = m.mk_implies(m.mk_and(ctx.bool_var2expr(lits[0].var()), ctx.bool_var2expr(lits[1].var()), ctx.bool_var2expr(lits[2].var())), ctx.bool_var2expr(lits[3].var())); - log_axiom_instantiation(body); - m.trace_stream() << "[end-of-instance]\n"; - } - return true; - } - - - /** - * Ensure that job overlaps don't exceed available energy - */ - bool theory_jobscheduler::constrain_resource_energy(unsigned r) { - bool blocked = false; - vector starts, ends; - res_info const& ri = m_resources[r]; - for (unsigned j : ri.m_jobs) { - if (resource(j) == r) { - starts.push_back(job_time(j, start(j))); - ends.push_back(job_time(j, end(j))); - } - } - job_overlap overlap(starts, ends); - while (overlap.next()) { - unsigned cap = 0; - auto const& jobs = overlap.jobs(); - for (auto j : jobs) { - cap += get_job_resource(j, r).m_loadpct; - if (cap > 100) { - block_job_overlap(r, jobs, j); - blocked = true; - goto try_next_overlap; - } - } - try_next_overlap: - ; - } - return blocked; - } - - void theory_jobscheduler::block_job_overlap(unsigned r, uint_set const& jobs, unsigned last_job) { - // - // block the following case: - // each job is assigned to r. - // max { start(j) | j0..last_job } <= min { end(j) | j0..last_job } - // joint capacity of jobs exceeds availability of resource. - // - time_t max_start = 0; - unsigned max_j = last_job; - for (auto j : jobs) { - if (max_start < start(j)) { - max_start = start(j); - max_j = j; - } - if (j == last_job) break; - } - literal_vector lits; - for (auto j : jobs) { - // create literals for: - // resource(j) == r - // m_jobs[j].m_start <= m_jobs[max_j].m_start; - // m_jobs[max_j].m_start <= m_jobs[j].m_end; - lits.push_back(~mk_eq_lit(m_jobs[j].m_job2resource, m_resources[r].m_resource)); - if (j != max_j) { - lits.push_back(~mk_le(m_jobs[j].m_start, m_jobs[max_j].m_start)); - lits.push_back(~mk_le(m_jobs[max_j].m_start, m_jobs[j].m_end)); - } - if (j == last_job) break; - } - ctx.mk_clause(lits.size(), lits.c_ptr(), nullptr, CLS_TH_LEMMA, nullptr); - } - - void theory_jobscheduler::propagate() { - while (m_bound_qhead < m_bound_jobs.size()) { - unsigned j = m_bound_jobs[m_bound_qhead++]; - unsigned r = 0; - job_info const& ji = m_jobs[j]; - VERIFY(u.is_resource(ji.m_job2resource->get_root()->get_owner(), r)); - TRACE("csp", tout << "job: " << j << " resource: " << r << "\n";); - std::cout << j << " -o " << r << "\n"; - propagate_job2resource(j, r); - } - } - - void theory_jobscheduler::propagate_job2resource(unsigned j, unsigned r) { - job_info const& ji = m_jobs[j]; - res_info const& ri = m_resources[r]; - literal eq = mk_eq_lit(ji.m_job2resource, ri.m_resource); - if (!job_has_resource(j, r)) { - IF_VERBOSE(0, verbose_stream() << "job " << j << " assigned non-registered resource " << r << "\n"); - return; - } - return; - job_resource const& jr = get_job_resource(j, r); - assert_last_end_time(j, r, jr, eq); - assert_last_start_time(j, r, eq); - assert_first_start_time(j, r, eq); - vector const& available = ri.m_available; - // TBD: needs to take properties into account - - unsigned i = 0; - if (!first_available(jr, ri, i)) return; - while (true) { - unsigned next = i + 1; - if (!first_available(jr, ri, next)) return; - SASSERT(available[i].m_end < available[next].m_start); - assert_job_not_in_gap(j, r, i, next, eq); - if (!ji.m_is_preemptable && available[i].m_end + 1 < available[i+1].m_start) { - assert_job_non_preemptable(j, r, i, next, eq); - } - i = next; - } - } - - theory_jobscheduler::theory_jobscheduler(context& ctx): - theory(ctx, ctx.get_manager().get_family_id("csp")), - m(ctx.get_manager()), - u(m), - a(m), - m_bound_qhead(0) { - } - - std::ostream& theory_jobscheduler::display(std::ostream & out, job_resource const& jr) const { - return out << "r:" << jr.m_resource_id << " cap:" << jr.m_capacity << " load:" << jr.m_loadpct << " end:" << jr.m_finite_capacity_end; - for (auto const& s : jr.m_properties) { - out << " " << s; - } - out << "\n"; - } - - std::ostream& theory_jobscheduler::display(std::ostream & out, job_info const& j) const { - for (job_resource const& jr : j.m_resources) { - display(out << " ", jr) << "\n"; - } - return out; - } - - std::ostream& theory_jobscheduler::display(std::ostream & out, res_available const& r) const { - return out << "[" << r.m_start << ":" << r.m_end << "] @ " << r.m_loadpct << "%"; - for (auto const& s : r.m_properties) { - out << " " << s; - } - out << "\n"; - } - - std::ostream& theory_jobscheduler::display(std::ostream & out, res_info const& r) const { - for (res_available const& ra : r.m_available) { - display(out << " ", ra); - } - return out; - } - - void theory_jobscheduler::display(std::ostream & out) const { - out << "jobscheduler:\n"; - for (unsigned j = 0; j < m_jobs.size(); ++j) { - display(out << "job " << j << ":\n", m_jobs[j]) << "\n"; - } - for (unsigned r = 0; r < m_resources.size(); ++r) { - display(out << "resource " << r << ":\n", m_resources[r]) << "\n"; - } - } - - void theory_jobscheduler::collect_statistics(::statistics & st) const { - - } - - bool theory_jobscheduler::include_func_interp(func_decl* f) { - return - f->get_decl_kind() == OP_JS_START || - f->get_decl_kind() == OP_JS_END || - f->get_decl_kind() == OP_JS_JOB2RESOURCE; - } - - void theory_jobscheduler::init_model(model_generator & m) { - - } - - model_value_proc * theory_jobscheduler::mk_value(enode * n, model_generator & mg) { - return alloc(expr_wrapper_proc, n->get_root()->get_owner()); - } - - bool theory_jobscheduler::get_value(enode * n, expr_ref & r) { - std::cout << mk_pp(n->get_owner(), m) << "\n"; - return false; - } - - theory * theory_jobscheduler::mk_fresh(context * new_ctx) { - return alloc(theory_jobscheduler, *new_ctx); - } - - time_t theory_jobscheduler::get_lo(expr* e) { - arith_value av(m); - av.init(&get_context()); - rational val; - bool is_strict; - if (av.get_lo(e, val, is_strict) && !is_strict && val.is_uint64()) { - return val.get_uint64(); - } - return 0; - } - - time_t theory_jobscheduler::get_up(expr* e) { - arith_value av(m); - av.init(&get_context()); - rational val; - bool is_strict; - if (av.get_up(e, val, is_strict) && !is_strict && val.is_uint64()) { - return val.get_uint64(); - } - return std::numeric_limits::max(); - } - - time_t theory_jobscheduler::get_value(expr* e) { - arith_value av(get_manager()); - av.init(&get_context()); - rational val; - if (av.get_value_equiv(e, val) && val.is_uint64()) { - return val.get_uint64(); - } - return 0; - } - - time_t theory_jobscheduler::est(unsigned j) { - return get_lo(m_jobs[j].m_start->get_owner()); - } - - time_t theory_jobscheduler::lst(unsigned j) { - return get_up(m_jobs[j].m_start->get_owner()); - } - - time_t theory_jobscheduler::ect(unsigned j) { - return get_lo(m_jobs[j].m_end->get_owner()); - } - - time_t theory_jobscheduler::lct(unsigned j) { - return get_up(m_jobs[j].m_end->get_owner()); - } - - time_t theory_jobscheduler::start(unsigned j) { - return get_value(m_jobs[j].m_start->get_owner()); - } - - time_t theory_jobscheduler::end(unsigned j) { - return get_value(m_jobs[j].m_end->get_owner()); - } - - unsigned theory_jobscheduler::resource(unsigned j) { - unsigned r; - enode* next = m_jobs[j].m_job2resource, *n = next; - do { - if (u.is_resource(next->get_owner(), r)) { - return r; - } - next = next->get_next(); - } - while (next != n); - return 0; - } - - void theory_jobscheduler::set_preemptable(unsigned j, bool is_preemptable) { - ensure_job(j).m_is_preemptable = is_preemptable; - } - - theory_jobscheduler::res_info& theory_jobscheduler::ensure_resource(unsigned last) { - while (m_resources.size() <= last) { - unsigned r = m_resources.size(); - m_resources.push_back(res_info()); - res_info& ri = m_resources.back(); - app_ref res(u.mk_resource(r), m); - if (!ctx.e_internalized(res)) ctx.internalize(res, false); - ri.m_resource = ctx.get_enode(res); - app_ref ms(u.mk_makespan(r), m); - if (!ctx.e_internalized(ms)) ctx.internalize(ms, false); - ri.m_makespan = ctx.get_enode(ms); - } - return m_resources[last]; - } - - theory_jobscheduler::job_info& theory_jobscheduler::ensure_job(unsigned last) { - while (m_jobs.size() <= last) { - unsigned j = m_jobs.size(); - m_jobs.push_back(job_info()); - job_info& ji = m_jobs.back(); - app_ref job(u.mk_job(j), m); - app_ref start(u.mk_start(j), m); - app_ref end(u.mk_end(j), m); - app_ref res(u.mk_job2resource(j), m); - if (!ctx.e_internalized(job)) ctx.internalize(job, false); - if (!ctx.e_internalized(start)) ctx.internalize(start, false); - if (!ctx.e_internalized(end)) ctx.internalize(end, false); - if (!ctx.e_internalized(res)) ctx.internalize(res, false); - ji.m_job = ctx.get_enode(job); - ji.m_start = ctx.get_enode(start); - ji.m_end = ctx.get_enode(end); - ji.m_job2resource = ctx.get_enode(res); - } - return m_jobs[last]; - } - - void theory_jobscheduler::add_job_resource(unsigned j, unsigned r, unsigned loadpct, uint64_t cap, time_t finite_capacity_end, properties const& ps) { - SASSERT(ctx.at_base_level()); - SASSERT(0 <= loadpct && loadpct <= 100); - SASSERT(0 <= cap); - job_info& ji = ensure_job(j); - res_info& ri = ensure_resource(r); - if (ji.m_resource2index.contains(r)) { - throw default_exception("resource already bound to job"); - } - ji.m_resource2index.insert(r, ji.m_resources.size()); - ji.m_resources.push_back(job_resource(r, cap, loadpct, finite_capacity_end, ps)); - SASSERT(!ri.m_jobs.contains(j)); - ri.m_jobs.push_back(j); - } - - - void theory_jobscheduler::add_resource_available(unsigned r, unsigned max_loadpct, time_t start, time_t end, properties const& ps) { - SASSERT(ctx.at_base_level()); - SASSERT(1 <= max_loadpct && max_loadpct <= 100); - SASSERT(start <= end); - res_info& ri = ensure_resource(r); - ri.m_available.push_back(res_available(max_loadpct, start, end, ps)); - } - - /* - * Initialize the state based on the set of jobs and resources added. - * Ensure that the availability slots for each resource is sorted by time. - * - * For each resource j: - * est(j) <= start(j) <= end(j) <= lct(j) - * - * possible strengthenings: - * - start(j) <= lst(j) - * - start(j) + min_completion_time(j) <= end(j) - * - start(j) + max_completion_time(j) >= end(j) - * - * makespan constraints? - * - */ - void theory_jobscheduler::add_done() { - TRACE("csp", tout << "add-done begin\n";); - - // sort availability intervals - for (res_info& ri : m_resources) { - vector& available = ri.m_available; - res_available::compare cmp; - std::sort(available.begin(), available.end(), cmp); - } - - literal lit; - unsigned job_id = 0; - - for (job_info const& ji : m_jobs) { - if (ji.m_resources.empty()) { - throw default_exception("every job should be associated with at least one resource"); - } - - // start(j) <= end(j) - lit = mk_le(ji.m_start, ji.m_end); - if (m.has_trace_stream()) log_axiom_instantiation(ctx.bool_var2expr(lit.var())); - ctx.mk_th_axiom(get_id(), 1, &lit); - if (m.has_trace_stream()) m.trace_stream() << "[end-of-instance]\n"; - - time_t start_lb = std::numeric_limits::max(); - time_t runtime_lb = std::numeric_limits::max(); - time_t end_ub = 0; // , runtime_ub = 0; - for (job_resource const& jr : ji.m_resources) { - unsigned r = jr.m_resource_id; - res_info const& ri = m_resources[r]; - if (ri.m_available.empty()) { - if (jr.m_capacity == 0) { - start_lb = 0; - end_ub = std::numeric_limits::max(); - runtime_lb = 0; - } - continue; - } - unsigned idx = 0; - if (first_available(jr, ri, idx)) { - start_lb = std::min(start_lb, ri.m_available[idx].m_start); - } - else { - IF_VERBOSE(0, verbose_stream() << "not first-available\n";); - } - idx = ri.m_available.size(); - if (last_available(jr, ri, idx)) { - end_ub = std::max(end_ub, ri.m_available[idx].m_end); - } - else { - IF_VERBOSE(0, verbose_stream() << "not last-available\n";); - } - runtime_lb = std::min(runtime_lb, jr.m_capacity); - // TBD: more accurate estimates for runtime_lb based on gaps - // TBD: correct estimate of runtime_ub taking gaps into account. - } - CTRACE("csp", (start_lb > end_ub), tout << "there is no associated resource working time\n";); - if (start_lb > end_ub) { - IF_VERBOSE(0, verbose_stream() << start_lb << " " << end_ub << "\n"); - warning_msg("Job %d has no associated resource working time", job_id); - continue; - } - - // start(j) >= start_lb - lit = mk_ge(ji.m_start, start_lb); - if (m.has_trace_stream()) log_axiom_instantiation(ctx.bool_var2expr(lit.var())); - ctx.mk_th_axiom(get_id(), 1, &lit); - if (m.has_trace_stream()) m.trace_stream() << "[end-of-instance]\n"; - - // end(j) <= end_ub - lit = mk_le(ji.m_end, end_ub); - if (m.has_trace_stream()) log_axiom_instantiation(ctx.bool_var2expr(lit.var())); - ctx.mk_th_axiom(get_id(), 1, &lit); - if (m.has_trace_stream()) m.trace_stream() << "[end-of-instance]\n"; - - // start(j) + runtime_lb <= end(j) - // end(j) <= start(j) + runtime_ub - - ++job_id; - } - - TRACE("csp", tout << "add-done end\n";); - } - - // resource(j) = r => end(j) <= end(j, r) - void theory_jobscheduler::assert_last_end_time(unsigned j, unsigned r, job_resource const& jr, literal eq) { -#if 0 - job_info const& ji = m_jobs[j]; - literal l2 = mk_le(ji.m_end, jr.m_finite_capacity_end); - if (m.has_trace_stream()) { - app_ref body(m); - body = m.mk_implies(ctx.bool_var2expr(eq.var()), ctx.bool_var2expr(l2.var())); - log_axiom_instantiation(body); - } - ctx.mk_th_axiom(get_id(), ~eq, l2); - if (m.has_trace_stream()) m.trace_stream() << "[end-of-instance]\n"; -#endif - } - - // resource(j) = r => start(j) <= lst(j, r, end(j, r)) - void theory_jobscheduler::assert_last_start_time(unsigned j, unsigned r, literal eq) { - time_t t; - if (lst(j, r, t)) { - literal le = mk_le(m_jobs[j].m_start, t); - if (m.has_trace_stream()) { - app_ref body(m); - body = m.mk_implies(ctx.bool_var2expr(eq.var()), ctx.bool_var2expr(le.var())); - log_axiom_instantiation(body); - } - ctx.mk_th_axiom(get_id(), ~eq, le); - if (m.has_trace_stream()) m.trace_stream() << "[end-of-instance]\n"; - } - else { - eq.neg(); - if (m.has_trace_stream()) { - app_ref body(m); - body = m.mk_not(ctx.bool_var2expr(eq.var())); - log_axiom_instantiation(body); - } - ctx.mk_th_axiom(get_id(), 1, &eq); - if (m.has_trace_stream()) m.trace_stream() << "[end-of-instance]\n"; - } - } - - // resource(j) = r => start(j) >= available[0].m_start - void theory_jobscheduler::assert_first_start_time(unsigned j, unsigned r, literal eq) { - job_resource const& jr = get_job_resource(j, r); - unsigned idx = 0; - if (!first_available(jr, m_resources[r], idx)) return; - vector& available = m_resources[r].m_available; - literal l2 = mk_ge(m_jobs[j].m_start, available[idx].m_start); - if (m.has_trace_stream()) { - app_ref body(m); - body = m.mk_implies(ctx.bool_var2expr(eq.var()), ctx.bool_var2expr(l2.var())); - log_axiom_instantiation(body); - } - ctx.mk_th_axiom(get_id(), ~eq, l2); - if (m.has_trace_stream()) m.trace_stream() << "[end-of-instance]\n"; - } - - // resource(j) = r => start(j) <= end[idx] || start[idx+1] <= start(j); - void theory_jobscheduler::assert_job_not_in_gap(unsigned j, unsigned r, unsigned idx, unsigned idx1, literal eq) { - job_resource const& jr = get_job_resource(j, r); - (void) jr; - vector& available = m_resources[r].m_available; - SASSERT(resource_available(jr, available[idx])); - literal l2 = mk_ge(m_jobs[j].m_start, available[idx1].m_start); - literal l3 = mk_le(m_jobs[j].m_start, available[idx].m_end); - if (m.has_trace_stream()) { - app_ref body(m); - body = m.mk_implies(ctx.bool_var2expr(eq.var()), m.mk_or(ctx.bool_var2expr(l2.var()), ctx.bool_var2expr(l3.var()))); - log_axiom_instantiation(body); - } - ctx.mk_th_axiom(get_id(), ~eq, l2, l3); - if (m.has_trace_stream()) m.trace_stream() << "[end-of-instance]\n"; - } - - // resource(j) = r => end(j) <= end[idx] || start[idx+1] <= start(j); - void theory_jobscheduler::assert_job_non_preemptable(unsigned j, unsigned r, unsigned idx, unsigned idx1, literal eq) { - vector& available = m_resources[r].m_available; - job_resource const& jr = get_job_resource(j, r); - (void) jr; - SASSERT(resource_available(jr, available[idx])); - literal l2 = mk_le(m_jobs[j].m_end, available[idx].m_end); - literal l3 = mk_ge(m_jobs[j].m_start, available[idx1].m_start); - if (m.has_trace_stream()) { - app_ref body(m); - body = m.mk_implies(ctx.bool_var2expr(eq.var()), m.mk_or(ctx.bool_var2expr(l2.var()), ctx.bool_var2expr(l3.var()))); - log_axiom_instantiation(body); - } - ctx.mk_th_axiom(get_id(), ~eq, l2, l3); - if (m.has_trace_stream()) m.trace_stream() << "[end-of-instance]\n"; - } - - /** - * bind a job to one of the resources it can run on. - */ - bool theory_jobscheduler::split_job2resource(unsigned j) { - job_info const& ji = m_jobs[j]; - if (ji.m_is_bound) return false; - auto const& jrs = ji.m_resources; - for (job_resource const& jr : jrs) { - unsigned r = jr.m_resource_id; - res_info const& ri = m_resources[r]; - enode* e1 = ji.m_job2resource; - enode* e2 = ri.m_resource; - if (ctx.is_diseq(e1, e2)) - continue; - literal eq = mk_eq_lit(e1, e2); - if (m.has_trace_stream()) { - app_ref body(m); - body = m.mk_or(ctx.bool_var2expr(eq.var()), m.mk_not(ctx.bool_var2expr(eq.var()))); - log_axiom_instantiation(body); - m.trace_stream() << "[end-of-instance]\n"; - } - if (ctx.get_assignment(eq) != l_false) { - ctx.mark_as_relevant(eq); - if (assume_eq(e1, e2)) { - return true; - } - } - } - literal_vector lits; - expr_ref_vector exprs(m); - for (job_resource const& jr : jrs) { - unsigned r = jr.m_resource_id; - res_info const& ri = m_resources[r]; - enode* e1 = ji.m_job2resource; - enode* e2 = ri.m_resource; - literal eq = mk_eq_lit(e1, e2); - lits.push_back(eq); - exprs.push_back(ctx.bool_var2expr(eq.var())); - } - if (m.has_trace_stream()) { - app_ref body(m); - body = m.mk_or(exprs.size(), exprs.c_ptr()); - log_axiom_instantiation(body); - } - ctx.mk_th_axiom(get_id(), lits.size(), lits.c_ptr()); - if (m.has_trace_stream()) m.trace_stream() << "[end-of-instance]\n"; - return true; - } - - /** - * check that each job is run on some resource according to - * requested capacity. - * - * Check that the sum of jobs run in each time instance - * does not exceed capacity. - */ - void theory_jobscheduler::validate_assignment() { - vector> start_times, end_times; - start_times.reserve(m_resources.size()); - end_times.reserve(m_resources.size()); - for (unsigned j = 0; j < m_jobs.size(); ++j) { - unsigned r = resource(j); - start_times[r].push_back(job_time(j, start(j))); - end_times[r].push_back(job_time(j, end(j))); - if (ect(j, r, start(j)) > end(j)) { - throw default_exception("job not assigned full capacity"); - } - unsigned idx; - if (!resource_available(r, start(j), idx)) { - throw default_exception("resource is not available at job start time"); - } - } - for (unsigned r = 0; r < m_resources.size(); ++r) { - // order jobs running on r by start, end-time intervals - // then consume ordered list to find jobs in scope. - job_overlap overlap(start_times[r], end_times[r]); - while (overlap.next()) { - // check that sum of job loads does not exceed 100% - unsigned cap = 0; - for (auto j : overlap.jobs()) { - cap += get_job_resource(j, r).m_loadpct; - } - if (cap > 100) { - throw default_exception("capacity on resource exceeded"); - } - } - } - } - - bool theory_jobscheduler::job_has_resource(unsigned j, unsigned r) const { - return m_jobs[j].m_resource2index.contains(r); - } - - theory_jobscheduler::job_resource const& theory_jobscheduler::get_job_resource(unsigned j, unsigned r) const { - job_info const& ji = m_jobs[j]; - return ji.m_resources[ji.m_resource2index[r]]; - } - - /** - * find idx, if any, such that t is within the time interval of available[idx] - */ - bool theory_jobscheduler::resource_available(unsigned r, time_t t, unsigned& idx) { - vector& available = m_resources[r].m_available; - unsigned lo = 0, hi = available.size(), mid = hi / 2; - while (lo < hi) { - SASSERT(lo <= mid && mid < hi); - res_available const& ra = available[mid]; - if (ra.m_start <= t && t <= ra.m_end) { - idx = mid; - return true; - } - else if (ra.m_start > t && mid > 0) { - hi = mid; - mid = lo + (mid - lo) / 2; - } - else if (ra.m_end < t) { - lo = mid + 1; - mid += (hi - mid) / 2; - } - else { - break; - } - } - return false; - } - - /** - * compute earliest completion time for job j on resource r starting at time start. - */ - time_t theory_jobscheduler::ect(unsigned j, unsigned r, time_t start) { - job_resource const& jr = get_job_resource(j, r); - vector& available = m_resources[r].m_available; - - unsigned j_load_pct = jr.m_loadpct; - time_t cap = jr.m_capacity; - unsigned idx = 0; - if (!resource_available(r, start, idx)) { - TRACE("csp", tout << "resource is not available for " << j << " " << r << "\n";); - return std::numeric_limits::max(); - } - SASSERT(cap > 0); - - for (; idx < available.size(); ++idx) { - if (!resource_available(jr, available[idx])) continue; - start = std::max(start, available[idx].m_start); - time_t end = available[idx].m_end; - unsigned load_pct = available[idx].m_loadpct; - time_t delta = solve_for_capacity(load_pct, j_load_pct, start, end); - TRACE("csp", tout << "delta: " << delta << " capacity: " << cap << " load " - << load_pct << " jload: " << j_load_pct << " start: " << start << " end " << end << "\n";); - if (delta > cap) { - end = solve_for_end(load_pct, j_load_pct, start, cap); - cap = 0; - } - else { - cap -= delta; - } - if (cap == 0) { - return end; - } - } - return std::numeric_limits::max(); - } - - /** - * find end, such that cap = (load / job_load_pct) * (end - start + 1) - */ - time_t theory_jobscheduler::solve_for_end(unsigned load_pct, unsigned job_load_pct, time_t start, time_t cap) { - SASSERT(load_pct > 0); - SASSERT(job_load_pct > 0); - // cap = (load / job_load_pct) * (end - start + 1) - // <=> - // end - start + 1 = (cap * job_load_pct) / load - // <=> - // end = start - 1 + (cap * job_load_pct) / load - // <=> - // end = (load * (start - 1) + cap * job_load_pct) / load - unsigned load = std::min(load_pct, job_load_pct); - return (load * (start - 1) + cap * job_load_pct) / load; - } - - /** - * find start, such that cap = (load / job_load_pct) * (end - start + 1) - */ - time_t theory_jobscheduler::solve_for_start(unsigned load_pct, unsigned job_load_pct, time_t end, time_t cap) { - SASSERT(load_pct > 0); - SASSERT(job_load_pct > 0); - // cap = (load / job_load_pct) * (end - start + 1) - // <=> - // end - start + 1 = (cap * job_load_pct) / load - // <=> - // start = end + 1 - (cap * job_load_pct) / load - // <=> - // start = (load * (end + 1) - cap * job_load_pct) / load - unsigned load = std::min(load_pct, job_load_pct); - return (load * (end + 1) - cap * job_load_pct) / load; - } - - /** - * find cap, such that cap = (load / job_load_pct) * (end - start + 1) - */ - time_t theory_jobscheduler::solve_for_capacity(unsigned load_pct, unsigned job_load_pct, time_t start, time_t end) { - SASSERT(job_load_pct > 0); - unsigned load = std::min(load_pct, job_load_pct); - return (load * (end - start + 1)) / job_load_pct; - } - - /** - * Compute last start time for job on resource r. - */ - bool theory_jobscheduler::lst(unsigned j, unsigned r, time_t& start) { - start = 0; - job_resource const& jr = get_job_resource(j, r); - vector& available = m_resources[r].m_available; - unsigned j_load_pct = jr.m_loadpct; - time_t cap = jr.m_capacity; - for (unsigned idx = available.size(); idx-- > 0; ) { - if (!resource_available(jr, available[idx])) continue; - start = available[idx].m_start; - time_t end = available[idx].m_end; - unsigned load_pct = available[idx].m_loadpct; - time_t delta = solve_for_capacity(load_pct, j_load_pct, start, end); - if (delta > cap) { - start = solve_for_start(load_pct, j_load_pct, end, cap); - cap = 0; - } - else { - cap -= delta; - } - if (cap == 0) { - return true; - } - } - return false; - } - - /** - * \brief check that job properties is a subset of resource properties. - * It assumes that both vectors are sorted. - */ - - bool theory_jobscheduler::resource_available(job_resource const& jr, res_available const& ra) const { - auto const& jps = jr.m_properties; - auto const& rps = ra.m_properties; - if (jps.size() > rps.size()) { - return false; - } - unsigned j = 0, i = 0; - for (; i < jps.size() && j < rps.size(); ) { - if (jps[i] == rps[j]) { - ++i; ++j; - } - else if (lt(rps[j], jps[i])) { - ++j; - } - else { - break; - } - } - return i == jps.size(); - } - - /** - * \brief minimal current resource available for job resource, includes idx. - */ - bool theory_jobscheduler::first_available(job_resource const& jr, res_info const& ri, unsigned& idx) const { - for (; idx < ri.m_available.size(); ++idx) { - if (resource_available(jr, ri.m_available[idx])) - return true; - } - return false; - } - - /** - * \brief maximal previous resource available for job resource, excludes idx. - */ - bool theory_jobscheduler::last_available(job_resource const& jr, res_info const& ri, unsigned& idx) const { - while (idx-- > 0) { - if (resource_available(jr, ri.m_available[idx])) - return true; - } - return false; - } - - -}; - diff --git a/src/smt/theory_jobscheduler.h b/src/smt/theory_jobscheduler.h deleted file mode 100644 index 9c0e73b3030..00000000000 --- a/src/smt/theory_jobscheduler.h +++ /dev/null @@ -1,247 +0,0 @@ -/*++ -Copyright (c) 2018 Microsoft Corporation - -Module Name: - - theory_jobscheduling.h - -Abstract: - - Propagation solver for jobscheduling problems. - It relies on an external module to tighten bounds of - job variables. - -Author: - - Nikolaj Bjorner (nbjorner) 2018-09-08. - -Revision History: - ---*/ -#pragma once - -#include "util/uint_set.h" -#include "ast/csp_decl_plugin.h" -#include "ast/arith_decl_plugin.h" -#include "smt/smt_theory.h" - -namespace smt { - - typedef uint64_t time_t; - - class theory_jobscheduler : public theory { - public: - typedef svector properties; - protected: - - struct job_resource { - unsigned m_resource_id; // id of resource - time_t m_capacity; // amount of resource to use - unsigned m_loadpct; // assuming loadpct - time_t m_finite_capacity_end; - properties m_properties; - job_resource(unsigned r, time_t cap, unsigned loadpct, time_t end, properties const& ps): - m_resource_id(r), m_capacity(cap), m_loadpct(loadpct), m_finite_capacity_end(end), m_properties(ps) {} - }; - - struct job_time { - unsigned m_job; - time_t m_time; - job_time(unsigned j, time_t time): m_job(j), m_time(time) {} - - struct compare { - bool operator()(job_time const& jt1, job_time const& jt2) const { - return jt1.m_time < jt2.m_time; - } - }; - }; - - struct job_info { - bool m_is_preemptable; // can job be pre-empted - vector m_resources; // resources allowed to run job. - u_map m_resource2index; // resource to index into vector - enode* m_job; - enode* m_start; - enode* m_end; - enode* m_job2resource; - bool m_is_bound; - job_info(): m_is_preemptable(false), m_job(nullptr), m_start(nullptr), m_end(nullptr), m_job2resource(nullptr), m_is_bound(false) {} - }; - - struct res_available { - unsigned m_loadpct; - time_t m_start; - time_t m_end; - properties m_properties; - res_available(unsigned load_pct, time_t start, time_t end, properties const& ps): - m_loadpct(load_pct), - m_start(start), - m_end(end), - m_properties(ps) - {} - struct compare { - bool operator()(res_available const& ra1, res_available const& ra2) const { - return ra1.m_start < ra2.m_start; - } - }; - }; - - struct res_info { - unsigned_vector m_jobs; // jobs allocated to run on resource - vector m_available; // time intervals where resource is available - enode* m_resource; - enode* m_makespan; - res_info(): m_resource(nullptr), m_makespan(nullptr) {} - }; - - ast_manager& m; - csp_util u; - arith_util a; - vector m_jobs; - vector m_resources; - unsigned_vector m_bound_jobs; - unsigned m_bound_qhead; - struct scope { - unsigned m_bound_jobs_lim; - unsigned m_bound_qhead; - }; - svector m_scopes; - - protected: - - theory_var mk_var(enode * n) override; - - bool internalize_atom(app * atom, bool gate_ctx) override; - - bool internalize_term(app * term) override; - - void assign_eh(bool_var v, bool is_true) override {} - - void new_eq_eh(theory_var v1, theory_var v2) override; - - void new_diseq_eh(theory_var v1, theory_var v2) override; - - void push_scope_eh() override; - - void pop_scope_eh(unsigned num_scopes) override; - - final_check_status final_check_eh() override; - - bool can_propagate() override; - - void propagate() override; - - public: - - theory_jobscheduler(context& ctx); - - ~theory_jobscheduler() override {} - - void display(std::ostream & out) const override; - - void collect_statistics(::statistics & st) const override; - - bool include_func_interp(func_decl* f) override; - - void init_model(model_generator & m) override; - - model_value_proc * mk_value(enode * n, model_generator & mg) override; - - bool get_value(enode * n, expr_ref & r) override; - - theory * mk_fresh(context * new_ctx) override; - - res_info& ensure_resource(unsigned r); - job_info& ensure_job(unsigned j); - - public: - // set up job/resource global constraints - void set_preemptable(unsigned j, bool is_preemptable); - void add_job_resource(unsigned j, unsigned r, unsigned loadpct, time_t cap, time_t end, properties const& ps); - void add_resource_available(unsigned r, unsigned max_loadpct, time_t start, time_t end, properties const& ps); - void add_done(); - - // assignments - time_t est(unsigned j); // earliest start time of job j - time_t lst(unsigned j); // last start time - time_t ect(unsigned j); // earliest completion time - time_t lct(unsigned j); // last completion time - time_t start(unsigned j); // start time of job j - time_t end(unsigned j); // end time of job j - time_t get_lo(expr* e); - time_t get_up(expr* e); - time_t get_value(expr* e); - unsigned resource(unsigned j); // resource of job j - - // derived bounds - time_t ect(unsigned j, unsigned r, time_t start); - bool lst(unsigned j, unsigned r, time_t& t); - - bool resource_available(job_resource const& jr, res_available const& ra) const; - bool first_available(job_resource const& jr, res_info const& ri, unsigned& idx) const; - bool last_available(job_resource const& jr, res_info const& ri, unsigned& idx) const; - - time_t solve_for_start(unsigned load_pct, unsigned job_load_pct, time_t end, time_t cap); - time_t solve_for_end(unsigned load_pct, unsigned job_load_pct, time_t start, time_t cap); - time_t solve_for_capacity(unsigned load_pct, unsigned job_load_pct, time_t start, time_t end); - - // validate assignment - void validate_assignment(); - bool resource_available(unsigned r, time_t t, unsigned& idx); // load available on resource r at time t. - time_t capacity_used(unsigned j, unsigned r, time_t start, time_t end); // capacity used between start and end - - job_resource const& get_job_resource(unsigned j, unsigned r) const; - bool job_has_resource(unsigned j, unsigned r) const; - - // propagation - void propagate_end_time(unsigned j, unsigned r); - void propagate_job2resource(unsigned j, unsigned r); - - // final check constraints - bool constrain_end_time_interval(unsigned j, unsigned r); - bool constrain_resource_energy(unsigned r); - bool split_job2resource(unsigned j); - - void assert_last_end_time(unsigned j, unsigned r, job_resource const& jr, literal eq); - void assert_last_start_time(unsigned j, unsigned r, literal eq); - void assert_first_start_time(unsigned j, unsigned r, literal eq); - void assert_job_not_in_gap(unsigned j, unsigned r, unsigned idx, unsigned idx1, literal eq); - void assert_job_non_preemptable(unsigned j, unsigned r, unsigned idx, unsigned idx1, literal eq); - - void block_job_overlap(unsigned r, uint_set const& jobs, unsigned last_job); - - class job_overlap { - time_t m_start; - vector & m_starts, &m_ends; - unsigned s_idx, e_idx; // index into starts/ends - uint_set m_jobs; - public: - job_overlap(vector& starts, vector& ends); - bool next(); - uint_set const& jobs() const { return m_jobs; } - }; - - // term builders - literal mk_ge(expr* e, time_t t); - expr* mk_ge_expr(expr* e, time_t t); - literal mk_ge(enode* e, time_t t); - literal mk_le(expr* e, time_t t); - expr* mk_le_expr(expr* e, time_t t); - literal mk_le(enode* e, time_t t); - literal mk_le(enode* l, enode* r); - literal mk_literal(expr* e); - literal mk_eq_lit(enode * l, enode * r) { return mk_eq_lit(l->get_owner(), r->get_owner()); } - literal mk_eq_lit(expr * l, expr * r); - - void internalize_cmd(expr* cmd); - void unrecognized_argument(expr* arg) { invalid_argument("unrecognized argument ", arg); } - void invalid_argument(char const* msg, expr* arg); - - std::ostream& display(std::ostream & out, res_info const& r) const; - std::ostream& display(std::ostream & out, res_available const& r) const; - std::ostream& display(std::ostream & out, job_info const& r) const; - std::ostream& display(std::ostream & out, job_resource const& r) const; - - }; -}; - diff --git a/src/test/egraph.cpp b/src/test/egraph.cpp index 40dc63c4dbc..e03340bbbba 100644 --- a/src/test/egraph.cpp +++ b/src/test/egraph.cpp @@ -120,7 +120,9 @@ static void test3() { std::cout << g << "\n"; SASSERT(g.inconsistent()); ptr_vector js; + g.begin_explain(); g.explain(js); + g.end_explain(); for (int* j : js) { std::cout << "conflict: " << *j << "\n"; } diff --git a/src/util/dlist.h b/src/util/dlist.h index 4dcaa205ac6..9b6c87066c8 100644 --- a/src/util/dlist.h +++ b/src/util/dlist.h @@ -18,6 +18,9 @@ Revision History: --*/ #pragma once +#if 0 +-- unused + /** Add element \c elem to the list headed by \c head. NextProc and PrevProc must have the methods: @@ -131,5 +134,52 @@ bool dlist_check_invariant(T * head, NextProc const & next = NextProc(), PrevPro return true; } +#endif + +template +class dll_base { + T* m_next { nullptr }; + T* m_prev { nullptr }; +public: + + T* prev() { return m_prev; } + T* next() { return m_next; } + + void init(T* t) { + m_next = t; + m_prev = t; + } + + static void remove_from(T*& list, T* elem) { + if (list->m_next == list) { + SASSERT(elem == list); + list = nullptr; + return; + } + if (list == elem) + list = elem->m_next; + auto* next = elem->m_next; + auto* prev = elem->m_prev; + prev->m_next = next; + next->m_prev = prev; + } + + static void push_to_front(T*& list, T* elem) { + if (!list) { + list = elem; + elem->m_next = elem; + elem->m_prev = elem; + } + else if (list != elem) { + remove_from(list, elem); + list->m_prev->m_next = elem; + elem->m_prev = list->m_prev; + elem->m_next = list; + list->m_prev = elem; + list = elem; + } + } +}; + diff --git a/src/util/scoped_limit_trail.h b/src/util/scoped_limit_trail.h new file mode 100644 index 00000000000..6ea82ab82e0 --- /dev/null +++ b/src/util/scoped_limit_trail.h @@ -0,0 +1,36 @@ + +#include "util/vector.h" + +#pragma once + +class scoped_limit_trail { + unsigned_vector m_lim; + unsigned m_scopes{ 0 }; + unsigned m_last{ 0 }; + public: + + void push(unsigned n) { + if (m_last == n) + m_scopes++; + else { + for (; m_scopes > 0; --m_scopes) + m_lim.push_back(m_last); + m_last = n; + } + } + unsigned pop(unsigned n) { + SASSERT(n > 0); + SASSERT(m_scopes + m_lim.size() >= n); + if (n <= m_scopes) { + m_scopes -= n; + return m_last; + } + else { + n -= m_scopes; + m_scopes = 0; + m_last = m_lim[m_lim.size() - n]; + m_lim.shrink(m_lim.size() - n); + return m_last; + } + } +}; diff --git a/src/util/statistics.cpp b/src/util/statistics.cpp index 9c8d1fd99a7..148dec78be8 100644 --- a/src/util/statistics.cpp +++ b/src/util/statistics.cpp @@ -99,7 +99,7 @@ unsigned get_max_len(ptr_buffer & keys) { return max; } -void statistics::display_smt2(std::ostream & out) const { +std::ostream& statistics::display_smt2(std::ostream & out) const { #define INIT_DISPLAY() \ key2val m_u; \ key2dval m_d; \ @@ -140,9 +140,10 @@ void statistics::display_smt2(std::ostream & out) const { } } out << ")\n"; + return out; } -void statistics::display(std::ostream & out) const { +std::ostream& statistics::display(std::ostream & out) const { INIT_DISPLAY(); #undef DISPLAY_KEY @@ -169,6 +170,7 @@ void statistics::display(std::ostream & out) const { out << " " << std::fixed << std::setprecision(2) << d_val << "\n"; } } + return out; } template diff --git a/src/util/statistics.h b/src/util/statistics.h index be0463ca788..cea9099d154 100644 --- a/src/util/statistics.h +++ b/src/util/statistics.h @@ -32,8 +32,8 @@ class statistics { void reset(); void update(char const * key, unsigned inc); void update(char const * key, double inc); - void display(std::ostream & out) const; - void display_smt2(std::ostream & out) const; + std::ostream& display(std::ostream & out) const; + std::ostream& display_smt2(std::ostream & out) const; void display_internal(std::ostream & out) const; unsigned size() const; bool is_uint(unsigned idx) const; @@ -42,6 +42,8 @@ class statistics { double get_double_value(unsigned idx) const; }; +inline std::ostream& operator<<(std::ostream& out, statistics const& st) { return st.display(out); } + void get_memory_statistics(statistics& st); void get_rlimit_statistics(reslimit& l, statistics& st); diff --git a/src/util/union_find.h b/src/util/union_find.h index ef81c9d5685..f706cd1625f 100644 --- a/src/util/union_find.h +++ b/src/util/union_find.h @@ -36,10 +36,10 @@ class union_find_default_ctx { _trail_stack m_stack; }; -template +template class union_find { Ctx & m_ctx; - trail_stack & m_trail_stack; + trail_stack & m_trail_stack; svector m_find; svector m_size; svector m_next; @@ -47,12 +47,12 @@ class union_find { class mk_var_trail; friend class mk_var_trail; - class mk_var_trail : public trail { + class mk_var_trail : public trail { union_find & m_owner; public: mk_var_trail(union_find & o):m_owner(o) {} ~mk_var_trail() override {} - void undo(Ctx & ctx) override { + void undo(StackCtx& ctx) override { m_owner.m_find.pop_back(); m_owner.m_size.pop_back(); m_owner.m_next.pop_back(); @@ -64,13 +64,13 @@ class union_find { class merge_trail; friend class merge_trail; - class merge_trail : public trail { + class merge_trail : public trail { union_find & m_owner; unsigned m_r1; public: merge_trail(union_find & o, unsigned r1):m_owner(o), m_r1(r1) {} ~merge_trail() override {} - void undo(Ctx & ctx) override { m_owner.unmerge(m_r1); } + void undo(StackCtx& ctx) override { m_owner.unmerge(m_r1); } }; void unmerge(unsigned r1) {