#pragma once
#include "ast/ast_smt2_pp.h"
namespace smt {
template<typename Ext>
expr * theory_arith<Ext>::mk_nary_mul(unsigned sz, expr * const * args, bool is_int) {
if (sz == 0)
return m_util.mk_numeral(rational(1), is_int);
if (sz == 1)
return args[0];
if (sz == 2)
return m_util.mk_mul(args[0], args[1]);
if (m_util.is_numeral(args[0]))
return m_util.mk_mul(args[0], m_util.mk_mul(sz - 1, args + 1));
return m_util.mk_mul(sz, args);
}
template<typename Ext>
expr * theory_arith<Ext>::mk_nary_add(unsigned sz, expr * const * args, bool is_int) {
if (sz == 0)
return m_util.mk_numeral(rational(0), is_int);
if (sz == 1)
return args[0];
return m_util.mk_add(sz, args);
}
template<typename Ext>
expr * theory_arith<Ext>::mk_nary_add(unsigned sz, expr * const * args) {
SASSERT(sz != 0);
return mk_nary_add(sz, args, false);
}
template<typename Ext>
void theory_arith<Ext>::mark_var(theory_var v, svector<theory_var> & vars, var_set & already_found) {
if (already_found.contains(v))
return;
already_found.insert(v);
vars.push_back(v);
}
template<typename Ext>
void theory_arith<Ext>::mark_dependents(theory_var v, svector<theory_var> & vars, var_set & already_found, row_set & already_visited_rows) {
if (is_pure_monomial(v)) {
expr * n = var2expr(v);
SASSERT(m_util.is_mul(n));
for (expr * curr : *to_app(n)) {
if (ctx.e_internalized(curr)) {
theory_var v = expr2var(curr);
SASSERT(v != null_theory_var);
mark_var(v, vars, already_found);
}
}
}
if (is_fixed(v))
return;
column & c = m_columns[v];
for (auto& ce : c) {
if (ce.is_dead() || already_visited_rows.contains(ce.m_row_id))
continue;
TRACE(non_linear_bug, tout << "visiting row: " << ce.m_row_id << "\n";);
already_visited_rows.insert(ce.m_row_id);
row & r = m_rows[ce.m_row_id];
theory_var s = r.get_base_var();
if (is_quasi_base(s))
continue;
if (s != null_theory_var && is_free(s) && s != v)
continue;
for (auto& re : r) {
if (!re.is_dead() && !is_fixed(re.m_var))
mark_var(re.m_var, vars, already_found);
CTRACE(non_linear, !re.is_dead() && is_fixed(re.m_var), tout << "skipped fixed\n");
}
}
}
template<typename Ext>
void theory_arith<Ext>::get_non_linear_cluster(svector<theory_var> & vars) {
if (m_nl_monomials.empty())
return;
var_set already_found;
row_set already_visited_rows;
for (theory_var v : m_nl_monomials) {
expr * n = var2expr(v);
if (ctx.is_relevant(n))
mark_var(v, vars, already_found);
}
for (unsigned idx = 0; idx < vars.size(); ++idx) {
theory_var v = vars[idx];
TRACE(non_linear, tout << "marking dependents of: v" << v << "\n";);
mark_dependents(v, vars, already_found, already_visited_rows);
}
TRACE(non_linear, tout << "variables in non linear cluster: ";
for (theory_var v : vars) tout << "v" << v << " "; tout << "\n";
for (theory_var v : m_nl_monomials) tout << "non-linear v" << v << " " << mk_pp(var2expr(v), m) << "\n";);
}
template<typename Ext>
typename theory_arith<Ext>::n_var_power_pair theory_arith<Ext>::analyze_monomial(expr * m) const {
buffer<var_power_pair> vp;
decompose_monomial(m, vp);
unsigned c = 0;
var_power_pair q(nullptr, 0);
for (auto const& p : vp) {
if (p.second % 2 == 1 && is_free(p.first)) {
c++;
q = p;
if (c > 1)
break;
}
}
return std::make_pair(c, q);
}
template<typename Ext>
rational theory_arith<Ext>::decompose_monomial(expr* m, buffer<var_power_pair>& vp) const {
rational coeff(1);
vp.reset();
expr_fast_mark1 mark;
auto insert = [&](expr* arg) {
rational r;
if (m_util.is_numeral(arg, r))
coeff *= r;
else if (mark.is_marked(arg)) {
for (unsigned i = vp.size(); i-- > 0; ) {
if (vp[i].first == arg) {
vp[i].second++;
break;
}
}
}
else {
mark.mark(arg);
vp.push_back(var_power_pair(arg, 1));
}
};
while (m_util.is_mul(m)) {
unsigned sz = to_app(m)->get_num_args();
for (unsigned i = 0; i + 1 < sz; ++i) {
insert(to_app(m)->get_arg(i));
}
m = to_app(m)->get_arg(sz - 1);
}
insert(m);
return coeff;
}
template<typename Ext>
interval theory_arith<Ext>::mk_interval_for(theory_var v) {
bound * l = lower(v);
bound * u = upper(v);
if (l && u) {
if (l->get_value() == u->get_value() && !l->get_value().get_infinitesimal().to_rational().is_zero()) {
return interval(m_dep_manager);
}
return interval(m_dep_manager,
l->get_value().get_rational().to_rational(),
l->get_value().get_infinitesimal().to_rational().is_pos(),
m_dep_manager.mk_leaf(l),
u->get_value().get_rational().to_rational(),
u->get_value().get_infinitesimal().to_rational().is_neg(),
m_dep_manager.mk_leaf(u));
}
else if (l) {
return interval(m_dep_manager,
l->get_value().get_rational().to_rational(),
l->get_value().get_infinitesimal().to_rational().is_pos(),
true,
m_dep_manager.mk_leaf(l));
}
else if (u) {
return interval(m_dep_manager,
u->get_value().get_rational().to_rational(),
u->get_value().get_infinitesimal().to_rational().is_neg(),
false,
m_dep_manager.mk_leaf(u));
}
else {
return interval(m_dep_manager);
}
}
template<typename Ext>
interval theory_arith<Ext>::mk_interval_for(expr * n) {
if (!has_var(n))
return interval(m_dep_manager);
return mk_interval_for(expr2var(n));
}
template<typename Ext>
void theory_arith<Ext>::mul_bound_of(expr * var, unsigned power, interval & target) {
theory_var v = expr2var(var);
interval i = mk_interval_for(v);
TRACE(non_linear,
display_interval(tout << "bound: ",i); tout << i << "\n";
tout << mk_pp(var, get_manager()) << "\n";
tout << "power " << power << ": " << expt(i, power) << "\n";
display_interval(tout << "target before: ", target); tout << "\n";);
i.expt(power);
target *= i;
get_manager().limit().inc((target.is_lower_open() || target.minus_infinity()) ? 1 : target.get_lower_value().bitsize());
get_manager().limit().inc((target.is_upper_open() || target.plus_infinity()) ? 1 : target.get_upper_value().bitsize());
TRACE(non_linear, display_interval(tout << "target after: ", target); tout << "\n";);
}
template<typename Ext>
interval theory_arith<Ext>::evaluate_as_interval(expr * n) {
expr* arg;
rational val;
#define TR() TRACE(nl_evaluate, tout << "eval: " << mk_bounded_pp(n, get_manager(), 10) << "\n";\
display_nested_form(tout, n); tout << "\ninterval: " << r << "\n";);
if (has_var(n)) {
interval r = mk_interval_for(n);
TR();
return r;
}
else if (m_util.is_add(n)) {
interval r(m_dep_manager, rational(0));
for (expr* arg : *to_app(n)) {
r += evaluate_as_interval(arg);
}
TR();
return r;
}
else if (m_util.is_mul(n)) {
buffer<var_power_pair> vp;
rational coeff = decompose_monomial(n, vp);
interval r(m_dep_manager, coeff);
for (var_power_pair const& p : vp) {
expr * var = p.first;
unsigned power = p.second;
interval it = evaluate_as_interval(var);
it.expt(power);
r *= it;
}
TR();
return r;
}
else if (m_util.is_to_real(n, arg)) {
return evaluate_as_interval(arg);
}
else if (m_util.is_numeral(n, val)) {
interval r = interval(m_dep_manager, val);
TR();
return r;
}
else {
TRACE(nl_evaluate, tout << "is unknown\n";);
return interval(m_dep_manager);
}
}
template<typename Ext>
void theory_arith<Ext>::display_monomial(std::ostream & out, expr * n) const {
bool first = true;
buffer<var_power_pair> vp;
rational coeff = decompose_monomial(n, vp);
if (!coeff.is_one()) {
out << coeff;
first = false;
}
for (auto const& p : vp) {
SASSERT(p.first != 0);
if (first) first = false; else out << " * ";
out << mk_bounded_pp(p.first, get_manager()) << "^" << p.second;
}
}
template<typename Ext>
void theory_arith<Ext>::dependency2new_bound(v_dependency * dep, derived_bound& new_bound) {
ptr_vector<void> bounds;
m_dep_manager.linearize(dep, bounds);
m_tmp_lit_set.reset();
m_tmp_eq_set.reset();
for (void* _b : bounds) {
bound * b = static_cast<bound*>(_b);
accumulate_justification(*b, new_bound, numeral::zero(), m_tmp_lit_set, m_tmp_eq_set);
}
}
template<typename Ext>
void theory_arith<Ext>::mk_derived_nl_bound(theory_var v, inf_numeral const & coeff, bound_kind k, v_dependency * dep) {
inf_numeral coeff_norm = normalize_bound(v, coeff, k);
derived_bound * new_bound = alloc(derived_bound, v, coeff_norm, k);
m_bounds_to_delete.push_back(new_bound);
m_asserted_bounds.push_back(new_bound);
dependency2new_bound(dep, *new_bound);
TRACE(non_linear, new_bound->display(*this, tout); tout << "\n";);
}
template<typename Ext>
bool theory_arith<Ext>::update_bounds_using_interval(theory_var v, interval const & i) {
SASSERT(v != null_theory_var);
bool r = false;
if (!i.minus_infinity()) {
inf_numeral new_lower(i.get_lower_value());
if (i.is_lower_open()) {
if (is_int(v)) {
if (new_lower.is_int()) {
new_lower += rational::one();
}
else {
new_lower = ceil(new_lower.get_rational());
}
}
else {
new_lower += get_epsilon(v);
}
}
bound * old_lower = lower(v);
if (old_lower == nullptr || new_lower > old_lower->get_value()) {
TRACE(non_linear, tout << "NEW lower bound for v" << v << " " << mk_pp(var2expr(v), get_manager())
<< " " << new_lower << "\n";
display_interval(tout, i); tout << "\n";);
mk_derived_nl_bound(v, new_lower, B_LOWER, i.get_lower_dependencies());
r = true;
}
}
if (!i.plus_infinity()) {
inf_numeral new_upper(i.get_upper_value());
if (i.is_upper_open()) {
if (is_int(v)) {
if (new_upper.is_int()) {
new_upper -= rational::one();
}
else {
new_upper = floor(new_upper.get_rational());
}
}
else {
new_upper -= get_epsilon(v);
}
}
bound * old_upper = upper(v);
if (old_upper == nullptr || new_upper < old_upper->get_value()) {
TRACE(non_linear, tout << "NEW upper bound for v" << v << " " << new_upper << "\n";
display_interval(tout, i); tout << "\n";);
mk_derived_nl_bound(v, new_upper, B_UPPER, i.get_upper_dependencies());
r = true;
}
}
return r;
}
template<typename Ext>
bool theory_arith<Ext>::update_bounds_using_interval(expr * n, interval const & i) {
SASSERT(expr2var(n) != null_theory_var);
TRACE(non_linear, tout << "NL bounds for m: " << i << "\n" << mk_pp(n, get_manager()) << "\n";);
return update_bounds_using_interval(expr2var(n), i);
}
template<typename Ext>
bool theory_arith<Ext>::propagate_nl_upward(expr * m) {
SASSERT(is_pure_monomial(m));
TRACE(nl_arith_bug, tout << "processing upward:\n" << mk_pp(m, get_manager()) << "\n";);
buffer<var_power_pair> vp;
rational coeff = decompose_monomial(m, vp);
interval new_bounds(m_dep_manager, coeff);
for (var_power_pair const& p : vp) {
expr * var = p.first;
unsigned power = p.second;
TRACE(nl_arith_bug, tout << "interval before: " << new_bounds << "\n";
theory_var v = expr2var(var);
interval i = mk_interval_for(v);
display_var(tout, v);
tout << "interval for v" << i << " " << mk_pp(var, get_manager()) << "\npower: " << power << " " << expt(i, power) << "\n";);
mul_bound_of(var, power, new_bounds);
TRACE(nl_arith_bug, tout << "interval after: " << new_bounds << "\n";);
}
return update_bounds_using_interval(m, new_bounds);
}
template<typename Ext>
bool theory_arith<Ext>::propagate_nl_downward(expr * n, var_power_pair const& p) {
SASSERT(is_pure_monomial(n));
expr * v = p.first;
unsigned power = p.second;
if (power != 1)
return false;
buffer<var_power_pair> vp;
rational coeff = decompose_monomial(n, vp);
interval other_bounds(m_dep_manager, coeff);
for (var_power_pair const& p : vp) {
if (p.first == v)
continue;
expr * var = p.first;
unsigned power = p.second;
mul_bound_of(var, power, other_bounds);
}
if (other_bounds.contains_zero())
return false; interval r = mk_interval_for(n);
TRACE(nl_arith_bug, tout << "m: " << mk_ismt2_pp(n, get_manager()) << "\nv: " << mk_ismt2_pp(v, get_manager()) <<
"\npower: " << power << "\n";
display_interval(tout << "monomial bounds\n", r);
display_interval(tout << "other bounds\n", other_bounds);
);
r /= other_bounds;
return update_bounds_using_interval(v, r);
}
template<typename Ext>
bool theory_arith<Ext>::propagate_nl_bounds(expr * m) {
TRACE(non_linear, tout << "propagate several bounds using:\n"; display_monomial(tout, m); tout << "\n";);
bool result = propagate_nl_upward(m);
buffer<var_power_pair> vp;
rational coeff = decompose_monomial(m, vp);
for (auto const& p : vp) {
if (propagate_nl_downward(m, p)) {
m_stats.m_nl_bounds++;
result = true;
}
}
return result;
}
template<typename Ext>
bool theory_arith<Ext>::propagate_nl_bounds() {
m_dep_manager.reset();
bool propagated = false;
for (unsigned i = 0; i < m_nl_monomials.size(); i++) {
theory_var v = m_nl_monomials[i];
expr * m = var2expr(v);
if (!ctx.is_relevant(m))
continue;
auto p = analyze_monomial(m);
TRACE(propagate_nl_bound, tout << "m: " << mk_ismt2_pp(m, get_manager()) << "\n" << "p: " << p.first << "\n";);
unsigned num_bad_vars = p.first;
var_power_pair q = p.second;
SASSERT(num_bad_vars != 1 || q.first != nullptr);
if (num_bad_vars >= 2)
continue;
bool is_free_m = is_free(m);
TRACE(propagate_nl_bound, tout << "is_free_m: " << is_free_m << "\n";);
if (num_bad_vars == 1 && is_free_m)
continue;
if (num_bad_vars == 0) {
if (!is_free_m) {
if (propagate_nl_bounds(m))
propagated = true;
}
else {
if (propagate_nl_upward(m)) {
m_stats.m_nl_bounds++;
propagated = true;
}
}
}
else {
SASSERT (!is_free_m);
if (propagate_nl_downward(m, q)) {
m_stats.m_nl_bounds++;
propagated = true;
}
}
}
return propagated;
}
template<typename Ext>
rational theory_arith<Ext>::get_value(theory_var v, bool & computed_epsilon) {
inf_numeral const & val = get_value(v);
if (!val.get_infinitesimal().is_zero() && !computed_epsilon) {
compute_epsilon();
refine_epsilon();
computed_epsilon = true;
m_model_depends_on_computed_epsilon = true;
}
return val.get_rational().to_rational() + m_epsilon.to_rational() * val.get_infinitesimal().to_rational();
}
template<typename Ext>
bool theory_arith<Ext>::check_monomial_assignment(theory_var v, bool & computed_epsilon) {
SASSERT(is_pure_monomial(var2expr(v)));
expr * m = var2expr(v);
rational val(1), v_val;
for (expr* arg : *to_app(m)) {
theory_var curr = expr2var(arg);
SASSERT(curr != null_theory_var);
v_val = get_value(curr, computed_epsilon);
TRACE(non_linear, tout << mk_pp(arg, get_manager()) << " = " << v_val << "\n";);
val *= v_val;
}
v_val = get_value(v, computed_epsilon);
TRACE(non_linear, tout << "v" << v << " := " << v_val << " == " << val << "\n";);
return v_val == val;
}
template<typename Ext>
bool theory_arith<Ext>::check_monomial_assignments() {
bool computed_epsilon = false;
for (theory_var v : m_nl_monomials) {
TRACE(non_linear, tout << "v" << v << " is relevant: " << ctx.is_relevant(get_enode(v)) << "\n");
if (ctx.is_relevant(get_enode(v)) && !check_monomial_assignment(v, computed_epsilon)) {
TRACE(non_linear, tout << "check_monomial_assignment failed for:\n" << mk_ismt2_pp(var2expr(v), get_manager()) << "\n";
display_var(tout, v););
return false;
}
}
return true;
}
template<typename Ext>
theory_var theory_arith<Ext>::find_nl_var_for_branching() {
TRACE(nl_branching, tout << "looking for variable to branch...\n"; display(tout););
theory_var target = null_theory_var;
bool bounded = false;
unsigned n = 0;
numeral range;
for (unsigned j = 0; j < m_nl_monomials.size(); ++j) {
theory_var v = m_nl_monomials[j];
if (is_real(v))
continue;
bool computed_epsilon = false;
bool r = check_monomial_assignment(v, computed_epsilon);
if (!r) {
expr * m = get_enode(v)->get_expr();
SASSERT(is_pure_monomial(m));
for (expr * arg : *to_app(m)) {
theory_var curr = ctx.get_enode(arg)->get_th_var(get_id());
TRACE(nl_branching, tout << "target: v" << target << ", curr: v" << curr << "\n";);
if (!is_fixed(curr) && is_int(curr)) {
if (is_bounded(curr)) {
numeral new_range;
new_range = upper_bound(curr).get_rational();
new_range -= lower_bound(curr).get_rational();
if (!bounded || new_range < range) {
target = curr;
range = new_range;
bounded = true;
}
}
else if (!bounded) {
n++;
TRACE(nl_branching, tout << "n: " << n << "\n";);
if (m_random()%n == 0)
target = curr;
SASSERT(target != null_theory_var);
}
SASSERT(target != null_theory_var);
}
TRACE(nl_branching, tout << "after target: v" << target << "\n";);
}
}
}
return target;
}
template<typename Ext>
bool theory_arith<Ext>::branch_nl_int_var(theory_var v) {
TRACE(non_linear, tout << "BRANCHING on v" << v << "\n";);
m_stats.m_nl_branching++;
SASSERT(is_int(v));
expr_ref bound(get_manager());
if (lower(v))
bound = m_util.mk_le(var2expr(v), m_util.mk_numeral(lower_bound(v).get_rational().to_rational(), true));
else if (upper(v))
bound = m_util.mk_ge(var2expr(v), m_util.mk_numeral(upper_bound(v).get_rational().to_rational(), true));
else
bound = m_util.mk_eq(var2expr(v), m_util.mk_numeral(rational(0), true));
TRACE(non_linear, tout << "new bound:\n" << mk_pp(bound, get_manager()) << "\n";);
ast_manager & m = get_manager();
{
std::function<expr*(void)> fn = [&]() { return m.mk_or(bound, m.mk_not(bound)); };
scoped_trace_stream _sts(*this, fn);
ctx.internalize(bound, true);
}
ctx.mark_as_relevant(bound.get());
literal l = ctx.get_literal(bound);
SASSERT(!l.sign());
ctx.set_true_first_flag(l.var()); return true;
}
template<typename Ext>
bool theory_arith<Ext>::is_monomial_linear(expr * m) const {
SASSERT(is_pure_monomial(m));
unsigned num_nl_vars = 0;
for (expr* arg : *to_app(m)) {
if (!ctx.e_internalized(arg))
return false;
theory_var _var = expr2var(arg);
CTRACE(non_linear, is_fixed(_var),
tout << mk_pp(arg, get_manager()) << " is fixed: " << lower_bound(_var) << "\n";);
if (!is_fixed(_var)) {
num_nl_vars++;
}
else if (lower_bound(_var).is_zero()) {
return true;
}
}
return num_nl_vars <= 1;
}
template<typename Ext>
typename theory_arith<Ext>::numeral theory_arith<Ext>::get_monomial_fixed_var_product(expr * m) const {
SASSERT(is_pure_monomial(m));
numeral r(1);
for (expr * arg : *to_app(m)) {
theory_var _var = expr2var(arg);
if (is_fixed(_var))
r *= lower_bound(_var).get_rational();
}
TRACE(arith, tout << mk_pp(m, get_manager()) << " " << r << "\n";);
return r;
}
template<typename Ext>
expr * theory_arith<Ext>::get_monomial_non_fixed_var(expr * m) const {
SASSERT(is_pure_monomial(m));
for (expr* arg : *to_app(m)) {
if (!is_fixed(expr2var(arg)))
return arg;
}
return nullptr;
}
template<typename Ext>
bool theory_arith<Ext>::propagate_linear_monomial(theory_var v) {
TRACE(non_linear_verbose, tout << "checking whether v" << v << " became linear...\n";);
if (m_data[v].m_nl_propagated)
return false; expr * m = var2expr(v);
if (!is_monomial_linear(m))
return false;
m_stats.m_nl_linear++;
m_data[v].m_nl_propagated = true;
m_nl_propagated.push_back(v);
TRACE(non_linear, tout << "v" << v << " is linear " << mk_pp(m, get_manager()) << "\n";);
numeral k = get_monomial_fixed_var_product(m);
TRACE(non_linear, tout << "new linear monomial... k: " << k << "\n";);
expr * x_n = k.is_zero() ? nullptr : get_monomial_non_fixed_var(m);
TRACE(non_linear_bug, if (x_n != 0) { tout << "x_n: " << mk_bounded_pp(x_n, get_manager()) << "\nx_n: #" << x_n->get_id() << "\n"; });
derived_bound * new_lower = nullptr;
derived_bound * new_upper = nullptr;
if (x_n != nullptr) {
TRACE(non_linear, tout << "x_n: " << mk_pp(x_n, get_manager()) << "\n";);
k.neg();
expr * k_x_n = k.is_one() ? x_n : m_util.mk_mul(m_util.mk_numeral(k.to_rational(), is_int(v)), x_n);
expr * rhs = m_util.mk_add(m, k_x_n);
TRACE(non_linear_bug, tout << "rhs: " << mk_bounded_pp(rhs, get_manager(),5) << "\ninternalized: " << ctx.e_internalized(rhs) << "\n";);
if (!has_var(rhs)) {
ctx.internalize(rhs, false);
ctx.mark_as_relevant(rhs);
}
TRACE(non_linear_bug, tout << "enode: " << ctx.get_enode(rhs) << " enode_id: " << ctx.get_enode(rhs)->get_owner_id() << "\n";);
IF_VERBOSE(3,
for (auto* arg : *to_app(m))
if (is_fixed(expr2var(arg)))
verbose_stream() << mk_pp(arg, get_manager()) << " = " << -k << "\n");
theory_var new_v = expr2var(rhs);
SASSERT(new_v != null_theory_var);
new_lower = alloc(derived_bound, new_v, inf_numeral(0), B_LOWER);
new_upper = alloc(derived_bound, new_v, inf_numeral(0), B_UPPER);
}
else {
TRACE(non_linear, tout << "all variables are fixed, and bound is: " << k << "\n";);
new_lower = alloc(derived_bound, v, inf_numeral(k), B_LOWER);
new_upper = alloc(derived_bound, v, inf_numeral(k), B_UPPER);
}
SASSERT(new_lower);
SASSERT(new_upper);
m_bounds_to_delete.push_back(new_lower);
m_asserted_bounds.push_back(new_lower);
m_bounds_to_delete.push_back(new_upper);
m_asserted_bounds.push_back(new_upper);
m_tmp_lit_set.reset();
m_tmp_eq_set.reset();
SASSERT(is_pure_monomial(m));
bool found_zero = false;
for (unsigned i = 0; !found_zero && i < to_app(m)->get_num_args(); i++) {
expr * arg = to_app(m)->get_arg(i);
theory_var _var = expr2var(arg);
if (is_fixed(_var)) {
bound * l = lower(_var);
bound * u = upper(_var);
if (l->get_value().is_zero()) {
SASSERT(k.is_zero());
found_zero = true;
m_tmp_lit_set.reset();
m_tmp_eq_set.reset();
new_lower->m_lits.reset();
new_lower->m_eqs.reset();
}
accumulate_justification(*l, *new_lower, numeral::zero(), m_tmp_lit_set, m_tmp_eq_set);
TRACE(non_linear,
for (literal l : new_lower->m_lits) {
ctx.display_detailed_literal(tout, l) << " ";
}
tout << "\n";);
accumulate_justification(*u, *new_lower, numeral::zero(), m_tmp_lit_set, m_tmp_eq_set);
TRACE(non_linear,
for (literal l : new_lower->m_lits) {
ctx.display_detailed_literal(tout, l) << " ";
}
tout << "\n";);
}
}
new_upper->m_lits.append(new_lower->m_lits);
new_upper->m_eqs.append(new_lower->m_eqs);
TRACE(non_linear,
new_lower->display(*this, tout << "lower: "); tout << "\n";
new_upper->display(*this, tout << "upper: "); tout << "\n";
for (literal lit : new_upper->m_lits) {
ctx.display_detailed_literal(tout, lit) << " ";
}
tout << "\n";);
return true;
}
template<typename Ext>
bool theory_arith<Ext>::propagate_linear_monomials() {
if (!m_params.m_nl_arith_propagate_linear_monomials)
return false;
if (!reflection_enabled())
return false;
TRACE(non_linear_verbose, tout << "propagating linear monomials...\n";);
bool p = false;
for (unsigned i = 0; i < m_nl_monomials.size(); i++) {
if (propagate_linear_monomial(m_nl_monomials[i]))
p = true;
}
CTRACE(non_linear, p, display(tout););
return p;
}
template<typename Ext>
bool theory_arith<Ext>::is_problematic_non_linear_row(row const & r) {
m_tmp_var_set.reset();
typename vector<row_entry>::const_iterator it = r.begin_entries();
typename vector<row_entry>::const_iterator end = r.end_entries();
for (; it != end; ++it) {
if (!it->is_dead()) {
theory_var v = it->m_var;
if (is_fixed(v))
continue;
if (is_pure_monomial(v)) {
expr * m = var2expr(v);
for (expr* arg : *to_app(m)) {
theory_var curr = expr2var(arg);
if (m_tmp_var_set.contains(curr))
return true;
}
SASSERT(m == var2expr(v));
for (expr* arg : *to_app(m)) {
theory_var curr = expr2var(arg);
if (!is_fixed(curr))
m_tmp_var_set.insert(curr);
}
}
else {
if (m_tmp_var_set.contains(v))
return true;
SASSERT(!is_fixed(v));
m_tmp_var_set.insert(v);
}
}
}
return false;
}
template<typename Ext>
bool theory_arith<Ext>::is_mixed_real_integer(row const & r) const {
bool found_int = false;
bool found_real = false;
typename vector<row_entry>::const_iterator it = r.begin_entries();
typename vector<row_entry>::const_iterator end = r.end_entries();
for (; it != end; ++it) {
if (it->is_dead())
continue;
theory_var v = it->m_var;
if (is_int(v))
found_int = true;
if (is_real(v))
found_real = true;
if (found_int && found_real)
return true;
}
return false;
}
template<typename Ext>
bool theory_arith<Ext>::is_integer(row const & r) const {
typename vector<row_entry>::const_iterator it = r.begin_entries();
typename vector<row_entry>::const_iterator end = r.end_entries();
for (; it != end; ++it) {
if (it->is_dead())
continue;
theory_var v = it->m_var;
if (!is_int(v))
return false;
}
return true;
}
template<typename Ext>
void theory_arith<Ext>::display_coeff_exprs(std::ostream & out, buffer<coeff_expr> const & p) const {
bool first = true;
for (coeff_expr const& ce : p) {
if (first)
first = false;
else
out << "+\n";
out << ce.first << " * " << mk_pp(ce.second, get_manager()) << "\n";
}
}
template<typename Ext>
bool theory_arith<Ext>::get_polynomial_info(buffer<coeff_expr> const & p, sbuffer<var_num_occs> & varinfo) {
varinfo.reset();
m_var2num_occs.reset();
auto add_occ = [&](expr* v) {
if (has_var(v) && !is_fixed(expr2var(v))) {
TRACE(nl_info, tout << "adding occ: " << mk_bounded_pp(v, get_manager()) << "\n";);
unsigned occs = 0;
m_var2num_occs.find(v, occs);
m_var2num_occs.insert(v, 1 + occs);
}
};
for (auto const& ce : p) {
expr * m = ce.second;
if (m_util.is_numeral(m)) {
continue;
}
else if (false && m_util.is_add(m)) return false;
else if (ctx.e_internalized(m) && !is_pure_monomial(m))
add_occ(m);
else {
buffer<var_power_pair> vp;
decompose_monomial(m, vp);
for (auto const& p : vp) {
add_occ(p.first);
}
}
}
for (auto const& vn : m_var2num_occs) {
if (vn.m_value > 1)
varinfo.push_back(var_num_occs(vn.m_key, vn.m_value));
}
return true;
}
template<typename Ext>
expr_ref theory_arith<Ext>::p2expr(buffer<coeff_expr> & p) {
SASSERT(!p.empty());
TRACE(p2expr_bug, display_coeff_exprs(tout, p););
ptr_buffer<expr> args;
rational c2;
for (coeff_expr const& ce : p) {
rational const & c = ce.first;
expr * var = ce.second;
if (m_util.is_numeral(var, c2)) {
expr* m = m_util.mk_numeral(c * c2, c.is_int() && m_util.is_int(var));
m_nl_new_exprs.push_back(m);
args.push_back(m);
}
else if (!c.is_one()) {
expr * m = m_util.mk_mul(m_util.mk_numeral(c, c.is_int() && m_util.is_int(var)), var);
m_nl_new_exprs.push_back(m);
args.push_back(m);
}
else {
args.push_back(var);
}
}
SASSERT(!args.empty());
expr_ref r(mk_nary_add(args.size(), args.data()), get_manager());
m_nl_new_exprs.push_back(r);
return r;
}
template<typename Ext>
expr * theory_arith<Ext>::power(expr * var, unsigned power) {
SASSERT(power > 0);
expr * r = var;
for (unsigned i = 1; i < power; i++)
r = m_util.mk_mul(var, r);
m_nl_new_exprs.push_back(r);
return r;
}
template<typename Ext>
bool theory_arith<Ext>::in_monovariate_monomials(buffer<coeff_expr> & p, expr * var,
unsigned & i1, rational & c1, unsigned & n1, unsigned & i2, rational & c2, unsigned & n2) {
int idx = 0;
auto set_result = [&](unsigned i, unsigned power, coeff_expr const& ce) {
if (idx == 0) {
c1 = ce.first;
n1 = power;
idx = 1;
i1 = i;
}
else if (idx == 1) {
c2 = ce.first;
n2 = power;
idx = 2;
i2 = i;
}
else {
idx = 3;
}
};
for (unsigned i = 0; i < p.size() && idx != 3; ++i) {
auto const& ce = p[i];
expr * m = ce.second;
if (is_pure_monomial(m)) {
buffer<var_power_pair> vp;
decompose_monomial(m, vp);
for (auto const& p : vp) {
if (p.first == var) {
if (vp.size() > 1)
return false;
set_result(i, p.second, ce); }
}
}
else if (m == var) {
set_result(i, 1, ce);
}
}
if (idx != 2)
return false;
return true;
}
template<typename Ext>
bool theory_arith<Ext>::is_pure_monomial(expr* mon) const {
if (!m_util.is_mul(mon))
return false;
app* p = to_app(mon);
for (expr* arg : *p)
if (m_util.is_numeral(arg) || m_util.is_mul(arg))
return false;
return true;
}
template<typename Ext>
void theory_arith<Ext>::display_nested_form(std::ostream & out, expr * p) {
if (has_var(p)) {
out << "#" << p->get_id();
}
else if (m_util.is_add(p)) {
SASSERT(!has_var(p));
out << "(";
for (unsigned i = 0; i < to_app(p)->get_num_args(); i++) {
if (i > 0) out << " + ";
display_nested_form(out, to_app(p)->get_arg(i));
}
out << ")";
}
else if (m_util.is_mul(p)) {
buffer<var_power_pair> vp;
rational c = decompose_monomial(p, vp);
bool first = true;
if (!c.is_one()) {
out << c;
first = false;
}
for (auto const& pair : vp) {
if (first) first = false; else out << "*";
expr * var = pair.first;
unsigned power = pair.second;
display_nested_form(out, var);
if (power != 1)
out << "^" << power;
}
}
else {
rational val;
if (m_util.is_numeral(p, val))
out << val;
else
out << "[unknown #" << p->get_id() << "]";
}
}
template<typename Ext>
unsigned theory_arith<Ext>::get_degree_of(expr * m, expr * var) {
if (m == var)
return 1;
if (is_pure_monomial(m)) {
buffer<var_power_pair> vp;
decompose_monomial(m, vp);
for (auto const& p : vp) {
if (p.first == var)
return p.second;
}
}
return 0;
}
template<typename Ext>
unsigned theory_arith<Ext>::get_min_degree(buffer<coeff_expr> & p, expr * var) {
SASSERT(!p.empty());
SASSERT(var != 0);
unsigned d = UINT_MAX; for (coeff_expr const& ce : p) {
expr * m = ce.second;
d = std::min(d, get_degree_of(m, var));
if (d == 0)
return d;
}
SASSERT(d != UINT_MAX);
return d;
}
template<typename Ext>
expr * theory_arith<Ext>::factor(expr * m, expr * var, unsigned d) {
TRACE(factor, tout << "m: " << mk_pp(m, get_manager()) << "\nvar: " << mk_pp(var, get_manager()) << "\nd: " << d << "\n";);
if (d == 0)
return m;
if (m == var) {
SASSERT(d == 1);
expr * result = m_util.mk_numeral(rational(1), m_util.is_int(var));
m_nl_new_exprs.push_back(result);
return result;
}
ptr_buffer<expr> new_args;
unsigned idx = 0;
auto insert = [&](expr* arg) {
if (idx < d && var == arg)
++idx;
else
new_args.push_back(arg);
};
while (m_util.is_mul(m) && idx < d) {
unsigned sz = to_app(m)->get_num_args();
for (unsigned i = 0; i + 1 < sz; ++i) {
insert(to_app(m)->get_arg(i));
}
m = to_app(m)->get_arg(sz - 1);
}
insert(m);
SASSERT(idx == d);
TRACE(factor_bug, tout << "new_args:\n"; for(unsigned i = 0; i < new_args.size(); i++) tout << mk_pp(new_args[i], get_manager()) << "\n";);
expr * result = mk_nary_mul(new_args.size(), new_args.data(), m_util.is_int(var));
m_nl_new_exprs.push_back(result);
TRACE(factor, tout << "result: " << mk_pp(result, get_manager()) << "\n";);
return result;
}
template<typename Ext>
expr_ref theory_arith<Ext>::horner(unsigned depth, buffer<coeff_expr> & p, expr * var) {
SASSERT(!p.empty());
SASSERT(var != 0);
unsigned d = get_min_degree(p, var);
TRACE(horner_bug, tout << "poly:\n";
for (unsigned i = 0; i < p.size(); i++) { if (i > 0) tout << " + "; tout << p[i].first << "*" << mk_pp(p[i].second, get_manager()); } tout << "\n";
tout << "var: " << mk_pp(var, get_manager()) << "\n";
tout << "min_degree: " << d << "\n";);
buffer<coeff_expr> e; buffer<coeff_expr> r; for (auto const& kv : p) {
expr * m = kv.second;
expr * f = factor(m, var, d);
if (get_degree_of(m, var) == d) {
e.push_back(coeff_expr(kv.first, f));
}
else {
SASSERT(get_degree_of(m, var) > d);
r.push_back(coeff_expr(kv.first, f));
}
}
expr_ref s = cross_nested(depth + 1, e, nullptr);
if (!r.empty()) {
expr_ref q = horner(depth + 1, r, var);
s = m_util.mk_add(q, s);
}
expr_ref result = s;
if (d != 0) {
expr * xd = power(var, d);
result = m_util.mk_mul(xd, s);
}
m_nl_new_exprs.push_back(result);
return result;
}
template<typename Ext>
expr_ref theory_arith<Ext>::cross_nested(unsigned depth, buffer<coeff_expr> & p, expr * var) {
TRACE(non_linear, tout << "p.size: " << p.size() << "\n";);
if (var == nullptr) {
sbuffer<var_num_occs> varinfo;
if (!get_polynomial_info(p, varinfo))
return p2expr(p);
if (varinfo.empty())
return p2expr(p);
unsigned max = 0;
for (auto const& kv : varinfo) {
if (kv.second >= max) {
max = kv.second;
var = kv.first;
}
}
}
if (depth > 20)
return p2expr(p);
SASSERT(var != nullptr);
unsigned i1 = UINT_MAX;
unsigned i2 = UINT_MAX;
rational a, b;
unsigned n = UINT_MAX;
unsigned nm = UINT_MAX;
if (in_monovariate_monomials(p, var, i1, a, n, i2, b, nm)) {
CTRACE(in_monovariate_monomials, n == nm,
for (unsigned i = 0; i < p.size(); i++) {
if (i > 0) tout << " + "; tout << p[i].first << "*" << mk_pp(p[i].second, get_manager());
}
tout << "\n";
tout << "var: " << mk_pp(var, get_manager()) << "\n";
tout << "i1: " << i1 << "\n";
tout << "a: " << a << "\n";
tout << "n: " << n << "\n";
tout << "i2: " << i2 << "\n";
tout << "b: " << b << "\n";
tout << "nm: " << nm << "\n";);
if (n == nm) return horner(depth, p, var);
SASSERT(n != nm);
expr * new_expr = nullptr;
if (nm < n) {
std::swap(n, nm);
std::swap(a, b);
}
SASSERT(nm > n);
unsigned m = nm - n;
if (n % 2 == m % 2 && n >= m) {
rational a2b = a;
expr_ref xm(power(var, m), get_manager());
a2b /= (rational(2) * b);
if (!m_util.is_int(var) || a2b.is_int()) {
rational ma2b2 = a2b * a2b;
ma2b2.neg();
expr * xm_a2b = m_util.mk_add(m_util.mk_numeral(a2b, m_util.is_int(var)), xm);
expr * xm_a2b2 = m_util.mk_mul(xm_a2b, xm_a2b);
expr * rhs = m_util.mk_add(xm_a2b2, m_util.mk_numeral(ma2b2, m_util.is_int(var)));
expr * rhs2 = nullptr;
if (n > m)
rhs2 = m_util.mk_mul(power(var, n - m), rhs);
else
rhs2 = rhs;
new_expr = b.is_one() ? rhs2 : m_util.mk_mul(m_util.mk_numeral(b, m_util.is_int(var)), rhs2);
m_nl_new_exprs.push_back(new_expr);
TRACE(non_linear, tout << "new_expr:\n"; display_nested_form(tout, new_expr); tout << "\n";);
buffer<coeff_expr> rest;
unsigned sz = p.size();
for (unsigned i = 0; i < sz; i++) {
if (i != i1 && i != i2)
rest.push_back(p[i]);
}
if (rest.empty())
return expr_ref(new_expr, get_manager());
TRACE(non_linear, tout << "rest size: " << rest.size() << ", i1: " << i1 << ", i2: " << i2 << "\n";);
expr_ref h = cross_nested(depth + 1, rest, nullptr);
expr * r = m_util.mk_add(new_expr, h);
m_nl_new_exprs.push_back(r);
return expr_ref(r, get_manager());
}
}
}
return horner(depth, p, var);
}
template<typename Ext>
bool theory_arith<Ext>::is_cross_nested_consistent(buffer<coeff_expr> & p) {
sbuffer<var_num_occs> varinfo;
if (!get_polynomial_info(p, varinfo))
return true;
if (varinfo.empty())
return true;
std::stable_sort(varinfo.begin(), varinfo.end(), var_num_occs_lt());
TRACE(cross_nested, tout << "var num occs:\n";
for (auto const& kv : varinfo) {
tout << mk_bounded_pp(kv.first, get_manager()) << " -> " << kv.second << "\n";
});
for (auto const& kv : varinfo) {
m_nl_new_exprs.reset();
expr * var = kv.first;
expr_ref cn = cross_nested(0, p, var);
if (!cn)
continue;
TRACE(cross_nested, tout << "nested form for var:\n" << mk_ismt2_pp(var, get_manager()) << "\n";
display_nested_form(tout, cn); tout << "\n";
tout << "c:\n" << mk_ismt2_pp(cn, get_manager()) << "\n";);
interval i = evaluate_as_interval(cn);
TRACE(cross_nested, tout << "interval: " << i << "\n";);
v_dependency * d = nullptr;
if (!i.minus_infinity() && (i.get_lower_value().is_pos() || (i.get_lower_value().is_zero() && i.is_lower_open())))
d = i.get_lower_dependencies();
else if (!i.plus_infinity() && (i.get_upper_value().is_neg() || (i.get_upper_value().is_zero() && i.is_upper_open())))
d = i.get_upper_dependencies();
if (d) {
TRACE(cross_nested, tout << "nested form conflict: " << i << "\n";);
set_conflict(d);
return false;
}
}
return true;
}
template<typename Ext>
bool theory_arith<Ext>::is_cross_nested_consistent(row const & r) {
if (!is_problematic_non_linear_row(r))
return true;
TRACE(cross_nested, tout << "is_cross_nested_consistent:\n"; display_row(tout, r, false);
display(tout);
);
if (!get_manager().int_real_coercions() && is_mixed_real_integer(r))
return true;
TRACE(cross_nested, tout << "checking problematic row...\n";);
rational c = rational::one();
if (is_integer(r))
c = r.get_denominators_lcm().to_rational();
TRACE(non_linear, tout << "check problematic row:\n"; display_row(tout, r); display_row(tout, r, false););
buffer<coeff_expr> p;
for (auto & col : r) {
if (!col.is_dead()) {
p.push_back(coeff_expr(col.m_coeff.to_rational() * c, var2expr(col.m_var)));
}
}
SASSERT(!p.empty());
CTRACE(cross_nested_bug, !c.is_one(), tout << "c: " << c << "\n"; display_row(tout, r); tout << "---> p (coeffs, exprs):\n"; display_coeff_exprs(tout, p););
return is_cross_nested_consistent(p);
}
template<typename Ext>
bool theory_arith<Ext>::is_cross_nested_consistent(svector<theory_var> const & nl_cluster) {
for (theory_var v : nl_cluster) {
if (!is_base(v))
continue;
m_stats.m_nl_cross_nested++;
row const & r = m_rows[get_var_row(v)];
if (!is_cross_nested_consistent(r))
return false;
}
return true;
}
#define FIXED 0
#define QUOTED_FIXED 1
#define BOUNDED 2
#define QUOTED_BOUNDED 3
#define NOT_FREE 4
#define QUOTED_NOT_FREE 5
#define FREE 6
#define QUOTED_FREE 7
#define MAX_DEFAULT_WEIGHT 7
template<typename Ext>
void theory_arith<Ext>::init_grobner_var_order(svector<theory_var> const & nl_cluster, grobner & gb) {
for (theory_var v : nl_cluster) {
expr * var = var2expr(v);
if (is_fixed(v)) {
gb.set_weight(var, is_pure_monomial(var) ? QUOTED_FIXED : FIXED);
}
else if (is_bounded(v)) {
gb.set_weight(var, is_pure_monomial(var) ? QUOTED_BOUNDED : BOUNDED);
}
else if (lower(v) || upper(v)) {
gb.set_weight(var, is_pure_monomial(var) ? QUOTED_NOT_FREE : NOT_FREE);
}
else {
SASSERT(is_free(v));
gb.set_weight(var, is_pure_monomial(var) ? QUOTED_FREE : FREE);
}
}
}
template<typename Ext>
grobner::monomial * theory_arith<Ext>::mk_gb_monomial(rational const & _coeff, expr * m, grobner & gb, v_dependency * & dep, var_set & already_found) {
ptr_buffer<expr> vars;
rational coeff = _coeff;
rational r;
auto proc_var = [&](expr* v) {
if (m_util.is_numeral(v, r)) {
coeff *= r;
return;
}
theory_var _var = expr2var(v);
if (is_fixed(_var)) {
if (!already_found.contains(_var)) {
already_found.insert(_var);
dep = m_dep_manager.mk_join(dep, m_dep_manager.mk_join(m_dep_manager.mk_leaf(lower(_var)), m_dep_manager.mk_leaf(upper(_var))));
}
coeff *= lower_bound(_var).get_rational().to_rational();
}
else {
vars.push_back(v);
}
};
while (m_util.is_mul(m)) {
unsigned sz = to_app(m)->get_num_args();
for (unsigned i = 0; i + 1 < sz; ++i) {
proc_var(to_app(m)->get_arg(i));
}
m = to_app(m)->get_arg(sz-1);
}
proc_var(m);
if (!coeff.is_zero())
return gb.mk_monomial(coeff, vars.size(), vars.data());
else
return nullptr;
}
template<typename Ext>
void theory_arith<Ext>::add_row_to_gb(row const & r, grobner & gb) {
TRACE(grobner, tout << "adding row to gb\n"; display_row(tout, r););
ptr_buffer<grobner::monomial> monomials;
v_dependency * dep = nullptr;
m_tmp_var_set.reset();
for (auto& re : r) {
if (re.is_dead())
continue;
rational coeff = re.m_coeff.to_rational();
expr * m = var2expr(re.m_var);
grobner::monomial * new_m = mk_gb_monomial(coeff, m, gb, dep, m_tmp_var_set);
if (new_m)
monomials.push_back(new_m);
TRACE(grobner,
tout << "monomial: " << mk_pp(m, get_manager()) << "\n";
tout << "new monomial: "; if (new_m) gb.display_monomial(tout, *new_m); else tout << "null"; tout << "\n";);
}
gb.assert_eq_0(monomials.size(), monomials.data(), dep);
}
template<typename Ext>
void theory_arith<Ext>::add_monomial_def_to_gb(theory_var v, grobner & gb) {
ptr_buffer<grobner::monomial> monomials;
v_dependency * dep = nullptr;
m_tmp_var_set.reset();
expr * m = var2expr(v);
SASSERT(is_pure_monomial(m));
grobner::monomial * new_m = mk_gb_monomial(rational(1), m, gb, dep, m_tmp_var_set);
if (new_m)
monomials.push_back(new_m);
rational coeff(-1);
if (is_fixed(v)) {
dep = m_dep_manager.mk_join(dep, m_dep_manager.mk_join(m_dep_manager.mk_leaf(lower(v)), m_dep_manager.mk_leaf(upper(v))));
coeff *= lower_bound(v).get_rational().to_rational();
if (!coeff.is_zero())
monomials.push_back(gb.mk_monomial(coeff, 0, nullptr));
}
else {
monomials.push_back(gb.mk_monomial(coeff, 1, &m));
}
gb.assert_eq_0(monomials.size(), monomials.data(), dep);
}
template<typename Ext>
void theory_arith<Ext>::init_grobner(svector<theory_var> const & nl_cluster, grobner & gb) {
init_grobner_var_order(nl_cluster, gb);
for (theory_var v : nl_cluster) {
if (is_base(v)) {
row const & r = m_rows[get_var_row(v)];
add_row_to_gb(r, gb);
}
if (is_pure_monomial(v) && !m_data[v].m_nl_propagated && is_fixed(v)) {
add_monomial_def_to_gb(v, gb);
}
}
}
template<typename Ext>
interval theory_arith<Ext>::mk_interval_for(grobner::monomial const * m) {
interval r(m_dep_manager, rational(m->get_coeff()));
expr * var = nullptr;
unsigned power = 0;
unsigned num_vars = m->get_degree();
for (unsigned i = 0; i < num_vars; i++) {
expr * curr = m->get_var(i);
if (var == nullptr) {
var = curr;
power = 1;
}
else if (curr == var) {
power++;
}
else {
mul_bound_of(var, power, r);
var = curr;
power = 1;
}
}
if (var != nullptr)
mul_bound_of(var, power, r);
return r;
}
template<typename Ext>
void theory_arith<Ext>::set_conflict(v_dependency * d) {
antecedents ante(*this);
derived_bound b(null_theory_var, inf_numeral(0), B_LOWER);
dependency2new_bound(d, b);
set_conflict(b, ante, "arith_nl");
TRACE(non_linear, for (literal lit : b.m_lits) ctx.display_literal_verbose(tout, lit) << "\n"; tout << "\n";);
}
template<typename Ext>
bool theory_arith<Ext>::is_inconsistent(interval const & I, unsigned num_monomials, grobner::monomial * const * monomials, v_dependency * dep) {
interval r(I);
for (unsigned i = 0; i < num_monomials; i++) {
grobner::monomial const * m = monomials[i];
r += mk_interval_for(m);
if (r.minus_infinity() && r.plus_infinity())
return false;
}
TRACE(non_linear_bug, tout << "is_inconsistent, r: " << r << "\n";);
v_dependency * interval_deps = nullptr;
bool conflict = false;
if (!r.minus_infinity() && (r.get_lower_value().is_pos() || (r.get_lower_value().is_zero() && r.is_lower_open()))) {
interval_deps = r.get_lower_dependencies();
conflict = true;
TRACE(non_linear_bug, tout << "is inconsistent, interval_deps: " << interval_deps << "\n";);
}
else if (!r.plus_infinity() && (r.get_upper_value().is_neg() || (r.get_upper_value().is_zero() && r.is_upper_open()))) {
interval_deps = r.get_upper_dependencies();
conflict = true;
TRACE(non_linear_bug, tout << "is inconsistent, interval_deps: " << interval_deps << "\n";);
}
if (conflict) {
TRACE(non_linear, tout << "conflicting interval for = 0 equation: " << r << "\n";);
set_conflict(m_dep_manager.mk_join(interval_deps, dep));
return true;
}
return false;
}
template<typename Ext>
bool theory_arith<Ext>::is_inconsistent(grobner::equation const * eq, grobner & gb) {
interval zero(m_dep_manager, rational(0));
if (is_inconsistent(zero, eq->get_num_monomials(), eq->get_monomials(), eq->get_dependency())) {
TRACE(non_linear, tout << "found conflict\n"; gb.display_equation(tout, *eq););
return true;
}
return false;
}
bool is_perfect_square(grobner::monomial const * m, rational & r) {
unsigned num_vars = m->get_degree();
if (num_vars % 2 == 1)
return false;
if (!m->get_coeff().is_perfect_square(r))
return false;
expr * var = nullptr;
unsigned power = 0;
for (unsigned i = 0; i < num_vars; i++) {
expr * curr = m->get_var(i);
if (var == nullptr) {
var = curr;
power = 1;
}
else if (var == curr) {
power++;
}
else {
if (power % 2 == 1)
return false;
var = curr;
power = 1;
}
}
return power % 2 == 0;
}
bool is_perfect_square(grobner::monomial const * m1_sq, rational const & a, grobner::monomial const * m2_sq, rational const & b, grobner::monomial const * m1m2) {
DEBUG_CODE({
rational a1;
rational b1;
SASSERT(is_perfect_square(m1_sq, a1) && a == a1 && is_perfect_square(m2_sq, b1) && b == b1);
});
if (m1m2->get_coeff().is_nonneg())
return false;
rational c(-2);
c *= a;
c *= b;
if (m1m2->get_coeff() != c)
return false;
unsigned num1 = m1_sq->get_degree();
unsigned num2 = m2_sq->get_degree();
unsigned num12 = m1m2->get_degree();
if (num1 + num2 != num12 * 2)
return false;
unsigned i1, i2, i12;
i1 = i2 = i12 = 0;
while (true) {
expr * v1 = nullptr;
expr * v2 = nullptr;
expr * v12 = nullptr;
if (i1 < num1)
v1 = m1_sq->get_var(i1);
if (i2 < num2)
v2 = m2_sq->get_var(i2);
if (i12 < num12)
v12 = m1m2->get_var(i12);
if (v1 == nullptr && v2 == nullptr && v12 == nullptr)
return true;
if (v12 == nullptr)
return false;
if (v1 == v12) {
SASSERT(m1_sq->get_var(i1+1) == v1);
i1 += 2;
i12 ++;
}
else if (v2 == v12) {
SASSERT(m2_sq->get_var(i2+1) == v2);
i2 += 2;
i12 ++;
}
else {
return false;
}
}
}
template<typename Ext>
bool theory_arith<Ext>::is_inconsistent2(grobner::equation const * eq, grobner & gb) {
buffer<interval> intervals;
unsigned num = eq->get_num_monomials();
for (unsigned i = 0; i < num; i++) {
grobner::monomial const * m = eq->get_monomial(i);
intervals.push_back(mk_interval_for(m));
}
sbuffer<bool> deleted;
deleted.resize(num, false);
ptr_buffer<grobner::monomial> monomials;
for (unsigned i = 0; i < num; i++) {
grobner::monomial const * m1 = eq->get_monomial(i);
rational a;
if (deleted[i])
continue;
if (!is_perfect_square(m1, a)) {
monomials.push_back(const_cast<grobner::monomial*>(m1));
continue;
}
TRACE(non_linear, tout << "found perfect square monomial m1: "; gb.display_monomial(tout, *m1); tout << "\n";);
unsigned j = i + 1;
for (; j < num; j++) {
if (deleted[j])
continue;
grobner::monomial const * m2 = eq->get_monomial(j);
rational b;
if (!is_perfect_square(m2, b))
continue;
TRACE(non_linear, tout << "found perfect square monomial m2: "; gb.display_monomial(tout, *m2); tout << "\n";);
unsigned k = i + 1;
for (; k < num; k++) {
if (deleted[k])
continue;
grobner::monomial const * m1m2 = eq->get_monomial(k);
if (!is_perfect_square(m1, a, m2, b, m1m2))
continue;
TRACE(non_linear, tout << "found perfect square (M1-M2)^2:\n";
gb.display_monomial(tout, *m1); tout << "\n";
gb.display_monomial(tout, *m2); tout << "\n";
gb.display_monomial(tout, *m1m2); tout << "\n";);
interval I = intervals[i];
I += intervals[j];
I += intervals[k];
if (I.minus_infinity() || I.get_lower_value().is_neg()) {
TRACE(non_linear, tout << "the lower bound improved when perfect square is eliminated.\n";);
deleted[i] = true;
deleted[j] = true;
deleted[k] = true;
break;
}
}
if (k < num)
break; }
if (j == num) {
monomials.push_back(const_cast<grobner::monomial*>(m1));
}
}
if (monomials.size() == num)
return false; interval ge_zero(m_dep_manager, rational(0), false, true, nullptr);
if (is_inconsistent(ge_zero, monomials.size(), monomials.data(), eq->get_dependency())) {
TRACE(non_linear, tout << "found conflict\n"; gb.display_equation(tout, *eq););
return true;
}
return false;
}
template<typename Ext>
expr * theory_arith<Ext>::monomial2expr(grobner::monomial const * m, bool is_int) {
unsigned num_vars = m->get_degree();
ptr_buffer<expr> args;
if (!m->get_coeff().is_one())
args.push_back(m_util.mk_numeral(m->get_coeff(), is_int));
for (unsigned j = 0; j < num_vars; j++)
args.push_back(m->get_var(j));
return mk_nary_mul(args.size(), args.data(), is_int);
}
template<typename Ext>
bool theory_arith<Ext>::internalize_gb_eq(grobner::equation const * eq) {
bool is_int = false;
unsigned num_monomials = eq->get_num_monomials();
for (unsigned i = 0; i < num_monomials; i++) {
grobner::monomial const * m = eq->get_monomial(i);
unsigned degree = m->get_degree();
if (degree > m_params.m_nl_arith_max_degree)
return false;
if (degree > 0)
is_int = m_util.is_int(m->get_var(0));
}
rational k;
ptr_buffer<expr> args;
for (unsigned i = 0; i < num_monomials; i++) {
grobner::monomial const * m = eq->get_monomial(i);
if (m->get_degree() == 0)
k -= m->get_coeff();
else
args.push_back(monomial2expr(eq->get_monomial(i), is_int));
}
th_rewriter& s = ctx.get_rewriter();
expr_ref pol(get_manager());
SASSERT(!args.empty());
pol = mk_nary_add(args.size(), args.data());
expr_ref s_pol(get_manager());
proof_ref pr(get_manager());
TRACE(gb_bug, tout << mk_ll_pp(pol, get_manager()) << "\n";);
s(pol, s_pol, pr);
if (!has_var(s_pol)) {
TRACE(spol_bug, tout << "internalizing...\n" << mk_ll_pp(s_pol, get_manager()) << "\n";);
ctx.internalize(s_pol, false);
ctx.mark_as_relevant(s_pol.get());
}
SASSERT(has_var(s_pol.get()));
theory_var v = expr2var(s_pol);
CTRACE(spol_bug, v == null_theory_var, tout << mk_ll_pp(s_pol, get_manager()) << "\n"; display(tout););
SASSERT(v != null_theory_var);
mk_derived_nl_bound(v, inf_numeral(k), B_LOWER, eq->get_dependency());
mk_derived_nl_bound(v, inf_numeral(k), B_UPPER, eq->get_dependency());
TRACE(non_linear, tout << "inserted new equation into the tableau\n"; display_var(tout, v););
return true;
}
template<typename Ext>
void theory_arith<Ext>::update_statistics(grobner & gb) {
m_stats.m_gb_simplify += gb.m_stats.m_simplify;
m_stats.m_gb_superpose += gb.m_stats.m_superpose;
m_stats.m_gb_num_processed += gb.m_stats.m_num_processed;
m_stats.m_gb_compute_basis++;
}
template<typename Ext>
void theory_arith<Ext>::set_gb_exhausted() {
IF_VERBOSE(3, verbose_stream() << "Grobner basis computation interrupted. Increase threshold using NL_ARITH_GB_THRESHOLD=<limit>\n";);
ctx.push_trail(value_trail<bool>(m_nl_gb_exhausted));
m_nl_gb_exhausted = true;
}
template<typename Ext>
bool theory_arith<Ext>::get_gb_eqs_and_look_for_conflict(ptr_vector<grobner::equation>& eqs, grobner& gb) {
eqs.reset();
gb.get_equations(eqs);
TRACE(grobner, tout << "after gb\n";
std::function<void(std::ostream& out, expr* v)> _fn = [&](std::ostream& out, expr* v) { out << "v" << expr2var(v); };
for (grobner::equation* eq : eqs)
gb.display_equation(tout, *eq, _fn);
);
for (grobner::equation* eq : eqs) {
if (is_inconsistent(eq, gb) || is_inconsistent2(eq, gb)) {
TRACE(grobner, tout << "inconsistent: "; gb.display_equation(tout, *eq););
return true;
}
}
TRACE(grobner, tout << "not found\n"; );
return false;
}
template<typename Ext>
bool theory_arith<Ext>::try_to_modify_eqs(ptr_vector<grobner::equation>& eqs, grobner& gb, unsigned & next_weight) {
bool modified = false;
for (grobner::equation const* eq : eqs) {
unsigned num_monomials = eq->get_num_monomials();
CTRACE(grobner_bug, num_monomials <= 0, gb.display_equation(tout, *eq););
if (num_monomials == 0)
continue; if (eq->get_monomial(0)->get_degree() != 1)
continue;
for (unsigned j = 1; j < num_monomials; j++) {
grobner::monomial const * m = eq->get_monomial(j);
if (m->get_degree() != 1)
continue;
expr * var = m->get_var(0);
if (gb.get_weight(var) > MAX_DEFAULT_WEIGHT)
continue; TRACE(non_linear, tout << "increased weight of: " << mk_bounded_pp(var, get_manager()) << "\n";);
gb.set_weight(var, next_weight);
next_weight++;
gb.update_order();
TRACE(non_linear, tout << "after updating order\n"; gb.display(tout););
modified = true;
break;
}
if (modified)
break;
}
return modified;
}
template<typename Ext>
bool theory_arith<Ext>::scan_for_linear(ptr_vector<grobner::equation>& eqs, grobner& gb) {
bool result = false;
if (m_params.m_nl_arith_gb_eqs) { SASSERT(false);
for (grobner::equation* eq : eqs) {
if (!eq->is_linear_combination()) {
TRACE(non_linear, tout << "processing new equality:\n"; gb.display_equation(tout, *eq););
TRACE(non_linear_bug, tout << "processing new equality:\n"; gb.display_equation(tout, *eq););
if (internalize_gb_eq(eq))
result = true;
}
}
}
return result;
}
template<typename Ext>
bool theory_arith<Ext>::compute_basis_loop(grobner & gb) {
while (gb.get_num_new_equations() < m_params.m_nl_arith_gb_threshold) {
if (ctx.get_cancel_flag()) {
return false;
}
if (gb.compute_basis_step())
return true;
}
return false;
}
template<typename Ext>
void theory_arith<Ext>::compute_basis(grobner& gb, bool& warn) {
gb.compute_basis_init();
if (!compute_basis_loop(gb) && !warn) {
set_gb_exhausted();
warn = true;
}
}
template<typename Ext>
typename theory_arith<Ext>::gb_result theory_arith<Ext>::compute_grobner(svector<theory_var> const & nl_cluster) {
if (m_nl_gb_exhausted)
return GB_FAIL;
grobner gb(get_manager(), m_dep_manager);
init_grobner(nl_cluster, gb);
TRACE(non_linear, display(tout););
bool warn = false;
unsigned next_weight = MAX_DEFAULT_WEIGHT + 1; ptr_vector<grobner::equation> eqs;
do {
TRACE(grobner, tout << "before grobner:\n";
std::function<void(std::ostream& out, expr* v)> _fn = [&](std::ostream& out, expr* v) { out << "v" << expr2var(v); };
gb.display(tout, _fn));
compute_basis(gb, warn);
update_statistics(gb);
TRACE(non_linear_gb, tout << "after:\n"; gb.display(tout););
if (ctx.get_cancel_flag())
return GB_FAIL;
if (get_gb_eqs_and_look_for_conflict(eqs, gb))
return GB_PROGRESS;
if (scan_for_linear(eqs, gb))
return GB_NEW_EQ;
}
while(m_params.m_nl_arith_gb_perturbate &&
(!m_nl_gb_exhausted) && try_to_modify_eqs(eqs, gb, next_weight));
return GB_FAIL;
}
template<typename Ext>
bool theory_arith<Ext>::max_min_nl_vars() {
if (!m_params.m_nl_arith_optimize_bounds)
return true;
var_set already_found;
svector<theory_var> vars;
for (theory_var v : m_nl_monomials) {
mark_var(v, vars, already_found);
expr * n = var2expr(v);
SASSERT(is_pure_monomial(n));
for (expr * curr : *to_app(n)) {
if (ctx.e_internalized(curr)) {
theory_var v = expr2var(curr);
SASSERT(v != null_theory_var);
mark_var(v, vars, already_found);
}
}
}
return max_min(vars);
}
template<typename Ext>
final_check_status theory_arith<Ext>::process_non_linear() {
m_model_depends_on_computed_epsilon = false;
if (m_nl_monomials.empty())
return FC_DONE;
if (!reflection_enabled())
return FC_GIVEUP;
if (check_monomial_assignments()) {
return FC_DONE;
}
if (!m_params.m_nl_arith) {
TRACE(non_linear, tout << "Non-linear is not enabled\n";);
return FC_GIVEUP;
}
TRACE(process_non_linear, display(tout););
if (m_nl_rounds > m_params.m_nl_arith_rounds) {
TRACE(non_linear, tout << "GIVEUP non linear problem...\n";);
IF_VERBOSE(3, verbose_stream() << "Max. non linear arithmetic rounds. Increase threshold using NL_ARITH_ROUNDS=<limit>\n";);
return FC_GIVEUP;
}
ctx.push_trail(value_trail<unsigned>(m_nl_rounds));
m_nl_rounds++;
elim_quasi_base_rows();
move_non_base_vars_to_bounds();
TRACE(non_linear_verbose, tout << "processing non linear constraints...\n"; ctx.display(tout););
if (!make_feasible()) {
TRACE(non_linear, tout << "failed to move variables to bounds.\n";);
failed();
return FC_CONTINUE;
}
if (!max_min_nl_vars())
return FC_CONTINUE;
if (check_monomial_assignments()) {
return m_liberal_final_check || !m_changed_assignment ? FC_DONE : FC_CONTINUE;
}
svector<theory_var> vars;
get_non_linear_cluster(vars);
bool progress;
unsigned old_idx = m_nl_strategy_idx;
ctx.push_trail(value_trail<unsigned>(m_nl_strategy_idx));
do {
progress = false;
switch (m_nl_strategy_idx) {
case 0:
if (propagate_nl_bounds()) {
propagate_core();
progress = true;
}
break;
case 1:
if (m_params.m_nl_arith_cross_nested && !is_cross_nested_consistent(vars))
progress = true;
break;
case 2:
if (m_params.m_nl_arith_gb) {
switch(compute_grobner(vars)) {
case GB_PROGRESS:
progress = true;
break;
case GB_NEW_EQ:
progress = true;
propagate_core();
break;
case GB_FAIL:
break;
}
}
break;
case 3:
if (m_params.m_nl_arith_branching) {
theory_var target = find_nl_var_for_branching();
if (target != null_theory_var && branch_nl_int_var(target))
progress = true;
}
break;
}
m_nl_strategy_idx = (m_nl_strategy_idx + 1) % 4;
if (progress)
return FC_CONTINUE;
}
while (m_nl_strategy_idx != old_idx);
if (check_monomial_assignments()) {
return m_liberal_final_check || !m_changed_assignment ? FC_DONE : FC_CONTINUE;
}
TRACE(non_linear, display(tout););
return FC_GIVEUP;
}
};