mirror of
https://github.com/Z3Prover/z3
synced 2025-08-12 14:10:54 +00:00
updates to euf-completion to
This commit is contained in:
parent
9db227dbf1
commit
9d35a8c702
3 changed files with 239 additions and 112 deletions
|
@ -28,7 +28,7 @@ Algorithm for extracting canonical form from an E-graph:
|
||||||
|
|
||||||
* Each f(t) = g(s) in E:
|
* Each f(t) = g(s) in E:
|
||||||
* add f(canon(t)) = canon(f(t)), g(canon(s)) = canon(g(s)) where canon(f(t)) = canon(g(s)) by construction.
|
* add f(canon(t)) = canon(f(t)), g(canon(s)) = canon(g(s)) where canon(f(t)) = canon(g(s)) by construction.
|
||||||
|
|
||||||
* Each other g(t) in E:
|
* Each other g(t) in E:
|
||||||
* add g(canon(t)) to E.
|
* add g(canon(t)) to E.
|
||||||
* Note that canon(g(t)) = true because g(t) = true is added to congruence closure of E.
|
* Note that canon(g(t)) = true because g(t) = true is added to congruence closure of E.
|
||||||
|
@ -37,16 +37,11 @@ Algorithm for extracting canonical form from an E-graph:
|
||||||
|
|
||||||
Conditional saturation:
|
Conditional saturation:
|
||||||
- forall X . Body => Head
|
- forall X . Body => Head
|
||||||
- propagate when (all assertions in) Body is merged with True
|
- propagate when (all assertions in) Body is merged with True
|
||||||
- Possible efficient approaches:
|
- insert expressions from Body into a watch list.
|
||||||
- use on_merge?
|
When elements of the watch list are merged by true/false
|
||||||
- or bit set in nodes with Body?
|
trigger rep-propagation with respect to body.
|
||||||
- register Boolean reduction rules to EUF?
|
|
||||||
- register function "body_of" and monitor merges based on function?
|
|
||||||
|
|
||||||
Delayed solver invocation
|
|
||||||
- So far default code for checking rules
|
|
||||||
- EUF check should be on demand, see note on conditional saturation
|
|
||||||
|
|
||||||
Mam optimization?
|
Mam optimization?
|
||||||
match(p, t, S) = suppose all variables in p are bound in S, check equality using canonization of p[S], otherwise prune instances from S.
|
match(p, t, S) = suppose all variables in p are bound in S, check equality using canonization of p[S], otherwise prune instances from S.
|
||||||
|
@ -59,10 +54,11 @@ Mam optimization?
|
||||||
#include "ast/rewriter/var_subst.h"
|
#include "ast/rewriter/var_subst.h"
|
||||||
#include "ast/simplifiers/euf_completion.h"
|
#include "ast/simplifiers/euf_completion.h"
|
||||||
#include "ast/shared_occs.h"
|
#include "ast/shared_occs.h"
|
||||||
|
#include "params/tactic_params.hpp"
|
||||||
|
|
||||||
namespace euf {
|
namespace euf {
|
||||||
|
|
||||||
completion::completion(ast_manager& m, dependent_expr_state& fmls):
|
completion::completion(ast_manager& m, dependent_expr_state& fmls) :
|
||||||
dependent_expr_simplifier(m, fmls),
|
dependent_expr_simplifier(m, fmls),
|
||||||
m_egraph(m),
|
m_egraph(m),
|
||||||
m_mam(mam::mk(*this, *this)),
|
m_mam(mam::mk(*this, *this)),
|
||||||
|
@ -75,16 +71,17 @@ namespace euf {
|
||||||
m_rewriter.set_order_eq(true);
|
m_rewriter.set_order_eq(true);
|
||||||
m_rewriter.set_flat_and_or(false);
|
m_rewriter.set_flat_and_or(false);
|
||||||
|
|
||||||
std::function<void(euf::enode*, euf::enode*)> _on_merge =
|
std::function<void(euf::enode*, euf::enode*)> _on_merge =
|
||||||
[&](euf::enode* root, euf::enode* other) {
|
[&](euf::enode* root, euf::enode* other) {
|
||||||
m_mam->on_merge(root, other);
|
m_mam->on_merge(root, other);
|
||||||
};
|
watch_rule(root, other);
|
||||||
|
};
|
||||||
std::function<void(euf::enode*)> _on_make =
|
|
||||||
|
std::function<void(euf::enode*)> _on_make =
|
||||||
[&](euf::enode* n) {
|
[&](euf::enode* n) {
|
||||||
m_mam->add_node(n, false);
|
m_mam->add_node(n, false);
|
||||||
};
|
};
|
||||||
|
|
||||||
m_egraph.set_on_merge(_on_merge);
|
m_egraph.set_on_merge(_on_merge);
|
||||||
m_egraph.set_on_make(_on_make);
|
m_egraph.set_on_make(_on_make);
|
||||||
}
|
}
|
||||||
|
@ -92,9 +89,76 @@ namespace euf {
|
||||||
completion::~completion() {
|
completion::~completion() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool completion::should_stop() {
|
||||||
|
return
|
||||||
|
!m.inc() ||
|
||||||
|
m_egraph.inconsistent() ||
|
||||||
|
m_fmls.inconsistent() ||
|
||||||
|
resource_limits_exceeded();
|
||||||
|
}
|
||||||
|
|
||||||
|
void completion::updt_params(params_ref const& p) {
|
||||||
|
tactic_params tp(p);
|
||||||
|
m_max_instantiations = tp.completion_max_instantiations();
|
||||||
|
}
|
||||||
|
|
||||||
|
struct completion::push_watch_rule : public trail {
|
||||||
|
vector<ptr_vector<conditional_rule>>& m_rules;
|
||||||
|
unsigned idx;
|
||||||
|
push_watch_rule(vector<ptr_vector<conditional_rule>>& r, unsigned i) : m_rules(r), idx(i) {}
|
||||||
|
void undo() override {
|
||||||
|
m_rules[idx].pop_back();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
void completion::push() {
|
||||||
|
if (m_side_condition_solver)
|
||||||
|
m_side_condition_solver->push();
|
||||||
|
m_egraph.push();
|
||||||
|
dependent_expr_simplifier::push();
|
||||||
|
}
|
||||||
|
|
||||||
|
void completion::pop(unsigned n) {
|
||||||
|
clear_propagation_queue();
|
||||||
|
dependent_expr_simplifier::pop(n);
|
||||||
|
m_egraph.pop(n);
|
||||||
|
if (m_side_condition_solver)
|
||||||
|
m_side_condition_solver->pop(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
void completion::clear_propagation_queue() {
|
||||||
|
for (auto r : m_propagation_queue)
|
||||||
|
r->m_in_queue = false;
|
||||||
|
m_propagation_queue.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void completion::watch_rule(enode* root, enode* other) {
|
||||||
|
auto oid = other->get_id();
|
||||||
|
if (oid >= m_rule_watch.size())
|
||||||
|
return;
|
||||||
|
if (m_rule_watch[oid].empty())
|
||||||
|
return;
|
||||||
|
auto is_true_or_false = m.is_true(root->get_expr()) || m.is_false(root->get_expr());
|
||||||
|
if (is_true_or_false) {
|
||||||
|
for (auto r : m_rule_watch[oid])
|
||||||
|
if (!r->m_in_queue)
|
||||||
|
r->m_in_queue = true,
|
||||||
|
m_propagation_queue.push_back(r);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// root is not true or false, use root to watch rules
|
||||||
|
auto rid = root->get_id();
|
||||||
|
m_rule_watch.reserve(rid + 1);
|
||||||
|
for (auto r : m_rule_watch[oid]) {
|
||||||
|
m_rule_watch[rid].push_back(r);
|
||||||
|
get_trail().push(push_watch_rule(m_rule_watch, rid));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void completion::reduce() {
|
void completion::reduce() {
|
||||||
m_has_new_eq = true;
|
m_has_new_eq = true;
|
||||||
for (unsigned rounds = 0; m_has_new_eq && rounds <= 3 && !m_fmls.inconsistent(); ++rounds) {
|
for (unsigned rounds = 0; m_has_new_eq && rounds <= 3 && !should_stop(); ++rounds) {
|
||||||
++m_epoch;
|
++m_epoch;
|
||||||
m_has_new_eq = false;
|
m_has_new_eq = false;
|
||||||
add_egraph();
|
add_egraph();
|
||||||
|
@ -113,23 +177,24 @@ namespace euf {
|
||||||
add_constraint(f, d);
|
add_constraint(f, d);
|
||||||
}
|
}
|
||||||
m_should_propagate = true;
|
m_should_propagate = true;
|
||||||
while (m_should_propagate && m.inc() && !m_egraph.inconsistent()) {
|
while (m_should_propagate && !should_stop()) {
|
||||||
m_should_propagate = false;
|
m_should_propagate = false;
|
||||||
m_egraph.propagate();
|
m_egraph.propagate();
|
||||||
m_mam->propagate();
|
m_mam->propagate();
|
||||||
|
propagate_rules();
|
||||||
IF_VERBOSE(11, verbose_stream() << "propagate " << m_stats.m_num_instances << "\n");
|
IF_VERBOSE(11, verbose_stream() << "propagate " << m_stats.m_num_instances << "\n");
|
||||||
if (!m_should_propagate)
|
if (!m_should_propagate)
|
||||||
check_rules();
|
propagate_all_rules();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void completion::add_constraint(expr* f, expr_dependency* d) {
|
void completion::add_constraint(expr* f, expr_dependency* d) {
|
||||||
if (m_egraph.inconsistent())
|
if (m_egraph.inconsistent())
|
||||||
return;
|
return;
|
||||||
auto add_children = [&](enode* n) {
|
auto add_children = [&](enode* n) {
|
||||||
for (auto* ch : enode_args(n))
|
for (auto* ch : enode_args(n))
|
||||||
m_nodes_to_canonize.push_back(ch);
|
m_nodes_to_canonize.push_back(ch);
|
||||||
};
|
};
|
||||||
expr* x, * y;
|
expr* x, * y;
|
||||||
if (m.is_eq(f, x, y)) {
|
if (m.is_eq(f, x, y)) {
|
||||||
enode* a = mk_enode(x);
|
enode* a = mk_enode(x);
|
||||||
|
@ -160,7 +225,7 @@ namespace euf {
|
||||||
if (!get_dependency(q)) {
|
if (!get_dependency(q)) {
|
||||||
m_q2dep.insert(q, d);
|
m_q2dep.insert(q, d);
|
||||||
get_trail().push(insert_obj_map(m_q2dep, q));
|
get_trail().push(insert_obj_map(m_q2dep, q));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
add_rule(f, d);
|
add_rule(f, d);
|
||||||
}
|
}
|
||||||
|
@ -174,9 +239,9 @@ namespace euf {
|
||||||
d = m.mk_join(d, explain_eq(n, n->get_root()));
|
d = m.mk_join(d, explain_eq(n, n->get_root()));
|
||||||
return l_true;
|
return l_true;
|
||||||
}
|
}
|
||||||
if (m.is_false(n->get_root()->get_expr()))
|
if (m.is_false(n->get_root()->get_expr()))
|
||||||
return l_false;
|
return l_false;
|
||||||
|
|
||||||
expr* g = nullptr;
|
expr* g = nullptr;
|
||||||
if (m.is_not(f, g)) {
|
if (m.is_not(f, g)) {
|
||||||
n = mk_enode(g);
|
n = mk_enode(g);
|
||||||
|
@ -184,6 +249,8 @@ namespace euf {
|
||||||
d = m.mk_join(d, explain_eq(n, n->get_root()));
|
d = m.mk_join(d, explain_eq(n, n->get_root()));
|
||||||
return l_true;
|
return l_true;
|
||||||
}
|
}
|
||||||
|
if (m.is_true(n->get_root()->get_expr()))
|
||||||
|
return l_false;
|
||||||
}
|
}
|
||||||
if (m_side_condition_solver) {
|
if (m_side_condition_solver) {
|
||||||
expr_dependency* sd = nullptr;
|
expr_dependency* sd = nullptr;
|
||||||
|
@ -203,7 +270,7 @@ namespace euf {
|
||||||
expr_ref_vector body(m);
|
expr_ref_vector body(m);
|
||||||
expr_ref head(y, m);
|
expr_ref head(y, m);
|
||||||
body.push_back(x);
|
body.push_back(x);
|
||||||
flatten_and(body);
|
flatten_and(body);
|
||||||
unsigned j = 0;
|
unsigned j = 0;
|
||||||
for (auto f : body) {
|
for (auto f : body) {
|
||||||
switch (eval_cond(f, d)) {
|
switch (eval_cond(f, d)) {
|
||||||
|
@ -217,51 +284,66 @@ namespace euf {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
body.shrink(j);
|
body.shrink(j);
|
||||||
if (body.empty())
|
if (body.empty())
|
||||||
add_constraint(head, d);
|
add_constraint(head, d);
|
||||||
else {
|
else {
|
||||||
m_rules.push_back(alloc(ground_rule, body, head, d));
|
// create a new rule.
|
||||||
|
// add all (one is actually enough) parts of the body to watch list.
|
||||||
|
auto r = alloc(conditional_rule, body, head, d);
|
||||||
|
m_rules.push_back(r);
|
||||||
|
get_trail().push(new_obj_trail(r));
|
||||||
get_trail().push(push_back_vector(m_rules));
|
get_trail().push(push_back_vector(m_rules));
|
||||||
}
|
for (auto f : body) {
|
||||||
}
|
auto n = m_egraph.find(f)->get_root();
|
||||||
|
if (m.is_not(n->get_expr()))
|
||||||
void completion::check_rules() {
|
n = n->get_arg(0)->get_root();
|
||||||
for (auto& r : m_rules) {
|
m_rule_watch.reserve(n->get_id() + 1);
|
||||||
if (!r->m_active)
|
m_rule_watch[n->get_id()].push_back(r);
|
||||||
continue;
|
get_trail().push(push_watch_rule(m_rule_watch, n->get_id()));
|
||||||
switch (check_rule(*r)) {
|
|
||||||
case l_true:
|
|
||||||
get_trail().push(value_trail(r->m_active));
|
|
||||||
r->m_active = false;
|
|
||||||
break; // remove rule, it is activated
|
|
||||||
case l_false:
|
|
||||||
get_trail().push(value_trail(r->m_active));
|
|
||||||
r->m_active = false;
|
|
||||||
break; // remove rule, premise is false
|
|
||||||
case l_undef:
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
lbool completion::check_rule(ground_rule& r) {
|
void completion::propagate_all_rules() {
|
||||||
|
for (auto* r : m_rules)
|
||||||
|
if (!r->m_in_queue)
|
||||||
|
r->m_in_queue = true,
|
||||||
|
m_propagation_queue.push_back(r);
|
||||||
|
propagate_rules();
|
||||||
|
}
|
||||||
|
|
||||||
|
void completion::propagate_rules() {
|
||||||
|
for (unsigned i = 0; i < m_propagation_queue.size() && !should_stop(); ++i) {
|
||||||
|
auto r = m_propagation_queue[i];
|
||||||
|
r->m_in_queue = false;
|
||||||
|
propagate_rule(*r);
|
||||||
|
}
|
||||||
|
clear_propagation_queue();
|
||||||
|
}
|
||||||
|
|
||||||
|
void completion::propagate_rule(conditional_rule& r) {
|
||||||
|
if (!r.m_active)
|
||||||
|
return;
|
||||||
for (auto* f : r.m_body) {
|
for (auto* f : r.m_body) {
|
||||||
switch (eval_cond(f, r.m_dep)) {
|
switch (eval_cond(f, r.m_dep)) {
|
||||||
case l_true:
|
case l_true:
|
||||||
break;
|
break;
|
||||||
case l_false:
|
case l_false:
|
||||||
return l_false;
|
get_trail().push(value_trail(r.m_active));
|
||||||
|
r.m_active = false;
|
||||||
|
return;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (r.m_body.empty()) {
|
if (r.m_body.empty()) {
|
||||||
add_constraint(r.m_head, r.m_dep);
|
add_constraint(r.m_head, r.m_dep);
|
||||||
return l_true;
|
get_trail().push(value_trail(r.m_active));
|
||||||
|
r.m_active = false;
|
||||||
}
|
}
|
||||||
return l_undef;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// callback when mam finds a binding
|
||||||
void completion::on_binding(quantifier* q, app* pat, enode* const* binding, unsigned mg, unsigned ming, unsigned mx) {
|
void completion::on_binding(quantifier* q, app* pat, enode* const* binding, unsigned mg, unsigned ming, unsigned mx) {
|
||||||
if (m_egraph.inconsistent())
|
if (m_egraph.inconsistent())
|
||||||
return;
|
return;
|
||||||
|
@ -272,6 +354,7 @@ namespace euf {
|
||||||
expr_ref r = subst(q->get_expr(), _binding);
|
expr_ref r = subst(q->get_expr(), _binding);
|
||||||
IF_VERBOSE(12, verbose_stream() << "add " << r << "\n");
|
IF_VERBOSE(12, verbose_stream() << "add " << r << "\n");
|
||||||
add_constraint(r, get_dependency(q));
|
add_constraint(r, get_dependency(q));
|
||||||
|
propagate_rules();
|
||||||
m_should_propagate = true;
|
m_should_propagate = true;
|
||||||
++m_stats.m_num_instances;
|
++m_stats.m_num_instances;
|
||||||
}
|
}
|
||||||
|
@ -285,7 +368,7 @@ namespace euf {
|
||||||
}
|
}
|
||||||
unsigned sz = qtail();
|
unsigned sz = qtail();
|
||||||
for (unsigned i = qhead(); i < sz; ++i) {
|
for (unsigned i = qhead(); i < sz; ++i) {
|
||||||
auto [f, p, d] = m_fmls[i]();
|
auto [f, p, d] = m_fmls[i]();
|
||||||
expr_dependency_ref dep(d, m);
|
expr_dependency_ref dep(d, m);
|
||||||
expr_ref g = canonize_fml(f, dep);
|
expr_ref g = canonize_fml(f, dep);
|
||||||
if (g != f) {
|
if (g != f) {
|
||||||
|
@ -346,7 +429,7 @@ namespace euf {
|
||||||
n = m_egraph.find(arg);
|
n = m_egraph.find(arg);
|
||||||
if (n)
|
if (n)
|
||||||
m_args.push_back(n);
|
m_args.push_back(n);
|
||||||
else
|
else
|
||||||
m_todo.push_back(arg);
|
m_todo.push_back(arg);
|
||||||
}
|
}
|
||||||
if (sz == m_todo.size()) {
|
if (sz == m_todo.size()) {
|
||||||
|
@ -361,7 +444,7 @@ namespace euf {
|
||||||
|
|
||||||
auto is_nullary = [&](expr* e) {
|
auto is_nullary = [&](expr* e) {
|
||||||
return is_app(e) && to_app(e)->get_num_args() == 0;
|
return is_app(e) && to_app(e)->get_num_args() == 0;
|
||||||
};
|
};
|
||||||
expr* x, * y;
|
expr* x, * y;
|
||||||
if (m.is_eq(f, x, y)) {
|
if (m.is_eq(f, x, y)) {
|
||||||
expr_ref x1 = canonize(x, d);
|
expr_ref x1 = canonize(x, d);
|
||||||
|
@ -379,10 +462,10 @@ namespace euf {
|
||||||
if (x == y)
|
if (x == y)
|
||||||
return expr_ref(m.mk_true(), m);
|
return expr_ref(m.mk_true(), m);
|
||||||
|
|
||||||
if (x == x1 && y == y1)
|
if (x == x1 && y == y1)
|
||||||
return m_rewriter.mk_eq(x, y);
|
return m_rewriter.mk_eq(x, y);
|
||||||
|
|
||||||
if (is_nullary(x) && is_nullary(y))
|
if (is_nullary(x) && is_nullary(y))
|
||||||
return mk_and(m_rewriter.mk_eq(x, x1), m_rewriter.mk_eq(y, x1));
|
return mk_and(m_rewriter.mk_eq(x, x1), m_rewriter.mk_eq(y, x1));
|
||||||
|
|
||||||
if (x == x1 && is_nullary(x))
|
if (x == x1 && is_nullary(x))
|
||||||
|
@ -390,13 +473,13 @@ namespace euf {
|
||||||
|
|
||||||
if (y == y1 && is_nullary(y))
|
if (y == y1 && is_nullary(y))
|
||||||
return m_rewriter.mk_eq(x1, y1);
|
return m_rewriter.mk_eq(x1, y1);
|
||||||
|
|
||||||
if (is_nullary(x))
|
if (is_nullary(x))
|
||||||
return mk_and(m_rewriter.mk_eq(x, x1), m_rewriter.mk_eq(y1, x1));
|
return mk_and(m_rewriter.mk_eq(x, x1), m_rewriter.mk_eq(y1, x1));
|
||||||
|
|
||||||
if (is_nullary(y))
|
if (is_nullary(y))
|
||||||
return mk_and(m_rewriter.mk_eq(y, y1), m_rewriter.mk_eq(x1, y1));
|
return mk_and(m_rewriter.mk_eq(y, y1), m_rewriter.mk_eq(x1, y1));
|
||||||
|
|
||||||
if (x1 == y1)
|
if (x1 == y1)
|
||||||
return expr_ref(m.mk_true(), m);
|
return expr_ref(m.mk_true(), m);
|
||||||
else {
|
else {
|
||||||
|
@ -438,8 +521,8 @@ namespace euf {
|
||||||
}
|
}
|
||||||
if (m.is_eq(f))
|
if (m.is_eq(f))
|
||||||
return m_rewriter.mk_eq(m_eargs.get(0), m_eargs.get(1));
|
return m_rewriter.mk_eq(m_eargs.get(0), m_eargs.get(1));
|
||||||
if (!change)
|
if (!change)
|
||||||
return expr_ref(f, m);
|
return expr_ref(f, m);
|
||||||
else
|
else
|
||||||
return expr_ref(m_rewriter.mk_app(to_app(f)->get_decl(), m_eargs.size(), m_eargs.data()), m);
|
return expr_ref(m_rewriter.mk_app(to_app(f)->get_decl(), m_eargs.size(), m_eargs.data()), m);
|
||||||
}
|
}
|
||||||
|
@ -448,11 +531,11 @@ namespace euf {
|
||||||
enode* n = m_egraph.find(f);
|
enode* n = m_egraph.find(f);
|
||||||
enode* r = n->get_root();
|
enode* r = n->get_root();
|
||||||
d = m.mk_join(d, explain_eq(n, r));
|
d = m.mk_join(d, explain_eq(n, r));
|
||||||
d = m.mk_join(d, m_deps.get(r->get_id(), nullptr));
|
d = m.mk_join(d, m_deps.get(r->get_id(), nullptr));
|
||||||
SASSERT(m_canonical.get(r->get_id()));
|
SASSERT(m_canonical.get(r->get_id()));
|
||||||
return m_canonical.get(r->get_id());
|
return m_canonical.get(r->get_id());
|
||||||
}
|
}
|
||||||
|
|
||||||
expr* completion::get_canonical(enode* n) {
|
expr* completion::get_canonical(enode* n) {
|
||||||
if (m_epochs.get(n->get_id(), 0) == m_epoch)
|
if (m_epochs.get(n->get_id(), 0) == m_epoch)
|
||||||
return m_canonical.get(n->get_id());
|
return m_canonical.get(n->get_id());
|
||||||
|
@ -466,10 +549,10 @@ namespace euf {
|
||||||
unsigned idx;
|
unsigned idx;
|
||||||
expr_ref old_value;
|
expr_ref old_value;
|
||||||
public:
|
public:
|
||||||
vtrail(expr_ref_vector& c, unsigned idx) :
|
vtrail(expr_ref_vector& c, unsigned idx) :
|
||||||
c(c), idx(idx), old_value(c.get(idx), c.m()) {
|
c(c), idx(idx), old_value(c.get(idx), c.m()) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void undo() override {
|
void undo() override {
|
||||||
c[idx] = old_value;
|
c[idx] = old_value;
|
||||||
old_value = nullptr;
|
old_value = nullptr;
|
||||||
|
@ -507,24 +590,24 @@ namespace euf {
|
||||||
}
|
}
|
||||||
|
|
||||||
void completion::collect_statistics(statistics& st) const {
|
void completion::collect_statistics(statistics& st) const {
|
||||||
st.update("euf-completion-rewrites", m_stats.m_num_rewrites);
|
st.update("euf-completion-rewrites", m_stats.m_num_rewrites);
|
||||||
st.update("euf-completion-instances", m_stats.m_num_instances);
|
st.update("euf-completion-instances", m_stats.m_num_instances);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool completion::is_gt(expr* lhs, expr* rhs) const {
|
bool completion::is_gt(expr* lhs, expr* rhs) const {
|
||||||
if (lhs == rhs)
|
if (lhs == rhs)
|
||||||
return false;
|
return false;
|
||||||
// values are always less in ordering than non-values.
|
// values are always less in ordering than non-values.
|
||||||
bool v1 = m.is_value(lhs);
|
bool v1 = m.is_value(lhs);
|
||||||
bool v2 = m.is_value(rhs);
|
bool v2 = m.is_value(rhs);
|
||||||
if (!v1 && v2)
|
if (!v1 && v2)
|
||||||
return true;
|
return true;
|
||||||
if (v1 && !v2)
|
if (v1 && !v2)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (get_depth(lhs) > get_depth(rhs))
|
if (get_depth(lhs) > get_depth(rhs))
|
||||||
return true;
|
return true;
|
||||||
if (get_depth(lhs) < get_depth(rhs))
|
if (get_depth(lhs) < get_depth(rhs))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// slow path
|
// slow path
|
||||||
|
@ -534,16 +617,16 @@ namespace euf {
|
||||||
return true;
|
return true;
|
||||||
if (n1 < n2)
|
if (n1 < n2)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (is_app(lhs) && is_app(rhs)) {
|
if (is_app(lhs) && is_app(rhs)) {
|
||||||
app* l = to_app(lhs);
|
app* l = to_app(lhs);
|
||||||
app* r = to_app(rhs);
|
app* r = to_app(rhs);
|
||||||
if (l->get_decl()->get_id() != r->get_decl()->get_id())
|
if (l->get_decl()->get_id() != r->get_decl()->get_id())
|
||||||
return l->get_decl()->get_id() > r->get_decl()->get_id();
|
return l->get_decl()->get_id() > r->get_decl()->get_id();
|
||||||
if (l->get_num_args() != r->get_num_args())
|
if (l->get_num_args() != r->get_num_args())
|
||||||
return l->get_num_args() > r->get_num_args();
|
return l->get_num_args() > r->get_num_args();
|
||||||
for (unsigned i = 0; i < l->get_num_args(); ++i)
|
for (unsigned i = 0; i < l->get_num_args(); ++i)
|
||||||
if (l->get_arg(i) != r->get_arg(i))
|
if (l->get_arg(i) != r->get_arg(i))
|
||||||
return is_gt(l->get_arg(i), r->get_arg(i));
|
return is_gt(l->get_arg(i), r->get_arg(i));
|
||||||
UNREACHABLE();
|
UNREACHABLE();
|
||||||
}
|
}
|
||||||
|
@ -569,14 +652,14 @@ namespace euf {
|
||||||
n->mark1();
|
n->mark1();
|
||||||
roots.push_back(n);
|
roots.push_back(n);
|
||||||
enode* rep = nullptr;
|
enode* rep = nullptr;
|
||||||
for (enode* k : enode_class(n))
|
for (enode* k : enode_class(n))
|
||||||
if (!rep || m.is_value(k->get_expr()) || is_gt(rep->get_expr(), k->get_expr()))
|
if (!rep || m.is_value(k->get_expr()) || is_gt(rep->get_expr(), k->get_expr()))
|
||||||
rep = k;
|
rep = k;
|
||||||
// IF_VERBOSE(0, verbose_stream() << m_egraph.bpp(n) << " ->\n" << m_egraph.bpp(rep) << "\n";);
|
// IF_VERBOSE(0, verbose_stream() << m_egraph.bpp(n) << " ->\n" << m_egraph.bpp(rep) << "\n";);
|
||||||
m_reps.setx(n->get_id(), rep, nullptr);
|
m_reps.setx(n->get_id(), rep, nullptr);
|
||||||
|
|
||||||
TRACE(euf_completion, tout << "rep " << m_egraph.bpp(n) << " -> " << m_egraph.bpp(rep) << "\n";
|
TRACE(euf_completion, tout << "rep " << m_egraph.bpp(n) << " -> " << m_egraph.bpp(rep) << "\n";
|
||||||
for (enode* k : enode_class(n)) tout << m_egraph.bpp(k) << "\n";);
|
for (enode* k : enode_class(n)) tout << m_egraph.bpp(k) << "\n";);
|
||||||
m_todo.push_back(n->get_expr());
|
m_todo.push_back(n->get_expr());
|
||||||
for (enode* arg : enode_args(n)) {
|
for (enode* arg : enode_args(n)) {
|
||||||
arg = arg->get_root();
|
arg = arg->get_root();
|
||||||
|
@ -602,7 +685,7 @@ namespace euf {
|
||||||
enode* n = m_egraph.find(e);
|
enode* n = m_egraph.find(e);
|
||||||
SASSERT(n->is_root());
|
SASSERT(n->is_root());
|
||||||
enode* rep = m_reps[n->get_id()];
|
enode* rep = m_reps[n->get_id()];
|
||||||
if (get_canonical(n))
|
if (get_canonical(n))
|
||||||
m_todo.pop_back();
|
m_todo.pop_back();
|
||||||
else if (get_depth(rep->get_expr()) == 0 || !is_app(rep->get_expr())) {
|
else if (get_depth(rep->get_expr()) == 0 || !is_app(rep->get_expr())) {
|
||||||
set_canonical(n, rep->get_expr());
|
set_canonical(n, rep->get_expr());
|
||||||
|
@ -626,15 +709,14 @@ namespace euf {
|
||||||
}
|
}
|
||||||
if (sz == m_todo.size()) {
|
if (sz == m_todo.size()) {
|
||||||
m_todo.pop_back();
|
m_todo.pop_back();
|
||||||
if (new_arg)
|
if (new_arg)
|
||||||
new_expr = m_rewriter.mk_app(to_app(rep->get_expr())->get_decl(), m_eargs.size(), m_eargs.data());
|
new_expr = m_rewriter.mk_app(to_app(rep->get_expr())->get_decl(), m_eargs.size(), m_eargs.data());
|
||||||
else
|
else
|
||||||
new_expr = rep->get_expr();
|
new_expr = rep->get_expr();
|
||||||
set_canonical(n, new_expr);
|
set_canonical(n, new_expr);
|
||||||
m_deps.setx(n->get_id(), d);
|
m_deps.setx(n->get_id(), d);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -7,7 +7,10 @@ Module Name:
|
||||||
|
|
||||||
Abstract:
|
Abstract:
|
||||||
|
|
||||||
Ground completion for equalities
|
Completion for (conditional) equalities.
|
||||||
|
This transforms expressions into a normal form by perorming equality saturation modulo
|
||||||
|
ground equations and E-matching on quantified axioms.
|
||||||
|
It supports conditional equations in terms of implications.
|
||||||
|
|
||||||
Author:
|
Author:
|
||||||
|
|
||||||
|
@ -27,11 +30,17 @@ namespace euf {
|
||||||
|
|
||||||
class side_condition_solver {
|
class side_condition_solver {
|
||||||
public:
|
public:
|
||||||
|
struct solution {
|
||||||
|
expr* var;
|
||||||
|
expr_ref term;
|
||||||
|
expr_ref guard;
|
||||||
|
};
|
||||||
virtual ~side_condition_solver() = default;
|
virtual ~side_condition_solver() = default;
|
||||||
virtual void add_constraint(expr* f, expr_dependency* d) = 0;
|
virtual void add_constraint(expr* f, expr_dependency* d) = 0;
|
||||||
virtual bool is_true(expr* f, expr_dependency*& d) = 0;
|
virtual bool is_true(expr* f, expr_dependency*& d) = 0;
|
||||||
virtual void push() = 0;
|
virtual void push() = 0;
|
||||||
virtual void pop(unsigned n) = 0;
|
virtual void pop(unsigned n) = 0;
|
||||||
|
virtual void solve_for(vector<solution>& sol) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
class completion : public dependent_expr_simplifier, public on_binding_callback, public mam_solver {
|
class completion : public dependent_expr_simplifier, public on_binding_callback, public mam_solver {
|
||||||
|
@ -42,12 +51,13 @@ namespace euf {
|
||||||
void reset() { memset(this, 0, sizeof(*this)); }
|
void reset() { memset(this, 0, sizeof(*this)); }
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ground_rule {
|
struct conditional_rule {
|
||||||
expr_ref_vector m_body;
|
expr_ref_vector m_body;
|
||||||
expr_ref m_head;
|
expr_ref m_head;
|
||||||
expr_dependency* m_dep;
|
expr_dependency* m_dep;
|
||||||
bool m_active = true;
|
bool m_active = true;
|
||||||
ground_rule(expr_ref_vector& b, expr_ref& h, expr_dependency* d) :
|
bool m_in_queue = false;
|
||||||
|
conditional_rule(expr_ref_vector& b, expr_ref& h, expr_dependency* d) :
|
||||||
m_body(b), m_head(h), m_dep(d) {}
|
m_body(b), m_head(h), m_dep(d) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -64,9 +74,11 @@ namespace euf {
|
||||||
th_rewriter m_rewriter;
|
th_rewriter m_rewriter;
|
||||||
stats m_stats;
|
stats m_stats;
|
||||||
scoped_ptr<side_condition_solver> m_side_condition_solver;
|
scoped_ptr<side_condition_solver> m_side_condition_solver;
|
||||||
ptr_vector<ground_rule> m_rules;
|
ptr_vector<conditional_rule> m_rules;
|
||||||
bool m_has_new_eq = false;
|
bool m_has_new_eq = false;
|
||||||
bool m_should_propagate = false;
|
bool m_should_propagate = false;
|
||||||
|
unsigned m_max_instantiations = std::numeric_limits<unsigned>::max();
|
||||||
|
vector<ptr_vector<conditional_rule>> m_rule_watch;
|
||||||
|
|
||||||
enode* mk_enode(expr* e);
|
enode* mk_enode(expr* e);
|
||||||
bool is_new_eq(expr* a, expr* b);
|
bool is_new_eq(expr* a, expr* b);
|
||||||
|
@ -87,32 +99,38 @@ namespace euf {
|
||||||
|
|
||||||
lbool eval_cond(expr* f, expr_dependency*& d);
|
lbool eval_cond(expr* f, expr_dependency*& d);
|
||||||
|
|
||||||
lbool check_rule(ground_rule& rule);
|
|
||||||
void check_rules();
|
bool should_stop();
|
||||||
|
|
||||||
void add_rule(expr* f, expr_dependency* d);
|
void add_rule(expr* f, expr_dependency* d);
|
||||||
|
void watch_rule(enode* root, enode* other);
|
||||||
|
void propagate_rule(conditional_rule& r);
|
||||||
|
void propagate_rules();
|
||||||
|
void propagate_all_rules();
|
||||||
|
void clear_propagation_queue();
|
||||||
|
ptr_vector<conditional_rule> m_propagation_queue;
|
||||||
|
struct push_watch_rule;
|
||||||
|
|
||||||
bool is_gt(expr* a, expr* b) const;
|
bool is_gt(expr* a, expr* b) const;
|
||||||
public:
|
public:
|
||||||
completion(ast_manager& m, dependent_expr_state& fmls);
|
completion(ast_manager& m, dependent_expr_state& fmls);
|
||||||
~completion() override;
|
~completion() override;
|
||||||
char const* name() const override { return "euf-completion"; }
|
char const* name() const override { return "euf-completion"; }
|
||||||
void push() override { if (m_side_condition_solver) m_side_condition_solver->push(); m_egraph.push(); dependent_expr_simplifier::push(); }
|
void push() override;
|
||||||
void pop(unsigned n) override { dependent_expr_simplifier::pop(n); m_egraph.pop(n); if (m_side_condition_solver) m_side_condition_solver->pop(1);
|
void pop(unsigned n) override;
|
||||||
}
|
|
||||||
void reduce() override;
|
void reduce() override;
|
||||||
void collect_statistics(statistics& st) const override;
|
void collect_statistics(statistics& st) const override;
|
||||||
void reset_statistics() override { m_stats.reset(); }
|
void reset_statistics() override { m_stats.reset(); }
|
||||||
|
void updt_params(params_ref const& p) override;
|
||||||
|
|
||||||
trail_stack& get_trail() override { return m_trail;}
|
trail_stack& get_trail() override { return m_trail;}
|
||||||
region& get_region() override { return m_trail.get_region(); }
|
region& get_region() override { return m_trail.get_region(); }
|
||||||
egraph& get_egraph() override { return m_egraph; }
|
egraph& get_egraph() override { return m_egraph; }
|
||||||
bool is_relevant(enode* n) const override { return true; }
|
bool is_relevant(enode* n) const override { return true; }
|
||||||
bool resource_limits_exceeded() const override { return false; }
|
bool resource_limits_exceeded() const override { return m_stats.m_num_instances > m_max_instantiations; }
|
||||||
ast_manager& get_manager() override { return m; }
|
ast_manager& get_manager() override { return m; }
|
||||||
|
|
||||||
void on_binding(quantifier* q, app* pat, enode* const* binding, unsigned mg, unsigned ming, unsigned mx) override;
|
void on_binding(quantifier* q, app* pat, enode* const* binding, unsigned mg, unsigned ming, unsigned mx) override;
|
||||||
|
|
||||||
void set_solver(side_condition_solver* s) { m_side_condition_solver = s; }
|
void set_solver(side_condition_solver* s) { m_side_condition_solver = s; }
|
||||||
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ Author:
|
||||||
#include "tactic/tactic.h"
|
#include "tactic/tactic.h"
|
||||||
#include "tactic/portfolio/euf_completion_tactic.h"
|
#include "tactic/portfolio/euf_completion_tactic.h"
|
||||||
#include "solver/solver.h"
|
#include "solver/solver.h"
|
||||||
|
#include "smt/smt_solver.h"
|
||||||
|
|
||||||
class euf_side_condition_solver : public euf::side_condition_solver {
|
class euf_side_condition_solver : public euf::side_condition_solver {
|
||||||
ast_manager& m;
|
ast_manager& m;
|
||||||
|
@ -25,55 +26,81 @@ class euf_side_condition_solver : public euf::side_condition_solver {
|
||||||
scoped_ptr<solver> m_solver;
|
scoped_ptr<solver> m_solver;
|
||||||
expr_ref_vector m_deps;
|
expr_ref_vector m_deps;
|
||||||
obj_map<expr, expr_dependency*> m_e2d;
|
obj_map<expr, expr_dependency*> m_e2d;
|
||||||
|
expr_ref_vector m_fmls;
|
||||||
|
obj_hashtable<expr> m_seen;
|
||||||
|
trail_stack m_trail;
|
||||||
|
|
||||||
void init_solver() {
|
void init_solver() {
|
||||||
if (m_solver.get())
|
if (m_solver.get())
|
||||||
return;
|
return;
|
||||||
m_params.set_uint("smt.max_conflicts", 100);
|
m_params.set_uint("smt.max_conflicts", 100);
|
||||||
scoped_ptr<solver_factory> f = mk_smt_strategic_solver_factory();
|
scoped_ptr<solver_factory> f = mk_smt_solver_factory();
|
||||||
m_solver = (*f)(m, m_params, false, false, true, symbol::null);
|
m_solver = (*f)(m, m_params, false, false, true, symbol::null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
euf_side_condition_solver(ast_manager& m, params_ref const& p) : m(m), m_params(p), m_deps(m) {}
|
|
||||||
|
euf_side_condition_solver(ast_manager& m, params_ref const& p) :
|
||||||
|
m(m), m_params(p), m_deps(m), m_fmls(m) {}
|
||||||
|
|
||||||
void push() override {
|
void push() override {
|
||||||
init_solver();
|
init_solver();
|
||||||
m_solver->push();
|
m_solver->push();
|
||||||
|
m_trail.pop_scope(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
void pop(unsigned n) override {
|
void pop(unsigned n) override {
|
||||||
|
m_trail.push_scope();
|
||||||
SASSERT(m_solver.get());
|
SASSERT(m_solver.get());
|
||||||
m_solver->pop(n);
|
m_solver->pop(n);
|
||||||
}
|
}
|
||||||
|
|
||||||
void add_constraint(expr* f, expr_dependency* d) override {
|
void add_constraint(expr* f, expr_dependency* d) override {
|
||||||
|
if (m_seen.contains(f))
|
||||||
|
return;
|
||||||
|
m_seen.insert(f);
|
||||||
|
m_trail.push(insert_obj_trail(m_seen, f));
|
||||||
if (!is_ground(f))
|
if (!is_ground(f))
|
||||||
return;
|
return;
|
||||||
|
if (m.is_implies(f))
|
||||||
|
return;
|
||||||
init_solver();
|
init_solver();
|
||||||
expr* e_dep = nullptr;
|
|
||||||
if (d) {
|
if (d) {
|
||||||
e_dep = m.mk_fresh_const("dep", m.mk_bool_sort());
|
expr* e_dep = m.mk_fresh_const("dep", m.mk_bool_sort());
|
||||||
m_deps.push_back(e_dep);
|
m_deps.push_back(e_dep);
|
||||||
m_e2d.insert(e_dep, d);
|
m_e2d.insert(e_dep, d);
|
||||||
|
m_trail.push(insert_obj_map(m_e2d, e_dep));
|
||||||
|
m_solver->assert_expr(f, e_dep);
|
||||||
}
|
}
|
||||||
m_solver->assert_expr(f, e_dep);
|
else
|
||||||
|
m_solver->assert_expr(f);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool is_true(expr* f, expr_dependency*& d) override {
|
bool is_true(expr* f, expr_dependency*& d) override {
|
||||||
d = nullptr;
|
d = nullptr;
|
||||||
m_solver->push();
|
solver::scoped_push _sp(*m_solver);
|
||||||
expr_ref_vector fmls(m);
|
m_fmls.reset();
|
||||||
fmls.push_back(m.mk_not(f));
|
m_fmls.push_back(m.mk_not(f));
|
||||||
expr_ref nf(m.mk_not(f), m);
|
expr_ref nf(m.mk_not(f), m);
|
||||||
lbool r = m_solver->check_sat(fmls);
|
lbool r = m_solver->check_sat(m_fmls);
|
||||||
if (r == l_false) {
|
if (r == l_false) {
|
||||||
expr_ref_vector core(m);
|
expr_ref_vector core(m);
|
||||||
m_solver->get_unsat_core(core);
|
m_solver->get_unsat_core(core);
|
||||||
for (auto c : core)
|
for (auto c : core)
|
||||||
d = m.mk_join(d, m_e2d[c]);
|
d = m.mk_join(d, m_e2d[c]);
|
||||||
}
|
}
|
||||||
m_solver->pop(1);
|
|
||||||
return r == l_false;
|
return r == l_false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void solve_for(vector<solution>& sol) override {
|
||||||
|
vector<solver::solution> ss;
|
||||||
|
for (auto [v, t, g] : sol)
|
||||||
|
ss.push_back({ v, t, g });
|
||||||
|
sol.reset();
|
||||||
|
m_solver->solve_for(ss);
|
||||||
|
for (auto [v, t, g] : ss)
|
||||||
|
sol.push_back({ v, t, g });
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
dependent_expr_simplifier* mk_euf_completion_simplifier(ast_manager& m, dependent_expr_state& s, params_ref const& p) {
|
dependent_expr_simplifier* mk_euf_completion_simplifier(ast_manager& m, dependent_expr_state& s, params_ref const& p) {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue