3
0
Fork 0
mirror of https://github.com/Z3Prover/z3 synced 2025-04-24 09:35:32 +00:00

use intervals for tracking bounds on arithmetic variables

leverage interval propagation for bounds.
merge functionality with propagate-ineqs tactic
remove the new propagate-bounds tactic and instead use propagate-ineqs
This commit is contained in:
Nikolaj Bjorner 2023-01-23 14:13:03 -08:00
parent eb751bec4c
commit d9f9cceea4
7 changed files with 444 additions and 764 deletions

View file

@ -17,7 +17,6 @@ z3_add_component(arith_tactics
pb2bv_model_converter.cpp
pb2bv_tactic.cpp
probe_arith.cpp
propagate_ineqs_tactic.cpp
purify_arith_tactic.cpp
recover_01_tactic.cpp
COMPONENT_DEPENDENCIES
@ -25,7 +24,6 @@ z3_add_component(arith_tactics
sat
TACTIC_HEADERS
add_bounds_tactic.h
bound_simplifier_tactic.h
card2bv_tactic.h
degree_shift_tactic.h
diff_neq_tactic.h

View file

@ -1,575 +0,0 @@
/*++
Copyright (c) 2012 Microsoft Corporation
Module Name:
propagate_ineqs_tactic.h
Abstract:
This tactic performs the following tasks:
- 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.
Author:
Leonardo (leonardo) 2012-02-19
Notes:
--*/
#include "tactic/tactical.h"
#include "ast/simplifiers/bound_propagator.h"
#include "ast/arith_decl_plugin.h"
#include "tactic/core/simplify_tactic.h"
#include "ast/ast_smt2_pp.h"
class propagate_ineqs_tactic : public tactic {
struct imp;
imp * m_imp;
params_ref m_params;
public:
propagate_ineqs_tactic(ast_manager & m, params_ref const & p);
tactic * translate(ast_manager & m) override {
return alloc(propagate_ineqs_tactic, m, m_params);
}
~propagate_ineqs_tactic() override;
char const* name() const override { return "propagate_ineqs"; }
void updt_params(params_ref const & p) override;
void collect_param_descrs(param_descrs & r) override {}
void operator()(goal_ref const & g, goal_ref_buffer & result) override;
void cleanup() override;
};
tactic * mk_propagate_ineqs_tactic(ast_manager & m, params_ref const & p) {
return clean(alloc(propagate_ineqs_tactic, m, p));
}
struct propagate_ineqs_tactic::imp {
ast_manager & m;
unsynch_mpq_manager nm;
small_object_allocator m_allocator;
bound_propagator bp;
arith_util m_util;
typedef bound_propagator::var a_var;
obj_map<expr, a_var> m_expr2var;
expr_ref_vector m_var2expr;
typedef numeral_buffer<mpq, unsynch_mpq_manager> mpq_buffer;
typedef svector<a_var> var_buffer;
mpq_buffer m_num_buffer;
var_buffer m_var_buffer;
goal_ref m_new_goal;
imp(ast_manager & _m, params_ref const & p):
m(_m),
m_allocator("ineq-simplifier"),
bp(nm, m_allocator, p),
m_util(m),
m_var2expr(m),
m_num_buffer(nm) {
updt_params_core(p);
}
void updt_params_core(params_ref const & p) {
}
void updt_params(params_ref const & p) {
updt_params_core(p);
bp.updt_params(p);
}
void display_bounds(std::ostream & out) {
unsigned sz = m_var2expr.size();
mpq k;
bool strict;
unsigned ts;
for (unsigned x = 0; x < sz; x++) {
if (bp.lower(x, k, strict, ts))
out << nm.to_string(k) << " " << (strict ? "<" : "<=");
else
out << "-oo <";
out << " " << mk_ismt2_pp(m_var2expr.get(x), m) << " ";
if (bp.upper(x, k, strict, ts))
out << (strict ? "<" : "<=") << " " << nm.to_string(k);
else
out << "< oo";
out << "\n";
}
nm.del(k);
}
a_var mk_var(expr * t) {
if (m_util.is_to_real(t))
t = to_app(t)->get_arg(0);
a_var x;
if (m_expr2var.find(t, x))
return x;
x = m_var2expr.size();
bp.mk_var(x, m_util.is_int(t));
m_var2expr.push_back(t);
m_expr2var.insert(t, x);
return x;
}
void expr2linear_pol(expr * t, mpq_buffer & as, var_buffer & xs) {
mpq c_mpq_val;
if (m_util.is_add(t)) {
rational c_val;
for (expr* mon : *to_app(t)) {
expr * c, * x;
if (m_util.is_mul(mon, c, x) && m_util.is_numeral(c, c_val)) {
nm.set(c_mpq_val, c_val.to_mpq());
as.push_back(c_mpq_val);
xs.push_back(mk_var(x));
}
else {
as.push_back(mpq(1));
xs.push_back(mk_var(mon));
}
}
}
else {
as.push_back(mpq(1));
xs.push_back(mk_var(t));
}
nm.del(c_mpq_val);
}
a_var mk_linear_pol(expr * t) {
a_var x;
if (m_expr2var.find(t, x))
return x;
x = mk_var(t);
if (m_util.is_add(t)) {
m_num_buffer.reset();
m_var_buffer.reset();
expr2linear_pol(t, m_num_buffer, m_var_buffer);
m_num_buffer.push_back(mpq(-1));
m_var_buffer.push_back(x);
bp.mk_eq(m_num_buffer.size(), m_num_buffer.data(), m_var_buffer.data());
}
return x;
}
enum kind { EQ, LE, GE };
bool process(expr * t) {
bool sign = false;
while (m.is_not(t, t))
sign = !sign;
bool strict = false;
kind k;
if (m.is_eq(t)) {
if (sign)
return false;
k = EQ;
}
else if (m_util.is_le(t)) {
if (sign) {
k = GE;
strict = true;
}
else {
k = LE;
}
}
else if (m_util.is_ge(t)) {
if (sign) {
k = LE;
strict = true;
}
else {
k = GE;
}
}
else if (m_util.is_lt(t)) {
if (sign) {
k = GE;
strict = false;
} else {
k = LE;
strict = true;
}
}
else if (m_util.is_gt(t)) {
//x > y == x <=y, strict = false
if (sign) {
k = LE;
strict = false;
} else {
k = GE;
strict = true;
}
}
else
return false;
expr * lhs = to_app(t)->get_arg(0);
expr * rhs = to_app(t)->get_arg(1);
expr* a, *b;
if (m_util.is_numeral(lhs)) {
std::swap(lhs, rhs);
if (k == LE)
k = GE;
else if (k == GE)
k = LE;
}
rational c;
// x = y mod c => 0 <= x < c
if (k == EQ && m_util.is_mod(rhs, a, b) && m_util.is_numeral(b, c) && c > 0) {
a_var x = mk_linear_pol(lhs);
mpq c_prime;
nm.set(c_prime, (c-1).to_mpq());
bp.assert_lower(x, mpq(0), false);
bp.assert_upper(x, c_prime, false);
nm.del(c_prime);
return lhs == a;
}
if (!m_util.is_numeral(rhs, c))
return false;
a_var x = mk_linear_pol(lhs);
mpq c_prime;
nm.set(c_prime, c.to_mpq());
if (k == EQ) {
SASSERT(!strict);
bp.assert_lower(x, c_prime, false);
bp.assert_upper(x, c_prime, false);
}
else if (k == LE) {
bp.assert_upper(x, c_prime, strict);
}
else {
SASSERT(k == GE);
bp.assert_lower(x, c_prime, strict);
}
nm.del(c_prime);
return true;
}
bool collect_bounds(goal const & g) {
bool found = false;
unsigned sz = g.size();
for (unsigned i = 0; i < sz; i++) {
expr * t = g.form(i);
if (process(t))
found = true;
else
m_new_goal->assert_expr(t); // save non-bounds here
}
return found;
}
bool lower_subsumed(expr * p, mpq const & k, bool strict) {
if (!m_util.is_add(p))
return false;
m_num_buffer.reset();
m_var_buffer.reset();
expr2linear_pol(p, m_num_buffer, m_var_buffer);
mpq implied_k;
bool implied_strict;
bool result =
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)));
nm.del(implied_k);
return result;
}
bool upper_subsumed(expr * p, mpq const & k, bool strict) {
if (!m_util.is_add(p))
return false;
m_num_buffer.reset();
m_var_buffer.reset();
expr2linear_pol(p, m_num_buffer, m_var_buffer);
mpq implied_k;
bool implied_strict;
bool result =
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)));
nm.del(implied_k);
return result;
}
void restore_bounds() {
mpq l, u;
bool strict_l, strict_u, has_l, has_u;
unsigned ts;
unsigned sz = m_var2expr.size();
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
m_new_goal->assert_expr(m.mk_eq(p, m_util.mk_numeral(rational(l), m_util.is_int(p))));
continue;
}
if (has_l && !lower_subsumed(p, l, strict_l)) {
if (strict_l)
m_new_goal->assert_expr(m.mk_not(m_util.mk_le(p, m_util.mk_numeral(rational(l), m_util.is_int(p)))));
else
m_new_goal->assert_expr(m_util.mk_ge(p, m_util.mk_numeral(rational(l), m_util.is_int(p))));
}
if (has_u && !upper_subsumed(p, u, strict_u)) {
if (strict_u)
m_new_goal->assert_expr(m.mk_not(m_util.mk_ge(p, m_util.mk_numeral(rational(u), m_util.is_int(p)))));
else
m_new_goal->assert_expr(m_util.mk_le(p, m_util.mk_numeral(rational(u), m_util.is_int(p))));
}
}
nm.del(l);
nm.del(u);
}
bool is_x_minus_y_eq_0(expr * t, expr * & x, expr * & y) {
expr * lhs, * rhs, * m1, * m2;
if (m.is_eq(t, lhs, rhs) && m_util.is_zero(rhs) && m_util.is_add(lhs, m1, m2)) {
if (m_util.is_times_minus_one(m2, y) && is_uninterp_const(m1)) {
x = m1;
return true;
}
if (m_util.is_times_minus_one(m1, y) && is_uninterp_const(m2)) {
x = m2;
return true;
}
}
return false;
}
bool is_unbounded(expr * t) {
a_var x;
if (m_expr2var.find(t, x))
return !bp.has_lower(x) && !bp.has_upper(x);
return true;
}
bool lower(expr * t, mpq & k, bool & strict) {
unsigned ts;
a_var x;
if (m_expr2var.find(t, x))
return bp.lower(x, k, strict, ts);
return false;
}
bool upper(expr * t, mpq & k, bool & strict) {
unsigned ts;
a_var x;
if (m_expr2var.find(t, x))
return bp.upper(x, k, strict, ts);
return false;
}
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););
}
void operator()(goal * g, goal_ref & r) {
tactic_report report("propagate-ineqs", *g);
m_new_goal = alloc(goal, *g, true);
m_new_goal->inc_depth();
r = m_new_goal.get();
if (!collect_bounds(*g)) {
m_new_goal = nullptr;
r = g;
return; // nothing to be done
}
TRACE("propagate_ineqs_tactic", g->display(tout); display_bounds(tout); tout << "bound propagator:\n"; bp.display(tout););
bp.propagate();
report_tactic_progress(":bound-propagations", bp.get_num_propagations());
report_tactic_progress(":bound-false-alarms", bp.get_num_false_alarms());
if (bp.inconsistent()) {
r->reset();
r->assert_expr(m.mk_false());
return;
}
// find_ite_bounds(); // did not help
restore_bounds();
TRACE("propagate_ineqs_tactic", tout << "after propagation:\n"; display_bounds(tout); bp.display(tout););
TRACE("propagate_ineqs_tactic", r->display(tout););
}
};
propagate_ineqs_tactic::propagate_ineqs_tactic(ast_manager & m, params_ref const & p):
m_params(p) {
m_imp = alloc(imp, m, p);
}
propagate_ineqs_tactic::~propagate_ineqs_tactic() {
dealloc(m_imp);
}
void propagate_ineqs_tactic::updt_params(params_ref const & p) {
m_params.append(p);
m_imp->updt_params(m_params);
}
void propagate_ineqs_tactic::operator()(goal_ref const & g,
goal_ref_buffer & result) {
fail_if_proof_generation("propagate-ineqs", g);
fail_if_unsat_core_generation("propagate-ineqs", g);
result.reset();
goal_ref r;
(*m_imp)(g.get(), r);
result.push_back(r.get());
SASSERT(r->is_well_formed());
}
void propagate_ineqs_tactic::cleanup() {
imp * d = alloc(imp, m_imp->m, m_params);
std::swap(d, m_imp);
dealloc(d);
}

View file

@ -51,11 +51,17 @@ This can be achieved by using option :arith-lhs true in the simplifier.
--*/
#pragma once
#include "util/params.h"
class ast_manager;
class tactic;
tactic * mk_propagate_ineqs_tactic(ast_manager & m, params_ref const & p = params_ref());
#include "util/params.h"
#include "tactic/tactic.h"
#include "tactic/dependent_expr_state_tactic.h"
#include "ast/simplifiers/bound_simplifier.h"
inline tactic* mk_propagate_ineqs_tactic(ast_manager& m, params_ref const& p = params_ref()) {
return alloc(dependent_expr_state_tactic, m, p,
[](auto& m, auto& p, auto &s) -> dependent_expr_simplifier* { return alloc(bound_simplifier, m, p, s); });
}
/*
ADD_TACTIC("propagate-ineqs", "propagate ineqs/bounds, remove subsumed inequalities.", "mk_propagate_ineqs_tactic(m, p)")
*/