mirror of
https://github.com/Z3Prover/z3
synced 2025-08-24 03:57:51 +00:00
wip - updated version of elim_uncstr_tactic
- remove reduce_invertible. It is subsumed by reduce_uncstr(2) - introduce a simplifier for reduce_unconstrained. It uses reference counting to deal with inefficiency bug of legacy reduce_uncstr. It decomposes theory plugins into expr_inverter. reduce_invertible is a tactic used in most built-in scenarios. It is useful for removing subterms that can be eliminated using "cheap" quantifier elimination. Specifically variables that occur only once can be removed in many cases by computing an expression that represents the effect computing a value for the eliminated occurrence. The theory plugins for variable elimination are very partial and should be augmented by extensions, esp. for the case of bit-vectors where the invertibility conditions are thoroughly documented by Niemetz and Preiner.
This commit is contained in:
parent
689af3b4df
commit
efbe0a6554
20 changed files with 1334 additions and 621 deletions
|
@ -447,6 +447,8 @@ public:
|
|||
app * mk_add(expr * arg1, expr * arg2, expr* arg3) const { return m_manager.mk_app(arith_family_id, OP_ADD, arg1, arg2, arg3); }
|
||||
app * mk_add(expr_ref_vector const& args) const { return mk_add(args.size(), args.data()); }
|
||||
app * mk_add(expr_ref_buffer const& args) const { return mk_add(args.size(), args.data()); }
|
||||
app * mk_add(ptr_buffer<expr> const& args) const { return mk_add(args.size(), args.data()); }
|
||||
app * mk_add(ptr_vector<expr> const& args) const { return mk_add(args.size(), args.data()); }
|
||||
|
||||
app * mk_sub(expr * arg1, expr * arg2) const { return m_manager.mk_app(arith_family_id, OP_SUB, arg1, arg2); }
|
||||
app * mk_sub(unsigned num_args, expr * const * args) const { return m_manager.mk_app(arith_family_id, OP_SUB, num_args, args); }
|
||||
|
|
|
@ -207,6 +207,10 @@ public:
|
|||
return mk_store(args.size(), args.data());
|
||||
}
|
||||
|
||||
app* mk_store(ptr_buffer<expr> const& args) const {
|
||||
return mk_store(args.size(), args.data());
|
||||
}
|
||||
|
||||
app * mk_select(unsigned num_args, expr * const * args) const {
|
||||
return m_manager.mk_app(m_fid, OP_SELECT, 0, nullptr, num_args, args);
|
||||
}
|
||||
|
|
|
@ -448,9 +448,17 @@ public:
|
|||
app * mk_bv_srem(expr * arg1, expr * arg2) const { return m_manager.mk_app(get_fid(), OP_BSREM, arg1, arg2); }
|
||||
app * mk_bv_smod(expr * arg1, expr * arg2) const { return m_manager.mk_app(get_fid(), OP_BSMOD, arg1, arg2); }
|
||||
app * mk_bv_add(expr * arg1, expr * arg2) const { return m_manager.mk_app(get_fid(), OP_BADD, arg1, arg2); }
|
||||
app * mk_bv_add(ptr_buffer<expr> const & args) const { return m_manager.mk_app(get_fid(), OP_BADD, args.size(), args.data()); }
|
||||
app * mk_bv_add(ptr_vector<expr> const & args) const { return m_manager.mk_app(get_fid(), OP_BADD, args.size(), args.data()); }
|
||||
app * mk_bv_add(expr_ref_vector const & args) const { return m_manager.mk_app(get_fid(), OP_BADD, args.size(), args.data()); }
|
||||
app * mk_bv_add(expr_ref_buffer const & args) const { return m_manager.mk_app(get_fid(), OP_BADD, args.size(), args.data()); }
|
||||
app * mk_bv_sub(expr * arg1, expr * arg2) const { return m_manager.mk_app(get_fid(), OP_BSUB, arg1, arg2); }
|
||||
app * mk_bv_mul(expr * arg1, expr * arg2) const { return m_manager.mk_app(get_fid(), OP_BMUL, arg1, arg2); }
|
||||
app * mk_bv_mul(unsigned n, expr* const* args) const { return m_manager.mk_app(get_fid(), OP_BMUL, n, args); }
|
||||
app* mk_bv_mul(ptr_buffer<expr> const& args) const { return m_manager.mk_app(get_fid(), OP_BMUL, args.size(), args.data()); }
|
||||
app* mk_bv_mul(ptr_vector<expr> const& args) const { return m_manager.mk_app(get_fid(), OP_BMUL, args.size(), args.data()); }
|
||||
app* mk_bv_mul(expr_ref_vector const& args) const { return m_manager.mk_app(get_fid(), OP_BMUL, args.size(), args.data()); }
|
||||
app* mk_bv_mul(expr_ref_buffer const& args) const { return m_manager.mk_app(get_fid(), OP_BMUL, args.size(), args.data()); }
|
||||
app * mk_bv_udiv(expr * arg1, expr * arg2) const { return m_manager.mk_app(get_fid(), OP_BUDIV, arg1, arg2); }
|
||||
app * mk_bv_udiv_i(expr * arg1, expr * arg2) const { return m_manager.mk_app(get_fid(), OP_BUDIV_I, arg1, arg2); }
|
||||
app * mk_bv_udiv0(expr * arg) const { return m_manager.mk_app(get_fid(), OP_BUDIV0, arg); }
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
z3_add_component(converters
|
||||
SOURCES
|
||||
expr_inverter.cpp
|
||||
equiv_proof_converter.cpp
|
||||
generic_model_converter.cpp
|
||||
horn_subsume_model_converter.cpp
|
||||
|
|
791
src/ast/converters/expr_inverter.cpp
Normal file
791
src/ast/converters/expr_inverter.cpp
Normal file
|
@ -0,0 +1,791 @@
|
|||
/*++
|
||||
Copyright (c) 2022 Microsoft Corporation
|
||||
|
||||
Module Name:
|
||||
|
||||
expr_inverter.cpp
|
||||
|
||||
Abstract:
|
||||
|
||||
inverter interface and instance
|
||||
|
||||
Author:
|
||||
|
||||
Nikolaj Bjorner (nbjorner) 2022-10-11
|
||||
|
||||
--*/
|
||||
|
||||
#include "ast/ast_pp.h"
|
||||
#include "ast/ast_ll_pp.h"
|
||||
#include "ast/ast_util.h"
|
||||
#include "ast/arith_decl_plugin.h"
|
||||
#include "ast/converters/expr_inverter.h"
|
||||
|
||||
class basic_expr_inverter : public iexpr_inverter {
|
||||
public:
|
||||
|
||||
basic_expr_inverter(ast_manager& m) : iexpr_inverter(m) {}
|
||||
|
||||
/**
|
||||
* if (c, x, x') -> fresh
|
||||
* x := fresh
|
||||
* x' := fresh
|
||||
*
|
||||
* if (x, x', e) -> fresh
|
||||
* x := true
|
||||
* x' := fresh
|
||||
*
|
||||
* if (x, t, x') -> fresh
|
||||
* x := false
|
||||
* x' := fresh
|
||||
*
|
||||
* not x -> fresh
|
||||
* x := not fresh
|
||||
*
|
||||
* x & x' -> fresh
|
||||
* x := fresh
|
||||
* x' := true
|
||||
*
|
||||
* x or x' -> fresh
|
||||
* x := fresh
|
||||
* x' := false
|
||||
*
|
||||
* x = t -> fresh
|
||||
* x := if(fresh, t, diff(t))
|
||||
* where diff is a diagnonalization function available in domains of size > 1.
|
||||
*
|
||||
*/
|
||||
|
||||
bool operator()(func_decl* f, unsigned num, expr* const* args, expr_ref& r, expr_ref& side_cond) {
|
||||
SASSERT(f->get_family_id() == m.get_basic_family_id());
|
||||
switch (f->get_decl_kind()) {
|
||||
case OP_ITE:
|
||||
SASSERT(num == 3);
|
||||
if (uncnstr(args[1]) && uncnstr(args[2])) {
|
||||
mk_fresh_uncnstr_var_for(f, r);
|
||||
add_def(args[1], r);
|
||||
add_def(args[2], r);
|
||||
return true;
|
||||
}
|
||||
if (uncnstr(args[0]) && uncnstr(args[1])) {
|
||||
mk_fresh_uncnstr_var_for(f, r);
|
||||
add_def(args[0], m.mk_true());
|
||||
add_def(args[1], r);
|
||||
return true;
|
||||
}
|
||||
if (uncnstr(args[0]) && uncnstr(args[2])) {
|
||||
mk_fresh_uncnstr_var_for(f, r);
|
||||
add_def(args[0], m.mk_false());
|
||||
add_def(args[2], r);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
case OP_NOT:
|
||||
SASSERT(num == 1);
|
||||
if (uncnstr(args[0])) {
|
||||
mk_fresh_uncnstr_var_for(f, r);
|
||||
add_def(args[0], m.mk_not(r));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
case OP_AND:
|
||||
if (num > 0 && uncnstr(num, args)) {
|
||||
mk_fresh_uncnstr_var_for(f, r);
|
||||
add_defs(num, args, r, m.mk_true());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
case OP_OR:
|
||||
if (num > 0 && uncnstr(num, args)) {
|
||||
mk_fresh_uncnstr_var_for(f, r);
|
||||
add_defs(num, args, r, m.mk_false());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
case OP_EQ:
|
||||
SASSERT(num == 2);
|
||||
// return process_eq(f, args[0], args[1]);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool mk_diff(expr* t, expr_ref& r) override {
|
||||
SASSERT(m.is_bool(t));
|
||||
r = mk_not(m, t);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
class arith_expr_inverter : public iexpr_inverter {
|
||||
arith_util a;
|
||||
public:
|
||||
|
||||
arith_expr_inverter(ast_manager& m) : iexpr_inverter(m), a(m) {}
|
||||
|
||||
family_id get_fid() const { return a.get_family_id(); }
|
||||
|
||||
bool process_le_ge(func_decl* f, expr* arg1, expr* arg2, bool le, expr_ref& r) {
|
||||
expr* v;
|
||||
expr* t;
|
||||
if (uncnstr(arg1)) {
|
||||
v = arg1;
|
||||
t = arg2;
|
||||
}
|
||||
else if (uncnstr(arg2)) {
|
||||
v = arg2;
|
||||
t = arg1;
|
||||
le = !le;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
|
||||
mk_fresh_uncnstr_var_for(f, r);
|
||||
if (m_mc) {
|
||||
// v = ite(u, t, t + 1) if le
|
||||
// v = ite(u, t, t - 1) if !le
|
||||
add_def(v, m.mk_ite(r, t, a.mk_add(t, a.mk_numeral(rational(le ? 1 : -1), arg1->get_sort()))));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool process_add(unsigned num, expr* const* args, expr_ref& u) {
|
||||
if (num == 0)
|
||||
return false;
|
||||
unsigned i;
|
||||
expr* v = nullptr;
|
||||
for (i = 0; i < num; i++) {
|
||||
expr* arg = args[i];
|
||||
if (uncnstr(arg)) {
|
||||
v = arg;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (v == nullptr)
|
||||
return false;
|
||||
mk_fresh_uncnstr_var_for(v->get_sort(), u);
|
||||
if (!m_mc)
|
||||
return true;
|
||||
ptr_buffer<expr> new_args;
|
||||
for (unsigned j = 0; j < num; j++)
|
||||
if (j != i)
|
||||
new_args.push_back(args[j]);
|
||||
|
||||
if (new_args.empty())
|
||||
add_def(v, u);
|
||||
else {
|
||||
expr* rest = a.mk_add(new_args);
|
||||
add_def(v, a.mk_sub(u, rest));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool process_arith_mul(unsigned num, expr* const* args, expr_ref & u) {
|
||||
if (num == 0)
|
||||
return false;
|
||||
sort* s = args[0]->get_sort();
|
||||
if (uncnstr(num, args)) {
|
||||
mk_fresh_uncnstr_var_for(s, u);
|
||||
if (m_mc)
|
||||
add_defs(num, args, u, a.mk_numeral(rational(1), s));
|
||||
return true;
|
||||
}
|
||||
// c * v case for reals
|
||||
bool is_int;
|
||||
rational val;
|
||||
if (num == 2 && uncnstr(args[1]) && a.is_numeral(args[0], val, is_int) && !is_int) {
|
||||
if (val.is_zero())
|
||||
return false;
|
||||
mk_fresh_uncnstr_var_for(s, u);
|
||||
if (m_mc) {
|
||||
val = rational(1) / val;
|
||||
add_def(args[1], a.mk_mul(a.mk_numeral(val, false), u));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool operator()(func_decl* f, unsigned num, expr* const* args, expr_ref& r, expr_ref& side_cond) override {
|
||||
SASSERT(f->get_family_id() == a.get_family_id());
|
||||
switch (f->get_decl_kind()) {
|
||||
case OP_ADD:
|
||||
return process_add(num, args, r);
|
||||
case OP_MUL:
|
||||
return process_arith_mul(num, args, r);
|
||||
case OP_LE:
|
||||
SASSERT(num == 2);
|
||||
return process_le_ge(f, args[0], args[1], true, r);
|
||||
case OP_GE:
|
||||
SASSERT(num == 2);
|
||||
return process_le_ge(f, args[0], args[1], false, r);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool mk_diff(expr* t, expr_ref& r) override {
|
||||
SASSERT(a.is_int_real(t));
|
||||
r = a.mk_add(t, a.mk_numeral(rational(1), a.is_int(t)));
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
class bv_expr_inverter : public iexpr_inverter {
|
||||
bv_util bv;
|
||||
|
||||
bool process_add(unsigned num, expr* const* args, expr_ref& u) {
|
||||
if (num == 0)
|
||||
return false;
|
||||
unsigned i;
|
||||
expr* v = nullptr;
|
||||
for (i = 0; i < num; i++) {
|
||||
expr* arg = args[i];
|
||||
if (uncnstr(arg)) {
|
||||
v = arg;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!v)
|
||||
return false;
|
||||
mk_fresh_uncnstr_var_for(v->get_sort(), u);
|
||||
if (!m_mc)
|
||||
return true;
|
||||
ptr_buffer<expr> new_args;
|
||||
for (unsigned j = 0; j < num; j++)
|
||||
if (j != i)
|
||||
new_args.push_back(args[j]);
|
||||
|
||||
if (new_args.empty())
|
||||
add_def(v, u);
|
||||
else {
|
||||
expr* rest = bv.mk_bv_add(new_args);
|
||||
add_def(v, bv.mk_bv_sub(u, rest));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool process_bv_mul(func_decl* f, unsigned num, expr* const* args, expr_ref& r) {
|
||||
if (num == 0)
|
||||
return nullptr;
|
||||
if (uncnstr(num, args)) {
|
||||
sort* s = args[0]->get_sort();
|
||||
mk_fresh_uncnstr_var_for(f, r);
|
||||
if (m_mc)
|
||||
add_defs(num, args, r, bv.mk_numeral(rational(1), s));
|
||||
return true;
|
||||
}
|
||||
// c * v (c is odd) case
|
||||
unsigned sz;
|
||||
rational val;
|
||||
rational inv;
|
||||
if (num == 2 &&
|
||||
uncnstr(args[1]) &&
|
||||
bv.is_numeral(args[0], val, sz) &&
|
||||
val.mult_inverse(sz, inv)) {
|
||||
mk_fresh_uncnstr_var_for(f, r);
|
||||
if (m_mc)
|
||||
add_def(args[1], bv.mk_bv_mul(bv.mk_numeral(inv, sz), r));
|
||||
return true;
|
||||
}
|
||||
|
||||
//
|
||||
// x * K -> fresh[hi-sh-1:0] ++ 0...0
|
||||
// where sh = parity of K
|
||||
// then x -> J^-1*fresh
|
||||
// where J = K >> sh
|
||||
// Because x * K = fresh * K * J^-1 = fresh * 2^sh = fresh[hi-sh-1:0] ++ 0...0
|
||||
//
|
||||
if (num == 2 &&
|
||||
uncnstr(args[1]) &&
|
||||
bv.is_numeral(args[0], val, sz) &&
|
||||
val.is_pos()) {
|
||||
unsigned sh = 0;
|
||||
while (val.is_even()) {
|
||||
val /= rational(2);
|
||||
++sh;
|
||||
}
|
||||
mk_fresh_uncnstr_var_for(f, r);
|
||||
if (sh > 0)
|
||||
r = bv.mk_concat(bv.mk_extract(sz - sh - 1, 0, r), bv.mk_numeral(0, sh));
|
||||
|
||||
if (m_mc) {
|
||||
rational inv_r;
|
||||
VERIFY(val.mult_inverse(sz, inv_r));
|
||||
add_def(args[1], bv.mk_bv_mul(bv.mk_numeral(inv_r, sz), r));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
//
|
||||
// assume x is unconstrained, we can handle general multiplication as follows:
|
||||
// x * y -> if y = 0 then y else fresh << parity(y)
|
||||
// and x -> if y = 0 then y else (y >> parity(y))^-1
|
||||
// parity can be defined using a "giant" ite expression.
|
||||
//
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool process_extract(func_decl* f, expr* arg, expr_ref& r) {
|
||||
if (!uncnstr(arg))
|
||||
return false;
|
||||
mk_fresh_uncnstr_var_for(f, r);
|
||||
if (!m_mc)
|
||||
return true;
|
||||
unsigned high = bv.get_extract_high(f);
|
||||
unsigned low = bv.get_extract_low(f);
|
||||
unsigned bv_size = bv.get_bv_size(arg->get_sort());
|
||||
if (bv_size == high - low + 1)
|
||||
add_def(arg, r);
|
||||
else {
|
||||
ptr_buffer<expr> args;
|
||||
if (high < bv_size - 1)
|
||||
args.push_back(bv.mk_numeral(rational(0), bv_size - high - 1));
|
||||
args.push_back(r);
|
||||
if (low > 0)
|
||||
args.push_back(bv.mk_numeral(rational(0), low));
|
||||
add_def(arg, bv.mk_concat(args.size(), args.data()));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool process_bv_div(func_decl* f, expr* arg1, expr* arg2, expr_ref& r) {
|
||||
if (uncnstr(arg1) && uncnstr(arg2)) {
|
||||
sort* s = arg1->get_sort();
|
||||
mk_fresh_uncnstr_var_for(f, r);
|
||||
if (m_mc) {
|
||||
add_def(arg1, r);
|
||||
add_def(arg2, bv.mk_numeral(rational(1), s));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool process_concat(func_decl* f, unsigned num, expr* const* args, expr_ref& r) {
|
||||
if (num == 0)
|
||||
return false;
|
||||
if (!uncnstr(num, args))
|
||||
return false;
|
||||
mk_fresh_uncnstr_var_for(f, r);
|
||||
if (m_mc) {
|
||||
unsigned i = num;
|
||||
unsigned low = 0;
|
||||
while (i > 0) {
|
||||
--i;
|
||||
expr* arg = args[i];
|
||||
unsigned sz = bv.get_bv_size(arg);
|
||||
add_def(arg, bv.mk_extract(low + sz - 1, low, r));
|
||||
low += sz;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool process_bv_le(func_decl* f, expr* arg1, expr* arg2, bool is_signed, expr_ref& r) {
|
||||
if (uncnstr(arg1)) {
|
||||
// v <= t
|
||||
expr* v = arg1;
|
||||
expr* t = arg2;
|
||||
// v <= t ---> (u or t == MAX) u is fresh
|
||||
// add definition v = ite(u or t == MAX, t, t+1)
|
||||
unsigned bv_sz = bv.get_bv_size(arg1);
|
||||
rational MAX;
|
||||
if (is_signed)
|
||||
MAX = rational::power_of_two(bv_sz - 1) - rational(1);
|
||||
else
|
||||
MAX = rational::power_of_two(bv_sz) - rational(1);
|
||||
mk_fresh_uncnstr_var_for(f, r);
|
||||
r = m.mk_or(r, m.mk_eq(t, bv.mk_numeral(MAX, bv_sz)));
|
||||
if (m_mc)
|
||||
add_def(v, m.mk_ite(r, t, bv.mk_bv_add(t, bv.mk_numeral(rational(1), bv_sz))));
|
||||
return true;
|
||||
}
|
||||
if (uncnstr(arg2)) {
|
||||
// v >= t
|
||||
expr* v = arg2;
|
||||
expr* t = arg1;
|
||||
// v >= t ---> (u ot t == MIN) u is fresh
|
||||
// add definition v = ite(u or t == MIN, t, t-1)
|
||||
unsigned bv_sz = bv.get_bv_size(arg1);
|
||||
rational MIN;
|
||||
if (is_signed)
|
||||
MIN = -rational::power_of_two(bv_sz - 1);
|
||||
else
|
||||
MIN = rational(0);
|
||||
mk_fresh_uncnstr_var_for(f, r);
|
||||
r = m.mk_or(r, m.mk_eq(t, bv.mk_numeral(MIN, bv_sz)));
|
||||
if (m_mc)
|
||||
add_def(v, m.mk_ite(r, t, bv.mk_bv_sub(t, bv.mk_numeral(rational(1), bv_sz))));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool process_bvnot(expr* e, expr_ref& r) {
|
||||
if (!uncnstr(e))
|
||||
return false;
|
||||
mk_fresh_uncnstr_var_for(e->get_sort(), r);
|
||||
if (m_mc)
|
||||
add_def(e, bv.mk_bv_not(r));
|
||||
return true;
|
||||
}
|
||||
|
||||
public:
|
||||
bv_expr_inverter(ast_manager& m) : iexpr_inverter(m), bv(m) {}
|
||||
|
||||
family_id get_fid() const { return bv.get_family_id(); }
|
||||
|
||||
/**
|
||||
* x + t -> fresh
|
||||
* x := fresh - t
|
||||
*
|
||||
* x * x' * x'' -> fresh
|
||||
* x := fresh
|
||||
* x', x'' := 1
|
||||
*
|
||||
* c * x -> fresh, c is odd
|
||||
* x := fresh*c^-1
|
||||
*
|
||||
* x[sz-1:0] -> fresh
|
||||
* x := fresh
|
||||
*
|
||||
* x[hi:lo] -> fresh
|
||||
* x := fresh1 ++ fresh ++ fresh2
|
||||
*
|
||||
* x udiv x', x sdiv x' -> fresh
|
||||
* x' := 1
|
||||
* x := fresh
|
||||
*
|
||||
* x ++ x' ++ x'' -> fresh
|
||||
* x := fresh[hi1:lo1]
|
||||
* x' := fresh[hi2:lo2]
|
||||
* x'' := fresh[hi3:lo3]
|
||||
*
|
||||
* x <= t -> fresh or t == MAX
|
||||
* x := if(fresh, t, t + 1)
|
||||
* t <= x -> fresh or t == MIN
|
||||
* x := if(fresh, t, t - 1)
|
||||
*
|
||||
* ~x -> fresh
|
||||
* x := ~fresh
|
||||
*
|
||||
* x | y -> fresh
|
||||
* x := fresh
|
||||
* y := 0
|
||||
*
|
||||
*/
|
||||
bool operator()(func_decl* f, unsigned num, expr* const* args, expr_ref& r, expr_ref& side_cond) override {
|
||||
SASSERT(f->get_family_id() == bv.get_family_id());
|
||||
switch (f->get_decl_kind()) {
|
||||
case OP_BADD:
|
||||
return process_add(num, args, r);
|
||||
case OP_BMUL:
|
||||
return process_bv_mul(f, num, args, r);
|
||||
case OP_BSDIV:
|
||||
case OP_BUDIV:
|
||||
case OP_BSDIV_I:
|
||||
case OP_BUDIV_I:
|
||||
SASSERT(num == 2);
|
||||
return process_bv_div(f, args[0], args[1], r);
|
||||
case OP_SLEQ:
|
||||
SASSERT(num == 2);
|
||||
return process_bv_le(f, args[0], args[1], true, r);
|
||||
case OP_ULEQ:
|
||||
SASSERT(num == 2);
|
||||
return process_bv_le(f, args[0], args[1], false, r);
|
||||
case OP_CONCAT:
|
||||
return process_concat(f, num, args, r);
|
||||
case OP_EXTRACT:
|
||||
SASSERT(num == 1);
|
||||
return process_extract(f, args[0], r);
|
||||
case OP_BNOT:
|
||||
SASSERT(num == 1);
|
||||
return process_bvnot(args[0], r);
|
||||
case OP_BOR:
|
||||
if (num > 0 && uncnstr(num, args)) {
|
||||
sort* s = args[0]->get_sort();
|
||||
mk_fresh_uncnstr_var_for(f, r);
|
||||
if (m_mc)
|
||||
add_defs(num, args, r, bv.mk_numeral(rational(0), s));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool mk_diff(expr* t, expr_ref& r) override {
|
||||
r = bv.mk_bv_not(t);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* F[select(x, i)] -> F[fresh]
|
||||
* x := const(fresh)
|
||||
|
||||
* F[store(x, ..., x')] -> F[fresh]
|
||||
* x' := select(x, ...)
|
||||
* x := fresh
|
||||
*/
|
||||
|
||||
class array_expr_inverter : public iexpr_inverter {
|
||||
array_util a;
|
||||
iexpr_inverter& inv;
|
||||
public:
|
||||
array_expr_inverter(ast_manager& m, iexpr_inverter& s) : iexpr_inverter(m), a(m), inv(s) {}
|
||||
|
||||
family_id get_fid() const { return a.get_family_id(); }
|
||||
|
||||
bool operator()(func_decl* f, unsigned num, expr* const* args, expr_ref& r, expr_ref& side_cond) override {
|
||||
SASSERT(f->get_family_id() == a.get_family_id());
|
||||
switch (f->get_decl_kind()) {
|
||||
case OP_SELECT:
|
||||
if (uncnstr(args[0])) {
|
||||
mk_fresh_uncnstr_var_for(f, r);
|
||||
sort* s = args[0]->get_sort();
|
||||
if (m_mc)
|
||||
add_def(args[0], a.mk_const_array(s, r));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
case OP_STORE:
|
||||
if (uncnstr(args[0]) && uncnstr(args[num - 1])) {
|
||||
mk_fresh_uncnstr_var_for(f, r);
|
||||
if (m_mc) {
|
||||
add_def(args[num - 1], m.mk_app(a.get_family_id(), OP_SELECT, num - 1, args));
|
||||
add_def(args[0], r);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool mk_diff(expr* t, expr_ref& r) override {
|
||||
sort* s = t->get_sort();
|
||||
SASSERT(a.is_array(s));
|
||||
if (m.is_uninterp(get_array_range(s)))
|
||||
return false;
|
||||
unsigned arity = get_array_arity(s);
|
||||
for (unsigned i = 0; i < arity; i++)
|
||||
if (m.is_uninterp(get_array_domain(s, i)))
|
||||
return false;
|
||||
// building
|
||||
// r = (store t i1 ... in d)
|
||||
// where i1 ... in are arbitrary values
|
||||
// and d is a term different from (select t i1 ... in)
|
||||
expr_ref_vector new_args(m);
|
||||
new_args.push_back(t);
|
||||
for (unsigned i = 0; i < arity; i++)
|
||||
new_args.push_back(m.get_some_value(get_array_domain(s, i)));
|
||||
expr_ref sel(m);
|
||||
sel = a.mk_select(new_args);
|
||||
expr_ref diff_sel(m);
|
||||
if (!inv.mk_diff(sel, diff_sel))
|
||||
return false;
|
||||
new_args.push_back(diff_sel);
|
||||
r = a.mk_store(new_args);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
class dt_expr_inverter : public iexpr_inverter {
|
||||
datatype_util dt;
|
||||
public:
|
||||
|
||||
dt_expr_inverter(ast_manager& m) : iexpr_inverter(m), dt(m) {}
|
||||
|
||||
family_id get_fid() const { return dt.get_family_id(); }
|
||||
/**
|
||||
* head(x) -> fresh
|
||||
* x := cons(fresh, arb)
|
||||
*/
|
||||
bool operator()(func_decl* f, unsigned num, expr* const* args, expr_ref& r, expr_ref& side_cond) override {
|
||||
if (dt.is_accessor(f)) {
|
||||
SASSERT(num == 1);
|
||||
if (uncnstr(args[0])) {
|
||||
if (!m_mc) {
|
||||
mk_fresh_uncnstr_var_for(f, r);
|
||||
return true;
|
||||
}
|
||||
func_decl* c = dt.get_accessor_constructor(f);
|
||||
for (unsigned i = 0; i < c->get_arity(); i++)
|
||||
if (!m.is_fully_interp(c->get_domain(i)))
|
||||
return false;
|
||||
mk_fresh_uncnstr_var_for(f, r);
|
||||
ptr_vector<func_decl> const& accs = *dt.get_constructor_accessors(c);
|
||||
ptr_buffer<expr> new_args;
|
||||
for (unsigned i = 0; i < accs.size(); i++) {
|
||||
if (accs[i] == f)
|
||||
new_args.push_back(r);
|
||||
else
|
||||
new_args.push_back(m.get_some_value(c->get_domain(i)));
|
||||
}
|
||||
add_def(args[0], m.mk_app(c, new_args));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool mk_diff(expr* t, expr_ref& r) override {
|
||||
// In the current implementation, I only handle the case where
|
||||
// the datatype has a recursive constructor.
|
||||
sort* s = t->get_sort();
|
||||
ptr_vector<func_decl> const& constructors = *dt.get_datatype_constructors(s);
|
||||
for (func_decl* constructor : constructors) {
|
||||
unsigned num = constructor->get_arity();
|
||||
unsigned target = UINT_MAX;
|
||||
for (unsigned i = 0; i < num; i++) {
|
||||
sort* s_arg = constructor->get_domain(i);
|
||||
if (s == s_arg) {
|
||||
target = i;
|
||||
continue;
|
||||
}
|
||||
if (m.is_uninterp(s_arg))
|
||||
break;
|
||||
}
|
||||
if (target == UINT_MAX)
|
||||
continue;
|
||||
// use the constructor the distinct term constructor(...,t,...)
|
||||
ptr_buffer<expr> new_args;
|
||||
for (unsigned i = 0; i < num; i++) {
|
||||
if (i == target)
|
||||
new_args.push_back(t);
|
||||
else
|
||||
new_args.push_back(m.get_some_value(constructor->get_domain(i)));
|
||||
}
|
||||
r = m.mk_app(constructor, new_args);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
expr_inverter::~expr_inverter() {
|
||||
for (auto* v : m_inverters)
|
||||
dealloc(v);
|
||||
}
|
||||
|
||||
|
||||
bool iexpr_inverter::uncnstr(unsigned num, expr * const * args) const {
|
||||
for (unsigned i = 0; i < num; i++)
|
||||
if (!m_is_var(args[i]))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
\brief Create a fresh variable for abstracting (f args[0] ... args[num-1])
|
||||
Return true if it a new variable was created, and false if the variable already existed for this
|
||||
application. Store the variable in v
|
||||
*/
|
||||
void iexpr_inverter::mk_fresh_uncnstr_var_for(sort * s, expr_ref & v) {
|
||||
v = m.mk_fresh_const(nullptr, s);
|
||||
if (m_mc)
|
||||
m_mc->hide(v);
|
||||
}
|
||||
|
||||
void iexpr_inverter::add_def(expr * v, expr * def) {
|
||||
expr_ref _v(v, m);
|
||||
expr_ref _d(def, m);
|
||||
if (!m_mc)
|
||||
return;
|
||||
SASSERT(uncnstr(v));
|
||||
SASSERT(to_app(v)->get_num_args() == 0);
|
||||
m_mc->add(to_app(v)->get_decl(), def);
|
||||
}
|
||||
|
||||
void iexpr_inverter::add_defs(unsigned num, expr* const* args, expr* u, expr* identity) {
|
||||
expr_ref _id(identity, m);
|
||||
if (!m_mc)
|
||||
return;
|
||||
add_def(args[0], u);
|
||||
for (unsigned i = 1; i < num; i++)
|
||||
add_def(args[i], identity);
|
||||
}
|
||||
|
||||
|
||||
expr_inverter::expr_inverter(ast_manager& m): iexpr_inverter(m) {
|
||||
auto* a = alloc(arith_expr_inverter, m);
|
||||
auto* b = alloc(bv_expr_inverter, m);
|
||||
auto* ar = alloc(array_expr_inverter, m, *this);
|
||||
auto* dt = alloc(dt_expr_inverter, m);
|
||||
m_inverters.setx(m.get_basic_family_id(), alloc(basic_expr_inverter, m), nullptr);
|
||||
m_inverters.setx(a->get_fid(), a, nullptr);
|
||||
m_inverters.setx(b->get_fid(), b, nullptr);
|
||||
m_inverters.setx(ar->get_fid(), ar, nullptr);
|
||||
m_inverters.setx(dt->get_fid(), dt, nullptr);
|
||||
}
|
||||
|
||||
bool expr_inverter::is_converted(func_decl* f, unsigned num, expr* const* args) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool expr_inverter::operator()(func_decl* f, unsigned num, expr* const* args, expr_ref& new_expr, expr_ref& side_cond) {
|
||||
if (num == 0)
|
||||
return false;
|
||||
|
||||
for (unsigned i = 0; i < num; i++)
|
||||
if (!is_ground(args[i]))
|
||||
return false;
|
||||
|
||||
family_id fid = f->get_family_id();
|
||||
if (fid == null_family_id)
|
||||
return false;
|
||||
|
||||
if (is_converted(f, num, args))
|
||||
return false;
|
||||
|
||||
auto* p = m_inverters.get(fid, nullptr);
|
||||
return p && (*p)(f, num, args, new_expr, side_cond);
|
||||
}
|
||||
|
||||
bool expr_inverter::mk_diff(expr* t, expr_ref& r) {
|
||||
sort * s = t->get_sort();
|
||||
if (!m.is_fully_interp(s))
|
||||
return false;
|
||||
|
||||
// If the interpreted sort has only one element,
|
||||
// then it is unsound to eliminate the unconstrained variable in the equality
|
||||
sort_size sz = s->get_num_elements();
|
||||
if (sz.is_finite() && sz.size() <= 1)
|
||||
return false;
|
||||
|
||||
if (!m_mc) {
|
||||
// easy case, model generation is disabled.
|
||||
mk_fresh_uncnstr_var_for(s, r);
|
||||
return true;
|
||||
}
|
||||
|
||||
family_id fid = s->get_family_id();
|
||||
auto* p = m_inverters.get(fid, nullptr);
|
||||
return p && p->mk_diff(t, r);
|
||||
}
|
||||
|
||||
void expr_inverter::set_is_var(std::function<bool(expr*)>& is_var) {
|
||||
for (auto* p : m_inverters)
|
||||
if (p)
|
||||
p->set_is_var(is_var);
|
||||
}
|
||||
|
||||
void expr_inverter::set_model_converter(generic_model_converter* mc) {
|
||||
for (auto* p : m_inverters)
|
||||
if (p)
|
||||
p->set_model_converter(mc);
|
||||
}
|
57
src/ast/converters/expr_inverter.h
Normal file
57
src/ast/converters/expr_inverter.h
Normal file
|
@ -0,0 +1,57 @@
|
|||
/*++
|
||||
Copyright (c) 2017 Microsoft Corporation
|
||||
|
||||
Module Name:
|
||||
|
||||
expr_inverter.h
|
||||
|
||||
Abstract:
|
||||
|
||||
inverter interface and instance
|
||||
|
||||
Author:
|
||||
|
||||
Nikolaj Bjorner (nbjorner) 2022-10-11
|
||||
|
||||
|
||||
--*/
|
||||
#pragma once
|
||||
|
||||
#include "ast/converters/generic_model_converter.h"
|
||||
|
||||
class iexpr_inverter {
|
||||
protected:
|
||||
ast_manager& m;
|
||||
std::function<bool(expr*)> m_is_var;
|
||||
generic_model_converter_ref m_mc;
|
||||
|
||||
bool uncnstr(expr* e) const { return m_is_var(e); }
|
||||
bool uncnstr(unsigned num, expr * const * args) const;
|
||||
void mk_fresh_uncnstr_var_for(sort* s, expr_ref& v);
|
||||
void mk_fresh_uncnstr_var_for(func_decl* f, expr_ref& v) { mk_fresh_uncnstr_var_for(f->get_range(), v); }
|
||||
void add_def(expr * v, expr * def);
|
||||
void add_defs(unsigned num, expr * const * args, expr * u, expr * identity);
|
||||
|
||||
public:
|
||||
iexpr_inverter(ast_manager& m): m(m) {}
|
||||
virtual ~iexpr_inverter() {}
|
||||
virtual void set_is_var(std::function<bool(expr*)>& is_var) { m_is_var = is_var; }
|
||||
virtual void set_model_converter(generic_model_converter* mc) { m_mc = mc; }
|
||||
|
||||
virtual bool operator()(func_decl* f, unsigned n, expr* const* args, expr_ref& new_expr, expr_ref& side_cond) = 0;
|
||||
virtual bool mk_diff(expr* t, expr_ref& r) = 0;
|
||||
};
|
||||
|
||||
class expr_inverter : public iexpr_inverter {
|
||||
ptr_vector<iexpr_inverter> m_inverters;
|
||||
|
||||
bool is_converted(func_decl* f, unsigned num, expr* const* args);
|
||||
|
||||
public:
|
||||
expr_inverter(ast_manager& m);
|
||||
~expr_inverter() override;
|
||||
bool operator()(func_decl* f, unsigned n, expr* const* args, expr_ref& new_expr, expr_ref& side_cond) override;
|
||||
bool mk_diff(expr* t, expr_ref& r) override;
|
||||
void set_is_var(std::function<bool(expr*)>& is_var) override;
|
||||
void set_model_converter(generic_model_converter* mc) override;
|
||||
};
|
|
@ -22,6 +22,7 @@ Notes:
|
|||
#include "ast/converters/model_converter.h"
|
||||
|
||||
class generic_model_converter : public model_converter {
|
||||
public:
|
||||
enum instruction { HIDE, ADD };
|
||||
struct entry {
|
||||
func_decl_ref m_f;
|
||||
|
@ -30,6 +31,7 @@ class generic_model_converter : public model_converter {
|
|||
entry(func_decl* f, expr* d, ast_manager& m, instruction i):
|
||||
m_f(f, m), m_def(d, m), m_instruction(i) {}
|
||||
};
|
||||
private:
|
||||
ast_manager& m;
|
||||
std::string m_orig;
|
||||
vector<entry> m_entries;
|
||||
|
@ -67,6 +69,8 @@ public:
|
|||
void set_env(ast_pp_util* visitor) override;
|
||||
|
||||
void get_units(obj_map<expr, bool>& units) override;
|
||||
|
||||
vector<entry> const& entries() const { return m_entries; }
|
||||
};
|
||||
|
||||
typedef ref<generic_model_converter> generic_model_converter_ref;
|
||||
|
|
|
@ -120,11 +120,11 @@ bool subterms::iterator::operator!=(iterator const& other) const {
|
|||
}
|
||||
|
||||
|
||||
subterms_postorder::subterms_postorder(expr_ref_vector const& es): m_es(es) {}
|
||||
subterms_postorder::subterms_postorder(expr_ref const& e) : m_es(e.m()) { if (e) m_es.push_back(e); }
|
||||
subterms_postorder::subterms_postorder(expr_ref_vector const& es, bool include_bound): m_include_bound(include_bound), m_es(es) {}
|
||||
subterms_postorder::subterms_postorder(expr_ref const& e, bool include_bound) : m_include_bound(include_bound), m_es(e.m()) { if (e) m_es.push_back(e); }
|
||||
subterms_postorder::iterator subterms_postorder::begin() { return iterator(*this, true); }
|
||||
subterms_postorder::iterator subterms_postorder::end() { return iterator(*this, false); }
|
||||
subterms_postorder::iterator::iterator(subterms_postorder& f, bool start): m_es(f.m_es) {
|
||||
subterms_postorder::iterator::iterator(subterms_postorder& f, bool start): m_include_bound(f.m_include_bound), m_es(f.m_es) {
|
||||
if (!start) m_es.reset();
|
||||
next();
|
||||
}
|
||||
|
@ -153,6 +153,13 @@ void subterms_postorder::iterator::next() {
|
|||
}
|
||||
}
|
||||
}
|
||||
else if (is_quantifier(e) && m_include_bound) {
|
||||
expr* body = to_quantifier(e)->get_expr();
|
||||
if (!m_visited.is_marked(body)) {
|
||||
m_es.push_back(body);
|
||||
all_visited = false;
|
||||
}
|
||||
}
|
||||
if (all_visited) {
|
||||
m_visited.mark(e, true);
|
||||
break;
|
||||
|
|
|
@ -200,9 +200,15 @@ public:
|
|||
};
|
||||
|
||||
class subterms_postorder {
|
||||
bool m_include_bound;
|
||||
expr_ref_vector m_es;
|
||||
subterms_postorder(expr_ref_vector const& es, bool include_bound);
|
||||
subterms_postorder(expr_ref const& e, bool include_bound);
|
||||
|
||||
|
||||
public:
|
||||
class iterator {
|
||||
bool m_include_bound = false;
|
||||
expr_ref_vector m_es;
|
||||
expr_mark m_visited, m_seen;
|
||||
void next();
|
||||
|
@ -214,8 +220,10 @@ public:
|
|||
bool operator==(iterator const& other) const;
|
||||
bool operator!=(iterator const& other) const;
|
||||
};
|
||||
subterms_postorder(expr_ref_vector const& es);
|
||||
subterms_postorder(expr_ref const& e);
|
||||
static subterms_postorder all(expr_ref_vector const& es) { return subterms_postorder(es, true); }
|
||||
static subterms_postorder ground(expr_ref_vector const& es) { return subterms_postorder(es, false); }
|
||||
static subterms_postorder all(expr_ref const& e) { return subterms_postorder(e, true); }
|
||||
static subterms_postorder ground(expr_ref const& e) { return subterms_postorder(e, false); }
|
||||
iterator begin();
|
||||
iterator end();
|
||||
};
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
z3_add_component(simplifiers
|
||||
SOURCES
|
||||
bv_slice.cpp
|
||||
elim_unconstrained.cpp
|
||||
euf_completion.cpp
|
||||
extract_eqs.cpp
|
||||
model_reconstruction_trail.cpp
|
||||
|
|
297
src/ast/simplifiers/elim_unconstrained.cpp
Normal file
297
src/ast/simplifiers/elim_unconstrained.cpp
Normal file
|
@ -0,0 +1,297 @@
|
|||
/*++
|
||||
Copyright (c) 2022 Microsoft Corporation
|
||||
|
||||
Module Name:
|
||||
|
||||
elim_unconstrained.cpp
|
||||
|
||||
Abstract:
|
||||
|
||||
Incremental, modular and more efficient version of elim_unconstr_tactic and
|
||||
reduce_invertible_tactic.
|
||||
|
||||
reduce_invertible_tactic should be subsumed by elim_unconstr_tactic
|
||||
elim_unconstr_tactic has some built-in limitations that are not easy to fix with small changes:
|
||||
- it is inefficient for examples like x <= y, y <= z, z <= u, ...
|
||||
All variables x, y, z, .. can eventually be eliminated, but the tactic requires a global
|
||||
analysis between each elimination. We address this by using reference counts and maintaining
|
||||
a heap of reference counts.
|
||||
- it does not accomodate side constraints. The more general invertibility reduction methods, such
|
||||
as those introduced for bit-vectors use side constraints.
|
||||
- it is not modular: we detach the expression invertion routines to self-contained code.
|
||||
|
||||
Maintain a representation of terms as a set of nodes.
|
||||
Each node has:
|
||||
|
||||
- reference count = number of parents that are live
|
||||
- orig - original term, the orig->get_id() is the index to the node
|
||||
- term - current term representing the node after rewriting
|
||||
- parents - list of parents where orig occurs.
|
||||
|
||||
Subterms have reference counts
|
||||
Elegible variables are inserted into a heap ordered by reference counts.
|
||||
Variables that have reference count 1 are examined for invertibility.
|
||||
|
||||
Author:
|
||||
|
||||
Nikolaj Bjorner (nbjorner) 2022-11-11.
|
||||
|
||||
--*/
|
||||
|
||||
|
||||
|
||||
#include "ast/ast_ll_pp.h"
|
||||
#include "ast/ast_pp.h"
|
||||
#include "ast/recfun_decl_plugin.h"
|
||||
#include "ast/simplifiers/elim_unconstrained.h"
|
||||
|
||||
elim_unconstrained::elim_unconstrained(ast_manager& m, dependent_expr_state& fmls) :
|
||||
dependent_expr_simplifier(m, fmls), m_inverter(m), m_lt(*this), m_heap(1024, m_lt), m_trail(m) {
|
||||
|
||||
std::function<bool(expr*)> is_var = [&](expr* e) {
|
||||
return is_uninterp_const(e) && !m_frozen.is_marked(e) && get_node(e).m_refcount == 1;
|
||||
};
|
||||
|
||||
m_inverter.set_is_var(is_var);
|
||||
}
|
||||
|
||||
|
||||
void elim_unconstrained::eliminate() {
|
||||
|
||||
while (!m_heap.empty()) {
|
||||
expr_ref r(m), side_cond(m);
|
||||
int v = m_heap.erase_min();
|
||||
node& n = get_node(v);
|
||||
IF_VERBOSE(11, verbose_stream() << mk_pp(n.m_orig, m) << " @ " << n.m_refcount << "\n");
|
||||
if (n.m_refcount == 0)
|
||||
continue;
|
||||
if (n.m_refcount > 1)
|
||||
return;
|
||||
|
||||
if (n.m_parents.empty())
|
||||
continue;
|
||||
expr* e = get_parent(v);
|
||||
for (expr* p : n.m_parents)
|
||||
IF_VERBOSE(11, verbose_stream() << "parent " << mk_pp(p, m) << "\n");
|
||||
if (!is_app(e))
|
||||
continue;
|
||||
if (!is_ground(e))
|
||||
continue;
|
||||
app* t = to_app(e);
|
||||
m_args.reset();
|
||||
for (expr* arg : *to_app(t))
|
||||
m_args.push_back(get_node(arg).m_term);
|
||||
if (!m_inverter(t->get_decl(), m_args.size(), m_args.data(), r, side_cond))
|
||||
continue;
|
||||
|
||||
m_stats.m_num_eliminated++;
|
||||
n.m_refcount = 0;
|
||||
SASSERT(r);
|
||||
m_trail.push_back(r);
|
||||
gc(e);
|
||||
|
||||
get_node(e).m_term = r;
|
||||
get_node(e).m_refcount++;
|
||||
IF_VERBOSE(11, verbose_stream() << mk_pp(e, m) << "\n");
|
||||
SASSERT(!m_heap.contains(e->get_id()));
|
||||
if (is_uninterp_const(r))
|
||||
m_heap.insert(e->get_id());
|
||||
|
||||
IF_VERBOSE(11, verbose_stream() << mk_pp(n.m_orig, m) << " " << mk_pp(t, m) << " -> " << r << " " << get_node(e).m_refcount << "\n");
|
||||
|
||||
SASSERT(!side_cond && "not implemented to add side conditions\n");
|
||||
}
|
||||
}
|
||||
|
||||
void elim_unconstrained::add_term(expr* t) {
|
||||
expr_ref_vector terms(m);
|
||||
terms.push_back(t);
|
||||
init_terms(terms);
|
||||
}
|
||||
|
||||
expr* elim_unconstrained::get_parent(unsigned n) const {
|
||||
for (expr* p : get_node(n).m_parents)
|
||||
if (get_node(p).m_refcount > 0 && get_node(p).m_term == get_node(p).m_orig)
|
||||
return p;
|
||||
IF_VERBOSE(0, verbose_stream() << "term " << mk_pp(get_node(n).m_term, m) << "\n");
|
||||
for (expr* p : get_node(n).m_parents)
|
||||
IF_VERBOSE(0, verbose_stream() << "parent " << mk_pp(p, m) << "\n");
|
||||
UNREACHABLE();
|
||||
return nullptr;
|
||||
}
|
||||
/**
|
||||
* initialize node structure
|
||||
*/
|
||||
void elim_unconstrained::init_nodes() {
|
||||
expr_ref_vector terms(m);
|
||||
for (unsigned i = 0; i < m_fmls.size(); ++i)
|
||||
terms.push_back(m_fmls[i].fml());
|
||||
m_trail.append(terms);
|
||||
m_heap.reset();
|
||||
|
||||
init_terms(terms);
|
||||
|
||||
for (expr* e : terms)
|
||||
inc_ref(e);
|
||||
|
||||
// freeze subterms before the head.
|
||||
terms.reset();
|
||||
for (unsigned i = 0; i < m_qhead; ++i)
|
||||
terms.push_back(m_fmls[i].fml());
|
||||
for (expr* e : subterms::all(terms))
|
||||
m_frozen.mark(e, true);
|
||||
|
||||
|
||||
recfun::util rec(m);
|
||||
if (rec.has_rec_defs()) {
|
||||
for (func_decl* f : rec.get_rec_funs()) {
|
||||
expr* rhs = rec.get_def(f).get_rhs();
|
||||
for (expr* t : subterms::all(expr_ref(rhs, m)))
|
||||
m_frozen.mark(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void elim_unconstrained::init_terms(expr_ref_vector const& terms) {
|
||||
unsigned max_id = 0;
|
||||
for (expr* e : subterms::all(terms))
|
||||
max_id = std::max(max_id, e->get_id());
|
||||
|
||||
m_nodes.reserve(max_id + 1);
|
||||
m_heap.reserve(max_id + 1);
|
||||
|
||||
for (expr* e : subterms_postorder::all(terms)) {
|
||||
node& n = get_node(e);
|
||||
if (n.m_term)
|
||||
continue;
|
||||
n.m_orig = e;
|
||||
n.m_term = e;
|
||||
n.m_refcount = 0;
|
||||
if (is_uninterp_const(e))
|
||||
m_heap.insert(e->get_id());
|
||||
if (is_quantifier(e)) {
|
||||
expr* body = to_quantifier(e)->get_expr();
|
||||
get_node(body).m_parents.push_back(e);
|
||||
inc_ref(body);
|
||||
}
|
||||
else if (is_app(e)) {
|
||||
for (expr* arg : *to_app(e)) {
|
||||
get_node(arg).m_parents.push_back(e);
|
||||
inc_ref(arg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void elim_unconstrained::gc(expr* t) {
|
||||
ptr_vector<expr> todo;
|
||||
todo.push_back(t);
|
||||
while (!todo.empty()) {
|
||||
t = todo.back();
|
||||
todo.pop_back();
|
||||
node& n = get_node(t);
|
||||
if (n.m_refcount == 0)
|
||||
continue;
|
||||
dec_ref(t);
|
||||
if (n.m_refcount != 0)
|
||||
continue;
|
||||
if (is_app(t)) {
|
||||
for (expr* arg : *to_app(t))
|
||||
todo.push_back(arg);
|
||||
}
|
||||
else if (is_quantifier(t))
|
||||
todo.push_back(to_quantifier(t)->get_expr());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* walk nodes starting from lowest depth and reconstruct their normalized forms.
|
||||
*/
|
||||
void elim_unconstrained::reconstruct_terms() {
|
||||
expr_ref_vector terms(m);
|
||||
for (unsigned i = m_qhead; i < m_fmls.size(); ++i)
|
||||
terms.push_back(m_fmls[i].fml());
|
||||
|
||||
for (expr* e : subterms_postorder::all(terms)) {
|
||||
node& n = get_node(e);
|
||||
expr* t = n.m_term;
|
||||
if (t != n.m_orig)
|
||||
continue;
|
||||
if (is_app(t)) {
|
||||
bool change = false;
|
||||
m_args.reset();
|
||||
for (expr* arg : *to_app(t)) {
|
||||
node& n2 = get_node(arg);
|
||||
m_args.push_back(n2.m_term);
|
||||
change |= n2.m_term != n2.m_orig;
|
||||
}
|
||||
if (change) {
|
||||
n.m_term = m.mk_app(to_app(t)->get_decl(), m_args);
|
||||
m_trail.push_back(n.m_term);
|
||||
}
|
||||
}
|
||||
else if (is_quantifier(t)) {
|
||||
node& n2 = get_node(to_quantifier(t)->get_expr());
|
||||
if (n2.m_term != n2.m_orig) {
|
||||
n.m_term = m.update_quantifier(to_quantifier(t), n2.m_term);
|
||||
m_trail.push_back(n.m_term);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void elim_unconstrained::assert_normalized(vector<dependent_expr>& old_fmls) {
|
||||
for (unsigned i = m_qhead; i < m_fmls.size(); ++i) {
|
||||
auto [f, d] = m_fmls[i]();
|
||||
node& n = get_node(f);
|
||||
expr* g = n.m_term;
|
||||
if (f == g)
|
||||
continue;
|
||||
old_fmls.push_back(m_fmls[i]);
|
||||
m_fmls.update(i, dependent_expr(m, g, d));
|
||||
IF_VERBOSE(11, verbose_stream() << mk_bounded_pp(f, m, 3) << " -> " << mk_bounded_pp(g, m, 3) << "\n");
|
||||
TRACE("elim_unconstrained", tout << mk_bounded_pp(f, m) << " -> " << mk_bounded_pp(g, m) << "\n");
|
||||
}
|
||||
}
|
||||
|
||||
void elim_unconstrained::update_model_trail(generic_model_converter& mc, vector<dependent_expr> const& old_fmls) {
|
||||
auto& trail = m_fmls.model_trail();
|
||||
scoped_ptr<expr_replacer> rp = mk_default_expr_replacer(m, false);
|
||||
scoped_ptr<expr_substitution> sub = alloc(expr_substitution, m, true, false);
|
||||
rp->set_substitution(sub.get());
|
||||
expr_ref new_def(m);
|
||||
for (auto const& entry : mc.entries()) {
|
||||
switch (entry.m_instruction) {
|
||||
case generic_model_converter::instruction::HIDE:
|
||||
break;
|
||||
case generic_model_converter::instruction::ADD:
|
||||
new_def = entry.m_def;
|
||||
(*rp)(new_def);
|
||||
sub->insert(m.mk_const(entry.m_f), new_def, nullptr, nullptr);
|
||||
break;
|
||||
}
|
||||
}
|
||||
trail.push(sub.detach(), old_fmls);
|
||||
|
||||
for (auto const& entry : mc.entries()) {
|
||||
switch (entry.m_instruction) {
|
||||
case generic_model_converter::instruction::HIDE:
|
||||
trail.push(entry.m_f);
|
||||
break;
|
||||
case generic_model_converter::instruction::ADD:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void elim_unconstrained::reduce() {
|
||||
generic_model_converter_ref mc = alloc(generic_model_converter, m, "elim-unconstrained");
|
||||
m_inverter.set_model_converter(mc.get());
|
||||
init_nodes();
|
||||
eliminate();
|
||||
reconstruct_terms();
|
||||
vector<dependent_expr> old_fmls;
|
||||
assert_normalized(old_fmls);
|
||||
update_model_trail(*mc, old_fmls);
|
||||
advance_qhead(m_fmls.size());
|
||||
}
|
84
src/ast/simplifiers/elim_unconstrained.h
Normal file
84
src/ast/simplifiers/elim_unconstrained.h
Normal file
|
@ -0,0 +1,84 @@
|
|||
/*++
|
||||
Copyright (c) 2022 Microsoft Corporation
|
||||
|
||||
Module Name:
|
||||
|
||||
elim_unconstrained.h
|
||||
|
||||
Author:
|
||||
|
||||
Nikolaj Bjorner (nbjorner) 2022-11-2.
|
||||
|
||||
--*/
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "util/heap.h"
|
||||
#include "ast/simplifiers/dependent_expr_state.h"
|
||||
#include "ast/rewriter/th_rewriter.h"
|
||||
#include "ast/converters/expr_inverter.h"
|
||||
|
||||
|
||||
class elim_unconstrained : public dependent_expr_simplifier {
|
||||
|
||||
struct node {
|
||||
unsigned m_refcount;
|
||||
expr* m_term;
|
||||
expr* m_orig;
|
||||
ptr_vector<expr> m_parents;
|
||||
};
|
||||
struct var_lt {
|
||||
elim_unconstrained& s;
|
||||
var_lt(elim_unconstrained& s) : s(s) {}
|
||||
bool operator()(int v1, int v2) const {
|
||||
return s.get_node(v1).m_refcount < s.get_node(v2).m_refcount;
|
||||
}
|
||||
};
|
||||
struct stats {
|
||||
unsigned m_num_eliminated = 0;
|
||||
void reset() { m_num_eliminated = 0; }
|
||||
};
|
||||
expr_inverter m_inverter;
|
||||
vector<node> m_nodes;
|
||||
var_lt m_lt;
|
||||
heap<var_lt> m_heap;
|
||||
expr_ref_vector m_trail;
|
||||
ptr_vector<expr> m_args;
|
||||
expr_mark m_frozen;
|
||||
stats m_stats;
|
||||
|
||||
bool operator()(int v1, int v2) const { return get_node(v1).m_refcount < get_node(v2).m_refcount; }
|
||||
|
||||
node& get_node(unsigned n) { return m_nodes[n]; }
|
||||
node const& get_node(unsigned n) const { return m_nodes[n]; }
|
||||
node& get_node(expr* t) { return m_nodes[t->get_id()]; }
|
||||
node const& get_node(expr* t) const { return m_nodes[t->get_id()]; }
|
||||
unsigned get_refcount(expr* t) const { return get_node(t).m_refcount; }
|
||||
void inc_ref(expr* t) { ++get_node(t).m_refcount; if (is_uninterp_const(t)) m_heap.increased(t->get_id()); }
|
||||
void dec_ref(expr* t) { --get_node(t).m_refcount; if (is_uninterp_const(t)) m_heap.decreased(t->get_id()); }
|
||||
void gc(expr* t);
|
||||
|
||||
expr* get_parent(unsigned n) const;
|
||||
void init_refcounts();
|
||||
void dec_refcounts(expr* t);
|
||||
|
||||
void add_term(expr* t);
|
||||
void init_terms(expr_ref_vector const& terms);
|
||||
|
||||
void init_nodes();
|
||||
void eliminate();
|
||||
void reconstruct_terms();
|
||||
void assert_normalized(vector<dependent_expr>& old_fmls);
|
||||
void update_model_trail(generic_model_converter& mc, vector<dependent_expr> const& old_fmls);
|
||||
|
||||
public:
|
||||
|
||||
elim_unconstrained(ast_manager& m, dependent_expr_state& fmls);
|
||||
|
||||
void reduce() override;
|
||||
|
||||
void collect_statistics(statistics& st) const override { st.update("elim-unconstr", m_stats.m_num_eliminated); }
|
||||
|
||||
void reset_statistics() override { m_stats.reset(); }
|
||||
};
|
|
@ -30,6 +30,9 @@ void model_reconstruction_trail::replay(dependent_expr const& d, vector<dependen
|
|||
if (!t->m_active)
|
||||
continue;
|
||||
|
||||
if (t->m_hide)
|
||||
continue;
|
||||
|
||||
// updates that have no intersections with current variables are skipped
|
||||
if (!t->intersects(free_vars))
|
||||
continue;
|
||||
|
@ -79,6 +82,11 @@ model_converter_ref model_reconstruction_trail::get_model_converter() {
|
|||
if (!t->m_active)
|
||||
continue;
|
||||
|
||||
if (t->m_hide) {
|
||||
mc->hide(t->m_hide);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (first) {
|
||||
first = false;
|
||||
for (auto const& [v, def] : t->m_subst->sub()) {
|
||||
|
|
|
@ -35,11 +35,14 @@ class model_reconstruction_trail {
|
|||
struct entry {
|
||||
scoped_ptr<expr_substitution> m_subst;
|
||||
vector<dependent_expr> m_removed;
|
||||
func_decl* m_hide = nullptr;
|
||||
bool m_active = true;
|
||||
|
||||
entry(expr_substitution* s, vector<dependent_expr> const& rem) :
|
||||
m_subst(s), m_removed(rem) {}
|
||||
|
||||
entry(func_decl* h) : m_hide(h) {}
|
||||
|
||||
bool is_loose() const { return !m_removed.empty(); }
|
||||
|
||||
bool intersects(ast_mark const& free_vars) const {
|
||||
|
@ -48,8 +51,6 @@ class model_reconstruction_trail {
|
|||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
||||
ast_manager& m;
|
||||
|
@ -81,6 +82,14 @@ public:
|
|||
*/
|
||||
void push(expr_substitution* s, vector<dependent_expr> const& removed) {
|
||||
m_trail.push_back(alloc(entry, s, removed));
|
||||
m_trail_stack.push(push_back_vector(m_trail));
|
||||
}
|
||||
|
||||
/**
|
||||
* add declaration to hide
|
||||
*/
|
||||
void push(func_decl* f) {
|
||||
m_trail.push_back(alloc(entry, f));
|
||||
m_trail_stack.push(push_back_vector(m_trail));
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue