mirror of
https://github.com/Z3Prover/z3
synced 2026-02-08 10:07:59 +00:00
fix issue 8397
Signed-off-by: Lev Nachmanson <levnach@hotmail.com>
This commit is contained in:
parent
46eeddae52
commit
beacd43587
4 changed files with 199 additions and 5 deletions
|
|
@ -368,19 +368,20 @@ namespace nlsat {
|
|||
TRACE(nlsat_explain, tout << "lc does no vaninsh\n";);
|
||||
return;
|
||||
}
|
||||
add_zero_assumption(lc);
|
||||
if (k == 0) {
|
||||
// all coefficients of p vanished in the current interpretation,
|
||||
// and were added as assumptions.
|
||||
p = m_pm.mk_zero();
|
||||
TRACE(nlsat_explain, tout << "all coefficients of p vanished\n";);
|
||||
if (m_add_all_coeffs) {
|
||||
add_zero_assumption(lc);
|
||||
return;
|
||||
}
|
||||
TRACE(nlsat_explain, tout << "falling back to add-all-coeffs projection\n";);
|
||||
m_add_all_coeffs = true;
|
||||
throw add_all_coeffs_restart();
|
||||
}
|
||||
add_zero_assumption(lc);
|
||||
k--;
|
||||
p = reduct;
|
||||
}
|
||||
|
|
@ -740,8 +741,14 @@ namespace nlsat {
|
|||
s = S.get(i);
|
||||
TRACE(nlsat_explain, display(tout << "processing psc(" << i << ")\n", m_solver, s) << "\n";);
|
||||
if (is_zero(s)) {
|
||||
TRACE(nlsat_explain, tout << "skipping psc is the zero polynomial\n";);
|
||||
continue;
|
||||
// PSC is identically zero - polynomials share a common factor.
|
||||
// This can cause unsound lemmas. Fall back to add-all-coeffs projection.
|
||||
TRACE(nlsat_explain, tout << "psc is zero polynomial - polynomials share common factor\n";);
|
||||
if (m_add_all_coeffs)
|
||||
continue;
|
||||
TRACE(nlsat_explain, tout << "falling back to add-all-coeffs projection\n";);
|
||||
m_add_all_coeffs = true;
|
||||
throw add_all_coeffs_restart();
|
||||
}
|
||||
if (is_const(s)) {
|
||||
TRACE(nlsat_explain, tout << "done, psc is a constant\n";);
|
||||
|
|
|
|||
|
|
@ -25,4 +25,4 @@ def_module_params('nlsat',
|
|||
('lws', BOOL, True, "apply levelwise."),
|
||||
('lws_spt_threshold', UINT, 2, "minimum both-side polynomial count to apply spanning tree optimization; < 2 disables spanning tree"),
|
||||
('canonicalize', BOOL, True, "canonicalize polynomials.")
|
||||
))
|
||||
))
|
||||
|
|
|
|||
|
|
@ -228,7 +228,7 @@ namespace nlsat {
|
|||
unsigned m_max_conflicts;
|
||||
unsigned m_lemma_rlimit;
|
||||
unsigned m_lemma_count;
|
||||
unsigned m_variable_ordering_strategy;
|
||||
unsigned m_variable_ordering_strategy;
|
||||
bool m_set_0_more;
|
||||
|
||||
struct stats {
|
||||
|
|
|
|||
|
|
@ -467,6 +467,11 @@ static nlsat::literal mk_eq(nlsat::solver& s, nlsat::poly* p) {
|
|||
return s.mk_ineq_literal(nlsat::atom::EQ, 1, _p, is_even);
|
||||
}
|
||||
|
||||
static nlsat::literal mk_root_eq(nlsat::solver& s, nlsat::poly* p, nlsat::var x, unsigned i) {
|
||||
nlsat::bool_var b = s.mk_root_atom(nlsat::atom::ROOT_EQ, x, i, p);
|
||||
return nlsat::literal(b, false);
|
||||
}
|
||||
|
||||
static void set_assignment_value(nlsat::assignment& as, anum_manager& am, nlsat::var v, rational const& val) {
|
||||
scoped_anum tmp(am);
|
||||
am.set(tmp, val.to_mpq());
|
||||
|
|
@ -2345,7 +2350,189 @@ static void tst_unsound_lws_ppblockterm() {
|
|||
std::cout << "=== END tst_unsound_lws_ppblockterm ===\n\n";
|
||||
}
|
||||
|
||||
// Test case for gh-8397: unsound lemma with lws=false
|
||||
// Unsound lemma detected by nlsat.check_lemmas=true:
|
||||
// !(1024 x1 = 0) or !(x1 + 1 > 0) or !(2048 x1^2 + 4096 x1 = 0) or
|
||||
// !(x1 = root[3](1024 x1^3 + 6144 x1^2 + 6144 x1)) or
|
||||
// !(x1 = root[3](2048 x1^3 + 6144 x1^2 + 4096 x1)) or !(x1 = 0) or
|
||||
// 4 x6^3 + 4 x5 x6^2 - 4 x1 x6^2 + 4 x6^2 - 4 x1 x5 x6 - 4 x1 x6 < 0 or
|
||||
// x6 - x1 = 0 or x6 > 0
|
||||
// Counterexample: x1 = 0, x5 = 0, x6 = -0.5
|
||||
static void tst_unsound_gh8397() {
|
||||
// Reproduce exact unsound lemma from gh-8397
|
||||
// Unsound lemma: !(1024 x1 = 0) or !(x1 + 1 > 0) or !(2048 x1^2 + 4096 x1 = 0) or
|
||||
// !(x1 = root[3](1024 x1^3 + 6144 x1^2 + 6144 x1)) or
|
||||
// !(x1 = root[3](2048 x1^3 + 6144 x1^2 + 4096 x1)) or !(x1 = 0) or
|
||||
// 4 x6^3 + 4 x5 x6^2 - 4 x1 x6^2 + 4 x6^2 - 4 x1 x5 x6 - 4 x1 x6 < 0 or x6 - x1 = 0 or x6 > 0
|
||||
// Counterexample: x1=0, x5=0, x6=-0.5 makes ALL literals FALSE
|
||||
// Sample point: x0=-1, x1=0, x2=0, x3=-1, x4=0, x5=-1 (x6 is conflict var)
|
||||
std::cout << "=== tst_unsound_gh8397 ===\n";
|
||||
|
||||
auto run_test = [](bool use_lws) {
|
||||
std::cout << "\n--- Running with lws=" << (use_lws ? "true" : "false") << " ---\n";
|
||||
|
||||
params_ref ps;
|
||||
ps.set_bool("lws", use_lws);
|
||||
reslimit rlim;
|
||||
nlsat::solver s(rlim, ps, false);
|
||||
anum_manager& am = s.am();
|
||||
nlsat::pmanager& pm = s.pm();
|
||||
nlsat::explain& ex = s.get_explain();
|
||||
|
||||
// Create variables in order: x0, x1, x2, x3, x4, x5, x6
|
||||
nlsat::var x0 = s.mk_var(false);
|
||||
nlsat::var x1 = s.mk_var(false);
|
||||
nlsat::var x2 = s.mk_var(false);
|
||||
nlsat::var x3 = s.mk_var(false);
|
||||
nlsat::var x4 = s.mk_var(false);
|
||||
nlsat::var x5 = s.mk_var(false);
|
||||
nlsat::var x6 = s.mk_var(false);
|
||||
(void)x0; (void)x2; (void)x3; (void)x4; // suppress unused warnings
|
||||
|
||||
polynomial_ref _x1(pm), _x5(pm), _x6(pm);
|
||||
_x1 = pm.mk_polynomial(x1);
|
||||
_x5 = pm.mk_polynomial(x5);
|
||||
_x6 = pm.mk_polynomial(x6);
|
||||
|
||||
// Polynomials from the unsound lemma
|
||||
// p_main: 4 x6^3 + 4 x5 x6^2 - 4 x1 x6^2 + 4 x6^2 - 4 x1 x5 x6 - 4 x1 x6
|
||||
polynomial_ref p_main(pm);
|
||||
p_main = 4*_x6*_x6*_x6 + 4*_x5*_x6*_x6 - 4*_x1*_x6*_x6 + 4*_x6*_x6 - 4*_x1*_x5*_x6 - 4*_x1*_x6;
|
||||
|
||||
// p_diff: x6 - x1
|
||||
polynomial_ref p_diff(pm);
|
||||
p_diff = _x6 - _x1;
|
||||
|
||||
// Additional polynomials for x1 constraints (from root literals)
|
||||
// 1024 x1^3 + 6144 x1^2 + 6144 x1 = 1024*x1*(x1^2 + 6*x1 + 6)
|
||||
polynomial_ref p_root1(pm);
|
||||
p_root1 = 1024*_x1*_x1*_x1 + 6144*_x1*_x1 + 6144*_x1;
|
||||
|
||||
// 2048 x1^3 + 6144 x1^2 + 4096 x1 = 1024*x1*(2*x1^2 + 6*x1 + 4)
|
||||
polynomial_ref p_root2(pm);
|
||||
p_root2 = 2048*_x1*_x1*_x1 + 6144*_x1*_x1 + 4096*_x1;
|
||||
|
||||
// 1024 x1
|
||||
polynomial_ref p_x1_eq(pm);
|
||||
p_x1_eq = 1024*_x1;
|
||||
|
||||
// x1 + 1
|
||||
polynomial_ref p_x1_gt(pm);
|
||||
p_x1_gt = _x1 + 1;
|
||||
|
||||
// 2048 x1^2 + 4096 x1
|
||||
polynomial_ref p_x1_eq2(pm);
|
||||
p_x1_eq2 = 2048*_x1*_x1 + 4096*_x1;
|
||||
|
||||
std::cout << "p_main: " << p_main << "\n";
|
||||
std::cout << "p_diff: " << p_diff << "\n";
|
||||
std::cout << "p_root1: " << p_root1 << "\n";
|
||||
std::cout << "p_root2: " << p_root2 << "\n";
|
||||
|
||||
// Set sample point: x0=-1, x1=0, x2=0, x3=-1, x4=0, x5=-1
|
||||
nlsat::assignment sample_as(am);
|
||||
scoped_anum val(am);
|
||||
am.set(val, -1); sample_as.set(x0, val);
|
||||
am.set(val, 0); sample_as.set(x1, val);
|
||||
am.set(val, 0); sample_as.set(x2, val);
|
||||
am.set(val, -1); sample_as.set(x3, val);
|
||||
am.set(val, 0); sample_as.set(x4, val);
|
||||
am.set(val, -1); sample_as.set(x5, val);
|
||||
// x6 is the conflict variable, set to 0 for sample
|
||||
am.set(val, 0); sample_as.set(x6, val);
|
||||
s.set_rvalues(sample_as);
|
||||
|
||||
// Input literals for the conflict (constraints on x6):
|
||||
// The conflict is that x6 > x1 (i.e. x6 > 0) AND p_main < 0
|
||||
// But at sample point x1=0, x5=-1, these are infeasible at x6=0
|
||||
nlsat::scoped_literal_vector lits(s);
|
||||
lits.push_back(mk_gt(s, p_diff.get())); // x6 - x1 > 0 (i.e. x6 > 0 since x1=0)
|
||||
lits.push_back(mk_lt(s, p_main.get())); // p_main < 0
|
||||
|
||||
// Also add x1 constraints that were part of the conflict core
|
||||
lits.push_back(mk_eq(s, p_x1_eq.get())); // 1024 x1 = 0
|
||||
lits.push_back(mk_gt(s, p_x1_gt.get())); // x1 + 1 > 0
|
||||
lits.push_back(mk_eq(s, p_x1_eq2.get())); // 2048 x1^2 + 4096 x1 = 0
|
||||
// Root literals: x1 = root[3](p_root1), x1 = root[3](p_root2)
|
||||
lits.push_back(mk_root_eq(s, p_root1.get(), x1, 3)); // x1 = root[3](p_root1)
|
||||
lits.push_back(mk_root_eq(s, p_root2.get(), x1, 3)); // x1 = root[3](p_root2)
|
||||
lits.push_back(mk_eq(s, _x1.get())); // x1 = 0
|
||||
|
||||
std::cout << "Input literals:\n";
|
||||
s.display(std::cout, lits.size(), lits.data());
|
||||
std::cout << "\n";
|
||||
|
||||
// Call compute_conflict_explanation
|
||||
nlsat::scoped_literal_vector result(s);
|
||||
ex.compute_conflict_explanation(lits.size(), lits.data(), result);
|
||||
|
||||
std::cout << "Result lemma (lws=" << (use_lws ? "true" : "false") << "):\n";
|
||||
std::cout << "(or";
|
||||
for (auto l : result) {
|
||||
s.display(std::cout << "\n ", l);
|
||||
}
|
||||
for (unsigned i = 0; i < lits.size(); ++i) {
|
||||
s.display(std::cout << "\n ", ~lits[i]);
|
||||
}
|
||||
std::cout << "\n)\n";
|
||||
|
||||
// Counterexample from checker: x1=0, x5=0, x6=-0.5
|
||||
nlsat::assignment counter_as(am);
|
||||
am.set(val, -1); counter_as.set(x0, val);
|
||||
am.set(val, 0); counter_as.set(x1, val);
|
||||
am.set(val, 0); counter_as.set(x2, val);
|
||||
am.set(val, -1); counter_as.set(x3, val);
|
||||
am.set(val, 0); counter_as.set(x4, val);
|
||||
am.set(val, 0); counter_as.set(x5, val); // changed from -1 to 0
|
||||
mpq half; am.qm().set(half, -1, 2); // -1/2
|
||||
am.set(val, half); counter_as.set(x6, val);
|
||||
|
||||
std::cout << "\nEvaluating at counterexample (x1=0, x5=0, x6=-0.5):\n";
|
||||
|
||||
// Evaluate each result literal at counterexample
|
||||
small_object_allocator allocator;
|
||||
nlsat::evaluator eval(s, counter_as, pm, allocator);
|
||||
|
||||
bool all_false = true;
|
||||
for (auto l : result) {
|
||||
nlsat::atom* a = s.bool_var2atom(l.var());
|
||||
if (a) {
|
||||
bool value = eval.eval(a, l.sign());
|
||||
s.display(std::cout << " ", l);
|
||||
std::cout << " -> " << (value ? "TRUE" : "FALSE") << "\n";
|
||||
if (value) all_false = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Also check negated input literals
|
||||
for (unsigned i = 0; i < lits.size(); ++i) {
|
||||
nlsat::literal nl = ~lits[i];
|
||||
nlsat::atom* a = s.bool_var2atom(nl.var());
|
||||
if (a) {
|
||||
bool value = eval.eval(a, nl.sign());
|
||||
s.display(std::cout << " ", nl);
|
||||
std::cout << " -> " << (value ? "TRUE" : "FALSE") << "\n";
|
||||
if (value) all_false = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (all_false) {
|
||||
std::cout << "*** ALL literals FALSE at counterexample - LEMMA IS UNSOUND! ***\n";
|
||||
} else {
|
||||
std::cout << "At least one literal is TRUE - lemma is sound at this point\n";
|
||||
}
|
||||
};
|
||||
|
||||
run_test(false); // lws=false (buggy)
|
||||
run_test(true); // lws=true (should be correct)
|
||||
|
||||
std::cout << "\n=== END tst_unsound_gh8397 ===\n\n";
|
||||
}
|
||||
|
||||
void tst_nlsat() {
|
||||
tst_unsound_gh8397();
|
||||
return;
|
||||
std::cout << "------------------\n";
|
||||
tst_unsound_lws_ppblockterm();
|
||||
std::cout << "------------------\n";
|
||||
tst_unsound_lws_n46();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue