feat(library/blast/congruence_closure): add "add_eqv"
This commit is contained in:
parent
f9ced4b3e1
commit
968b615390
2 changed files with 173 additions and 11 deletions
|
@ -10,6 +10,7 @@ Author: Leonardo de Moura
|
||||||
#include "library/blast/congruence_closure.h"
|
#include "library/blast/congruence_closure.h"
|
||||||
#include "library/blast/util.h"
|
#include "library/blast/util.h"
|
||||||
#include "library/blast/blast.h"
|
#include "library/blast/blast.h"
|
||||||
|
#include "library/blast/trace.h"
|
||||||
|
|
||||||
namespace lean {
|
namespace lean {
|
||||||
namespace blast {
|
namespace blast {
|
||||||
|
@ -88,7 +89,7 @@ void congruence_closure::mk_entry_for(name const & R, expr const & e) {
|
||||||
n.m_next = e;
|
n.m_next = e;
|
||||||
n.m_root = e;
|
n.m_root = e;
|
||||||
n.m_cg_root = e;
|
n.m_cg_root = e;
|
||||||
n.m_rank = 0;
|
n.m_size = 1;
|
||||||
m_entries.insert(eqc_key(R, e), n);
|
m_entries.insert(eqc_key(R, e), n);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -159,6 +160,8 @@ void congruence_closure::internalize(name const & R, expr const & e) {
|
||||||
lean_assert(closed(e));
|
lean_assert(closed(e));
|
||||||
if (has_expr_metavar(e))
|
if (has_expr_metavar(e))
|
||||||
return;
|
return;
|
||||||
|
if (m_entries.find(eqc_key(R, e)))
|
||||||
|
return; // e has already been internalized
|
||||||
switch (e.kind()) {
|
switch (e.kind()) {
|
||||||
case expr_kind::Var: case expr_kind::Meta:
|
case expr_kind::Var: case expr_kind::Meta:
|
||||||
lean_unreachable();
|
lean_unreachable();
|
||||||
|
@ -221,14 +224,117 @@ static void clear_todo() {
|
||||||
get_todo().clear();
|
get_todo().clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
void congruence_closure::add_eqv(name const & R, expr const & lhs, expr const & rhs, expr const & pr) {
|
/*
|
||||||
|
The fields m_target and m_proof in e's entry are encoding a transitivity proof
|
||||||
|
Let target(e) and proof(e) denote these fields.
|
||||||
|
|
||||||
|
e = target(e) : proof(e)
|
||||||
|
... = target(target(e)) : proof(target(e))
|
||||||
|
... ...
|
||||||
|
= root(e) : ...
|
||||||
|
|
||||||
|
The transitivity proof eventually reaches the root of the equivalence class.
|
||||||
|
This method "inverts" the proof. That is, the m_target goes from root(e) to e after
|
||||||
|
we execute it.
|
||||||
|
*/
|
||||||
|
void congruence_closure::invert_trans(name const & R, expr const & e, optional<expr> new_target, optional<expr> new_proof) {
|
||||||
|
eqc_key k(R, e);
|
||||||
|
auto n = m_entries.find(k);
|
||||||
|
lean_assert(n);
|
||||||
|
entry new_n = *n;
|
||||||
|
if (n->m_target)
|
||||||
|
invert_trans(R, *new_n.m_target, some_expr(e), new_n.m_proof);
|
||||||
|
new_n.m_target = new_target;
|
||||||
|
new_n.m_proof = new_proof;
|
||||||
|
m_entries.insert(k, new_n);
|
||||||
|
}
|
||||||
|
void congruence_closure::invert_trans(name const & R, expr const & e) {
|
||||||
|
invert_trans(R, e, none_expr(), none_expr());
|
||||||
|
}
|
||||||
|
|
||||||
|
void congruence_closure::remove_parents(name const & R, expr const & e) {
|
||||||
|
std::cout << R << " " << e << "\n";
|
||||||
|
// TODO(Leo):
|
||||||
|
}
|
||||||
|
|
||||||
|
void congruence_closure::insert_parents(name const & R, expr const & e) {
|
||||||
|
std::cout << R << " " << e << "\n";
|
||||||
|
// TODO(Leo):
|
||||||
|
}
|
||||||
|
|
||||||
|
void congruence_closure::add_eqv_step(name const & R, expr e1, expr e2, expr const & H) {
|
||||||
|
auto n1 = m_entries.find(eqc_key(R, e1));
|
||||||
|
auto n2 = m_entries.find(eqc_key(R, e2));
|
||||||
|
if (!n1 || !n2)
|
||||||
|
return;
|
||||||
|
if (n1->m_root == n2->m_root)
|
||||||
|
return; // they are already in the same equivalence class
|
||||||
|
auto r1 = m_entries.find(eqc_key(R, n1->m_root));
|
||||||
|
auto r2 = m_entries.find(eqc_key(R, n2->m_root));
|
||||||
|
lean_assert(r1 && r2);
|
||||||
|
|
||||||
|
// We want r2 to be the root of the combined class.
|
||||||
|
|
||||||
|
if (r1->m_size > r2->m_size) {
|
||||||
|
std::swap(e1, e2);
|
||||||
|
std::swap(n1, n2);
|
||||||
|
std::swap(r1, r2);
|
||||||
|
// Remark: we don't apply symmetry eagerly. So, we don't adjust H.
|
||||||
|
}
|
||||||
|
|
||||||
|
expr e1_root = n1->m_root;
|
||||||
|
expr e2_root = n2->m_root;
|
||||||
|
entry new_n1 = *n1;
|
||||||
|
|
||||||
|
// Following target/proof we have
|
||||||
|
// e1 -> ... -> r1
|
||||||
|
// e2 -> ... -> r2
|
||||||
|
// We want
|
||||||
|
// r1 -> ... -> e1 -> e2 -> ... -> r2
|
||||||
|
invert_trans(R, e1);
|
||||||
|
new_n1.m_target = e2;
|
||||||
|
new_n1.m_proof = H;
|
||||||
|
m_entries.insert(eqc_key(R, e1), new_n1);
|
||||||
|
|
||||||
|
// The hash code for the parents is going to change
|
||||||
|
remove_parents(R, e1);
|
||||||
|
|
||||||
|
// force all m_root fields in e1 equivalence class to point to e2_root
|
||||||
|
expr it = e1;
|
||||||
|
do {
|
||||||
|
auto it_n = m_entries.find(eqc_key(R, it));
|
||||||
|
lean_assert(it_n);
|
||||||
|
entry new_it_n = *it_n;
|
||||||
|
new_it_n.m_root = e2_root;
|
||||||
|
m_entries.insert(eqc_key(R, it), new_it_n);
|
||||||
|
it = new_it_n.m_next;
|
||||||
|
} while (it != e1);
|
||||||
|
|
||||||
|
insert_parents(R, e1);
|
||||||
|
|
||||||
|
// update next of e1_root and e2_root, and size of e2_root
|
||||||
|
r1 = m_entries.find(eqc_key(R, e1_root));
|
||||||
|
r2 = m_entries.find(eqc_key(R, e2_root));
|
||||||
|
lean_assert(r1 && r2);
|
||||||
|
lean_assert(r1->m_root == e2_root);
|
||||||
|
entry new_r1 = *r1;
|
||||||
|
entry new_r2 = *r2;
|
||||||
|
new_r1.m_next = r2->m_next;
|
||||||
|
new_r2.m_next = r1->m_next;
|
||||||
|
new_r2.m_size += r1->m_size;
|
||||||
|
m_entries.insert(eqc_key(R, e1_root), new_r1);
|
||||||
|
m_entries.insert(eqc_key(R, e2_root), new_r2);
|
||||||
|
lean_assert(check_invariant());
|
||||||
|
}
|
||||||
|
|
||||||
|
void congruence_closure::add_eqv(name const & _R, expr const & _lhs, expr const & _rhs, expr const & _H) {
|
||||||
auto & todo = get_todo();
|
auto & todo = get_todo();
|
||||||
todo.emplace_back(R, lhs, rhs, pr);
|
todo.emplace_back(_R, _lhs, _rhs, _H);
|
||||||
while (!todo.empty()) {
|
while (!todo.empty()) {
|
||||||
name R; expr lhs, rhs, pr;
|
name R; expr lhs, rhs, H;
|
||||||
std::tie(R, lhs, rhs, pr) = todo.back();
|
std::tie(R, lhs, rhs, H) = todo.back();
|
||||||
todo.pop_back();
|
todo.pop_back();
|
||||||
// TODO(Leo): process
|
add_eqv_step(R, lhs, rhs, H);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -241,7 +347,7 @@ void congruence_closure::add(hypothesis_idx hidx) {
|
||||||
hypothesis const & h = s.get_hypothesis_decl(hidx);
|
hypothesis const & h = s.get_hypothesis_decl(hidx);
|
||||||
try {
|
try {
|
||||||
expr const & type = h.get_type();
|
expr const & type = h.get_type();
|
||||||
expr p;
|
expr p = type;
|
||||||
bool is_neg = is_not(type, p);
|
bool is_neg = is_not(type, p);
|
||||||
if (is_neg && !is_standard(env()))
|
if (is_neg && !is_standard(env()))
|
||||||
return;
|
return;
|
||||||
|
@ -385,11 +491,60 @@ expr congruence_closure::get_next(name const & R, expr const & e) const {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void congruence_closure::display_eqc(name const & R, expr const & e) const {
|
||||||
|
auto out = diagnostic(env(), ios());
|
||||||
|
bool first = true;
|
||||||
|
expr it = e;
|
||||||
|
out << R << " {";
|
||||||
|
do {
|
||||||
|
auto it_n = m_entries.find(eqc_key(R, it));
|
||||||
|
if (first) first = false; else out << ", ";
|
||||||
|
out << ppb(it);
|
||||||
|
it = it_n->m_next;
|
||||||
|
} while (it != e);
|
||||||
|
out << "}";
|
||||||
|
}
|
||||||
|
|
||||||
void congruence_closure::display() const {
|
void congruence_closure::display() const {
|
||||||
// TODO(Leo):
|
auto out = diagnostic(env(), ios());
|
||||||
|
m_entries.for_each([&](eqc_key const & k, entry const & n) {
|
||||||
|
if (k.m_expr == n.m_root) {
|
||||||
|
display_eqc(k.m_R, k.m_expr);
|
||||||
|
out << "\n";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bool congruence_closure::check_eqc(name const & R, expr const & e) const {
|
||||||
|
expr root = get_root(R, e);
|
||||||
|
unsigned size = 0;
|
||||||
|
expr it = e;
|
||||||
|
do {
|
||||||
|
auto it_n = m_entries.find(eqc_key(R, it));
|
||||||
|
lean_assert(it_n);
|
||||||
|
lean_assert(it_n->m_root == root);
|
||||||
|
auto it2 = it;
|
||||||
|
// following m_target fields should lead to root
|
||||||
|
while (true) {
|
||||||
|
auto it2_n = m_entries.find(eqc_key(R, it2));
|
||||||
|
if (!it2_n->m_target)
|
||||||
|
break;
|
||||||
|
it2 = *it2_n->m_target;
|
||||||
|
}
|
||||||
|
lean_assert(it2 == root);
|
||||||
|
it = it_n->m_next;
|
||||||
|
size++;
|
||||||
|
} while (it != e);
|
||||||
|
lean_assert(m_entries.find(eqc_key(R, root))->m_size == size);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool congruence_closure::check_invariant() const {
|
bool congruence_closure::check_invariant() const {
|
||||||
|
m_entries.for_each([&](eqc_key const & k, entry const & n) {
|
||||||
|
if (k.m_expr == n.m_root) {
|
||||||
|
lean_assert(check_eqc(k.m_R, k.m_expr));
|
||||||
|
}
|
||||||
|
});
|
||||||
// TODO(Leo):
|
// TODO(Leo):
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,7 +43,7 @@ class congruence_closure {
|
||||||
// store 'target' at 'm_target', and 'H' at 'm_proof'. Both fields are none if 'e' == m_root
|
// store 'target' at 'm_target', and 'H' at 'm_proof'. Both fields are none if 'e' == m_root
|
||||||
optional<expr> m_target;
|
optional<expr> m_target;
|
||||||
optional<expr> m_proof;
|
optional<expr> m_proof;
|
||||||
unsigned m_rank; // rank of the equivalence class, it is meaningless if 'e' != m_root
|
unsigned m_size; // number of elements in the equivalence class, it is meaningless if 'e' != m_root
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Key (R, e) for the mapping (R, e) -> entry */
|
/* Key (R, e) for the mapping (R, e) -> entry */
|
||||||
|
@ -58,7 +58,7 @@ class congruence_closure {
|
||||||
int operator()(eqc_key const & k1, eqc_key const & k2) const {
|
int operator()(eqc_key const & k1, eqc_key const & k2) const {
|
||||||
int r = quick_cmp(k1.m_R, k2.m_R);
|
int r = quick_cmp(k1.m_R, k2.m_R);
|
||||||
if (r != 0) return r;
|
if (r != 0) return r;
|
||||||
else return is_lt(k1.m_expr, k2.m_expr, true);
|
else return expr_quick_cmp()(k1.m_expr, k2.m_expr);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -95,8 +95,14 @@ class congruence_closure {
|
||||||
void mk_entry_for(name const & R, expr const & e);
|
void mk_entry_for(name const & R, expr const & e);
|
||||||
void add_occurrence(name const & Rp, expr const & parent, name const & Rc, expr const & child);
|
void add_occurrence(name const & Rp, expr const & parent, name const & Rc, expr const & child);
|
||||||
void add_congruence_table(ext_congr_lemma const & lemma, expr const & e);
|
void add_congruence_table(ext_congr_lemma const & lemma, expr const & e);
|
||||||
void add_eqv(name const & R, expr const & lhs, expr const & rhs, expr const & pr);
|
void invert_trans(name const & R, expr const & e, optional<expr> new_target, optional<expr> new_proof);
|
||||||
|
void invert_trans(name const & R, expr const & e);
|
||||||
|
void remove_parents(name const & R, expr const & e);
|
||||||
|
void insert_parents(name const & R, expr const & e);
|
||||||
|
void add_eqv_step(name const & R, expr e1, expr e2, expr const & H);
|
||||||
|
void add_eqv(name const & R, expr const & lhs, expr const & rhs, expr const & H);
|
||||||
|
|
||||||
|
void display_eqc(name const & R, expr const & e) const;
|
||||||
public:
|
public:
|
||||||
/** \brief Register expression \c e in this data-structure.
|
/** \brief Register expression \c e in this data-structure.
|
||||||
It creates entries for each sub-expression in \c e.
|
It creates entries for each sub-expression in \c e.
|
||||||
|
@ -154,6 +160,7 @@ public:
|
||||||
|
|
||||||
/** \brief dump for debugging purposes. */
|
/** \brief dump for debugging purposes. */
|
||||||
void display() const;
|
void display() const;
|
||||||
|
bool check_eqc(name const & R, expr const & e) const;
|
||||||
bool check_invariant() const;
|
bool check_invariant() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue