3
0
Fork 0
mirror of https://github.com/Z3Prover/z3 synced 2025-04-24 09:35:32 +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:
Nikolaj Bjorner 2022-11-12 17:56:45 -08:00
parent 689af3b4df
commit efbe0a6554
20 changed files with 1334 additions and 621 deletions

View file

@ -17,7 +17,6 @@ z3_add_component(core_tactics
pb_preprocess_tactic.cpp
propagate_values_tactic.cpp
reduce_args_tactic.cpp
reduce_invertible_tactic.cpp
simplify_tactic.cpp
solve_eqs_tactic.cpp
special_relations_tactic.cpp
@ -39,6 +38,7 @@ z3_add_component(core_tactics
dom_simplify_tactic.h
elim_term_ite_tactic.h
elim_uncnstr_tactic.h
elim_uncnstr2_tactic.h
euf_completion_tactic.h
injectivity_tactic.h
nnf_tactic.h
@ -46,7 +46,6 @@ z3_add_component(core_tactics
pb_preprocess_tactic.h
propagate_values_tactic.h
reduce_args_tactic.h
reduce_invertible_tactic.h
simplify_tactic.h
solve_eqs_tactic.h
solve_eqs2_tactic.h

View file

@ -0,0 +1,41 @@
/*++
Copyright (c) 2022 Microsoft Corporation
Module Name:
elim_unconstr2_tactic.h
Abstract:
Tactic for eliminating unconstrained terms.
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/elim_unconstrained.h"
#include "ast/simplifiers/elim_unconstrained.h"
class elim_uncnstr2_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(elim_unconstrained, m, s);
}
};
inline tactic * mk_elim_uncnstr2_tactic(ast_manager & m, params_ref const & p = params_ref()) {
return alloc(dependent_expr_state_tactic, m, p, alloc(elim_uncnstr2_tactic_factory), "elim-unconstr2");
}
/*
ADD_TACTIC("elim-uncnstr2", "eliminate unconstrained variables.", "mk_elim_uncnstr2_tactic(m, p)")
*/

View file

@ -856,9 +856,8 @@ class elim_uncnstr_tactic : public tactic {
void init_mc(bool produce_models) {
m_mc = nullptr;
if (produce_models) {
m_mc = alloc(mc, m(), "elim_uncstr");
}
if (produce_models)
m_mc = alloc(mc, m(), "elim_uncstr");
}
void init_rw(bool produce_proofs) {
@ -872,7 +871,7 @@ class elim_uncnstr_tactic : public tactic {
m_vars.reset();
collect_occs p;
p(*g, m_vars);
if (m_vars.empty() || recfun::util(m()).has_defs()) {
if (m_vars.empty() || recfun::util(m()).has_rec_defs()) {
result.push_back(g.get());
// did not increase depth since it didn't do anything.
return;

View file

@ -1,576 +0,0 @@
/*++
Copyright (c) 2018 Microsoft Corporation
Module Name:
reduce_invertible_tactic.cpp
Abstract:
Reduce invertible variables.
Author:
Nuno Lopes (nlopes) 2018-6-30
Nikolaj Bjorner (nbjorner)
Notes:
1. Walk through top-level uninterpreted constants.
--*/
#include "ast/bv_decl_plugin.h"
#include "ast/arith_decl_plugin.h"
#include "ast/ast_pp.h"
#include "ast/rewriter/expr_safe_replace.h"
#include "ast/rewriter/rewriter_def.h"
#include "ast/rewriter/th_rewriter.h"
#include "tactic/tactic.h"
#include "tactic/core/reduce_invertible_tactic.h"
#include "tactic/core/collect_occs.h"
#include "ast/converters/generic_model_converter.h"
#include <cstdint>
namespace {
class reduce_invertible_tactic : public tactic {
ast_manager& m;
bv_util m_bv;
arith_util m_arith;
public:
reduce_invertible_tactic(ast_manager & m):
m(m),
m_bv(m),
m_arith(m)
{}
char const* name() const override { return "reduce_invertible"; }
tactic * translate(ast_manager & m) override {
return alloc(reduce_invertible_tactic, m);
}
void operator()(goal_ref const & g, goal_ref_buffer & result) override {
tactic_report report("reduce-invertible", *g);
bool change = true;
while (change) {
change = false;
m_inverted.reset();
m_parents.reset();
collect_parents(g);
collect_occs occs;
obj_hashtable<expr> vars;
generic_model_converter_ref mc;
occs(*g, vars);
expr_safe_replace sub(m);
expr_ref new_v(m);
expr * p;
for (expr* v : vars) {
if (is_invertible(v, p, new_v, &mc)) {
mark_inverted(p);
sub.insert(p, new_v);
TRACE("invertible_tactic", tout << mk_pp(p, m) << " " << new_v << "\n";);
change = true;
break;
}
}
reduce_q_rw rw(*this);
unsigned sz = g->size();
for (unsigned idx = 0; !g->inconsistent() && idx < sz; idx++) {
checkpoint();
expr* f = g->form(idx);
expr_ref f_new(m);
sub(f, f_new);
rw(f_new, f_new);
if (f == f_new) continue;
proof_ref new_pr(m);
if (g->proofs_enabled()) {
proof * pr = g->pr(idx);
new_pr = m.mk_rewrite(f, f_new);
new_pr = m.mk_modus_ponens(pr, new_pr);
}
g->update(idx, f_new, new_pr, g->dep(idx));
}
if (mc) g->add(mc.get());
TRACE("invertible_tactic", g->display(tout););
g->inc_depth();
}
result.push_back(g.get());
CTRACE("invertible_tactic", g->mc(), g->mc()->display(tout););
}
void cleanup() override {}
private:
void checkpoint() {
tactic::checkpoint(m);
}
bool is_bv_neg(expr * e) {
if (m_bv.is_bv_neg(e))
return true;
expr *a, *b;
if (m_bv.is_bv_mul(e, a, b)) {
return m_bv.is_allone(a) || m_bv.is_allone(b);
}
return false;
}
expr_mark m_inverted;
void mark_inverted(expr *p) {
ptr_buffer<expr> todo;
todo.push_back(p);
while (!todo.empty()) {
p = todo.back();
todo.pop_back();
if (!m_inverted.is_marked(p)) {
m_inverted.mark(p, true);
if (is_app(p)) {
for (expr* arg : *to_app(p)) {
todo.push_back(arg);
}
}
else if (is_quantifier(p)) {
todo.push_back(to_quantifier(p)->get_expr());
}
}
}
}
// store one parent of expression, or null if multiple
struct parents {
parents(): m_p(0) {}
uintptr_t m_p;
void set(expr * e) {
SASSERT((uintptr_t)e != 1);
if (!m_p) m_p = (uintptr_t)e;
else m_p = 1;
}
expr * get() const {
return m_p == 1 ? nullptr : (expr*)m_p;
}
};
svector<parents> m_parents;
struct parent_collector {
reduce_invertible_tactic& c;
parent_collector(reduce_invertible_tactic& c):c(c) {}
void operator()(app* n) {
for (expr* arg : *n) {
c.m_parents.reserve(arg->get_id() + 1);
c.m_parents[arg->get_id()].set(n);
}
}
void operator()(var* v) {
c.m_parents.reserve(v->get_id() + 1);
}
void operator()(quantifier* q) {}
};
void collect_parents(goal_ref const& g) {
parent_collector proc(*this);
expr_fast_mark1 visited;
unsigned sz = g->size();
for (unsigned i = 0; i < sz; i++) {
checkpoint();
quick_for_each_expr(proc, visited, g->form(i));
}
}
void ensure_mc(generic_model_converter_ref* mc) {
if (mc && !(*mc)) *mc = alloc(generic_model_converter, m, "reduce-invertible");
}
bool is_full_domain_var(expr* v, rational& model) {
auto f = is_app(v) ? to_app(v)->get_decl() : nullptr;
if (!f || f->get_family_id() != m_bv.get_family_id() || f->get_arity() == 0)
return false;
switch (f->get_decl_kind()) {
case OP_BADD:
case OP_BSUB:
model = rational::zero();
return true;
case OP_BAND:
model = rational::power_of_two(m_bv.get_bv_size(v)) - rational::one();
return true;
case OP_BMUL:
model = rational::one();
return true;
case OP_BSDIV:
case OP_BSDIV0:
case OP_BSDIV_I:
case OP_BUDIV:
case OP_BUDIV0:
case OP_BUDIV_I:
default:
return false;
}
}
bool rewrite_unconstr(expr* v, expr_ref& new_v, generic_model_converter_ref* mc, unsigned max_var) {
rational mdl;
if (!is_full_domain_var(v, mdl))
return false;
rational r;
app* a = to_app(v);
expr* fst_arg = a->get_arg(0);
for (expr* arg : *a)
if (!m_parents[arg->get_id()].get())
return false;
if (is_var(fst_arg)) {
for (expr* arg : *a) {
if (!is_var(arg))
return false;
if (to_var(arg)->get_idx() >= max_var)
return false;
}
}
else {
if (!is_uninterp_const(fst_arg))
return false;
bool first = true;
for (expr* arg : *a) {
if (!is_app(arg))
return false;
if (is_uninterp_const(arg))
continue;
if (m_bv.is_numeral(arg, r) && r == mdl) {
if (first || mdl.is_zero()) {
first = false;
continue;
}
else
return false;
}
return false;
}
}
if (mc) {
ensure_mc(mc);
expr_ref num(m_bv.mk_numeral(mdl, fst_arg->get_sort()), m);
for (unsigned i = 1, n = a->get_num_args(); i != n; ++i) {
expr* arg = a->get_arg(i);
if (m_bv.is_numeral(arg))
continue;
(*mc)->add(arg, num);
}
}
new_v = fst_arg;
return true;
}
// TBD: could be made to be recursive, by walking multiple layers of parents.
bool is_invertible(expr* v, expr*& p, expr_ref& new_v, generic_model_converter_ref* mc, unsigned max_var = 0) {
rational r;
if (m_parents.size() <= v->get_id()) {
return false;
}
p = m_parents[v->get_id()].get();
if (!p || m_inverted.is_marked(p) || (mc && !is_ground(p))) {
return false;
}
if (m_bv.is_bv_xor(p) ||
m_bv.is_bv_not(p) ||
is_bv_neg(p)) {
if (mc) {
ensure_mc(mc);
(*mc)->add(v, p);
}
new_v = v;
return true;
}
if (rewrite_unconstr(p, new_v, mc, max_var))
return true;
if (m_bv.is_bv_add(p)) {
if (mc) {
ensure_mc(mc);
// if we solve for v' := v + t
// then the value for v is v' - t
expr_ref def(v, m);
for (expr* arg : *to_app(p)) {
if (arg != v) def = m_bv.mk_bv_sub(def, arg);
}
(*mc)->add(v, def);
}
new_v = v;
return true;
}
if (m_bv.is_bv_mul(p)) {
expr_ref rest(m);
for (expr* arg : *to_app(p)) {
if (arg != v) {
if (rest)
rest = m_bv.mk_bv_mul(rest, arg);
else
rest = arg;
}
}
if (!rest) return false;
// so far just support numeral
if (!m_bv.is_numeral(rest, r))
return false;
// create case split on
// divisbility of 2
// v * t ->
// if t = 0, set v' := 0 and the solution for v is 0.
// otherwise,
// let i be the position of the least bit of t set to 1
// then extract[sz-1:i](v) ++ zero[i-1:0] is the invertible of v * t
// thus
// extract[i+1:0](t) = 1 ++ zero[i-1:0] -> extract[sz-1:i](v) ++ zero[i-1:0]
// to reproduce the original v from t
// solve for v*t = extract[sz-1:i](v') ++ zero[i-1:0]
// using values for t and v'
// thus let t' = t / 2^i
// and t'' = the multiplicative inverse of t'
// then t'' * v' * t = t'' * v' * t' * 2^i = v' * 2^i = extract[sz-1:i](v') ++ zero[i-1:0]
// so t'' *v' works
//
unsigned sz = m_bv.get_bv_size(p);
expr_ref bit1(m_bv.mk_numeral(1, 1), m);
unsigned sh = 0;
while (r.is_pos() && r.is_even()) {
r /= rational(2);
++sh;
}
if (r.is_pos() && sh > 0)
new_v = m_bv.mk_concat(m_bv.mk_extract(sz-sh-1, 0, v), m_bv.mk_numeral(0, sh));
else
new_v = v;
if (mc && !r.is_zero()) {
ensure_mc(mc);
expr_ref def(m);
rational inv_r;
VERIFY(r.mult_inverse(sz, inv_r));
def = m_bv.mk_bv_mul(m_bv.mk_numeral(inv_r, sz), v);
(*mc)->add(v, def);
TRACE("invertible_tactic", tout << def << "\n";);
}
return true;
}
if (m_bv.is_bv_sub(p)) {
// TBD
}
if (m_bv.is_bv_udivi(p)) {
// TBD
}
// sdivi, sremi, uremi, smodi
// TBD
if (m_arith.is_mul(p) && m_arith.is_real(p)) {
expr_ref rest(m);
for (expr* arg : *to_app(p)) {
if (arg != v) {
if (rest)
rest = m_arith.mk_mul(rest, arg);
else
rest = arg;
}
}
if (!rest) return false;
if (!m_arith.is_numeral(rest, r) || r.is_zero())
return false;
expr_ref zero(m_arith.mk_real(0), m);
new_v = m.mk_ite(m.mk_eq(zero, rest), zero, v);
if (mc) {
ensure_mc(mc);
expr_ref def(m_arith.mk_div(v, rest), m);
(*mc)->add(v, def);
}
return true;
}
expr* e1 = nullptr, *e2 = nullptr;
// v / t unless t != 0
if (m_arith.is_div(p, e1, e2) && e1 == v && m_arith.is_numeral(e2, r) && !r.is_zero()) {
new_v = v;
if (mc) {
ensure_mc(mc);
(*mc)->add(v, m_arith.mk_mul(e1, e2));
}
return true;
}
if (m.is_eq(p, e1, e2)) {
TRACE("invertible_tactic", tout << mk_pp(v, m) << "\n";);
if (mc && has_diagonal(e1)) {
ensure_mc(mc);
new_v = m.mk_fresh_const("eq", m.mk_bool_sort());
SASSERT(v == e1 || v == e2);
expr* other = (v == e1) ? e2 : e1;
(*mc)->hide(new_v);
(*mc)->add(v, m.mk_ite(new_v, other, mk_diagonal(other)));
return true;
}
else if (mc) {
// diagonal functions for other types depend on theory.
return false;
}
else if (is_var(v) && is_non_singleton_sort(v->get_sort())) {
new_v = m.mk_var(to_var(v)->get_idx(), m.mk_bool_sort());
return true;
}
}
//
// v <= u
// => u + 1 == 0 or delta
// v := delta ? u : u + 1
//
if (m_bv.is_bv_ule(p, e1, e2) && e1 == v && mc) {
ensure_mc(mc);
unsigned sz = m_bv.get_bv_size(e2);
expr_ref delta(m.mk_fresh_const("ule", m.mk_bool_sort()), m);
expr_ref succ_e2(m_bv.mk_bv_add(e2, m_bv.mk_numeral(1, sz)), m);
new_v = m.mk_or(delta, m.mk_eq(succ_e2, m_bv.mk_numeral(0, sz)));
(*mc)->hide(delta);
(*mc)->add(v, m.mk_ite(delta, e2, succ_e2));
return true;
}
//
// u <= v
// => u == 0 or delta
// v := delta ? u : u - 1
//
if (m_bv.is_bv_ule(p, e1, e2) && e2 == v && mc) {
ensure_mc(mc);
unsigned sz = m_bv.get_bv_size(e1);
expr_ref delta(m.mk_fresh_const("ule", m.mk_bool_sort()), m);
expr_ref pred_e1(m_bv.mk_bv_sub(e1, m_bv.mk_numeral(1, sz)), m);
new_v = m.mk_or(delta, m.mk_eq(e1, m_bv.mk_numeral(0, sz)));
(*mc)->hide(delta);
(*mc)->add(v, m.mk_ite(delta, e1, pred_e1));
return true;
}
return false;
}
bool has_diagonal(expr* e) {
return
m_bv.is_bv(e) ||
m.is_bool(e) ||
m_arith.is_int_real(e);
}
expr * mk_diagonal(expr* e) {
if (m_bv.is_bv(e)) return m_bv.mk_bv_not(e);
if (m.is_bool(e)) return m.mk_not(e);
if (m_arith.is_int(e)) return m_arith.mk_add(m_arith.mk_int(1), e);
if (m_arith.is_real(e)) return m_arith.mk_add(m_arith.mk_real(1), e);
UNREACHABLE();
return e;
}
bool is_non_singleton_sort(sort* s) {
if (m.is_uninterp(s)) return false;
sort_size sz = s->get_num_elements();
if (sz.is_finite() && sz.size() == 1) return false;
return true;
}
struct reduce_q_rw_cfg : public default_rewriter_cfg {
ast_manager& m;
reduce_invertible_tactic& t;
reduce_q_rw_cfg(reduce_invertible_tactic& t): m(t.m), t(t) {}
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) {
if (is_lambda(old_q)) return false;
if (has_quantifiers(new_body)) return false;
ref_buffer<var, ast_manager> vars(m);
ptr_buffer<sort> new_sorts;
unsigned n = old_q->get_num_decls();
for (unsigned i = 0; i < n; ++i) {
sort* srt = old_q->get_decl_sort(i);
vars.push_back(m.mk_var(n - i - 1, srt));
new_sorts.push_back(srt);
}
// for each variable, collect parents,
// ensure they are in unique location and not under other quantifiers.
// if they are invertible, then produce inverting expression.
//
expr_safe_replace sub(m);
t.m_parents.reset();
t.m_inverted.reset();
expr_ref new_v(m);
expr * p;
{
parent_collector proc(t);
expr_fast_mark1 visited;
quick_for_each_expr(proc, visited, new_body);
}
bool has_new_var = false;
for (unsigned i = 0; i < vars.size(); ++i) {
var* v = vars[i];
if (!occurs_under_nested_q(v, new_body) && t.is_invertible(v, p, new_v, nullptr, vars.size())) {
TRACE("invertible_tactic", tout << mk_pp(v, m) << " " << mk_pp(p, m) << "\n";);
t.mark_inverted(p);
sub.insert(p, new_v);
new_sorts[i] = new_v->get_sort();
has_new_var |= new_v != v;
}
}
if (has_new_var) {
sub(new_body, result);
result = m.mk_quantifier(old_q->get_kind(), new_sorts.size(), new_sorts.data(), old_q->get_decl_names(), result, old_q->get_weight());
result_pr = nullptr;
return true;
}
if (!sub.empty()) {
sub(new_body, result);
result = m.update_quantifier(old_q, old_q->get_num_patterns(), new_patterns, old_q->get_num_no_patterns(), new_no_patterns, result);
result_pr = nullptr;
return true;
}
return false;
}
bool occurs_under_nested_q(var* v, expr* body) {
return has_quantifiers(body);
}
};
struct reduce_q_rw : rewriter_tpl<reduce_q_rw_cfg> {
reduce_q_rw_cfg m_cfg;
public:
reduce_q_rw(reduce_invertible_tactic& t):
rewriter_tpl<reduce_q_rw_cfg>(t.m, false, m_cfg),
m_cfg(t) {}
};
};
}
tactic * mk_reduce_invertible_tactic(ast_manager & m, params_ref const &) {
return alloc(reduce_invertible_tactic, m);
}

View file

@ -1,32 +0,0 @@
/*++
Copyright (c) 2018 Microsoft Corporation
Module Name:
reduce_invertible_tactic.h
Abstract:
Reduce invertible variables.
Author:
Nuno Lopes (nlopes) 2018-6-30
Nikolaj Bjorner (nbjorner)
Notes:
--*/
#pragma once
#include "util/params.h"
class tactic;
class ast_manager;
tactic * mk_reduce_invertible_tactic(ast_manager & m, params_ref const & p = params_ref());
/*
ADD_TACTIC("reduce-invertible", "reduce invertible variable occurrences.", "mk_reduce_invertible_tactic(m, p)")
*/

View file

@ -15,6 +15,7 @@ Author:
Nikolaj Bjorner (nbjorner) 2022-11-2.
--*/
#pragma once
#include "tactic/tactic.h"
#include "ast/simplifiers/dependent_expr_state.h"