3
0
Fork 0
mirror of https://github.com/Z3Prover/z3 synced 2025-04-28 19:35:50 +00:00

Add EUF (congruence closure) proof hints and checker to the new core

EUF proofs are checked modulo union-find.
Equalities are added to to union-find if they are assumptions or if they can be derived using congruence closure. The congruence closure assumptions are added as proof-hints.
Note that this proof format does not track equality inferences, symmetry and transitivity. Instead they are handled by assuming a union-find based checker.
This commit is contained in:
Nikolaj Bjorner 2022-09-25 14:26:20 -07:00
parent 6f2fde87d1
commit 9be8fc7857
11 changed files with 315 additions and 57 deletions

View file

@ -126,7 +126,7 @@ namespace euf {
if (n2 == n)
update_children(n);
else
merge(n, n2, justification::congruence(comm));
merge(n, n2, justification::congruence(comm, m_congruence_timestamp++));
}
return n;
}
@ -554,7 +554,7 @@ namespace euf {
force_push();
for (unsigned i = 0; i < m_to_merge.size() && m.limit().inc() && !inconsistent(); ++i) {
auto const& w = m_to_merge[i];
merge(w.a, w.b, justification::congruence(w.commutativity));
merge(w.a, w.b, justification::congruence(w.commutativity, m_congruence_timestamp++));
}
m_to_merge.reset();
return
@ -707,25 +707,28 @@ namespace euf {
}
template <typename T>
void egraph::explain(ptr_vector<T>& justifications) {
void egraph::explain(ptr_vector<T>& justifications, cc_justification* cc) {
SASSERT(m_inconsistent);
push_todo(m_n1);
push_todo(m_n2);
explain_eq(justifications, m_n1, m_n2, m_justification);
explain_todo(justifications);
explain_eq(justifications, cc, m_n1, m_n2, m_justification);
explain_todo(justifications, cc);
}
template <typename T>
void egraph::explain_eq(ptr_vector<T>& justifications, enode* a, enode* b, justification const& j) {
void egraph::explain_eq(ptr_vector<T>& justifications, cc_justification* cc, enode* a, enode* b, justification const& j) {
TRACE("euf_verbose", tout << "explain-eq: " << bpp(a) << " == " << bpp(b) << " jst: " << j << "\n";);
if (j.is_external())
justifications.push_back(j.ext<T>());
else if (j.is_congruence())
push_congruence(a, b, j.is_commutative());
if (cc && j.is_congruence())
cc->push_back(std::tuple(a, b, j.timestamp(), j.is_commutative()));
}
template <typename T>
void egraph::explain_eq(ptr_vector<T>& justifications, enode* a, enode* b) {
void egraph::explain_eq(ptr_vector<T>& justifications, cc_justification* cc, enode* a, enode* b) {
SASSERT(a->get_root() == b->get_root());
enode* lca = find_lca(a, b);
@ -734,27 +737,27 @@ namespace euf {
push_to_lca(b, lca);
if (m_used_eq)
m_used_eq(a->get_expr(), b->get_expr(), lca->get_expr());
explain_todo(justifications);
explain_todo(justifications, cc);
}
template <typename T>
unsigned egraph::explain_diseq(ptr_vector<T>& justifications, enode* a, enode* b) {
unsigned egraph::explain_diseq(ptr_vector<T>& justifications, cc_justification* cc, enode* a, enode* b) {
enode* ra = a->get_root(), * rb = b->get_root();
SASSERT(ra != rb);
if (ra->interpreted() && rb->interpreted()) {
explain_eq(justifications, a, ra);
explain_eq(justifications, b, rb);
explain_eq(justifications, cc, a, ra);
explain_eq(justifications, cc, b, rb);
return sat::null_bool_var;
}
enode* r = tmp_eq(ra, rb);
SASSERT(r && r->get_root()->value() == l_false);
explain_eq(justifications, r, r->get_root());
explain_eq(justifications, cc, r, r->get_root());
return r->get_root()->bool_var();
}
template <typename T>
void egraph::explain_todo(ptr_vector<T>& justifications) {
void egraph::explain_todo(ptr_vector<T>& justifications, cc_justification* cc) {
for (unsigned i = 0; i < m_todo.size(); ++i) {
enode* n = m_todo[i];
if (n->is_marked1())
@ -762,7 +765,7 @@ namespace euf {
if (n->m_target) {
n->mark1();
CTRACE("euf_verbose", m_display_justification, n->m_justification.display(tout << n->get_expr_id() << " = " << n->m_target->get_expr_id() << " ", m_display_justification) << "\n";);
explain_eq(justifications, n, n->m_target, n->m_justification);
explain_eq(justifications, cc, n, n->m_target, n->m_justification);
}
else if (!n->is_marked1() && n->value() != l_undef) {
n->mark1();
@ -890,15 +893,15 @@ namespace euf {
}
}
template void euf::egraph::explain(ptr_vector<int>& justifications);
template void euf::egraph::explain_todo(ptr_vector<int>& justifications);
template void euf::egraph::explain_eq(ptr_vector<int>& justifications, enode* a, enode* b);
template unsigned euf::egraph::explain_diseq(ptr_vector<int>& justifications, enode* a, enode* b);
template void euf::egraph::explain(ptr_vector<int>& justifications, cc_justification*);
template void euf::egraph::explain_todo(ptr_vector<int>& justifications, cc_justification*);
template void euf::egraph::explain_eq(ptr_vector<int>& justifications, cc_justification*, enode* a, enode* b);
template unsigned euf::egraph::explain_diseq(ptr_vector<int>& justifications, cc_justification*, enode* a, enode* b);
template void euf::egraph::explain(ptr_vector<size_t>& justifications);
template void euf::egraph::explain_todo(ptr_vector<size_t>& justifications);
template void euf::egraph::explain_eq(ptr_vector<size_t>& justifications, enode* a, enode* b);
template unsigned euf::egraph::explain_diseq(ptr_vector<size_t>& justifications, enode* a, enode* b);
template void euf::egraph::explain(ptr_vector<size_t>& justifications, cc_justification*);
template void euf::egraph::explain_todo(ptr_vector<size_t>& justifications, cc_justification*);
template void euf::egraph::explain_eq(ptr_vector<size_t>& justifications, cc_justification*, enode* a, enode* b);
template unsigned euf::egraph::explain_diseq(ptr_vector<size_t>& justifications, cc_justification*, enode* a, enode* b);

View file

@ -72,6 +72,13 @@ namespace euf {
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) {}
};
// cc_justification contains the uses of congruence closure
// It is the only information collected from justifications in order to
// reconstruct EUF proofs. Transitivity, Symmetry of equality are not
// tracked.
typedef std::tuple<enode*,enode*,uint64_t, bool> cc_justification_record;
typedef svector<cc_justification_record> cc_justification;
class egraph {
@ -186,6 +193,8 @@ namespace euf {
stats m_stats;
bool m_uses_congruence = false;
bool m_default_relevant = true;
uint64_t m_congruence_timestamp = 0;
std::vector<std::function<void(enode*,enode*)>> m_on_merge;
std::function<void(enode*)> m_on_make;
std::function<void(expr*,expr*,expr*)> m_used_eq;
@ -226,10 +235,10 @@ namespace euf {
void erase_from_table(enode* p);
template <typename T>
void explain_eq(ptr_vector<T>& justifications, enode* a, enode* b, justification const& j);
void explain_eq(ptr_vector<T>& justifications, cc_justification* cc, enode* a, enode* b, justification const& j);
template <typename T>
void explain_todo(ptr_vector<T>& justifications);
void explain_todo(ptr_vector<T>& justifications, cc_justification* cc);
std::ostream& display(std::ostream& out, unsigned max_args, enode* n) const;
@ -306,11 +315,11 @@ namespace euf {
void end_explain();
bool uses_congruence() const { return m_uses_congruence; }
template <typename T>
void explain(ptr_vector<T>& justifications);
void explain(ptr_vector<T>& justifications, cc_justification* cc);
template <typename T>
void explain_eq(ptr_vector<T>& justifications, enode* a, enode* b);
void explain_eq(ptr_vector<T>& justifications, cc_justification* cc, enode* a, enode* b);
template <typename T>
unsigned explain_diseq(ptr_vector<T>& justifications, enode* a, enode* b);
unsigned explain_diseq(ptr_vector<T>& justifications, cc_justification* cc, enode* a, enode* b);
enode_vector const& nodes() const { return m_nodes; }
ast_manager& get_manager() { return m; }

View file

@ -13,6 +13,11 @@ Author:
Nikolaj Bjorner (nbjorner) 2020-08-23
Notes:
- congruence closure justifications are given a timestamp so it is easy to sort them.
See the longer descriptoin in euf_proof_checker.cpp
--*/
#pragma once
@ -27,11 +32,15 @@ namespace euf {
};
kind_t m_kind;
bool m_comm;
void* m_external;
justification(bool comm):
union {
void* m_external;
uint64_t m_timestamp;
};
justification(bool comm, uint64_t ts):
m_kind(kind_t::congruence_t),
m_comm(comm),
m_external(nullptr)
m_timestamp(ts)
{}
justification(void* ext):
@ -48,12 +57,13 @@ namespace euf {
{}
static justification axiom() { return justification(); }
static justification congruence(bool c) { return justification(c); }
static justification congruence(bool c, uint64_t ts) { return justification(c, ts); }
static justification external(void* ext) { return justification(ext); }
bool is_external() const { return m_kind == kind_t::external_t; }
bool is_congruence() const { return m_kind == kind_t::congruence_t; }
bool is_commutative() const { return m_comm; }
uint64_t timestamp() const { SASSERT(is_congruence()); return m_timestamp; }
template <typename T>
T* ext() const { SASSERT(is_external()); return static_cast<T*>(m_external); }
@ -64,7 +74,7 @@ namespace euf {
case kind_t::axiom_t:
return axiom();
case kind_t::congruence_t:
return congruence(m_comm);
return congruence(m_comm, m_timestamp);
default:
UNREACHABLE();
return axiom();
@ -90,4 +100,8 @@ namespace euf {
return out;
}
};
inline std::ostream& operator<<(std::ostream& out, justification const& j) {
return j.display(out, nullptr);
}
}