mirror of
https://github.com/Z3Prover/z3
synced 2025-04-12 20:18:18 +00:00
564 lines
17 KiB
C++
564 lines
17 KiB
C++
/*++
|
|
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"tactical.h"
|
|
#include"bound_propagator.h"
|
|
#include"arith_decl_plugin.h"
|
|
#include"simplify_tactic.h"
|
|
#include"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);
|
|
|
|
virtual tactic * translate(ast_manager & m) {
|
|
return alloc(propagate_ineqs_tactic, m, m_params);
|
|
}
|
|
|
|
virtual ~propagate_ineqs_tactic();
|
|
|
|
virtual void updt_params(params_ref const & p);
|
|
virtual void collect_param_descrs(param_descrs & r) {}
|
|
|
|
virtual void operator()(goal_ref const & g, goal_ref_buffer & result, model_converter_ref & mc, proof_converter_ref & pc, expr_dependency_ref & core);
|
|
|
|
virtual void cleanup();
|
|
protected:
|
|
virtual void set_cancel(bool f);
|
|
};
|
|
|
|
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";
|
|
}
|
|
}
|
|
|
|
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;
|
|
unsigned num = to_app(t)->get_num_args();
|
|
for (unsigned i = 0; i < num; i++) {
|
|
expr * mon = to_app(t)->get_arg(i);
|
|
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.c_ptr(), m_var_buffer.c_ptr());
|
|
}
|
|
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 {
|
|
return false;
|
|
}
|
|
expr * lhs = to_app(t)->get_arg(0);
|
|
expr * rhs = to_app(t)->get_arg(1);
|
|
if (m_util.is_numeral(lhs)) {
|
|
std::swap(lhs, rhs);
|
|
if (k == LE)
|
|
k = GE;
|
|
else if (k == GE)
|
|
k = LE;
|
|
}
|
|
|
|
rational c;
|
|
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);
|
|
}
|
|
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.c_ptr(), m_var_buffer.c_ptr(), 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.c_ptr(), m_var_buffer.c_ptr(), 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))));
|
|
}
|
|
}
|
|
}
|
|
|
|
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 = 0;
|
|
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 = 0;
|
|
}
|
|
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 == 0) {
|
|
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 == 0) {
|
|
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 = 0;
|
|
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););
|
|
}
|
|
|
|
void set_cancel(bool f) {
|
|
// TODO
|
|
}
|
|
};
|
|
|
|
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 = p;
|
|
m_imp->updt_params(p);
|
|
}
|
|
|
|
void propagate_ineqs_tactic::operator()(goal_ref const & g,
|
|
goal_ref_buffer & result,
|
|
model_converter_ref & mc,
|
|
proof_converter_ref & pc,
|
|
expr_dependency_ref & core) {
|
|
SASSERT(g->is_well_sorted());
|
|
fail_if_proof_generation("propagate-ineqs", g);
|
|
fail_if_unsat_core_generation("propagate-ineqs", g);
|
|
mc = 0; pc = 0; core = 0; result.reset();
|
|
goal_ref r;
|
|
(*m_imp)(g.get(), r);
|
|
result.push_back(r.get());
|
|
SASSERT(r->is_well_sorted());
|
|
}
|
|
|
|
void propagate_ineqs_tactic::set_cancel(bool f) {
|
|
if (m_imp)
|
|
m_imp->set_cancel(f);
|
|
}
|
|
|
|
void propagate_ineqs_tactic::cleanup() {
|
|
ast_manager & m = m_imp->m;
|
|
imp * d = m_imp;
|
|
#pragma omp critical (tactic_cancel)
|
|
{
|
|
d = m_imp;
|
|
}
|
|
dealloc(d);
|
|
d = alloc(imp, m, m_params);
|
|
#pragma omp critical (tactic_cancel)
|
|
{
|
|
m_imp = d;
|
|
}
|
|
}
|