mirror of
https://github.com/Z3Prover/z3
synced 2025-08-02 01:13:18 +00:00
1020 lines
30 KiB
C++
1020 lines
30 KiB
C++
/*++
|
|
Copyright (c) 2022 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
eliminate_predicates.cpp
|
|
|
|
Author:
|
|
|
|
Nikolaj Bjorner (nbjorner) 2022-11-17.
|
|
|
|
Notes:
|
|
|
|
The simplifier
|
|
- detects macros of the form p(x) = q(x)
|
|
- other more general macro detection is TBD.
|
|
For example {~p, a} {~p, b} {p, ~a, ~b} {p, C} {~p, D} defines p as a conjunction
|
|
and we can obbtain {a, C}, {b, C} {~a, ~b, D } similar to propositional case.
|
|
Instead the case is handled by predicate elimination when p only occurs positively
|
|
outside of {~p, a} {~p, b} {p, ~a, ~b}
|
|
The SAT based definition detection scheme creates clauses
|
|
{a}, {b}, {~a,~b}, C, D and checks for an unsat core.
|
|
The core {a}, {b}, {~a, ~b} maps back to a definition for p
|
|
Then {p, C}, {~p, D} clauses are replaced based on the definition.
|
|
Claim: {C, D} is a consequence when we have created resolvents {C,X}, {D,Y}, where X => p => Y => X
|
|
(a task for a "Kitten"?)
|
|
- Handle various permutations of variables that are arguments to p?
|
|
- other SMT-based macro detection could be made here as well.
|
|
The (legacy) macro finder is not very flexible and could be replaced
|
|
by a module building on this one.
|
|
|
|
|
|
- eliminates predicates p(x) that occur at most once in each clause and the
|
|
number of occurrences is small.
|
|
|
|
Two sets of disabled functions are tracked:
|
|
|
|
forbidden from macros vs forbidden from elimination
|
|
- forbidden from macros: uninterpreted functions in recursive definitions
|
|
predicates before m_qhead
|
|
arguments to as-array
|
|
- forbidden from elimination:
|
|
- forbidden from macros,
|
|
- occurs more than once in some clause, or in nested occurrence.
|
|
|
|
--*/
|
|
|
|
|
|
#include "util/uint_set.h"
|
|
#include "ast/ast_pp.h"
|
|
#include "ast/ast_ll_pp.h"
|
|
#include "ast/ast_util.h"
|
|
#include "ast/for_each_ast.h"
|
|
#include "ast/recfun_decl_plugin.h"
|
|
#include "ast/bv_decl_plugin.h"
|
|
#include "ast/arith_decl_plugin.h"
|
|
#include "ast/occurs.h"
|
|
#include "ast/array_decl_plugin.h"
|
|
#include "ast/rewriter/var_subst.h"
|
|
#include "ast/rewriter/rewriter_def.h"
|
|
#include "ast/simplifiers/eliminate_predicates.h"
|
|
#include "ast/rewriter/th_rewriter.h"
|
|
#include "ast/rewriter/macro_replacer.h"
|
|
|
|
|
|
std::ostream& eliminate_predicates::clause::display(std::ostream& out) const {
|
|
ast_manager& m = m_dep.get_manager();
|
|
for (sort* s : m_bound)
|
|
out << mk_pp(s, m) << " ";
|
|
for (auto const& [atom, sign] : m_literals)
|
|
out << (sign ? "~" : "") << mk_bounded_pp(atom, m) << " ";
|
|
return out;
|
|
}
|
|
|
|
eliminate_predicates::eliminate_predicates(ast_manager& m, dependent_expr_state& fmls):
|
|
dependent_expr_simplifier(m, fmls), m_der(m), m_rewriter(m) {}
|
|
|
|
|
|
void eliminate_predicates::add_use_list(clause& cl) {
|
|
ast_mark seen;
|
|
for (auto const& [atom, sign] : cl.m_literals) {
|
|
if (!is_uninterp(atom)) {
|
|
m_to_exclude.push_back(atom);
|
|
continue;
|
|
}
|
|
|
|
func_decl* p = to_app(atom)->get_decl();
|
|
m_use_list.get(p, sign).push_back(&cl);
|
|
|
|
if (!m_predicate_decls.is_marked(p)) {
|
|
m_predicates.push_back(p);
|
|
m_predicate_decls.mark(p, true);
|
|
}
|
|
if (seen.is_marked(p))
|
|
m_to_exclude.push_back(atom);
|
|
else {
|
|
seen.mark(p, true);
|
|
m_to_exclude.append(to_app(atom)->get_num_args(), to_app(atom)->get_args());
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check that all arguments are distinct variables that are bound.
|
|
*/
|
|
|
|
bool eliminate_predicates::can_be_macro_head(expr* _head, unsigned num_bound) {
|
|
if (!is_app(_head))
|
|
return false;
|
|
app* head = to_app(_head);
|
|
func_decl* f = head->get_decl();
|
|
if (m_fmls.frozen(f))
|
|
return false;
|
|
if (m_is_macro.is_marked(f))
|
|
return false;
|
|
if (f->is_associative())
|
|
return false;
|
|
if (!is_uninterp(f))
|
|
return false;
|
|
uint_set indices;
|
|
for (expr* arg : *head) {
|
|
if (!is_var(arg))
|
|
return false;
|
|
unsigned idx = to_var(arg)->get_idx();
|
|
if (indices.contains(idx))
|
|
return false;
|
|
if (idx >= num_bound)
|
|
return false;
|
|
indices.insert(idx);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* a quasi macro head is of the form
|
|
* f(x,x) where x is the only bound variable
|
|
* f(x,y,x+y+3,1) where x, y are the only bound variables
|
|
*/
|
|
|
|
bool eliminate_predicates::can_be_quasi_macro_head(expr* _head, unsigned num_bound) {
|
|
if (!is_app(_head))
|
|
return false;
|
|
app* head = to_app(_head);
|
|
func_decl* f = head->get_decl();
|
|
if (m_fmls.frozen(f))
|
|
return false;
|
|
if (m_is_macro.is_marked(f))
|
|
return false;
|
|
if (f->is_associative())
|
|
return false;
|
|
if (!is_uninterp(f))
|
|
return false;
|
|
uint_set indices;
|
|
for (expr* arg : *head) {
|
|
if (occurs(f, arg))
|
|
return false;
|
|
if (!is_macro_safe(arg))
|
|
return false;
|
|
if (!is_var(arg))
|
|
continue;
|
|
unsigned idx = to_var(arg)->get_idx();
|
|
if (indices.contains(idx))
|
|
continue;
|
|
if (idx >= num_bound)
|
|
return false;
|
|
indices.insert(idx);
|
|
}
|
|
return indices.num_elems() == num_bound;
|
|
}
|
|
|
|
|
|
//
|
|
// (= (f x y (+ x y)) s), where x y are all bound variables.
|
|
// then replace (f x y z) by (if (= z (+ x y)) s (f' x y z))
|
|
//
|
|
|
|
void eliminate_predicates::insert_quasi_macro(app* head, expr* body, clause& cl) {
|
|
expr_ref _body(body, m);
|
|
uint_set indices;
|
|
expr_ref_vector args(m), eqs(m);
|
|
var_ref new_var(m);
|
|
app_ref lhs(m), rhs(m);
|
|
func_decl_ref f1(m);
|
|
sort_ref_vector sorts(m);
|
|
svector<symbol> names;
|
|
|
|
unsigned num_decls = cl.m_bound.size();
|
|
func_decl* f = head->get_decl();
|
|
|
|
for (expr* arg : *head) {
|
|
sorts.push_back(arg->get_sort());
|
|
names.push_back(symbol(std::string("x") + std::to_string(args.size())));
|
|
if (is_var(arg)) {
|
|
unsigned idx = to_var(arg)->get_idx();
|
|
if (!indices.contains(idx)) {
|
|
indices.insert(idx);
|
|
args.push_back(arg);
|
|
continue;
|
|
}
|
|
}
|
|
new_var = m.mk_var(eqs.size() + num_decls, arg->get_sort());
|
|
args.push_back(new_var);
|
|
eqs.push_back(m.mk_eq(arg, new_var));
|
|
}
|
|
|
|
// forall vars . f(args) = if eqs then body else f'(args)
|
|
f1 = m.mk_fresh_func_decl(f->get_name(), symbol::null, sorts.size(), sorts.data(), f->get_range());
|
|
|
|
lhs = m.mk_app(f, args);
|
|
rhs = m.mk_ite(mk_and(eqs), body, m.mk_app(f1, args));
|
|
insert_macro(lhs, rhs, cl);
|
|
}
|
|
|
|
|
|
|
|
expr_ref eliminate_predicates::bind_free_variables_in_def(clause& cl, app* head, expr* def) {
|
|
unsigned num_bound = cl.m_bound.size();
|
|
if (head->get_num_args() == num_bound)
|
|
return expr_ref(def, m);
|
|
|
|
// head(x) <=> forall yx', x = x' => def(yx')
|
|
svector<symbol> names;
|
|
expr_ref_vector ors(m);
|
|
expr_ref result(m);
|
|
ors.push_back(def);
|
|
|
|
for (unsigned i = 0; i < num_bound; ++i)
|
|
names.push_back(symbol(i));
|
|
for (expr* arg : *head) {
|
|
unsigned idx = to_var(arg)->get_idx();
|
|
ors.push_back(m.mk_not(m.mk_eq(arg, m.mk_var(idx + num_bound, arg->get_sort()))));
|
|
}
|
|
result = mk_or(ors);
|
|
result = m.mk_forall(num_bound, cl.m_bound.data(), names.data(), result);
|
|
rewrite(result);
|
|
return result;
|
|
}
|
|
/**
|
|
* cheap/simplistic heuristic to find definitions that are based on binary clauses
|
|
* (or (head x) (not (def x))
|
|
* (or (not (head x)) (def x))
|
|
*/
|
|
bool eliminate_predicates::try_find_binary_definition(func_decl* p, app_ref& head, expr_ref& def, expr_dependency_ref& dep) {
|
|
if (m_fmls.frozen(p))
|
|
return false;
|
|
expr_mark binary_pos, binary_neg;
|
|
obj_map<expr, expr_dependency*> deps;
|
|
auto is_def_predicate = [&](clause& cl, expr* atom) {
|
|
return is_app(atom) && to_app(atom)->get_decl() == p && can_be_macro_head(to_app(atom), cl.m_bound.size());
|
|
};
|
|
auto add_def = [&](clause& cl, expr* atom1, bool sign1, expr* atom2, bool sign2) {
|
|
if (is_def_predicate(cl, atom1) && !sign1) {
|
|
if (sign2)
|
|
binary_neg.mark(atom2);
|
|
else
|
|
binary_pos.mark(atom2);
|
|
if (cl.m_dep)
|
|
deps.insert(atom1, cl.m_dep);
|
|
}
|
|
};
|
|
|
|
for (auto* cl : m_use_list.get(p, false)) {
|
|
if (cl->m_alive && cl->size() == 2) {
|
|
auto const& [atom1, sign1] = cl->m_literals[0];
|
|
auto const& [atom2, sign2] = cl->m_literals[1];
|
|
add_def(*cl, atom1, sign1, atom2, sign2);
|
|
add_def(*cl, atom2, sign2, atom1, sign1);
|
|
}
|
|
}
|
|
|
|
auto is_def = [&](unsigned i, unsigned j, clause& cl) {
|
|
auto const& [atom1, sign1] = cl.m_literals[i];
|
|
auto const& [atom2, sign2] = cl.m_literals[j];
|
|
expr_dependency* d = nullptr;
|
|
if (is_def_predicate(cl, atom1) && sign1) {
|
|
if (sign2 && binary_pos.is_marked(atom2) && is_macro_safe(atom2) && !occurs(p, atom2)) {
|
|
head = to_app(atom1);
|
|
def = bind_free_variables_in_def(cl, head, m.mk_not(atom2));
|
|
dep = cl.m_dep;
|
|
if (deps.find(atom1, d))
|
|
dep = m.mk_join(dep, d);
|
|
return true;
|
|
}
|
|
if (!sign2 && binary_neg.is_marked(atom2) && is_macro_safe(atom2) && !occurs(p, atom2)) {
|
|
head = to_app(atom1);
|
|
def = bind_free_variables_in_def(cl, head, atom2);
|
|
dep = cl.m_dep;
|
|
if (deps.find(atom1, d))
|
|
dep = m.mk_join(dep, d);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
};
|
|
|
|
for (auto* cl : m_use_list.get(p, true)) {
|
|
if (cl->m_alive && cl->size() == 2) {
|
|
if (is_def(0, 1, *cl))
|
|
return true;
|
|
if (is_def(1, 0, *cl))
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool eliminate_predicates::is_macro_safe(expr* e) {
|
|
for (expr* arg : subterms::all(expr_ref(e, m)))
|
|
if (is_app(arg) && m_is_macro.is_marked(to_app(arg)->get_decl()))
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
void eliminate_predicates::insert_macro(app* head, expr* def, clause& cl) {
|
|
insert_macro(head, def, cl.m_dep);
|
|
TRACE("elim_predicates", tout << "remove " << cl << "\n");
|
|
cl.m_alive = false;
|
|
}
|
|
|
|
void eliminate_predicates::insert_macro(app* head, expr* def, expr_dependency* dep) {
|
|
unsigned num = head->get_num_args();
|
|
ptr_buffer<expr> vars, subst_args;
|
|
subst_args.resize(num, nullptr);
|
|
vars.resize(num, nullptr);
|
|
for (unsigned i = 0; i < num; i++) {
|
|
var* v = to_var(head->get_arg(i));
|
|
var* w = m.mk_var(i, v->get_sort());
|
|
unsigned idx = v->get_idx();
|
|
VERIFY(idx < num);
|
|
SASSERT(subst_args[idx] == 0);
|
|
subst_args[idx] = w;
|
|
vars[i] = w;
|
|
}
|
|
var_subst sub(m, false);
|
|
app_ref _head(m);
|
|
expr_ref _def(m);
|
|
expr_dependency_ref _dep(dep, m);
|
|
_def = sub(def, subst_args.size(), subst_args.data());
|
|
_head = m.mk_app(head->get_decl(), vars);
|
|
|
|
auto* info = alloc(macro_def, _head, _def, _dep);
|
|
m_macros.insert(head->get_decl(), info);
|
|
m_fmls.model_trail().push(head->get_decl(), _def, _dep, {}); // augment with definition for head
|
|
m_is_macro.mark(head->get_decl(), true);
|
|
TRACE("elim_predicates", tout << "insert " << _head << " " << _def << "\n");
|
|
++m_stats.m_num_macros;
|
|
}
|
|
|
|
void eliminate_predicates::try_resolve_definition(func_decl* p) {
|
|
app_ref head(m);
|
|
expr_ref def(m);
|
|
expr_dependency_ref dep(m);
|
|
if (try_find_binary_definition(p, head, def, dep))
|
|
insert_macro(head, def, dep);
|
|
}
|
|
|
|
/**
|
|
* Port of macros handled by macro_finder/macro_util
|
|
*/
|
|
void eliminate_predicates::try_find_macro(clause& cl) {
|
|
if (!cl.m_alive)
|
|
return;
|
|
expr* x, * y;
|
|
auto can_be_def = [&](expr* _x, expr* y) {
|
|
if (!is_app(_x))
|
|
return false;
|
|
app* x = to_app(_x);
|
|
return
|
|
can_be_macro_head(x, cl.m_bound.size()) &&
|
|
is_macro_safe(y) &&
|
|
x->get_num_args() == cl.m_bound.size() &&
|
|
!occurs(x->get_decl(), y);
|
|
};
|
|
// (= (f x) t)
|
|
if (cl.is_unit() && !cl.sign(0) && m.is_eq(cl.atom(0), x, y)) {
|
|
if (can_be_def(x, y)) {
|
|
insert_macro(to_app(x), y, cl);
|
|
return;
|
|
}
|
|
if (can_be_def(y, x)) {
|
|
insert_macro(to_app(y), x, cl);
|
|
return;
|
|
}
|
|
}
|
|
// not (= (p x) t) -> (p x) = (not t)
|
|
if (cl.is_unit() && cl.sign(0) && m.is_iff(cl.atom(0), x, y)) {
|
|
if (can_be_def(x, y)) {
|
|
insert_macro(to_app(x), m.mk_not(y), cl);
|
|
return;
|
|
}
|
|
if (can_be_def(y, x)) {
|
|
insert_macro(to_app(y), m.mk_not(x), cl);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// pseudo-macros:
|
|
// (iff (= (f x) t) cond)
|
|
// rewrites to (f x) = (if cond t else (k x))
|
|
// add clause (not (= (k x) t))
|
|
//
|
|
// we will call them _conditioned_ macros
|
|
|
|
auto can_be_conditioned = [&](expr* f, expr* t, expr* cond) {
|
|
return
|
|
can_be_def(f, t) &&
|
|
!occurs(to_app(f)->get_decl(), cond) &&
|
|
is_macro_safe(cond);
|
|
};
|
|
|
|
auto make_conditioned = [&](app* f, expr* t, expr* cond) {
|
|
func_decl* df = f->get_decl();
|
|
app_ref def(m), k(m), fml(m);
|
|
func_decl_ref fn(m);
|
|
fn = m.mk_fresh_func_decl(df->get_arity(), df->get_domain(), df->get_range());
|
|
m_fmls.model_trail().hide(fn); // hide definition of fn
|
|
k = m.mk_app(fn, f->get_num_args(), f->get_args());
|
|
def = m.mk_ite(cond, t, k);
|
|
insert_macro(f, def, cl);
|
|
fml = m.mk_not(m.mk_eq(k, t));
|
|
clause* new_cl = init_clause(fml, cl.m_dep, UINT_MAX);
|
|
add_use_list(*new_cl);
|
|
m_clauses.push_back(new_cl);
|
|
};
|
|
|
|
if (cl.is_unit() && !cl.sign(0) && m.is_iff(cl.atom(0), x, y)) {
|
|
expr* z, * u;
|
|
if (m.is_eq(x, z, u) && can_be_conditioned(z, u, y)) {
|
|
make_conditioned(to_app(z), u, y);
|
|
return;
|
|
}
|
|
if (m.is_eq(x, u, z) && can_be_conditioned(z, u, y)) {
|
|
make_conditioned(to_app(z), u, y);
|
|
return;
|
|
}
|
|
if (m.is_eq(y, z, u) && can_be_conditioned(z, u, x)) {
|
|
make_conditioned(to_app(z), u, x);
|
|
return;
|
|
}
|
|
if (m.is_eq(y, u, z) && can_be_conditioned(z, u, x)) {
|
|
make_conditioned(to_app(z), u, x);
|
|
return;
|
|
}
|
|
}
|
|
|
|
//
|
|
// other macros handled by macro_finder:
|
|
//
|
|
// arithmetic/bit-vectors
|
|
// (= (+ (f x) s) t)
|
|
// becomes (= (f x) (- t s))
|
|
//
|
|
// (= (+ (* -1 (f x)) x) t)
|
|
// becomes (= (f x) (- (- t s)))
|
|
|
|
bv_util bv(m);
|
|
arith_util a(m);
|
|
auto is_add = [&](expr * e) {
|
|
rational n;
|
|
return a.is_add(e) || bv.is_bv_add(e);
|
|
};
|
|
|
|
auto sub = [&](expr* t, expr* s) {
|
|
if (a.is_int_real(t))
|
|
return expr_ref(a.mk_sub(t, s), m);
|
|
else
|
|
return expr_ref(bv.mk_bv_sub(t, s), m);
|
|
};
|
|
|
|
auto subtract = [&](expr* t, app* s, unsigned i) {
|
|
expr_ref result(t, m);
|
|
unsigned j = 0;
|
|
for (expr* arg : *s) {
|
|
++j;
|
|
if (i != j)
|
|
result = sub(result, arg);
|
|
}
|
|
return result;
|
|
};
|
|
|
|
auto uminus = [&](expr* t) {
|
|
if (a.is_int_real(t))
|
|
return expr_ref(a.mk_uminus(t), m);
|
|
else
|
|
return expr_ref(bv.mk_bv_neg(t), m);
|
|
};
|
|
|
|
auto is_inverse = [&](expr*& t) {
|
|
expr* x, * y;
|
|
rational n;
|
|
if (a.is_mul(t, x, y) && a.is_numeral(x, n) && n == -1) {
|
|
t = y;
|
|
return true;
|
|
}
|
|
if (bv.is_bv_mul(t, x, y) && bv.is_numeral(x, n) && n + 1 == rational::power_of_two(bv.get_bv_size(t))) {
|
|
t = y;
|
|
return true;
|
|
}
|
|
return false;
|
|
};
|
|
|
|
auto find_arith_macro = [&](expr* x, expr* y) {
|
|
if (!is_add(x))
|
|
return false;
|
|
|
|
if (!is_macro_safe(y))
|
|
return false;
|
|
|
|
unsigned i = 0;
|
|
for (expr* arg : *to_app(x)) {
|
|
++i;
|
|
bool inv = is_inverse(arg);
|
|
if (!can_be_macro_head(arg, cl.m_bound.size()))
|
|
continue;
|
|
app* head = to_app(arg);
|
|
func_decl* f = head->get_decl();
|
|
if (head->get_num_args() != cl.m_bound.size())
|
|
continue;
|
|
if (occurs(f, y))
|
|
continue;
|
|
unsigned j = 0;
|
|
for (expr* arg2 : *head) {
|
|
++j;
|
|
if (i == j)
|
|
continue;
|
|
if (occurs(f, arg2))
|
|
goto next;
|
|
if (!is_macro_safe(arg2))
|
|
goto next;
|
|
}
|
|
{
|
|
// arg = y - x - arg;
|
|
expr_ref y1 = subtract(y, to_app(x), i);
|
|
if (inv)
|
|
y1 = uminus(y1);
|
|
insert_macro(to_app(arg), y1, cl);
|
|
return true;
|
|
}
|
|
next:
|
|
;
|
|
}
|
|
return false;
|
|
};
|
|
|
|
if (cl.is_unit() && !cl.sign(0) && m.is_eq(cl.atom(0), x, y)) {
|
|
if (find_arith_macro(x, y))
|
|
return;
|
|
if (find_arith_macro(y, x))
|
|
return;
|
|
}
|
|
|
|
|
|
//
|
|
// macro_finder also has:
|
|
// (>= (+ (f x) s) t)
|
|
// becomes (= (f x) (- t s (k x))
|
|
// add (>= (k x) 0)
|
|
// why is this a real improvement?
|
|
//
|
|
|
|
//
|
|
// quasi-macros
|
|
// (= (f x y (+ x y)) s), where x y are all bound variables.
|
|
// then replace (f x y z) by (if (= z (+ x y)) s (f' x y z))
|
|
//
|
|
auto can_be_qdef = [&](expr* _x, expr* y) {
|
|
if (!is_app(_x))
|
|
return false;
|
|
app* x = to_app(_x);
|
|
return
|
|
can_be_quasi_macro_head(x, cl.m_bound.size()) &&
|
|
is_macro_safe(y) &&
|
|
!occurs(x->get_decl(), y);
|
|
};
|
|
|
|
if (cl.is_unit() && m.is_eq(cl.atom(0), x, y) && !cl.m_bound.empty()) {
|
|
if (!cl.sign(0) && can_be_qdef(x, y)) {
|
|
insert_quasi_macro(to_app(x), y, cl);
|
|
return;
|
|
}
|
|
else if (!cl.sign(0) && can_be_qdef(y, x)) {
|
|
insert_quasi_macro(to_app(y), x, cl);
|
|
return;
|
|
}
|
|
else if (cl.sign(0) && m.is_bool(y) && can_be_qdef(x, y)) {
|
|
insert_quasi_macro(to_app(x), m.mk_not(y), cl);
|
|
return;
|
|
}
|
|
else if (cl.sign(0) && m.is_bool(y) && can_be_qdef(y, x)) {
|
|
insert_quasi_macro(to_app(y), m.mk_not(x), cl);
|
|
return;
|
|
}
|
|
}
|
|
if (cl.is_unit() && !cl.m_bound.empty()) {
|
|
expr* body = cl.sign(0) ? m.mk_false() : m.mk_true();
|
|
expr* x = cl.atom(0);
|
|
if (can_be_qdef(x, body)) {
|
|
insert_quasi_macro(to_app(x), body, cl);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void eliminate_predicates::find_definitions() {
|
|
for (auto* p : m_predicates)
|
|
try_resolve_definition(p);
|
|
for (auto* cl : m_clauses)
|
|
try_find_macro(*cl);
|
|
}
|
|
|
|
void eliminate_predicates::rewrite(expr_ref& t) {
|
|
proof_ref pr(m);
|
|
m_der(t, t, pr);
|
|
m_rewriter(t);
|
|
}
|
|
|
|
void eliminate_predicates::reduce_definitions() {
|
|
if (m_macros.empty())
|
|
return;
|
|
|
|
macro_replacer macro_expander(m);
|
|
for (auto const& [k, v] : m_macros)
|
|
macro_expander.insert(v->m_head, v->m_def, v->m_dep);
|
|
|
|
for (unsigned i : indices()) {
|
|
auto [f, p, d] = m_fmls[i]();
|
|
expr_ref fml(f, m), new_fml(m);
|
|
expr_dependency_ref dep(d, m);
|
|
while (true) {
|
|
macro_expander(fml, dep, new_fml, dep);
|
|
if (new_fml == fml)
|
|
break;
|
|
rewrite(new_fml);
|
|
fml = new_fml;
|
|
}
|
|
m_fmls.update(i, dependent_expr(m, fml, nullptr, dep));
|
|
}
|
|
reset();
|
|
init_clauses();
|
|
}
|
|
|
|
void eliminate_predicates::try_resolve(func_decl* p) {
|
|
if (m_disable_elimination.is_marked(p))
|
|
return;
|
|
if (m_fmls.frozen(p))
|
|
return;
|
|
|
|
unsigned num_pos = 0, num_neg = 0;
|
|
for (auto* cl : m_use_list.get(p, false))
|
|
if (cl->m_alive)
|
|
++num_pos;
|
|
for (auto* cl : m_use_list.get(p, true))
|
|
if (cl->m_alive)
|
|
++num_neg;
|
|
|
|
TRACE("elim_predicates", tout << "try resolve " << p->get_name() << " " << num_pos << " " << num_neg << "\n");
|
|
|
|
if (num_pos >= 4 && num_neg >= 2)
|
|
return;
|
|
if (num_neg >= 4 && num_pos >= 2)
|
|
return;
|
|
if (num_neg >= 3 && num_pos >= 3)
|
|
return;
|
|
|
|
for (auto* pos : m_use_list.get(p, false)) {
|
|
for (auto* neg : m_use_list.get(p, true)) {
|
|
clause* cl = resolve(p, *pos, *neg);
|
|
if (!cl)
|
|
continue;
|
|
m_clauses.push_back(cl);
|
|
add_use_list(*cl);
|
|
process_to_exclude(m_disable_elimination);
|
|
IF_VERBOSE(11, verbose_stream() << "resolve " << p->get_name() << "\n" << *pos << "\n" << *neg << "\n------\n" << *cl << "\n\n");
|
|
}
|
|
}
|
|
|
|
update_model(p);
|
|
|
|
for (auto* pos : m_use_list.get(p, false))
|
|
pos->m_alive = false;
|
|
for (auto* neg : m_use_list.get(p, true))
|
|
neg->m_alive = false;
|
|
|
|
++m_stats.m_num_eliminated;
|
|
}
|
|
|
|
//
|
|
// update model for p
|
|
//
|
|
// Example, ground case:
|
|
// {p, a} {p, b} {-p, c}, {-p, d}
|
|
// p <=> !(a & b)
|
|
// p <=> c & d
|
|
//
|
|
// Example non-ground cases
|
|
// {p(t)} {p(s)} {~p(u)}
|
|
// p(x) <=> (x = t or x = s)
|
|
// p(x) <=> x != u
|
|
//
|
|
// {p(t), a, b}
|
|
// p(x) <=> (x = t & !(a or b))
|
|
//
|
|
// {~p(t), a, b}
|
|
// ~p(x) <=> (x = t & !(a or b))
|
|
// p(x) <=> x = t => a or b
|
|
//
|
|
|
|
void eliminate_predicates::update_model(func_decl* p) {
|
|
expr_ref_vector fmls(m);
|
|
expr_ref def(m);
|
|
expr_dependency_ref dep(m);
|
|
unsigned numpos = 0, numneg = 0;
|
|
vector<dependent_expr> deleted;
|
|
for (auto* pos : m_use_list.get(p, false))
|
|
if (pos->m_alive)
|
|
++numpos;
|
|
for (auto* neg : m_use_list.get(p, true))
|
|
if (neg->m_alive)
|
|
++numneg;
|
|
|
|
if (numpos < numneg) {
|
|
for (auto* pos : m_use_list.get(p, false))
|
|
if (pos->m_alive) {
|
|
fmls.push_back(create_residue_formula(p, *pos));
|
|
dep = m.mk_join(dep, pos->m_dep);
|
|
}
|
|
def = mk_or(fmls);
|
|
}
|
|
else {
|
|
for (auto* neg : m_use_list.get(p, true))
|
|
if (neg->m_alive) {
|
|
fmls.push_back(mk_not(m, create_residue_formula(p, *neg)));
|
|
dep = m.mk_join(dep, neg->m_dep);
|
|
}
|
|
def = mk_and(fmls);
|
|
}
|
|
|
|
rewrite(def);
|
|
TRACE("elim_predicates", tout << "insert " << p->get_name() << " " << def << "\n");
|
|
m_fmls.model_trail().push(p, def, dep, deleted);
|
|
}
|
|
|
|
/**
|
|
* Convert a clause that contains p(t) into a definition for p
|
|
* forall y . (or p(t) C)
|
|
* Into
|
|
* exists y . x = t[y] & !(or C)
|
|
*/
|
|
|
|
expr_ref eliminate_predicates::create_residue_formula(func_decl* p, clause& cl) {
|
|
unsigned num_args = p->get_arity();
|
|
unsigned num_bound = cl.m_bound.size();
|
|
expr_ref_vector ors(m), ands(m);
|
|
expr_ref fml(m);
|
|
app_ref patom(m);
|
|
for (auto const& [atom, sign] : cl.m_literals) {
|
|
if (is_app(atom) && to_app(atom)->get_decl() == p) {
|
|
SASSERT(!patom);
|
|
patom = to_app(atom);
|
|
continue;
|
|
}
|
|
fml = sign ? m.mk_not(atom) : atom.get();
|
|
ors.push_back(fml);
|
|
}
|
|
if (!ors.empty()) {
|
|
fml = mk_not(m, mk_or(ors));
|
|
ands.push_back(fml);
|
|
}
|
|
for (unsigned i = 0; i < num_args; ++i) {
|
|
SASSERT(patom->get_arg(i)->get_sort() == p->get_domain(i));
|
|
ands.push_back(m.mk_eq(patom->get_arg(i), m.mk_var(num_bound + i, p->get_domain(i))));
|
|
}
|
|
fml = m.mk_and(ands);
|
|
if (num_bound > 0) {
|
|
svector<symbol> names;
|
|
for (unsigned i = 0; i < num_bound; ++i)
|
|
names.push_back(symbol(i));
|
|
fml = m.mk_exists(num_bound, cl.m_bound.data(), names.data(), fml, 1);
|
|
}
|
|
return fml;
|
|
}
|
|
|
|
/**
|
|
* Resolve p in clauses pos, neg where p occurs only once.
|
|
*/
|
|
eliminate_predicates::clause* eliminate_predicates::resolve(func_decl* p, clause& pos, clause& neg) {
|
|
var_shifter sh(m);
|
|
expr_dependency_ref dep(m);
|
|
dep = m.mk_join(pos.m_dep, neg.m_dep);
|
|
expr_ref new_lit(m);
|
|
expr_ref_vector lits(m);
|
|
expr* plit = nullptr, * nlit = nullptr;
|
|
|
|
for (auto const& [lit, sign] : pos.m_literals)
|
|
if (is_app(lit) && to_app(lit)->get_decl() == p)
|
|
plit = lit;
|
|
else
|
|
lits.push_back(sign ? m.mk_not(lit) : lit.get());
|
|
for (auto const & [lit, sign] : neg.m_literals) {
|
|
if (is_app(lit) && to_app(lit)->get_decl() == p)
|
|
nlit = lit;
|
|
else {
|
|
sh(lit, pos.m_bound.size(), new_lit);
|
|
lits.push_back(sign ? m.mk_not(new_lit) : new_lit.get());
|
|
}
|
|
}
|
|
sh(nlit, pos.m_bound.size(), new_lit);
|
|
for (unsigned i = 0; i < p->get_arity(); ++i) {
|
|
expr* a = to_app(plit)->get_arg(i);
|
|
expr* b = to_app(new_lit)->get_arg(i);
|
|
if (a != b)
|
|
lits.push_back(m.mk_not(m.mk_eq(a, b)));
|
|
}
|
|
|
|
expr_ref cl = mk_or(lits);
|
|
ptr_vector<sort> bound;
|
|
bound.append(neg.m_bound);
|
|
bound.append(pos.m_bound);
|
|
if (!bound.empty()) {
|
|
svector<symbol> names;
|
|
for (unsigned i = 0; i < bound.size(); ++i)
|
|
names.push_back(symbol(i));
|
|
cl = m.mk_forall(bound.size(), bound.data(), names.data(), cl, 1);
|
|
}
|
|
rewrite(cl);
|
|
if (m.is_true(cl))
|
|
return nullptr;
|
|
return init_clause(cl, dep, UINT_MAX);
|
|
}
|
|
|
|
void eliminate_predicates::try_resolve() {
|
|
for (auto* f : m_predicates)
|
|
try_resolve(f);
|
|
}
|
|
|
|
/**
|
|
* Process the terms m_to_exclude, walk all subterms.
|
|
* Uninterpreted function declarations in these terms are added to 'exclude_set'
|
|
*/
|
|
void eliminate_predicates::process_to_exclude(ast_mark& exclude_set) {
|
|
ast_mark visited;
|
|
struct proc {
|
|
ast_mark& to_exclude;
|
|
proc(ast_mark& f) :
|
|
to_exclude(f) {}
|
|
void operator()(func_decl* f) {
|
|
if (is_uninterp(f))
|
|
to_exclude.mark(f, true);
|
|
}
|
|
void operator()(ast* s) {}
|
|
};
|
|
proc proc(exclude_set);
|
|
|
|
for (expr* e : m_to_exclude)
|
|
for_each_ast(proc, visited, e);
|
|
m_to_exclude.reset();
|
|
}
|
|
|
|
|
|
eliminate_predicates::clause* eliminate_predicates::init_clause(unsigned i) {
|
|
auto [f, p, d] = m_fmls[i]();
|
|
return init_clause(f, d, i);
|
|
}
|
|
|
|
/**
|
|
* Create a clause from a formula.
|
|
*/
|
|
eliminate_predicates::clause* eliminate_predicates::init_clause(expr* f, expr_dependency* d, unsigned i) {
|
|
clause* cl = alloc(clause, m, d);
|
|
cl->m_fml = f;
|
|
cl->m_fml_index = i;
|
|
while (is_forall(f)) {
|
|
cl->m_bound.append(to_quantifier(f)->get_num_decls(), to_quantifier(f)->get_decl_sorts());
|
|
f = to_quantifier(f)->get_expr();
|
|
}
|
|
expr_ref_vector ors(m);
|
|
flatten_or(f, ors);
|
|
for (expr* lit : ors) {
|
|
bool sign = m.is_not(lit, lit);
|
|
cl->m_literals.push_back({ expr_ref(lit, m), sign });
|
|
}
|
|
|
|
// extend macro detection to exploit bijective functions?
|
|
// f(+ x 1) = g(x) -> f(x) = g(- x 1)
|
|
// init_injective(*cl);
|
|
// init_surjective(*cl);
|
|
return cl;
|
|
}
|
|
|
|
/**
|
|
* functions in the prefix of qhead are fully disabled
|
|
* Create clauses from the suffix, and process subeterms of clauses to be disabled from
|
|
* eliminations.
|
|
*/
|
|
void eliminate_predicates::init_clauses() {
|
|
|
|
m_fmls.freeze_suffix();
|
|
|
|
for (unsigned i : indices()) {
|
|
clause* cl = init_clause(i);
|
|
add_use_list(*cl);
|
|
m_clauses.push_back(cl);
|
|
}
|
|
process_to_exclude(m_disable_elimination);
|
|
}
|
|
|
|
/**
|
|
* Ad hoc recognize surjectivity axioms
|
|
* - exists y . f(y) = x
|
|
*/
|
|
void eliminate_predicates::init_surjective(clause const& cl) {
|
|
if (!cl.is_unit())
|
|
return;
|
|
if (cl.sign(0))
|
|
return;
|
|
if (!is_exists(cl.atom(0)))
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* Ad hoc recognize injectivity axioms
|
|
* - f(x) = f(y) => x = y
|
|
*/
|
|
void eliminate_predicates::init_injective(clause const& cl) {
|
|
if (cl.size() != 2)
|
|
return;
|
|
if (cl.m_bound.size() != 2)
|
|
return;
|
|
if (cl.sign(0) == cl.sign(1))
|
|
return;
|
|
expr* a = cl.atom(0), *b = cl.atom(1);
|
|
if (!cl.sign(0) && cl.sign(1))
|
|
std::swap(a, b);
|
|
expr* x, *y, *fx, *fy;
|
|
if (!m.is_eq(a, fx, fy))
|
|
return;
|
|
if (!m.is_eq(b, x, y))
|
|
return;
|
|
|
|
auto is_fx = [&](expr* _fx, func_decl*& f, unsigned& idx) {
|
|
if (!is_app(_fx))
|
|
return false;
|
|
app* fx = to_app(_fx);
|
|
if (fx->get_num_args() != 1)
|
|
return false;
|
|
if (!is_var(fx->get_arg(0)))
|
|
return false;
|
|
f = fx->get_decl();
|
|
idx = to_var(fx->get_arg(0))->get_idx();
|
|
return true;
|
|
};
|
|
func_decl* f1, *f2;
|
|
unsigned idx1, idx2;
|
|
if (!is_fx(fx, f1, idx1))
|
|
return;
|
|
if (!is_fx(fy, f2, idx2))
|
|
return;
|
|
if (idx1 == idx2 || f1 != f2)
|
|
return;
|
|
|
|
auto check_var = [&](expr* x, unsigned& idx) {
|
|
if (!is_var(x))
|
|
return false;
|
|
idx = to_var(x)->get_idx();
|
|
return true;
|
|
};
|
|
if (!check_var(x, idx1) || !check_var(y, idx2))
|
|
return;
|
|
if (idx1 == idx2)
|
|
return;
|
|
m_is_injective.mark(f1, true);
|
|
}
|
|
|
|
/**
|
|
* Convert clauses to m_fmls
|
|
*/
|
|
void eliminate_predicates::decompile() {
|
|
for (clause* cl : m_clauses) {
|
|
if (m_fmls.inconsistent())
|
|
break;
|
|
if (cl->m_fml_index != UINT_MAX) {
|
|
if (cl->m_alive)
|
|
continue;
|
|
dependent_expr de(m, m.mk_true(), nullptr, nullptr);
|
|
m_fmls.update(cl->m_fml_index, de);
|
|
}
|
|
else if (cl->m_alive) {
|
|
expr_ref new_cl = cl->m_fml;
|
|
dependent_expr de(m, new_cl, nullptr, cl->m_dep);
|
|
m_fmls.add(de);
|
|
}
|
|
}
|
|
}
|
|
|
|
void eliminate_predicates::reset() {
|
|
m_predicates.reset();
|
|
m_predicate_decls.reset();
|
|
m_to_exclude.reset();
|
|
m_disable_elimination.reset();
|
|
m_is_macro.reset();
|
|
m_is_injective.reset();
|
|
m_is_surjective.reset();
|
|
for (auto const& [k, v] : m_macros)
|
|
dealloc(v);
|
|
m_macros.reset();
|
|
m_clauses.reset();
|
|
m_use_list.reset();
|
|
}
|
|
|
|
|
|
void eliminate_predicates::reduce() {
|
|
reset();
|
|
init_clauses();
|
|
find_definitions();
|
|
reduce_definitions();
|
|
try_resolve();
|
|
decompile();
|
|
reset();
|
|
}
|