diff --git a/src/ast/expr_substitution.h b/src/ast/expr_substitution.h index 0e285ff7d..8f756e061 100644 --- a/src/ast/expr_substitution.h +++ b/src/ast/expr_substitution.h @@ -59,6 +59,10 @@ public: std::ostream& display(std::ostream& out); }; +inline std::ostream& operator<<(std::ostream& out, expr_substitution& s) { + return s.display(out); +} + class scoped_expr_substitution { expr_substitution& m_subst; expr_ref_vector m_trail; diff --git a/src/ast/for_each_expr.cpp b/src/ast/for_each_expr.cpp index 54e176fc5..0790b418d 100644 --- a/src/ast/for_each_expr.cpp +++ b/src/ast/for_each_expr.cpp @@ -64,15 +64,22 @@ bool has_skolem_functions(expr * n) { return false; } -subterms::subterms(expr_ref_vector const& es, bool include_bound): m_include_bound(include_bound), m_es(es) {} -subterms::subterms(expr_ref const& e, bool include_bound) : m_include_bound(include_bound), m_es(e.m()) {if (e) m_es.push_back(e); } -subterms::iterator subterms::begin() { return iterator(*this, true); } -subterms::iterator subterms::end() { return iterator(*this, false); } -subterms::iterator::iterator(subterms& f, bool start): m_include_bound(f.m_include_bound), m_es(f.m_es) { - if (!start) m_es.reset(); +subterms::subterms(expr_ref_vector const& es, bool include_bound, ptr_vector* esp, expr_mark* vp): m_include_bound(include_bound), m_es(es), m_esp(esp), m_vp(vp) {} +subterms::subterms(expr_ref const& e, bool include_bound, ptr_vector* esp, expr_mark* vp) : m_include_bound(include_bound), m_es(e.m()), m_esp(esp), m_vp(vp) { if (e) m_es.push_back(e); } +subterms::iterator subterms::begin() { return iterator(* this, m_esp, m_vp, true); } +subterms::iterator subterms::end() { return iterator(*this, nullptr, nullptr, false); } +subterms::iterator::iterator(subterms& f, ptr_vector* esp, expr_mark* vp, bool start): m_include_bound(f.m_include_bound), m_esp(esp), m_visitedp(vp) { + if (!esp) + m_esp = &m_es; + else + m_esp->reset(); + if (!m_visitedp) + m_visitedp = &m_visited; + if (start) + m_esp->append(f.m_es.size(), f.m_es.data()); } expr* subterms::iterator::operator*() { - return m_es.back(); + return m_esp->back(); } subterms::iterator subterms::iterator::operator++(int) { iterator tmp = *this; @@ -80,27 +87,29 @@ subterms::iterator subterms::iterator::operator++(int) { return tmp; } subterms::iterator& subterms::iterator::operator++() { - expr* e = m_es.back(); - m_visited.mark(e, true); + expr* e = m_esp->back(); + // IF_VERBOSE(0, verbose_stream() << e->get_ref_count() << "\n"); + SASSERT(e->get_ref_count() > 0); + m_visitedp->mark(e, true); if (is_app(e)) for (expr* arg : *to_app(e)) - m_es.push_back(arg); + m_esp->push_back(arg); else if (is_quantifier(e) && m_include_bound) - m_es.push_back(to_quantifier(e)->get_expr()); + m_esp->push_back(to_quantifier(e)->get_expr()); - while (!m_es.empty() && m_visited.is_marked(m_es.back())) - m_es.pop_back(); + while (!m_esp->empty() && m_visitedp->is_marked(m_esp->back())) + m_esp->pop_back(); return *this; } bool subterms::iterator::operator==(iterator const& other) const { // ignore state of visited - if (other.m_es.size() != m_es.size()) { + if (other.m_esp->size() != m_esp->size()) { return false; } - for (unsigned i = m_es.size(); i-- > 0; ) { - if (m_es.get(i) != other.m_es.get(i)) + for (unsigned i = m_esp->size(); i-- > 0; ) { + if (m_esp->get(i) != other.m_esp->get(i)) return false; } return true; diff --git a/src/ast/for_each_expr.h b/src/ast/for_each_expr.h index b724bed86..99a0f6b9d 100644 --- a/src/ast/for_each_expr.h +++ b/src/ast/for_each_expr.h @@ -170,15 +170,20 @@ bool has_skolem_functions(expr * n); class subterms { bool m_include_bound = false; expr_ref_vector m_es; - subterms(expr_ref const& e, bool include_bound); - subterms(expr_ref_vector const& es, bool include_bound); + ptr_vector* m_esp = nullptr; + expr_mark* m_vp = nullptr; + subterms(expr_ref const& e, bool include_bound, ptr_vector* esp, expr_mark* vp); + subterms(expr_ref_vector const& es, bool include_bound, ptr_vector* esp, expr_mark* vp); public: + ~subterms() { if (m_vp) m_vp->reset(); } class iterator { - bool m_include_bound = false; - expr_ref_vector m_es; - expr_mark m_visited; + bool m_include_bound = false; + ptr_vector m_es; + ptr_vector* m_esp = nullptr; + expr_mark m_visited; + expr_mark* m_visitedp = nullptr; public: - iterator(subterms& f, bool start); + iterator(subterms& f, ptr_vector* esp, expr_mark* vp, bool start); expr* operator*(); iterator operator++(int); iterator& operator++(); @@ -186,11 +191,10 @@ public: bool operator!=(iterator const& other) const; }; - - static subterms all(expr_ref const& e) { return subterms(e, true); } - static subterms ground(expr_ref const& e) { return subterms(e, false); } - static subterms all(expr_ref_vector const& e) { return subterms(e, true); } - static subterms ground(expr_ref_vector const& e) { return subterms(e, false); } + static subterms all(expr_ref const& e, ptr_vector* esp = nullptr, expr_mark* vp = nullptr) { return subterms(e, true, esp, vp); } + static subterms ground(expr_ref const& e, ptr_vector* esp = nullptr, expr_mark* vp = nullptr) { return subterms(e, false, esp, vp); } + static subterms all(expr_ref_vector const& e, ptr_vector* esp = nullptr, expr_mark* vp = nullptr) { return subterms(e, true, esp, vp); } + static subterms ground(expr_ref_vector const& e, ptr_vector* esp = nullptr, expr_mark* vp = nullptr) { return subterms(e, false, esp, vp); } iterator begin(); iterator end(); }; diff --git a/src/ast/rewriter/bool_rewriter.cpp b/src/ast/rewriter/bool_rewriter.cpp index 1964e6528..442bef855 100644 --- a/src/ast/rewriter/bool_rewriter.cpp +++ b/src/ast/rewriter/bool_rewriter.cpp @@ -244,10 +244,29 @@ br_status bool_rewriter::mk_nflat_or_core(unsigned num_args, expr * const * args result = buffer.back(); return BR_DONE; default: +#if 0 + // stupid or removal. A very special case of circuit optimization. + expr* x, * y, * z, * u; + auto is_complement = [&](expr* a, expr* b) { + expr* c; + if (m().is_not(a, c) && c == b) + return true; + if (m().is_not(b, c) && c == a) + return true; + return false; + }; + + if (sz == 2 && m().is_and(buffer[0], x, y) && m().is_and(buffer[1], z, u) && x == z && is_complement(y, u)) { + result = x; + return BR_DONE; + } +#endif + if (m_local_ctx && m_local_ctx_cost <= m_local_ctx_limit) { if (local_ctx_simp(sz, buffer.data(), result)) return BR_DONE; } + if (s) { ast_lt lt; std::sort(buffer.begin(), buffer.end(), lt); @@ -556,9 +575,7 @@ bool bool_rewriter::local_ctx_simp(unsigned num_args, expr * const * args, expr_ return true; \ } \ if (m_flat_and_or && m().is_or(arg)) { \ - unsigned sz = to_app(arg)->get_num_args(); \ - for (unsigned j = 0; j < sz; j++) { \ - expr * arg_arg = to_app(arg)->get_arg(j); \ + for (expr * arg_arg : *to_app(arg)) { \ push_new_arg(arg_arg, new_args, neg_lits, pos_lits); \ } \ } \ diff --git a/src/ast/simplifiers/extract_eqs.cpp b/src/ast/simplifiers/extract_eqs.cpp index e77ce9e06..543030e91 100644 --- a/src/ast/simplifiers/extract_eqs.cpp +++ b/src/ast/simplifiers/extract_eqs.cpp @@ -37,6 +37,8 @@ namespace euf { auto [f, d] = e(); expr* x, * y; if (m.is_eq(f, x, y)) { + if (x == y) + return; if (is_uninterp_const(x)) eqs.push_back(dependent_eq(e.fml(), to_app(x), expr_ref(y, m), d)); if (is_uninterp_const(y)) diff --git a/src/ast/simplifiers/solve_context_eqs.cpp b/src/ast/simplifiers/solve_context_eqs.cpp index 766c18535..c11505726 100644 --- a/src/ast/simplifiers/solve_context_eqs.cpp +++ b/src/ast/simplifiers/solve_context_eqs.cpp @@ -30,6 +30,7 @@ if every occurrence of v occurs in the same conjunctive context as eq. #include "ast/ast.h" #include "ast/ast_pp.h" +#include "ast/ast_ll_pp.h" #include "ast/occurs.h" #include "ast/simplifiers/solve_context_eqs.h" #include "ast/simplifiers/solve_eqs.h" @@ -134,14 +135,6 @@ namespace euf { return false; } - void solve_context_eqs::init_contains(expr* v) { - m_contains_v.reset(); - for (unsigned i = 0; i < m_fmls.size(); ++i) - m_todo.push_back(m_fmls[i].fml()); - mark_occurs(m_todo, v, m_contains_v); - SASSERT(m_todo.empty()); - } - void solve_context_eqs::collect_nested_equalities(dep_eq_vector& eqs) { expr_mark visited; for (unsigned i = m_solve_eqs.m_qhead; i < m_fmls.size(); ++i) @@ -149,7 +142,23 @@ namespace euf { unsigned j = 0; for (auto const& eq : eqs) { - init_contains(eq.var); + + m_contains_v.reset(); + + // first check if v is in term. If it is, then the substitution candidate is unsafe + m_todo.push_back(eq.term); + mark_occurs(m_todo, eq.var, m_contains_v); + SASSERT(m_todo.empty()); + if (m_contains_v.is_marked(eq.term)) + continue; + + // then mark occurrences + for (unsigned i = 0; i < m_fmls.size(); ++i) + m_todo.push_back(m_fmls[i].fml()); + mark_occurs(m_todo, eq.var, m_contains_v); + SASSERT(m_todo.empty()); + + // subject to occurrences, check if equality is safe if (is_safe_eq(eq.orig)) eqs[j++] = eq; } diff --git a/src/ast/simplifiers/solve_context_eqs.h b/src/ast/simplifiers/solve_context_eqs.h index b3db74127..fb330b57b 100644 --- a/src/ast/simplifiers/solve_context_eqs.h +++ b/src/ast/simplifiers/solve_context_eqs.h @@ -44,9 +44,7 @@ namespace euf { bool is_disjunctively_safe(unsigned recursion_depth, expr* f, bool sign, expr* e); bool contains_conjunctively(expr* f, bool sign, expr* e, signed_expressions& conjuncts); - void collect_nested_equalities(dependent_expr const& f, expr_mark& visited, dep_eq_vector& eqs); - void init_contains(expr* v); - + void collect_nested_equalities(dependent_expr const& f, expr_mark& visited, dep_eq_vector& eqs); public: diff --git a/src/ast/simplifiers/solve_eqs.cpp b/src/ast/simplifiers/solve_eqs.cpp index 31e063119..b611e0144 100644 --- a/src/ast/simplifiers/solve_eqs.cpp +++ b/src/ast/simplifiers/solve_eqs.cpp @@ -20,6 +20,8 @@ Author: #include "ast/ast_util.h" #include "ast/for_each_expr.h" #include "ast/ast_pp.h" +#include "ast/ast_ll_pp.h" +#include "ast/occurs.h" #include "ast/recfun_decl_plugin.h" #include "ast/rewriter/expr_replacer.h" #include "ast/simplifiers/solve_eqs.h" @@ -75,13 +77,15 @@ namespace euf { auto is_safe = [&](unsigned lvl, expr* t) { for (auto* e : subterms::all(expr_ref(t, m))) + for (auto* e : subterms::all(expr_ref(t, m), &m_todo, &m_visited)) if (is_var(e) && m_id2level[var2id(e)] < lvl) - return false; + return false; return true; }; unsigned init_level = UINT_MAX; unsigned_vector todo; + for (unsigned id = 0; id < m_id2var.size(); ++id) { if (is_explored(id)) continue; @@ -96,14 +100,16 @@ namespace euf { todo.pop_back(); if (is_explored(j)) continue; - m_id2level[id] = curr_level++; + m_id2level[j] = curr_level++; for (auto const& eq : m_next[j]) { auto const& [orig, v, t, d] = eq; + SASSERT(j == var2id(v)); if (!is_safe(curr_level, t)) continue; + SASSERT(!occurs(v, t)); m_next[j][0] = eq; - m_subst_ids.push_back(id); - for (expr* e : subterms::all(expr_ref(t, m))) + m_subst_ids.push_back(j); + for (expr* e : subterms::all(expr_ref(t, m), &m_todo, &m_visited)) if (is_var(e) && !is_explored(var2id(e))) todo.push_back(var2id(e)); break; @@ -113,19 +119,20 @@ namespace euf { } void solve_eqs::normalize() { - scoped_ptr rp = mk_default_expr_replacer(m, true); + scoped_ptr rp = mk_default_expr_replacer(m, false); rp->set_substitution(m_subst.get()); std::sort(m_subst_ids.begin(), m_subst_ids.end(), [&](unsigned u, unsigned v) { return m_id2level[u] > m_id2level[v]; }); for (unsigned id : m_subst_ids) { if (!m.inc()) - break; + return; auto const& [orig, v, def, dep] = m_next[id][0]; auto [new_def, new_dep] = rp->replace_with_dep(def); m_stats.m_num_steps += rp->get_num_steps() + 1; ++m_stats.m_num_elim_vars; new_dep = m.mk_join(dep, new_dep); + IF_VERBOSE(11, verbose_stream() << mk_bounded_pp(v, m) << " -> " << mk_bounded_pp(new_def, m) << "\n"); m_subst->insert(v, new_def, new_dep); SASSERT(can_be_var(v)); // we updated the substitution, but we don't need to reset rp @@ -139,12 +146,14 @@ namespace euf { expr* def = m_subst->find(eq.var); tout << mk_pp(eq.var, m) << "\n----->\n" << mk_pp(def, m) << "\n\n"; }); + + } void solve_eqs::apply_subst(vector& old_fmls) { if (!m.inc()) return; - scoped_ptr rp = mk_default_expr_replacer(m, true); + scoped_ptr rp = mk_default_expr_replacer(m, false); rp->set_substitution(m_subst.get()); for (unsigned i = m_qhead; i < m_fmls.size() && !m_fmls.inconsistent(); ++i) { @@ -152,6 +161,7 @@ namespace euf { auto [new_f, new_dep] = rp->replace_with_dep(f); if (new_f == f) continue; + m_rewriter(new_f); new_dep = m.mk_join(d, new_dep); old_fmls.push_back(m_fmls[i]); m_fmls.update(i, dependent_expr(m, new_f, new_dep)); @@ -164,29 +174,35 @@ namespace euf { ex->pre_process(m_fmls); unsigned count = 0; + vector old_fmls; + dep_eq_vector eqs; do { - vector old_fmls; + old_fmls.reset(); m_subst_ids.reset(); - if (!m.inc()) - return; - dep_eq_vector eqs; + eqs.reset(); get_eqs(eqs); extract_dep_graph(eqs); extract_subst(); + normalize(); apply_subst(old_fmls); ++count; } - while (!m_subst_ids.empty() && count < 20); + while (!m_subst_ids.empty() && count < 20 && m.inc()); + + if (!m.inc()) + return; + save_subst({}); - if (m_config.m_context_solve) { - vector old_fmls; - dep_eq_vector eqs; + if (m_config.m_context_solve) { + old_fmls.reset(); m_subst_ids.reset(); + eqs.reset(); solve_context_eqs context_solve(*this); context_solve.collect_nested_equalities(eqs); extract_dep_graph(eqs); extract_subst(); + normalize(); apply_subst(old_fmls); save_subst(old_fmls); } @@ -203,7 +219,7 @@ namespace euf { m_unsafe_vars.reset(); recfun::util rec(m); for (func_decl* f : rec.get_rec_funs()) - for (expr* term : subterms::all(expr_ref(rec.get_def(f).get_rhs(), m))) + for (expr* term : subterms::all(expr_ref(rec.get_def(f).get_rhs(), m), &m_todo, &m_visited)) m_unsafe_vars.mark(term); } diff --git a/src/ast/simplifiers/solve_eqs.h b/src/ast/simplifiers/solve_eqs.h index 35044b373..8f5988a38 100644 --- a/src/ast/simplifiers/solve_eqs.h +++ b/src/ast/simplifiers/solve_eqs.h @@ -50,6 +50,8 @@ namespace euf { vector m_next; // adjacency list for solved equations scoped_ptr m_subst; // current substitution expr_mark m_unsafe_vars; // expressions that cannot be replaced + ptr_vector m_todo; + expr_mark m_visited; bool is_var(expr* e) const { return e->get_id() < m_var2id.size() && m_var2id[e->get_id()] != UINT_MAX; } unsigned var2id(expr* v) const { return m_var2id[v->get_id()]; } diff --git a/src/tactic/core/CMakeLists.txt b/src/tactic/core/CMakeLists.txt index e57510d4f..38d2699f0 100644 --- a/src/tactic/core/CMakeLists.txt +++ b/src/tactic/core/CMakeLists.txt @@ -49,6 +49,7 @@ z3_add_component(core_tactics reduce_invertible_tactic.h simplify_tactic.h solve_eqs_tactic.h + solve_eqs2_tactic.h special_relations_tactic.h split_clause_tactic.h symmetry_reduce_tactic.h diff --git a/src/tactic/core/solve_eqs_tactic.cpp b/src/tactic/core/solve_eqs_tactic.cpp index cfc4d8eeb..fdba65b3f 100644 --- a/src/tactic/core/solve_eqs_tactic.cpp +++ b/src/tactic/core/solve_eqs_tactic.cpp @@ -425,6 +425,7 @@ class solve_eqs_tactic : public tactic { else pr = m().mk_modus_ponens(g.pr(idx), pr); } + IF_VERBOSE(11, verbose_stream() << mk_bounded_pp(var, m()) << " -> " << mk_bounded_pp(def, m()) << "\n"); m_subst->insert(var, def, pr, g.dep(idx)); } @@ -620,9 +621,11 @@ class solve_eqs_tactic : public tactic { expr* arg = args.get(i), *lhs = nullptr, *rhs = nullptr; if (m().is_eq(arg, lhs, rhs) && !m().is_bool(lhs)) { if (trivial_solve1(lhs, rhs, var, def, pr) && is_compatible(g, idx, path, var, arg)) { + IF_VERBOSE(11, verbose_stream() << "nested " << mk_bounded_pp(var.get(), m()) << " -> " << mk_bounded_pp(def, m()) << "\n"); insert_solution(g, idx, arg, var, def, pr); } else if (trivial_solve1(rhs, lhs, var, def, pr) && is_compatible(g, idx, path, var, arg)) { + IF_VERBOSE(11, verbose_stream() << "nested " << mk_bounded_pp(var.get(), m()) << " -> " << mk_bounded_pp(def, m()) << "\n"); insert_solution(g, idx, arg, var, def, pr); } else { @@ -981,6 +984,10 @@ class solve_eqs_tactic : public tactic { unsigned get_num_eliminated_vars() const { return m_num_eliminated_vars; } + + void collect_statistics(statistics& st) { + st.update("solve eqs elim vars", get_num_eliminated_vars()); + } // // TBD: rewrite the tactic to first apply a topological sorting that @@ -1033,6 +1040,9 @@ class solve_eqs_tactic : public tactic { g->inc_depth(); g->add(mc.get()); result.push_back(g.get()); + + + IF_VERBOSE(10, statistics st; collect_statistics(st); st.display_smt2(verbose_stream())); } }; @@ -1066,7 +1076,6 @@ public: void operator()(goal_ref const & in, goal_ref_buffer & result) override { (*m_imp)(in, result); - report_tactic_progress(":num-elim-vars", m_imp->get_num_eliminated_vars()); } void cleanup() override { @@ -1085,7 +1094,7 @@ public: } void collect_statistics(statistics & st) const override { - st.update("eliminated vars", m_imp->get_num_eliminated_vars()); + m_imp->collect_statistics(st); } void reset_statistics() override { diff --git a/src/tactic/dependent_expr_state_tactic.h b/src/tactic/dependent_expr_state_tactic.h index 41b32baac..01e135e8a 100644 --- a/src/tactic/dependent_expr_state_tactic.h +++ b/src/tactic/dependent_expr_state_tactic.h @@ -93,6 +93,10 @@ public: if (in->models_enabled()) in->set(m_model_trail->get_model_converter().get()); result.push_back(in.get()); + + statistics st; + collect_statistics(st); + IF_VERBOSE(10, st.display_smt2(verbose_stream())); } void cleanup() override {