3
0
Fork 0
mirror of https://github.com/Z3Prover/z3 synced 2026-01-19 00:38:57 +00:00

refine stellensatz solver

Signed-off-by: Nikolaj Bjorner <nbjorner@microsoft.com>
This commit is contained in:
Nikolaj Bjorner 2026-01-06 16:23:15 -08:00
parent f1b21ecbcc
commit 30f859a1f6
5 changed files with 111 additions and 13 deletions

View file

@ -125,10 +125,13 @@ namespace nla {
++c().lp_settings().stats().m_stellensatz.m_num_backtracks;
lbool rb = m_solver.solve(m_core);
TRACE(arith, tout << "backtrack search " << rb << ": " << m_core << "\n");
// verbose_stream() << rb << " " << num_conflicts << " " << m_config.max_conflicts << "\n";
if (rb == l_false)
continue;
if (rb == l_undef)
return rb;
if (rb == l_undef) {
//verbose_stream() << "undetermined solve\n";
// return rb;
}
m_solver.update_values(m_values);
goto start_saturate;
}
@ -137,6 +140,10 @@ namespace nla {
}
if (r == l_true && !set_model())
r = l_undef;
if (r == l_undef) {
IF_VERBOSE(2, display(verbose_stream()));
}
// verbose_stream() << "result " << r << "\n";
return r;
}
@ -154,6 +161,7 @@ namespace nla {
m_coi.init();
m_values.reset();
init_vars();
simplify();
init_occurs();
init_bounds();
}
@ -444,6 +452,8 @@ namespace nla {
init_occurs(new_ci);
// display_constraint(verbose_stream(), new_ci) << "\n";
return new_ci;
}
@ -1019,12 +1029,16 @@ namespace nla {
m_var2level.reset();
for (unsigned v = 0; v < m_values.size(); ++v)
m_level2var.push_back(v);
init_levels();
}
void stellensatz::init_levels() {
random_gen rand(c().random());
shuffle(m_level2var.size(), m_level2var.begin(), rand);
m_var2level.resize(m_values.size(), m_level2var.size());
for (unsigned i = 0; i < m_level2var.size(); ++i)
m_var2level[m_level2var[i]] = i;
m_var2level[m_level2var[i]] = i;
for (auto const &c : m_constraints)
if (constraint_is_false(c))
for (auto v : c.p.free_vars())
@ -1251,6 +1265,7 @@ namespace nla {
lp::lconstraint_kind k;
lpvar w;
init_levels();
unsigned num_fixed = 0;
unsigned num_levels = m_level2var.size();
unsigned num_rounds = 0;
@ -1273,6 +1288,8 @@ namespace nla {
if (m_tabu.contains(conflict)) // already attempted to repair this constraint without success
continue;
//verbose_stream() << "repair v" << w << " @ " << level
// << "\n ";
m_tabu.insert(conflict);
auto p = m_constraints[conflict].p;
@ -1298,7 +1315,7 @@ namespace nla {
//
}
if (find_split(w, value, k)) {
if (m_num_scopes < 4 && find_split(w, value, k)) {
SASSERT(k == lp::lconstraint_kind::LE || k == lp::lconstraint_kind::GE);
bool is_upper = k == lp::lconstraint_kind::LE;
SASSERT(!is_upper || !has_lo(w) || lo_val(w) <= value);
@ -1316,6 +1333,7 @@ namespace nla {
SASSERT(well_formed_var(w));
SASSERT(well_formed_last_bound());
++c().lp_settings().stats().m_stellensatz.m_num_decisions;
// verbose_stream() << "split " << m_num_scopes << "\n";
return true;
}
@ -1613,8 +1631,11 @@ namespace nla {
std::unordered_map<lpvar, rational> _values;
lra_solver->get_model(_values);
unsigned sz = values.size();
for (unsigned i = 0; i < sz; ++i)
for (unsigned i = 0; i < sz; ++i) {
values[i] = _values[i];
if (s.var_is_int(i) && !values[i].is_int())
values[i] = ceil(values[i]);
}
TRACE(arith, for (unsigned i = 0; i < sz; ++i) tout << "j" << i << " := " << values[i] << "\n";);
}
@ -1658,7 +1679,8 @@ namespace nla {
}
else {
auto const& b = m_bounds[m_lower[v]];
auto res = resolve_variable(v, b.m_constraint_justification, sup);
auto bci = b.m_constraint_justification;
auto res = resolve_variable(v, bci, sup);
TRACE(arith, display_constraint(tout << "resolved against implied lower bound ", res) << "\n");
if (constraint_is_false(res))
return res;
@ -1872,16 +1894,20 @@ namespace nla {
}
}
}
if (has_lo(v) && m_bounds[m_lower[v]].m_constraint_justification != lp::null_ci) {
if (inf == lp::null_ci || lo < lo_val(v)) {
if (has_lo(v)) {
auto lc = lo_constraint(v);
if (lc != lp::null_ci && !m_tabu.contains(lc) && (inf == lp::null_ci || lo < lo_val(v))) {
lo = lo_val(v);
inf = m_bounds[m_lower[v]].m_constraint_justification;
inf = lc;
m_tabu.insert(lc);
}
}
if (has_hi(v) && m_bounds[m_upper[v]].m_constraint_justification != lp::null_ci) {
if (sup == lp::null_ci || hi > hi_val(v)) {
if (has_hi(v)) {
auto hc = hi_constraint(v);
if (hc != lp::null_ci && !m_tabu.contains(hc) && (sup == lp::null_ci || hi > hi_val(v))) {
hi = hi_val(v);
sup = m_bounds[m_upper[v]].m_constraint_justification;
sup = hc;
m_tabu.insert(hc);
}
}
return result;
@ -2127,4 +2153,70 @@ namespace nla {
}
return out;
}
//
// collect variables that are defined x + p >= 0: d1, -x - p >= 0 : d2.
// substitute x by p elsewhere.
// Accumulate dependencies from d1, d2 into substituted (can be a propagation justification)
//
void stellensatz::simplify() {
using var_ci_vector = svector<std::pair<unsigned, lp::constraint_index>>;
struct eq_info {
lpvar v;
dd::pdd eq;
lp::constraint_index ci1, ci2;
};
u_map<var_ci_vector> bounds;
vector<dd::pdd> pdds;
vector<eq_info> eqs;
for (unsigned ci = 0; ci < m_constraints.size(); ++ci) {
auto const& [p, k, j] = m_constraints[ci];
bool is_le = true;
if (k != lp::lconstraint_kind::GE)
continue;
for (auto const &[coeff, vars] : p) {
if (vars.size() != 1)
continue;
if (coeff != 1 && coeff != -1)
continue;
auto v = vars[0];
auto &qs = bounds.insert_if_not_there(v, var_ci_vector());
auto mp = -p;
for (auto [q, ci2] : qs) {
auto Q = pdds[q];
if (mp != Q)
continue;
eqs.push_back({v, coeff == 1 ? -p : p, ci, ci2});
}
qs.push_back({pdds.size(), ci});
pdds.push_back(p);
}
}
uint_set tracked;
for (auto [v, p, ci1, ci2] : eqs) {
if (tracked.contains(ci1) || tracked.contains(ci2))
continue;
auto q = p + pddm.mk_var(v);
if (q.free_vars().contains(v))
continue;
tracked.insert(ci1);
tracked.insert(ci2);
unsigned sz = m_constraints.size();
for (unsigned ci = 0; ci < sz; ++ci) {
auto const &[p, k, j] = m_constraints[ci];
if (!p.free_vars().contains(v))
continue;
auto r = p.subst_pdd(v, q);
if (r.is_val())
continue;
svector<lp::constraint_index> assumptions;
assumptions.push_back(ci1);
assumptions.push_back(ci2);
assumptions.push_back(ci);
propagation_justification prop{assumptions};
add_constraint(r, k, prop);
}
}
}
}

View file

@ -219,6 +219,7 @@ namespace nla {
lbool search();
lbool resolve_conflict();
void init_search();
void init_levels();
void pop_bound();
void mark_dependencies(u_dependency *d);
bool should_propagate() const { return m_prop_qhead < m_bounds.size(); }
@ -322,6 +323,7 @@ namespace nla {
// initialization
void init_solver();
void init_vars();
void simplify();
void init_occurs();
void init_occurs(lp::constraint_index ci);
void init_bounds();

View file

@ -770,6 +770,8 @@ struct solver::imp {
void set_value(lp::lpvar v, rational const& value) {
if (!m_nlsat)
reset();
if (!m_values)
m_values = alloc(scoped_anum_vector, am());
scoped_anum a(am());

View file

@ -1917,6 +1917,7 @@ namespace nlsat {
break;
init_search();
do_simplify();
IF_VERBOSE(2, verbose_stream() << "(nlsat-b&b :conflicts " << m_stats.m_conflicts
<< " :decisions " << m_stats.m_decisions
<< " :propagations " << m_stats.m_propagations
@ -1924,6 +1925,7 @@ namespace nlsat {
<< " :learned " << m_learned.size() << ")\n");
TRACE(nlsat, display(tout << "branch and bound\n"));
// todo, review, test
// cut on the first model value
if (!m_model_values.empty() && m_stats.m_restarts % 10 == 0) {

View file

@ -90,7 +90,7 @@ def_module_params(module_name='smt',
('arith.nl.optimize_bounds', BOOL, True, 'enable bounds optimization'),
('arith.nl.cross_nested', BOOL, True, 'enable cross-nested consistency checking'),
('arith.nl.stellensatz', BOOL, False, 'enable stellensatz saturation'),
('arith.nl.stellensatz.max_conflicts', UINT, UINT_MAX, 'limit for stellensatz saturation'),
('arith.nl.stellensatz.max_conflicts', UINT, 30, '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'),