3
0
Fork 0
mirror of https://github.com/Z3Prover/z3 synced 2025-08-02 01:13:18 +00:00
z3/src/ast/simplifiers/eliminate_predicates.cpp

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();
}