3
0
Fork 0
mirror of https://github.com/Z3Prover/z3 synced 2025-04-24 01:25:31 +00:00

Add new tactic bound-simplifier for integer-based bit-vector reasoning.

This commit is contained in:
Nikolaj Bjorner 2023-01-22 22:07:18 -08:00
parent 83662701b6
commit db79346ef7
14 changed files with 460 additions and 12 deletions

View file

@ -2,6 +2,8 @@ z3_add_component(simplifiers
SOURCES
bit_blaster.cpp
bound_manager.cpp
bound_propagator.cpp
bound_simplifier.cpp
bv_slice.cpp
card2bv.cpp
demodulator_simplifier.cpp
@ -11,6 +13,7 @@ z3_add_component(simplifiers
eliminate_predicates.cpp
euf_completion.cpp
extract_eqs.cpp
linear_equation.cpp
max_bv_sharing.cpp
model_reconstruction_trail.cpp
propagate_values.cpp

View file

@ -0,0 +1,942 @@
/*++
Copyright (c) 2011 Microsoft Corporation
Module Name:
bound_propagator.cpp
Abstract:
Bound propagators for arithmetic.
Support class for implementing strategies and search procedures
Author:
Leonardo de Moura (leonardo) 2011-06-18.
Revision History:
--*/
#include "ast/simplifiers/bound_propagator.h"
#include<cmath>
// -------------------------------
// Bound Relaxation configuration
//
// The idea is to minimize errors in floating point computations
//
// If RELAX_BOUNDS is undefined, then bound relaxation is disabled.
// Otherwise, lower bounds l are relaxed using the formula
// PRECISION * floor(l * INV_PRECISION + TOLERANCE)
// and upper bounds u as:
// PRECISION * ceil(u * INV_PRECISION - TOLERANCE)
// In the LP literature, the suggested values are
// l := 10^-5 * floor(l*10^5 + 10^-6)
// u := 10^-5 * ceil(u*10^5 - 10^-6)
// I'm using the following values because of strict bounds
// l := 10^-6 * floor(l*10^6 + 10^-7)
// u := 10^-6 * ceil(u*10^6 - 10^-7)
#define RELAX_BOUNDS
#define TOLERANCE 0.0000001
#define PRECISION 0.000001
#define INV_PRECISION 1000000.0
// -------------------------------
bound_propagator::bound::bound(numeral_manager & m,
mpq const & k,
double approx_k,
bool lower,
bool strict,
unsigned lvl,
unsigned ts,
bkind bk,
unsigned c_idx,
assumption a,
bound * prev):
m_approx_k(approx_k),
m_lower(lower),
m_strict(strict),
m_kind(bk),
m_level(lvl),
m_timestamp(ts),
m_prev(prev) {
m.set(m_k, k);
if (bk == DERIVED)
m_constraint_idx = c_idx;
else
m_assumption = a;
}
bound_propagator::bound_propagator(numeral_manager & _m, allocator & a, params_ref const & p):
m(_m),
m_allocator(a),
m_eq_manager(m, a) {
m_timestamp = 0;
m_qhead = 0;
m_conflict = null_var;
updt_params(p);
reset_statistics();
}
bound_propagator::~bound_propagator() {
m.del(m_tmp);
reset();
}
void bound_propagator::del_constraints_core() {
for (auto & c : m_constraints) {
del_constraint(c);
}
m_constraints.reset();
}
void bound_propagator::del_constraints() {
SASSERT(scope_lvl() == 0);
if (m_constraints.empty())
return;
del_constraints_core();
m_constraints.finalize();
for (auto& wl : m_watches) {
wl.finalize();
}
}
void bound_propagator::del_constraint(constraint & c) {
switch (c.m_kind) {
case LINEAR:
m_eq_manager.del(c.m_eq);
break;
default:
UNREACHABLE();
break;
}
}
void bound_propagator::updt_params(params_ref const & p) {
m_max_refinements = p.get_uint("bound_max_refinements", 16);
m_threshold = p.get_double("bound_threshold", 0.05);
m_small_interval = p.get_double("bound_small_interval", 128);
m_strict2double = p.get_double("strict2double", 0.00001);
}
void bound_propagator::get_param_descrs(param_descrs & r) {
r.insert("bound_max_refinements", CPK_UINT, "(default: 16) maximum number of bound refinements (per round) for unbounded variables.");
r.insert("bound_threshold", CPK_DOUBLE, "(default: 0.05) bound propagation improvement threshold ratio.");
}
void bound_propagator::collect_statistics(statistics & st) const {
st.update("bound conflicts", m_conflicts);
st.update("bound propagations", m_propagations);
st.update("bound false alarms", m_false_alarms);
}
void bound_propagator::reset_statistics() {
m_conflicts = 0;
m_propagations = 0;
m_false_alarms = 0;
}
void bound_propagator::mk_var(var x, bool is_int) {
m_is_int.reserve(x+1, false);
m_dead.reserve(x+1, true);
m_lowers.reserve(x+1, 0);
m_uppers.reserve(x+1, 0);
m_lower_refinements.reserve(x+1, 0);
m_upper_refinements.reserve(x+1, 0);
m_watches.reserve(x+1);
SASSERT(m_dead[x]);
m_is_int[x] = is_int;
m_dead[x] = false;
m_lowers[x] = 0;
m_uppers[x] = 0;
m_lower_refinements[x] = 0;
m_upper_refinements[x] = 0;
m_watches[x].reset();
}
void bound_propagator::del_var(var x) {
SASSERT(!m_dead[x]);
m_dead[x] = true;
// mark constraints containing x as dead.
wlist & wl = m_watches[x];
for (auto c : wl) {
m_constraints[c].m_dead = true;
}
}
void bound_propagator::mk_eq(unsigned sz, mpq * as, var * xs) {
linear_equation * eq = m_eq_manager.mk(sz, as, xs);
init_eq(eq);
}
void bound_propagator::mk_eq(unsigned sz, mpz * as, var * xs) {
linear_equation * eq = m_eq_manager.mk(sz, as, xs);
init_eq(eq);
}
void bound_propagator::init_eq(linear_equation * eq) {
if (eq == nullptr)
return;
unsigned c_idx = m_constraints.size();
m_constraints.push_back(constraint());
constraint & new_c = m_constraints.back();
new_c.m_kind = LINEAR;
new_c.m_dead = false;
new_c.m_timestamp = 0;
new_c.m_act = 0;
new_c.m_counter = 0;
new_c.m_eq = eq;
unsigned sz = eq->size();
for (unsigned i = 0; i < sz; i++) {
m_watches[eq->x(i)].push_back(c_idx);
}
if (propagate(c_idx) && scope_lvl() > 0)
m_reinit_stack.push_back(c_idx);
}
void bound_propagator::push() {
m_scopes.push_back(scope());
scope & s = m_scopes.back();
s.m_trail_limit = m_trail.size();
s.m_qhead_old = m_qhead;
s.m_reinit_stack_limit = m_reinit_stack.size();
s.m_timestamp_old = m_timestamp;
s.m_in_conflict = inconsistent();
}
void bound_propagator::undo_trail(unsigned old_sz) {
SASSERT(old_sz <= m_trail.size());
unsigned i = m_trail.size();
while (i > old_sz) {
--i;
trail_info & info = m_trail.back();
var x = info.x();
bool is_lower = info.is_lower();
m_trail.pop_back();
bound * b;
if (is_lower) {
b = m_lowers[x];
m_lowers[x] = b->m_prev;
}
else {
b = m_uppers[x];
m_uppers[x] = b->m_prev;
}
m.del(b->m_k);
b->~bound();
m_allocator.deallocate(sizeof(bound), b);
}
SASSERT(m_trail.size() == old_sz);
}
void bound_propagator::pop(unsigned num_scopes) {
unsigned lvl = scope_lvl();
SASSERT(num_scopes <= lvl);
unsigned new_lvl = lvl - num_scopes;
scope & s = m_scopes[new_lvl];
undo_trail(s.m_trail_limit);
m_timestamp = s.m_timestamp_old;
m_qhead = s.m_qhead_old;
if (!s.m_in_conflict)
m_conflict = null_var;
unsigned reinit_stack_sz = s.m_reinit_stack_limit;
m_scopes.shrink(new_lvl);
// reinitialize
unsigned i = reinit_stack_sz;
unsigned j = reinit_stack_sz;
unsigned sz = m_reinit_stack.size();
for (; i < sz; i++) {
unsigned c_idx = m_reinit_stack[i];
bool p = propagate(c_idx);
if (new_lvl > 0 && p) {
m_reinit_stack[j] = c_idx;
j++;
}
}
m_reinit_stack.shrink(j);
}
bool bound_propagator::assert_lower_core(var x, mpq & k, bool strict, bkind bk, unsigned c_idx, assumption a) {
if (is_int(x)) {
if (m.is_int(k)) {
if (strict)
m.inc(k);
}
else {
m.ceil(k, k);
}
SASSERT(m.is_int(k));
strict = false;
}
TRACE("bound_propagator_detail", tout << "new lower x" << x << " " << m.to_string(k) << " strict: " << strict << "\n";);
bound * old_lower = m_lowers[x];
if (old_lower) {
bool improves = m.gt(k, old_lower->m_k) || (!old_lower->m_strict && strict && m.eq(k, old_lower->m_k));
if (!improves) {
if (bk == DERIVED) {
TRACE("bound_propagator_detail", tout << "false alarm\n";);
m_false_alarms++;
}
return false;
}
}
if (bk == DERIVED) {
TRACE("bound_propagator_derived", tout << "new lower x" << x << " " << m.to_string(k) << " strict: " << strict << "\n";);
m_propagations++;
}
if (scope_lvl() == 0 && bk == DERIVED)
bk = AXIOM; // don't need justification at level 0
double approx_k = m.get_double(k);
TRACE("new_bound", tout << "x" << x << " lower: " << m.to_string(k) << " approx: " << approx_k << "\n";);
#ifdef RELAX_BOUNDS
approx_k = PRECISION*floor(approx_k*INV_PRECISION + TOLERANCE);
TRACE("new_bound", tout << "x" << x << " lower: " << m.to_string(k) << " relaxed approx: " << approx_k << "\n";);
#endif
void * mem = m_allocator.allocate(sizeof(bound));
bound * new_lower = new (mem) bound(m, k, approx_k, true, strict, scope_lvl(), m_timestamp, bk, c_idx, a, old_lower);
m_timestamp++;
m_lowers[x] = new_lower;
m_trail.push_back(trail_info(x, true));
m_lower_refinements[x]++;
check_feasibility(x);
return true;
}
bool bound_propagator::assert_upper_core(var x, mpq & k, bool strict, bkind bk, unsigned c_idx, assumption a) {
if (is_int(x)) {
if (m.is_int(k)) {
if (strict)
m.dec(k);
}
else {
m.floor(k, k);
}
SASSERT(m.is_int(k));
strict = false;
}
TRACE("bound_propagator_detail", tout << "new upper x" << x << " " << m.to_string(k) << " strict: " << strict << "\n";);
bound * old_upper = m_uppers[x];
if (old_upper) {
bool improves = m.lt(k, old_upper->m_k) || (!old_upper->m_strict && strict && m.eq(k, old_upper->m_k));
if (!improves) {
if (bk == DERIVED) {
TRACE("bound_propagator_detail", tout << "false alarm\n";);
m_false_alarms++;
}
return false;
}
}
if (bk == DERIVED) {
m_propagations++;
TRACE("bound_propagator_derived", tout << "new upper x" << x << " " << m.to_string(k) << " strict: " << strict << "\n";);
}
if (scope_lvl() == 0 && bk == DERIVED)
bk = AXIOM; // don't need justification at level 0
double approx_k = m.get_double(k);
TRACE("new_bound", tout << "x" << x << " upper: " << m.to_string(k) << " approx: " << approx_k << "\n";);
#ifdef RELAX_BOUNDS
approx_k = PRECISION*ceil(approx_k*INV_PRECISION - TOLERANCE);
TRACE("new_bound", tout << "x" << x << " upper: " << m.to_string(k) << " relaxed approx: " << approx_k << "\n";);
#endif
void * mem = m_allocator.allocate(sizeof(bound));
bound * new_upper = new (mem) bound(m, k, approx_k, false, strict, scope_lvl(), m_timestamp, bk, c_idx, a, m_uppers[x]);
m_timestamp++;
m_uppers[x] = new_upper;
m_trail.push_back(trail_info(x, false));
m_upper_refinements[x]++;
check_feasibility(x);
return true;
}
bool bound_propagator::get_interval_size(var x, double & r) const {
bound * l = m_lowers[x];
bound * u = m_uppers[x];
if (l && u) {
r = u->m_approx_k - l->m_approx_k;
return true;
}
return false;
}
template<bool LOWER>
bool bound_propagator::relevant_bound(var x, double new_k) const {
TRACE("bound_propagator_detail", tout << "relevant_bound x" << x << " " << new_k << " LOWER: " << LOWER << "\n";
if (LOWER && has_lower(x)) tout << "old: " << m.to_string(m_lowers[x]->m_k) << " | " << m_lowers[x]->m_approx_k << "\n";
if (!LOWER && has_upper(x)) tout << "old: " << m.to_string(m_uppers[x]->m_k) << " | " << m_uppers[x]->m_approx_k << "\n";);
bound * b = LOWER ? m_lowers[x] : m_uppers[x];
if (b == nullptr)
return true; // variable did not have a bound
double interval_size;
bool bounded = get_interval_size(x, interval_size);
if (!is_int(x)) {
// check if the improvement is significant
double improvement;
double abs_k = b->m_approx_k;
if (abs_k < 0.0)
abs_k -= abs_k;
if (bounded)
improvement = m_threshold * std::max(std::min(interval_size, abs_k), 1.0);
else
improvement = m_threshold * std::max(abs_k, 1.0);
if (LOWER) {
if (new_k <= b->m_approx_k + improvement) {
TRACE("bound_propagator", tout << "LOWER new: " << new_k << " old: " << b->m_approx_k << " improvement is too small\n";);
return false; // improvement is too small
}
}
else {
if (new_k >= b->m_approx_k - improvement) {
TRACE("bound_propagator", tout << "UPPER new: " << new_k << " old: " << b->m_approx_k << " improvement is too small\n";);
return false; // improvement is too small
}
}
}
else {
if (LOWER) {
if (new_k < b->m_approx_k + 1.0)
return false; // no improvement
}
else {
if (new_k > b->m_approx_k - 1.0)
return false; // no improvement
}
}
if (bounded && interval_size <= m_small_interval)
return true;
if (LOWER)
return m_lower_refinements[x] < m_max_refinements;
else
return m_upper_refinements[x] < m_max_refinements;
}
bool bound_propagator::relevant_lower(var x, double approx_k) const {
return relevant_bound<true>(x, approx_k);
}
bool bound_propagator::relevant_upper(var x, double approx_k) const {
return relevant_bound<false>(x, approx_k);
}
void bound_propagator::check_feasibility(var x) {
if (inconsistent())
return;
bound * l = m_lowers[x];
bound * u = m_uppers[x];
if (l && u) {
if (m.lt(l->m_k, u->m_k))
return;
if (!l->m_strict && !u->m_strict && m.eq(l->m_k, u->m_k))
return;
m_conflict = x;
m_conflicts++;
SASSERT(inconsistent());
TRACE("bound_propagator", tout << "inconsistency detected: x" << x << "\n"; display(tout););
}
}
void bound_propagator::propagate() {
m_to_reset_ts.reset();
while (m_qhead < m_trail.size()) {
if (inconsistent())
break;
trail_info & info = m_trail[m_qhead];
var x = info.x();
bool is_lower = info.is_lower();
bound * b = is_lower ? m_lowers[x] : m_uppers[x];
SASSERT(b);
unsigned ts = b->m_timestamp;
TRACE("bound_propagator_detail", tout << "propagating x" << x << "\n";);
m_qhead++;
wlist const & wl = m_watches[x];
for (unsigned c_idx : wl) {
constraint & c = m_constraints[c_idx];
// We don't need to visit c if it was already propagated using b.
// Whenever we visit c we store in c.m_timestamp the current timestamp
// So, we know that c was already propagated any bound using bounds with timestamp lower than c.m_timestamp.
if (ts >= c.m_timestamp) {
if (c.m_timestamp == 0)
m_to_reset_ts.push_back(c_idx);
c.m_timestamp = m_timestamp;
propagate(c_idx);
}
}
}
for (unsigned c : m_to_reset_ts)
m_constraints[c].m_timestamp = 0;
}
bool bound_propagator::propagate(unsigned c_idx) {
constraint const & c = m_constraints[c_idx];
if (c.m_dead)
return false;
if (c.m_kind == LINEAR)
return propagate_eq(c_idx);
return false;
}
bool bound_propagator::propagate_eq(unsigned c_idx) {
constraint const & c = m_constraints[c_idx];
linear_equation * eq = c.m_eq;
#if 0
{
static unsigned counter = 0;
static unsigned visited = 0;
counter++;
visited += eq->size();
if (counter % 1000 == 0)
verbose_stream() << "[bound-propagator] :propagate-eq " << counter << " :visited-vars " << visited << std::endl;
}
#endif
TRACE("bound_propagator_detail", tout << "propagating using eq: "; m_eq_manager.display(tout, *eq); tout << "\n";);
// ll = (Sum_{a_i < 0} -a_i*lower(x_i)) + (Sum_{a_i > 0} -a_i * upper(x_i))
// uu = (Sum_{a_i > 0} -a_i*lower(x_i)) + (Sum_{a_i < 0} -a_i * upper(x_i))
unsigned ll_i = UINT_MAX; // position of the variable that couldn't contribute to ll
unsigned uu_i = UINT_MAX; // position of the variable that coundn't contribute to uu
bool ll_failed = false;
bool uu_failed = false;
double ll = 0.0;
double uu = 0.0;
unsigned sz = eq->size();
for (unsigned i = 0; i < sz; i++) {
var x_i = eq->x(i);
double a_i = eq->approx_a(i);
bound * l_i = m_lowers[x_i];
bound * u_i = m_uppers[x_i];
if (a_i < 0.0) {
if (!ll_failed) {
if (l_i == nullptr) {
if (ll_i == UINT_MAX)
ll_i = i;
else
ll_failed = true;
}
else {
ll -= a_i * l_i->m_approx_k;
}
}
if (!uu_failed) {
if (u_i == nullptr) {
if (uu_i == UINT_MAX)
uu_i = i;
else
uu_failed = true;
}
else {
uu -= a_i * u_i->m_approx_k;
}
}
}
else {
if (!ll_failed) {
if (u_i == nullptr) {
if (ll_i == UINT_MAX)
ll_i = i;
else
ll_failed = true;
}
else {
ll -= a_i * u_i->m_approx_k;
}
}
if (!uu_failed) {
if (l_i == nullptr) {
if (uu_i == UINT_MAX)
uu_i = i;
else
uu_failed = true;
}
else {
uu -= a_i * l_i->m_approx_k;
}
}
}
if (ll_failed && uu_failed)
return false; // nothing to propagate
}
bool propagated = false;
SASSERT(!ll_failed || !uu_failed);
if (ll_i == UINT_MAX || uu_i == UINT_MAX) {
for (unsigned i = 0; i < sz; i++) {
var x_i = eq->x(i);
double a_i = eq->approx_a(i);
bound * l_i = m_lowers[x_i];
bound * u_i = m_uppers[x_i];
// ll = (Sum_{a_i < 0} -a_i*lower(x_i)) + (Sum_{a_i > 0} -a_i * upper(x_i))
// uu = (Sum_{a_i > 0} -a_i*lower(x_i)) + (Sum_{a_i < 0} -a_i * upper(x_i))
if (ll_i == UINT_MAX) {
// can propagate a lower bound for a_i*x_i
if (a_i > 0.0) {
// can propagate a lower bound for x_i
double new_k = (ll + a_i * u_i->m_approx_k)/a_i;
if (relevant_lower(x_i, new_k) && propagate_lower(c_idx, i))
propagated = true;
}
else {
// a_i < 0.0
// can propagate a upper bound for x_i
double new_k = (ll + a_i * l_i->m_approx_k)/a_i;
if (relevant_upper(x_i, new_k) && propagate_upper(c_idx, i))
propagated = true;
}
}
if (uu_i == UINT_MAX) {
// can propagate an upper bound for a_i*x_i
if (a_i > 0.0) {
// can propagate a upper bound for x_i
double new_k = (uu + a_i * l_i->m_approx_k)/a_i;
if (relevant_upper(x_i, new_k) && propagate_upper(c_idx, i))
propagated = true;
}
else {
// a_i < 0.0
// can propagate a lower bound for x_i
double new_k = (uu + a_i * u_i->m_approx_k)/a_i;
if (relevant_lower(x_i, new_k) && propagate_lower(c_idx, i))
propagated = true;
}
}
}
}
if (!ll_failed && ll_i != UINT_MAX) {
// can propagate a lower bound for the monomial at position ll_i
var x_i = eq->x(ll_i);
double a_i = eq->approx_a(ll_i);
double new_k = ll/a_i;
if (a_i > 0.0) {
if (relevant_lower(x_i, new_k) && propagate_lower(c_idx, ll_i))
propagated = true;
}
else {
if (relevant_upper(x_i, new_k) && propagate_upper(c_idx, ll_i))
propagated = true;
}
}
if (!uu_failed && uu_i != UINT_MAX) {
// can propagate a upper bound for the monomial at position uu_i
var x_i = eq->x(uu_i);
double a_i = eq->approx_a(uu_i);
double new_k = uu/a_i;
if (a_i > 0.0) {
if (relevant_upper(x_i, new_k) && propagate_upper(c_idx, uu_i))
propagated = true;
}
else {
if (relevant_lower(x_i, new_k) && propagate_lower(c_idx, uu_i))
propagated = true;
}
}
return propagated;
}
/**
\brief Try to propagate a lower bound for the variable stored at position i, using mpq's (rationals).
When this method is invoked, we know that all other variables have the "right" bounds, and
using doubles we improve the current known bound.
*/
bool bound_propagator::propagate_lower(unsigned c_idx, unsigned i) {
constraint const & c = m_constraints[c_idx];
linear_equation * eq = c.m_eq;
var x_i = eq->x(i);
mpz const & a_i = eq->a(i);
unsigned sz = eq->size();
mpq k;
bool strict = false;
bool neg_a_i = m.is_neg(a_i);
for (unsigned j = 0; j < sz; j++) {
if (i == j)
continue;
var x_j = eq->x(j);
mpz const & a_j = eq->a(j);
bound * b_j = (m.is_neg(a_j) == neg_a_i) ? m_uppers[x_j] : m_lowers[x_j];
TRACE("bound_propagator_step_detail", tout << "k: " << m.to_string(k) << " b_j->m_k: " << m.to_string(b_j->m_k) <<
" a_j: " << m.to_string(a_j) << "\n";);
SASSERT(b_j);
if (b_j->m_strict)
strict = true;
m.addmul(k, a_j, b_j->m_k, k);
}
TRACE("bound_propagator_step_detail", tout << "k: " << m.to_string(k) << "\n";);
m.neg(k);
m.div(k, a_i, k);
TRACE("bound_propagator_step", tout << "propagating lower x" << x_i << " " << m.to_string(k) << " strict: " << strict << " using\n";
m_eq_manager.display(tout, *eq); tout << "\n"; display_bounds_of(tout, *eq););
bool r = assert_lower_core(x_i, k, strict, DERIVED, c_idx, null_assumption);
m.del(k);
return r;
}
/**
\brief Try to propagate a upper bound for the variable stored at position i, using mpq's (rationals).
When this method is invoked, we know that all other variables have the "right" bounds, and
using doubles we improve the current known bound.
*/
bool bound_propagator::propagate_upper(unsigned c_idx, unsigned i) {
constraint const & c = m_constraints[c_idx];
linear_equation * eq = c.m_eq;
var x_i = eq->x(i);
mpz const & a_i = eq->a(i);
unsigned sz = eq->size();
mpq k;
bool strict = false;
bool neg_a_i = m.is_neg(a_i);
for (unsigned j = 0; j < sz; j++) {
if (i == j)
continue;
var x_j = eq->x(j);
mpz const & a_j = eq->a(j);
bound * b_j = (m.is_neg(a_j) == neg_a_i) ? m_lowers[x_j] : m_uppers[x_j];
SASSERT(b_j);
if (b_j->m_strict)
strict = true;
m.addmul(k, a_j, b_j->m_k, k);
}
m.neg(k);
m.div(k, a_i, k);
TRACE("bound_propagator_step", tout << "propagating upper x" << x_i << " " << m.to_string(k) << " strict: " << strict << " using\n";
m_eq_manager.display(tout, *eq); tout << "\n"; display_bounds_of(tout, *eq););
bool r = assert_upper_core(x_i, k, strict, DERIVED, c_idx, null_assumption);
m.del(k);
return r;
}
void bound_propagator::reset() {
undo_trail(0);
del_constraints_core();
m_constraints.finalize();
m_is_int.finalize();
m_dead.finalize();
m_lowers.finalize();
m_uppers.finalize();
m_watches.finalize();
m_trail.finalize();
m_qhead = 0;
m_reinit_stack.finalize();
m_lower_refinements.finalize();
m_upper_refinements.finalize();
m_timestamp = 0;
m_conflict = null_var;
m_scopes.finalize();
}
bool bound_propagator::lower(var x, mpq & k, bool & strict, unsigned & ts) const {
bound * b = m_lowers[x];
if (!b)
return false;
m.set(k, b->m_k);
strict = b->m_strict;
ts = b->m_timestamp;
return true;
}
bool bound_propagator::upper(var x, mpq & k, bool & strict, unsigned & ts) const {
bound * b = m_uppers[x];
if (!b)
return false;
m.set(k, b->m_k);
strict = b->m_strict;
ts = b->m_timestamp;
return true;
}
bound_propagator::bound * bound_propagator::bound::at(unsigned timestamp) {
bound * r = this;
while (r != nullptr && r->m_timestamp >= timestamp)
r = r->m_prev;
return r;
}
/**
\brief Return true if the coefficient of x in eq is positive
*/
bool bound_propagator::is_a_i_pos(linear_equation const & eq, var x) const {
unsigned i = eq.pos(x);
if (i == UINT_MAX)
return false;
return m.is_pos(eq.a(i));
}
void bound_propagator::explain(var x, bound * b, unsigned ts, assumption_vector & ex) const {
if (!b)
return;
b = b->at(ts);
if (!b)
return;
if (b->m_kind == AXIOM || b->m_kind == DECISION)
return;
if (b->m_kind == ASSUMPTION) {
ex.push_back(b->m_assumption);
return;
}
svector<var_bound> & todo = const_cast<bound_propagator*>(this)->m_todo;
todo.reset();
unsigned qhead = 0;
todo.push_back(var_bound(x, b));
b->m_mark = true;
while (qhead < todo.size()) {
var_bound & vb = todo[qhead];
qhead ++;
var x = vb.first;
bound * b = vb.second;
SASSERT(b->kind() == ASSUMPTION || b->kind() == DERIVED);
if (b->kind() == ASSUMPTION) {
ex.push_back(b->m_assumption);
continue;
}
SASSERT(b->kind() == DERIVED);
constraint const & c = m_constraints[b->m_constraint_idx];
switch (c.m_kind) {
case LINEAR: {
linear_equation * eq = c.m_eq;
bool is_lower = b->is_lower();
if (!is_a_i_pos(*eq, x))
is_lower = !is_lower;
unsigned sz = eq->size();
for (unsigned i = 0; i < sz; i++) {
var x_i = eq->x(i);
if (x_i == x)
continue;
bound * b = (m.is_neg(eq->a(i)) == is_lower) ? m_lowers[x_i] : m_uppers[x_i];
SASSERT(b);
if (b->kind() == DERIVED || b->kind() == ASSUMPTION) {
if (!b->m_mark) {
b->m_mark = true;
todo.push_back(var_bound(x_i, b));
}
}
}
break;
}
default:
break;
}
}
for (var_bound& vb : todo)
vb.second->m_mark = false;
todo.reset();
}
/**
\brief Compute lower (upper) bound for the linear polynomial as[0]*xs[0] + ... + as[sz-1]*xs[sz-1]
Return false if the lower (upper) bound is -oo (oo)
*/
template<bool LOWER, typename Numeral>
bool bound_propagator::get_bound(unsigned sz, Numeral const * as, var const * xs, mpq & r, bool & st) const {
st = false;
m.reset(r);
for (unsigned i = 0; i < sz; i++) {
var x_i = xs[i];
Numeral const & a_i = as[i];
if (m.is_zero(a_i))
continue;
bound * b = (m.is_neg(a_i) == LOWER) ? m_uppers[x_i] : m_lowers[x_i];
if (!b) {
m.reset(r);
return false;
}
if (b->m_strict)
st = true;
m.addmul(r, a_i, b->m_k, r);
}
return true;
}
bool bound_propagator::lower(unsigned sz, mpq const * as, var const * xs, mpq & r, bool & st) const {
return get_bound<true, mpq>(sz, as, xs, r, st);
}
bool bound_propagator::upper(unsigned sz, mpq const * as, var const * xs, mpq & r, bool & st) const {
return get_bound<false, mpq>(sz, as, xs, r, st);
}
void bound_propagator::display_bounds_of(std::ostream & out, linear_equation const & eq) const {
for (unsigned i = 0; i < eq.size(); i++) {
display_var_bounds(out, eq.x(i));
out << "\n";
}
}
void bound_propagator::display_var_bounds(std::ostream & out, var x, bool approx, bool precise) const {
if (m_lowers[x]) {
if (precise)
out << m.to_string(m_lowers[x]->m_k);
if (precise && approx)
out << " | ";
if (approx)
out << m_lowers[x]->m_approx_k;
out << " " << (m_lowers[x]->m_strict ? "<" : "<=");
}
else {
out << "-oo <";
}
out << " x" << x << " ";
if (m_uppers[x]) {
out << (m_uppers[x]->m_strict ? "<" : "<=") << " ";
if (precise)
out << m.to_string(m_uppers[x]->m_k);
if (precise && approx)
out << " | ";
if (approx)
out << m_uppers[x]->m_approx_k;
}
else {
out << "< oo";
}
}
void bound_propagator::display_bounds(std::ostream & out, bool approx, bool precise) const {
unsigned num_vars = m_dead.size();
for (unsigned x = 0; x < num_vars; x++) {
if (!is_dead(x)) {
display_var_bounds(out, x, approx, precise);
out << "\n";
}
}
}
void bound_propagator::display_constraints(std::ostream & out) const {
for (constraint const& c : m_constraints) {
if (c.m_kind == LINEAR) {
m_eq_manager.display(out, *(c.m_eq));
out << "\n";
}
}
}
void bound_propagator::display(std::ostream & out) const {
display_bounds(out);
display_constraints(out);
}

View file

@ -0,0 +1,263 @@
/*++
Copyright (c) 2011 Microsoft Corporation
Module Name:
bound_propagator.h
Abstract:
Bound propagators for arithmetic.
Support class for implementing strategies and search procedures
Author:
Leonardo de Moura (leonardo) 2011-06-18.
Revision History:
--*/
#pragma once
#include "util/mpq.h"
#include "util/vector.h"
#include "util/params.h"
#include "util/statistics.h"
#include "util/numeral_buffer.h"
#include "ast/simplifiers/linear_equation.h"
class bound_propagator {
public:
typedef unsigned var;
typedef unsigned assumption;
typedef unsynch_mpq_manager numeral_manager;
typedef unsigned_vector assumption_vector;
typedef unsigned constraint_id;
typedef numeral_buffer<mpz, numeral_manager> mpz_buffer;
typedef svector<double> double_vector;
static const assumption null_assumption = UINT_MAX;
static const var null_var = UINT_MAX;
static const unsigned null_constraint_idx = UINT_MAX;
class trail_info {
unsigned m_x_lower;
public:
trail_info(var x, bool is_lower):m_x_lower((x << 1) + static_cast<unsigned>(is_lower)) {}
trail_info():m_x_lower(UINT_MAX) {}
var x() const { return m_x_lower >> 1; }
bool is_lower() const { return (m_x_lower & 1) != 0; }
};
protected:
enum ckind { LINEAR // only linear equalities so far.
};
/**
\brief Constraints don't need justification.
*/
class constraint {
friend class bound_propagator;
unsigned m_kind:2;
unsigned m_dead:1;
unsigned m_timestamp; // Constraint tried to propagate new bounds using bounds with timestamp < m_timestamp.
unsigned m_act; // activity
unsigned m_counter; // number of times the constraint propagated
union {
linear_equation * m_eq;
};
};
enum bkind { AXIOM, // doesn't need justification
ASSUMPTION, // aka external case-split, it is used to connect with external search engine.
DERIVED, // implied
DECISION // internal case-split
};
struct bound {
mpq m_k;
double m_approx_k;
unsigned m_lower:1;
unsigned m_strict:1;
unsigned m_mark:1;
unsigned m_kind:2;
unsigned m_level:27;
unsigned m_timestamp;
union {
assumption m_assumption;
unsigned m_constraint_idx;
};
bound * m_prev;
bound(numeral_manager & m, mpq const & k, double approx_k, bool lower, bool strict, unsigned lvl, unsigned ts, bkind bk,
unsigned c_idx, assumption a, bound * prev);
bound * at(unsigned timestamp);
bkind kind() const { return static_cast<bkind>(m_kind); }
bool is_lower() const { return m_lower != 0; }
};
typedef ptr_vector<bound> var2bound;
typedef svector<var> var_vector;
typedef svector<constraint> constraint_vector;
typedef unsigned_vector c_idx_vector;
typedef c_idx_vector wlist;
typedef small_object_allocator allocator;
typedef linear_equation_manager lin_eq_manager;
numeral_manager & m;
allocator & m_allocator;
lin_eq_manager m_eq_manager;
constraint_vector m_constraints;
char_vector m_is_int;
char_vector m_dead;
var2bound m_lowers;
var2bound m_uppers;
vector<wlist> m_watches;
svector<trail_info> m_trail;
unsigned m_qhead;
c_idx_vector m_reinit_stack;
unsigned_vector m_lower_refinements; // number of times a lower bound was propagated for each variable (loop prevention)
unsigned_vector m_upper_refinements; // number of times a upper bound was propagated for each variable (loop prevention)
unsigned m_timestamp;
var m_conflict;
mpq m_tmp;
struct scope {
unsigned m_trail_limit;
unsigned m_qhead_old;
unsigned m_reinit_stack_limit;
unsigned m_timestamp_old:31;
unsigned m_in_conflict:1;
};
svector<scope> m_scopes;
unsigned_vector m_to_reset_ts; // temp field: ids of the constraints we must reset the field m_timestamp
// config
unsigned m_max_refinements; // maximum number of refinements per round
double m_small_interval;
double m_threshold; // improvement threshold
double m_strict2double;
// statistics
unsigned m_conflicts;
unsigned m_propagations;
unsigned m_false_alarms;
void del_constraint(constraint & cnstr);
void del_constraints_core();
template<bool LOWER>
bool relevant_bound(var x, double approx_k) const;
bool relevant_lower(var x, double approx_k) const;
bool relevant_upper(var x, double approx_k) const;
bool get_interval_size(var x, double & r) const;
void check_feasibility(var x);
bool assert_lower_core(var x, mpq & k, bool strict, bkind bk, unsigned c_idx, assumption a);
bool assert_upper_core(var x, mpq & k, bool strict, bkind bk, unsigned c_idx, assumption a);
bool propagate(unsigned c_idx);
bool propagate_eq(unsigned c_idx);
bool propagate_lower(unsigned c_idx, unsigned i);
bool propagate_upper(unsigned c_idx, unsigned i);
void undo_trail(unsigned old_sz);
typedef std::pair<var, bound *> var_bound;
svector<var_bound> m_todo;
void explain(var x, bound * b, unsigned ts, assumption_vector & ex) const;
bool is_a_i_pos(linear_equation const & eq, var x) const;
template<bool LOWER, typename Numeral>
bool get_bound(unsigned sz, Numeral const * as, var const * xs, mpq & r, bool & st) const;
void init_eq(linear_equation * eq);
public:
bound_propagator(numeral_manager & m, allocator & a, params_ref const & p);
~bound_propagator();
void updt_params(params_ref const & p);
static void get_param_descrs(param_descrs & r);
void collect_statistics(statistics & st) const;
void reset_statistics();
double strict2double() const { return m_strict2double; }
bool is_int(var x) const { return m_is_int[x] != 0; }
unsigned scope_lvl() const { return m_scopes.size(); }
void mk_var(var x, bool is_int);
void del_var(var x);
bool is_dead(var x) const { return m_dead[x] != 0; }
void mk_eq(unsigned sz, mpq * as, var * xs);
void mk_eq(unsigned sz, mpz * as, var * xs);
void del_constraints();
void assert_lower(var x, mpq const & k, bool strict, assumption a = null_assumption) {
m.set(m_tmp, k);
assert_lower_core(x, m_tmp, strict, a == null_assumption ? AXIOM : ASSUMPTION, 0, a);
}
void assert_upper(var x, mpq const & k, bool strict, assumption a = null_assumption) {
m.set(m_tmp, k);
assert_upper_core(x, m_tmp, strict, a == null_assumption ? AXIOM : ASSUMPTION, 0, a);
}
void assert_decided_lower(var x, mpq const & k) {
m.set(m_tmp, k);
assert_lower_core(x, m_tmp, false, DECISION, 0, null_assumption);
}
void assert_decided_upper(var x, mpq const & k) {
m.set(m_tmp, k);
assert_upper_core(x, m_tmp, false, DECISION, 0, null_assumption);
}
void propagate();
void push();
void pop(unsigned num_scopes);
void reset();
bool has_lower(var x) const { return m_lowers[x] != 0; }
bool has_upper(var x) const { return m_uppers[x] != 0; }
bool lower(var x, mpq & k, bool & strict, unsigned & ts) const;
bool upper(var x, mpq & k, bool & strict, unsigned & ts) const;
bool is_fixed(var x) const { return has_lower(x) && has_upper(x) && m.eq(m_lowers[x]->m_k, m_uppers[x]->m_k) && !inconsistent(); }
mpq const & lower(var x, bool & strict) const { SASSERT(has_lower(x)); bound * b = m_lowers[x]; strict = b->m_strict; return b->m_k; }
mpq const & upper(var x, bool & strict) const { SASSERT(has_upper(x)); bound * b = m_uppers[x]; strict = b->m_strict; return b->m_k; }
mpq const & lower(var x) const { SASSERT(has_lower(x)); return m_lowers[x]->m_k; }
mpq const & upper(var x) const { SASSERT(has_upper(x)); return m_uppers[x]->m_k; }
double approx_lower(var x) const {
SASSERT(has_lower(x));
return m_lowers[x]->m_strict ? m_lowers[x]->m_approx_k + m_strict2double : m_lowers[x]->m_approx_k;
}
double approx_upper(var x) const {
SASSERT(has_upper(x));
return m_uppers[x]->m_strict ? m_uppers[x]->m_approx_k - m_strict2double : m_uppers[x]->m_approx_k;
}
bool is_zero(var x) const { return has_lower(x) && has_upper(x) && m.is_zero(lower(x)) && m.is_zero(upper(x)); }
void explain_lower(var x, unsigned ts, assumption_vector & ex) const { explain(x, m_lowers[x], ts, ex); }
void explain_upper(var x, unsigned ts, assumption_vector & ex) const { explain(x, m_uppers[x], ts, ex); }
void explain_lower(var x, assumption_vector & ex) const { explain_lower(x, m_timestamp, ex); }
void explain_upper(var x, assumption_vector & ex) const { explain_upper(x, m_timestamp, ex); }
var conflict_var() const { return m_conflict; }
bool inconsistent() const { return m_conflict != null_var; }
unsigned trail_size() const { return m_trail.size(); }
unsigned qhead() const { return m_qhead; }
typedef svector<trail_info>::const_iterator trail_iterator;
trail_iterator begin_trail() const { return m_trail.begin(); }
trail_iterator end_trail() const { return m_trail.end(); }
bool lower(unsigned sz, mpq const * as, var const * xs, mpq & r, bool & st) const;
bool upper(unsigned sz, mpq const * as, var const * xs, mpq & r, bool & st) const;
void display(std::ostream & out) const;
void display_var_bounds(std::ostream & out, var x, bool approx = true, bool precise = true) const;
void display_bounds(std::ostream & out, bool approx = true, bool precise = true) const;
void display_precise_bounds(std::ostream & out) const { display_bounds(out, false, true); }
void display_approx_bounds(std::ostream & out) const { display_bounds(out, true, false); }
void display_constraints(std::ostream & out) const;
void display_bounds_of(std::ostream & out, linear_equation const & eq) const;
unsigned get_num_false_alarms() const { return m_false_alarms; }
unsigned get_num_propagations() const { return m_propagations; }
};

View file

@ -0,0 +1,306 @@
/*++
Copyright (c) 2022 Microsoft Corporation
Module Name:
bounds_simplifier.cpp
Author:
Nikolaj Bjorner (nbjorner) 2023-01-22
--*/
#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 {
ast_manager& m;
bound_propagator& bp;
bound_simplifier& s;
arith_util a;
rw_cfg(bound_simplifier& s):m(s.m), bp(s.bp), s(s), a(m) {}
br_status reduce_app(func_decl* f, unsigned num_args, expr * const* args, expr_ref& result, proof_ref& pr) {
rational N, hi, lo;
bool strict;
if (a.is_mod(f) && num_args == 2 && a.is_numeral(args[1], N)) {
expr* x = args[0];
if (!s.has_upper(x, hi, strict) || strict)
return BR_FAILED;
if (!s.has_lower(x, lo, strict) || strict)
return BR_FAILED;
if (hi - lo >= N)
return BR_FAILED;
if (N > hi && lo >= 0) {
result = x;
return BR_DONE;
}
if (2*N > hi && lo >= N) {
result = a.mk_sub(x, a.mk_int(N));
s.m_rewriter(result);
return BR_DONE;
}
IF_VERBOSE(2, verbose_stream() << "potentially missed simplification: " << mk_pp(x, m) << " " << lo << " " << hi << " not reduced\n");
}
return BR_FAILED;
}
};
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) {
}
};
void bound_simplifier::reduce() {
m_updated = true;
for (unsigned i = 0; i < 5 && m_updated; ++i) {
m_updated = false;
reset();
for (unsigned idx : indices())
insert_bound(m_fmls[idx]);
for (unsigned idx : indices())
tighten_bound(m_fmls[idx]);
rw rw(*this);
// TODO: take over propagate_ineq:
// bp.propagate();
// serialize bounds
proof_ref pr(m);
expr_ref r(m);
for (unsigned idx : indices()) {
auto const& d = m_fmls[idx];
rw(d.fml(), r, pr);
if (r != d.fml()) {
m_fmls.update(idx, dependent_expr(m, r, mp(d.pr(), pr), d.dep()));
m_updated = true;
}
}
}
}
// 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;
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);
}
}
if (m.is_not(f, f) && m.is_eq(f, x, y)) {
if (a.is_numeral(x))
std::swap(x, y);
bool strict;
if (a.is_numeral(y, n)) {
if (has_lower(x, k, strict) && !strict && k == n)
assert_lower(x, k, true);
else if (has_upper(x, k, strict) && !strict && k == n)
assert_upper(x, k, true);
}
}
}
void bound_simplifier::assert_upper(expr* x, rational const& n, bool strict) {
scoped_mpq c(nm);
nm.set(c, n.to_mpq());
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());
bp.assert_lower(to_var(x), c, strict);
}
//
// TODO: Use math/interval/interval.h
//
bool bound_simplifier::has_lower(expr* x, rational& n, bool& strict) {
if (is_var(x)) {
unsigned v = to_var(x);
if (bp.has_lower(v)) {
mpq const & q = bp.lower(v, strict);
n = rational(q);
return true;
}
}
if (a.is_numeral(x, n)) {
strict = false;
return true;
}
if (a.is_mod(x)) {
n = rational::zero();
strict = false;
return true;
}
expr* y, *z;
if (a.is_idiv(x, y, z) && has_lower(z, n, strict) && n > 0 && has_lower(y, n, strict))
return true;
if (a.is_add(x)) {
rational bound;
strict = false;
n = 0;
bool is_strict;
for (expr* arg : *to_app(x)) {
if (!has_lower(arg, bound, is_strict))
return false;
strict |= is_strict;
n += bound;
}
return true;
}
if (a.is_mul(x, y, z)) {
// TODO: this is done generally using math/interval/interval.h
rational bound1, bound2;
bool strict1, strict2;
if (has_lower(y, bound1, strict1) && !strict1 &&
has_lower(z, bound1, strict2) && !strict2 &&
bound1 >= 0 && bound2 >= 0) {
n = bound1*bound2;
strict = false;
return true;
}
}
return false;
}
bool bound_simplifier::has_upper(expr* x, rational& n, bool& strict) {
if (is_var(x)) {
unsigned v = to_var(x);
if (bp.has_upper(v)) {
mpq const & q = bp.upper(v, strict);
n = rational(q);
return true;
}
}
// perform light-weight abstract analysis
// y * (u / y) is bounded by u, if y > 0
if (a.is_numeral(x, n)) {
strict = false;
return true;
}
if (a.is_add(x)) {
rational bound;
strict = false;
n = 0;
bool is_strict;
for (expr* arg : *to_app(x)) {
if (!has_upper(arg, bound, is_strict))
return false;
strict |= is_strict;
n += bound;
}
return true;
}
expr* y, *z, *u, *v;
if (a.is_mul(x, y, z) && a.is_idiv(z, u, v) && v == y && has_lower(y, n, strict) && n > 0 && has_upper(u, n, strict))
return true;
if (a.is_idiv(x, y, z) && has_lower(z, n, strict) && n > 0 && has_upper(y, n, strict))
return true;
return false;
}
void bound_simplifier::reset() {
bp.reset();
m_var2expr.reset();
m_expr2var.reset();
m_num_vars = 0;
}

View file

@ -0,0 +1,90 @@
/*++
Copyright (c) 2022 Microsoft Corporation
Module Name:
bound_simplifier.h
Author:
Nikolaj Bjorner (nbjorner) 2023-01-22
Description:
Collects bounds of sub-expressions and uses them to simplify modulus
expressions.
propagate_ineqs_tactic handles other propagations with bounds.
--*/
#pragma once
#include "ast/arith_decl_plugin.h"
#include "ast/rewriter/th_rewriter.h"
#include "ast/simplifiers/dependent_expr_state.h"
#include "ast/simplifiers/bound_propagator.h"
class bound_simplifier : public dependent_expr_simplifier {
arith_util a;
th_rewriter m_rewriter;
unsynch_mpq_manager nm;
small_object_allocator m_alloc;
bound_propagator bp;
unsigned m_num_vars = 0;
ptr_vector<expr> m_var2expr;
unsigned_vector m_expr2var;
bool m_updated = false;
struct rw_cfg;
struct rw;
bool insert_bound(dependent_expr const& de);
void tighten_bound(dependent_expr const& de);
void reset();
expr* to_expr(unsigned v) const {
return m_var2expr.get(v, nullptr);
}
bool is_var(expr* e) const {
return UINT_MAX != m_expr2var.get(e->get_id(), UINT_MAX);
}
unsigned to_var(expr* e) {
unsigned v = m_expr2var.get(e->get_id(), UINT_MAX);
if (v == UINT_MAX) {
v = m_num_vars++;
bp.mk_var(v, a.is_int(e));
m_expr2var.setx(e->get_id(), v, UINT_MAX);
m_var2expr.setx(v, e, nullptr);
}
return v;
}
void assert_lower(expr* x, rational const& n, bool strict);
void assert_upper(expr* x, rational const& n, bool strict);
bool has_upper(expr* x, rational& n, bool& strict);
bool has_lower(expr* x, rational& n, bool& strict);
// e = x + offset
bool is_offset(expr* e, expr* x, rational& offset);
public:
bound_simplifier(ast_manager& m, params_ref const& p, dependent_expr_state& fmls):
dependent_expr_simplifier(m, fmls),
a(m),
m_rewriter(m),
bp(nm, m_alloc, p) {
}
char const* name() const override { return "bounds-simplifier"; }
bool supports_proofs() const override { return false; }
void reduce() override;
};

View file

@ -88,10 +88,13 @@ namespace euf {
expr_ref_vector m_args, m_trail;
expr_sparse_mark m_nonzero;
bool m_enabled = true;
bool m_eliminate_mod = true;
// solve u mod r1 = y -> u = r1*mod!1 + y
void solve_mod(expr* orig, expr* x, expr* y, expr_dependency* d, dep_eq_vector& eqs) {
if (!m_eliminate_mod)
return;
expr* u, * z;
rational r1, r2;
if (!a.is_mod(x, u, z))
@ -296,13 +299,12 @@ break;
add_pos(f);
m_bm(f, d, p);
}
}
void updt_params(params_ref const& p) override {
tactic_params tp(p);
m_enabled = p.get_bool("theory_solver", tp.solve_eqs_ite_solver());
m_eliminate_mod = p.get_bool("eliminate_mod", true);
}
};

View file

@ -0,0 +1,279 @@
/*++
Copyright (c) 2006 Microsoft Corporation
Module Name:
linear_equation.cpp
Abstract:
Basic infrastructure for managing linear equations of the form:
a_1 * x_1 + ... + a_n * x_n = 0
Author:
Leonardo de Moura (leonardo) 2011-06-28
Revision History:
--*/
#include "ast/simplifiers/linear_equation.h"
/**
\brief Return the position of variable x_i in the linear equation.
Return UINT_MAX, if the variable is not in the linear_equation.
*/
unsigned linear_equation::pos(unsigned x_i) const {
int low = 0;
int high = m_size - 1;
while (true) {
int mid = low + ((high - low) / 2);
var x_mid = m_xs[mid];
if (x_i > x_mid) {
low = mid + 1;
if (low > high)
return UINT_MAX;
}
else if (x_i < x_mid) {
high = mid - 1;
if (low > high)
return UINT_MAX;
}
else {
return mid;
}
}
}
void linear_equation_manager::display(std::ostream & out, linear_equation const & eq) const {
unsigned sz = eq.m_size;
for (unsigned i = 0; i < sz; i++) {
if (i > 0)
out << " + ";
out << m.to_string(eq.m_as[i]) << "*x" << eq.m_xs[i];
}
out << " = 0";
}
linear_equation * linear_equation_manager::mk(unsigned sz, mpq * as, var * xs, bool normalized) {
SASSERT(sz > 1);
// compute lcm of the denominators
mpz l;
mpz r;
m.set(l, as[0].denominator());
for (unsigned i = 1; i < sz; i++) {
m.set(r, as[i].denominator());
m.lcm(r, l, l);
}
TRACE("linear_equation_mk", tout << "lcm: " << m.to_string(l) << "\n";);
// copy l * as to m_int_buffer.
m_int_buffer.reset();
for (unsigned i = 0; i < sz; i++) {
TRACE("linear_equation_mk", tout << "before as[" << i << "]: " << m.to_string(as[i]) << "\n";);
m.mul(l, as[i], as[i]);
TRACE("linear_equation_mk", tout << "after as[" << i << "]: " << m.to_string(as[i]) << "\n";);
SASSERT(m.is_int(as[i]));
m_int_buffer.push_back(as[i].numerator());
}
linear_equation * new_eq = mk(sz, m_int_buffer.data(), xs, normalized);
m.del(r);
m.del(l);
return new_eq;
}
linear_equation * linear_equation_manager::mk_core(unsigned sz, mpz * as, var * xs) {
SASSERT(sz > 0);
DEBUG_CODE({
for (unsigned i = 1; i < sz; i++) {
SASSERT(xs[i-1] < xs[i]);
}
});
TRACE("linear_equation_bug", for (unsigned i = 0; i < sz; i++) tout << m.to_string(as[i]) << "*x" << xs[i] << " "; tout << "\n";);
mpz g;
m.set(g, as[0]);
for (unsigned i = 1; i < sz; i++) {
if (m.is_one(g))
break;
if (m.is_neg(as[i])) {
m.neg(as[i]);
m.gcd(g, as[i], g);
m.neg(as[i]);
}
else {
m.gcd(g, as[i], g);
}
}
if (!m.is_one(g)) {
for (unsigned i = 0; i < sz; i++) {
m.div(as[i], g, as[i]);
}
}
TRACE("linear_equation_bug",
tout << "g: " << m.to_string(g) << "\n";
for (unsigned i = 0; i < sz; i++) tout << m.to_string(as[i]) << "*x" << xs[i] << " "; tout << "\n";);
m.del(g);
unsigned obj_sz = linear_equation::get_obj_size(sz);
void * mem = m_allocator.allocate(obj_sz);
linear_equation * new_eq = new (mem) linear_equation();
mpz * new_as = reinterpret_cast<mpz*>(reinterpret_cast<char*>(new_eq) + sizeof(linear_equation));
double * new_app_as = reinterpret_cast<double*>(reinterpret_cast<char*>(new_as) + sz * sizeof(mpz));
var * new_xs = reinterpret_cast<var *>(reinterpret_cast<char*>(new_app_as) + sz * sizeof(double));
for (unsigned i = 0; i < sz; i++) {
new (new_as + i) mpz();
m.set(new_as[i], as[i]);
new_app_as[i] = m.get_double(as[i]);
var x_i = xs[i];
new_xs[i] = x_i;
}
new_eq->m_size = sz;
new_eq->m_as = new_as;
new_eq->m_approx_as = new_app_as;
new_eq->m_xs = new_xs;
return new_eq;
}
linear_equation * linear_equation_manager::mk(unsigned sz, mpz * as, var * xs, bool normalized) {
if (!normalized) {
for (unsigned i = 0; i < sz; i++) {
var x = xs[i];
m_mark.reserve(x+1, false);
m_val_buffer.reserve(x+1);
if (m_mark[x]) {
m.add(m_val_buffer[x], as[i], m_val_buffer[x]);
}
else {
m.set(m_val_buffer[x], as[i]);
m_mark[x] = true;
}
}
unsigned j = 0;
for (unsigned i = 0; i < sz; i++) {
var x = xs[i];
if (m_mark[x]) {
if (!m.is_zero(m_val_buffer[x])) {
xs[j] = xs[i];
m.set(as[j], m_val_buffer[x]);
j++;
}
m_mark[x] = false;
}
}
sz = j;
if (sz <= 1)
return nullptr;
}
else {
DEBUG_CODE({
for (unsigned i = 0; i < sz; i++) {
var x = xs[i];
m_mark.reserve(x+1, false);
SASSERT(!m_mark[x]);
m_mark[x] = true;
}
for (unsigned i = 0; i < sz; i++) {
var x = xs[i];
m_mark[x] = false;
}
});
}
for (unsigned i = 0; i < sz; i++) {
var x = xs[i];
m_val_buffer.reserve(x+1);
m.swap(m_val_buffer[x], as[i]);
}
std::sort(xs, xs+sz);
for (unsigned i = 0; i < sz; i++) {
var x = xs[i];
m.swap(as[i], m_val_buffer[x]);
}
return mk_core(sz, as, xs);
}
linear_equation * linear_equation_manager::mk(mpz const & b1, linear_equation const & eq1, mpz const & b2, linear_equation const & eq2) {
SASSERT(!m.is_zero(b1));
SASSERT(!m.is_zero(b2));
mpz tmp, new_a;
m_int_buffer.reset();
m_var_buffer.reset();
unsigned sz1 = eq1.size();
unsigned sz2 = eq2.size();
unsigned i1 = 0;
unsigned i2 = 0;
while (true) {
if (i1 == sz1) {
// copy remaining entries from eq2
while (i2 < sz2) {
m_int_buffer.push_back(eq2.a(i2));
m.mul(m_int_buffer.back(), b2, m_int_buffer.back());
m_var_buffer.push_back(eq2.x(i2));
i2++;
}
break;
}
if (i2 == sz2) {
// copy remaining entries from eq1
while (i1 < sz1) {
m_int_buffer.push_back(eq1.a(i1));
m.mul(m_int_buffer.back(), b1, m_int_buffer.back());
m_var_buffer.push_back(eq1.x(i1));
i1++;
}
break;
}
var x1 = eq1.x(i1);
var x2 = eq2.x(i2);
if (x1 < x2) {
m_int_buffer.push_back(eq1.a(i1));
m.mul(m_int_buffer.back(), b1, m_int_buffer.back());
m_var_buffer.push_back(eq1.x(i1));
i1++;
}
else if (x1 > x2) {
m_int_buffer.push_back(eq2.a(i2));
m.mul(m_int_buffer.back(), b2, m_int_buffer.back());
m_var_buffer.push_back(eq2.x(i2));
i2++;
}
else {
m.mul(eq1.a(i1), b1, tmp);
m.addmul(tmp, b2, eq2.a(i2), new_a);
if (!m.is_zero(new_a)) {
m_int_buffer.push_back(new_a);
m_var_buffer.push_back(eq1.x(i1));
}
i1++;
i2++;
}
}
m.del(tmp);
m.del(new_a);
SASSERT(m_int_buffer.size() == m_var_buffer.size());
if (m_int_buffer.empty())
return nullptr;
return mk_core(m_int_buffer.size(), m_int_buffer.data(), m_var_buffer.data());
}
void linear_equation_manager::del(linear_equation * eq) {
for (unsigned i = 0; i < eq->m_size; i++) {
m.del(eq->m_as[i]);
}
unsigned obj_sz = linear_equation::get_obj_size(eq->m_size);
m_allocator.deallocate(obj_sz, eq);
}

View file

@ -0,0 +1,82 @@
/*++
Copyright (c) 2006 Microsoft Corporation
Module Name:
linear_equation.h
Abstract:
Basic infrastructure for managing linear equations of the form:
a_1 * x_1 + ... + a_n * x_n = 0
Author:
Leonardo de Moura (leonardo) 2011-06-28
Revision History:
--*/
#pragma once
#include "util/mpq.h"
#include "util/small_object_allocator.h"
#include "util/numeral_buffer.h"
#include "util/double_manager.h"
class linear_equation {
public:
typedef unsigned var;
private:
static unsigned get_obj_size(unsigned sz) { return sizeof(linear_equation) + sz * (sizeof(mpz) + sizeof(double) + sizeof(var)); }
friend class linear_equation_manager;
unsigned m_size;
mpz * m_as; // precise coefficients
double * m_approx_as; // approximated coefficients
var * m_xs; // var ids
linear_equation() {}
public:
unsigned size() const { return m_size; }
mpz const & a(unsigned idx) const { SASSERT(idx < m_size); return m_as[idx]; }
double approx_a(unsigned idx) const { SASSERT(idx < m_size); return m_approx_as[idx]; }
var x(unsigned idx) const { SASSERT(idx < m_size); return m_xs[idx]; }
unsigned pos(unsigned x_i) const;
void get_a(double_manager & m, unsigned idx, double & r) const { r = m_approx_as[idx]; }
template<typename NumManager>
void get_a(NumManager & m, unsigned idx, mpq & r) const { m.set(r, m_as[idx]); }
template<typename NumManager>
void get_a(NumManager & m, unsigned idx, mpz & r) const { m.set(r, m_as[idx]); }
};
class linear_equation_manager {
public:
typedef unsynch_mpq_manager numeral_manager;
typedef linear_equation::var var;
typedef numeral_buffer<mpz, numeral_manager> mpz_buffer;
private:
typedef svector<var> var_buffer;
small_object_allocator & m_allocator;
numeral_manager & m;
mpz_buffer m_int_buffer;
mpz_buffer m_val_buffer;
char_vector m_mark;
var_buffer m_var_buffer;
linear_equation * mk_core(unsigned sz, mpz * as, var * xs);
public:
linear_equation_manager(numeral_manager & _m, small_object_allocator & a):m_allocator(a), m(_m), m_int_buffer(m), m_val_buffer(m) {}
linear_equation * mk(unsigned sz, mpq * as, var * xs, bool normalized = false);
linear_equation * mk(unsigned sz, mpz * as, var * xs, bool normalized = false);
void del(linear_equation * eq);
// Return b1 * eq1 + b2 * eq2
// return 0 if the b1 * eq1 + b2 * eq2 == 0
linear_equation * mk(mpz const & b1, linear_equation const & eq1, mpz const & b2, linear_equation const & eq2);
void display(std::ostream & out, linear_equation const & eq) const;
};

View file

@ -294,6 +294,7 @@ namespace euf {
r.insert("theory_solver", CPK_BOOL, "theory solvers.", "true");
r.insert("ite_solver", CPK_BOOL, "use if-then-else solver.", "true");
r.insert("context_solve", CPK_BOOL, "solve equalities under disjunctions.", "false");
r.insert("eliminate_mod", CPK_BOOL, "eliminate modulus from equations", "true");
}
void solve_eqs::collect_statistics(statistics& st) const {