3
0
Fork 0
mirror of https://github.com/Z3Prover/z3 synced 2025-08-07 19:51:22 +00:00
z3/src/ast/simplifiers/bound_simplifier.cpp
Nikolaj Bjorner 2ad9f220f2 add logging
2024-04-30 16:57:59 -07:00

648 lines
20 KiB
C++

/*++
Copyright (c) 2022 Microsoft Corporation
Module Name:
bound_simplifier.cpp
Author:
Nikolaj Bjorner (nbjorner) 2023-01-22
Description:
Extract bounds for sub-expressions and use the bounds for propagation and simplification.
It applies the simplificaitons from the bounds_propagator and it applies nested rewriting
of sub-expressions based on bounds information. Initially, rewriting amounts to eliminating
occurrences of mod N.
From the description of propagate_ineqs_tactic:
- Propagate bounds using the bound_propagator.
- Eliminate subsumed inequalities.
For example:
x - y >= 3
can be replaced with true if we know that
x >= 3 and y <= 0
- Convert inequalities of the form p <= k and p >= k into p = k,
where p is a polynomial and k is a constant.
This strategy assumes the input is in arith LHS mode.
This can be achieved by using option :arith-lhs true in the
simplifier.
--*/
#include "ast/ast_pp.h"
#include "ast/simplifiers/bound_simplifier.h"
#include "ast/rewriter/rewriter_def.h"
struct bound_simplifier::rw_cfg : public default_rewriter_cfg {
bound_simplifier& s;
rw_cfg(bound_simplifier& s): s(s) {}
br_status reduce_app(func_decl* f, unsigned num_args, expr * const* args, expr_ref& result, proof_ref& pr) {
return s.reduce_app(f, num_args, args, result, pr);
}
};
struct bound_simplifier::rw : public rewriter_tpl<rw_cfg> {
rw_cfg m_cfg;
rw(bound_simplifier& s):
rewriter_tpl<rw_cfg>(s.m, false, m_cfg),
m_cfg(s) {
}
};
br_status bound_simplifier::reduce_app(func_decl* f, unsigned num_args, expr* const* args, expr_ref& result, proof_ref& pr) {
rational N, hi, lo;
if (a.is_mod(f) && num_args == 2 && a.is_numeral(args[1], N)) {
auto& im = m_interval;
scoped_dep_interval i(im);
expr* x = args[0];
get_bounds(x, i);
if (im.upper_is_inf(i) || im.lower_is_inf(i))
return BR_FAILED;
if (im.upper_is_open(i) || im.lower_is_open(i))
return BR_FAILED;
lo = im.lower(i);
hi = im.upper(i);
if (hi - lo >= N)
return BR_FAILED;
if (N > hi && lo >= 0) {
result = x;
TRACE("propagate-ineqs", tout << expr_ref(m.mk_app(f, num_args, args), m) << " -> " << result << "\n");
return BR_DONE;
}
if (2 * N > hi && lo >= N) {
result = a.mk_sub(x, a.mk_int(N));
m_rewriter(result);
TRACE("propagate-ineqs", tout << expr_ref(m.mk_app(f, num_args, args), m) << " -> " << result << "\n");
return BR_DONE;
}
IF_VERBOSE(2, verbose_stream() << "potentially missed simplification: " << mk_pp(x, m) << " " << lo << " " << hi << " not reduced\n");
}
expr_ref_buffer new_args(m);
expr_ref new_arg(m);
bool change = false;
for (unsigned i = 0; i < num_args; ++i) {
expr* arg = args[i];
change = reduce_arg(arg, new_arg) || change;
new_args.push_back(new_arg);
}
if (!change)
return BR_FAILED;
result = m.mk_app(f, num_args, new_args.data());
return BR_DONE;
}
bool bound_simplifier::reduce_arg(expr* arg, expr_ref& result) {
result = arg;
expr* x, *y;
rational N, lo, hi;
bool strict;
if ((a.is_le(arg, x, y) && a.is_numeral(y, N)) ||
(a.is_ge(arg, y, x) && a.is_numeral(y, N))) {
if (has_upper(x, hi, strict) && !strict && N >= hi) {
result = m.mk_true();
return true;
}
if (has_lower(x, lo, strict) && !strict && N < lo) {
result = m.mk_false();
return true;
}
return false;
}
if ((a.is_le(arg, y, x) && a.is_numeral(y, N)) ||
(a.is_ge(arg, x, y) && a.is_numeral(y, N))) {
if (has_lower(x, lo, strict) && !strict && N <= lo) {
result = m.mk_true();
return true;
}
if (has_upper(x, hi, strict) && !strict && N > hi) {
result = m.mk_false();
return true;
}
return false;
}
return false;
}
void bound_simplifier::reduce() {
bool updated = true, found_bound = false;
for (unsigned i = 0; i < 5 && updated; ++i) {
updated = false;
found_bound = false;
reset();
for (unsigned idx : indices()) {
if (insert_bound(m_fmls[idx])) {
m_fmls.update(idx, dependent_expr(m, m.mk_true(), nullptr, nullptr));
found_bound = true;
}
}
if (!found_bound)
break;
for (unsigned idx : indices())
tighten_bound(m_fmls[idx]);
bp.propagate();
proof_ref pr(m);
expr_ref r(m);
rw rw(*this);
for (unsigned idx : indices()) {
auto const& d = m_fmls[idx];
if (d.pr())
continue;
rw(d.fml(), r, pr);
if (r != d.fml()) {
m_fmls.update(idx, dependent_expr(m, r, mp(d.pr(), pr), d.dep()));
++m_num_reduced;
updated = true;
}
}
restore_bounds();
}
}
// generalization to summations?
bool bound_simplifier::is_offset(expr* e, expr* x, rational& n) {
expr* y, *z;
if (a.is_add(e, y, z)) {
if (x != y)
std::swap(y, z);
return x == y && a.is_numeral(z, n);
}
return false;
}
bool bound_simplifier::insert_bound(dependent_expr const& de) {
if (de.pr())
return false;
if (de.dep())
return false;
rational n, n0;
expr* x, *y, *f = de.fml();
if (m.is_eq(f, x, y)) {
if (a.is_numeral(y))
std::swap(x, y);
if (a.is_numeral(x, n)) {
assert_lower(y, n, false);
assert_upper(y, n, false);
return true;
}
}
else if (a.is_le(f, x, y)) {
if (a.is_numeral(x, n))
assert_lower(y, n, false);
else if (a.is_numeral(y, n))
assert_upper(x, n, false);
else
return false;
return true;
}
else if (a.is_ge(f, x, y)) {
if (a.is_numeral(x, n))
assert_upper(y, n, false);
else if (a.is_numeral(y, n))
assert_lower(x, n, false);
else
return false;
return true;
}
else if (m.is_not(f, f)) {
if (a.is_le(f, x, y)) {
if (a.is_numeral(x, n))
assert_upper(y, n, true);
else if (a.is_numeral(y, n))
assert_lower(x, n, true);
else
return false;
return true;
}
else if (a.is_ge(f, x, y)) {
if (a.is_numeral(x, n))
assert_lower(y, n, true);
else if (a.is_numeral(y, n))
assert_upper(x, n, true);
else
return false;
return true;
}
}
return false;
}
void bound_simplifier::tighten_bound(dependent_expr const& de) {
if (de.pr())
return;
if (de.dep())
return;
rational n, k;
expr* x, *y, *f = de.fml();
expr* z, * u, * v, * w;
bool strict;
if (a.is_le(f, x, y)) {
// x <= (x + k) mod N && x >= 0 -> x + k < N
if (a.is_mod(y, z, u) && a.is_numeral(u, n) && has_lower(x, k, strict) && k >= 0 && is_offset(z, x, k) && k > 0 && k < n)
assert_upper(x, n - k, true);
// x <= (x + y) mod N && x >= 0 && 0 <= y < N => x + y < N
if (a.is_mod(y, z, u) && a.is_numeral(u, n) && n > 0) {
assert_upper(x, n, true);
if (has_lower(x, k, strict) && k >= 0 && a.is_add(z, v, w)) {
if (x == v && has_upper(w, k, strict) && k < n)
assert_upper(z, n, true);
if (x == w && has_upper(v, k, strict) && k < n)
assert_upper(z, n, true);
}
}
}
// x != k, k <= x -> k < x
if (m.is_not(f, f) && m.is_eq(f, x, y)) {
if (a.is_numeral(x))
std::swap(x, y);
if (a.is_numeral(y, n)) {
scoped_dep_interval i(m_interval);
get_bounds(x, i);
scoped_mpq k(nm);
if (!i.m().lower_is_inf(i) && !i.m().lower_is_open(i) && i.m().lower(i) == n)
assert_lower(x, n, true);
else if (!i.m().upper_is_inf(i) && !i.m().upper_is_open(i) && i.m().upper(i) == n)
assert_upper(x, n, true);
}
}
}
void bound_simplifier::assert_upper(expr* x, rational const& n, bool strict) {
scoped_mpq c(nm);
nm.set(c, n.to_mpq());
TRACE("propagate-ineqs", tout << to_var(x) << ": " << mk_pp(x, m) << (strict ? " < " : " <= ") << n << "\n");
bp.assert_upper(to_var(x), c, strict);
}
void bound_simplifier::assert_lower(expr* x, rational const& n, bool strict) {
scoped_mpq c(nm);
nm.set(c, n.to_mpq());
TRACE("propagate-ineqs", tout << to_var(x) << ": " << mk_pp(x, m) << (strict ? " > " : " >= ") << n << "\n");
bp.assert_lower(to_var(x), c, strict);
}
bool bound_simplifier::has_lower(expr* x, rational& n, bool& strict) {
scoped_dep_interval i(m_interval);
get_bounds(x, i);
if (m_interval.lower_is_inf(i))
return false;
strict = m_interval.lower_is_open(i);
n = m_interval.lower(i);
TRACE("propagate-ineqs", tout << to_var(x) << ": " << mk_pp(x, m) << (strict ? " > " : " >= ") << n << "\n");
return true;
}
bool bound_simplifier::has_upper(expr* x, rational& n, bool& strict) {
scoped_dep_interval i(m_interval);
get_bounds(x, i);
if (m_interval.upper_is_inf(i))
return false;
strict = m_interval.upper_is_open(i);
n = m_interval.upper(i);
TRACE("propagate-ineqs", tout << to_var(x) << ": " << mk_pp(x, m) << (strict ? " < " : " <= ") << n << "\n");
return true;
}
void bound_simplifier::get_bounds(expr* x, scoped_dep_interval& i) {
auto& im = m_interval;
im.reset(i);
scoped_dep_interval arg_i(im);
rational n;
if (a.is_numeral(x, n)) {
im.set_value(i, n);
return;
}
if (is_var(x)) {
unsigned v = to_var(x);
bool strict;
if (bp.has_upper(v)) {
im.set_upper(i, bp.upper(v, strict));
im.set_upper_is_inf(i, false);
im.set_upper_is_open(i, strict);
}
if (bp.has_lower(v)) {
im.set_lower(i, bp.lower(v, strict));
im.set_lower_is_inf(i, false);
im.set_lower_is_open(i, strict);
}
}
if (a.is_add(x)) {
scoped_dep_interval tmp_i(im), sum_i(im);
im.set_value(sum_i, rational::zero());
for (expr* arg : *to_app(x)) {
get_bounds(arg, arg_i);
im.add(sum_i, arg_i, tmp_i);
im.set<dep_intervals::without_deps>(sum_i, tmp_i);
}
im.intersect <dep_intervals::without_deps>(i, sum_i, i);
}
if (a.is_mul(x)) {
scoped_dep_interval tmp_i(im);
im.set_value(tmp_i, rational::one());
for (expr* arg : *to_app(x)) {
get_bounds(arg, arg_i);
im.mul(tmp_i, arg_i, tmp_i);
}
im.intersect <dep_intervals::without_deps>(i, tmp_i, i);
}
expr* y, * z, * u, * v;
if (a.is_mod(x, y, z) && a.is_numeral(z, n) && n > 0) {
scoped_dep_interval tmp_i(im);
im.set_lower_is_inf(tmp_i, false);
im.set_lower_is_open(tmp_i, false);
im.set_lower(tmp_i, mpq(0));
im.set_upper_is_inf(tmp_i, false);
im.set_upper_is_open(tmp_i, false);
im.set_upper(tmp_i, n - 1);
im.intersect <dep_intervals::without_deps>(i, tmp_i, i);
}
// x = y*(u div y), y > 0 -> x <= u
if (a.is_mul(x, y, z) && a.is_idiv(z, u, v) && v == y) {
scoped_dep_interval iy(im), iu(im), tmp_i(im);
get_bounds(y, iy);
get_bounds(u, iu);
if (!im.lower_is_inf(iy) && im.lower(iy) > 0 &&
!im.upper_is_inf(iu) && im.upper(iu) >= 0) {
im.set_upper_is_inf(tmp_i, false);
im.set_upper_is_open(tmp_i, im.upper_is_open(iu));
im.set_upper(tmp_i, im.upper(iu));
im.intersect<dep_intervals::without_deps>(i, tmp_i, i);
}
}
// x = y div z, z > 0 => x <= y
if (a.is_idiv(x, y, z)) {
scoped_dep_interval iy(im), iz(im), tmp_i(im);
get_bounds(y, iy);
get_bounds(z, iz);
if (!im.lower_is_inf(iz) && im.lower(iz) > 0 &&
!im.upper_is_inf(iy) && im.upper(iy) >= 0) {
im.set_upper_is_inf(tmp_i, false);
im.set_upper_is_open(tmp_i, im.upper_is_open(iy));
im.set_upper(tmp_i, im.upper(iy));
im.set_lower_is_inf(tmp_i, false);
im.set_lower_is_open(tmp_i, false); // TODO - could be refined
im.set_lower(tmp_i, rational::zero());
im.intersect<dep_intervals::without_deps>(i, tmp_i, i);
}
}
if (a.is_div(x, y, z)) {
scoped_dep_interval iy(im), iz(im), tmp_i(im);
get_bounds(y, iy);
get_bounds(z, iz);
im.div<dep_intervals::without_deps>(iy, iz, tmp_i);
im.intersect<dep_intervals::without_deps>(i, tmp_i, i);
}
}
void bound_simplifier::expr2linear_pol(expr* t, mpq_buffer& as, var_buffer& xs) {
scoped_mpq c_mpq_val(nm);
if (a.is_add(t)) {
rational c_val;
for (expr* mon : *to_app(t)) {
expr* c, * x;
if (a.is_mul(mon, c, x) && a.is_numeral(c, c_val)) {
nm.set(c_mpq_val, c_val.to_mpq());
as.push_back(c_mpq_val);
xs.push_back(to_var(x));
}
else {
as.push_back(mpq(1));
xs.push_back(to_var(mon));
}
}
}
else {
as.push_back(mpq(1));
xs.push_back(to_var(t));
}
}
bool bound_simplifier::lower_subsumed(expr* p, mpq const& k, bool strict) {
if (!a.is_add(p))
return false;
m_num_buffer.reset();
m_var_buffer.reset();
expr2linear_pol(p, m_num_buffer, m_var_buffer);
scoped_mpq implied_k(nm);
bool implied_strict;
return
bp.lower(m_var_buffer.size(), m_num_buffer.data(), m_var_buffer.data(), implied_k, implied_strict) &&
(nm.gt(implied_k, k) || (nm.eq(implied_k, k) && (!strict || implied_strict)));
}
bool bound_simplifier::upper_subsumed(expr* p, mpq const& k, bool strict) {
if (!a.is_add(p))
return false;
m_num_buffer.reset();
m_var_buffer.reset();
expr2linear_pol(p, m_num_buffer, m_var_buffer);
scoped_mpq implied_k(nm);
bool implied_strict;
return
bp.upper(m_var_buffer.size(), m_num_buffer.data(), m_var_buffer.data(), implied_k, implied_strict) &&
(nm.lt(implied_k, k) || (nm.eq(implied_k, k) && (!strict || implied_strict)));
}
void bound_simplifier::restore_bounds() {
scoped_mpq l(nm), u(nm);
bool strict_l, strict_u, has_l, has_u;
unsigned ts;
unsigned sz = m_var2expr.size();
rw rw(*this);
auto add = [&](expr* fml) {
expr_ref tmp(fml, m);
rw(tmp, tmp);
m_rewriter(tmp);
m_fmls.add(dependent_expr(m, tmp, nullptr, nullptr));
};
for (unsigned x = 0; x < sz; x++) {
expr* p = m_var2expr.get(x);
has_l = bp.lower(x, l, strict_l, ts);
has_u = bp.upper(x, u, strict_u, ts);
if (!has_l && !has_u)
continue;
if (has_l && has_u && nm.eq(l, u) && !strict_l && !strict_u) {
// l <= p <= l --> p = l
add(m.mk_eq(p, a.mk_numeral(rational(l), a.is_int(p))));
continue;
}
if (has_l && !lower_subsumed(p, l, strict_l)) {
if (strict_l)
add(m.mk_not(a.mk_le(p, a.mk_numeral(rational(l), a.is_int(p)))));
else
add(a.mk_ge(p, a.mk_numeral(rational(l), a.is_int(p))));
}
if (has_u && !upper_subsumed(p, u, strict_u)) {
if (strict_u)
add(m.mk_not(a.mk_ge(p, a.mk_numeral(rational(u), a.is_int(p)))));
else
add(a.mk_le(p, a.mk_numeral(rational(u), a.is_int(p))));
}
}
}
void bound_simplifier::reset() {
bp.reset();
m_var2expr.reset();
m_expr2var.reset();
m_trail.reset();
}
#if 0
void find_ite_bounds(expr* root) {
TRACE("find_ite_bounds_bug", display_bounds(tout););
expr* n = root;
expr* target = nullptr;
expr* c, * t, * e;
expr* x, * y;
bool has_l, has_u;
mpq l_min, u_max;
bool l_strict, u_strict;
mpq curr;
bool curr_strict;
while (true) {
TRACE("find_ite_bounds_bug", tout << mk_ismt2_pp(n, m) << "\n";);
if (m.is_ite(n, c, t, e)) {
if (is_x_minus_y_eq_0(t, x, y))
n = e;
else if (is_x_minus_y_eq_0(e, x, y))
n = t;
else
break;
}
else if (is_x_minus_y_eq_0(n, x, y)) {
n = nullptr;
}
else {
break;
}
TRACE("find_ite_bounds_bug", tout << "x: " << mk_ismt2_pp(x, m) << ", y: " << mk_ismt2_pp(y, m) << "\n";
if (target) {
tout << "target: " << mk_ismt2_pp(target, m) << "\n";
tout << "has_l: " << has_l << " " << nm.to_string(l_min) << " has_u: " << has_u << " " << nm.to_string(u_max) << "\n";
});
if (is_unbounded(y))
std::swap(x, y);
if (!is_unbounded(x)) {
TRACE("find_ite_bounds_bug", tout << "x is already bounded\n";);
break;
}
if (target == nullptr) {
target = x;
if (lower(y, curr, curr_strict)) {
has_l = true;
nm.set(l_min, curr);
l_strict = curr_strict;
}
else {
has_l = false;
TRACE("find_ite_bounds_bug", tout << "y does not have lower\n";);
}
if (upper(y, curr, curr_strict)) {
has_u = true;
nm.set(u_max, curr);
u_strict = curr_strict;
}
else {
has_u = false;
TRACE("find_ite_bounds_bug", tout << "y does not have upper\n";);
}
}
else if (target == x) {
if (has_l) {
if (lower(y, curr, curr_strict)) {
if (nm.lt(curr, l_min) || (!curr_strict && l_strict && nm.eq(curr, l_min))) {
nm.set(l_min, curr);
l_strict = curr_strict;
}
}
else {
has_l = false;
TRACE("find_ite_bounds_bug", tout << "y does not have lower\n";);
}
}
if (has_u) {
if (upper(y, curr, curr_strict)) {
if (nm.gt(curr, u_max) || (curr_strict && !u_strict && nm.eq(curr, u_max))) {
nm.set(u_max, curr);
u_strict = curr_strict;
}
}
else {
has_u = false;
TRACE("find_ite_bounds_bug", tout << "y does not have upper\n";);
}
}
}
else {
break;
}
if (!has_l && !has_u)
break;
if (n == nullptr) {
TRACE("find_ite_bounds", tout << "found bounds for: " << mk_ismt2_pp(target, m) << "\n";
tout << "has_l: " << has_l << " " << nm.to_string(l_min) << " l_strict: " << l_strict << "\n";
tout << "has_u: " << has_u << " " << nm.to_string(u_max) << " u_strict: " << u_strict << "\n";
tout << "root:\n" << mk_ismt2_pp(root, m) << "\n";);
a_var x = mk_var(target);
if (has_l)
bp.assert_lower(x, l_min, l_strict);
if (has_u)
bp.assert_upper(x, u_max, u_strict);
break;
}
}
nm.del(l_min);
nm.del(u_max);
nm.del(curr);
}
void find_ite_bounds() {
unsigned sz = m_new_goal->size();
for (unsigned i = 0; i < sz; i++) {
expr* f = m_new_goal->form(i);
if (m.is_ite(f))
find_ite_bounds(to_app(f));
}
bp.propagate();
TRACE("find_ite_bounds", display_bounds(tout););
}
#endif