3
0
Fork 0
mirror of https://github.com/Z3Prover/z3 synced 2025-04-24 01:25:31 +00:00

bv and gc of literals (#4692)

* bv and gc of literals

Signed-off-by: Nikolaj Bjorner <nbjorner@microsoft.com>

* overload

Signed-off-by: Nikolaj Bjorner <nbjorner@microsoft.com>

* diseq

Signed-off-by: Nikolaj Bjorner <nbjorner@microsoft.com>

* diseq

Signed-off-by: Nikolaj Bjorner <nbjorner@microsoft.com>
This commit is contained in:
Nikolaj Bjorner 2020-09-17 14:24:07 -07:00 committed by GitHub
parent 2d52367368
commit 549753845e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
34 changed files with 1480 additions and 854 deletions

View file

@ -41,6 +41,7 @@ namespace euf {
enode* n = enode::mk(m_region, f, num_args, args);
m_nodes.push_back(n);
m_exprs.push_back(f);
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);
@ -65,7 +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()) {
if (p->get_arg(0)->get_root() == p->get_arg(1)->get_root() && m_value(p) != l_true) {
add_literal(p, true);
}
}
@ -97,8 +98,7 @@ namespace euf {
SASSERT(!find(f));
force_push();
enode *n = mk_enode(f, num_args, args);
SASSERT(n->class_size() == 1);
m_expr2enode.setx(f->get_id(), n, nullptr);
SASSERT(n->class_size() == 1);
if (num_args == 0 && m.is_unique_value(f))
n->mark_interpreted();
if (num_args == 0)
@ -110,26 +110,17 @@ namespace euf {
}
enode_bool_pair p = m_table.insert(n);
enode* n2 = p.first;
if (n2 == n) {
update_children(n);
}
else {
merge(n, n2, justification::congruence(p.second));
#if 0
SASSERT(n2->get_expr() != n->get_expr());
SASSERT(n->class_size() == 1);
SASSERT(n->is_root());
merge_justification(n, n2, justification::congruence(p.second));
enode* r2 = n2->get_root();
std::swap(n->m_next, r2->m_next);
n->m_root = r2;
r2->inc_class_size(n->class_size());
push_eq(n, n, r2->num_parents());
#endif
}
if (n2 == n)
update_children(n);
else
merge(n, n2, justification::congruence(p.second));
return n;
}
egraph::egraph(ast_manager& m) : m(m), m_table(m), m_exprs(m) {
m_tmp_eq = enode::mk_tmp(m_region, 2);
}
egraph::~egraph() {
for (enode* n : m_nodes)
n->m_parents.finalize();
@ -142,6 +133,15 @@ namespace euf {
++m_stats.m_num_th_eqs;
}
void egraph::add_th_diseq(theory_id id, theory_var v1, theory_var v2, expr* eq) {
if (!th_propagates_diseqs(id))
return;
TRACE("euf_verbose", tout << "eq: " << v1 << " != " << v2 << "\n";);
m_new_th_eqs.push_back(th_eq(id, v1, v2, eq));
m_updates.push_back(update_record(update_record::new_th_eq()));
++m_stats.m_num_th_diseqs;
}
void egraph::add_literal(enode* n, bool is_eq) {
TRACE("euf_verbose", tout << "lit: " << n->get_expr_id() << "\n";);
m_new_lits.push_back(enode_bool_pair(n, is_eq));
@ -149,6 +149,68 @@ namespace euf {
if (is_eq) ++m_stats.m_num_eqs; else ++m_stats.m_num_lits;
}
void egraph::new_diseq(enode* n1) {
SASSERT(m.is_eq(n1->get_expr()));
enode* arg1 = n1->get_arg(0), * arg2 = n1->get_arg(1);
enode* r1 = arg1->get_root();
enode* r2 = arg2->get_root();
TRACE("euf", tout << "new-diseq: " << mk_pp(r1->get_expr(), m) << " " << mk_pp(r2->get_expr(), m) << ": " << r1->has_th_vars() << " " << r2->has_th_vars() << "\n";);
if (r1 == r2)
return;
if (!r1->has_th_vars())
return;
if (!r2->has_th_vars())
return;
if (r1->has_one_th_var() && r2->has_one_th_var() && r1->get_first_th_id() == r2->get_first_th_id()) {
theory_id id = r1->get_first_th_id();
if (!th_propagates_diseqs(id))
return;
theory_var v1 = arg1->get_closest_th_var(id);
theory_var v2 = arg2->get_closest_th_var(id);
add_th_diseq(id, v1, v2, n1->get_expr());
return;
}
for (auto p : euf::enode_th_vars(r1)) {
if (!th_propagates_diseqs(p.get_id()))
continue;
for (auto q : euf::enode_th_vars(r2))
if (p.get_id() == q.get_id())
add_th_diseq(p.get_id(), p.get_var(), q.get_var(), n1->get_expr());
}
}
/*
* Propagate disequalities over equality atoms that are assigned to false.
*/
void egraph::add_th_diseqs(theory_id id, theory_var v1, enode* r) {
SASSERT(r->is_root());
if (!th_propagates_diseqs(id))
return;
for (enode* p : enode_parents(r)) {
if (m.is_eq(p->get_expr()) && m.is_false(p->get_root()->get_expr())) {
enode* n = nullptr;
n = (r == p->get_arg(0)->get_root()) ? p->get_arg(1) : p->get_arg(0);
n = n->get_root();
theory_var v2 = n->get_closest_th_var(id);
if (v2 != null_theory_var)
add_th_diseq(id, v1, v2, p->get_expr());
}
}
}
void egraph::set_th_propagates_diseqs(theory_id id) {
m_th_propagates_diseqs.reserve(id + 1, false);
m_th_propagates_diseqs[id] = true;
}
bool egraph::th_propagates_diseqs(theory_id id) const {
return m_th_propagates_diseqs.get(id, false);
}
void egraph::add_th_var(enode* n, theory_var v, theory_id id) {
force_push();
theory_var w = n->get_th_var(id);
@ -159,10 +221,12 @@ namespace euf {
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)
if (u == null_theory_var) {
r->add_th_var(v, id, m_region);
else
add_th_eq(id, v, u, n, r);
add_th_diseqs(id, v, r);
}
else
add_th_eq(id, v, u, n, r);
}
}
else {
@ -201,10 +265,12 @@ namespace euf {
SASSERT(m_new_lits_qhead <= m_new_lits.size());
unsigned old_lim = m_scopes.size() - num_scopes;
unsigned num_updates = m_scopes[old_lim];
auto undo_node = [&](enode* n) {
if (n->num_args() > 1)
auto undo_node = [&]() {
enode* n = m_nodes.back();
expr* e = m_exprs.back();
if (n->num_args() > 0)
m_table.erase(n);
m_expr2enode[n->get_expr_id()] = nullptr;
m_expr2enode[e->get_id()] = nullptr;
n->~enode();
m_nodes.pop_back();
m_exprs.pop_back();
@ -213,7 +279,7 @@ namespace euf {
auto const& p = m_updates[i];
switch (p.tag) {
case update_record::tag_t::is_add_node:
undo_node(p.r1);
undo_node();
break;
case update_record::tag_t::is_toggle_merge:
p.r1->set_merge_enabled(!p.r1->merge_enabled());
@ -257,13 +323,13 @@ namespace euf {
SASSERT(m_new_th_eqs_qhead <= m_new_th_eqs.size());
}
void egraph::merge(enode* n1, enode* n2, justification j) {
void egraph::merge(enode* n1, enode* n2, justification j) {
SASSERT(m.get_sort(n1->get_expr()) == m.get_sort(n2->get_expr()));
enode* r1 = n1->get_root();
enode* r2 = n2->get_root();
if (r1 == r2)
return;
TRACE("euf", j.display(tout << n1->get_expr_id() << " == " << n2->get_expr_id() << " ", m_display_justification) << "\n";);
TRACE("euf", j.display(tout << "merge: " << mk_bounded_pp(n1->get_expr(), m) << " == " << mk_bounded_pp(n2->get_expr(), m) << " ", m_display_justification) << "\n";);
force_push();
SASSERT(m_num_scopes == 0);
++m_stats.m_num_merge;
@ -275,9 +341,10 @@ namespace euf {
std::swap(r1, r2);
std::swap(n1, n2);
}
if ((m.is_true(r2->get_expr()) || m.is_false(r2->get_expr())) && j.is_congruence()) {
add_literal(n1, false);
}
if ((m.is_true(r2->get_expr()) || m.is_false(r2->get_expr())) && j.is_congruence())
add_literal(n1, false);
if (m.is_false(r2->get_expr()) && m.is_eq(n1->get_expr()))
new_diseq(n1);
for (enode* p : enode_parents(n1))
m_table.erase(p);
for (enode* p : enode_parents(n2))
@ -301,6 +368,7 @@ namespace euf {
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()));
add_th_diseqs(id, iv.get_var(), root);
}
else {
SASSERT(v != iv.get_var());
@ -378,6 +446,25 @@ namespace euf {
SASSERT(!n1->get_root()->m_target);
}
bool egraph::are_diseq(enode* a, enode* b) const {
enode* ra = a->get_root(), * rb = b->get_root();
if (ra == rb)
return false;
if (ra->interpreted() && rb->interpreted())
return true;
if (m.get_sort(ra->get_expr()) != m.get_sort(rb->get_expr()))
return true;
expr_ref eq(m.mk_eq(a->get_expr(), b->get_expr()), m);
m_tmp_eq->m_args[0] = a;
m_tmp_eq->m_args[1] = b;
m_tmp_eq->m_expr = eq;
SASSERT(m_tmp_eq->num_args() == 2);
enode* r = m_table.find(m_tmp_eq);
if (r && m_value(r->get_root()) == l_false)
return true;
return false;
}
/**
\brief generate an explanation for a congruence.
Each pair of children under a congruence have the same roots
@ -529,9 +616,10 @@ 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 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);
st.update("euf propagations eqs", m_stats.m_num_eqs);
st.update("euf propagations theory eqs", m_stats.m_num_th_eqs);
st.update("euf propagations theory diseqs", m_stats.m_num_th_diseqs);
st.update("euf propagations literal", m_stats.m_num_lits);
}
void egraph::copy_from(egraph const& src, std::function<void*(void*)>& copy_justification) {

View file

@ -26,26 +26,47 @@ Notes:
#pragma once
#include "util/statistics.h"
#include "util/trail.h"
#include "util/lbool.h"
#include "ast/euf/euf_enode.h"
#include "ast/euf/euf_etable.h"
namespace euf {
/***
\brief store derived theory equalities.
Theory 'id' is notified with the equality of theory variables v1, v2
that are merged into the common root of child and root (their roots may
\brief store derived theory equalities and disequalities
Theory 'id' is notified with the equality/disequality of theory variables v1, v2.
For equalities, v1 and v2 are merged into the common root of child and root (their roots may
have been updated since the equality was derived, but the explanation for
v1 == v2 is provided by explaining the equality child == root.
For disequalities, m_child refers to an equality atom of the form e1 == e2.
It is equal to false under the current context.
The explanation for the disequality v1 != v2 is derived from explaining the
equality between the expression for v1 and e1, and the expression for v2 and e2
and the equality of m_eq and false: the literal corresponding to m_eq is false in the
current assignment stack, or m_child is congruent to false in the egraph.
*/
struct th_eq {
class th_eq {
theory_id m_id;
theory_var m_v1;
theory_var m_v2;
enode* m_child;
union {
enode* m_child;
expr* m_eq;
};
enode* m_root;
public:
bool is_eq() const { return m_root != nullptr; }
theory_id id() const { return m_id; }
theory_var v1() const { return m_v1; }
theory_var v2() const { return m_v2; }
enode* child() const { SASSERT(is_eq()); return m_child; }
enode* root() const { SASSERT(is_eq()); return m_root; }
expr* eq() const { SASSERT(!is_eq()); return m_eq; }
th_eq(theory_id id, theory_var v1, theory_var v2, enode* c, enode* r) :
m_id(id), m_v1(v1), m_v2(v2), m_child(c), m_root(r) {}
th_eq(theory_id id, theory_var v1, theory_var v2, expr* eq) :
m_id(id), m_v1(v1), m_v2(v2), m_eq(eq), m_root(nullptr) {}
};
class egraph {
@ -53,6 +74,7 @@ namespace euf {
struct stats {
unsigned m_num_merge;
unsigned m_num_th_eqs;
unsigned m_num_th_diseqs;
unsigned m_num_lits;
unsigned m_num_eqs;
unsigned m_num_conflicts;
@ -111,6 +133,7 @@ namespace euf {
svector<update_record> m_updates;
unsigned_vector m_scopes;
enode_vector m_expr2enode;
enode* m_tmp_eq { nullptr };
enode_vector m_nodes;
expr_ref_vector m_exprs;
unsigned m_num_scopes { 0 };
@ -122,11 +145,13 @@ namespace euf {
unsigned m_new_th_eqs_qhead { 0 };
svector<enode_bool_pair> m_new_lits;
svector<th_eq> m_new_th_eqs;
bool_vector m_th_propagates_diseqs;
enode_vector m_todo;
stats m_stats;
std::function<void(expr*,expr*,expr*)> m_used_eq;
std::function<void(app*,app*)> m_used_cc;
std::function<void(std::ostream&, void*)> m_display_justification;
std::function<lbool(enode*)> m_value;
void push_eq(enode* r1, enode* n1, unsigned r2_num_parents) {
m_updates.push_back(update_record(r1, n1, r2_num_parents));
@ -134,6 +159,10 @@ namespace euf {
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 new_diseq(enode* n1);
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 undo_eq(enode* r1, enode* n1, unsigned r2_num_parents);
void undo_add_th_var(enode* n, theory_id id);
@ -166,7 +195,7 @@ namespace euf {
std::ostream& display(std::ostream& out, unsigned max_args, enode* n) const;
public:
egraph(ast_manager& m): m(m), m_table(m), m_exprs(m) {}
egraph(ast_manager& m);
~egraph();
enode* find(expr* f) { return m_expr2enode.get(f->get_id(), nullptr); }
enode* mk(expr* f, unsigned n, enode *const* args);
@ -192,12 +221,18 @@ namespace euf {
bool propagate();
bool inconsistent() const { return m_inconsistent; }
/**
* \brief check if two nodes are known to be disequal.
*/
bool are_diseq(enode* a, enode* b) const;
/**
\brief Maintain and update cursor into propagated consequences.
The result of get_literal() is a pair (n, is_eq)
where \c n is an enode and \c is_eq indicates whether the enode
is an equality consequence.
*/
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]; }
@ -205,13 +240,14 @@ namespace euf {
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++; }
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_used_eq(std::function<void(expr*,expr*,expr*)>& used_eq) { m_used_eq = used_eq; }
void set_used_cc(std::function<void(app*,app*)>& used_cc) { m_used_cc = used_cc; }
void set_display_justification(std::function<void (std::ostream&, void*)> & d) { m_display_justification = d; }
void set_eval(std::function<lbool(enode*)>& eval) { m_value = eval; }
void begin_explain();
void end_explain();

View file

@ -70,6 +70,21 @@ namespace euf {
return true;
}
/*
* Find the theory var that that is heuristically
* linked with the fewest equality justifications.
*/
theory_var enode::get_closest_th_var(theory_id id) const {
enode const* n = this;
while (n) {
theory_var v = n->get_th_var(id);
if (v != null_theory_var)
return v;
n = n->m_target;
}
return null_theory_var;
}
bool enode::acyclic() const {
enode const* n = this;
enode const* p = this;

View file

@ -36,23 +36,23 @@ namespace euf {
const theory_id null_theory_id = -1;
class enode {
expr* m_expr{ 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 };
expr* m_expr{ 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{ nullptr };
enode* m_root{ nullptr };
enode* m_target { nullptr };
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 { 0 };
enode* m_args[0];
unsigned m_num_args{ 0 };
enode* m_args[0];
friend class enode_args;
friend class enode_parents;
@ -81,6 +81,20 @@ namespace euf {
}
return n;
}
static enode* mk_tmp(region& r, unsigned num_args) {
void* mem = r.allocate(get_enode_size(num_args));
enode* n = new (mem) enode();
n->m_expr = nullptr;
n->m_next = n;
n->m_root = n;
n->m_commutative = true;
n->m_num_args = 2;
n->m_merge_enabled = true;
for (unsigned i = 0; i < num_args; ++i)
n->m_args[i] = nullptr;
return n;
}
void set_update_children() { m_update_children = true; }
@ -148,10 +162,12 @@ namespace euf {
unsigned get_expr_id() const { return m_expr->get_id(); }
unsigned get_root_id() const { return m_root->m_expr->get_id(); }
theory_var get_th_var(theory_id id) const { return m_th_vars.find(id); }
theory_var get_closest_th_var(theory_id id) const;
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(); }
bool has_one_th_var() const { return !m_th_vars.empty() && !m_th_vars.get_next();}
theory_var get_first_th_id() const { SASSERT(has_th_vars()); return m_th_vars.get_id(); }
void inc_class_size(unsigned n) { m_class_size += n; }
void dec_class_size(unsigned n) { m_class_size -= n; }