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

ufbv-rewriter is really a demodulator rewriter and does not reference ufbv

so moving first the rewriter into place of other rewriters
This commit is contained in:
Nikolaj Bjorner 2022-12-04 04:44:02 -08:00
parent e455897178
commit 7fe6787748
5 changed files with 37 additions and 36 deletions

View file

@ -11,6 +11,7 @@ z3_add_component(rewriter
cached_var_subst.cpp
char_rewriter.cpp
datatype_rewriter.cpp
demodulator_rewriter.cpp
der.cpp
distribute_forall.cpp
dl_rewriter.cpp

View file

@ -0,0 +1,887 @@
/*++
Copyright (c) 2006 Microsoft Corporation
Module Name:
demodulator.cpp
Abstract:
<abstract>
Author:
Leonardo de Moura (leonardo) 2010-04-12.
Revision History:
Christoph M. Wintersteiger (cwinter) 2010-04-21: Implementation
Christoph M. Wintersteiger (cwinter) 2012-10-24: Moved from demodulator.h to ufbv_rewriter.h
--*/
#include "util/uint_set.h"
#include "ast/ast_pp.h"
#include "ast/for_each_expr.h"
#include "ast/rewriter/var_subst.h"
#include "ast/rewriter/demodulator_rewriter.h"
demodulator_rewriter::demodulator_rewriter(ast_manager & m):
m(m),
m_match_subst(m),
m_bsimp(m),
m_todo(m),
m_in_processed(m),
m_new_args(m),
m_rewrite_todo(m),
m_rewrite_cache(m),
m_new_exprs(m) {
params_ref p;
p.set_bool("elim_and", true);
m_bsimp.updt_params(p);
}
demodulator_rewriter::~demodulator_rewriter() {
reset_dealloc_values(m_fwd_idx);
reset_dealloc_values(m_back_idx);
for (auto & kv : m_demodulator2lhs_rhs) {
m.dec_ref(kv.m_key);
m.dec_ref(kv.m_value.first);
m.dec_ref(kv.m_value.second);
}
}
bool demodulator_rewriter::is_demodulator(expr * e, app_ref & large, expr_ref & small) const {
if (!is_forall(e)) {
return false;
}
expr * qe = to_quantifier(e)->get_expr();
expr * lhs = nullptr, *rhs = nullptr, *n;
if (m.is_eq(qe, lhs, rhs)) {
int subset = is_subset(lhs, rhs);
int smaller = is_smaller(lhs, rhs);
TRACE("demodulator", tout << "testing is_demodulator:\n"
<< mk_pp(lhs, m) << "\n"
<< mk_pp(rhs, m) << "\n"
<< "subset: " << subset << ", smaller: " << smaller << "\n";);
// We only track uninterpreted functions, everything else is likely too expensive.
if ((subset == +1 || subset == +2) && smaller == +1) {
if (is_uninterp(rhs)) {
large = to_app(rhs);
small = lhs;
return true;
}
// lhs = (not rhs) --> (not lhs) = rhs
if (m.is_not(rhs, n) && is_uninterp(n)) {
large = to_app(n);
small = m.mk_not(lhs);
return true;
}
}
if ((subset == -1 || subset == +2) && smaller == -1) {
if (is_uninterp(lhs)) {
large = to_app(lhs);
small = rhs;
return true;
}
// (not lhs) = rhs --> lhs = (not rhs)
if (m.is_not(lhs, n) && is_uninterp(n)) {
large = to_app(n);
small = m.mk_not(rhs);
return true;
}
}
}
else if (m.is_not(qe, n) && is_app(n)) {
// this is like (not (f ... )) --> (= (f ...) false)
large = to_app(n);
small = m.mk_false();
return true;
}
else if (is_uninterp(qe)) {
// this is like (f ... ) --> (= (f ...) true)
large = to_app(qe);
small = m.mk_true();
return true;
}
return false;
}
class var_set_proc {
uint_set & m_set;
public:
var_set_proc(uint_set &s):m_set(s) {}
void operator()(var * n) { m_set.insert(n->get_idx()); }
void operator()(quantifier * n) {}
void operator()(app * n) {}
};
int demodulator_rewriter::is_subset(expr * e1, expr * e2) const {
uint_set ev1, ev2;
if (m.is_value(e1))
return 1; // values are always a subset!
var_set_proc proc1(ev1);
for_each_expr(proc1, e1);
var_set_proc proc2(ev2);
for_each_expr(proc2, e2);
return (ev1==ev2 ) ? +2 : // We return +2 if the sets are equal.
(ev1.subset_of(ev2)) ? +1 :
(ev2.subset_of(ev1)) ? -1 :
0 ;
}
int demodulator_rewriter::is_smaller(expr * e1, expr * e2) const {
unsigned sz1 = 0, sz2 = 0;
// values are always smaller!
if (m.is_value(e1))
return +1;
else if (m.is_value(e2))
return -1;
// interpreted stuff is always better than uninterpreted.
if (!is_uninterp(e1) && is_uninterp(e2))
return +1;
else if (is_uninterp(e1) && !is_uninterp(e2))
return -1;
// two uninterpreted functions are ordered first by the number of
// arguments, then by their id.
if (is_uninterp(e1) && is_uninterp(e2)) {
if (to_app(e1)->get_num_args() < to_app(e2)->get_num_args())
return +1;
else if (to_app(e1)->get_num_args() > to_app(e2)->get_num_args())
return -1;
else {
unsigned a = to_app(e1)->get_decl()->get_id();
unsigned b = to_app(e2)->get_decl()->get_id();
if (a < b)
return +1;
else if (a > b)
return -1;
}
}
sz1 = get_depth(e1);
sz2 = get_depth(e2);
return (sz1 == sz2) ? 0 :
(sz1 < sz2) ? +1 :
-1 ;
}
class max_var_id_proc {
unsigned m_max_var_id;
public:
max_var_id_proc():m_max_var_id(0) {}
void operator()(var * n) {
if(n->get_idx() > m_max_var_id)
m_max_var_id = n->get_idx();
}
void operator()(quantifier * n) {}
void operator()(app * n) {}
unsigned get_max() { return m_max_var_id; }
};
unsigned demodulator_rewriter::max_var_id(expr * e)
{
max_var_id_proc proc;
for_each_expr(proc, e);
return proc.get_max();
}
void demodulator_rewriter::insert_fwd_idx(expr * large, expr * small, quantifier * demodulator) {
SASSERT(large->get_kind() == AST_APP);
SASSERT(demodulator);
SASSERT(large && small);
TRACE("demodulator_fwd", tout << "INSERT: " << mk_pp(demodulator, m) << std::endl; );
func_decl * fd = to_app(large)->get_decl();
fwd_idx_map::iterator it = m_fwd_idx.find_iterator(fd);
if (it == m_fwd_idx.end()) {
quantifier_set * qs = alloc(quantifier_set, 1);
m_fwd_idx.insert(fd, qs);
it = m_fwd_idx.find_iterator(fd);
}
SASSERT(it->m_value);
it->m_value->insert(demodulator);
m.inc_ref(demodulator);
m.inc_ref(large);
m.inc_ref(small);
m_demodulator2lhs_rhs.insert(demodulator, expr_pair(large, small));
}
void demodulator_rewriter::remove_fwd_idx(func_decl * f, quantifier * demodulator) {
TRACE("demodulator_fwd", tout << "REMOVE: " << std::hex << (size_t)demodulator << std::endl; );
fwd_idx_map::iterator it = m_fwd_idx.find_iterator(f);
if (it != m_fwd_idx.end()) {
demodulator2lhs_rhs::iterator fit = m_demodulator2lhs_rhs.find_iterator(demodulator);
expr_pair p = fit->m_value;
m_demodulator2lhs_rhs.erase(demodulator);
it->m_value->erase(demodulator);
m.dec_ref(p.first);
m.dec_ref(p.second);
m.dec_ref(demodulator);
} else {
SASSERT(m_demodulator2lhs_rhs.contains(demodulator));
}
}
bool demodulator_rewriter::check_fwd_idx_consistency() {
for (auto & kv : m_fwd_idx) {
quantifier_set * set = kv.m_value;
SASSERT(set);
for (auto e : *set) {
if (!m_demodulator2lhs_rhs.contains(e))
return false;
}
}
return true;
}
void demodulator_rewriter::show_fwd_idx(std::ostream & out) {
for (auto & kv : m_fwd_idx) {
quantifier_set * set = kv.m_value;
SASSERT(!set);
out << kv.m_key->get_name() << ": " << std::endl;
for (auto e : *set) {
out << std::hex << (size_t)e << std::endl;
}
}
out << "D2LR: " << std::endl;
for (auto & kv : m_demodulator2lhs_rhs) {
out << (size_t) kv.m_key << std::endl;
}
}
bool demodulator_rewriter::rewrite1(func_decl * f, expr_ref_vector & m_new_args, expr_ref & np) {
fwd_idx_map::iterator it = m_fwd_idx.find_iterator(f);
if (it != m_fwd_idx.end()) {
TRACE("demodulator_bug", tout << "trying to rewrite: " << f->get_name() << " args:\n";
tout << m_new_args << "\n";);
for (quantifier* d : *it->m_value) {
SASSERT(m_demodulator2lhs_rhs.contains(d));
expr_pair l_s;
m_demodulator2lhs_rhs.find(d, l_s);
app * large = to_app(l_s.first);
if (large->get_num_args() != m_new_args.size())
continue;
TRACE("demodulator_bug", tout << "Matching with demodulator: " << mk_pp(d, m) << std::endl; );
SASSERT(large->get_decl() == f);
if (m_match_subst(large, l_s.second, m_new_args.data(), np)) {
TRACE("demodulator_bug", tout << "succeeded...\n" << mk_pp(l_s.second, m) << "\n===>\n" << mk_pp(np, m) << "\n";);
return true;
}
}
}
return false;
}
bool demodulator_rewriter::rewrite_visit_children(app * a) {
bool res=true;
unsigned j = a->get_num_args();
while (j > 0) {
expr * e = a->get_arg(--j);
if (!m_rewrite_cache.contains(e) || !m_rewrite_cache.get(e).second) {
bool recursive = false;
unsigned sz = m_rewrite_todo.size();
expr * v = e;
if (m_rewrite_cache.contains(e)) {
expr_bool_pair const & ebp = m_rewrite_cache.get(e);
if (ebp.second) {
v = ebp.first;
}
}
for (unsigned i = sz; i-- > 0;) {
if (m_rewrite_todo[i] == v) {
recursive = true;
TRACE("demodulator", tout << "Detected demodulator cycle: " <<
mk_pp(a, m) << " --> " << mk_pp(v, m) << std::endl;);
rewrite_cache(e, v, true);
break;
}
}
if (!recursive) {
m_rewrite_todo.push_back(e);
res = false;
}
}
}
return res;
}
void demodulator_rewriter::rewrite_cache(expr * e, expr * new_e, bool done) {
m_rewrite_cache.insert(e, expr_bool_pair(new_e, done));
}
expr * demodulator_rewriter::rewrite(expr * n) {
if (m_fwd_idx.empty())
return n;
TRACE("demodulator", tout << "rewrite: " << mk_pp(n, m) << std::endl; );
app * a;
SASSERT(m_rewrite_todo.empty());
m_rewrite_cache.reset();
m_rewrite_todo.push_back(n);
while (!m_rewrite_todo.empty()) {
TRACE("demodulator_stack", tout << "STACK: " << std::endl;
for (unsigned i = 0; i < m_rewrite_todo.size(); i++)
tout << std::dec << i << ": " << std::hex << (size_t)m_rewrite_todo[i] <<
" = " << mk_pp(m_rewrite_todo[i], m) << std::endl;
);
expr * e = m_rewrite_todo.back();
expr_ref actual(e, m);
if (m_rewrite_cache.contains(e)) {
const expr_bool_pair &ebp = m_rewrite_cache.get(e);
if (ebp.second) {
m_rewrite_todo.pop_back();
continue;
}
else {
actual = ebp.first;
}
}
switch (actual->get_kind()) {
case AST_VAR:
rewrite_cache(e, actual, true);
m_rewrite_todo.pop_back();
break;
case AST_APP:
a = to_app(actual);
if (rewrite_visit_children(a)) {
func_decl * f = a->get_decl();
m_new_args.reset();
bool all_untouched = true;
for (expr* o_child : *a) {
expr * n_child;
SASSERT(m_rewrite_cache.contains(o_child) && m_rewrite_cache.get(o_child).second);
expr_bool_pair const & ebp = m_rewrite_cache.get(o_child);
n_child = ebp.first;
if (n_child != o_child)
all_untouched = false;
m_new_args.push_back(n_child);
}
expr_ref np(m);
if (rewrite1(f, m_new_args, np)) {
rewrite_cache(e, np, false);
// No pop.
}
else {
if (all_untouched) {
rewrite_cache(e, actual, true);
}
else {
expr_ref na(m);
if (f->get_family_id() != m.get_basic_family_id())
na = m.mk_app(f, m_new_args);
else
m_bsimp.mk_app(f, m_new_args.size(), m_new_args.data(), na);
TRACE("demodulator_bug", tout << "e:\n" << mk_pp(e, m) << "\nnew_args: \n";
tout << m_new_args << "\n";
tout << "=====>\n";
tout << "na:\n " << na << "\n";);
rewrite_cache(e, na, true);
}
m_rewrite_todo.pop_back();
}
}
break;
case AST_QUANTIFIER: {
expr * body = to_quantifier(actual)->get_expr();
if (m_rewrite_cache.contains(body)) {
const expr_bool_pair ebp = m_rewrite_cache.get(body);
SASSERT(ebp.second);
expr * new_body = ebp.first;
quantifier_ref q(m);
q = m.update_quantifier(to_quantifier(actual), new_body);
m_new_exprs.push_back(q);
expr_ref new_q = elim_unused_vars(m, q, params_ref());
m_new_exprs.push_back(new_q);
rewrite_cache(e, new_q, true);
m_rewrite_todo.pop_back();
} else {
m_rewrite_todo.push_back(body);
}
break;
}
default:
UNREACHABLE();
}
}
SASSERT(m_rewrite_cache.contains(n));
const expr_bool_pair & ebp = m_rewrite_cache.get(n);
SASSERT(ebp.second);
expr * r = ebp.first;
TRACE("demodulator", tout << "rewrite result: " << mk_pp(r, m) << std::endl; );
return r;
}
class demodulator_rewriter::add_back_idx_proc {
back_idx_map & m_back_idx;
expr * m_expr;
public:
add_back_idx_proc(back_idx_map & bi, expr * e):m_back_idx(bi),m_expr(e) {}
void operator()(var * n) {}
void operator()(quantifier * n) {}
void operator()(app * n) {
// We track only uninterpreted and constant functions.
if (n->get_num_args()==0) return;
SASSERT(m_expr && m_expr != (expr*) 0x00000003);
func_decl * d=n->get_decl();
if (d->get_family_id() == null_family_id) {
back_idx_map::iterator it = m_back_idx.find_iterator(d);
if (it != m_back_idx.end()) {
SASSERT(it->m_value);
it->m_value->insert(m_expr);
} else {
expr_set * e = alloc(expr_set);
e->insert(m_expr);
m_back_idx.insert(d, e);
}
}
}
};
class demodulator_rewriter::remove_back_idx_proc {
back_idx_map & m_back_idx;
expr * m_expr;
public:
remove_back_idx_proc(back_idx_map & bi, expr * e):m_back_idx(bi),m_expr(e) {}
void operator()(var * n) {}
void operator()(quantifier * n) {}
void operator()(app * n) {
// We track only uninterpreted and constant functions.
if (n->get_num_args()==0) return;
func_decl * d=n->get_decl();
if (d->get_family_id() == null_family_id) {
back_idx_map::iterator it = m_back_idx.find_iterator(d);
if (it != m_back_idx.end()) {
SASSERT(it->m_value);
it->m_value->remove(m_expr);
}
}
}
};
void demodulator_rewriter::reschedule_processed(func_decl * f) {
//use m_back_idx to find all formulas p in m_processed that contains f {
back_idx_map::iterator it = m_back_idx.find_iterator(f);
if (it != m_back_idx.end()) {
SASSERT(it->m_value);
expr_set temp;
for (expr* p : *it->m_value) {
if (m_processed.contains(p))
temp.insert(p);
}
for (expr * p : temp) {
// remove p from m_processed and m_back_idx
m_processed.remove(p);
remove_back_idx_proc proc(m_back_idx, p); // this could change it->m_value, thus we need the `temp' set.
for_each_expr(proc, p);
// insert p into m_todo
m_todo.push_back(p);
}
}
}
bool demodulator_rewriter::can_rewrite(expr * n, expr * lhs) {
// this is a quick check, we just traverse d and check if there is an expression in d that is an instance of lhs of n'.
// we cannot use the trick used for m_processed, since the main loop would not terminate.
ptr_vector<expr> stack;
expr * curr;
expr_mark visited;
stack.push_back(n);
while (!stack.empty()) {
curr = stack.back();
if (visited.is_marked(curr)) {
stack.pop_back();
continue;
}
switch(curr->get_kind()) {
case AST_VAR:
visited.mark(curr, true);
stack.pop_back();
break;
case AST_APP:
if (for_each_expr_args(stack, visited, to_app(curr)->get_num_args(), to_app(curr)->get_args())) {
if (m_match_subst(lhs, curr))
return true;
visited.mark(curr, true);
stack.pop_back();
}
break;
case AST_QUANTIFIER:
if (!for_each_expr_args(stack, visited, to_quantifier(curr)->get_num_patterns(),
to_quantifier(curr)->get_patterns())) {
break;
}
if (!for_each_expr_args(stack, visited, to_quantifier(curr)->get_num_no_patterns(),
to_quantifier(curr)->get_no_patterns())) {
break;
}
if (!visited.is_marked(to_quantifier(curr)->get_expr())) {
stack.push_back(to_quantifier(curr)->get_expr());
break;
}
stack.pop_back();
break;
default:
UNREACHABLE();
}
}
return false;
}
void demodulator_rewriter::reschedule_demodulators(func_decl * f, expr * lhs) {
// use m_back_idx to find all demodulators d in m_fwd_idx that contains f {
//ptr_vector<expr> to_remove;
back_idx_map::iterator it = m_back_idx.find_iterator(f);
if (it != m_back_idx.end()) {
SASSERT(it->m_value);
expr_set all_occurrences;
expr_ref l(m);
for (auto s : *it->m_value)
all_occurrences.insert(s);
// Run over all f-demodulators
for (expr* occ : all_occurrences) {
if (!is_quantifier(occ))
continue;
// Use the fwd idx to find out whether this is a demodulator.
demodulator2lhs_rhs::iterator d2lr_it = m_demodulator2lhs_rhs.find_iterator(to_quantifier(occ));
if (d2lr_it != m_demodulator2lhs_rhs.end()) {
l = d2lr_it->m_value.first;
quantifier_ref d(m);
func_decl_ref df(m);
d = to_quantifier(occ);
df = to_app(l)->get_decl();
// Now we know there is an occurrence of f in d
// if n' can rewrite d {
if (can_rewrite(d, lhs)) {
TRACE("demodulator", tout << "Rescheduling: " << std::endl << mk_pp(d, m) << std::endl; );
// remove d from m_fwd_idx
remove_fwd_idx(df, d);
// remove d from m_back_idx
// just remember it here, because otherwise it and/or esit might become invalid?
// to_remove.insert(d);
remove_back_idx_proc proc(m_back_idx, d);
for_each_expr(proc, d);
// insert d into m_todo
m_todo.push_back(d);
}
}
}
}
}
void demodulator_rewriter::operator()(unsigned n, expr * const * exprs, proof * const * prs,
expr_ref_vector & new_exprs, proof_ref_vector & new_prs) {
if (m.proofs_enabled()) {
TRACE("tactic", tout << "PRE_DEMODULATOR=true is not supported when proofs are enabled.";);
// Let us not waste time with proof production
new_exprs.append(n, exprs);
new_prs.append(n, prs);
return;
}
TRACE("demodulator", tout << "before demodulator:\n";
for ( unsigned i = 0 ; i < n ; i++ )
tout << mk_pp(exprs[i], m) << std::endl; );
// Initially, m_todo contains all formulas. That is, it contains the argument exprs. m_fwd_idx, m_processed, m_back_idx are empty.
unsigned max_vid = 0;
for ( unsigned i = 0 ; i < n ; i++ ) {
m_todo.push_back(exprs[i]);
max_vid = std::max(max_vid, max_var_id(exprs[i]));
}
m_match_subst.reserve(max_vid);
while (!m_todo.empty()) {
// let n be the next formula in m_todo.
expr_ref cur(m);
cur = m_todo.back();
m_todo.pop_back();
// rewrite cur using m_fwd_idx, and let n' be the result.
expr_ref np(rewrite(cur), m);
// at this point, it should be the case that there is no demodulator in m_fwd_idx that can rewrite n'.
// unless there is a demodulator cycle
// SASSERT(rewrite(np)==np);
// if (n' is not a demodulator) {
app_ref large(m);
expr_ref small(m);
if (!is_demodulator(np, large, small)) {
// insert n' into m_processed
m_processed.insert(np);
m_in_processed.push_back(np);
// update m_back_idx (traverse n' and for each uninterpreted function declaration f in n' add the entry f->n' to m_back_idx)
add_back_idx_proc proc(m_back_idx, np);
for_each_expr(proc, np);
} else {
// np is a demodulator that allows us to replace 'large' with 'small'.
TRACE("demodulator", tout << "Found demodulator: " << std::endl;
tout << mk_pp(large.get(), m) << std::endl << " ---> " <<
std::endl << mk_pp(small.get(), m) << std::endl; );
TRACE("demodulator_s", tout << "Found demodulator: " << std::endl;
tout << to_app(large)->get_decl()->get_name() <<
"[" << to_app(large)->get_depth() << "]" << " ---> ";
if (is_app(small))
tout << to_app(small)->get_decl()->get_name() <<
"[" << to_app(small)->get_depth() << "]" << std::endl;
else
tout << mk_pp(small.get(), m) << std::endl; );
// let f be the top symbol of n'
func_decl * f = large->get_decl();
reschedule_processed(f);
reschedule_demodulators(f, large);
// insert n' into m_fwd_idx
insert_fwd_idx(large, small, to_quantifier(np));
// update m_back_idx
add_back_idx_proc proc(m_back_idx, np);
for_each_expr(proc, np);
}
}
// the result is the contents of m_processed + all demodulators in m_fwd_idx.
for (expr* e : m_processed) {
new_exprs.push_back(e);
TRACE("demodulator", tout << mk_pp(e, m) << std::endl; );
}
for (auto const& kv : m_fwd_idx) {
if (kv.m_value) {
for (expr* e : *kv.m_value) {
new_exprs.push_back(e);
TRACE("demodulator", tout << mk_pp(e, m) << std::endl; );
}
}
}
TRACE("demodulator", tout << "after demodulator:\n" << new_exprs << "\n";);
}
demodulator_rewriter::match_subst::match_subst(ast_manager & m):
m(m),
m_subst(m) {
}
/**
\brief Auxiliary functor used to implement optimization in match_args. See comment there.
*/
struct match_args_aux_proc {
substitution & m_subst;
struct no_match {};
match_args_aux_proc(substitution & s):m_subst(s) {}
void operator()(var * n) {
expr_offset r;
if (m_subst.find(n, 0, r)) {
if (r.get_expr() != n) {
SASSERT(r.get_offset() == 1);
throw no_match();
}
else {
m_subst.insert(n, 0, expr_offset(n, 1));
}
}
else
throw no_match();
}
void operator()(quantifier * n) { throw no_match(); }
void operator()(app * n) {}
};
bool demodulator_rewriter::match_subst::match_args(app * lhs, expr * const * args) {
m_cache.reset();
m_todo.reset();
// fill todo-list, and perform quick success/failure tests
m_all_args_eq = true;
unsigned num_args = lhs->get_num_args();
for (unsigned i = 0; i < num_args; i++) {
expr * t_arg = lhs->get_arg(i);
expr * i_arg = args[i];
if (t_arg != i_arg)
m_all_args_eq = false;
if (is_app(t_arg) && is_app(i_arg) && to_app(t_arg)->get_decl() != to_app(i_arg)->get_decl()) {
// quick failure...
return false;
}
m_todo.push_back(expr_pair(t_arg, i_arg));
}
if (m_all_args_eq) {
// quick success worked...
return true;
}
m_subst.reset();
while (!m_todo.empty()) {
expr_pair const & p = m_todo.back();
if (is_var(p.first)) {
expr_offset r;
if (m_subst.find(to_var(p.first), 0, r)) {
if (r.get_expr() != p.second)
return false;
}
else {
m_subst.insert(to_var(p.first), 0, expr_offset(p.second, 1));
}
m_todo.pop_back();
continue;
}
if (is_var(p.second))
return false;
// we may have nested quantifiers.
if (is_quantifier(p.first) || is_quantifier(p.second))
return false;
SASSERT(is_app(p.first) && is_app(p.second));
if (to_app(p.first)->is_ground() && !to_app(p.second)->is_ground())
return false;
if (p.first == p.second && to_app(p.first)->is_ground()) {
SASSERT(to_app(p.second)->is_ground());
m_todo.pop_back();
continue;
}
if (m_cache.contains(p)) {
m_todo.pop_back();
continue;
}
if (p.first == p.second) {
// p.first and p.second is not ground...
// Traverse p.first and check whether every variable X:0 in p.first
// 1) is unbounded (then we bind X:0 -> X:1)
// 2) or, is already bounded to X:1
// If that is, the case, we execute:
// m_todo.pop_back();
// m_cache.insert(p);
// continue;
// Otherwise
// return false;
match_args_aux_proc proc(m_subst);
try {
for_each_expr(proc, p.first);
// succeeded
m_todo.pop_back();
m_cache.insert(p);
continue;
}
catch (const match_args_aux_proc::no_match &) {
return false;
}
}
app * n1 = to_app(p.first);
app * n2 = to_app(p.second);
if (n1->get_decl() != n2->get_decl())
return false;
unsigned num_args1 = n1->get_num_args();
if (num_args1 != n2->get_num_args())
return false;
m_todo.pop_back();
if (num_args1 == 0)
continue;
m_cache.insert(p);
unsigned j = num_args1;
while (j > 0) {
--j;
m_todo.push_back(expr_pair(n1->get_arg(j), n2->get_arg(j)));
}
}
return true;
}
bool demodulator_rewriter::match_subst::operator()(app * lhs, expr * rhs, expr * const * args, expr_ref & new_rhs) {
if (match_args(lhs, args)) {
if (m_all_args_eq) {
// quick success...
new_rhs = rhs;
return true;
}
unsigned deltas[2] = { 0, 0 };
m_subst.apply(2, deltas, expr_offset(rhs, 0), new_rhs);
return true;
}
return false;
}
bool demodulator_rewriter::match_subst::operator()(expr * t, expr * i) {
m_cache.reset();
m_todo.reset();
if (is_var(t))
return true;
if (is_app(t) && is_app(i) &&
to_app(t)->get_decl() == to_app(i)->get_decl() &&
to_app(t)->get_num_args() == to_app(i)->get_num_args()) {
return match_args(to_app(t), to_app(i)->get_args());
}
return false;
}

View file

@ -0,0 +1,266 @@
/*++
Copyright (c) 2006 Microsoft Corporation
Module Name:
demodulator_rewriter.h
Abstract:
<abstract>
Author:
Leonardo de Moura (leonardo) 2010-04-12.
Revision History:
Christoph M. Wintersteiger (cwinter) 2012-10-24: Moved from demodulator.h to ufbv_rewriter.h
Nikolaj Bjorner (nbjorner) 2022-12-4: Moved to rewriter and renamed to demodulator_rewriter.h
--*/
#pragma once
#include "ast/ast.h"
#include "ast/substitution/substitution.h"
#include "ast/rewriter/bool_rewriter.h"
#include "util/obj_hashtable.h"
#include "util/obj_pair_hashtable.h"
#include "util/array_map.h"
/**
\brief Apply demodulators as a preprocessing technique.
In first-order theorem proving (FOTP), a demodulator is a universally quantified formula of the form:
Forall X1, ..., Xn. L[X1, ..., Xn] = R[X1, ..., Xn]
Where L[X1, ..., Xn] contains all variables in R[X1, ..., Xn], and
L[X1, ..., Xn] is "bigger" than R[X1, ...,Xn].
The idea is to replace something big L[X1, ..., Xn] with something smaller R[X1, ..., Xn].
In FOTP, they use term orderings to decide what does it mean to be smaller.
We are using demodulators in a different context (pre-processing).
So, I suggest we have a virtual method is_smaller for comparing expressions.
The default implementation just compares the size of the expressions.
Similarly, in our context, formulas using iff are also demodulators.
Forall X1, ..., Xn. L[X1, ..., Xn] iff R[X1, ..., Xn]
After selecting the demodulators, we traverse the rest of the formula looking for instances of L[X1, ..., Xn].
Whenever we find an instance, we replace it with the associated instance of R[X1, ..., Xn].
For example, suppose we have
Forall x, y. f(x+y, y) = y
and
f(g(b) + h(c), h(c)) <= 0
The term f(g(b) + h(c), h(c)) is an instance of f(x+y, y) if we replace x <- g(b) and y <- h(c).
So, we can replace it with "y" which is bound to h(c) in this example. So, the result of the transformation is:
Forall x, y. f(x+y, y) = y
and
h(c) <= 0
In the first implementation, let us ignore theory matching. That is,
for us the term f(a+1) is not an instance of f(1+x), because the
matcher doesn't know + is commutative. Observe the demodulator is
*not* copied to the macro manager in this case.
Another complication is when we are looking for instances inside other universally quantified formulas.
The problem is that both formulas (demodular) and target are reusing variables names (ids).
To avoid renaming, we use offsets. The idea is to represent renames implicitly. In this case,
each offset is a different "variable bank". A pair (expr, offset) is essentially an expression
where every variable in expr is assumed to be from the "bank" offset.
The class substitution (in substitution.h) manages offsets for us.
The class matcher (in matcher.h) can be used to test whether an expression is an instance of another one.
Finally, there is the problem when we have N demodulators (where N is big), and a big formula, and we want
to traverse the formula only once looking for opportunities for applying these N demodulators.
We want to efficiently find the applicable demodulars.
We can start with a simple optimization that given a func_decl it returns the set of demodulators that start with this declaration.
For example, suppose we have the demodulators.
forall x, f(x, g(0)) = 10
forall x, f(g(h(x)), g(1)) = 20
Then, in our "index" f would map to these two demodulators.
As a final optimization, we should adapt the code to use substitution-trees.
The current implementation in Z3 is not efficient, and I think it is buggy.
So, it would be great to replace it with a new one.
The code in spc_rewriter.* does something like that. We cannot reuse this code directly since it is meant
for the superposion engine in Z3, but we can adapt it for our needs in the preprocessor.
*/
class demodulator_rewriter final {
class rewrite_proc;
class add_back_idx_proc;
class remove_back_idx_proc;
class can_rewrite_proc;
typedef std::pair<expr *, bool> expr_bool_pair;
class plugin {
ast_manager& m;
public:
plugin(ast_manager& m): m(m) { }
void ins_eh(expr* k, expr_bool_pair v) { m.inc_ref(k); m.inc_ref(v.first); }
void del_eh(expr* k, expr_bool_pair v) { m.dec_ref(k); m.dec_ref(v.first); }
static unsigned to_int(expr const * k) { return k->get_id(); }
};
typedef array_map<expr*, expr_bool_pair, plugin> expr_map;
typedef std::pair<expr *, expr *> expr_pair;
typedef obj_hashtable<expr> expr_set;
typedef obj_map<func_decl, expr_set *> back_idx_map;
typedef obj_hashtable<quantifier> quantifier_set;
typedef obj_map<func_decl, quantifier_set *> fwd_idx_map;
typedef obj_map<quantifier, expr_pair> demodulator2lhs_rhs;
typedef expr_map rewrite_cache_map;
/**
\brief Custom matcher & substitution application
*/
class match_subst {
typedef std::pair<expr *, expr *> expr_pair;
typedef obj_pair_hashtable<expr, expr> cache;
void reset();
ast_manager & m;
substitution m_subst;
cache m_cache;
svector<expr_pair> m_todo;
bool m_all_args_eq;
bool match_args(app * t, expr * const * args);
public:
match_subst(ast_manager & m);
void reserve(unsigned max_vid) { m_subst.reserve(2, max_vid+1); }
/**
\brief Let f be the top symbol of lhs. If (f args) is an
instance of lhs, that is, there is a substitution s
s.t. s[lhs] = (f args), then return true and store s[rhs]
into new_rhs. Where s[t] represents the application of the
substitution s into t.
Assumptions, the variables in lhs and (f args) are assumed to be distinct.
So, (f x y) matches (f y x).
Moreover, the result should be in terms of the variables in (f args).
*/
bool operator()(app * lhs, expr * rhs, expr * const * args, expr_ref & new_rhs);
/**
\brief Return true if \c i is an instance of \c t.
*/
bool operator()(expr * t, expr * i);
};
ast_manager & m;
match_subst m_match_subst;
bool_rewriter m_bsimp;
fwd_idx_map m_fwd_idx;
back_idx_map m_back_idx;
demodulator2lhs_rhs m_demodulator2lhs_rhs;
expr_ref_buffer m_todo;
obj_hashtable<expr> m_processed;
expr_ref_vector m_in_processed;
expr_ref_vector m_new_args;
expr_ref_buffer m_rewrite_todo;
rewrite_cache_map m_rewrite_cache;
expr_ref_buffer m_new_exprs;
void insert_fwd_idx(expr * large, expr * small, quantifier * demodulator);
void remove_fwd_idx(func_decl * f, quantifier * demodulator);
bool check_fwd_idx_consistency();
void show_fwd_idx(std::ostream & out);
bool is_demodulator(expr * e, app_ref & large, expr_ref & small) const;
bool can_rewrite(expr * n, expr * lhs);
expr * rewrite(expr * n);
bool rewrite1(func_decl * f, expr_ref_vector & m_new_args, expr_ref & np);
bool rewrite_visit_children(app * a);
void rewrite_cache(expr * e, expr * new_e, bool done);
void reschedule_processed(func_decl * f);
void reschedule_demodulators(func_decl * f, expr * np);
unsigned max_var_id(expr * e);
// is_smaller returns -1 for e1<e2, 0 for e1==e2 and +1 for e1>e2.
int is_smaller(expr * e1, expr * e2) const;
// is_subset returns -1 for e1 subset e2, +1 for e2 subset e1, 0 else.
int is_subset(expr * e1, expr * e2) const;
public:
demodulator_rewriter(ast_manager & m);
~demodulator_rewriter();
void operator()(unsigned n, expr * const * exprs, proof * const * prs, expr_ref_vector & new_exprs, proof_ref_vector & new_prs);
/**
Given a demodulator (aka rewrite rule) of the form
Forall X. L[X] = R[X]
We say the top symbol is the first symbol in L.
For example: f is the top symbol in
Forall x, f(h(x)) = x + 1
The rewrite engine main loop is based on the DISCOUNT loop used in first-order theorem provers.
Main structures:
- m_todo: The todo-stack of formulas to be processed.
- m_fwd_idx: "Forward index" for finding efficiently which demodulators can be used to rewrite an expression.
We organize this set as a mapping from func_decl to a set of demodulators which start with the same top symbol.
- m_processed: The set of already processed formulas. We can represent it using a hashtable.
- m_back_idx: "Backward index" we use it to find efficiently which already processed expressions and demodulators may be rewritten
by a new demodulator. Again, we use a very simple index, for each uninterpreted function symbol (ignore constants)
f, store the expressions in m_processed and the demodulators that contain f.
If you prefer, you may use two m_back_idxs (one for formulas in m_processed and another for demodulators in m_fwd_idx).
Initially, m_todo contains all formulas. That is, it contains the argument exprs. m_fwd_idx, m_processed, m_back_idx are empty.
while (m_todo is not empty) {
let n be the next formula in m_todo.
rewrite n using m_fwd_idx, and let n' be the result.
// at this point, it should be the case that there is no demodulator in m_fwd_idx that can rewrite n'.
if (n' is not a demodulator) {
insert n' into m_processed
update m_back_idx (traverse n' and for each uninterpreted function declaration f in n' add the entry f->n' to m_back_idx)
}
else {
let f be the top symbol of n'
use m_back_idx to find all formulas p in m_processed that contains f {
remove p from m_processed
remove p from m_back_idx
insert p into m_todo
}
use m_back_idx to find all demodulators d in m_fwd_idx that contains f {
if n' can rewrite d {
// this is a quick check, we just traverse d and check if there is an expression in d that is an instance of lhs of n'.
// we cannot use the trick used for m_processed, since the main loop would not terminate.
remove d from m_fwd_idx
remode d from m_back_idx
insert p into m_todo
}
}
insert n' into m_fwd_idx
update m_back_idx
}
}
the result is the contents of m_processed + all demodulators in m_fwd_idx.
Note: to remove p from m_back_idx, we need to traverse p, and for every function declartion f in p, we should remove the entry f->p from m_back_idx.
Note: we can implement m_back_idx for formulas as:
typedef obj_hashtable<expr> expr_set;
obj_map<func_decl, expr_set *> m_back_idx;
we should represent the sets as hashtables because we want to be able to efficiently remove elements from these sets.
ptr_vector<expr_set> m_expr_set_to_delete; // same trick we used in macro_manager.
we can use a similar structure for m_back_idx and m_fwd_idx for demodulators.
Note: m_processed should be obj_hashtable<expr> since we want to remove elements from there efficiently.
*/
};