mirror of
https://github.com/Z3Prover/z3
synced 2025-04-12 20:18:18 +00:00
new simplifier/tactic
eliminate_predicates finds macros and eliminates predicates from formulas as pre-processing.
This commit is contained in:
parent
d735faae4e
commit
771157696b
|
@ -2,6 +2,7 @@ z3_add_component(simplifiers
|
||||||
SOURCES
|
SOURCES
|
||||||
bv_slice.cpp
|
bv_slice.cpp
|
||||||
elim_unconstrained.cpp
|
elim_unconstrained.cpp
|
||||||
|
eliminate_predicates.cpp
|
||||||
euf_completion.cpp
|
euf_completion.cpp
|
||||||
extract_eqs.cpp
|
extract_eqs.cpp
|
||||||
model_reconstruction_trail.cpp
|
model_reconstruction_trail.cpp
|
||||||
|
|
664
src/ast/simplifiers/eliminate_predicates.cpp
Normal file
664
src/ast/simplifiers/eliminate_predicates.cpp
Normal file
|
@ -0,0 +1,664 @@
|
||||||
|
/*++
|
||||||
|
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}
|
||||||
|
- 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 "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/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/macros/macro_util.h"
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rewriting formulas using macro definitions.
|
||||||
|
*/
|
||||||
|
struct eliminate_predicates::macro_expander_cfg : public default_rewriter_cfg {
|
||||||
|
ast_manager& m;
|
||||||
|
eliminate_predicates& ep;
|
||||||
|
expr_dependency_ref& m_used_macro_dependencies;
|
||||||
|
expr_ref_vector m_trail;
|
||||||
|
|
||||||
|
macro_expander_cfg(ast_manager& m, eliminate_predicates& ep, expr_dependency_ref& deps) :
|
||||||
|
m(m),
|
||||||
|
ep(ep),
|
||||||
|
m_used_macro_dependencies(deps),
|
||||||
|
m_trail(m)
|
||||||
|
{}
|
||||||
|
|
||||||
|
bool rewrite_patterns() const { return false; }
|
||||||
|
bool flat_assoc(func_decl* f) const { return false; }
|
||||||
|
br_status reduce_app(func_decl* f, unsigned num, expr* const* args, expr_ref& result, proof_ref& result_pr) {
|
||||||
|
result_pr = nullptr;
|
||||||
|
return BR_FAILED;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* adapted from macro_manager.cpp
|
||||||
|
*/
|
||||||
|
bool reduce_quantifier(quantifier* old_q,
|
||||||
|
expr* new_body,
|
||||||
|
expr* const* new_patterns,
|
||||||
|
expr* const* new_no_patterns,
|
||||||
|
expr_ref& result,
|
||||||
|
proof_ref& result_pr) {
|
||||||
|
|
||||||
|
bool erase_patterns = false;
|
||||||
|
for (unsigned i = 0; !erase_patterns && i < old_q->get_num_patterns(); i++)
|
||||||
|
if (old_q->get_pattern(i) != new_patterns[i])
|
||||||
|
erase_patterns = true;
|
||||||
|
|
||||||
|
for (unsigned i = 0; !erase_patterns && i < old_q->get_num_no_patterns(); i++)
|
||||||
|
if (old_q->get_no_pattern(i) != new_no_patterns[i])
|
||||||
|
erase_patterns = true;
|
||||||
|
|
||||||
|
if (erase_patterns)
|
||||||
|
result = m.update_quantifier(old_q, 0, nullptr, 0, nullptr, new_body);
|
||||||
|
|
||||||
|
if (erase_patterns && m.proofs_enabled())
|
||||||
|
result_pr = m.mk_rewrite(old_q, result);
|
||||||
|
|
||||||
|
return erase_patterns;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool get_subst(expr* _n, expr*& r, proof*& p) {
|
||||||
|
if (!is_app(_n))
|
||||||
|
return false;
|
||||||
|
p = nullptr;
|
||||||
|
app* n = to_app(_n);
|
||||||
|
quantifier* q = nullptr;
|
||||||
|
func_decl* d = n->get_decl(), * d2 = nullptr;
|
||||||
|
app_ref head(m);
|
||||||
|
expr_ref def(m);
|
||||||
|
expr_dependency_ref dep(m);
|
||||||
|
if (ep.has_macro(d, head, def, dep)) {
|
||||||
|
unsigned num = head->get_num_args();
|
||||||
|
ptr_buffer<expr> subst_args;
|
||||||
|
subst_args.resize(num, 0);
|
||||||
|
// TODO: we can exploit that variables occur in "non-standard" order
|
||||||
|
// that is in order (:var 0) (:var 1) (:var 2)
|
||||||
|
// then substitution just takes n->get_args() instead of this renaming.
|
||||||
|
for (unsigned i = 0; i < num; i++) {
|
||||||
|
var* v = to_var(head->get_arg(i));
|
||||||
|
VERIFY(v->get_idx() < num);
|
||||||
|
unsigned nidx = num - v->get_idx() - 1;
|
||||||
|
SASSERT(subst_args[nidx] == 0);
|
||||||
|
subst_args[nidx] = n->get_arg(i);
|
||||||
|
}
|
||||||
|
var_subst s(m);
|
||||||
|
expr_ref rr = s(def, num, subst_args.data());
|
||||||
|
r = rr;
|
||||||
|
m_trail.push_back(rr);
|
||||||
|
m_used_macro_dependencies = m.mk_join(m_used_macro_dependencies, dep);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct eliminate_predicates::macro_expander_rw : public rewriter_tpl<eliminate_predicates::macro_expander_cfg> {
|
||||||
|
eliminate_predicates::macro_expander_cfg m_cfg;
|
||||||
|
|
||||||
|
macro_expander_rw(ast_manager& m, eliminate_predicates& ep, expr_dependency_ref& deps) :
|
||||||
|
rewriter_tpl<eliminate_predicates::macro_expander_cfg>(m, false, m_cfg),
|
||||||
|
m_cfg(m, ep, deps)
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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_disable_macro.is_marked(p))
|
||||||
|
return false;
|
||||||
|
expr_mark binary_pos, binary_neg;
|
||||||
|
macro_util mutil(m);
|
||||||
|
obj_map<expr, expr_dependency*> deps;
|
||||||
|
auto is_def_predicate = [&](expr* atom) {
|
||||||
|
return is_app(atom) && to_app(atom)->get_decl() == p && mutil.is_macro_head(atom, p->get_arity());
|
||||||
|
};
|
||||||
|
auto add_def = [&](clause& cl, expr* atom1, bool sign1, expr* atom2, bool sign2) {
|
||||||
|
if (is_def_predicate(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->m_literals.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(atom1) && sign1) {
|
||||||
|
if (sign2 && binary_pos.is_marked(atom2) && is_macro_safe(atom2) && !occurs(p, atom2)) {
|
||||||
|
head = to_app(atom1);
|
||||||
|
def = 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 = 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->m_literals.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_ref& head, expr_ref& def, expr_dependency_ref& 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);
|
||||||
|
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, {});
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool eliminate_predicates::has_macro(func_decl* p, app_ref& head, expr_ref& def, expr_dependency_ref& dep) {
|
||||||
|
macro_def* md = nullptr;
|
||||||
|
if (m_macros.find(p, md)) {
|
||||||
|
head = md->m_head;
|
||||||
|
def = md->m_def;
|
||||||
|
dep = md->m_dep;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void eliminate_predicates::find_definitions() {
|
||||||
|
for (auto* p : m_predicates)
|
||||||
|
try_resolve_definition(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
for (unsigned i = m_qhead; i < m_fmls.size(); ++i) {
|
||||||
|
auto [f, d] = m_fmls[i]();
|
||||||
|
expr_ref fml(f, m), new_fml(m);
|
||||||
|
expr_dependency_ref dep(m);
|
||||||
|
while (true) {
|
||||||
|
macro_expander_rw macro_expander(m, *this, dep);
|
||||||
|
macro_expander(fml, new_fml);
|
||||||
|
if (new_fml == fml)
|
||||||
|
break;
|
||||||
|
rewrite(new_fml);
|
||||||
|
fml = new_fml;
|
||||||
|
}
|
||||||
|
if (fml != f) {
|
||||||
|
dep = m.mk_join(d, dep);
|
||||||
|
m_fmls.update(i, dependent_expr(m, fml, dep));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
reset();
|
||||||
|
init_clauses();
|
||||||
|
}
|
||||||
|
|
||||||
|
void eliminate_predicates::try_resolve(func_decl* p) {
|
||||||
|
if (m_disable_elimination.is_marked(p))
|
||||||
|
return;
|
||||||
|
if (m_disable_macro.is_marked(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_VERBOSE(0, verbose_stream() << "try resolve " << p->get_name() << " " << num_pos << " " << num_neg << "\n");
|
||||||
|
// TODO - probe for a definition
|
||||||
|
// generally, probe for binary clause equivalences in binary implication graph
|
||||||
|
|
||||||
|
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);
|
||||||
|
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));
|
||||||
|
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)));
|
||||||
|
def = mk_and(fmls);
|
||||||
|
}
|
||||||
|
|
||||||
|
IF_VERBOSE(0, verbose_stream() << p->get_name() << " " << def << "\n");
|
||||||
|
rewrite(def);
|
||||||
|
m_fmls.model_trail().push(p, def, 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);
|
||||||
|
}
|
||||||
|
IF_VERBOSE(0, verbose_stream() << fml << "\n");
|
||||||
|
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'
|
||||||
|
* Uninterpreted function declarations from as-array terms are added to 'm_disable_macro'
|
||||||
|
*/
|
||||||
|
void eliminate_predicates::process_to_exclude(ast_mark& exclude_set) {
|
||||||
|
ast_mark visited;
|
||||||
|
array_util a(m);
|
||||||
|
|
||||||
|
struct proc {
|
||||||
|
array_util& a;
|
||||||
|
ast_mark& to_exclude;
|
||||||
|
ast_mark& to_disable;
|
||||||
|
proc(array_util& a, ast_mark& f, ast_mark& d) :
|
||||||
|
a(a), to_exclude(f), to_disable(d) {}
|
||||||
|
void operator()(func_decl* f) {
|
||||||
|
if (is_uninterp(f))
|
||||||
|
to_exclude.mark(f, true);
|
||||||
|
}
|
||||||
|
void operator()(app* e) {
|
||||||
|
func_decl* f;
|
||||||
|
if (a.is_as_array(e, f) && is_uninterp(f))
|
||||||
|
to_disable.mark(f, true);
|
||||||
|
}
|
||||||
|
void operator()(ast* s) {}
|
||||||
|
};
|
||||||
|
proc proc(a, exclude_set, m_disable_macro);
|
||||||
|
|
||||||
|
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, 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 });
|
||||||
|
}
|
||||||
|
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() {
|
||||||
|
for (unsigned i = 0; i < m_qhead; ++i)
|
||||||
|
m_to_exclude.push_back(m_fmls[i].fml());
|
||||||
|
recfun::util rec(m);
|
||||||
|
if (rec.has_rec_defs())
|
||||||
|
for (auto& d : rec.get_rec_funs())
|
||||||
|
m_to_exclude.push_back(rec.get_def(d).get_rhs());
|
||||||
|
|
||||||
|
process_to_exclude(m_disable_macro);
|
||||||
|
|
||||||
|
for (unsigned i = m_qhead; i < m_fmls.size(); ++i) {
|
||||||
|
clause* cl = init_clause(i);
|
||||||
|
add_use_list(*cl);
|
||||||
|
m_clauses.push_back(cl);
|
||||||
|
}
|
||||||
|
process_to_exclude(m_disable_elimination);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
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, cl->m_dep);
|
||||||
|
m_fmls.add(de);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void eliminate_predicates::reset() {
|
||||||
|
m_predicates.reset();
|
||||||
|
m_predicate_decls.reset();
|
||||||
|
m_to_exclude.reset();
|
||||||
|
m_disable_macro.reset();
|
||||||
|
m_disable_elimination.reset();
|
||||||
|
m_is_macro.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();
|
||||||
|
}
|
143
src/ast/simplifiers/eliminate_predicates.h
Normal file
143
src/ast/simplifiers/eliminate_predicates.h
Normal file
|
@ -0,0 +1,143 @@
|
||||||
|
/*++
|
||||||
|
Copyright (c) 2022 Microsoft Corporation
|
||||||
|
|
||||||
|
Module Name:
|
||||||
|
|
||||||
|
eliminate_predicates.h
|
||||||
|
|
||||||
|
Author:
|
||||||
|
|
||||||
|
Nikolaj Bjorner (nbjorner) 2022-11-2.
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
|
||||||
|
Eliminate predicates through Davis-Putnam rules
|
||||||
|
|
||||||
|
(forall (x y) (or (P x) Q)) (forall (x y) (or (not (P x)) R))
|
||||||
|
is converted to
|
||||||
|
(forall (x y) (or Q R))
|
||||||
|
when P occurs only in positive or only in negative polarities and the
|
||||||
|
expansion does not increase the formula size.
|
||||||
|
|
||||||
|
Macros are also eliminated
|
||||||
|
|
||||||
|
|
||||||
|
create clause abstractions, index into fmls, indicator if it was removed
|
||||||
|
map from predicates to clauses where they occur in unitary role.
|
||||||
|
process predicates to check if they can be eliminated, creating new clauses and updated use-list.
|
||||||
|
|
||||||
|
|
||||||
|
--*/
|
||||||
|
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "util/scoped_ptr_vector.h"
|
||||||
|
#include "ast/rewriter/der.h"
|
||||||
|
#include "ast/rewriter/th_rewriter.h"
|
||||||
|
#include "ast/simplifiers/dependent_expr_state.h"
|
||||||
|
|
||||||
|
|
||||||
|
class eliminate_predicates : public dependent_expr_simplifier {
|
||||||
|
|
||||||
|
public:
|
||||||
|
struct clause {
|
||||||
|
ptr_vector<sort> m_bound; // bound variables
|
||||||
|
vector<std::pair<expr_ref, bool>> m_literals; // clause literals
|
||||||
|
expr_dependency_ref m_dep; // dependencies
|
||||||
|
expr_ref m_fml; // formula corresponding to clause
|
||||||
|
unsigned m_fml_index = UINT_MAX; // index of formula where clause came from
|
||||||
|
bool m_alive = true;
|
||||||
|
|
||||||
|
clause(ast_manager& m, expr_dependency* d) :
|
||||||
|
m_dep(d, m), m_fml(m)
|
||||||
|
{}
|
||||||
|
|
||||||
|
std::ostream& display(std::ostream& out) const;
|
||||||
|
};
|
||||||
|
private:
|
||||||
|
struct stats {
|
||||||
|
unsigned m_num_eliminated = 0;
|
||||||
|
unsigned m_num_macros = 0;
|
||||||
|
void reset() { m_num_eliminated = 0; m_num_macros = 0; }
|
||||||
|
};
|
||||||
|
|
||||||
|
struct macro_def {
|
||||||
|
app_ref m_head;
|
||||||
|
expr_ref m_def;
|
||||||
|
expr_dependency_ref m_dep;
|
||||||
|
macro_def(app_ref& head, expr_ref& def, expr_dependency_ref& dep) :
|
||||||
|
m_head(head), m_def(def), m_dep(dep) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef ptr_vector<clause> clause_use_list;
|
||||||
|
|
||||||
|
class use_list {
|
||||||
|
vector<clause_use_list> m_use_list;
|
||||||
|
unsigned index(func_decl* f, bool sign) const { return 2*f->get_small_id() + sign; }
|
||||||
|
void reserve(func_decl* f, bool sign) {
|
||||||
|
m_use_list.reserve(index(f, sign) + 3);
|
||||||
|
}
|
||||||
|
public:
|
||||||
|
clause_use_list& get(func_decl* f, bool sign) { reserve(f, sign); return m_use_list[index(f, sign)]; }
|
||||||
|
void reset() { m_use_list.reset(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
scoped_ptr_vector<clause> m_clauses;
|
||||||
|
ast_mark m_disable_elimination, m_disable_macro, m_predicate_decls, m_is_macro;
|
||||||
|
ptr_vector<func_decl> m_predicates;
|
||||||
|
ptr_vector<expr> m_to_exclude;
|
||||||
|
stats m_stats;
|
||||||
|
use_list m_use_list;
|
||||||
|
der_rewriter m_der;
|
||||||
|
th_rewriter m_rewriter;
|
||||||
|
obj_map<func_decl, macro_def*> m_macros;
|
||||||
|
|
||||||
|
struct macro_expander_cfg;
|
||||||
|
struct macro_expander_rw;
|
||||||
|
|
||||||
|
void rewrite(expr_ref& t);
|
||||||
|
|
||||||
|
clause* init_clause(unsigned i);
|
||||||
|
clause* init_clause(expr* f, expr_dependency* d, unsigned i);
|
||||||
|
clause* resolve(func_decl* p, clause& pos, clause& neg);
|
||||||
|
void add_use_list(clause& cl);
|
||||||
|
|
||||||
|
bool try_find_binary_definition(func_decl* p, app_ref& head, expr_ref& def, expr_dependency_ref& dep);
|
||||||
|
void try_resolve_definition(func_decl* p);
|
||||||
|
void insert_macro(app_ref& head, expr_ref& def, expr_dependency_ref& dep);
|
||||||
|
bool has_macro(func_decl* p, app_ref& head, expr_ref& def, expr_dependency_ref& dep);
|
||||||
|
bool is_macro_safe(expr* e);
|
||||||
|
|
||||||
|
void try_resolve(func_decl* p);
|
||||||
|
void update_model(func_decl* p);
|
||||||
|
expr_ref create_residue_formula(func_decl* p, clause& cl);
|
||||||
|
void process_to_exclude(ast_mark&);
|
||||||
|
|
||||||
|
void init_clauses();
|
||||||
|
void find_definitions();
|
||||||
|
void reduce_definitions();
|
||||||
|
void try_resolve();
|
||||||
|
void decompile();
|
||||||
|
void reset();
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
eliminate_predicates(ast_manager& m, dependent_expr_state& fmls);
|
||||||
|
|
||||||
|
~eliminate_predicates() override { reset(); }
|
||||||
|
|
||||||
|
void reduce() override;
|
||||||
|
|
||||||
|
void collect_statistics(statistics& st) const override {
|
||||||
|
st.update("elim-predicates", m_stats.m_num_eliminated);
|
||||||
|
st.update("elim-predicates-macros", m_stats.m_num_macros);
|
||||||
|
}
|
||||||
|
|
||||||
|
void reset_statistics() override { m_stats.reset(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
inline std::ostream& operator<<(std::ostream& out, eliminate_predicates::clause const& c) {
|
||||||
|
return c.display(out);
|
||||||
|
}
|
|
@ -38,6 +38,7 @@ z3_add_component(core_tactics
|
||||||
elim_term_ite_tactic.h
|
elim_term_ite_tactic.h
|
||||||
elim_uncnstr_tactic.h
|
elim_uncnstr_tactic.h
|
||||||
elim_uncnstr2_tactic.h
|
elim_uncnstr2_tactic.h
|
||||||
|
eliminate_predicates_tactic.h
|
||||||
euf_completion_tactic.h
|
euf_completion_tactic.h
|
||||||
injectivity_tactic.h
|
injectivity_tactic.h
|
||||||
nnf_tactic.h
|
nnf_tactic.h
|
||||||
|
|
40
src/tactic/core/eliminate_predicates_tactic.h
Normal file
40
src/tactic/core/eliminate_predicates_tactic.h
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
/*++
|
||||||
|
Copyright (c) 2022 Microsoft Corporation
|
||||||
|
|
||||||
|
Module Name:
|
||||||
|
|
||||||
|
eliminate_predicates_tactic.h
|
||||||
|
|
||||||
|
Abstract:
|
||||||
|
|
||||||
|
Tactic for eliminating macros and predicates
|
||||||
|
|
||||||
|
Author:
|
||||||
|
|
||||||
|
Nikolaj Bjorner (nbjorner) 2022-10-30
|
||||||
|
|
||||||
|
--*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "util/params.h"
|
||||||
|
#include "tactic/tactic.h"
|
||||||
|
#include "tactic/dependent_expr_state_tactic.h"
|
||||||
|
#include "ast/simplifiers/eliminate_predicates.h"
|
||||||
|
|
||||||
|
|
||||||
|
class eliminate_predicates_tactic_factory : public dependent_expr_simplifier_factory {
|
||||||
|
public:
|
||||||
|
dependent_expr_simplifier* mk(ast_manager& m, params_ref const& p, dependent_expr_state& s) override {
|
||||||
|
return alloc(eliminate_predicates, m, s);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
inline tactic * mk_eliminate_predicates_tactic(ast_manager& m, params_ref const& p = params_ref()) {
|
||||||
|
return alloc(dependent_expr_state_tactic, m, p, alloc(eliminate_predicates_tactic_factory), "elim-predicates");
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
ADD_TACTIC("elim-predicates", "eliminate predicates.", "mk_eliminate_predicates_tactic(m, p)")
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue