3
0
Fork 0
mirror of https://github.com/Z3Prover/z3 synced 2025-11-29 08:49:51 +00:00

statistics and configuable

Signed-off-by: Nikolaj Bjorner <nbjorner@microsoft.com>
This commit is contained in:
Nikolaj Bjorner 2025-11-22 16:48:15 -08:00
parent e44994b9a4
commit 151be6ac9a
7 changed files with 174 additions and 34 deletions

View file

@ -57,6 +57,7 @@ core::core(lp::lar_solver& s, params_ref const& p, reslimit & lim) :
void core::updt_params(params_ref const& p) { void core::updt_params(params_ref const& p) {
m_grobner.updt_params(p); m_grobner.updt_params(p);
m_stellensatz.updt_params(p);
} }
bool core::compare_holds(const rational& ls, llc cmp, const rational& rs) const { bool core::compare_holds(const rational& ls, llc cmp, const rational& rs) const {

View file

@ -62,6 +62,8 @@ class core {
friend class monomial_bounds; friend class monomial_bounds;
friend class nra::solver; friend class nra::solver;
friend class divisions; friend class divisions;
friend class stellensatz;
unsigned m_nlsat_delay = 0; unsigned m_nlsat_delay = 0;
unsigned m_nlsat_delay_bound = 0; unsigned m_nlsat_delay_bound = 0;

View file

@ -1,11 +1,10 @@
/*++ /*++
Copyright (c) 2025 Microsoft Corporation Copyright (c) 2025 Microsoft Corporation
--*/
*/
#include "util/heap.h" #include "util/heap.h"
#include "params/smt_params_helper.hpp"
#include "math/dd/pdd_eval.h" #include "math/dd/pdd_eval.h"
#include "math/lp/nla_core.h" #include "math/lp/nla_core.h"
#include "math/lp/nla_coi.h" #include "math/lp/nla_coi.h"
@ -89,42 +88,48 @@ namespace nla {
common(core), common(core),
m_solver(*this), m_solver(*this),
m_coi(*core), m_coi(*core),
pddm(core->lra_solver().number_of_vars()) pddm(core->lra_solver().number_of_vars()) {
{} }
lbool stellensatz::saturate() { lbool stellensatz::saturate() {
unsigned num_constraints = 0; unsigned num_constraints = 0, num_conflicts = 0;
init_solver(); init_solver();
TRACE(arith, display(tout << "stellensatz before saturation\n")); TRACE(arith, display(tout << "stellensatz before saturation\n"));
start_saturate: start_saturate:
num_constraints = m_constraints.size(); num_constraints = m_constraints.size();
lbool r; lbool r;
#if 1 if (m_config.strategy == 0)
r = conflict_saturation(); r = conflict_saturation();
#else else
r = model_repair(); r = model_repair();
#endif
if (m_constraints.size() == num_constraints) if (m_constraints.size() == num_constraints)
return l_undef; return l_undef;
if (r != l_false) if (r == l_undef)
r = m_solver.solve(m_core); r = m_solver.solve(m_core);
TRACE(arith, display(tout << "stellensatz after saturation " << r << "\n")); TRACE(arith, display(tout << "stellensatz after saturation " << r << "\n"));
if (r == l_false) { if (r == l_false) {
IF_VERBOSE(2, verbose_stream() << "(nla.stellensatz.backtrack)\n"); ++num_conflicts;
IF_VERBOSE(2, verbose_stream() << "(nla.stellensatz :conflicts " << num_conflicts << ":constraints " << m_constraints.size() << ")\n");
if (num_conflicts >= m_config.max_conflicts)
return l_undef;
while (backtrack(m_core)) { while (backtrack(m_core)) {
++c().lp_settings().stats().m_stellensatz.m_num_backtracks; ++c().lp_settings().stats().m_stellensatz.m_num_backtracks;
lbool rb = m_solver.solve(m_core); lbool rb = m_solver.solve(m_core);
if (rb == l_false) if (rb == l_false)
continue; continue;
if (rb == l_undef) // TODO: if there is a core that doesn't depend on new monomials we could use this for conflicts if (rb == l_undef) {
return rb; if (core_is_linear(m_core))
break;
return rb;
}
m_solver.update_values(m_values); m_solver.update_values(m_values);
goto start_saturate; goto start_saturate;
} }
conflict(m_core); conflict(m_core);
} }
if (r == l_true) if (r == l_true && !set_model())
r = l_undef; r = l_undef;
return r; return r;
} }
@ -174,6 +179,39 @@ namespace nla {
m_occurs.reserve(sz); m_occurs.reserve(sz);
} }
// set the model based on m_values computed by the solver
bool stellensatz::set_model() {
if (any_of(m_constraints, [&](auto const &c) { return !constraint_is_true(c); }))
return false;
auto &src = c().lra_solver();
c().m_use_nra_model = true;
m_values.reserve(src.number_of_vars());
for (unsigned v = 0; v < src.number_of_vars(); ++v) {
if (c().is_monic_var(v)) {
auto& mon = c().emons()[v];
rational val(1);
for (auto w : mon.vars())
val *= m_values[w];
m_values[v] = val;
}
else if (src.column_has_term(v)) {
auto const& t = src.get_term(v);
rational val(0);
for (auto cv : t)
val += m_values[cv.j()] * cv.coeff();
m_values[v] = val;
}
else if (m_coi.vars().contains(v)) {
// variable is in coi, m_values[v] is set.
}
else {
m_values[v] = c().val(v);
}
c().m_nra.set_value(v, m_values[v]);
}
return true;
}
dd::pdd stellensatz::to_pdd(lpvar v) { dd::pdd stellensatz::to_pdd(lpvar v) {
if (m_coi.mons().contains(v)) { if (m_coi.mons().contains(v)) {
auto& mon = c().emons()[v]; auto& mon = c().emons()[v];
@ -279,6 +317,8 @@ namespace nla {
add_active(ci, uint_set()); add_active(ci, uint_set());
} }
while (!m_active.empty() && c().reslim().inc()) { while (!m_active.empty() && c().reslim().inc()) {
if (m_constraints.size() >= m_config.max_constraints)
break;
// factor ci into x*p + q <= rhs, where degree(x, q) < degree(x, p) + 1 // factor ci into x*p + q <= rhs, where degree(x, q) < degree(x, p) + 1
// if p is vanishing (evaluates to 0), then add p = 0 as assumption and reduce to q. // if p is vanishing (evaluates to 0), then add p = 0 as assumption and reduce to q.
// otherwise // otherwise
@ -288,6 +328,7 @@ namespace nla {
m_active.remove(ci); m_active.remove(ci);
if (constraint_is_true(ci)) if (constraint_is_true(ci))
continue; continue;
TRACE(arith, display_constraint(tout << "process (" << ci << ") ", ci) << " " << m_tabu[ci] << "\n"); TRACE(arith, display_constraint(tout << "process (" << ci << ") ", ci) << " " << m_tabu[ci] << "\n");
@ -345,6 +386,7 @@ namespace nla {
if (g.degree != 1) if (g.degree != 1)
return true; // not handling degree > 1 return true; // not handling degree > 1
auto p_value2 = value(g.p); auto p_value2 = value(g.p);
//
// check that p_value and p_value2 have different signs // check that p_value and p_value2 have different signs
// check that other_p is disjoint from tabu // check that other_p is disjoint from tabu
// compute overlaps extending x // compute overlaps extending x
@ -413,7 +455,7 @@ namespace nla {
++c().lp_settings().stats().m_stellensatz.m_num_resolutions; ++c().lp_settings().stats().m_stellensatz.m_num_resolutions;
if (!is_new_constraint(new_ci)) if (!is_new_constraint(new_ci))
return true; return true;
if (m_constraints[new_ci].p.degree() <= 3) if (m_constraints[new_ci].p.degree() <= m_config.max_degree)
init_occurs(new_ci); init_occurs(new_ci);
TRACE(arith, tout << "eliminate j" << x << ":\n"; TRACE(arith, tout << "eliminate j" << x << ":\n";
display_constraint(tout << "ci: ", ci) << "\n"; display_constraint(tout << "ci: ", ci) << "\n";
@ -464,14 +506,13 @@ namespace nla {
for (auto v : vars) for (auto v : vars)
if (!model_repair(v)) if (!model_repair(v))
return l_false; return l_false;
#if 0
for (unsigned i = vars.size(); i-- > 0;) for (unsigned i = vars.size(); i-- > 0;)
set_value(vars[i]); set_value(vars[i]);
#endif return all_of(m_constraints, [&](constraint const& c) { return constraint_is_true(c); }) ? l_true : l_undef;
return l_undef;
} }
bool stellensatz::model_repair(lp::lpvar v) { bool stellensatz::model_repair(lp::lpvar v) {
m_sup_inf.reserve(v + 1);
auto bounds = find_bounds(v); auto bounds = find_bounds(v);
auto const &[lo, inf, infs] = bounds.first; auto const &[lo, inf, infs] = bounds.first;
auto const &[hi, sup, sups] = bounds.second; auto const &[hi, sup, sups] = bounds.second;
@ -480,6 +521,7 @@ namespace nla {
TRACE(arith, tout << "j" << v << " " << infs.size() << " " << sups.size() << "\n"); TRACE(arith, tout << "j" << v << " " << infs.size() << " " << sups.size() << "\n");
if (!has_false) if (!has_false)
return true; return true;
m_sup_inf[v] = {inf, sup};
SASSERT(!infs.empty() || !sups.empty()); SASSERT(!infs.empty() || !sups.empty());
if (infs.empty()) { if (infs.empty()) {
SASSERT(!sups.empty()); SASSERT(!sups.empty());
@ -540,6 +582,39 @@ namespace nla {
return true; return true;
} }
void stellensatz::set_value(lp::lpvar v) {
auto [inf, sup] = m_sup_inf[v];
if (inf == lp::null_ci && sup == lp::null_ci)
return;
else if (inf != lp::null_ci) {
auto f = factor(v, inf);
SASSERT(f.degree == 1);
auto quot = -value(f.q) / value(f.p);
bool is_strict = m_constraints[inf].k == lp::lconstraint_kind::GT;
if (is_strict) {
if (sup != lp::null_ci) {
auto g = factor(v, sup);
SASSERT(g.degree == 1);
auto quot2 = -value(g.q) / value(g.p);
quot = (quot + quot2) / rational(2);
}
else {
quot += rational(1);
}
}
m_values[v] = is_int(v) ? ceil(quot) : quot;
}
else if (sup != lp::null_ci) {
auto f = factor(v, sup);
SASSERT(f.degree == 1);
auto quot = -value(f.q) / value(f.p);
bool is_strict = m_constraints[sup].k == lp::lconstraint_kind::GT;
if (is_strict)
quot -= rational(1);
m_values[v] = is_int(v) ? floor(quot) : quot;
}
}
// lo <= hi // lo <= hi
bool stellensatz::assume_ge(lpvar v, lp::constraint_index lo, lp::constraint_index hi) { bool stellensatz::assume_ge(lpvar v, lp::constraint_index lo, lp::constraint_index hi) {
if (lo == hi) if (lo == hi)
@ -583,21 +658,24 @@ namespace nla {
if (!m_active.contains(ci)) if (!m_active.contains(ci))
continue; continue;
auto f = factor(v, ci); auto f = factor(v, ci);
auto p_value = value(f.p); if (vanishing(v, f, ci))
auto q_value = value(f.q);
if (p_value == 0) // it is vanishing
continue; continue;
if (f.degree > 1) if (f.degree > 1)
continue; continue;
SASSERT(f.degree == 1); SASSERT(f.degree == 1);
auto p_value = value(f.p);
auto q_value = value(f.q);
auto quot = -q_value / p_value; auto quot = -q_value / p_value;
// TODO: ignore strict inequalities for now.
if (p_value < 0) { if (p_value < 0) {
// p*x + q >= 0 => x <= -q / p if p < 0 // p*x + q >= 0 => x <= -q / p if p < 0
if (sups.empty() || hi > quot) { if (sups.empty() || hi > quot) {
hi = quot; hi = quot;
sup = ci; sup = ci;
} }
else if (hi == quot && m_constraints[ci].k == lp::lconstraint_kind::GT) {
sup = ci;
}
sups.push_back(ci); sups.push_back(ci);
} }
else { else {
@ -606,6 +684,9 @@ namespace nla {
lo = quot; lo = quot;
inf = ci; inf = ci;
} }
else if (lo == quot && m_constraints[ci].k == lp::lconstraint_kind::GT) {
inf = ci;
}
infs.push_back(ci); infs.push_back(ci);
} }
} }
@ -628,12 +709,12 @@ namespace nla {
p_is_0 = multiply(p_is_0, r); p_is_0 = multiply(p_is_0, r);
auto new_ci = add(ci, p_is_0); auto new_ci = add(ci, p_is_0);
TRACE(arith, display_constraint(tout << "reduced", new_ci) << "\n"); TRACE(arith, display_constraint(tout << "reduced", new_ci) << "\n");
if (!is_new_constraint(new_ci)) if (is_new_constraint(new_ci)) {
return false; init_occurs(new_ci);
init_occurs(new_ci); uint_set new_tabu(m_tabu[ci]);
uint_set new_tabu(m_tabu[ci]); new_tabu.insert(x);
new_tabu.insert(x); add_active(new_ci, new_tabu);
add_active(new_ci, new_tabu); }
++c().lp_settings().stats().m_stellensatz.m_num_vanishings; ++c().lp_settings().stats().m_stellensatz.m_num_vanishings;
return true; return true;
} }
@ -676,7 +757,7 @@ namespace nla {
} }
bool stellensatz::is_new_constraint(lp::constraint_index ci) const { bool stellensatz::is_new_constraint(lp::constraint_index ci) const {
return ci == m_constraints.size() - 1; return ci + 1 == m_constraints.size();
} }
lp::lpvar stellensatz::select_variable_to_eliminate(lp::constraint_index ci) { lp::lpvar stellensatz::select_variable_to_eliminate(lp::constraint_index ci) {
@ -749,6 +830,14 @@ namespace nla {
return true; return true;
} }
bool stellensatz::core_is_linear(svector<lp::constraint_index> const &core) {
m_constraints_in_conflict.reset();
svector<lp::constraint_index> external, assumptions;
for (auto ci : core)
explain_constraint(ci, external, assumptions);
return all_of(assumptions, [&](auto ci) { return has_term_offset(m_constraints[ci].p); });
}
void stellensatz::explain_constraint(lp::constraint_index ci, svector<lp::constraint_index> &external, void stellensatz::explain_constraint(lp::constraint_index ci, svector<lp::constraint_index> &external,
svector<lp::constraint_index> &assumptions) { svector<lp::constraint_index> &assumptions) {
if (m_constraints_in_conflict.contains(ci)) if (m_constraints_in_conflict.contains(ci))
@ -874,7 +963,7 @@ namespace nla {
return add_constraint(p, k, eq_justification{left, right}); return add_constraint(p, k, eq_justification{left, right});
} }
u_dependency *d = nullptr; u_dependency *d = nullptr;
auto has_bound = [&](rational a, lpvar x, rational b) { auto has_bound = [&](rational const& a, lpvar x, rational const& b) {
rational bound; rational bound;
bool is_strict = false; bool is_strict = false;
if (a == 1 && k == lp::lconstraint_kind::GE && c().lra_solver().has_lower_bound(x, d, bound, is_strict) && if (a == 1 && k == lp::lconstraint_kind::GE && c().lra_solver().has_lower_bound(x, d, bound, is_strict) &&
@ -989,7 +1078,11 @@ namespace nla {
} }
bool stellensatz::constraint_is_true(lp::constraint_index ci) const { bool stellensatz::constraint_is_true(lp::constraint_index ci) const {
auto const& [p, k, j] = m_constraints[ci]; return constraint_is_true(m_constraints[ci]);
}
bool stellensatz::constraint_is_true(constraint const &c) const {
auto const& [p, k, j] = c;
auto lhs = value(p); auto lhs = value(p);
switch (k) { switch (k) {
case lp::lconstraint_kind::GT: return lhs > 0; case lp::lconstraint_kind::GT: return lhs > 0;
@ -1179,6 +1272,14 @@ namespace nla {
return out; return out;
} }
void stellensatz::updt_params(params_ref const& p) {
smt_params_helper sp(p);
m_config.max_degree = sp.arith_nl_stellensatz_max_degree();
m_config.max_conflicts = sp.arith_nl_stellensatz_max_conflicts();
m_config.max_constraints = sp.arith_nl_stellensatz_max_constraints();
m_config.strategy = sp.arith_nl_stellensatz_strategy();
}
// Solver component // Solver component
// check LRA feasibilty // check LRA feasibilty

View file

@ -124,11 +124,19 @@ namespace nla {
dd::pdd p, q; dd::pdd p, q;
}; };
struct config {
unsigned max_degree = 3;
unsigned max_conflicts = UINT_MAX;
unsigned max_constraints = UINT_MAX;
unsigned strategy = 0;
};
trail_stack m_trail; trail_stack m_trail;
coi m_coi; coi m_coi;
dd::pdd_manager pddm; dd::pdd_manager pddm;
config m_config;
vector<constraint> m_constraints; vector<constraint> m_constraints;
monomial_factory m_monomial_factory; monomial_factory m_monomial_factory;
indexed_uint_set m_active; indexed_uint_set m_active;
@ -186,6 +194,7 @@ namespace nla {
bool resolve_variable(lpvar x, lp::constraint_index ci, lp::constraint_index other_ci, rational const& p_value, bool resolve_variable(lpvar x, lp::constraint_index ci, lp::constraint_index other_ci, rational const& p_value,
factorization const& f, unsigned_vector const& m1, dd::pdd _f_p); factorization const& f, unsigned_vector const& m1, dd::pdd _f_p);
svector<std::pair<lp::constraint_index, lp::constraint_index>> m_sup_inf;
lbool model_repair(); lbool model_repair();
bool model_repair(lp::lpvar v); bool model_repair(lp::lpvar v);
struct bound_info { struct bound_info {
@ -197,6 +206,7 @@ namespace nla {
bool assume_ge(lpvar v, lp::constraint_index lo, lp::constraint_index hi); bool assume_ge(lpvar v, lp::constraint_index lo, lp::constraint_index hi);
bool constraint_is_true(lp::constraint_index ci) const; bool constraint_is_true(lp::constraint_index ci) const;
bool constraint_is_true(constraint const &c) const;
bool is_new_constraint(lp::constraint_index ci) const; bool is_new_constraint(lp::constraint_index ci) const;
lp::constraint_index gcd_normalize(lp::constraint_index ci); lp::constraint_index gcd_normalize(lp::constraint_index ci);
@ -211,6 +221,8 @@ namespace nla {
bool is_int(dd::pdd const &p) const; bool is_int(dd::pdd const &p) const;
rational value(dd::pdd const& p) const; rational value(dd::pdd const& p) const;
rational value(lp::lpvar v) const { return m_values[v]; } rational value(lp::lpvar v) const { return m_values[v]; }
bool set_model();
void set_value(lp::lpvar v);
void add_active(lp::constraint_index ci, uint_set const &tabu); void add_active(lp::constraint_index ci, uint_set const &tabu);
@ -220,6 +232,7 @@ namespace nla {
void explain_constraint(lp::constraint_index ci, svector<lp::constraint_index> &external, void explain_constraint(lp::constraint_index ci, svector<lp::constraint_index> &external,
svector<lp::constraint_index> &assumptions); svector<lp::constraint_index> &assumptions);
bool backtrack(svector<lp::constraint_index> const& core); bool backtrack(svector<lp::constraint_index> const& core);
bool core_is_linear(svector<lp::constraint_index> const &core);
struct pp_j { struct pp_j {
stellensatz const &s; stellensatz const &s;
@ -244,6 +257,8 @@ namespace nla {
public: public:
stellensatz(core* core); stellensatz(core* core);
lbool saturate(); lbool saturate();
void updt_params(params_ref const &p);
}; };
} }

View file

@ -768,6 +768,17 @@ struct solver::imp {
} }
} }
void set_value(lp::lpvar v, rational const& value) {
if (!m_values)
m_values = alloc(scoped_anum_vector, am());
scoped_anum a(am());
am().set(a, value.to_mpq());
while (m_values->size() <= v)
m_values->push_back(a);
am().set((*m_values)[v], a);
}
nlsat::anum_manager& am() { nlsat::anum_manager& am() {
return m_nlsat->am(); return m_nlsat->am();
} }
@ -848,4 +859,8 @@ void solver::updt_params(params_ref& p) {
m_imp->updt_params(p); m_imp->updt_params(p);
} }
void solver::set_value(lp::lpvar v, rational const& value) {
m_imp->set_value(v, value);
}
} }

View file

@ -59,6 +59,8 @@ namespace nra {
nlsat::anum_manager& am(); nlsat::anum_manager& am();
void set_value(lp::lpvar v, rational const &value);
scoped_anum& tmp1(); scoped_anum& tmp1();
scoped_anum& tmp2(); scoped_anum& tmp2();

View file

@ -90,6 +90,10 @@ def_module_params(module_name='smt',
('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.stellensatz', BOOL, False, 'enable stellensatz saturation'),
('arith.nl.stellensatz.max_conflicts', UINT, UINT_MAX, 'limit for stellensatz saturation'),
('arith.nl.stellensatz.max_constraints', UINT, UINT_MAX, 'max number of constraints for stellensatz saturation'),
('arith.nl.stellensatz.max_degree', UINT, 3, 'max degree for stellensatz saturation'),
('arith.nl.stellensatz.strategy', UINT, 0, '0 - conflict saturation, 1 - model repair'),
('arith.nl.linearize', BOOL, True, 'enable NL linearization'), ('arith.nl.linearize', BOOL, True, 'enable NL linearization'),
('arith.nl.nra.poly', BOOL, False, 'use polynomial translation'), ('arith.nl.nra.poly', BOOL, False, 'use polynomial translation'),
('arith.nl.nra.model_bounds', BOOL, False, 'use model-based integer bounds for nra solver'), ('arith.nl.nra.model_bounds', BOOL, False, 'use model-based integer bounds for nra solver'),