mirror of
https://github.com/Z3Prover/z3
synced 2025-10-07 16:31:55 +00:00
1180 lines
46 KiB
C++
1180 lines
46 KiB
C++
/*++
|
|
Copyright (c) 2025 Microsoft Corporation
|
|
|
|
given a monic m = x * y * z ... used in a constraint that is false under the current evaluation of x,y,z
|
|
|
|
saturate constraints with respect to m
|
|
in other words, if a constraint contains x*y + p >= 0,
|
|
then include the constraint x*y*z + z*p >= 0
|
|
assuming current value of z is non-negative.
|
|
Check if the system with new constraints is LP (and MIP) feasible.
|
|
If it is not, then produce a lemma that explains infeasibility.
|
|
|
|
Strategy 1: The lemma is in terms of the original constraints and bounds.
|
|
Strategy 2: Attempt to eliminate new monomials from the lemma by relying on Farkas multipliers.
|
|
If it succeeds to eliminate new monomials we have a lemma that is a linear
|
|
combination of existing variables.
|
|
The idea is that a conflict over the new system is a set of multipliers lambda, such
|
|
that lambda * A is separated from b for the constraints Axy >= b.
|
|
The coefficients in lambda are non-negative.
|
|
They correspond to variables x, y where x are variables from the input constraints
|
|
and y are new variables introduced for new monomials.
|
|
We can test if lambda allows eliminating y by taking a subset of lambda indices where
|
|
A has rows containing y_i for some fresh y_i. Replace those rows r_j by the partial sum of
|
|
of rows multiplied by lambda_j. The sum r_j1 * lambda_j1 + .. + r_jk * lambda_jk does not
|
|
contain y_i. Repeat the same process with other variables y_i'. If a sum is
|
|
generated without any y, it is a linear consequence of the new constraints but not
|
|
necessarily derivable with the old constraints.
|
|
Strategy 3: The lemma uses the new constraints.
|
|
|
|
Auxiliary methods:
|
|
- basic lemmas
|
|
- factoring
|
|
|
|
Potential auxiliary methods:
|
|
- tangent, ordering lemmas (lemmas that use values from the current model)
|
|
- with internal bounded backtracking.
|
|
|
|
Currently missed lemma:
|
|
|
|
(30) j17 >= 0
|
|
(15) j4 - j7 >= 1
|
|
(51) j25 - j27 >= 1
|
|
(38) j11 - j23 >= 0
|
|
(9) j4 >= 1
|
|
(26) j5*j7 - j4*j11_ - j17 >= 0
|
|
(57) j11 >= 1
|
|
(10) j5 - j4 >= 0
|
|
(12) j7 >= 1
|
|
(46) - j5 + j23 + j27 >= 0
|
|
(44) j4 - j7 - j25 >= 0
|
|
|
|
j4 - j7 - j27 >= 1 (from (51) + (46))
|
|
j4 - j5 - j7 + j23 >= 1 (from (46))
|
|
j4 - j5 + j23 >= 2 (from (12))
|
|
j4 - j5 + j11 >= 2 (from (38))
|
|
j4j4 - j4j5 + j4j11 >= 2j4 (multiply by j4)
|
|
j4j11 >= 2j4 (subtract 10 multiplied by j4)
|
|
..
|
|
..
|
|
|
|
*/
|
|
|
|
#include "math/lp/nla_core.h"
|
|
#include "math/lp/nla_coi.h"
|
|
#include "math/lp/nla_stellensatz.h"
|
|
|
|
namespace nla {
|
|
|
|
stellensatz::stellensatz(core* core) :
|
|
common(core),
|
|
m_solver(*this),
|
|
m_coi(*core),
|
|
m_pm(c().reslim(), m_qm, &m_allocator)
|
|
{}
|
|
|
|
lbool stellensatz::saturate() {
|
|
lp::explanation ex;
|
|
vector<ineq> ineqs;
|
|
init_solver();
|
|
saturate_constraints();
|
|
saturate_basic_linearize();
|
|
TRACE(arith, display(tout << "stellensatz after saturation\n"));
|
|
lbool r = m_solver.solve(ex, ineqs);
|
|
if (r == l_false)
|
|
add_lemma(ex, ineqs);
|
|
return r;
|
|
}
|
|
|
|
void stellensatz::init_solver() {
|
|
m_solver.init();
|
|
m_vars2mon.reset();
|
|
m_mon2vars.reset();
|
|
m_values.reset();
|
|
m_resolvents.reset();
|
|
m_assumptions.reset();
|
|
m_monomials_to_refine.reset();
|
|
m_false_constraints.reset();
|
|
m_factored_constraints.reset();
|
|
m_max_monomial_degree = 0;
|
|
m_coi.init();
|
|
init_vars();
|
|
init_occurs();
|
|
}
|
|
|
|
void stellensatz::init_vars() {
|
|
auto const& src = c().lra_solver();
|
|
auto &dst = m_solver.lra();
|
|
auto sz = src.number_of_vars();
|
|
for (unsigned v = 0; v < sz; ++v) {
|
|
// Declare v into m_solver.lra()
|
|
unsigned w;
|
|
if (m_coi.mons().contains(v))
|
|
init_monomial(v);
|
|
else
|
|
m_values.push_back(c().val(v));
|
|
if (m_coi.terms().contains(v)) {
|
|
auto const& t = src.get_term(v);
|
|
// Assumption: variables in coefficients are always declared before term variable.
|
|
SASSERT(all_of(t, [&](auto p) { return p.j() < v; }));
|
|
w = dst.add_term(t.coeffs_as_vector(), v);
|
|
}
|
|
else
|
|
w = dst.add_var(v, src.var_is_int(v));
|
|
|
|
// assert bounds on v in the new solver.
|
|
VERIFY(w == v);
|
|
if (src.column_has_lower_bound(v)) {
|
|
auto lo_bound = src.get_lower_bound(v);
|
|
SASSERT(lo_bound.y >= 0);
|
|
auto k = lo_bound.y > 0 ? lp::lconstraint_kind::GT : lp::lconstraint_kind::GE;
|
|
auto rhs = lo_bound.x;
|
|
auto dep = src.get_column_lower_bound_witness(v);
|
|
auto ci = dst.add_var_bound(v, k, rhs);
|
|
m_assumptions.insert(ci, assumptions{dep});
|
|
}
|
|
if (src.column_has_upper_bound(v)) {
|
|
auto hi_bound = src.get_upper_bound(v);
|
|
SASSERT(hi_bound.y <= 0);
|
|
auto k = hi_bound.y < 0 ? lp::lconstraint_kind::LT : lp::lconstraint_kind::LE;
|
|
auto rhs = hi_bound.x;
|
|
auto dep = src.get_column_upper_bound_witness(v);
|
|
auto ci = dst.add_var_bound(v, k, rhs);
|
|
m_assumptions.insert(ci, assumptions{dep});
|
|
}
|
|
}
|
|
for (auto const &[v, vars] : m_mon2vars)
|
|
m_max_monomial_degree = std::max(m_max_monomial_degree, vars.size());
|
|
}
|
|
|
|
void stellensatz::init_monomial(unsigned mon_var) {
|
|
auto& mon = c().emons()[mon_var];
|
|
svector<lpvar> vars(mon.vars());
|
|
std::sort(vars.begin(), vars.end());
|
|
m_vars2mon.insert(vars, mon_var);
|
|
m_mon2vars.insert(mon_var, vars);
|
|
m_values.push_back(value(vars));
|
|
}
|
|
|
|
//
|
|
// convert a conflict from m_solver.lra()/lia() into
|
|
// a conflict for c().lra_solver()
|
|
// 1. constraints that are obtained by multiplication are explained from the original constraint
|
|
// 2. bounds assumptions are added as assumptions to the lemma.
|
|
//
|
|
void stellensatz::add_lemma(lp::explanation const& ex1, vector<ineq> const& ineqs) {
|
|
TRACE(arith, display(tout));
|
|
auto& lra = c().lra_solver();
|
|
lp::explanation ex2;
|
|
lemma_builder new_lemma(c(), "stellensatz");
|
|
m_constraints_in_conflict.reset();
|
|
for (auto p : ex1)
|
|
explain_constraint(new_lemma, p.ci(), ex2);
|
|
new_lemma &= ex2;
|
|
for (auto const &ineq : ineqs)
|
|
new_lemma |= ineq;
|
|
IF_VERBOSE(5, verbose_stream() << "unsat \n" << new_lemma << "\n");
|
|
TRACE(arith, tout << "unsat\n" << new_lemma << "\n");
|
|
c().lra_solver().settings().stats().m_nla_stellensatz++;
|
|
}
|
|
|
|
//
|
|
// a constraint can be explained by a set of bounds used as assumptions
|
|
// and by an original constraint.
|
|
//
|
|
void stellensatz::explain_constraint(lemma_builder& new_lemma, lp::constraint_index ci, lp::explanation& ex) {
|
|
if (m_constraints_in_conflict.contains(ci))
|
|
return;
|
|
m_constraints_in_conflict.insert(ci);
|
|
if (!m_assumptions.contains(ci))
|
|
return;
|
|
auto const &bounds = m_assumptions[ci];
|
|
for (auto const &b : bounds) {
|
|
if (std::holds_alternative<u_dependency *>(b)) {
|
|
auto dep = *std::get_if<u_dependency *>(&b);
|
|
m_solver.lra().push_explanation(dep, ex);
|
|
}
|
|
else if (std::holds_alternative<lp::constraint_index>(b)) {
|
|
auto c = *std::get_if<lp::constraint_index>(&b);
|
|
explain_constraint(new_lemma, c, ex);
|
|
}
|
|
else {
|
|
//
|
|
// the inequality was posted as an assumption
|
|
// negate it to add to the lemma
|
|
// recall that lemmas are represented in the form: /\ Assumptions => \/ C
|
|
//
|
|
auto [v, k, rhs] = *std::get_if<bound>(&b);
|
|
k = negate(k);
|
|
if (m_solver.lra().var_is_int(v)) {
|
|
if (k == lp::lconstraint_kind::GT) {
|
|
rhs += 1;
|
|
k = lp::lconstraint_kind::GE;
|
|
}
|
|
if (k == lp::lconstraint_kind::LT) {
|
|
rhs -= 1;
|
|
k = lp::lconstraint_kind::LE;
|
|
}
|
|
}
|
|
new_lemma |= ineq(v, k, rhs);
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Emulate linearization within stellensatz to enforce simple axioms.
|
|
// Incremental linearization in the main procedure produces new atoms that are pushed to lemmas
|
|
// Here, it creates potentially new atoms from bounds assumptions, but it checks linear
|
|
// feasibility first.
|
|
//
|
|
// Use to_refine and model from c().lra_solver() for initial pass.
|
|
// Use model from m_solver.lra() for subsequent passes.
|
|
//
|
|
// Basic linearization - encoded
|
|
// - sign axioms - implicit in down saturation (establish redundancy?)
|
|
// - unit axioms - do not follow
|
|
// - monotonicity - implicit in down saturation (establish redundancy?)
|
|
// - squares - do not follow from anything else
|
|
// Order lemmas - claim: are subsumed by downwards saturation.
|
|
// x > 0, y > z => xy > xz because y > z gets multiplied by x due to the subterm xy.
|
|
// Monotonicity lemmas - x >= |val(x)| & y >= |val(y)| => xy >= |val(x)||val(y)|
|
|
// - not encoded
|
|
// Tangent lemmas - (x - |val(x)|+1) * (y - |val(y)| + 1) > 0 in case |val(xy)| < |val(x)||val(y)|
|
|
// - not encoded
|
|
//
|
|
void stellensatz::saturate_basic_linearize() {
|
|
for (auto j : c().to_refine()) {
|
|
auto const &vars = m_mon2vars[j];
|
|
auto val_j = c().val(j);
|
|
auto val_vars = m_values[j];
|
|
saturate_basic_linearize(j, val_j, vars, val_vars);
|
|
}
|
|
}
|
|
|
|
void stellensatz::saturate_basic_linearize(lpvar j, rational const& val_j, svector<lpvar> const& vars,
|
|
rational const& val_vars) {
|
|
saturate_signs(j, val_j, vars, val_vars);
|
|
saturate_units(j, vars);
|
|
saturate_monotonicity(j, val_j, vars, val_vars);
|
|
saturate_squares(j, val_j, vars);
|
|
}
|
|
|
|
//
|
|
// Sign axioms:
|
|
// x = 0 => x*y = 0
|
|
// the equation x*y = 0 is asserted with assumption x = 0
|
|
// x > 0 & y < 0 => x*y < 0
|
|
//
|
|
void stellensatz::saturate_signs(lpvar j, rational const& val_j, svector<lpvar> const& vars,
|
|
rational const& val_vars) {
|
|
if (val_vars == 0 && val_j != 0) {
|
|
assumptions bounds;
|
|
lbool sign = add_bounds(vars, bounds);
|
|
VERIFY(sign == l_undef);
|
|
add_ineq("signs = 0", bounds, j, lp::lconstraint_kind::EQ, rational(0));
|
|
}
|
|
else if (val_j <= 0 && 0 < val_vars) {
|
|
assumptions bounds;
|
|
lbool sign = add_bounds(vars, bounds);
|
|
VERIFY(sign == l_false);
|
|
add_ineq("signs > 0", bounds, j, lp::lconstraint_kind::GT, rational(0));
|
|
}
|
|
else if (val_j >= 0 && 0 > val_vars) {
|
|
assumptions bounds;
|
|
lbool sign = add_bounds(vars, bounds);
|
|
VERIFY(sign == l_true);
|
|
add_ineq("signs < 0", bounds, j, lp::lconstraint_kind::LT, rational(0));
|
|
}
|
|
}
|
|
|
|
//
|
|
// Unit axioms:
|
|
// x = -1 => x*y = -y
|
|
// x = 1 => x*y = y
|
|
//
|
|
void stellensatz::saturate_units(lpvar j, svector<lpvar> const &vars) {
|
|
lpvar non_unit = lp::null_lpvar;
|
|
int sign = 1;
|
|
for (auto v : vars) {
|
|
if (m_values[v] == 1)
|
|
continue;
|
|
if (m_values[v] == -1) {
|
|
sign = -sign;
|
|
continue;
|
|
}
|
|
if (non_unit != lp::null_lpvar)
|
|
return;
|
|
non_unit = v;
|
|
}
|
|
assumptions bounds;
|
|
for (auto v : vars) {
|
|
if (m_values[v] == 1 || m_values[v] == -1) {
|
|
bound b(v, lp::lconstraint_kind::EQ, m_values[v]);
|
|
bounds.push_back(b);
|
|
}
|
|
}
|
|
// assert j = +/- non_unit
|
|
lp::lar_term lhs;
|
|
lhs.add_monomial(rational(1), j);
|
|
if (non_unit != lp::null_lpvar) {
|
|
lhs.add_monomial(rational(-sign), non_unit);
|
|
add_ineq("unit mul", bounds, lhs, lp::lconstraint_kind::EQ, rational(0));
|
|
} else {
|
|
add_ineq("unit mul", bounds, lhs, lp::lconstraint_kind::EQ, rational(sign));
|
|
}
|
|
}
|
|
|
|
// Monotonicity axioms:
|
|
// x > 1 & y > 0 => x*y > y
|
|
// x < -1 & y > 0 => -x*y > -x
|
|
// x > 1 & y < 0 => -x*y > x
|
|
// x < -1 & y < 0 => x*y > -y
|
|
void stellensatz::saturate_monotonicity(lpvar j, rational const &val_j, svector<lpvar> const &vars,
|
|
rational const &val_vars) {
|
|
if (vars.size() != 2)
|
|
return;
|
|
lpvar x = vars[0];
|
|
lpvar y = vars[1];
|
|
if (m_values[x] == 0 || m_values[y] == 0)
|
|
return;
|
|
if (abs(m_values[x]) <= 1 && abs(m_values[y]) <= 1)
|
|
return;
|
|
saturate_monotonicity(j, val_j, x, y);
|
|
saturate_monotonicity(j, val_j, y, x);
|
|
}
|
|
|
|
void stellensatz::saturate_monotonicity(lpvar j, rational const &val_j, lpvar x, lpvar y) {
|
|
auto valx = m_values[x];
|
|
auto valy = m_values[y];
|
|
if (valx > 1 && valy > 0 && val_j <= valy)
|
|
saturate_monotonicity(j, val_j, x, 1, y, 1);
|
|
else if (valx > 1 && valy < 0 && -val_j <= -valy)
|
|
saturate_monotonicity(j, val_j, x, 1, y, -1);
|
|
else if (valx < -1 && valy > 0 && -val_j <= valy)
|
|
saturate_monotonicity(j, val_j, x, -1, y, 1);
|
|
else if (valx < -1 && valy < 0 && val_j <= -valy)
|
|
saturate_monotonicity(j, val_j, x, -1, y, -1);
|
|
}
|
|
|
|
// x > 1, y > 0 => xy > y
|
|
// x > 1, y < 1 => -xy > -y
|
|
void stellensatz::saturate_monotonicity(lpvar j, rational const& val_j, lpvar x, int sign_x, lpvar y, int sign_y) {
|
|
assumptions bounds;
|
|
bound b1(x, sign_x < 0 ? lp::lconstraint_kind::LT : lp::lconstraint_kind::GT, rational(sign_x));
|
|
bound b2(y, sign_y < 0 ? lp::lconstraint_kind::LT : lp::lconstraint_kind::GT, rational(0));
|
|
bounds.push_back(b1);
|
|
bounds.push_back(b2);
|
|
lp::lar_term lhs;
|
|
lhs.add_monomial(rational(sign_x * sign_y), j);
|
|
lhs.add_monomial(rational(-sign_y), y);
|
|
add_ineq("monotonicity", bounds, lhs, lp::lconstraint_kind::GT, rational(0));
|
|
}
|
|
|
|
void stellensatz::saturate_squares(lpvar j, rational const& val_j, svector<lpvar> const& vars) {
|
|
if (val_j >= 0)
|
|
return;
|
|
if (vars.size() % 2 != 0)
|
|
return;
|
|
for (unsigned i = 0; i + 1 < vars.size(); i += 2) {
|
|
if (vars[i] != vars[i + 1])
|
|
return;
|
|
}
|
|
// it is a square.
|
|
assumptions bounds;
|
|
add_ineq("squares", bounds, j, lp::lconstraint_kind::GE, rational(0));
|
|
}
|
|
|
|
rational stellensatz::value(lp::lar_term const &t) const {
|
|
rational r(0);
|
|
for (auto cv : t)
|
|
r += m_values[cv.j()] * cv.coeff();
|
|
return r;
|
|
}
|
|
|
|
rational stellensatz::value(svector<lpvar> const &prod) const {
|
|
rational p(1);
|
|
for (auto v : prod)
|
|
p *= m_values[v];
|
|
return p;
|
|
}
|
|
|
|
lp::constraint_index stellensatz::add_ineq(char const* rule,
|
|
assumptions const& bounds,
|
|
lp::lar_term const& t, lp::lconstraint_kind k,
|
|
rational const& rhs) {
|
|
SASSERT(!t.coeffs_as_vector().empty());
|
|
auto ti = m_solver.lra().add_term(t.coeffs_as_vector(), m_solver.lra().number_of_vars());
|
|
m_values.push_back(value(t));
|
|
auto new_ci = m_solver.lra().add_var_bound(ti, k, rhs);
|
|
TRACE(arith,
|
|
display_constraint(tout << rule << ": ", new_ci) << "\n";
|
|
if (!bounds.empty()) display(tout << "\t <- ", bounds) << "\n";);
|
|
SASSERT(m_values.size() - 1 == ti);
|
|
m_assumptions.insert(new_ci, bounds);
|
|
init_occurs(new_ci);
|
|
return new_ci;
|
|
}
|
|
|
|
lp::constraint_index stellensatz::add_ineq(char const* rule, assumptions const& bounds, lpvar j, lp::lconstraint_kind k,
|
|
rational const& rhs) {
|
|
auto new_ci = m_solver.lra().add_var_bound(j, k, rhs);
|
|
m_assumptions.insert(new_ci, bounds);
|
|
init_occurs(new_ci);
|
|
return new_ci;
|
|
}
|
|
|
|
void stellensatz::init_occurs() {
|
|
m_occurs.reset();
|
|
for (auto ci : m_solver.lra().constraints().indices())
|
|
init_occurs(ci);
|
|
}
|
|
|
|
void stellensatz::init_occurs(lp::constraint_index ci) {
|
|
auto const &con = m_solver.lra().constraints()[ci];
|
|
for (auto cv : con.lhs()) {
|
|
auto v = cv.j();
|
|
if (is_mon_var(v)) {
|
|
for (auto w : m_mon2vars[v]) {
|
|
if (w >= m_occurs.size())
|
|
m_occurs.resize(w + 1);
|
|
m_occurs[w].push_back(ci);
|
|
}
|
|
continue;
|
|
}
|
|
if (v >= m_occurs.size())
|
|
m_occurs.resize(v + 1);
|
|
m_occurs[v].push_back(ci);
|
|
}
|
|
}
|
|
|
|
// record new monomials that are created and recursively down-saturate with respect to these.
|
|
// this is a simplistic pass
|
|
void stellensatz::saturate_constraints() {
|
|
|
|
for (auto ci : m_solver.lra().constraints().indices())
|
|
insert_monomials_from_constraint(ci);
|
|
|
|
auto is_subset = [&](svector<lpvar> const &a, svector<lpvar> const& b) {
|
|
if (a.size() >= b.size())
|
|
return false;
|
|
// check if a is a subset of b, counting multiplicies, assume a, b are sorted
|
|
unsigned i = 0, j = 0;
|
|
while (i < a.size() && j < b.size()) {
|
|
if (a[i] == b[j])
|
|
++i;
|
|
++j;
|
|
}
|
|
return i == a.size();
|
|
};
|
|
|
|
unsigned initial_false_constraints = m_false_constraints.size();
|
|
for (unsigned it = 0; it < m_false_constraints.size(); ++it) {
|
|
if (it > 5*initial_false_constraints)
|
|
break;
|
|
auto ci1 = m_false_constraints[it];
|
|
auto const &con = m_solver.lra().constraints()[ci1];
|
|
for (auto const &cv1 : con.lhs()) {
|
|
auto j = cv1.j();
|
|
if (!is_mon_var(j))
|
|
continue;
|
|
auto vars = m_mon2vars[j];
|
|
if (vars.size() > m_max_monomial_degree)
|
|
continue;
|
|
for (auto v : vars) {
|
|
if (v >= m_occurs.size())
|
|
continue;
|
|
svector<lpvar> _vars;
|
|
_vars.push_back(v);
|
|
for (unsigned cidx = 0; cidx < m_occurs[v].size(); ++cidx) {
|
|
auto ci2 = m_occurs[v][cidx];
|
|
for (auto const & cv2 : m_solver.lra().constraints()[ci2].lhs()) {
|
|
auto u = cv2.j();
|
|
if (u == v && is_resolvable(ci1, cv1.coeff(), ci2, cv2.coeff()))
|
|
resolve(j, ci1, saturate_constraint(ci2, j, _vars));
|
|
else if (is_mon_var(u) && is_subset(m_mon2vars[u], vars) &&
|
|
is_resolvable(ci1, cv1.coeff(), ci2, cv2.coeff()))
|
|
resolve(j, ci1, saturate_constraint(ci2, j, m_mon2vars[u]));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#if 0
|
|
for (unsigned it = 0; it < m_monomials_to_refine.size(); ++it) {
|
|
auto j = m_monomials_to_refine[it];
|
|
auto vars = m_mon2vars[j];
|
|
TRACE(arith, tout << "j" << j << " " << vars << "\n");
|
|
for (auto v : vars) {
|
|
if (v >= m_occurs.size())
|
|
continue;
|
|
svector<lpvar> _vars;
|
|
_vars.push_back(v);
|
|
for (unsigned cidx = 0; cidx < m_occurs[v].size(); ++cidx) {
|
|
auto ci = m_occurs[v][cidx];
|
|
for (auto const &cv1 : m_solver.lra().constraints()[ci].lhs()) {
|
|
auto u = cv1.j();
|
|
if (u == v)
|
|
saturate_constraint(ci, j, _vars);
|
|
else if (is_mon_var(u) && is_subset(m_mon2vars[u], vars))
|
|
saturate_constraint(ci, j, m_mon2vars[u]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
IF_VERBOSE(5,
|
|
c().lra_solver().display(verbose_stream() << "original\n");
|
|
c().display(verbose_stream());
|
|
display(verbose_stream() << "saturated\n"));
|
|
}
|
|
|
|
bool stellensatz::is_resolvable(lp::constraint_index ci1, rational const& c1, lp::constraint_index ci2,
|
|
rational const& c2) const {
|
|
SASSERT(c1 != 0);
|
|
SASSERT(c2 != 0);
|
|
auto const &con1 = m_solver.lra().constraints()[ci1];
|
|
auto const &con2 = m_solver.lra().constraints()[ci2];
|
|
auto k1 = con1.kind();
|
|
auto k2 = con2.kind();
|
|
for (auto const& cv : con1.lhs())
|
|
if (is_mon_var(cv.j()) && m_mon2vars[cv.j()].size() > m_max_monomial_degree)
|
|
return false;
|
|
for (auto const &cv : con2.lhs())
|
|
if (is_mon_var(cv.j()) && m_mon2vars[cv.j()].size() > m_max_monomial_degree)
|
|
return false;
|
|
bool same_sign = (c1.is_pos() == c2.is_pos());
|
|
if (k1 == lp::lconstraint_kind::EQ)
|
|
return true;
|
|
if (k2 == lp::lconstraint_kind::EQ)
|
|
return true;
|
|
bool is_le1 = (k1 == lp::lconstraint_kind::LE || k1 == lp::lconstraint_kind::LT);
|
|
bool is_le2 = (k2 == lp::lconstraint_kind::LE || k2 == lp::lconstraint_kind::LT);
|
|
if ((is_le1 == is_le2) && !same_sign)
|
|
return true;
|
|
if ((is_le1 != is_le2) && same_sign)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
void stellensatz::resolve(lpvar j, lp::constraint_index ci1, lp::constraint_index ci2) {
|
|
// resolut of saturate_constraint could swap inequality,
|
|
// so the premise of is_resolveable may not hold.
|
|
return;
|
|
auto const &constraints = m_solver.lra().constraints();
|
|
if (!constraints.valid_index(ci1))
|
|
return;
|
|
if (!constraints.valid_index(ci2))
|
|
return;
|
|
auto const &con1 = constraints[ci1];
|
|
auto const &con2 = constraints[ci2];
|
|
auto k1 = con1.kind();
|
|
auto k2 = con2.kind();
|
|
auto const & lhs1 = con1.lhs();
|
|
auto const & lhs2 = con2.lhs();
|
|
auto const& c1 = lhs1.get_coeff(j);
|
|
auto const& c2 = lhs2.get_coeff(j);
|
|
verbose_stream() << "resolve on " << m_mon2vars[j] << " c1: " << c1 << " c2: " << c2 << "\n";
|
|
display_constraint(verbose_stream(), ci1) << "\n";
|
|
display_constraint(verbose_stream(), ci2) << "\n";
|
|
rational mul1, mul2;
|
|
bool is_le1 = (k1 == lp::lconstraint_kind::LE || k1 == lp::lconstraint_kind::LT);
|
|
bool is_le2 = (k2 == lp::lconstraint_kind::LE || k2 == lp::lconstraint_kind::LT);
|
|
bool is_strict = (k1 == lp::lconstraint_kind::LT || k2 == lp::lconstraint_kind::LT);
|
|
if (k1 == lp::lconstraint_kind::EQ) {
|
|
mul1 = (c1 > 0 ? -1 : 1) * c2;
|
|
mul2 = (c1 > 0 ? c1 : -c1);
|
|
}
|
|
else if (k2 == lp::lconstraint_kind::EQ) {
|
|
mul1 = (c2 > 0 ? c2 : -c2);
|
|
mul2 = (c2 > 0 ? -1 : 1) * c1;
|
|
}
|
|
else if (is_le1 == is_le2) {
|
|
SASSERT(c1.is_pos() != c2.is_pos());
|
|
mul1 = abs(c2);
|
|
mul2 = abs(c1);
|
|
}
|
|
else {
|
|
SASSERT(c1.is_pos() == c2.is_pos());
|
|
mul1 = abs(c2);
|
|
mul2 = -abs(c1);
|
|
}
|
|
auto lhs = mul1 * lhs1 + mul2 * lhs2;
|
|
auto rhs = mul1 * con1.rhs() + mul2 * con2.rhs();
|
|
assumptions bounds;
|
|
bounds.push_back(ci1);
|
|
bounds.push_back(ci2);
|
|
auto new_ci = add_ineq("resolve", bounds, lhs, k1, rhs);
|
|
insert_monomials_from_constraint(new_ci);
|
|
display_constraint(verbose_stream(), new_ci) << "\n";
|
|
}
|
|
|
|
// multiply by remaining vars
|
|
lp::constraint_index stellensatz::saturate_constraint(lp::constraint_index old_ci, lpvar mi, svector<lpvar> const& xs) {
|
|
resolvent r = {old_ci, mi, xs};
|
|
if (m_resolvents.contains(r))
|
|
return lp::null_ci;
|
|
m_resolvents.insert(r);
|
|
lp::lar_base_constraint const& con = m_solver.lra().constraints()[old_ci];
|
|
auto const& lhs = con.lhs();
|
|
auto const& rhs = con.rhs();
|
|
auto k = con.kind();
|
|
if (k == lp::lconstraint_kind::NE || k == lp::lconstraint_kind::EQ)
|
|
return lp::null_ci; // not supported
|
|
|
|
|
|
// xs is a proper subset of vars in mi
|
|
svector<lpvar> vars(m_mon2vars[mi]);
|
|
|
|
for (auto x : xs) {
|
|
SASSERT(vars.contains(x));
|
|
vars.erase(x);
|
|
}
|
|
SASSERT(!vars.empty());
|
|
SASSERT(vars.size() + xs.size() == m_mon2vars[mi].size());
|
|
|
|
assumptions bounds;
|
|
// compute bounds constraints and sign of vars
|
|
lbool sign = add_bounds(vars, bounds);
|
|
lp::lar_term new_lhs;
|
|
rational new_rhs(rhs);
|
|
for (auto const & cv : lhs) {
|
|
unsigned old_sz = vars.size();
|
|
if (is_mon_var(cv.j()))
|
|
vars.append(m_mon2vars[cv.j()]);
|
|
else
|
|
vars.push_back(cv.j());
|
|
lpvar new_monic_var = add_monomial(vars);
|
|
new_lhs.add_monomial(cv.coeff(), new_monic_var);
|
|
vars.shrink(old_sz);
|
|
}
|
|
if (rhs != 0) {
|
|
lpvar new_monic_var = add_monomial(vars);
|
|
new_lhs.add_monomial(-rhs, new_monic_var);
|
|
new_rhs = 0;
|
|
}
|
|
|
|
if (sign == l_true)
|
|
k = swap_side(k);
|
|
|
|
if (sign == l_undef) {
|
|
// x = 0 => x*y >= 0
|
|
switch (k) {
|
|
case lp::lconstraint_kind::GT:
|
|
k = lp::lconstraint_kind::GE;
|
|
break;
|
|
case lp::lconstraint_kind::LT:
|
|
k = lp::lconstraint_kind::LE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
bounds.push_back(old_ci);
|
|
auto new_ci = add_ineq("superpose", bounds, new_lhs, k, new_rhs);
|
|
IF_VERBOSE(4, display_constraint(verbose_stream(), old_ci) << " -> ";
|
|
display_constraint(verbose_stream(), new_lhs.coeffs_as_vector(), k, new_rhs) << "\n");
|
|
insert_monomials_from_constraint(new_ci);
|
|
m_factored_constraints.insert(new_ci); // don't bother to factor this because it comes from factors already
|
|
return new_ci;
|
|
}
|
|
|
|
// call polynomial factorization using the polynomial manager
|
|
// return true if there is a non-trivial factorization
|
|
// need the following:
|
|
// term -> polynomial translation
|
|
// polynomial -> term translation
|
|
// the call to factorization
|
|
bool stellensatz::get_factors(term_offset& t, vector<std::pair<term_offset, unsigned>>& factors) {
|
|
auto p = to_poly(t);
|
|
polynomial::factors fs(m_pm);
|
|
m_pm.factor(p, fs);
|
|
if (fs.total_factors() <= 1)
|
|
return false;
|
|
unsigned df = fs.distinct_factors();
|
|
for (unsigned i = 0; i < df; ++i) {
|
|
auto f = fs[i];
|
|
auto degree = fs.get_degree(i);
|
|
factors.push_back({to_term(*f), degree});
|
|
}
|
|
SASSERT(!factors.empty());
|
|
return true;
|
|
}
|
|
|
|
polynomial::polynomial_ref stellensatz::to_poly(term_offset const& t) {
|
|
ptr_vector<polynomial::monomial> ms;
|
|
polynomial::scoped_numeral_vector coeffs(m_pm.m());
|
|
rational den(1);
|
|
for (lp::lar_term::ival kv : t.first) {
|
|
auto v = kv.j();
|
|
while (v >= m_pm.num_vars())
|
|
m_pm.mk_var();
|
|
if (is_mon_var(v)) {
|
|
auto const &vars = m_mon2vars[v];
|
|
ms.push_back(m_pm.mk_monomial(vars.size(), vars.data()));
|
|
}
|
|
else
|
|
ms.push_back(m_pm.mk_monomial(v));
|
|
den = lcm(den, denominator(kv.coeff()));
|
|
}
|
|
|
|
for (auto kv : t.first)
|
|
coeffs.push_back((den * kv.coeff()).to_mpq().numerator());
|
|
coeffs.push_back((den * t.second).to_mpq().numerator());
|
|
ms.push_back(m_pm.mk_monomial(0, nullptr));
|
|
polynomial::polynomial_ref p(m_pm);
|
|
p = m_pm.mk_polynomial(ms.size(), coeffs.data(), ms.data());
|
|
return p;
|
|
}
|
|
|
|
stellensatz::term_offset stellensatz::to_term(polynomial::polynomial const& p) {
|
|
term_offset t;
|
|
auto sz = m_pm.size(&p);
|
|
for (unsigned i = 0; i < sz; ++i) {
|
|
auto m = m_pm.get_monomial(&p, i);
|
|
auto const &c = m_pm.coeff(&p, i);
|
|
svector<lpvar> vars;
|
|
for (unsigned j = 0; j < m_pm.size(m); ++j)
|
|
vars.push_back(m_pm.get_var(m, j));
|
|
if (vars.empty())
|
|
t.second += c;
|
|
else
|
|
t.first.add_monomial(c, add_monomial(vars));
|
|
}
|
|
return t;
|
|
}
|
|
|
|
bool stellensatz::saturate_factors(lp::explanation &ex, vector<ineq> &ineqs) {
|
|
return true;
|
|
auto indices = m_solver.lra().constraints().indices();
|
|
return all_of(indices, [&](auto ci) { return saturate_factors(ci, ex, ineqs); });
|
|
}
|
|
|
|
bool stellensatz::saturate_factors(lp::constraint_index ci, lp::explanation& ex, vector<ineq>& ineqs) {
|
|
if (m_factored_constraints.contains(ci))
|
|
return true;
|
|
m_factored_constraints.insert(ci);
|
|
auto const &con = m_solver.lra().constraints()[ci];
|
|
if (all_of(con.lhs(), [&](auto const& cv) { return !is_mon_var(cv.j()); }))
|
|
return true;
|
|
term_offset t;
|
|
// ignore nested terms and nested polynomials..
|
|
for (auto const& cv : con.lhs())
|
|
t.first.add_monomial(cv.coeff(), cv.j());
|
|
t.second = -con.rhs();
|
|
|
|
vector<std::pair<term_offset, unsigned>> factors;
|
|
if (!get_factors(t, factors))
|
|
return true;
|
|
|
|
vector<rational> values;
|
|
rational prod(1);
|
|
for (auto const & [t, degree] : factors) {
|
|
values.push_back(value(t.first) + t.second);
|
|
prod *= values.back();
|
|
if (degree % 2 == 0)
|
|
prod *= values.back();
|
|
}
|
|
|
|
bool is_square = all_of(factors, [&](auto const &f) { return f.second % 2 == 0; });
|
|
if (is_square) {
|
|
auto v = add_term(t);
|
|
assumptions bounds;
|
|
add_ineq("squares", bounds, v, lp::lconstraint_kind::GE, rational(0));
|
|
}
|
|
|
|
IF_VERBOSE(
|
|
3, display_constraint(verbose_stream() << "factored ", ci) << "\n";
|
|
for (auto const &[t, degree] : factors) {
|
|
display(verbose_stream() << " factor ", t)
|
|
<< " ^ " << degree << " = " << (value(t.first) + t.second) << "\n";
|
|
});
|
|
|
|
//
|
|
// p * q >= 0 => (p >= 0 & q >= 0) or (p <= 0 & q <= 0)
|
|
// assert both factors with bound assumptions, and reference to original constraint
|
|
// p >= 0 <- q >= 0 and
|
|
// q >= 0 <- p >= 0
|
|
// the values of p, q in the current interpretation may not satisfy p*q >= 0
|
|
// therefore, assume all but one polynomial satisfies the bound (ignoring multiplicities in this argument).
|
|
// Then derive the bound on the remaining polynomial from the others.
|
|
//
|
|
// prod > 0 and k is <=, <: pick random entry to flip value and sign.
|
|
// prod = 0 and k is <=, >=, pick random 0 entry and assert it
|
|
// prod = 0 and k is >, <, revert 0s from values, recalculate prod, flip random entry if needed.
|
|
//
|
|
auto k = con.kind();
|
|
|
|
if (prod == 0 && (k == lp::lconstraint_kind::LE || k == lp::lconstraint_kind::GE || k == lp::lconstraint_kind::EQ)) {
|
|
unsigned n = 0, k = factors.size();
|
|
for (unsigned i = 0; i < factors.size(); ++i)
|
|
if (values[i] == 0 && rand() % (++n) == 0)
|
|
k = i;
|
|
SASSERT(k < factors.size());
|
|
|
|
auto v = add_term(factors[k].first);
|
|
assumptions bounds;
|
|
bound b(v, lp::lconstraint_kind::EQ, rational(0));
|
|
bounds.push_back(b);
|
|
add_ineq("factor = 0", bounds, v, lp::lconstraint_kind::EQ, rational(0));
|
|
return true;
|
|
}
|
|
|
|
if ((prod > 0 && k == lp::lconstraint_kind::GE) || (prod < 0 && k == lp::lconstraint_kind::LE)) {
|
|
// the current evaluation is consistent with inequalities.
|
|
// add factors that enforce the current evaluation.
|
|
for (auto & f : factors) {
|
|
if (f.second % 2 == 0)
|
|
continue;
|
|
assumptions bounds;
|
|
auto v = add_term(f.first);
|
|
auto k = m_values[v] > 0 ? lp::lconstraint_kind::GE : lp::lconstraint_kind::LE;
|
|
bounds.push_back(bound(v, k, rational(0)));
|
|
add_ineq("factor >= 0", bounds, v, k, rational(0));
|
|
}
|
|
return true;
|
|
}
|
|
|
|
if ((prod > 0 && k == lp::lconstraint_kind::LE) || (prod < 0 && k == lp::lconstraint_kind::GE)) {
|
|
return true;
|
|
// this is a conflict with the current evaluation. Produce a lemma that blocks
|
|
// TODO, need to check if variables use fresh monomials
|
|
ex.push_back(ci);
|
|
for (auto &f : factors) {
|
|
auto [t, offset] = f.first;
|
|
auto val = value(t) + offset;
|
|
auto k = val > 0 ? lp::lconstraint_kind::LE : lp::lconstraint_kind::GE;
|
|
ineqs.push_back(ineq(t, k, -offset));
|
|
}
|
|
TRACE(arith, tout << "factor conflict\n";
|
|
for (auto const &f : factors) display(tout, f.first) << "; "; tout << "\n";
|
|
);
|
|
return false;
|
|
}
|
|
|
|
verbose_stream() << "still todo\n";
|
|
return true;
|
|
}
|
|
|
|
//
|
|
// add bound assumptions from vars
|
|
// return the sign of the product of vars under the current interpretation.
|
|
//
|
|
lbool stellensatz::add_bounds(svector<lpvar> const& vars, assumptions& bounds) {
|
|
bool sign = false;
|
|
auto &src = c().lra_solver();
|
|
auto &dst = m_solver.lra();
|
|
auto add_bound = [&](lpvar v, lp::lconstraint_kind k, int r) {
|
|
bound b(v, k, rational(r));
|
|
bounds.push_back(b);
|
|
};
|
|
for (auto v : vars) {
|
|
if (m_values[v] != 0)
|
|
continue;
|
|
add_bound(v, lp::lconstraint_kind::EQ, 0);
|
|
return l_undef;
|
|
}
|
|
for (auto v : vars) {
|
|
// retrieve bounds of v
|
|
// if v has positive lower bound add as positive
|
|
// if v has negative upper bound add as negative
|
|
// otherwise look at the current value of v and add bounds assumption based on current sign.
|
|
// todo: detect squares, allow for EQ but skip bounds assumptions.
|
|
if (src.number_of_vars() > v && src.column_has_lower_bound(v) && src.get_lower_bound(v).is_pos()) {
|
|
bounds.push_back(src.get_column_lower_bound_witness(v));
|
|
}
|
|
else if (src.number_of_vars() > v && src.column_has_upper_bound(v) && src.get_upper_bound(v).is_neg()) {
|
|
bounds.push_back(src.get_column_upper_bound_witness(v));
|
|
sign = !sign;
|
|
}
|
|
else if (m_values[v] < 0) {
|
|
add_bound(v, lp::lconstraint_kind::LT, 0);
|
|
sign = !sign;
|
|
}
|
|
else if (m_values[v] > 0) {
|
|
add_bound(v, lp::lconstraint_kind::GT, 0);
|
|
} else {
|
|
UNREACHABLE();
|
|
}
|
|
}
|
|
return sign ? l_true : l_false;
|
|
}
|
|
|
|
// Insert a (new) monomial representing product of vars.
|
|
// if the length of vars is 1 then the new monomial is vars[0]
|
|
// if the same monomial was previous defined, return the previously defined monomial.
|
|
// otherwise create a fresh variable representing the monomial.
|
|
// todo: if _vars is a square, then add bound axiom.
|
|
lpvar stellensatz::add_monomial(svector<lpvar> const& vars) {
|
|
lpvar v;
|
|
if (vars.size() == 1)
|
|
return vars[0];
|
|
svector<lpvar> _vars(vars);
|
|
std::sort(_vars.begin(), _vars.end());
|
|
if (m_vars2mon.find(_vars, v))
|
|
return v;
|
|
|
|
v = add_var(is_int(vars));
|
|
m_vars2mon.insert(_vars, v);
|
|
m_mon2vars.insert(v, _vars);
|
|
SASSERT(m_values.size() == v);
|
|
m_values.push_back(value(vars));
|
|
return v;
|
|
}
|
|
|
|
lpvar stellensatz::add_term(term_offset& t) {
|
|
if (t.second != 0) {
|
|
auto w = add_var(t.second.is_int()); // or reuse the constant 1 that is already there.
|
|
m_values.push_back(t.second);
|
|
m_solver.lra().add_var_bound(w, lp::lconstraint_kind::GE, t.second);
|
|
m_solver.lra().add_var_bound(w, lp::lconstraint_kind::LE, t.second);
|
|
t.first.add_monomial(rational(1), w);
|
|
t.second = 0;
|
|
}
|
|
auto ti = m_solver.lra().add_term(t.first.coeffs_as_vector(), m_solver.lra().number_of_vars());
|
|
m_values.push_back(value(t.first));
|
|
return ti;
|
|
}
|
|
|
|
bool stellensatz::is_int(svector<lp::lpvar> const& vars) const {
|
|
return all_of(vars, [&](lpvar v) { return m_solver.lra().var_is_int(v); });
|
|
}
|
|
|
|
lpvar stellensatz::add_var(bool is_int) {
|
|
auto v = m_solver.lra().number_of_vars();
|
|
auto w = m_solver.lra().add_var(v, is_int);
|
|
SASSERT(v == w);
|
|
return w;
|
|
}
|
|
|
|
// if a constraint is false, then insert _all_ monomials from that constraint.
|
|
// other approach: use a lex ordering on monomials and insert the lex leading monomial.
|
|
// to avoid blowup, only insert monomials up to a certain degree.
|
|
void stellensatz::insert_monomials_from_constraint(lp::constraint_index ci) {
|
|
if (/*false && */ constraint_is_true(ci))
|
|
return;
|
|
m_false_constraints.insert(ci);
|
|
auto const& con = m_solver.lra().constraints()[ci];
|
|
for (auto cv : con.lhs())
|
|
if (is_mon_var(cv.j()) && m_mon2vars[cv.j()].size() <= m_max_monomial_degree)
|
|
m_monomials_to_refine.insert(cv.j());
|
|
}
|
|
|
|
bool stellensatz::constraint_is_true(lp::constraint_index ci) const {
|
|
auto const& con = m_solver.lra().constraints()[ci];
|
|
auto lhs = -con.rhs();
|
|
for (auto const& cv : con.lhs())
|
|
lhs += cv.coeff() * m_values[cv.j()];
|
|
switch (con.kind()) {
|
|
case lp::lconstraint_kind::GT:
|
|
return lhs > 0;
|
|
case lp::lconstraint_kind::GE:
|
|
return lhs >= 0;
|
|
case lp::lconstraint_kind::LE:
|
|
return lhs <= 0;
|
|
case lp::lconstraint_kind::LT:
|
|
return lhs < 0;
|
|
case lp::lconstraint_kind::EQ:
|
|
return lhs == 0;
|
|
case lp::lconstraint_kind::NE:
|
|
return lhs != 0;
|
|
default:
|
|
UNREACHABLE();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
std::ostream& stellensatz::display(std::ostream& out) const {
|
|
m_solver.lra().display(out);
|
|
for (auto const& [vars, v] : m_vars2mon) {
|
|
out << "j" << v << " := ";
|
|
display_product(out, vars);
|
|
out << "\n";
|
|
}
|
|
for (auto ci : m_solver.lra().constraints().indices()) {
|
|
out << "(" << ci << ") ";
|
|
display_constraint(out, ci);
|
|
bool is_true = constraint_is_true(ci);
|
|
out << (is_true ? " [true]" : " [false]") << "\n";
|
|
if (!m_assumptions.contains(ci))
|
|
continue;
|
|
out << "\t<- ";
|
|
display(out, m_assumptions[ci]);
|
|
out << "\n";
|
|
}
|
|
return out;
|
|
}
|
|
|
|
std::ostream& stellensatz::display(std::ostream& out, assumptions const& bounds) const {
|
|
for (auto b : bounds) {
|
|
if (std::holds_alternative<u_dependency *>(b)) {
|
|
auto dep = *std::get_if<u_dependency *>(&b);
|
|
unsigned_vector cs;
|
|
c().lra_solver().dep_manager().linearize(dep, cs);
|
|
for (auto c : cs)
|
|
out << "[o " << c << "] "; // constraint index from c().lra_solver.
|
|
}
|
|
else if (std::holds_alternative<lp::constraint_index>(b)) {
|
|
auto ci = *std::get_if<lp::constraint_index>(&b);
|
|
out << "(" << ci << ") "; // constraint index from this solver.
|
|
}
|
|
else {
|
|
auto [v, k, rhs] = *std::get_if<bound>(&b);
|
|
out << "[j" << v << " " << k << " " << rhs << "] ";
|
|
}
|
|
}
|
|
return out;
|
|
}
|
|
|
|
std::ostream& stellensatz::display_product(std::ostream& out, svector<lpvar> const& vars) const {
|
|
bool first = true;
|
|
for (auto v : vars) {
|
|
if (first)
|
|
first = false;
|
|
else
|
|
out << " * ";
|
|
out << "j" << v;
|
|
}
|
|
return out;
|
|
}
|
|
|
|
std::ostream& stellensatz::display(std::ostream& out, term_offset const& t) const {
|
|
bool first = true;
|
|
for (auto [v, coeff] : t.first) {
|
|
c().display_coeff(out, first, coeff);
|
|
first = false;
|
|
if (is_mon_var(v))
|
|
display_product(out, m_mon2vars[v]);
|
|
else
|
|
out << "j" << v;
|
|
}
|
|
if (t.second != 0)
|
|
out << " + " << t.second;
|
|
return out;
|
|
}
|
|
|
|
std::ostream& stellensatz::display_constraint(std::ostream& out, lp::constraint_index ci) const {
|
|
auto const& con = m_solver.lra().constraints()[ci];
|
|
return display_constraint(out, con.lhs(), con.kind(), con.rhs());
|
|
}
|
|
|
|
std::ostream& stellensatz::display_constraint(std::ostream& out,
|
|
lp::lar_term const& lhs,
|
|
lp::lconstraint_kind k, rational const& rhs) const {
|
|
bool first = true;
|
|
for (auto cv : lhs) {
|
|
auto v = cv.j();
|
|
c().display_coeff(out, first, cv.coeff());
|
|
first = false;
|
|
if (is_mon_var(v))
|
|
display_product(out, m_mon2vars[v]);
|
|
else
|
|
out << "j" << v;
|
|
}
|
|
return out << " " << k << " " << rhs;
|
|
}
|
|
|
|
// Solver component
|
|
// check LRA feasibilty
|
|
// (partial) check LIA feasibility
|
|
// try patch LIA model
|
|
// option: iterate by continuing saturation based on partial results
|
|
// option: add incremental linearization axioms
|
|
// option: detect squares and add axioms for violated squares
|
|
// option: add NIA filters (non-linear divisbility axioms)
|
|
|
|
void stellensatz::solver::init() {
|
|
lra_solver = alloc(lp::lar_solver);
|
|
int_solver = alloc(lp::int_solver, *lra_solver);
|
|
}
|
|
|
|
lbool stellensatz::solver::solve(lp::explanation &ex, vector<ineq>& ineqs) {
|
|
while (true) {
|
|
lbool r = solve_lra(ex);
|
|
if (r != l_true)
|
|
return r;
|
|
r = solve_lia(ex);
|
|
if (r != l_true)
|
|
return r;
|
|
unsigned sz = lra_solver->number_of_vars();
|
|
if (update_values())
|
|
return l_true;
|
|
|
|
return l_undef;
|
|
// At this point it is not sound to use value assumptions
|
|
// because the model changed from the outer LRA solver.
|
|
// Also value assumptions made in a previous iteration may no longer be valid.
|
|
// this can be fixed by asserting the value assumptions as bounds directly and
|
|
// making them depend on themselves.
|
|
TRACE(arith, s.display(tout));
|
|
if (!s.saturate_factors(ex, ineqs))
|
|
return l_false;
|
|
// s.saturate_constraints(); ?
|
|
if (sz == lra_solver->number_of_vars())
|
|
return l_undef;
|
|
}
|
|
}
|
|
|
|
lbool stellensatz::solver::solve_lra(lp::explanation &ex) {
|
|
auto status = lra_solver->find_feasible_solution();;
|
|
if (lra_solver->is_feasible())
|
|
return l_true;
|
|
if (status == lp::lp_status::INFEASIBLE) {
|
|
lra_solver->get_infeasibility_explanation(ex);
|
|
return l_false;
|
|
}
|
|
return l_undef;
|
|
}
|
|
|
|
lbool stellensatz::solver::solve_lia(lp::explanation &ex) {
|
|
switch (int_solver->check(&ex)) {
|
|
case lp::lia_move::sat:
|
|
return l_true;
|
|
case lp::lia_move::conflict:
|
|
return l_false;
|
|
default: // TODO: an option is to perform (bounded) search here to get an LIA verdict.
|
|
return l_undef;
|
|
}
|
|
return l_undef;
|
|
}
|
|
|
|
// update m_values
|
|
// return true if the new assignment satisfies the products.
|
|
// return false if value constraints are not satisfied on monomials and there is a false constaint.
|
|
bool stellensatz::solver::update_values() {
|
|
std::unordered_map<lpvar, rational> values;
|
|
lra_solver->get_model(values);
|
|
unsigned sz = lra_solver->number_of_vars();
|
|
for (unsigned i = 0; i < sz; ++i) {
|
|
auto const &value = values[i];
|
|
bool is_int = lra_solver->var_is_int(i);
|
|
SASSERT(!is_int || value.is_int());
|
|
if (s.is_mon_var(i))
|
|
s.m_values[i] = s.value(s.m_mon2vars[i]);
|
|
else
|
|
s.m_values[i] = value;
|
|
}
|
|
auto indices = lra_solver->constraints().indices();
|
|
bool satisfies_products = all_of(indices, [&](auto ci) {
|
|
return s.constraint_is_true(ci);});
|
|
s.m_monomials_to_refine.reset();
|
|
s.m_false_constraints.reset();
|
|
// check if all constraints are satisfied
|
|
// if they are, then update the model of parent lra solver.
|
|
for (auto ci : indices)
|
|
s.insert_monomials_from_constraint(ci);
|
|
SASSERT(satisfies_products == s.m_monomials_to_refine.empty());
|
|
if (satisfies_products) {
|
|
TRACE(arith, tout << "found satisfying model\n");
|
|
for (auto v : s.m_coi.vars())
|
|
s.c().lra_solver().set_column_value(v, lp::impq(s.m_values[v], rational(0)));
|
|
}
|
|
for (auto j : s.m_monomials_to_refine) {
|
|
auto val_j = values[j];
|
|
auto const &vars = s.m_mon2vars[j];
|
|
auto val_vars = s.m_values[j];
|
|
TRACE(arith, tout << "refine j" << j << " " << vars << "\n");
|
|
if (val_j != val_vars)
|
|
s.saturate_basic_linearize(j, val_j, vars, val_vars);
|
|
}
|
|
return satisfies_products;
|
|
}
|
|
}
|