3
0
Fork 0
mirror of https://github.com/Z3Prover/z3 synced 2025-08-14 23:05:26 +00:00

updates to ac-plugin

fix incrementality bugs by allowing destructive updates during saturation at the cost of redoing saturation after a pop.
This commit is contained in:
Nikolaj Bjorner 2025-07-27 13:38:24 -07:00
parent 07613942da
commit 67695b4cd6
5 changed files with 263 additions and 195 deletions

View file

@ -14,7 +14,7 @@ Author:
Nikolaj Bjorner (nbjorner) 2023-11-11 Nikolaj Bjorner (nbjorner) 2023-11-11
Completion modulo AC Completion modulo AC
E set of eqs E set of eqs
pick critical pair xy = z by j1 xu = v by j2 in E pick critical pair xy = z by j1 xu = v by j2 in E
Add new equation zu = xyu = vy by j1, j2 Add new equation zu = xyu = vy by j1, j2
@ -22,7 +22,7 @@ Completion modulo AC
Sets P - processed, R - reductions, S - to simplify Sets P - processed, R - reductions, S - to simplify
new equality l = r: new equality l = r:
reduce l = r modulo R if equation is external reduce l = r modulo R if equation is external
orient l = r - if it cannot be oriented, discard orient l = r - if it cannot be oriented, discard
if l = r is a reduction rule then reduce R, S using l = r, insert into R if l = r is a reduction rule then reduce R, S using l = r, insert into R
@ -46,9 +46,9 @@ backward subsumption e as (l = r) using (l' = r') in P u S:
is reduction rule e as (l = r): is reduction rule e as (l = r):
l is a unit, and r is unit, is empty, or is zero. l is a unit, and r is unit, is empty, or is zero.
superpose e as (l = r) with (l' = r') in P: superpose e as (l = r) with (l' = r') in P:
if l and l' share a common subset x. if l and l' share a common subset x.
forward simplify (l' = r') in P u S using e as (l = r): forward simplify (l' = r') in P u S using e as (l = r):
@ -56,10 +56,10 @@ forward simplify (l' = r') in P u S using e as (l = r):
More notes: More notes:
Justifications for new equations are joined (requires extension to egraph/justification) Justifications for new equations are joined (requires extension to egraph/justification)
Process new merges so use list is updated Process new merges so use list is updated
Justifications for processed merges are recorded Justifications for processed merges are recorded
Updated equations are recorded for restoration on backtracking Updated equations are recorded for restoration on backtracking
Keep track of foreign / shared occurrences of AC functions. Keep track of foreign / shared occurrences of AC functions.
@ -91,7 +91,7 @@ More notes:
TODOs: TODOs:
- Efficiency of handling shared terms. - Efficiency of handling shared terms.
- The shared terms hash table is not incremental. - The shared terms hash table is not incremental.
It could be made incremental by updating it on every merge similar to how the egraph handles it. It could be made incremental by updating it on every merge similar to how the egraph handles it.
- V2 using multiplicities instead of repeated values in monomials. - V2 using multiplicities instead of repeated values in monomials.
- Squash trail updates when equations or monomials are modified within the same epoch. - Squash trail updates when equations or monomials are modified within the same epoch.
@ -131,20 +131,18 @@ namespace euf {
return; return;
for (auto arg : enode_args(n)) for (auto arg : enode_args(n))
if (is_op(arg)) if (is_op(arg))
register_shared(arg); register_shared(arg);
} }
// unit -> {} // unit -> {}
void ac_plugin::add_unit(enode* u) { void ac_plugin::add_unit(enode* u) {
m_units.push_back(u); push_equation(u, nullptr);
mk_node(u);
auto m_id = to_monomial(u, {});
init_equation(eq(to_monomial(u), m_id, justification::axiom(get_id())));
} }
// zero x -> zero // zero x -> zero
void ac_plugin::add_zero(enode* z) { void ac_plugin::add_zero(enode* z) {
mk_node(z)->is_zero = true; mk_node(z)->is_zero = true;
// zeros persist
} }
void ac_plugin::register_shared(enode* n) { void ac_plugin::register_shared(enode* n) {
@ -165,12 +163,16 @@ namespace euf {
push_undo(is_register_shared); push_undo(is_register_shared);
} }
void ac_plugin::push_scope_eh() {
push_undo(is_push_scope);
}
void ac_plugin::undo() { void ac_plugin::undo() {
auto k = m_undo.back(); auto k = m_undo.back();
m_undo.pop_back(); m_undo.pop_back();
switch (k) { switch (k) {
case is_add_eq: { case is_queue_eq: {
m_active.pop_back(); m_queued.pop_back();
break; break;
} }
case is_add_node: { case is_add_node: {
@ -180,14 +182,15 @@ namespace euf {
n->~node(); n->~node();
break; break;
} }
case is_add_monomial: { case is_push_scope: {
m_monomials.pop_back(); m_active.reset();
m_passive.reset();
m_units.reset();
m_queue_head = 0;
break; break;
} }
case is_update_eq: { case is_add_monomial: {
auto const& [idx, eq] = m_update_eq_trail.back(); m_monomials.pop_back();
m_active[idx] = eq;
m_update_eq_trail.pop_back();
break; break;
} }
case is_add_shared_index: { case is_add_shared_index: {
@ -203,7 +206,7 @@ namespace euf {
break; break;
} }
case is_register_shared: { case is_register_shared: {
auto s = m_shared.back(); auto s = m_shared.back();
m_shared_nodes[s.n->get_id()] = false; m_shared_nodes[s.n->get_id()] = false;
m_shared.pop_back(); m_shared.pop_back();
break; break;
@ -316,14 +319,24 @@ namespace euf {
} }
void ac_plugin::merge_eh(enode* l, enode* r) { void ac_plugin::merge_eh(enode* l, enode* r) {
if (l == r) push_equation(l, r);
return; }
void ac_plugin::pop_equation(enode* l, enode* r) {
m_fuel += m_fuel_inc; m_fuel += m_fuel_inc;
auto j = justification::equality(l, r); if (!r) {
auto m1 = to_monomial(l); m_units.push_back(l);
auto m2 = to_monomial(r); mk_node(l);
TRACE(plugin, tout << "merge: " << m_name << " " << g.bpp(l) << " == " << g.bpp(r) << " " << m_pp_ll(*this, monomial(m1)) << " == " << m_pp_ll(*this, monomial(m2)) << "\n"); auto m_id = to_monomial(l, {});
init_equation(eq(m1, m2, j)); init_equation(eq(to_monomial(l), m_id, justification::axiom(get_id())), true);
}
else {
auto j = justification::equality(l, r);
auto m1 = to_monomial(l);
auto m2 = to_monomial(r);
TRACE(plugin, tout << "merge: " << m_name << " " << g.bpp(l) << " == " << g.bpp(r) << " " << m_pp_ll(*this, monomial(m1)) << " == " << m_pp_ll(*this, monomial(m2)) << "\n");
init_equation(eq(m1, m2, j), true);
}
} }
void ac_plugin::diseq_eh(enode* eq) { void ac_plugin::diseq_eh(enode* eq) {
@ -336,64 +349,83 @@ namespace euf {
register_shared(b); register_shared(b);
} }
bool ac_plugin::init_equation(eq const& e) { void ac_plugin::push_equation(enode* l, enode* r) {
m_active.push_back(e); if (l == r)
auto& eq = m_active.back(); return;
deduplicate(monomial(eq.l).m_nodes, monomial(eq.r).m_nodes); m_queued.push_back({ l, r });
push_undo(is_queue_eq);
if (orient_equation(eq)) {
auto& ml = monomial(eq.l);
auto& mr = monomial(eq.r);
unsigned eq_id = m_active.size() - 1;
if (ml.size() == 1 && mr.size() == 1)
push_merge(ml[0]->n, mr[0]->n, eq.j);
for (auto n : ml) {
if (!n->n->is_marked2()) {
n->eqs.push_back(eq_id);
n->n->mark2();
push_undo(is_add_eq_index);
m_node_trail.push_back(n);
for (auto s : n->shared)
m_shared_todo.insert(s);
}
}
for (auto n : mr) {
if (!n->n->is_marked2()) {
n->eqs.push_back(eq_id);
n->n->mark2();
push_undo(is_add_eq_index);
m_node_trail.push_back(n);
for (auto s : n->shared)
m_shared_todo.insert(s);
}
}
for (auto n : ml)
n->n->unmark2();
for (auto n : mr)
n->n->unmark2();
SASSERT(well_formed(eq));
TRACE(plugin, display_equation_ll(tout, eq) << " shared: " << m_shared_todo << "\n");
m_to_simplify_todo.insert(eq_id);
m_new_eqs.push_back(eq_id);
//display_equation_ll(verbose_stream() << "init " << eq_id << ": ", eq) << "\n";
return true;
}
else {
m_active.pop_back();
return false;
}
} }
bool ac_plugin::init_equation(eq eq, bool is_active) {
deduplicate(monomial(eq.l), monomial(eq.r));
if (!orient_equation(eq))
return false;
#if 0
if (is_reducing(eq))
is_active = true;
#endif
is_active = true; // set to active because forward reduction is not implemented yet.
// we will have to forward reduce a passive equation before it can be activated.
// this means that we have to iterate over all overlaps of variables in equation with
// reducing eqations. Otherwise, we will not have the invariant that reducing variables
// are eliminated from all equations.
if (!is_active) {
m_passive.push_back(eq);
return true;
}
m_active.push_back(eq);
auto& ml = monomial(eq.l);
auto& mr = monomial(eq.r);
unsigned eq_id = m_active.size() - 1;
if (ml.size() == 1 && mr.size() == 1)
push_merge(ml[0]->n, mr[0]->n, eq.j);
for (auto n : ml) {
if (!n->n->is_marked2()) {
n->eqs.push_back(eq_id);
n->n->mark2();
push_undo(is_add_eq_index);
m_node_trail.push_back(n);
for (auto s : n->shared)
m_shared_todo.insert(s);
}
}
for (auto n : mr) {
if (!n->n->is_marked2()) {
n->eqs.push_back(eq_id);
n->n->mark2();
push_undo(is_add_eq_index);
m_node_trail.push_back(n);
for (auto s : n->shared)
m_shared_todo.insert(s);
}
}
for (auto n : ml)
n->n->unmark2();
for (auto n : mr)
n->n->unmark2();
SASSERT(well_formed(eq));
TRACE(plugin, display_equation_ll(tout, eq) << " shared: " << m_shared_todo << "\n");
m_to_simplify_todo.insert(eq_id);
m_new_eqs.push_back(eq_id);
//display_equation_ll(verbose_stream() << "init " << eq_id << ": ", eq) << "\n";
return true;
}
bool ac_plugin::orient_equation(eq& e) { bool ac_plugin::orient_equation(eq& e) {
auto& ml = monomial(e.l); auto& ml = monomial(e.l);
auto& mr = monomial(e.r); auto& mr = monomial(e.r);
@ -402,7 +434,7 @@ namespace euf {
if (ml.size() < mr.size()) { if (ml.size() < mr.size()) {
std::swap(e.l, e.r); std::swap(e.l, e.r);
return true; return true;
} }
else { else {
sort(ml); sort(ml);
sort(mr); sort(mr);
@ -412,7 +444,7 @@ namespace euf {
if (ml[i]->id() < mr[i]->id()) if (ml[i]->id() < mr[i]->id())
std::swap(e.l, e.r); std::swap(e.l, e.r);
return true; return true;
} }
return false; return false;
} }
} }
@ -429,7 +461,7 @@ namespace euf {
return false; return false;
if (!is_sorted(mr)) if (!is_sorted(mr))
return false; return false;
for (unsigned i = 0; i < ml.size(); ++i) { for (unsigned i = 0; i < ml.size(); ++i) {
if (ml[i]->id() == mr[i]->id()) if (ml[i]->id() == mr[i]->id())
continue; continue;
if (ml[i]->id() < mr[i]->id()) if (ml[i]->id() < mr[i]->id())
@ -455,15 +487,21 @@ namespace euf {
uint64_t ac_plugin::filter(monomial_t& m) { uint64_t ac_plugin::filter(monomial_t& m) {
auto& bloom = m.m_bloom; auto& bloom = m.m_bloom;
if (bloom.m_tick == m_tick)
if (bloom.m_tick == m_tick) {
uint64_t f = 0;
for (auto n : m)
f |= (1ull << (n->id() % 64ull));
SASSERT(f == bloom.m_filter);
return bloom.m_filter; return bloom.m_filter;
}
bloom.m_filter = 0; bloom.m_filter = 0;
for (auto n : m) for (auto n : m)
bloom.m_filter |= (1ull << (n->id() % 64ull)); bloom.m_filter |= (1ull << (n->id() % 64ull));
if (!is_sorted(m)) if (!is_sorted(m))
sort(m); sort(m);
bloom.m_tick = m_tick; bloom.m_tick = m_tick;
return bloom.m_filter; return bloom.m_filter;
} }
bool ac_plugin::can_be_subset(monomial_t& subset, monomial_t& superset) { bool ac_plugin::can_be_subset(monomial_t& subset, monomial_t& superset) {
@ -501,10 +539,10 @@ namespace euf {
ns.push_back(n); ns.push_back(n);
for (unsigned i = 0; i < ns.size(); ++i) { for (unsigned i = 0; i < ns.size(); ++i) {
auto k = ns[i]; auto k = ns[i];
if (is_op(k)) if (is_op(k))
ns.append(k->num_args(), k->args()); ns.append(k->num_args(), k->args());
else else
m.push_back(mk_node(k)); m.push_back(mk_node(k));
} }
return to_monomial(n, m); return to_monomial(n, m);
} }
@ -562,6 +600,10 @@ namespace euf {
} }
void ac_plugin::propagate() { void ac_plugin::propagate() {
while (m_queue_head < m_queued.size()) {
auto [l, r] = m_queued[m_queue_head++];
pop_equation(l, r);
}
while (true) { while (true) {
loop_start: loop_start:
if (m_fuel == 0) if (m_fuel == 0)
@ -575,15 +617,15 @@ namespace euf {
SASSERT(well_formed(m_active[eq_id])); SASSERT(well_formed(m_active[eq_id]));
// simplify eq using processed // simplify eq using processed
TRACE(plugin, TRACE(plugin,
for (auto other_eq : forward_iterator(eq_id)) for (auto other_eq : forward_iterator(eq_id))
tout << "forward iterator " << eq_id << " vs " << other_eq << " " << is_processed(other_eq) << "\n"); tout << "forward iterator " << eq_id << " vs " << other_eq << " " << is_processed(other_eq) << "\n");
for (auto other_eq : forward_iterator(eq_id)) for (auto other_eq : forward_iterator(eq_id))
if (is_processed(other_eq) && forward_simplify(eq_id, other_eq)) if (is_processed(other_eq) && forward_simplify(eq_id, other_eq))
goto loop_start; goto loop_start;
auto& eq = m_active[eq_id]; auto& eq = m_active[eq_id];
deduplicate(monomial(eq.l).m_nodes, monomial(eq.r).m_nodes); deduplicate(monomial(eq.l), monomial(eq.r));
if (monomial(eq.l).size() == 0) { if (monomial(eq.l).size() == 0) {
set_status(eq_id, eq_status::is_dead_eq); set_status(eq_id, eq_status::is_dead_eq);
continue; continue;
@ -605,8 +647,8 @@ namespace euf {
for (auto other_eq : backward_iterator(eq_id)) for (auto other_eq : backward_iterator(eq_id))
if (is_active(other_eq)) if (is_active(other_eq))
backward_simplify(eq_id, other_eq); backward_simplify(eq_id, other_eq);
forward_subsume_new_eqs(); forward_subsume_new_eqs();
// superpose, create new equations // superpose, create new equations
unsigned new_sup = 0; unsigned new_sup = 0;
for (auto other_eq : superpose_iterator(eq_id)) for (auto other_eq : superpose_iterator(eq_id))
@ -623,12 +665,20 @@ namespace euf {
} }
unsigned ac_plugin::pick_next_eq() { unsigned ac_plugin::pick_next_eq() {
init_pick:
while (!m_to_simplify_todo.empty()) { while (!m_to_simplify_todo.empty()) {
unsigned id = *m_to_simplify_todo.begin(); unsigned id = *m_to_simplify_todo.begin();
if (id < m_active.size() && is_to_simplify(id)) if (id < m_active.size() && is_to_simplify(id))
return id; return id;
m_to_simplify_todo.remove(id); m_to_simplify_todo.remove(id);
} }
if (!m_passive.empty()) {
auto eq = m_passive.back();
verbose_stream() << "pick passive " << eq_pp_ll(*this, eq) << "\n";
m_passive.pop_back();
init_equation(eq, true);
goto init_pick;
}
return UINT_MAX; return UINT_MAX;
} }
@ -637,14 +687,10 @@ namespace euf {
auto& eq = m_active[id]; auto& eq = m_active[id];
if (eq.status == eq_status::is_dead_eq) if (eq.status == eq_status::is_dead_eq)
return; return;
if (are_equal(monomial(eq.l), monomial(eq.r))) if (are_equal(monomial(eq.l), monomial(eq.r)))
s = eq_status::is_dead_eq; s = eq_status::is_dead_eq;
if (eq.status != s) { eq.status = s;
m_update_eq_trail.push_back({ id, eq });
eq.status = s;
push_undo(is_update_eq);
}
switch (s) { switch (s) {
case eq_status::is_processed_eq: case eq_status::is_processed_eq:
case eq_status::is_reducing_eq: case eq_status::is_reducing_eq:
@ -657,7 +703,7 @@ namespace euf {
set_status(id, eq_status::is_dead_eq); set_status(id, eq_status::is_dead_eq);
} }
break; break;
} }
} }
// //
@ -673,7 +719,7 @@ namespace euf {
} }
// //
// backward iterator allows simplification of eq // forward iterator allows simplification of eq
// The rhs of eq is a super-set of lhs of other eq. // The rhs of eq is a super-set of lhs of other eq.
// //
unsigned_vector const& ac_plugin::forward_iterator(unsigned eq_id) { unsigned_vector const& ac_plugin::forward_iterator(unsigned eq_id) {
@ -733,7 +779,7 @@ namespace euf {
unsigned j = 0; unsigned j = 0;
m_eq_seen.reserve(m_active.size() + 1, false); m_eq_seen.reserve(m_active.size() + 1, false);
for (unsigned i = 0; i < m_eq_occurs.size(); ++i) { for (unsigned i = 0; i < m_eq_occurs.size(); ++i) {
unsigned id = m_eq_occurs[i]; unsigned id = m_eq_occurs[i];
if (m_eq_seen[id]) if (m_eq_seen[id])
continue; continue;
if (id == eq_id) if (id == eq_id)
@ -749,7 +795,7 @@ namespace euf {
} }
// //
// forward iterator simplifies other eqs where their rhs is a superset of lhs of eq // backward iterator simplifies other eqs where their rhs is a superset of lhs of eq
// //
unsigned_vector const& ac_plugin::backward_iterator(unsigned eq_id) { unsigned_vector const& ac_plugin::backward_iterator(unsigned eq_id) {
auto& eq = m_active[eq_id]; auto& eq = m_active[eq_id];
@ -768,7 +814,7 @@ namespace euf {
} }
void ac_plugin::init_ref_counts(monomial_t const& monomial, ref_counts& counts) const { void ac_plugin::init_ref_counts(monomial_t const& monomial, ref_counts& counts) const {
init_ref_counts(monomial.m_nodes, counts); init_ref_counts(monomial.m_nodes, counts);
} }
void ac_plugin::init_ref_counts(ptr_vector<node> const& monomial, ref_counts& counts) const { void ac_plugin::init_ref_counts(ptr_vector<node> const& monomial, ref_counts& counts) const {
@ -786,7 +832,7 @@ namespace euf {
init_ref_counts(m, check); init_ref_counts(m, check);
return return
all_of(counts, [&](unsigned i) { return check[i] == counts[i]; }) && all_of(counts, [&](unsigned i) { return check[i] == counts[i]; }) &&
all_of(check, [&](unsigned i) { return check[i] == counts[i]; }); all_of(check, [&](unsigned i) { return check[i] == counts[i]; });
} }
void ac_plugin::backward_simplify(unsigned src_eq, unsigned dst_eq) { void ac_plugin::backward_simplify(unsigned src_eq, unsigned dst_eq) {
@ -843,10 +889,8 @@ namespace euf {
reduce(m_src_r, j); reduce(m_src_r, j);
auto new_r = to_monomial(m_src_r); auto new_r = to_monomial(m_src_r);
index_new_r(dst_eq, monomial(m_active[dst_eq].r), monomial(new_r)); index_new_r(dst_eq, monomial(m_active[dst_eq].r), monomial(new_r));
m_update_eq_trail.push_back({ dst_eq, m_active[dst_eq] });
m_active[dst_eq].r = new_r; m_active[dst_eq].r = new_r;
m_active[dst_eq].j = j; m_active[dst_eq].j = j;
push_undo(is_update_eq);
m_src_r.reset(); m_src_r.reset();
m_src_r.append(monomial(src.r).m_nodes); m_src_r.append(monomial(src.r).m_nodes);
TRACE(plugin_verbose, tout << "rewritten to " << m_pp_ll(*this, monomial(new_r)) << "\n"); TRACE(plugin_verbose, tout << "rewritten to " << m_pp_ll(*this, monomial(new_r)) << "\n");
@ -862,7 +906,7 @@ namespace euf {
// //
// dst_ids, dst_count contain rhs of dst_eq // dst_ids, dst_count contain rhs of dst_eq
// //
TRACE(plugin, tout << "backward simplify " << eq_pp_ll(*this, src) << " " << eq_pp_ll(*this, dst) << " can-be-subset: " << can_be_subset(monomial(src.l), monomial(dst.r)) << "\n"); TRACE(plugin, tout << "forward simplify " << eq_pp_ll(*this, src) << " " << eq_pp_ll(*this, dst) << " can-be-subset: " << can_be_subset(monomial(src.l), monomial(dst.r)) << "\n");
if (forward_subsumes(src_eq, dst_eq)) { if (forward_subsumes(src_eq, dst_eq)) {
set_status(dst_eq, eq_status::is_dead_eq); set_status(dst_eq, eq_status::is_dead_eq);
@ -873,11 +917,11 @@ namespace euf {
// check that src.l is a subset of dst.r // check that src.l is a subset of dst.r
if (!can_be_subset(monomial(src.l), monomial(dst.r))) if (!can_be_subset(monomial(src.l), monomial(dst.r)))
return false; return false;
if (!is_subset(m_dst_r_counts, m_src_l_counts, monomial(src.l))) if (!is_subset(m_dst_r_counts, m_src_l_counts, monomial(src.l)))
return false; return false;
if (monomial(dst.r).size() == 0) if (monomial(dst.r).size() == 0)
return false; return false;
SASSERT(is_correct_ref_count(monomial(dst.r), m_dst_r_counts)); SASSERT(is_correct_ref_count(monomial(dst.r), m_dst_r_counts));
@ -885,17 +929,15 @@ namespace euf {
init_ref_counts(monomial(src.l), m_src_l_counts); init_ref_counts(monomial(src.l), m_src_l_counts);
//verbose_stream() << "forward simplify " << eq_pp_ll(*this, src_eq) << " for " << eq_pp_ll(*this, dst_eq) << "\n"; //verbose_stream() << "forward simplify " << eq_pp_ll(*this, src_eq) << " for " << eq_pp_ll(*this, dst_eq) << "\n";
rewrite1(m_src_l_counts, monomial(src.r), m_dst_r_counts, m); rewrite1(m_src_l_counts, monomial(src.r), m_dst_r_counts, m);
auto j = justify_rewrite(src_eq, dst_eq); auto j = justify_rewrite(src_eq, dst_eq);
reduce(m, j); reduce(m, j);
auto new_r = to_monomial(m); auto new_r = to_monomial(m);
index_new_r(dst_eq, monomial(m_active[dst_eq].r), monomial(new_r)); index_new_r(dst_eq, monomial(m_active[dst_eq].r), monomial(new_r));
m_update_eq_trail.push_back({ dst_eq, m_active[dst_eq] });
m_active[dst_eq].r = new_r; m_active[dst_eq].r = new_r;
m_active[dst_eq].j = j; m_active[dst_eq].j = j;
TRACE(plugin, tout << "rewritten to " << m_pp(*this, monomial(new_r)) << "\n"); TRACE(plugin, tout << "rewritten to " << m_pp(*this, monomial(new_r)) << "\n");
push_undo(is_update_eq);
return true; return true;
} }
@ -913,7 +955,7 @@ namespace euf {
m_new_eqs.reset(); m_new_eqs.reset();
} }
bool ac_plugin::is_forward_subsumed(unsigned eq_id) { bool ac_plugin::is_forward_subsumed(unsigned eq_id) {
return any_of(forward_iterator(eq_id), [&](unsigned other_eq) { return forward_subsumes(other_eq, eq_id); }); return any_of(forward_iterator(eq_id), [&](unsigned other_eq) { return forward_subsumes(other_eq, eq_id); });
} }
@ -968,14 +1010,16 @@ namespace euf {
} }
// now dst.r and src.r should align and have the same elements. // now dst.r and src.r should align and have the same elements.
// since src.r is a subset of dst.r we iterate over dst.r // since src.r is a subset of dst.r we iterate over dst.r
if (!all_of(monomial(src.r), [&](node* n) { if (!all_of(monomial(src.r), [&](node* n) {
unsigned id = n->id(); unsigned id = n->id();
return m_src_r_counts[id] == m_dst_r_counts[id]; })) { return m_src_r_counts[id] == m_dst_r_counts[id]; })) {
TRACE(plugin_verbose, tout << "dst.r and src.r do not align\n"); TRACE(plugin_verbose, tout << "dst.r and src.r do not align\n");
SASSERT(!are_equal(m_active[src_eq], m_active[dst_eq])); SASSERT(!are_equal(m_active[src_eq], m_active[dst_eq]));
return false; return false;
} }
return all_of(monomial(dst.r), [&](node* n) { unsigned id = n->id(); return m_src_r_counts[id] == m_dst_r_counts[id]; }); bool r = all_of(monomial(dst.r), [&](node* n) { unsigned id = n->id(); return m_src_r_counts[id] == m_dst_r_counts[id]; });
SASSERT(r || !are_equal(m_active[src_eq], m_active[dst_eq]));
return r;
} }
// src_l_counts, src_r_counts are initialized for src.l, src.r // src_l_counts, src_r_counts are initialized for src.l, src.r
@ -990,7 +1034,7 @@ namespace euf {
return false; return false;
unsigned size_diff = monomial(dst.l).size() - monomial(src.l).size(); unsigned size_diff = monomial(dst.l).size() - monomial(src.l).size();
if (size_diff != monomial(dst.r).size() - monomial(src.r).size()) if (size_diff != monomial(dst.r).size() - monomial(src.r).size())
return false; return false;
if (!is_superset(m_src_l_counts, m_dst_l_counts, monomial(dst.l))) if (!is_superset(m_src_l_counts, m_dst_l_counts, monomial(dst.l)))
return false; return false;
if (!is_superset(m_src_r_counts, m_dst_r_counts, monomial(dst.r))) if (!is_superset(m_src_r_counts, m_dst_r_counts, monomial(dst.r)))
@ -1026,14 +1070,14 @@ namespace euf {
unsigned dst_count = dst_counts[id]; unsigned dst_count = dst_counts[id];
unsigned src_count = src_l[id]; unsigned src_count = src_l[id];
SASSERT(dst_count > 0); SASSERT(dst_count > 0);
if (src_count == 0) { if (src_count == 0) {
dst[j++] = n; dst[j++] = n;
} }
else if (src_count < dst_count) { else if (src_count < dst_count) {
dst[j++] = n; dst[j++] = n;
dst_counts.dec(id, 1); dst_counts.dec(id, 1);
} }
} }
dst.shrink(j); dst.shrink(j);
dst.append(src_r.m_nodes); dst.append(src_r.m_nodes);
@ -1047,11 +1091,11 @@ namespace euf {
init_loop: init_loop:
if (m.size() == 1) if (m.size() == 1)
return change; return change;
bloom b; bloom b;
init_ref_counts(m, m_m_counts); init_ref_counts(m, m_m_counts);
for (auto n : m) { for (auto n : m) {
if (n->is_zero) { if (n->is_zero) {
m[0] = n; m[0] = n;
m.shrink(1); m.shrink(1);
break; break;
} }
@ -1060,9 +1104,9 @@ namespace euf {
if (!is_reducing(eq)) // also can use processed? if (!is_reducing(eq)) // also can use processed?
continue; continue;
auto& src = m_active[eq]; auto& src = m_active[eq];
if (!is_equation_oriented(src)) if (!is_equation_oriented(src))
continue; continue;
if (!can_be_subset(monomial(src.l), m, b)) if (!can_be_subset(monomial(src.l), m, b))
continue; continue;
if (!is_subset(m_m_counts, m_eq_counts, monomial(src.l))) if (!is_subset(m_m_counts, m_eq_counts, monomial(src.l)))
@ -1078,9 +1122,8 @@ namespace euf {
change = true; change = true;
goto init_loop; goto init_loop;
} }
} }
} } while (false);
while (false);
VERIFY(sz >= m.size()); VERIFY(sz >= m.size());
return change; return change;
} }
@ -1122,7 +1165,7 @@ namespace euf {
auto& src = m_active[src_eq]; auto& src = m_active[src_eq];
auto& dst = m_active[dst_eq]; auto& dst = m_active[dst_eq];
unsigned max_left = std::max(monomial(src.l).size(), monomial(dst.l).size()); unsigned max_left = std::max(monomial(src.l).size(), monomial(dst.l).size());
unsigned min_right = std::max(monomial(src.r).size(), monomial(dst.r).size()); unsigned min_right = std::max(monomial(src.r).size(), monomial(dst.r).size());
@ -1151,7 +1194,7 @@ namespace euf {
m_src_r.shrink(src_r_size); m_src_r.shrink(src_r_size);
return false; return false;
} }
// compute CD // compute CD
for (auto n : monomial(src.l)) { for (auto n : monomial(src.l)) {
unsigned id = n->id(); unsigned id = n->id();
@ -1171,18 +1214,18 @@ namespace euf {
reduce(m_src_r, j); reduce(m_src_r, j);
deduplicate(m_src_r, m_dst_r); deduplicate(m_src_r, m_dst_r);
bool added_eq = false; bool added_eq = false;
auto src_r = src.r; auto src_r = src.r;
unsigned max_left_new = std::max(m_src_r.size(), m_dst_r.size()); unsigned max_left_new = std::max(m_src_r.size(), m_dst_r.size());
unsigned min_right_new = std::min(m_src_r.size(), m_dst_r.size()); unsigned min_right_new = std::min(m_src_r.size(), m_dst_r.size());
if (max_left_new <= max_left && min_right_new <= min_right) if (max_left_new <= max_left && min_right_new <= min_right)
added_eq = init_equation(eq(to_monomial(m_src_r), to_monomial(m_dst_r), j)); added_eq = init_equation(eq(to_monomial(m_src_r), to_monomial(m_dst_r), j), false);
CTRACE(plugin, added_eq, CTRACE(plugin, added_eq,
tout << "superpose: " << m_name << " " << eq_pp_ll(*this, src) << " " << eq_pp_ll(*this, dst) << " --> "; tout << "superpose: " << m_name << " " << eq_pp_ll(*this, src) << " " << eq_pp_ll(*this, dst) << " --> ";
tout << m_pp_ll(*this, m_src_r) << "== " << m_pp_ll(*this, m_dst_r) << "\n";); tout << m_pp_ll(*this, m_src_r) << "== " << m_pp_ll(*this, m_dst_r) << "\n";);
m_src_r.reset(); m_src_r.reset();
m_src_r.append(monomial(src_r).m_nodes); m_src_r.append(monomial(src_r).m_nodes);
return added_eq; return added_eq;
@ -1191,7 +1234,7 @@ namespace euf {
bool ac_plugin::is_reducing(eq const& e) const { bool ac_plugin::is_reducing(eq const& e) const {
auto const& l = monomial(e.l); auto const& l = monomial(e.l);
auto const& r = monomial(e.r); auto const& r = monomial(e.r);
return l.size() == 1 && r.size() <= 1; return l.size() == 1 && r.size() <= 1;
} }
void ac_plugin::backward_reduce(unsigned eq_id) { void ac_plugin::backward_reduce(unsigned eq_id) {
@ -1204,21 +1247,24 @@ namespace euf {
} }
} }
// TODO: this is destructive. It breaks reversibility.
// TODO: also need justifications from eq if there is a change.
void ac_plugin::backward_reduce(eq const& eq, unsigned other_eq_id) { void ac_plugin::backward_reduce(eq const& eq, unsigned other_eq_id) {
auto& other_eq = m_active[other_eq_id]; auto& other_eq = m_active[other_eq_id];
TRACE(plugin_verbose,
tout << "backward reduce " << eq_pp_ll(*this, eq) << " " << eq_pp_ll(*this, other_eq) << "\n");
bool change = false; bool change = false;
if (backward_reduce_monomial(eq, monomial(other_eq.l))) if (backward_reduce_monomial(eq, other_eq, monomial(other_eq.l)))
change = true; change = true;
if (backward_reduce_monomial(eq, monomial(other_eq.r))) if (backward_reduce_monomial(eq, other_eq, monomial(other_eq.r)))
change = true; change = true;
if (change) CTRACE(plugin, change,
set_status(other_eq_id, eq_status::is_to_simplify_eq); tout << "backward reduce " << eq_pp_ll(*this, eq) << " " << eq_pp_ll(*this, other_eq) << "\n");
if (change) {
set_status(other_eq_id, eq_status::is_to_simplify_eq);
}
} }
bool ac_plugin::backward_reduce_monomial(eq const& eq, monomial_t& m) { bool ac_plugin::backward_reduce_monomial(eq const& src, eq& dst, monomial_t& m) {
auto const& r = monomial(eq.r); auto const& r = monomial(src.r);
unsigned j = 0; unsigned j = 0;
bool change = false; bool change = false;
for (auto n : m) { for (auto n : m) {
@ -1241,7 +1287,11 @@ namespace euf {
m.m_nodes[j++] = r[0]; m.m_nodes[j++] = r[0];
} }
m.m_nodes.shrink(j); m.m_nodes.shrink(j);
return change; if (change) {
m.m_bloom.m_tick = 0;
dst.j = join(dst.j, src);
}
return change;
} }
bool ac_plugin::are_equal(monomial_t& a, monomial_t& b) { bool ac_plugin::are_equal(monomial_t& a, monomial_t& b) {
@ -1252,8 +1302,8 @@ namespace euf {
if (a.size() != b.size()) if (a.size() != b.size())
return false; return false;
m_eq_counts.reset(); m_eq_counts.reset();
for (auto n : a) for (auto n : a)
m_eq_counts.inc(n->id(), 1); m_eq_counts.inc(n->id(), 1);
for (auto n : b) { for (auto n : b) {
unsigned id = n->id(); unsigned id = n->id();
if (m_eq_counts[id] == 0) if (m_eq_counts[id] == 0)
@ -1277,21 +1327,29 @@ namespace euf {
return true; return true;
} }
void ac_plugin::deduplicate(monomial_t& a, monomial_t& b) {
unsigned sza = a.size(), szb = b.size();
deduplicate(a.m_nodes, b.m_nodes);
if (sza != a.size())
a.m_bloom.m_tick = 0;
if (szb != b.size())
b.m_bloom.m_tick = 0;
}
void ac_plugin::deduplicate(ptr_vector<node>& a, ptr_vector<node>& b) { void ac_plugin::deduplicate(ptr_vector<node>& a, ptr_vector<node>& b) {
{ for (auto n : a) {
for (auto n : a) { if (n->is_zero) {
if (n->is_zero) { a[0] = n;
a[0] = n; a.shrink(1);
a.shrink(1); break;
break;
}
} }
for (auto n : b) { }
if (n->is_zero) { for (auto n : b) {
b[0] = n; if (n->is_zero) {
b.shrink(1); b[0] = n;
break; b.shrink(1);
} break;
} }
} }
@ -1340,14 +1398,14 @@ namespace euf {
while (!m_shared_todo.empty()) { while (!m_shared_todo.empty()) {
auto idx = *m_shared_todo.begin(); auto idx = *m_shared_todo.begin();
m_shared_todo.remove(idx); m_shared_todo.remove(idx);
if (idx < m_shared.size()) if (idx < m_shared.size())
simplify_shared(idx, m_shared[idx]); simplify_shared(idx, m_shared[idx]);
} }
m_monomial_table.reset(); m_monomial_table.reset();
for (auto const& s1 : m_shared) { for (auto const& s1 : m_shared) {
shared s2; shared s2;
TRACE(plugin_verbose, tout << "shared " << s1.m << ": " << m_pp_ll(*this, monomial(s1.m)) << "\n"); TRACE(plugin_verbose, tout << "shared " << s1.m << ": " << m_pp_ll(*this, monomial(s1.m)) << "\n");
if (!m_monomial_table.find(s1.m, s2)) if (!m_monomial_table.find(s1.m, s2))
m_monomial_table.insert(s1.m, s1); m_monomial_table.insert(s1.m, s1);
else if (s2.n->get_root() != s1.n->get_root()) { else if (s2.n->get_root() != s1.n->get_root()) {
TRACE(plugin, tout << "merge shared " << g.bpp(s1.n->get_root()) << " and " << g.bpp(s2.n->get_root()) << "\n"); TRACE(plugin, tout << "merge shared " << g.bpp(s1.n->get_root()) << " and " << g.bpp(s2.n->get_root()) << "\n");
@ -1380,14 +1438,12 @@ namespace euf {
} }
} }
for (auto n : monomial(old_m)) for (auto n : monomial(old_m))
n->n->unmark2(); n->n->unmark2();
m_update_shared_trail.push_back({ idx, s }); m_update_shared_trail.push_back({ idx, s });
push_undo(is_update_shared); push_undo(is_update_shared);
m_shared[idx].m = new_m; m_shared[idx].m = new_m;
m_shared[idx].j = j; m_shared[idx].j = j;
TRACE(plugin_verbose, tout << "shared simplified to " << m_pp_ll(*this, monomial(new_m)) << "\n"); TRACE(plugin_verbose, tout << "shared simplified to " << m_pp_ll(*this, monomial(new_m)) << "\n");
push_merge(old_n, new_n, j); push_merge(old_n, new_n, j);
} }
@ -1397,19 +1453,15 @@ namespace euf {
} }
justification::dependency* ac_plugin::justify_equation(unsigned eq) { justification::dependency* ac_plugin::justify_equation(unsigned eq) {
auto const& e = m_active[eq]; return m_dep_manager.mk_leaf(m_active[eq].j);
auto* j = m_dep_manager.mk_leaf(e.j);
j = justify_monomial(j, monomial(e.l));
j = justify_monomial(j, monomial(e.r));
return j;
}
justification::dependency* ac_plugin::justify_monomial(justification::dependency* j, monomial_t const& m) {
return j;
} }
justification ac_plugin::join(justification j, unsigned eq) { justification ac_plugin::join(justification j, unsigned eq) {
return justification::dependent(m_dep_manager.mk_join(m_dep_manager.mk_leaf(j), justify_equation(eq))); return justification::dependent(m_dep_manager.mk_join(m_dep_manager.mk_leaf(j), justify_equation(eq)));
} }
justification ac_plugin::join(justification j, eq const& eq) {
return justification::dependent(m_dep_manager.mk_join(m_dep_manager.mk_leaf(j), m_dep_manager.mk_leaf(eq.j)));
}
} }

View file

@ -123,6 +123,7 @@ namespace euf {
func_decl* m_decl = nullptr; func_decl* m_decl = nullptr;
bool m_is_injective = false; bool m_is_injective = false;
vector<eq> m_active, m_passive; vector<eq> m_active, m_passive;
enode_pair_vector m_queued;
ptr_vector<node> m_nodes; ptr_vector<node> m_nodes;
bool_vector m_shared_nodes; bool_vector m_shared_nodes;
vector<monomial_t> m_monomials; vector<monomial_t> m_monomials;
@ -146,21 +147,21 @@ namespace euf {
// backtrackable state // backtrackable state
enum undo_kind { enum undo_kind {
is_add_eq, is_queue_eq,
is_add_monomial, is_add_monomial,
is_add_node, is_add_node,
is_update_eq,
is_add_shared_index, is_add_shared_index,
is_add_eq_index, is_add_eq_index,
is_register_shared, is_register_shared,
is_update_shared is_update_shared,
is_push_scope
}; };
svector<undo_kind> m_undo; svector<undo_kind> m_undo;
ptr_vector<node> m_node_trail; ptr_vector<node> m_node_trail;
unsigned m_queue_head = 0;
svector<std::pair<unsigned, shared>> m_update_shared_trail; svector<std::pair<unsigned, shared>> m_update_shared_trail;
svector<std::tuple<node*, unsigned, unsigned>> m_merge_trail; svector<std::tuple<node*, unsigned, unsigned>> m_merge_trail;
svector<std::pair<unsigned, eq>> m_update_eq_trail;
@ -191,8 +192,8 @@ namespace euf {
bool well_formed(eq const& e) const; bool well_formed(eq const& e) const;
bool is_reducing(eq const& e) const; bool is_reducing(eq const& e) const;
void backward_reduce(unsigned eq_id); void backward_reduce(unsigned eq_id);
void backward_reduce(eq const& src, unsigned dst); void backward_reduce(eq const& eq, unsigned dst);
bool backward_reduce_monomial(eq const& eq, monomial_t& m); bool backward_reduce_monomial(eq const& src, eq & dst, monomial_t& m);
void forward_subsume_new_eqs(); void forward_subsume_new_eqs();
bool is_forward_subsumed(unsigned dst_eq); bool is_forward_subsumed(unsigned dst_eq);
bool forward_subsumes(unsigned src_eq, unsigned dst_eq); bool forward_subsumes(unsigned src_eq, unsigned dst_eq);
@ -207,8 +208,10 @@ namespace euf {
UNREACHABLE(); UNREACHABLE();
return nullptr; return nullptr;
} }
bool init_equation(eq const& e); bool init_equation(eq e, bool is_active);
void push_equation(enode* l, enode* r);
void pop_equation(enode* l, enode* r);
bool orient_equation(eq& e); bool orient_equation(eq& e);
void set_status(unsigned eq_id, eq_status s); void set_status(unsigned eq_id, eq_status s);
unsigned pick_next_eq(); unsigned pick_next_eq();
@ -217,6 +220,7 @@ namespace euf {
void backward_simplify(unsigned eq_id, unsigned using_eq); void backward_simplify(unsigned eq_id, unsigned using_eq);
bool forward_simplify(unsigned eq_id, unsigned using_eq); bool forward_simplify(unsigned eq_id, unsigned using_eq);
bool superpose(unsigned src_eq, unsigned dst_eq); bool superpose(unsigned src_eq, unsigned dst_eq);
void deduplicate(monomial_t& a, monomial_t& b);
void deduplicate(ptr_vector<node>& a, ptr_vector<node>& b); void deduplicate(ptr_vector<node>& a, ptr_vector<node>& b);
ptr_vector<node> m_src_r, m_src_l, m_dst_r, m_dst_l; ptr_vector<node> m_src_r, m_src_l, m_dst_r, m_dst_l;
@ -260,8 +264,8 @@ namespace euf {
justification justify_rewrite(unsigned eq1, unsigned eq2); justification justify_rewrite(unsigned eq1, unsigned eq2);
justification::dependency* justify_equation(unsigned eq); justification::dependency* justify_equation(unsigned eq);
justification::dependency* justify_monomial(justification::dependency* d, monomial_t const& m);
justification join(justification j1, unsigned eq); justification join(justification j1, unsigned eq);
justification join(justification j1, eq const& eq);
bool is_correct_ref_count(monomial_t const& m, ref_counts const& counts) const; bool is_correct_ref_count(monomial_t const& m, ref_counts const& counts) const;
bool is_correct_ref_count(ptr_vector<node> const& m, ref_counts const& counts) const; bool is_correct_ref_count(ptr_vector<node> const& m, ref_counts const& counts) const;
@ -301,6 +305,8 @@ namespace euf {
void undo() override; void undo() override;
void push_scope_eh() override;
void propagate() override; void propagate() override;
std::ostream& display(std::ostream& out) const override; std::ostream& display(std::ostream& out) const override;

View file

@ -43,6 +43,11 @@ namespace euf {
void undo() override; void undo() override;
void push_scope_eh() override {
m_add.push_scope_eh();
m_mul.push_scope_eh();
}
void propagate() override; void propagate() override;
std::ostream& display(std::ostream& out) const override; std::ostream& display(std::ostream& out) const override;

View file

@ -103,6 +103,9 @@ namespace euf {
m_scopes.push_back(m_updates.size()); m_scopes.push_back(m_updates.size());
m_region.push_scope(); m_region.push_scope();
m_updates.push_back(update_record(m_new_th_eqs_qhead, update_record::new_th_eq_qhead())); m_updates.push_back(update_record(m_new_th_eqs_qhead, update_record::new_th_eq_qhead()));
for (auto p : m_plugins)
if (p)
p->push_scope_eh();
} }
SASSERT(m_new_th_eqs_qhead <= m_new_th_eqs.size()); SASSERT(m_new_th_eqs_qhead <= m_new_th_eqs.size());
} }

View file

@ -52,6 +52,8 @@ namespace euf {
virtual void propagate() = 0; virtual void propagate() = 0;
virtual void undo() = 0; virtual void undo() = 0;
virtual void push_scope_eh() {}
virtual std::ostream& display(std::ostream& out) const = 0; virtual std::ostream& display(std::ostream& out) const = 0;