3
0
Fork 0
mirror of https://github.com/Z3Prover/z3 synced 2025-04-29 03:45:51 +00:00

new core perf - add merge_tf and enable_cgc distinction

perf fix for propagation behavior for equalities in the new core.
The old behavior was not to allow congruence closure on equalities.
The new behavior is to just not allow merging tf with equalities unless they appear somewhere in a foreign context (not under a Boolean operator)

The change re-surfaces merge_tf and enable_cgc distinction from the old core.
They can both be turned on or off.

merge_enabled renamed to cgc_enabled

The change is highly likely to introduce regressions in the new core.

Change propagation of literals from congruence:
- track antecedent enode. There are four ways to propagate
literals from the egraph.
- the literal is an equality and the two arguments are congruent
- the antecedent is merged with node n and the antecedent has a Boolean variable assignment.
- the antecedent is true or false, they are merged.
- the merge_tf flag is toggled to true but the node n has not been merged with true/false
This commit is contained in:
Nikolaj Bjorner 2022-11-23 11:37:07 +07:00
parent 11b712fee0
commit 22353c2d6c
10 changed files with 177 additions and 132 deletions

View file

@ -36,8 +36,10 @@ namespace euf {
}
m_expr2enode.setx(f->get_id(), n, nullptr);
push_node(n);
for (unsigned i = 0; i < num_args; ++i)
set_merge_enabled(args[i], true);
for (unsigned i = 0; i < num_args; ++i) {
set_cgc_enabled(args[i], true);
set_merge_tf_enabled(args[i], true);
}
return n;
}
@ -78,9 +80,8 @@ namespace euf {
void egraph::reinsert_equality(enode* p) {
SASSERT(p->is_equality());
if (p->value() != l_true && p->get_arg(0)->get_root() == p->get_arg(1)->get_root()) {
add_literal(p, true);
}
if (p->value() != l_true && p->get_arg(0)->get_root() == p->get_arg(1)->get_root())
add_literal(p, nullptr);
}
void egraph::force_push() {
@ -116,18 +117,16 @@ namespace euf {
m_on_make(n);
if (num_args == 0)
return n;
if (m.is_eq(f)) {
if (m.is_eq(f) && !m.is_iff(f)) {
n->set_is_equality();
update_children(n);
reinsert_equality(n);
}
else {
auto [n2, comm] = insert_table(n);
if (n2 == n)
update_children(n);
else
merge(n, n2, justification::congruence(comm, m_congruence_timestamp++));
}
auto [n2, comm] = insert_table(n);
if (n2 == n)
update_children(n);
else
merge(n, n2, justification::congruence(comm, m_congruence_timestamp++));
return n;
}
@ -158,11 +157,11 @@ namespace euf {
++m_stats.m_num_th_diseqs;
}
void egraph::add_literal(enode* n, bool is_eq) {
void egraph::add_literal(enode* n, enode* ante) {
TRACE("euf_verbose", tout << "lit: " << n->get_expr_id() << "\n";);
m_new_lits.push_back(enode_bool_pair(n, is_eq));
m_new_lits.push_back(enode_pair(n, ante));
m_updates.push_back(update_record(update_record::new_lit()));
if (is_eq) ++m_stats.m_num_eqs; else ++m_stats.m_num_lits;
if (!ante) ++m_stats.m_num_eqs; else ++m_stats.m_num_lits;
}
void egraph::new_diseq(enode* n) {
@ -173,7 +172,7 @@ namespace euf {
enode* r2 = arg2->get_root();
TRACE("euf", tout << "new-diseq: " << bpp(r1) << " " << bpp(r2) << ": " << r1->has_th_vars() << " " << r2->has_th_vars() << "\n";);
if (r1 == r2) {
add_literal(n, true);
add_literal(n, nullptr);
return;
}
if (!r1->has_th_vars())
@ -264,10 +263,26 @@ namespace euf {
root->del_th_var(tid);
}
void egraph::set_merge_enabled(enode* n, bool enable_merge) {
if (enable_merge != n->merge_enabled()) {
toggle_merge_enabled(n, false);
m_updates.push_back(update_record(n, update_record::toggle_merge()));
void egraph::set_merge_tf_enabled(enode* n, bool enable_merge_tf) {
if (!m.is_bool(n->get_sort()))
return;
if (enable_merge_tf != n->merge_tf()) {
n->set_merge_tf(enable_merge_tf);
m_updates.push_back(update_record(n, update_record::toggle_merge_tf()));
if (enable_merge_tf && n->value() != l_undef && !m.is_value(n->get_root()->get_expr())) {
expr* b = n->value() == l_true ? m.mk_true() : m.mk_false();
enode* tf = find(b);
if (!tf)
tf = mk(b, 0, 0, nullptr);
add_literal(n, tf);
}
}
}
void egraph::set_cgc_enabled(enode* n, bool enable_merge) {
if (enable_merge != n->cgc_enabled()) {
toggle_cgc_enabled(n, false);
m_updates.push_back(update_record(n, update_record::toggle_cgc()));
}
}
@ -278,9 +293,9 @@ namespace euf {
m_updates.push_back(update_record(n, update_record::set_relevant()));
}
void egraph::toggle_merge_enabled(enode* n, bool backtracking) {
bool enable_merge = !n->merge_enabled();
n->set_merge_enabled(enable_merge);
void egraph::toggle_cgc_enabled(enode* n, bool backtracking) {
bool enable_merge = !n->cgc_enabled();
n->set_cgc_enabled(enable_merge);
if (n->num_args() > 0) {
if (enable_merge) {
auto [n2, comm] = insert_table(n);
@ -290,7 +305,7 @@ namespace euf {
else if (n->is_cgr())
erase_from_table(n);
}
VERIFY(n->num_args() == 0 || !n->merge_enabled() || m_table.contains(n));
VERIFY(n->num_args() == 0 || !n->cgc_enabled() || m_table.contains(n));
}
void egraph::set_value(enode* n, lbool value, justification j) {
@ -300,6 +315,8 @@ namespace euf {
n->set_value(value);
n->m_lit_justification = j;
m_updates.push_back(update_record(n, update_record::value_assignment()));
if (n->is_equality() && n->value() == l_false)
new_diseq(n);
}
}
@ -352,8 +369,11 @@ namespace euf {
case update_record::tag_t::is_add_node:
undo_node();
break;
case update_record::tag_t::is_toggle_merge:
toggle_merge_enabled(p.r1, true);
case update_record::tag_t::is_toggle_cgc:
toggle_cgc_enabled(p.r1, true);
break;
case update_record::tag_t::is_toggle_merge_tf:
p.r1->set_merge_tf(!p.r1->merge_tf());
break;
case update_record::tag_t::is_set_parent:
undo_eq(p.r1, p.n1, p.r2_num_parents);
@ -419,7 +439,7 @@ namespace euf {
void egraph::merge(enode* n1, enode* n2, justification j) {
if (!n1->merge_enabled() && !n2->merge_enabled())
if (!n1->cgc_enabled() && !n2->cgc_enabled())
return;
SASSERT(n1->get_sort() == n2->get_sort());
enode* r1 = n1->get_root();
@ -436,6 +456,7 @@ namespace euf {
set_conflict(n1, n2, j);
return;
}
if (r1->value() != r2->value() && r1->value() != l_undef && r2->value() != l_undef) {
SASSERT(m.is_bool(r1->get_expr()));
set_conflict(n1, n2, j);
@ -448,9 +469,11 @@ namespace euf {
}
if (j.is_congruence() && (m.is_false(r2->get_expr()) || m.is_true(r2->get_expr())))
add_literal(n1, false);
if (n1->is_equality() && n1->value() == l_false)
new_diseq(n1);
add_literal(n1, r2);
if (r2->value() != l_undef && n1->value() == l_undef)
add_literal(n1, r2);
else if (r1->value() != l_undef && n2->value() == l_undef)
add_literal(n2, r1);
remove_parents(r1);
push_eq(r1, n1, r2->num_parents());
merge_justification(n1, n2, j);
@ -468,7 +491,7 @@ namespace euf {
for (enode* p : enode_parents(r)) {
if (p->is_marked1())
continue;
if (p->merge_enabled()) {
if (p->cgc_enabled()) {
if (!p->is_cgr())
continue;
SASSERT(m_table.contains_ptr(p));
@ -486,8 +509,8 @@ namespace euf {
if (!p->is_marked1())
continue;
p->unmark1();
TRACE("euf", tout << "reinsert " << bpp(r1) << " " << bpp(r2) << " " << bpp(p) << " " << p->merge_enabled() << "\n";);
if (p->merge_enabled()) {
TRACE("euf", tout << "reinsert " << bpp(r1) << " " << bpp(r2) << " " << bpp(p) << " " << p->cgc_enabled() << "\n";);
if (p->cgc_enabled()) {
auto [p_other, comm] = insert_table(p);
SASSERT(m_table.contains_ptr(p) == (p_other == p));
TRACE("euf", tout << "other " << bpp(p_other) << "\n";);
@ -531,9 +554,9 @@ namespace euf {
for (auto it = begin; it != end; ++it) {
enode* p = *it;
TRACE("euf", tout << "erase " << bpp(p) << "\n";);
SASSERT(!p->merge_enabled() || m_table.contains_ptr(p));
SASSERT(!p->merge_enabled() || p->is_cgr());
if (p->merge_enabled())
SASSERT(!p->cgc_enabled() || m_table.contains_ptr(p));
SASSERT(!p->cgc_enabled() || p->is_cgr());
if (p->cgc_enabled())
erase_from_table(p);
}
@ -541,7 +564,7 @@ namespace euf {
c->m_root = r1;
for (enode* p : enode_parents(r1))
if (p->merge_enabled() && (p->is_cgr() || !p->congruent(p->m_cg)))
if (p->cgc_enabled() && (p->is_cgr() || !p->congruent(p->m_cg)))
insert_table(p);
r2->m_parents.shrink(r2_num_parents);
unmerge_justification(n1);
@ -783,7 +806,7 @@ namespace euf {
for (enode* n : m_nodes)
n->invariant(*this);
for (enode* n : m_nodes)
if (n->merge_enabled() && n->num_args() > 0 && (!m_table.find(n) || n->get_root() != m_table.find(n)->get_root())) {
if (n->cgc_enabled() && n->num_args() > 0 && (!m_table.find(n) || n->get_root() != m_table.find(n)->get_root())) {
CTRACE("euf", !m_table.find(n), tout << "node is not in table\n";);
CTRACE("euf", m_table.find(n), tout << "root " << bpp(n->get_root()) << " table root " << bpp(m_table.find(n)->get_root()) << "\n";);
TRACE("euf", display(tout << bpp(n) << " is not closed under congruence\n"););
@ -818,7 +841,7 @@ namespace euf {
}
};
if (n->bool_var() != sat::null_bool_var)
out << "[b" << n->bool_var() << " := " << value_of() << (n->merge_tf() ? "" : " no merge") << "] ";
out << "[b" << n->bool_var() << " := " << value_of() << (n->cgc_enabled() ? "" : " no-cgc") << (n->merge_tf()? " merge-tf" : "") << "] ";
if (n->has_th_vars()) {
out << "[t";
for (auto const& v : enode_th_vars(n))
@ -873,7 +896,8 @@ namespace euf {
n2->set_value(n1->value());
n2->m_bool_var = n1->m_bool_var;
n2->m_commutative = n1->m_commutative;
n2->m_merge_enabled = n1->m_merge_enabled;
n2->m_cgc_enabled = n1->m_cgc_enabled;
n2->m_merge_tf_enabled = n1->m_merge_tf_enabled;
n2->m_is_equality = n1->m_is_equality;
}
for (unsigned i = 0; i < src.m_nodes.size(); ++i) {

View file

@ -101,7 +101,8 @@ namespace euf {
void reset() { memset(this, 0, sizeof(*this)); }
};
struct update_record {
struct toggle_merge {};
struct toggle_cgc {};
struct toggle_merge_tf {};
struct add_th_var {};
struct replace_th_var {};
struct new_lit {};
@ -114,7 +115,7 @@ namespace euf {
struct lbl_set {};
struct update_children {};
struct set_relevant {};
enum class tag_t { is_set_parent, is_add_node, is_toggle_merge, is_update_children,
enum class tag_t { is_set_parent, is_add_node, is_toggle_cgc, is_toggle_merge_tf, is_update_children,
is_add_th_var, is_replace_th_var, is_new_lit, is_new_th_eq,
is_lbl_hash, is_new_th_eq_qhead, is_new_lits_qhead,
is_inconsistent, is_value_assignment, is_lbl_set, is_set_relevant };
@ -136,8 +137,10 @@ namespace euf {
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, toggle_cgc) :
tag(tag_t::is_toggle_cgc), r1(n), n1(nullptr), r2_num_parents(UINT_MAX) {}
update_record(enode* n, toggle_merge_tf) :
tag(tag_t::is_toggle_merge_tf), 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) :
@ -186,7 +189,7 @@ namespace euf {
justification m_justification;
unsigned m_new_lits_qhead = 0;
unsigned m_new_th_eqs_qhead = 0;
svector<enode_bool_pair> m_new_lits;
svector<enode_pair> m_new_lits;
svector<th_eq> m_new_th_eqs;
bool_vector m_th_propagates_diseqs;
enode_vector m_todo;
@ -210,7 +213,7 @@ namespace euf {
void add_th_diseqs(theory_id id, theory_var v1, enode* r);
bool th_propagates_diseqs(theory_id id) const;
void add_literal(enode* n, bool is_eq);
void add_literal(enode* n, enode* ante);
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 generation, unsigned num_args, enode * const* args);
@ -229,7 +232,7 @@ namespace euf {
void push_to_lca(enode* a, enode* lca);
void push_congruence(enode* n1, enode* n2, bool commutative);
void push_todo(enode* n);
void toggle_merge_enabled(enode* n, bool backtracking);
void toggle_cgc_enabled(enode* n, bool backtracking);
enode_bool_pair insert_table(enode* p);
void erase_from_table(enode* p);
@ -289,7 +292,7 @@ namespace euf {
void add_th_diseq(theory_id id, theory_var v1, theory_var v2, expr* eq);
bool has_literal() const { return m_new_lits_qhead < m_new_lits.size(); }
bool has_th_eq() const { return m_new_th_eqs_qhead < m_new_th_eqs.size(); }
enode_bool_pair get_literal() const { return m_new_lits[m_new_lits_qhead]; }
enode_pair get_literal() const { return m_new_lits[m_new_lits_qhead]; }
th_eq get_th_eq() const { return m_new_th_eqs[m_new_th_eqs_qhead]; }
void next_literal() { force_push(); SASSERT(m_new_lits_qhead < m_new_lits.size()); m_new_lits_qhead++; }
void next_th_eq() { force_push(); SASSERT(m_new_th_eqs_qhead < m_new_th_eqs.size()); m_new_th_eqs_qhead++; }
@ -299,7 +302,9 @@ namespace euf {
void add_th_var(enode* n, theory_var v, theory_id id);
void set_th_propagates_diseqs(theory_id id);
void set_merge_enabled(enode* n, bool enable_merge);
void set_cgc_enabled(enode* n, bool enable_cgc);
void set_merge_tf_enabled(enode* n, bool enable_merge_tf);
void set_value(enode* n, lbool value, justification j);
void set_bool_var(enode* n, unsigned v) { n->set_bool_var(v); }
void set_relevant(enode* n);

View file

@ -36,7 +36,7 @@ namespace euf {
if (is_root()) {
VERIFY(!m_target);
for (enode* p : enode_parents(this)) {
if (!p->merge_enabled())
if (!p->cgc_enabled())
continue;
bool found = false;
for (enode* arg : enode_args(p)) {
@ -49,7 +49,7 @@ namespace euf {
if (c == this)
continue;
for (enode* p : enode_parents(c)) {
if (!p->merge_enabled())
if (!p->cgc_enabled())
continue;
bool found = false;
for (enode* q : enode_parents(this)) {

View file

@ -48,7 +48,8 @@ namespace euf {
bool m_mark3 = false;
bool m_commutative = false;
bool m_interpreted = false;
bool m_merge_enabled = true;
bool m_cgc_enabled = true;
bool m_merge_tf_enabled = false;
bool m_is_equality = false; // Does the expression represent an equality
bool m_is_relevant = false;
lbool m_value = l_undef; // Assignment by SAT solver for Boolean node
@ -91,7 +92,7 @@ namespace euf {
n->m_generation = generation,
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;
n->m_cgc_enabled = true;
for (unsigned i = 0; i < num_args; ++i) {
SASSERT(to_app(f)->get_arg(i) == args[i]->get_expr());
n->m_args[i] = args[i];
@ -107,7 +108,7 @@ namespace euf {
n->m_root = n;
n->m_commutative = true;
n->m_num_args = 2;
n->m_merge_enabled = true;
n->m_cgc_enabled = true;
for (unsigned i = 0; i < num_args; ++i)
n->m_args[i] = nullptr;
return n;
@ -121,7 +122,7 @@ namespace euf {
n->m_root = n;
n->m_commutative = true;
n->m_num_args = 2;
n->m_merge_enabled = true;
n->m_cgc_enabled = true;
for (unsigned i = 0; i < num_args; ++i)
n->m_args[i] = nullptr;
return n;
@ -132,7 +133,8 @@ namespace euf {
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 set_merge_enabled(bool m) { m_merge_enabled = m; }
void set_cgc_enabled(bool m) { m_cgc_enabled = m; }
void set_merge_tf(bool m) { m_merge_tf_enabled = m; }
void set_value(lbool v) { m_value = v; }
void set_justification(justification j) { m_justification = j; }
void set_is_equality() { m_is_equality = true; }
@ -152,14 +154,13 @@ namespace euf {
bool is_relevant() const { return m_is_relevant; }
void set_relevant(bool b) { m_is_relevant = b; }
lbool value() const { return m_value; }
bool value_conflict() const { return value() != l_undef && get_root()->value() != l_undef && value() != get_root()->value(); }
sat::bool_var bool_var() const { return m_bool_var; }
bool is_cgr() const { return this == m_cg; }
enode* get_cg() const { return m_cg; }
bool commutative() const { return m_commutative; }
void mark_interpreted() { SASSERT(num_args() == 0); m_interpreted = true; }
bool merge_enabled() const { return m_merge_enabled; }
bool merge_tf() const { return merge_enabled() && (class_size() > 1 || num_parents() > 0 || num_args() > 0); }
bool cgc_enabled() const { return m_cgc_enabled; }
bool merge_tf() const { return m_merge_tf_enabled && (class_size() > 1 || num_parents() > 0 || num_args() > 0); }
enode* get_arg(unsigned i) const { SASSERT(i < num_args()); return m_args[i]; }
unsigned hash() const { return m_expr->get_id(); }