diff --git a/src/ast/array_decl_plugin.h b/src/ast/array_decl_plugin.h index 529df32fa..2f24bd507 100644 --- a/src/ast/array_decl_plugin.h +++ b/src/ast/array_decl_plugin.h @@ -182,30 +182,31 @@ public: bool is_as_array_tree(expr * n); - app * mk_store(unsigned num_args, expr * const * args) { + app * mk_store(unsigned num_args, expr * const * args) const { return m_manager.mk_app(m_fid, OP_STORE, 0, nullptr, num_args, args); } - app * mk_store(expr_ref_vector const& args) { - return mk_store(args.size(), args.c_ptr()); - } - app * mk_store(ptr_vector const& args) { + app * mk_store(expr_ref_vector const& args) const { return mk_store(args.size(), args.c_ptr()); } - app * mk_select(unsigned num_args, expr * const * args) { + app * mk_store(ptr_vector const& args) const { + return mk_store(args.size(), args.c_ptr()); + } + + app * mk_select(unsigned num_args, expr * const * args) const { return m_manager.mk_app(m_fid, OP_SELECT, 0, nullptr, num_args, args); } - app * mk_select(ptr_vector const& args) { + app * mk_select(ptr_vector const& args) const { return mk_select(args.size(), args.c_ptr()); } - app * mk_select(ptr_buffer const& args) { + app * mk_select(ptr_buffer const& args) const { return mk_select(args.size(), args.c_ptr()); } - app * mk_select(expr_ref_vector const& args) { + app * mk_select(expr_ref_vector const& args) const { return mk_select(args.size(), args.c_ptr()); } diff --git a/src/sat/smt/CMakeLists.txt b/src/sat/smt/CMakeLists.txt index a53d80cc4..43b91b2f2 100644 --- a/src/sat/smt/CMakeLists.txt +++ b/src/sat/smt/CMakeLists.txt @@ -5,6 +5,7 @@ z3_add_component(sat_smt arith_internalize.cpp arith_solver.cpp array_axioms.cpp + array_diagnostics.cpp array_internalize.cpp array_model.cpp array_solver.cpp diff --git a/src/sat/smt/arith_solver.cpp b/src/sat/smt/arith_solver.cpp index 820dd7e4e..daa4bee48 100644 --- a/src/sat/smt/arith_solver.cpp +++ b/src/sat/smt/arith_solver.cpp @@ -78,7 +78,7 @@ namespace arith { } // clone rows into m_solver, m_nla, m_lia - NOT_IMPLEMENTED_YET(); + // NOT_IMPLEMENTED_YET(); return result; } diff --git a/src/sat/smt/array_axioms.cpp b/src/sat/smt/array_axioms.cpp index 0b6fa9f5c..b6b310b34 100644 --- a/src/sat/smt/array_axioms.cpp +++ b/src/sat/smt/array_axioms.cpp @@ -25,22 +25,27 @@ namespace array { void solver::push_axiom(axiom_record const& r) { unsigned idx = m_axiom_trail.size(); m_axiom_trail.push_back(r); + TRACE("array", display(tout, r) << " " << m_axioms.contains(idx) << "\n";); if (m_axioms.contains(idx)) m_axiom_trail.pop_back(); else ctx.push(push_back_vector>(m_axiom_trail)); } - bool solver::propagate_axiom(unsigned idx) { - if (m_axioms.contains(idx)) + bool solver::propagate_axiom(unsigned idx) { + if (!m_axioms.contains(idx)) { + m_axioms.insert(idx); + ctx.push(insert_map(m_axioms, idx)); + } + else if (!m_axiom_trail[idx].is_delayed()) return false; - m_axioms.insert(idx); - ctx.push(insert_map(m_axioms, idx)); return assert_axiom(idx); } bool solver::assert_axiom(unsigned idx) { axiom_record& r = m_axiom_trail[idx]; + if (!is_relevant(r)) + return false; switch (r.m_kind) { case axiom_record::kind_t::is_store: return assert_store_axiom(to_app(r.n->get_expr())); @@ -62,8 +67,7 @@ namespace array { bool solver::assert_default(axiom_record& r) { expr* child = r.n->get_expr(); SASSERT(can_beta_reduce(r.n)); - if (!ctx.is_relevant(child)) - return false; + TRACE("array", tout << "default-axiom: " << mk_bounded_pp(child, m, 2) << "\n";); if (a.is_const(child)) return assert_default_const_axiom(to_app(child)); @@ -79,30 +83,47 @@ namespace array { solver& s; unsigned m_idx; set_delay_bit(solver& s, unsigned idx) : s(s), m_idx(idx) {} - void undo(/*euf::solver& euf*/) override { + void undo() override { s.m_axiom_trail[m_idx].m_delayed = false; } }; + bool solver::is_relevant(axiom_record const& r) const { + return true; +#if 0 + // relevancy propagation is currently incomplete on terms + + expr* child = r.n->get_expr(); + switch (r.m_kind) { + case axiom_record::kind_t::is_select: { + app* select = r.select->get_app(); + for (unsigned i = 1; i < select->get_num_args(); ++i) + if (!ctx.is_relevant(select->get_arg(i))) + return false; + return ctx.is_relevant(child); + } + case axiom_record::kind_t::is_default: + return ctx.is_relevant(child); + default: + return true; + } +#endif + } + bool solver::assert_select(unsigned idx, axiom_record& r) { expr* child = r.n->get_expr(); app* select = r.select->get_app(); SASSERT(a.is_select(select)); SASSERT(can_beta_reduce(r.n)); - if (!ctx.is_relevant(child)) - return false; - for (unsigned i = 1; i < select->get_num_args(); ++i) - if (!ctx.is_relevant(select->get_arg(i))) - return false; - TRACE("array", tout << "select-axiom: " << mk_bounded_pp(select, m, 2) << " " << mk_bounded_pp(child, m, 2) << "\n";); - if (get_config().m_array_delay_exp_axiom && r.select->get_arg(0)->get_root() != r.n->get_root() && !r.m_delayed) { + TRACE("array", display(tout << "select-axiom: ", r) << "\n";); + + if (get_config().m_array_delay_exp_axiom && r.select->get_arg(0)->get_root() != r.n->get_root() && !r.m_delayed && m_enable_delay) { IF_VERBOSE(11, verbose_stream() << "delay: " << mk_bounded_pp(child, m) << " " << mk_bounded_pp(select, m) << "\n"); ctx.push(set_delay_bit(*this, idx)); r.m_delayed = true; return false; } - if (r.select->get_arg(0)->get_root() != r.n->get_root() && r.m_delayed) - return false; + r.m_delayed = false; if (a.is_const(child)) return assert_select_const_axiom(select, to_app(child)); else if (a.is_as_array(child)) @@ -163,8 +184,13 @@ namespace array { expr_ref sel_eq_e(m.mk_eq(sel1, sel2), m); euf::enode* s1 = e_internalize(sel1); euf::enode* s2 = e_internalize(sel2); + TRACE("array", + tout << "select-store " << ctx.bpp(s1) << " " << ctx.bpp(s1->get_root()) << "\n"; + tout << "select-store " << ctx.bpp(s2) << " " << ctx.bpp(s2->get_root()) << "\n";); + if (s1->get_root() == s2->get_root()) return false; + sat::literal sel_eq = mk_literal(sel_eq_e); if (s().value(sel_eq) == l_true) return false; @@ -173,8 +199,8 @@ namespace array { for (unsigned i = 1; i < num_args; i++) { expr* idx1 = store->get_arg(i); expr* idx2 = select->get_arg(i); - euf::enode* r1 = expr2enode(idx1)->get_root(); - euf::enode* r2 = expr2enode(idx2)->get_root(); + euf::enode* r1 = expr2enode(idx1); + euf::enode* r2 = expr2enode(idx2); if (r1 == r2) continue; if (m.are_distinct(r1->get_expr(), r2->get_expr())) { @@ -186,6 +212,7 @@ namespace array { if (add_clause(idx_eq, sel_eq)) new_prop = true; } + TRACE("array", tout << "select-stored " << new_prop << "\n";); return new_prop; } @@ -483,9 +510,11 @@ namespace array { bool change = false; unsigned sz = m_axiom_trail.size(); m_delay_qhead = 0; + for (; m_delay_qhead < sz; ++m_delay_qhead) - if (m_axiom_trail[m_delay_qhead].m_delayed && assert_axiom(m_delay_qhead)) - change = true; + if (m_axiom_trail[m_delay_qhead].is_delayed() && assert_axiom(m_delay_qhead)) + change = true; + flet _enable_delay(m_enable_delay, false); if (unit_propagate()) change = true; return change; diff --git a/src/sat/smt/array_diagnostics.cpp b/src/sat/smt/array_diagnostics.cpp new file mode 100644 index 000000000..4c48dfa40 --- /dev/null +++ b/src/sat/smt/array_diagnostics.cpp @@ -0,0 +1,138 @@ +/*++ +Copyright (c) 2020 Microsoft Corporation + +Module Name: + + array_diagnostics.cpp + +Abstract: + + Theory plugin for arrays, diagnostics functions + +Author: + + Nikolaj Bjorner (nbjorner) 2020-09-08 + +*/ + +#include "sat/smt/array_solver.h" +#include "sat/smt/euf_solver.h" + +namespace array { + std::ostream& solver::display(std::ostream& out) const { + if (get_num_vars() > 0) + out << "array\n"; + for (unsigned i = 0; i < get_num_vars(); ++i) { + auto& d = get_var_data(i); + out << var2enode(i)->get_expr_id() << " " << (d.m_prop_upward?"up":"fx") << " " << mk_bounded_pp(var2expr(i), m, 2) << "\n"; + display_info(out, "parent lambdas", d.m_parent_lambdas); + display_info(out, "parent select", d.m_parent_selects); + display_info(out, "lambdas", d.m_lambdas); + } + return out; + } + + std::ostream& solver::display_info(std::ostream& out, char const* id, euf::enode_vector const& v) const { + if (v.empty()) + return out; + out << id << ":\n"; + for (euf::enode* p : v) + out << " " << ctx.bpp(p) << "\n"; + return out; + } + + std::ostream& solver::display(std::ostream& out, axiom_record const& r) const { + if (r.is_delayed()) + out << "delay "; + switch (r.m_kind) { + case axiom_record::kind_t::is_store: + return out << "store " << ctx.bpp(r.n); + case axiom_record::kind_t::is_select: + return out << "select " << ctx.bpp(r.n) << " " << ctx.bpp(r.select); + case axiom_record::kind_t::is_default: + return out << "default " << ctx.bpp(r.n); + case axiom_record::kind_t::is_extensionality: + return out << "extensionality " << ctx.bpp(r.n) << " " << ctx.bpp(r.select); + case axiom_record::kind_t::is_congruence: + return out << "congruence " << ctx.bpp(r.n) << " " << ctx.bpp(r.select); + default: + UNREACHABLE(); + } + 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 { + st.update("array store", m_stats.m_num_store_axiom); + st.update("array sel/store", m_stats.m_num_select_store_axiom); + st.update("array sel/const", m_stats.m_num_select_const_axiom); + st.update("array sel/map", m_stats.m_num_select_map_axiom); + st.update("array sel/as array", m_stats.m_num_select_as_array_axiom); + st.update("array sel/lambda", m_stats.m_num_select_lambda_axiom); + st.update("array def/map", m_stats.m_num_default_map_axiom); + st.update("array def/const", m_stats.m_num_default_const_axiom); + st.update("array def/store", m_stats.m_num_default_store_axiom); + st.update("array ext ax", m_stats.m_num_extensionality_axiom); + st.update("array cong ax", m_stats.m_num_congruence_axiom); + st.update("array exp ax2", m_stats.m_num_select_store_axiom_delayed); + st.update("array splits", m_stats.m_num_eq_splits); + } + + void solver::validate_check() const { + for (euf::enode* n : ctx.get_egraph().nodes()) { + if (!ctx.is_relevant(n)) + continue; + if (a.is_select(n->get_expr()) && a.is_store(n->get_arg(0)->get_expr())) + validate_select_store(n); + if (is_array(n) && n->is_root() && ctx.is_shared(n)) { + for (euf::enode* k : ctx.get_egraph().nodes()) + if (k->get_expr_id() > n->get_expr_id() && k->is_root() && is_array(k) && ctx.is_shared(k)) + validate_extensionality(n, k); + } + expr* x = nullptr, *y = nullptr; + if (m.is_eq(n->get_expr(), x, y) && a.is_array(x)) + std::cout << ctx.bpp(n) << " " << s().value(n->bool_var()) << "\n"; + if (m.is_eq(n->get_expr(), x, y) && a.is_array(x) && s().value(n->bool_var()) == l_false) + validate_extensionality(expr2enode(x), expr2enode(y)); + } + } + + + void solver::validate_select_store(euf::enode* n) const { + SASSERT(a.is_select(n->get_expr())); + SASSERT(a.is_store(n->get_arg(0)->get_expr())); + bool same_args = true; + for (unsigned i = 1; same_args && i < n->num_args(); ++i) + same_args = n->get_arg(i)->get_root() == n->get_arg(0)->get_arg(i)->get_root(); + if (same_args) { + VERIFY(n->get_arg(0)->get_arg(n->num_args())->get_root() == n->get_root()); + return; + } + euf::enode_vector args; + ptr_vector eargs; + args.push_back(n->get_arg(0)->get_arg(0)); + for (unsigned i = 1; i < n->num_args(); ++i) + args.push_back(n->get_arg(i)); + for (euf::enode* n : args) + eargs.push_back(n->get_expr()); + expr_ref sel(a.mk_select(eargs.size(), eargs.c_ptr()), m); + euf::enode* n1 = ctx.get_egraph().find(sel, args.size(), args.c_ptr()); + if (n1 && n1->get_root() == n->get_root()) + return; + IF_VERBOSE(0, + verbose_stream() << ctx.bpp(n) << "\n"; + verbose_stream() << sel << "\n"; + verbose_stream() << n1 << " " << n->get_root() << "\n";); + } + + void solver::validate_extensionality(euf::enode* s, euf::enode* t) const { + if (s->get_sort() != t->get_sort()) + return; + IF_VERBOSE(0, + verbose_stream() << "extensionality " << ctx.bpp(s) << " " << ctx.bpp(t) << "\n";); + } + +} diff --git a/src/sat/smt/array_solver.cpp b/src/sat/smt/array_solver.cpp index e235126a8..850429579 100644 --- a/src/sat/smt/array_solver.cpp +++ b/src/sat/smt/array_solver.cpp @@ -3,7 +3,7 @@ Copyright (c) 2020 Microsoft Corporation Module Name: - array_solver.h + array_solver.cpp Abstract: @@ -101,6 +101,10 @@ namespace array { else if (!turn[idx] && add_interface_equalities()) return sat::check_result::CR_CONTINUE; } + if (m_delay_qhead < m_axiom_trail.size()) + return sat::check_result::CR_CONTINUE; + + // validate_check(); return sat::check_result::CR_DONE; } @@ -109,48 +113,6 @@ namespace array { m_var_data.resize(get_num_vars()); } - std::ostream& solver::display(std::ostream& out) const { - if (get_num_vars() > 0) - out << "array\n"; - for (unsigned i = 0; i < get_num_vars(); ++i) { - auto& d = get_var_data(i); - out << var2enode(i)->get_expr_id() << " " << mk_bounded_pp(var2expr(i), m, 2) << "\n"; - display_info(out, "parent lambdas", d.m_parent_lambdas); - display_info(out, "parent select", d.m_parent_selects); - display_info(out, "lambdas", d.m_lambdas); - } - return out; - } - - std::ostream& solver::display_info(std::ostream& out, char const* id, euf::enode_vector const& v) const { - if (v.empty()) - return out; - out << id << ": "; - for (euf::enode* p : v) - out << mk_bounded_pp(p->get_expr(), m, 2) << " "; - out << "\n"; - 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 { - st.update("array store", m_stats.m_num_store_axiom); - st.update("array sel/store", m_stats.m_num_select_store_axiom); - st.update("array sel/const", m_stats.m_num_select_const_axiom); - st.update("array sel/map", m_stats.m_num_select_map_axiom); - st.update("array sel/as array", m_stats.m_num_select_as_array_axiom); - st.update("array sel/lambda", m_stats.m_num_select_lambda_axiom); - st.update("array def/map", m_stats.m_num_default_map_axiom); - st.update("array def/const", m_stats.m_num_default_const_axiom); - st.update("array def/store", m_stats.m_num_default_store_axiom); - st.update("array ext ax", m_stats.m_num_extensionality_axiom); - st.update("array cong ax", m_stats.m_num_congruence_axiom); - st.update("array exp ax2", m_stats.m_num_select_store_axiom_delayed); - st.update("array splits", m_stats.m_num_eq_splits); - } - euf::th_solver* solver::clone(euf::solver& dst_ctx) { auto* result = alloc(solver, dst_ctx, get_id()); for (unsigned i = 0; i < get_num_vars(); ++i) @@ -186,6 +148,7 @@ namespace array { void solver::merge_eh(theory_var v1, theory_var v2, theory_var, theory_var) { euf::enode* n1 = var2enode(v1); euf::enode* n2 = var2enode(v2); + TRACE("array", tout << "merge: " << ctx.bpp(n1) << " == " << ctx.bpp(n2) << "\n";); SASSERT(n1->get_root() == n2->get_root()); SASSERT(v1 == find(v1)); expr* e1 = n1->get_expr(); @@ -207,11 +170,11 @@ namespace array { void solver::add_parent_select(theory_var v_child, euf::enode* select) { SASSERT(a.is_select(select->get_expr())); SASSERT(select->get_arg(0)->get_sort() == var2expr(v_child)->get_sort()); - v_child = find(v_child); ctx.push_vec(get_var_data(v_child).m_parent_selects, select); euf::enode* child = var2enode(v_child); - if (can_beta_reduce(child) && child != select->get_arg(0)) + TRACE("array", tout << "v" << v_child << " - " << ctx.bpp(select) << " " << ctx.bpp(child) << " prop: " << should_prop_upward(get_var_data(v_child)) << "\n";); + if (can_beta_reduce(child)) push_axiom(select_axiom(select, child)); } @@ -260,7 +223,9 @@ namespace array { expr* e = var2expr(v); if (!a.is_array(e)) return; + auto& d = get_var_data(v); + for (euf::enode* lambda : d.m_parent_lambdas) propagate_select_axioms(d, lambda); } diff --git a/src/sat/smt/array_solver.h b/src/sat/smt/array_solver.h index a039b8dfb..f0ac956e8 100644 --- a/src/sat/smt/array_solver.h +++ b/src/sat/smt/array_solver.h @@ -96,6 +96,8 @@ namespace array { bool m_delayed { false }; axiom_record(kind_t k, euf::enode* n, euf::enode* select = nullptr) : m_kind(k), n(n), select(select) {} + bool is_delayed() const { return m_delayed; } + struct hash { solver& s; hash(solver& s) :s(s) {} @@ -122,12 +124,14 @@ namespace array { svector m_axiom_trail; unsigned m_qhead { 0 }; unsigned m_delay_qhead { 0 }; + bool m_enable_delay { true }; struct set_delay_bit; void push_axiom(axiom_record const& r); bool propagate_axiom(unsigned idx); bool assert_axiom(unsigned idx); bool assert_select(unsigned idx, axiom_record & r); bool assert_default(axiom_record & r); + bool is_relevant(axiom_record const& r) const; axiom_record select_axiom(euf::enode* s, euf::enode* n) { return axiom_record(axiom_record::kind_t::is_select, n, s); } axiom_record default_axiom(euf::enode* n) { return axiom_record(axiom_record::kind_t::is_default, n); } @@ -188,7 +192,11 @@ namespace array { bool have_different_model_values(theory_var v1, theory_var v2); // diagnostics - std::ostream& display_info(std::ostream& out, char const* id, euf::enode_vector const& v) const; + std::ostream& display_info(std::ostream& out, char const* id, euf::enode_vector const& v) const; + std::ostream& display(std::ostream& out, axiom_record const& r) const; + void validate_check() const; + void validate_select_store(euf::enode* n) const; + void validate_extensionality(euf::enode* s, euf::enode* t) const; public: solver(euf::solver& ctx, theory_id id); ~solver() override; diff --git a/src/sat/smt/euf_model.cpp b/src/sat/smt/euf_model.cpp index 3695a6036..19ab5d1d9 100644 --- a/src/sat/smt/euf_model.cpp +++ b/src/sat/smt/euf_model.cpp @@ -236,13 +236,16 @@ namespace euf { expr* e = n->get_expr(); if (!m.is_bool(e)) continue; - unsigned id = n->get_root_id(); - if (!m_values.get(id)) + if (!is_relevant(n)) continue; - bool tt = m.is_true(m_values.get(id)); - if (mdl.is_true(e) != tt) { - IF_VERBOSE(0, verbose_stream() << "Failed to evaluate " << id << " " << mk_bounded_pp(e, m) << " " << mdl(e) << " " << mk_bounded_pp(m_values.get(id), m) << "\n"); + bool tt = l_true == s().value(n->bool_var()); + if (tt && mdl.is_false(e)) { + IF_VERBOSE(0, verbose_stream() << "Failed to validate " << bpp(n) << " " << mdl(e) << "\n"); + for (auto* arg : euf::enode_args(n)) + IF_VERBOSE(0, verbose_stream() << bpp(arg) << "\n" << mdl(arg->get_expr()) << "\n"); } + if (!tt && mdl.is_true(e)) + IF_VERBOSE(0, verbose_stream() << "Failed to validate " << bpp(n) << " " << mdl(e) << "\n"); } } diff --git a/src/sat/smt/euf_solver.h b/src/sat/smt/euf_solver.h index c2c2e0777..379c21a90 100644 --- a/src/sat/smt/euf_solver.h +++ b/src/sat/smt/euf_solver.h @@ -359,6 +359,7 @@ namespace euf { bool relevancy_enabled() const { return get_config().m_relevancy_lvl > 0; } void add_root(unsigned n, sat::literal const* lits); void add_aux(unsigned n, sat::literal const* lits); + void add_aux(sat::literal a, sat::literal b) { sat::literal lits[2] = {a, b}; add_aux(2, lits); } void track_relevancy(sat::bool_var v); bool is_relevant(expr* e) const; bool is_relevant(enode* n) const; diff --git a/src/smt/params/theory_array_params.h b/src/smt/params/theory_array_params.h index 7bc64ff0c..250d17ffb 100644 --- a/src/smt/params/theory_array_params.h +++ b/src/smt/params/theory_array_params.h @@ -59,23 +59,6 @@ struct theory_array_params { void updt_params(params_ref const & _p); -#if 0 - void register_params(ini_params & p) { - p.register_int_param("array_solver", 0, 3, reinterpret_cast(m_array_mode), "0 - no array, 1 - simple, 2 - model based, 3 - full"); - p.register_bool_param("array_weak", m_array_weak); - p.register_bool_param("array_extensional", m_array_extensional); - p.register_unsigned_param("array_laziness", m_array_laziness); - p.register_bool_param("array_delay_exp_axiom", m_array_delay_exp_axiom); - p.register_bool_param("array_cg", m_array_cg); - p.register_bool_param("array_always_prop_upward", m_array_always_prop_upward, - "Disable the built-in filter upwards propagation"); - p.register_bool_param("array_lazy_ieq", m_array_lazy_ieq); - p.register_unsigned_param("array_lazy_ieq_delay", m_array_lazy_ieq_delay); - p.register_bool_param("array_canonize", m_array_canonize_simplify, - "Normalize arrays into normal form during simplification"); - } -#endif - void display(std::ostream & out) const; };