3
0
Fork 0
mirror of https://github.com/Z3Prover/z3 synced 2025-10-08 17:01:55 +00:00

mul-saturation wip

fixup conflict explanations in mul_saturation, add parameter to enable it, add statistics
This commit is contained in:
Nikolaj Bjorner 2025-09-27 12:17:40 +03:00
parent ad2c97a4df
commit 88844a84aa
9 changed files with 165 additions and 101 deletions

View file

@ -34,6 +34,7 @@ void lp::lp_settings::updt_params(params_ref const& _p) {
report_frequency = p.arith_rep_freq(); report_frequency = p.arith_rep_freq();
m_simplex_strategy = static_cast<lp::simplex_strategy_enum>(p.arith_simplex_strategy()); m_simplex_strategy = static_cast<lp::simplex_strategy_enum>(p.arith_simplex_strategy());
m_nlsat_delay = p.arith_nl_delay(); m_nlsat_delay = p.arith_nl_delay();
m_enable_stellensatz = p.arith_nl_stellensatz();
auto eps = p.arith_epsilon(); auto eps = p.arith_epsilon();
m_epsilon = rational(std::max(1, (int)(100000*eps)), 100000); m_epsilon = rational(std::max(1, (int)(100000*eps)), 100000);
m_dio = lp_p.dio(); m_dio = lp_p.dio();

View file

@ -137,6 +137,7 @@ struct statistics {
unsigned m_bounds_tightening_conflicts = 0; unsigned m_bounds_tightening_conflicts = 0;
unsigned m_bounds_tightenings = 0; unsigned m_bounds_tightenings = 0;
unsigned m_nla_throttled_lemmas = 0; unsigned m_nla_throttled_lemmas = 0;
unsigned m_nla_stellensatz = 0;
::statistics m_st = {}; ::statistics m_st = {};
@ -176,6 +177,7 @@ struct statistics {
st.update("arith-bounds-tightening-conflicts", m_bounds_tightening_conflicts); st.update("arith-bounds-tightening-conflicts", m_bounds_tightening_conflicts);
st.update("arith-bounds-tightenings", m_bounds_tightenings); st.update("arith-bounds-tightenings", m_bounds_tightenings);
st.update("arith-nla-throttled-lemmas", m_nla_throttled_lemmas); st.update("arith-nla-throttled-lemmas", m_nla_throttled_lemmas);
st.update("arith-nla-stellensatz", m_nla_stellensatz);
st.copy(m_st); st.copy(m_st);
} }
}; };
@ -260,6 +262,7 @@ private:
bool m_dio_run_gcd = true; bool m_dio_run_gcd = true;
public: public:
bool m_enable_relevancy = false; bool m_enable_relevancy = false;
bool m_enable_stellensatz = true;
unsigned dio_calls_period() const { return m_dio_calls_period; } unsigned dio_calls_period() const { return m_dio_calls_period; }
unsigned & dio_calls_period() { return m_dio_calls_period; } unsigned & dio_calls_period() { return m_dio_calls_period; }
bool print_external_var_name() const { return m_print_external_var_name; } bool print_external_var_name() const { return m_print_external_var_name; }

View file

@ -40,6 +40,7 @@ namespace lp {
EQ = 0, EQ = 0,
NE = 3 NE = 3
}; };
typedef unsigned lpvar; typedef unsigned lpvar;
const lpvar null_lpvar = UINT_MAX; const lpvar null_lpvar = UINT_MAX;
const constraint_index null_ci = UINT_MAX; const constraint_index null_ci = UINT_MAX;

View file

@ -1333,7 +1333,7 @@ lbool core::check() {
return l_false; return l_false;
} }
if (false && no_effect()) if (no_effect() && lp_settings().m_enable_stellensatz)
ret = m_mul_saturate.saturate(); ret = m_mul_saturate.saturate();
if (no_effect() && should_run_bounded_nlsat()) if (no_effect() && should_run_bounded_nlsat())

View file

@ -1,19 +1,30 @@
/*++ /*++
Copyright (c) 2025 Microsoft Corporation Copyright (c) 2025 Microsoft Corporation
given a monic m = x * y * z ... with evaluation val(m) != val(x) * val(y) * val(z) ... 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 saturate constraints with respect to m
in other words, if a constraint contains x*y + p >= 0, in other words, if a constraint contains x*y + p >= 0,
then include the constraint z >= 0 => x*y*z + z*p >= 0 then include the constraint x*y*z + z*p >= 0
assuming current value of z is non-negative. assuming current value of z is non-negative.
Check if the system with new constraints is LP feasible. Check if the system with new constraints is LP (and MIP) feasible.
If it is not, then produce a lemma that explains the infeasibility. 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 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. 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 If it succeeds to eliminate new monomials we have a lemma that is a linear
combination of existing variables. 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. Strategy 3: The lemma uses the new constraints.
--*/ --*/
@ -38,7 +49,8 @@ namespace nla {
} }
void mul_saturate::init_solver() { void mul_saturate::init_solver() {
local_solver = alloc(lp::lar_solver); lra_solver = alloc(lp::lar_solver);
int_solver = alloc(lp::int_solver, *lra_solver);
m_vars2mon.reset(); m_vars2mon.reset();
m_mon2vars.reset(); m_mon2vars.reset();
m_values.reset(); m_values.reset();
@ -50,6 +62,7 @@ namespace nla {
auto const& lra = c().lra_solver(); auto const& lra = c().lra_solver();
auto sz = lra.number_of_vars(); auto sz = lra.number_of_vars();
for (unsigned v = 0; v < sz; ++v) { for (unsigned v = 0; v < sz; ++v) {
// Declare v into lra_solver
unsigned w; unsigned w;
if (m_coi.mons().contains(v)) if (m_coi.mons().contains(v))
init_monomial(v); init_monomial(v);
@ -59,24 +72,28 @@ namespace nla {
auto const& t = lra.get_term(v); auto const& t = lra.get_term(v);
// Assumption: variables in coefficients are always declared before term variable. // Assumption: variables in coefficients are always declared before term variable.
SASSERT(all_of(t, [&](auto p) { return p.j() < v; })); SASSERT(all_of(t, [&](auto p) { return p.j() < v; }));
w = local_solver->add_term(t.coeffs_as_vector(), v); w = lra_solver->add_term(t.coeffs_as_vector(), v);
} }
else else
w = local_solver->add_var(v, lra.var_is_int(v)); w = lra_solver->add_var(v, lra.var_is_int(v));
// assert bounds on v in the new solver.
VERIFY(w == v); VERIFY(w == v);
if (lra.column_has_lower_bound(v)) { if (lra.column_has_lower_bound(v)) {
auto lo_dep = lra.get_column_lower_bound_witness(v);
auto lo_bound = lra.get_lower_bound(v); auto lo_bound = lra.get_lower_bound(v);
auto k = lo_bound.y > 0 ? lp::lconstraint_kind::GT : lp::lconstraint_kind::GE; auto k = lo_bound.y > 0 ? lp::lconstraint_kind::GT : lp::lconstraint_kind::GE;
auto ci = local_solver->add_var_bound(v, k, lo_bound.x); auto rhs = lo_bound.x;
auto dep = lra.get_column_lower_bound_witness(v);
auto ci = lra_solver->add_var_bound(v, k, rhs);
m_ci2dep.setx(ci, dep, nullptr);
} }
if (lra.column_has_upper_bound(v)) { if (lra.column_has_upper_bound(v)) {
auto hi_dep = lra.get_column_upper_bound_witness(v);
auto hi_bound = lra.get_upper_bound(v); auto hi_bound = lra.get_upper_bound(v);
auto k = hi_bound.y < 0 ? lp::lconstraint_kind::LT : lp::lconstraint_kind::LE; auto k = hi_bound.y < 0 ? lp::lconstraint_kind::LT : lp::lconstraint_kind::LE;
auto ci = local_solver->add_var_bound(v, k, hi_bound.x); auto rhs = hi_bound.x;
m_ci2dep.setx(ci, hi_dep, nullptr); auto dep = lra.get_column_upper_bound_witness(v);
auto ci = lra_solver->add_var_bound(v, k, rhs);
m_ci2dep.setx(ci, dep, nullptr);
} }
} }
} }
@ -96,81 +113,106 @@ namespace nla {
void mul_saturate::add_lemma(lp::explanation const& ex1) { void mul_saturate::add_lemma(lp::explanation const& ex1) {
auto& lra = c().lra_solver(); auto& lra = c().lra_solver();
lp::explanation ex2; lp::explanation ex2;
for (auto p : ex1) {
lp::constraint_index src_ci;
if (!m_new_mul_constraints.find(p.ci(), src_ci))
src_ci = p.ci();
auto dep = m_ci2dep.get(src_ci, nullptr);
local_solver->push_explanation(dep, ex2);
}
for (auto [v, sign, dep] : m_var_signs) {
if (!dep) {
verbose_stream() << "TODO explain non-implied bound\n";
continue;
}
local_solver->push_explanation(dep, ex2);
}
lemma_builder new_lemma(c(), "stellensatz"); lemma_builder new_lemma(c(), "stellensatz");
for (auto p : ex1) {
auto dep = m_ci2dep.get(p.ci(), nullptr);
lra_solver->push_explanation(dep, ex2);
if (!m_new_mul_constraints.contains(p.ci()))
continue;
auto const& bounds = m_new_mul_constraints[p.ci()];
for (auto const& b : bounds) {
if (std::holds_alternative<u_dependency*>(b)) {
auto dep = *std::get_if<u_dependency*>(&b);
lra_solver->push_explanation(dep, ex2);
}
else {
auto const &[v, k, rhs] = *std::get_if<bound>(&b);
new_lemma |= ineq(v, negate(k), rhs);
}
}
}
new_lemma &= ex2; new_lemma &= ex2;
IF_VERBOSE(1, verbose_stream() << "unsat \n" << new_lemma << "\n"); IF_VERBOSE(5, verbose_stream() << "unsat \n" << new_lemma << "\n");
TRACE(arith, tout << "unsat\n" << new_lemma << "\n"); TRACE(arith, tout << "unsat\n" << new_lemma << "\n");
c().lra_solver().settings().stats().m_nla_stellensatz++;
} }
lbool mul_saturate::solve(lp::explanation& ex) { lbool mul_saturate::solve(lp::explanation& ex) {
for (auto const& [new_ci, old_ci] : m_new_mul_constraints) lbool r = solve_lra(ex);
local_solver->activate(new_ci); if (r != l_true)
auto st = local_solver->solve(); return r;
lbool r = l_undef; r = solve_lia(ex);
if (r != l_true)
return r;
// if (r == l_true) check if solution satisfies constraints
// variables outside the slice have values from the outer solver.
return l_undef;
}
lbool mul_saturate::solve_lra(lp::explanation& ex) {
auto st = lra_solver->solve();
if (st == lp::lp_status::INFEASIBLE) { if (st == lp::lp_status::INFEASIBLE) {
local_solver->get_infeasibility_explanation(ex); lra_solver->get_infeasibility_explanation(ex);
r = l_false; return l_false;
} }
if (st == lp::lp_status::OPTIMAL || st == lp::lp_status::FEASIBLE) { else if (lra_solver->is_feasible())
// TODO: check model just in case it got lucky. return l_true;
IF_VERBOSE(1, verbose_stream() << "saturation is LP feasible, maybe it is a model of the NLA problem\n"); else
return l_undef;
}
lbool mul_saturate::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;
} }
IF_VERBOSE(0, display(verbose_stream())); return l_undef;
return r;
} }
// record new monomials that are created and recursively down-saturate with respect to these. // record new monomials that are created and recursively down-saturate with respect to these.
// this is a simplistic pass // this is a simplistic pass
void mul_saturate::add_multiply_constraints() { void mul_saturate::add_multiply_constraints() {
m_new_mul_constraints.reset(); m_new_mul_constraints.reset();
m_seen_vars.reset();
m_var_signs.reset();
m_to_refine.reset(); m_to_refine.reset();
vector<svector<lp::constraint_index>> var2cs; vector<svector<lp::constraint_index>> var2cs;
for (auto ci : local_solver->constraints().indices()) { // current approach: only resolve against var2cs, which is initialized
auto const& con = local_solver->constraints()[ci]; // with monomials in the input.
for (auto ci : lra_solver->constraints().indices()) {
auto const& con = lra_solver->constraints()[ci];
for (auto [coeff, v] : con.coeffs()) { for (auto [coeff, v] : con.coeffs()) {
if (v >= var2cs.size()) if (v >= var2cs.size())
var2cs.resize(v + 1); var2cs.resize(v + 1);
var2cs[v].push_back(ci); var2cs[v].push_back(ci);
} }
// insert monomials to be refined // insert monomials to be refined
insert_monomials_from_constraint(ci); insert_monomials_from_constraint(ci);
} }
for (unsigned it = 0; it < m_to_refine.size(); ++it) { for (unsigned it = 0; it < m_to_refine.size(); ++it) {
auto j = m_to_refine[it]; auto j = m_to_refine[it];
verbose_stream() << "refining " << j << " := " << m_mon2vars[j] << "\n";
auto vars = m_mon2vars[j]; auto vars = m_mon2vars[j];
for (auto v : vars) { for (auto v : vars) {
if (v >= var2cs.size()) if (v >= var2cs.size())
continue; continue;
auto cs = var2cs[v]; auto cs = var2cs[v];
for (auto ci : cs) { for (auto ci : cs) {
for (auto [coeff, u] : local_solver->constraints()[ci].coeffs()) { for (auto [coeff, u] : lra_solver->constraints()[ci].coeffs()) {
if (u == v) if (u == v)
add_multiply_constraint(ci, j, v); add_multiply_constraint(ci, j, v);
} }
} }
} }
} }
IF_VERBOSE(0, IF_VERBOSE(5,
c().lra_solver().display(verbose_stream() << "original\n"); c().lra_solver().display(verbose_stream() << "original\n");
c().display(verbose_stream()); c().display(verbose_stream());
display(verbose_stream() << "saturated\n")); display(verbose_stream() << "saturated\n"));
@ -178,52 +220,60 @@ namespace nla {
// multiply by remaining vars // multiply by remaining vars
void mul_saturate::add_multiply_constraint(lp::constraint_index old_ci, lp::lpvar mi, lpvar x) { void mul_saturate::add_multiply_constraint(lp::constraint_index old_ci, lp::lpvar mi, lpvar x) {
lp::lar_base_constraint const& con = local_solver->constraints()[old_ci]; lp::lar_base_constraint const& con = lra_solver->constraints()[old_ci];
auto &lra = c().lra_solver(); auto &lra = c().lra_solver();
auto const& lhs = con.coeffs(); auto const& lhs = con.coeffs();
auto const& rhs = con.rhs(); auto const& rhs = con.rhs();
auto k = con.kind(); auto k = con.kind();
if (k == lp::lconstraint_kind::NE || k == lp::lconstraint_kind::EQ) if (k == lp::lconstraint_kind::NE || k == lp::lconstraint_kind::EQ)
return; // not supported return; // not supported
auto sign = false; auto sign = false, strict = true;
svector<lpvar> vars; svector<lpvar> vars;
bool first = true; bool first = true;
for (auto v : c().emon(mi).vars()) { for (auto v : m_mon2vars[mi]) {
if (v != x || !first) if (v != x || !first)
vars.push_back(v); vars.push_back(v);
else else
first = false; first = false;
} }
// compute sign of vars SASSERT(!first); // v was a member and was removed
vector<bound_justification> bounds;
// compute bounds constraints and sign of vars
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) { for (auto v : vars) {
if (m_seen_vars.contains(v))
continue;
m_seen_vars.insert(v);
// retrieve bounds of v // retrieve bounds of v
// if v has positive lower bound add as positive // if v has positive lower bound add as positive
// if v has negative upper bound add as negative // if v has negative upper bound add as negative
// otherwise, soft-fail (for now unsound) // otherwise look at the current value of v and add bounds assumption based on current sign.
// proper signs of variables from old tableau should be extracted using lra_solver()
// instead of local_solver.
// TODO is to also add case where lower or upper bound is zero and then the sign
// of the inequality is relaxed if it is strict.
if (lra.number_of_vars() > v && lra.column_has_lower_bound(v) && lra.get_lower_bound(v).is_pos()) { if (lra.number_of_vars() > v && lra.column_has_lower_bound(v) && lra.get_lower_bound(v).is_pos()) {
m_var_signs.push_back({v, false, lra.get_column_lower_bound_witness(v)}); bounds.push_back(lra.get_column_lower_bound_witness(v));
} }
else if (lra.number_of_vars() > v && lra.column_has_upper_bound(v) && lra.get_upper_bound(v).is_neg()) { else if (lra.number_of_vars() > v && lra.column_has_upper_bound(v) && lra.get_upper_bound(v).is_neg()) {
m_var_signs.push_back({v, true, lra.get_column_upper_bound_witness(v)}); bounds.push_back(lra.get_column_upper_bound_witness(v));
sign = !sign; sign = !sign;
} }
else if (m_values[v].is_neg()) { else if (m_values[v].is_neg()) {
m_var_signs.push_back({v, true, nullptr}); if (lra.var_is_int(v))
add_bound(v, lp::lconstraint_kind::LE, -1);
else
add_bound(v, lp::lconstraint_kind::LT, 0);
sign = !sign; sign = !sign;
} }
else if (m_values[v].is_pos()) { else if (m_values[v].is_pos()) {
m_var_signs.push_back({v, false, nullptr}); if (lra.var_is_int(v))
add_bound(v, lp::lconstraint_kind::GE, 1);
else
add_bound(v, lp::lconstraint_kind::GT, 0);
} }
else { else {
IF_VERBOSE(0, verbose_stream() << "not separated from 0\n"); SASSERT(m_values[v] == 0);
return; strict = false;
add_bound(v, lp::lconstraint_kind::GE, 0);
} }
} }
lp::lar_term new_lhs; lp::lar_term new_lhs;
@ -246,34 +296,35 @@ namespace nla {
new_rhs = 0; new_rhs = 0;
} }
if (sign) { if (sign)
k = swap_side(k);
if (!strict) {
switch (k) { switch (k) {
case lp::lconstraint_kind::LE: case lp::lconstraint_kind::GT:
k = lp::lconstraint_kind::GE; k = lp::lconstraint_kind::GE;
break; break;
case lp::lconstraint_kind::LT: case lp::lconstraint_kind::LT:
k = lp::lconstraint_kind::GT;
break;
case lp::lconstraint_kind::GE:
k = lp::lconstraint_kind::LE; k = lp::lconstraint_kind::LE;
break; break;
case lp::lconstraint_kind::GT:
k = lp::lconstraint_kind::LT;
break;
default:
break;
} }
} }
display_constraint(verbose_stream(), old_ci) << " -> ";
display_constraint(verbose_stream(), new_lhs.coeffs_as_vector(), k, new_rhs) << "\n"; auto ti = lra_solver->add_term(new_lhs.coeffs_as_vector(), lra_solver->number_of_vars());
auto ti = local_solver->add_term(new_lhs.coeffs_as_vector(), local_solver->number_of_vars()); auto new_ci = lra_solver->add_var_bound(ti, k, new_rhs);
auto new_ci = local_solver->add_var_bound(ti, 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); insert_monomials_from_constraint(new_ci);
m_values.push_back(term_value); m_values.push_back(term_value);
SASSERT(m_values.size() - 1 == ti); SASSERT(m_values.size() - 1 == ti);
m_new_mul_constraints.insert(new_ci, old_ci); m_new_mul_constraints.insert(new_ci, bounds);
m_ci2dep.setx(new_ci, m_ci2dep.get(old_ci, nullptr), nullptr);
} }
// 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.
lpvar mul_saturate::add_monomial(svector<lpvar> const& vars) { lpvar mul_saturate::add_monomial(svector<lpvar> const& vars) {
lpvar v; lpvar v;
if (vars.size() == 1) if (vars.size() == 1)
@ -300,23 +351,25 @@ namespace nla {
} }
lpvar mul_saturate::add_var(bool is_int) { lpvar mul_saturate::add_var(bool is_int) {
auto v = local_solver->number_of_vars(); auto v = lra_solver->number_of_vars();
auto w = local_solver->add_var(v, is_int); auto w = lra_solver->add_var(v, is_int);
VERIFY(v == w); SASSERT(v == w);
return 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.
void mul_saturate::insert_monomials_from_constraint(lp::constraint_index ci) { void mul_saturate::insert_monomials_from_constraint(lp::constraint_index ci) {
if (constraint_is_true(ci)) if (constraint_is_true(ci))
return; return;
auto const& con = local_solver->constraints()[ci]; auto const& con = lra_solver->constraints()[ci];
for (auto [coeff, v] : con.coeffs()) for (auto [coeff, v] : con.coeffs())
if (c().is_monic_var(v)) if (c().is_monic_var(v))
m_to_refine.insert(v); m_to_refine.insert(v);
} }
bool mul_saturate::constraint_is_true(lp::constraint_index ci) { bool mul_saturate::constraint_is_true(lp::constraint_index ci) {
auto const& con = local_solver->constraints()[ci]; auto const& con = lra_solver->constraints()[ci];
auto lhs = -con.rhs(); auto lhs = -con.rhs();
for (auto const& [coeff, v] : con.coeffs()) for (auto const& [coeff, v] : con.coeffs())
lhs += coeff * m_values[v]; lhs += coeff * m_values[v];
@ -340,7 +393,7 @@ namespace nla {
} }
std::ostream& mul_saturate::display(std::ostream& out) const { std::ostream& mul_saturate::display(std::ostream& out) const {
local_solver->display(out); lra_solver->display(out);
for (auto const& [vars, v] : m_vars2mon) { for (auto const& [vars, v] : m_vars2mon) {
out << "j" << v << " := "; out << "j" << v << " := ";
display_product(out, vars); display_product(out, vars);
@ -362,7 +415,7 @@ namespace nla {
} }
std::ostream& mul_saturate::display_constraint(std::ostream& out, lp::constraint_index ci) const { std::ostream& mul_saturate::display_constraint(std::ostream& out, lp::constraint_index ci) const {
auto const& con = local_solver->constraints()[ci]; auto const& con = lra_solver->constraints()[ci];
return display_constraint(out, con.coeffs(), con.kind(), con.rhs()); return display_constraint(out, con.coeffs(), con.kind(), con.rhs());
} }

View file

@ -5,6 +5,8 @@
#pragma once #pragma once
#include "math/lp/nla_coi.h" #include "math/lp/nla_coi.h"
#include "math/lp/int_solver.h"
#include <variant>
namespace nla { namespace nla {
@ -12,18 +14,19 @@ namespace nla {
class lar_solver; class lar_solver;
class mul_saturate : common { class mul_saturate : common {
struct var_sign {
struct bound {
lpvar v = lp::null_lpvar; lpvar v = lp::null_lpvar;
bool is_neg = false; lp::lconstraint_kind k;
u_dependency* dep = nullptr; rational rhs;
}; };
using bound_justification = std::variant<u_dependency*, bound>;
coi m_coi; coi m_coi;
// source of multiplication constraint u_map<vector<bound_justification>> m_new_mul_constraints;
u_map<lp::constraint_index> m_new_mul_constraints;
svector<var_sign> m_var_signs;
tracked_uint_set m_seen_vars;
indexed_uint_set m_to_refine; indexed_uint_set m_to_refine;
scoped_ptr<lp::lar_solver> local_solver; scoped_ptr<lp::lar_solver> lra_solver;
scoped_ptr<lp::int_solver> int_solver;
ptr_vector<u_dependency> m_ci2dep; ptr_vector<u_dependency> m_ci2dep;
vector<rational> m_values; vector<rational> m_values;
struct eq { struct eq {
@ -51,6 +54,8 @@ namespace nla {
// solving // solving
lbool solve(lp::explanation& ex); lbool solve(lp::explanation& ex);
lbool solve_lra(lp::explanation &ex);
lbool solve_lia(lp::explanation &ex);
// lemmas // lemmas
void add_lemma(lp::explanation const& ex); void add_lemma(lp::explanation const& ex);

View file

@ -88,6 +88,7 @@ def_module_params(module_name='smt',
('arith.nl.propagate_linear_monomials', BOOL, True, 'propagate linear monomials'), ('arith.nl.propagate_linear_monomials', BOOL, True, 'propagate linear monomials'),
('arith.nl.optimize_bounds', BOOL, True, 'enable bounds optimization'), ('arith.nl.optimize_bounds', BOOL, True, 'enable bounds optimization'),
('arith.nl.cross_nested', BOOL, True, 'enable cross-nested consistency checking'), ('arith.nl.cross_nested', BOOL, True, 'enable cross-nested consistency checking'),
('arith.nl.stellensatz', BOOL, False, 'enable stellensatz saturation'),
('arith.nl.log', BOOL, False, 'Log lemmas sent to nra solver'), ('arith.nl.log', BOOL, False, 'Log lemmas sent to nra solver'),
('arith.propagate_eqs', BOOL, True, 'propagate (cheap) equalities'), ('arith.propagate_eqs', BOOL, True, 'propagate (cheap) equalities'),
('arith.epsilon', DOUBLE, 1.0, 'initial value of epsilon used for model generation of infinitesimals'), ('arith.epsilon', DOUBLE, 1.0, 'initial value of epsilon used for model generation of infinitesimals'),

View file

@ -3467,15 +3467,15 @@ public:
void set_conflict_or_lemma(literal_vector const& core, bool is_conflict) { void set_conflict_or_lemma(literal_vector const& core, bool is_conflict) {
reset_evidence(); reset_evidence();
for (literal lit : core) { for (literal lit : core)
m_core.push_back(lit); m_core.push_back(lit);
}
// lp().shrink_explanation_to_minimum(m_explanation); // todo, enable when perf is fixed // lp().shrink_explanation_to_minimum(m_explanation); // todo, enable when perf is fixed
++m_num_conflicts; ++m_num_conflicts;
++m_stats.m_conflicts; ++m_stats.m_conflicts;
for (auto ev : m_explanation) for (auto ev : m_explanation)
set_evidence(ev.ci(), m_core, m_eqs); set_evidence(ev.ci(), m_core, m_eqs);
if (m_eqs.empty() && all_of(m_core, [&](literal l) { return ctx().get_assignment(l) == l_false; })) if (all_of(m_core, [&](literal l) { return ctx().get_assignment(l) == l_false; }))
is_conflict = true; is_conflict = true;
TRACE(arith_conflict, TRACE(arith_conflict,
tout << "@" << ctx().get_scope_level() << (is_conflict ? " conflict":" lemma"); tout << "@" << ctx().get_scope_level() << (is_conflict ? " conflict":" lemma");