3
0
Fork 0
mirror of https://github.com/Z3Prover/z3 synced 2025-12-04 19:16:46 +00:00

stellensatz fixes

Signed-off-by: Nikolaj Bjorner <nbjorner@microsoft.com>
This commit is contained in:
Nikolaj Bjorner 2025-11-22 10:15:14 -08:00
parent ee63585581
commit e44994b9a4
3 changed files with 101 additions and 48 deletions

View file

@ -138,6 +138,16 @@ struct statistics {
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; unsigned m_nla_stellensatz = 0;
struct stellensatz {
unsigned m_num_constraints = 0;
unsigned m_num_conflicts = 0;
unsigned m_num_factorings = 0;
unsigned m_num_resolutions = 0;
unsigned m_num_vanishings = 0;
unsigned m_num_model_repairs = 0;
unsigned m_num_backtracks = 0;
};
stellensatz m_stellensatz;
::statistics m_st = {}; ::statistics m_st = {};
@ -178,6 +188,13 @@ struct statistics {
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.update("arith-nla-stellensatz", m_nla_stellensatz);
st.update("arith-nla-stellensatz-conflicts", m_stellensatz.m_num_conflicts);
st.update("arith-nla-stellensatz-vanishing", m_stellensatz.m_num_vanishings);
st.update("arith-nla-stellensatz-factorings", m_stellensatz.m_num_factorings);
st.update("arith-nla-stellensatz-resolutions", m_stellensatz.m_num_resolutions);
st.update("arith-nla-stellensatz-model-repairs", m_stellensatz.m_num_model_repairs);
st.update("arith-nla-stellensatz-backtracks", m_stellensatz.m_num_backtracks);
st.update("arith-nla-stellensatz-constraints", m_stellensatz.m_num_constraints);
st.copy(m_st); st.copy(m_st);
} }
}; };

View file

@ -93,27 +93,32 @@ namespace nla {
{} {}
lbool stellensatz::saturate() { lbool stellensatz::saturate() {
unsigned num_constraints = 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();
lbool r; lbool r;
#if 1 #if 1
r = conflict_saturation(); r = conflict_saturation();
#else #else
r = model_repair(); r = model_repair();
#endif #endif
if (m_constraints.size() == num_constraints)
return l_undef;
if (r != l_false) if (r != l_false)
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");
while (backtrack(m_core)) { while (backtrack(m_core)) {
++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) // TODO: if there is a core that doesn't depend on new monomials we could use this for conflicts
return rb; return rb;
m_solver.update_values(m_values); m_solver.update_values(m_values);
goto start_saturate; goto start_saturate;
} }
@ -124,11 +129,16 @@ namespace nla {
return r; return r;
} }
void stellensatz::pop_constraint() {
auto const &[p, k, j] = m_constraints.back();
m_constraint_index.erase({p.index(), k});
m_constraints.pop_back();
}
void stellensatz::init_solver() { void stellensatz::init_solver() {
m_constraints.reset(); m_trail.reset();
m_monomial_factory.reset(); m_monomial_factory.reset();
m_coi.init(); m_coi.init();
m_constraint_index.reset();
m_values.reset(); m_values.reset();
init_vars(); init_vars();
init_occurs(); init_occurs();
@ -236,7 +246,20 @@ namespace nla {
ci = m_constraints.size(); ci = m_constraints.size();
m_constraints.push_back({p, k, j }); m_constraints.push_back({p, k, j });
m_constraint_index.insert({p.index(), k}, ci); m_constraint_index.insert({p.index(), k}, ci);
++c().lp_settings().stats().m_stellensatz.m_num_constraints;
m_tabu.reserve(ci + 1); m_tabu.reserve(ci + 1);
m_tabu[ci].reset();
m_has_occurs.reserve(ci + 1);
struct undo_constraint : public trail {
stellensatz &s;
undo_constraint(stellensatz& s): s(s) {}
void undo() override {
s.pop_constraint();
}
};
m_trail.push_scope();
m_trail.push(undo_constraint(*this));
return ci; return ci;
} }
@ -244,7 +267,6 @@ namespace nla {
if (m_active.contains(ci)) if (m_active.contains(ci))
return; return;
m_active.insert(ci); m_active.insert(ci);
m_tabu.reserve(ci + 1);
m_tabu[ci] = tabu; m_tabu[ci] = tabu;
} }
@ -252,7 +274,6 @@ namespace nla {
// loop over active set to eliminate variables. // loop over active set to eliminate variables.
lbool stellensatz::conflict_saturation() { lbool stellensatz::conflict_saturation() {
m_active.reset(); m_active.reset();
m_tabu.reset();
for (unsigned ci = 0; ci < m_constraints.size(); ++ci) { for (unsigned ci = 0; ci < m_constraints.size(); ++ci) {
if (!constraint_is_true(ci)) if (!constraint_is_true(ci))
add_active(ci, uint_set()); add_active(ci, uint_set());
@ -316,15 +337,19 @@ namespace nla {
dd::pdd _f_p) { dd::pdd _f_p) {
if (ci == other_ci) if (ci == other_ci)
return true; return true;
if (f.degree != 1)
return true; // future - could handle this
auto const &[other_p, other_k, other_j] = m_constraints[other_ci]; auto const &[other_p, other_k, other_j] = m_constraints[other_ci];
auto const &[p, k, j] = m_constraints[ci]; auto const &[p, k, j] = m_constraints[ci];
auto g = factor(x, other_ci); auto g = factor(x, other_ci);
if (g.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
// //
TRACE(arith, display_constraint(tout << "resolve with " << p_value << " " << p_value2 << " ", other_ci) TRACE(arith, display_constraint(tout << "(" << other_ci << ") resolve with x" << x << " " << p_value << " " << p_value2 << " ", other_ci)
<< "\n"); << "\n");
SASSERT(g.degree > 0); SASSERT(g.degree > 0);
SASSERT(p_value != 0); SASSERT(p_value != 0);
@ -352,13 +377,6 @@ namespace nla {
f_p = f_p.mul(m1); f_p = f_p.mul(m1);
g_p = g_p.mul(m2); g_p = g_p.mul(m2);
#if 0
if (!has_term_offset(f_p))
return true;
if (!has_term_offset(g_p))
return true;
#endif
TRACE(arith, tout << "m1 " << m1 << " m2 " << m2 << " m1m2: " << m1m2 << "\n"); TRACE(arith, tout << "m1 " << m1 << " m2 " << m2 << " m1m2: " << m1m2 << "\n");
auto sign_f = value(f_p) < 0; auto sign_f = value(f_p) < 0;
@ -392,6 +410,7 @@ namespace nla {
auto new_ci = add(ci_a, ci_b); auto new_ci = add(ci_a, ci_b);
CTRACE(arith, !is_new_constraint(new_ci), display_constraint(tout << "not new ", new_ci) << "\n"); CTRACE(arith, !is_new_constraint(new_ci), display_constraint(tout << "not new ", new_ci) << "\n");
++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() <= 3)
@ -444,7 +463,11 @@ namespace nla {
shuffle(vars.size(), vars.begin(), rand); shuffle(vars.size(), vars.begin(), rand);
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;)
set_value(vars[i]);
#endif
return l_undef; return l_undef;
} }
@ -477,31 +500,33 @@ namespace nla {
m_values[v] = is_int(v) ? ceil(lo) : lo; m_values[v] = is_int(v) ? ceil(lo) : lo;
return true; return true;
} }
// lo > hi - pick a side and assume inf or sup and enforce order between sup and inf.
// maybe just add constraints that are false and skip the rest? auto resolve = [&](unsigned inf, unsigned_vector const &sups) {
// remove sups and infs from active because we computed resolvents
if (infs.size() < sups.size()) {
auto f = factor(v, inf); auto f = factor(v, inf);
SASSERT(f.degree == 1); SASSERT(f.degree == 1);
auto p_value = value(f.p); auto p_value = value(f.p);
f.p *= pddm.mk_var(v); f.p *= pddm.mk_var(v);
auto [m, f_p] = f.p.var_factors(); auto [m, f_p] = f.p.var_factors();
for (auto s : sups) return all_of(sups, [&](auto s) { return resolve_variable(v, inf, s, p_value, f, m, f_p); });
if (!resolve_variable(v, inf, s, p_value, f, m, f_p)) };
return false; // lo > hi - pick a side and assume inf or sup and enforce order between sup and inf.
// maybe just add constraints that are false and skip the rest?
// remove sups and infs from active because we computed resolvents
if (infs.size() <= 3 && sups.size() <= 3 && (infs.size() <= 2 || sups.size() <= 2)) {
for (auto inf : infs)
if (!resolve(inf, sups))
return false;
}
else if (infs.size() < sups.size()) {
if (!resolve(inf, sups))
return false;
for (auto i : infs) for (auto i : infs)
if (!assume_ge(v, i, inf)) if (!assume_ge(v, i, inf))
return false; return false;
} }
else { else {
auto f = factor(v, sup); if (!resolve(sup, infs))
SASSERT(f.degree == 1); return false;
auto p_value = value(f.p);
f.p *= pddm.mk_var(v);
auto [m, f_p] = f.p.var_factors();
for (auto i : infs)
if (!resolve_variable(v, sup, i, p_value, f, m, f_p))
return false;
for (auto s : sups) for (auto s : sups)
if (!assume_ge(v, sup, s)) if (!assume_ge(v, sup, s))
return false; return false;
@ -609,6 +634,7 @@ namespace nla {
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;
return true; return true;
} }
@ -694,7 +720,7 @@ namespace nla {
for (auto ci : assumptions) for (auto ci : assumptions)
max_ci = std::max(ci, max_ci); max_ci = std::max(ci, max_ci);
TRACE(arith, tout << "max " << max_ci << " external " << external << " assumptions " << assumptions << "\n"; TRACE(arith, tout << "max " << max_ci << " external " << external << " assumptions " << assumptions << "\n";
display_constraint(tout, max_ci);); display_constraint(tout, max_ci) << "\n";);
// TODO, we can in reality replay all constraints that don't depend on max_ci // TODO, we can in reality replay all constraints that don't depend on max_ci
vector<constraint> replay; vector<constraint> replay;
unsigned i = 0; unsigned i = 0;
@ -706,13 +732,9 @@ namespace nla {
} }
external.shrink(i); external.shrink(i);
auto [p, k, j] = m_constraints[max_ci]; auto [p, k, j] = m_constraints[max_ci];
while (m_constraints.size() > max_ci) { while (m_constraints.size() > max_ci)
auto const& [_p, _k, _j] = m_constraints.back(); m_trail.pop_scope(1);
m_constraint_index.erase({_p.index(), _k});
auto ci = m_constraints.size() - 1;
remove_occurs(ci);
m_constraints.pop_back();
}
for (auto const &[_p, _k, _j] : replay) { for (auto const &[_p, _k, _j] : replay) {
auto ci = add_constraint(_p, _k, _j); auto ci = add_constraint(_p, _k, _j);
init_occurs(ci); init_occurs(ci);
@ -932,20 +954,28 @@ namespace nla {
void stellensatz::init_occurs(lp::constraint_index ci) { void stellensatz::init_occurs(lp::constraint_index ci) {
if (ci == lp::null_ci) if (ci == lp::null_ci)
return; return;
m_occurs_trail.push_back(ci); if (m_has_occurs[ci])
return;
struct undo_occurs : public trail {
stellensatz & s;
lp::constraint_index ci;
undo_occurs(stellensatz & s, lp::constraint_index ci) : s(s), ci(ci) {}
void undo() override {
s.remove_occurs(ci);
}
};
m_trail.push(undo_occurs(*this, ci));
m_has_occurs[ci] = true;
auto const &con = m_constraints[ci]; auto const &con = m_constraints[ci];
verbose_stream() << "init-occurs " << ci << " vars: " << con.p.free_vars() << "\n";
for (auto v : con.p.free_vars()) for (auto v : con.p.free_vars())
m_occurs[v].push_back(ci); m_occurs[v].push_back(ci);
} }
void stellensatz::remove_occurs(lp::constraint_index ci) { void stellensatz::remove_occurs(lp::constraint_index ci) {
if (m_occurs_trail.empty() || m_occurs_trail.back() != ci) SASSERT(m_has_occurs[ci]);
return; m_has_occurs[ci] = false;
m_occurs_trail.pop_back();
auto const &con = m_constraints[ci]; auto const &con = m_constraints[ci];
verbose_stream() << "remove-occurs " << ci << " vars: " << con.p.free_vars() << "\n";
for (auto v : con.p.free_vars()) for (auto v : con.p.free_vars())
m_occurs[v].pop_back(); m_occurs[v].pop_back();
} }
@ -1240,5 +1270,6 @@ namespace nla {
unsigned sz = values.size(); unsigned sz = values.size();
for (unsigned i = 0; i < sz; ++i) for (unsigned i = 0; i < sz; ++i)
values[i] = _values[i]; values[i] = _values[i];
TRACE(arith, for (unsigned i = 0; i < sz; ++i) tout << "j" << i << " := " << values[i] << "\n";);
} }
} }

View file

@ -125,6 +125,8 @@ namespace nla {
}; };
trail_stack m_trail;
coi m_coi; coi m_coi;
dd::pdd_manager pddm; dd::pdd_manager pddm;
vector<constraint> m_constraints; vector<constraint> m_constraints;
@ -132,7 +134,9 @@ namespace nla {
indexed_uint_set m_active; indexed_uint_set m_active;
vector<uint_set> m_tabu; vector<uint_set> m_tabu;
vector<rational> m_values; vector<rational> m_values;
svector<lp::constraint_index> m_core, m_occurs_trail; svector<lp::constraint_index> m_core;
vector<svector<lp::constraint_index>> m_occurs; // map from variable to constraints they occur.
bool_vector m_has_occurs;
struct constraint_key { struct constraint_key {
unsigned pdd; unsigned pdd;
@ -160,13 +164,14 @@ namespace nla {
lp::constraint_index add_var_bound(lp::lpvar v, lp::lconstraint_kind k, rational const &rhs, justification j); lp::constraint_index add_var_bound(lp::lpvar v, lp::lconstraint_kind k, rational const &rhs, justification j);
vector<svector<lp::constraint_index>> m_occurs; // map from variable to constraints they occur.
// initialization // initialization
void init_solver(); void init_solver();
void init_vars(); void init_vars();
void init_occurs(); void init_occurs();
void init_occurs(lp::constraint_index ci); void init_occurs(lp::constraint_index ci);
void pop_constraint();
void remove_occurs(lp::constraint_index ci); void remove_occurs(lp::constraint_index ci);
lbool conflict_saturation(); lbool conflict_saturation();