3
0
Fork 0
mirror of https://github.com/Z3Prover/z3 synced 2025-08-05 10:50:24 +00:00

overhaul of proof format for new solver

This commit overhauls the proof format (in development) for the new core.

NOTE: this functionality is work in progress with a long way to go.
It is shielded by the sat.euf option, which is off by default and in pre-release state.
It is too early to fuzz or use it. It is pushed into master to shed light on road-map for certifying inferences of sat.euf.

It retires the ad-hoc extension of DRUP used by the SAT solver.
Instead it relies on SMT with ad-hoc extensions for proof terms.
It adds the following commands (consumed by proof_cmds.cpp):

- assume  - for input clauses
- learn   - when a clause is learned (or redundant clause is added)
- del     - when a clause is deleted.

The commands take a list of expressions of type Bool and the
last argument can optionally be of type Proof.
When the last argument is of type Proof it is provided as a hint
to justify the learned clause.

Proof hints can be checked using a self-contained proof
checker. The sat/smt/euf_proof_checker.h class provides
a plugin dispatcher for checkers.
It is instantiated with a checker for arithmetic lemmas,
so far for Farkas proofs.

Use example:
```
(set-option :sat.euf true)
(set-option :tactic.default_tactic smt)
(set-option :sat.smt.proof f.proof)
(declare-const x Int)
(declare-const y Int)
(declare-const z Int)
(declare-const u Int)
(assert (< x y))
(assert (< y z))
(assert (< z x))
(check-sat)
```

Run z3 on a file with above content.
Then run z3 on f.proof

```
(verified-smt)
(verified-smt)
(verified-smt)
(verified-farkas)
(verified-smt)
```
This commit is contained in:
Nikolaj Bjorner 2022-08-28 17:44:33 -07:00
parent 9922c766b9
commit e2f4fc2307
37 changed files with 809 additions and 1078 deletions

View file

@ -52,8 +52,7 @@ namespace sat {
void drat::updt_config() {
m_check_unsat = s.get_config().m_drat_check_unsat;
m_check_sat = s.get_config().m_drat_check_sat;
m_trim = s.get_config().m_drup_trim;
m_check = m_check_unsat || m_check_sat || m_trim;
m_check = m_check_unsat || m_check_sat;
m_activity = s.get_config().m_drat_activity;
}
@ -130,14 +129,6 @@ namespace sat {
}
}
buffer[len++] = '0';
if (st.get_hint()) {
buffer[len++] = ' ';
buffer[len++] = 'p';
buffer[len++] = ' ';
auto* ps = st.get_hint();
for (auto ch : ps->to_string())
buffer[len++] = ch;
}
buffer[len++] = '\n';
m_out->write(buffer, len);
}
@ -210,8 +201,6 @@ namespace sat {
if (st.is_redundant() && st.is_sat())
verify(1, &l);
if (m_trim)
m_proof.push_back({mk_clause(1, &l, st.is_redundant()), st});
if (st.is_deleted())
return;
@ -230,8 +219,7 @@ namespace sat {
IF_VERBOSE(20, trace(verbose_stream(), 2, lits, st););
if (st.is_deleted()) {
if (m_trim)
m_proof.push_back({mk_clause(2, lits, true), st});
;
}
else {
if (st.is_redundant() && st.is_sat())
@ -658,7 +646,7 @@ namespace sat {
verify(0, nullptr);
SASSERT(m_inconsistent);
}
if (m_print_clause) m_print_clause->on_clause(0, nullptr, status::redundant());
if (m_clause_eh) m_clause_eh->on_clause(0, nullptr, status::redundant());
}
void drat::add(literal l, bool learned) {
++m_stats.m_num_add;
@ -666,7 +654,8 @@ namespace sat {
if (m_out) dump(1, &l, st);
if (m_bout) bdump(1, &l, st);
if (m_check) append(l, st);
if (m_print_clause) m_print_clause->on_clause(1, &l, st);
TRACE("sat", tout << "add " << m_clause_eh << "\n");
if (m_clause_eh) m_clause_eh->on_clause(1, &l, st);
}
void drat::add(literal l1, literal l2, status st) {
if (st.is_deleted())
@ -677,7 +666,7 @@ namespace sat {
if (m_out) dump(2, ls, st);
if (m_bout) bdump(2, ls, st);
if (m_check) append(l1, l2, st);
if (m_print_clause) m_print_clause->on_clause(2, ls, st);
if (m_clause_eh) m_clause_eh->on_clause(2, ls, st);
}
void drat::add(clause& c, status st) {
if (st.is_deleted())
@ -687,7 +676,7 @@ namespace sat {
if (m_out) dump(c.size(), c.begin(), st);
if (m_bout) bdump(c.size(), c.begin(), st);
if (m_check) append(mk_clause(c), st);
if (m_print_clause) m_print_clause->on_clause(c.size(), c.begin(), st);
if (m_clause_eh) m_clause_eh->on_clause(c.size(), c.begin(), st);
}
void drat::add(literal_vector const& lits, status st) {
@ -709,8 +698,8 @@ namespace sat {
if (m_out)
dump(sz, lits, st);
if (m_print_clause)
m_print_clause->on_clause(sz, lits, st);
if (m_clause_eh)
m_clause_eh->on_clause(sz, lits, st);
}
void drat::add(literal_vector const& c) {
@ -730,8 +719,8 @@ namespace sat {
}
}
}
if (m_print_clause)
m_print_clause->on_clause(c.size(), c.data(), status::redundant());
if (m_clause_eh)
m_clause_eh->on_clause(c.size(), c.data(), status::redundant());
}
void drat::del(literal l) {
@ -739,6 +728,7 @@ namespace sat {
if (m_out) dump(1, &l, status::deleted());
if (m_bout) bdump(1, &l, status::deleted());
if (m_check) append(l, status::deleted());
if (m_clause_eh) m_clause_eh->on_clause(1, &l, status::deleted());
}
void drat::del(literal l1, literal l2) {
@ -747,6 +737,7 @@ namespace sat {
if (m_out) dump(2, ls, status::deleted());
if (m_bout) bdump(2, ls, status::deleted());
if (m_check) append(l1, l2, status::deleted());
if (m_clause_eh) m_clause_eh->on_clause(2, ls, status::deleted());
}
void drat::del(clause& c) {
@ -764,7 +755,8 @@ namespace sat {
++m_stats.m_num_del;
if (m_out) dump(c.size(), c.begin(), status::deleted());
if (m_bout) bdump(c.size(), c.begin(), status::deleted());
if (m_check) append(mk_clause(c), status::deleted());
if (m_check) append(mk_clause(c), status::deleted());
if (m_clause_eh) m_clause_eh->on_clause(c.size(), c.begin(), status::deleted());
}
clause& drat::mk_clause(clause& c) {
@ -780,23 +772,9 @@ namespace sat {
if (m_out) dump(c.size(), c.begin(), status::deleted());
if (m_bout) bdump(c.size(), c.begin(), status::deleted());
if (m_check) append(mk_clause(c.size(), c.begin(), true), status::deleted());
if (m_clause_eh) m_clause_eh->on_clause(c.size(), c.begin(), status::deleted());
}
//
// placeholder for trim function.
// 1. trail contains justification for the empty clause.
// 2. backward pass to prune.
//
svector<std::pair<clause&, status>> drat::trim() {
SASSERT(m_units.empty());
svector<std::pair<clause&, status>> proof;
for (auto const& [c, st] : m_proof)
if (!st.is_deleted())
proof.push_back({c,st});
return proof;
}
void drat::check_model(model const& m) {
}
@ -828,196 +806,4 @@ namespace sat {
return out;
}
std::string proof_hint::to_string() const {
std::ostringstream ous;
switch (m_ty) {
case hint_type::null_h:
return std::string();
case hint_type::farkas_h:
ous << "farkas ";
break;
case hint_type::bound_h:
ous << "bound ";
break;
case hint_type::implied_eq_h:
ous << "implied_eq ";
break;
default:
UNREACHABLE();
break;
}
for (auto const& [q, l] : m_literals)
ous << rational(q) << " * " << l << " ";
for (auto const& [a, b] : m_eqs)
ous << " = " << a << " " << b << " ";
for (auto const& [a, b] : m_diseqs)
ous << " != " << a << " " << b << " ";
return ous.str();
}
void proof_hint::from_string(char const* s) {
proof_hint& h = *this;
h.reset();
h.m_ty = hint_type::null_h;
if (!s)
return;
auto ws = [&]() {
while (*s == ' ' || *s == '\n' || *s == '\t')
++s;
};
auto parse_type = [&]() {
if (0 == strncmp(s, "farkas", 6)) {
h.m_ty = hint_type::farkas_h;
s += 6;
return true;
}
if (0 == strncmp(s, "bound", 5)) {
h.m_ty = hint_type::bound_h;
s += 5;
return true;
}
if (0 == strncmp(s, "implied_eq", 10)) {
h.m_ty = hint_type::implied_eq_h;
s += 10;
return true;
}
return false;
};
sbuffer<char> buff;
auto parse_coeff = [&]() {
buff.reset();
while (*s && *s != ' ') {
buff.push_back(*s);
++s;
}
buff.push_back(0);
return rational(buff.data());
};
auto parse_literal = [&]() {
rational r = parse_coeff();
if (!r.is_int())
return sat::null_literal;
if (r < 0)
return sat::literal((-r).get_unsigned(), true);
return sat::literal(r.get_unsigned(), false);
};
auto parse_coeff_literal = [&]() {
if (*s == '=') {
++s;
ws();
unsigned a = parse_coeff().get_unsigned();
ws();
unsigned b = parse_coeff().get_unsigned();
h.m_eqs.push_back(std::make_pair(a, b));
return true;
}
if (*s == '!' && *(s + 1) == '=') {
s += 2;
ws();
unsigned a = parse_coeff().get_unsigned();
ws();
unsigned b = parse_coeff().get_unsigned();
h.m_diseqs.push_back(std::make_pair(a, b));
return true;
}
rational coeff = parse_coeff();
ws();
if (*s == '*') {
++s;
ws();
sat::literal lit = parse_literal();
h.m_literals.push_back(std::make_pair(coeff, lit));
return true;
}
return false;
};
ws();
if (!parse_type())
return;
ws();
while (*s) {
if (!parse_coeff_literal())
return;
ws();
}
}
#if 0
// debugging code
bool drat::is_clause(clause& c, literal l1, literal l2, literal l3, drat::status st1, drat::status st2) {
//if (st1 != st2) return false;
if (c.size() != 3) return false;
if (l1 == c[0]) {
if (l2 == c[1] && l3 == c[2]) return true;
if (l2 == c[2] && l3 == c[1]) return true;
}
if (l2 == c[0]) {
if (l1 == c[1] && l3 == c[2]) return true;
if (l1 == c[2] && l3 == c[1]) return true;
}
if (l3 == c[0]) {
if (l1 == c[1] && l2 == c[2]) return true;
if (l1 == c[2] && l2 == c[1]) return true;
}
return false;
}
#endif
#if 0
if (!m_inconsistent) {
literal_vector lits(n, c);
IF_VERBOSE(0, verbose_stream() << "not drup " << lits << "\n");
for (unsigned v = 0; v < m_assignment.size(); ++v) {
lbool val = m_assignment[v];
if (val != l_undef) {
IF_VERBOSE(0, verbose_stream() << literal(v, false) << " |-> " << val << "\n");
}
}
for (clause* cp : s.m_clauses) {
clause& cl = *cp;
bool found = false;
for (literal l : cl) {
if (m_assignment[l.var()] != (l.sign() ? l_true : l_false)) {
found = true;
break;
}
}
if (!found) {
IF_VERBOSE(0, verbose_stream() << "Clause is false under assignment: " << cl << "\n");
}
}
for (clause* cp : s.m_learned) {
clause& cl = *cp;
bool found = false;
for (literal l : cl) {
if (m_assignment[l.var()] != (l.sign() ? l_true : l_false)) {
found = true;
break;
}
}
if (!found) {
IF_VERBOSE(0, verbose_stream() << "Clause is false under assignment: " << cl << "\n");
}
}
svector<sat::solver::bin_clause> bin;
s.collect_bin_clauses(bin, true);
for (auto& b : bin) {
bool found = false;
if (m_assignment[b.first.var()] != (b.first.sign() ? l_true : l_false)) found = true;
if (m_assignment[b.second.var()] != (b.second.sign() ? l_true : l_false)) found = true;
if (!found) {
IF_VERBOSE(0, verbose_stream() << "Bin clause is false under assignment: " << b.first << " " << b.second << "\n");
}
}
IF_VERBOSE(0, s.display(verbose_stream()));
exit(0);
}
#endif
}