Skip to content

Commit 2ac78b6

Browse files
committed
revive nlsat check_lemma()
Signed-off-by: Lev Nachmanson <levnach@hotmail.com>
1 parent 7b182c9 commit 2ac78b6

File tree

1 file changed

+100
-72
lines changed

1 file changed

+100
-72
lines changed

src/nlsat/nlsat_solver.cpp

Lines changed: 100 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ Revision History:
2727
#include "util/map.h"
2828
#include "util/dependency.h"
2929
#include "util/permutation.h"
30+
#include "util/scoped_timer.h"
31+
#include "util/cancel_eh.h"
3032
#include "math/polynomial/algebraic_numbers.h"
3133
#include "math/polynomial/polynomial_cache.h"
3234
#include "nlsat/nlsat_solver.h"
@@ -311,7 +313,7 @@ namespace nlsat {
311313
m_explain.set_minimize_cores(min_cores);
312314
m_explain.set_factor(p.factor());
313315
m_explain.set_add_all_coeffs(p.add_all_coeffs());
314-
m_explain.set_add_zero_disc(p.zero_disc());
316+
m_explain.set_add_zero_disc(p.zero_disc());
315317
m_am.updt_params(p.p);
316318
}
317319

@@ -1013,42 +1015,27 @@ namespace nlsat {
10131015
}
10141016
}
10151017

1016-
void check_lemma(unsigned n, literal const* cls, bool is_valid, assumption_set a) {
1017-
TRACE(nlsat, display(tout << "check lemma: ", n, cls) << "\n";
1018-
display(tout););
1019-
if (!m_debug_known_solution_file_name.empty()) {
1020-
debug_check_lemma_on_known_sat_values(n, cls);
1021-
return;
1022-
}
1023-
IF_VERBOSE(2, display(verbose_stream() << "check lemma " << (is_valid?"valid: ":"consequence: "), n, cls) << "\n");
1024-
for (clause* c : m_learned) IF_VERBOSE(1, display(verbose_stream() << "lemma: ", *c) << "\n");
1025-
scoped_suspend_rlimit _limit(m_rlimit);
1026-
ctx c(m_rlimit, m_ctx.m_params, m_ctx.m_incremental);
1027-
solver solver2(c);
1028-
imp& checker = *(solver2.m_imp);
1029-
checker.m_check_lemmas = false;
1030-
checker.m_log_lemmas = false;
1031-
checker.m_dump_mathematica = false;
1032-
checker.m_inline_vars = false;
1033-
1018+
// Helper: Setup checker solver and translate atoms/clauses
1019+
void setup_checker(imp& checker, scoped_bool_vars& tr, unsigned n, literal const* cls, assumption_set a) {
10341020
auto pconvert = [&](poly* p) {
10351021
return convert(m_pm, p, checker.m_pm);
10361022
};
10371023

1038-
// need to translate Boolean variables and literals
1039-
scoped_bool_vars tr(checker);
1024+
// Register variables (must use mk_var to also create vars in polynomial manager)
10401025
for (var x = 0; x < m_is_int.size(); ++x) {
1041-
checker.register_var(x, is_int(x));
1026+
checker.mk_var(is_int(x));
10421027
}
1028+
1029+
// Translate Boolean variables and atoms
10431030
bool_var bv = 0;
10441031
tr.push_back(bv);
10451032
for (bool_var b = 1; b < m_atoms.size(); ++b) {
1046-
atom* a = m_atoms[b];
1047-
if (a == nullptr) {
1033+
atom* at = m_atoms[b];
1034+
if (at == nullptr) {
10481035
bv = checker.mk_bool_var();
10491036
}
1050-
else if (a->is_ineq_atom()) {
1051-
ineq_atom& ia = *to_ineq_atom(a);
1037+
else if (at->is_ineq_atom()) {
1038+
ineq_atom& ia = *to_ineq_atom(at);
10521039
unsigned sz = ia.size();
10531040
polynomial_ref_vector ps(checker.m_pm);
10541041
bool_vector is_even;
@@ -1058,67 +1045,108 @@ namespace nlsat {
10581045
}
10591046
bv = checker.mk_ineq_atom(ia.get_kind(), sz, ps.data(), is_even.data());
10601047
}
1061-
else if (a->is_root_atom()) {
1062-
root_atom& r = *to_root_atom(a);
1048+
else if (at->is_root_atom()) {
1049+
root_atom& r = *to_root_atom(at);
10631050
if (r.x() >= max_var(r.p())) {
1064-
// permutation may be reverted after check completes,
1065-
// but then root atoms are not used in lemmas.
10661051
bv = checker.mk_root_atom(r.get_kind(), r.x(), r.i(), pconvert(r.p()));
10671052
}
1053+
else {
1054+
// root atom cannot be translated due to variable ordering
1055+
bv = checker.mk_bool_var();
1056+
}
10681057
}
10691058
else {
10701059
UNREACHABLE();
10711060
}
10721061
tr.push_back(bv);
10731062
}
1074-
if (!is_valid) {
1075-
for (clause* c : m_clauses) {
1076-
if (!a && c->assumptions()) {
1077-
continue;
1078-
}
1079-
literal_vector lits;
1080-
for (literal lit : *c) {
1081-
lits.push_back(literal(tr[lit.var()], lit.sign()));
1082-
}
1083-
checker.mk_external_clause(lits.size(), lits.data(), nullptr);
1063+
1064+
// Add original clauses (checking that lemma is a consequence)
1065+
for (clause* c : m_clauses) {
1066+
if (!a && c->assumptions()) {
1067+
continue;
1068+
}
1069+
literal_vector lits;
1070+
for (literal lit : *c) {
1071+
lits.push_back(literal(tr[lit.var()], lit.sign()));
10841072
}
1073+
checker.mk_external_clause(lits.size(), lits.data(), nullptr);
10851074
}
1075+
1076+
// Add negation of lemma literals
10861077
for (unsigned i = 0; i < n; ++i) {
10871078
literal lit = cls[i];
10881079
literal nlit(tr[lit.var()], !lit.sign());
10891080
checker.mk_external_clause(1, &nlit, nullptr);
10901081
}
1091-
lbool r = checker.check();
1092-
if (r == l_true) {
1093-
for (bool_var b : tr) {
1094-
literal lit(b, false);
1095-
IF_VERBOSE(0, checker.display(verbose_stream(), lit) << " := " << checker.value(lit) << "\n");
1096-
TRACE(nlsat, checker.display(tout, lit) << " := " << checker.value(lit) << "\n";);
1097-
}
1098-
for (clause* c : m_learned) {
1099-
bool found = false;
1100-
for (literal lit: *c) {
1101-
literal tlit(tr[lit.var()], lit.sign());
1102-
found |= checker.value(tlit) == l_true;
1103-
}
1104-
if (!found) {
1105-
IF_VERBOSE(0, display(verbose_stream() << "violdated clause: ", *c) << "\n");
1106-
TRACE(nlsat, display(tout << "violdated clause: ", *c) << "\n";);
1107-
}
1082+
}
1083+
1084+
// Helper: Display unsound lemma failure information
1085+
void display_unsound_lemma(imp& checker, scoped_bool_vars& tr, unsigned n, literal const* cls) {
1086+
verbose_stream() << "\n";
1087+
verbose_stream() << "========== UNSOUND LEMMA DETECTED ==========\n";
1088+
verbose_stream() << "The following lemma is NOT implied by the original clauses:\n";
1089+
display(verbose_stream() << " Lemma: ", n, cls) << "\n\n";
1090+
verbose_stream() << "Reason: Found a satisfying assignment where:\n";
1091+
verbose_stream() << " - The original clauses are satisfied\n";
1092+
verbose_stream() << " - But ALL literals in the lemma are FALSE\n\n";
1093+
1094+
// Display variable values used in the lemma
1095+
verbose_stream() << "Variable values in counterexample:\n";
1096+
auto lemma_vars = collect_vars_on_clause(n, cls);
1097+
for (var x : lemma_vars) {
1098+
if (checker.m_assignment.is_assigned(x)) {
1099+
verbose_stream() << " ";
1100+
m_display_var(verbose_stream(), x);
1101+
verbose_stream() << " = ";
1102+
checker.m_am.display_decimal(verbose_stream(), checker.m_assignment.value(x));
1103+
verbose_stream() << "\n";
11081104
}
1109-
for (clause* c : m_valids) {
1110-
bool found = false;
1111-
for (literal lit: *c) {
1112-
literal tlit(tr[lit.var()], lit.sign());
1113-
found |= checker.value(tlit) == l_true;
1114-
}
1115-
if (!found) {
1116-
IF_VERBOSE(0, display(verbose_stream() << "violdated tautology clause: ", *c) << "\n");
1117-
TRACE(nlsat, display(tout << "violdated tautology clause: ", *c) << "\n";);
1118-
}
1105+
}
1106+
1107+
verbose_stream() << "\nLemma literals evaluated to FALSE:\n";
1108+
for (unsigned i = 0; i < n; ++i) {
1109+
literal lit = cls[i];
1110+
literal tlit(tr[lit.var()], lit.sign());
1111+
verbose_stream() << " ";
1112+
display(verbose_stream(), lit);
1113+
verbose_stream() << " = " << checker.value(tlit) << "\n";
1114+
}
1115+
verbose_stream() << "=============================================\n";
1116+
verbose_stream() << "ABORTING: Unsound lemma detected!\n";
1117+
}
1118+
1119+
void check_lemma(unsigned n, literal const* cls, assumption_set a) {
1120+
TRACE(nlsat, display(tout << "check lemma: ", n, cls) << "\n";
1121+
display(tout););
1122+
1123+
try {
1124+
// Create a separate reslimit for the checker with 10 second timeout
1125+
reslimit checker_rlimit;
1126+
cancel_eh<reslimit> eh(checker_rlimit);
1127+
scoped_timer timer(10000, &eh);
1128+
1129+
ctx c(checker_rlimit, m_ctx.m_params, m_ctx.m_incremental);
1130+
solver solver2(c);
1131+
imp& checker = *(solver2.m_imp);
1132+
checker.m_check_lemmas = false;
1133+
checker.m_log_lemmas = false;
1134+
checker.m_dump_mathematica = false;
1135+
checker.m_inline_vars = false;
1136+
1137+
scoped_bool_vars tr(checker);
1138+
setup_checker(checker, tr, n, cls, a);
1139+
lbool r = checker.check();
1140+
if (r == l_undef) // Checker timed out - skip this lemma check
1141+
return;
1142+
1143+
if (r == l_true) {
1144+
display_unsound_lemma(checker, tr, n, cls);
1145+
exit(1);
11191146
}
1120-
throw default_exception("lemma did not check");
1121-
UNREACHABLE();
1147+
}
1148+
catch (...) {
1149+
// Ignore exceptions from the checker - just skip this lemma check
11221150
}
11231151
}
11241152

@@ -2096,7 +2124,7 @@ namespace nlsat {
20962124

20972125
if (m_check_lemmas)
20982126
for (clause* c : m_learned)
2099-
check_lemma(c->size(), c->data(), false, nullptr);
2127+
check_lemma(c->size(), c->data(), nullptr);
21002128

21012129
assumptions.reset();
21022130
assumptions.append(result);
@@ -2333,7 +2361,7 @@ namespace nlsat {
23332361
}
23342362

23352363
if (m_check_lemmas) {
2336-
check_lemma(m_lazy_clause.size(), m_lazy_clause.data(), false, nullptr);
2364+
check_lemma(m_lazy_clause.size(), m_lazy_clause.data(), nullptr);
23372365
m_valids.push_back(mk_clause_core(m_lazy_clause.size(), m_lazy_clause.data(), false, nullptr));
23382366
}
23392367

@@ -2578,7 +2606,7 @@ namespace nlsat {
25782606
tout << "found_decision: " << found_decision << "\n";);
25792607

25802608
if (m_check_lemmas) {
2581-
check_lemma(m_lemma.size(), m_lemma.data(), false, m_lemma_assumptions.get());
2609+
check_lemma(m_lemma.size(), m_lemma.data(), m_lemma_assumptions.get());
25822610
}
25832611

25842612
// if (m_log_lemmas)

0 commit comments

Comments
 (0)