/*++ Copyright (c) 2017 Microsoft Corporation Module Name: sat_drat.cpp Abstract: Produce DRUP/DRAT proofs. Check them using a very simple forward checker that interacts with external plugins. Author: Nikolaj Bjorner (nbjorner) 2017-2-3 Notes: --*/ #include "util/rational.h" #include "sat/sat_solver.h" #include "sat/sat_drat.h" namespace sat { drat::drat(solver& s) : s(s) { if (s.get_config().m_drat && s.get_config().m_drat_file.is_non_empty_string()) { auto mode = s.get_config().m_drat_binary ? (std::ios_base::binary | std::ios_base::out | std::ios_base::trunc) : std::ios_base::out; m_out = alloc(std::ofstream, s.get_config().m_drat_file.str(), mode); if (s.get_config().m_drat_binary) std::swap(m_out, m_bout); } } drat::~drat() { if (m_out) m_out->flush(); if (m_bout) m_bout->flush(); dealloc(m_out); dealloc(m_bout); for (auto & [c, st] : m_proof) m_alloc.del_clause(&c); m_proof.reset(); m_out = nullptr; m_bout = nullptr; } 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_check = m_check_unsat || m_check_sat; m_activity = s.get_config().m_drat_activity; } std::ostream& drat::pp(std::ostream& out, status st) const { if (st.is_deleted()) out << "d"; return out; } void drat::dump(unsigned n, literal const* c, status st) { if (st.is_asserted() && !s.m_ext) return; if (m_activity && ((m_stats.m_num_add % 1000) == 0)) dump_activity(); char buffer[10000]; char digits[20]; // enough for storing unsigned char* lastd = digits + sizeof(digits); unsigned len = 0; if (st.is_deleted()) { buffer[len++] = 'd'; buffer[len++] = ' '; } else if (st.is_input()) { buffer[len++] = 'i'; buffer[len++] = ' '; } else if (!st.is_sat()) { if (st.is_redundant()) { buffer[len++] = 'r'; buffer[len++] = ' '; } else if (st.is_asserted()) { buffer[len++] = 'a'; buffer[len++] = ' '; } } for (unsigned i = 0; i < n; ++i) { literal lit = c[i]; unsigned v = lit.var(); if (lit.sign()) buffer[len++] = '-'; char* d = lastd; SASSERT(v > 0); while (v > 0) { d--; *d = (v % 10) + '0'; v /= 10; SASSERT(d > digits); } SASSERT(len + lastd < sizeof(buffer) + d); memcpy(buffer + len, d, lastd - d); len += static_cast(lastd - d); buffer[len++] = ' '; if (static_cast(len) + 50 > sizeof(buffer)) { m_out->write(buffer, len); len = 0; } } buffer[len++] = '0'; buffer[len++] = '\n'; m_out->write(buffer, len); } void drat::dump_activity() { (*m_out) << "c activity "; for (unsigned v = 0; v < s.num_vars(); ++v) (*m_out) << s.m_activity[v] << " "; (*m_out) << "\n"; } void drat::bdump(unsigned n, literal const* c, status st) { unsigned char ch = 0; if (st.is_redundant()) ch = 'a'; else if (st.is_deleted()) ch = 'd'; else return; char buffer[10000]; int len = 0; buffer[len++] = ch; for (unsigned i = 0; i < n; ++i) { literal lit = c[i]; unsigned v = 2 * lit.var() + (lit.sign() ? 1 : 0); do { ch = static_cast(v & 255); v >>= 7; if (v) ch |= 128; buffer[len++] = ch; if (len == sizeof(buffer)) { m_bout->write(buffer, len); len = 0; } } while (v); } buffer[len++] = 0; m_bout->write(buffer, len); } bool drat::is_cleaned(clause& c) const { literal last = null_literal; unsigned n = c.size(); for (unsigned i = 0; i < n; ++i) { if (c[i] == last) return true; last = c[i]; } return false; } void drat::trace(std::ostream& out, unsigned n, literal const* c, status st) { pp(out, st) << " "; literal last = null_literal; for (unsigned i = 0; i < n; ++i) { if (c[i] != last) { out << c[i] << " "; last = c[i]; } } out << "\n"; } void drat::append(literal l, status st) { TRACE("sat_drat", pp(tout, st) << " " << l << "\n";); declare(l); IF_VERBOSE(20, trace(verbose_stream(), 1, &l, st);); if (st.is_redundant() && st.is_sat()) verify(1, &l); if (st.is_deleted()) return; if (m_check_unsat) { assign_propagate(l, nullptr); m_units.push_back({l, nullptr}); } } void drat::append(literal l1, literal l2, status st) { TRACE("sat_drat", pp(tout, st) << " " << l1 << " " << l2 << "\n";); declare(l1); declare(l2); literal lits[2] = { l1, l2 }; IF_VERBOSE(20, trace(verbose_stream(), 2, lits, st);); if (st.is_deleted()) { ; } else { if (st.is_redundant() && st.is_sat()) verify(2, lits); clause& c = mk_clause(2, lits, st.is_redundant()); m_proof.push_back({c, st}); if (!m_check_unsat) return; unsigned idx = m_watched_clauses.size(); m_watched_clauses.push_back(watched_clause(&c, l1, l2)); m_watches[(~l1).index()].push_back(idx); m_watches[(~l2).index()].push_back(idx); if (value(l1) == l_false && value(l2) == l_false) m_inconsistent = true; else if (value(l1) == l_false) assign_propagate(l2, &c); else if (value(l2) == l_false) assign_propagate(l1, &c); } } void drat::append(clause& c, status st) { TRACE("sat_drat", pp(tout, st) << " " << c << "\n";); for (literal lit : c) declare(lit); unsigned n = c.size(); IF_VERBOSE(20, trace(verbose_stream(), n, c.begin(), st);); if (st.is_redundant() && st.is_sat()) verify(c); m_proof.push_back({c, st}); if (st.is_deleted()) { if (n > 0) del_watch(c, c[0]); if (n > 1) del_watch(c, c[1]); return; } unsigned num_watch = 0; literal l1, l2; for (unsigned i = 0; i < n; ++i) { if (value(c[i]) != l_false) { if (num_watch == 0) { l1 = c[i]; ++num_watch; } else { l2 = c[i]; ++num_watch; break; } } } if (!m_check_unsat) return; switch (num_watch) { case 0: m_inconsistent = true; break; case 1: assign_propagate(l1, &c); break; default: { SASSERT(num_watch == 2); unsigned idx = m_watched_clauses.size(); m_watched_clauses.push_back(watched_clause(&c, l1, l2)); m_watches[(~l1).index()].push_back(idx); m_watches[(~l2).index()].push_back(idx); break; } } } void drat::del_watch(clause& c, literal l) { watch& w = m_watches[(~l).index()]; for (unsigned i = 0; i < w.size(); ++i) { if (m_watched_clauses[w[i]].m_clause == &c) { w[i] = w.back(); w.pop_back(); break; } } } void drat::declare(literal l) { if (!m_check) return; unsigned n = static_cast(l.var()); while (m_assignment.size() <= n) { m_assignment.push_back(l_undef); m_watches.push_back(watch()); m_watches.push_back(watch()); } } bool drat::is_drup(unsigned n, literal const* c, literal_vector& units) { if (m_inconsistent) return true; if (n == 0) return false; unsigned num_units = m_units.size(); for (unsigned i = 0; !m_inconsistent && i < n; ++i) { declare(c[i]); assign_propagate(~c[i], nullptr); } for (unsigned i = num_units; i < m_units.size(); ++i) m_assignment[m_units[i].first.var()] = l_undef; for (unsigned i = num_units; i < m_units.size(); ++i) units.push_back(m_units[i].first); m_units.shrink(num_units); bool ok = m_inconsistent; m_inconsistent = false; return ok; } bool drat::is_drup(unsigned n, literal const* c) { if (m_inconsistent) return true; if (n == 0) return false; unsigned num_units = m_units.size(); for (unsigned i = 0; !m_inconsistent && i < n; ++i) assign_propagate(~c[i], nullptr); DEBUG_CODE(if (!m_inconsistent) validate_propagation();); DEBUG_CODE( for (auto const& [u,c] : m_units) SASSERT(m_assignment[u.var()] != l_undef); ); for (unsigned i = num_units; i < m_units.size(); ++i) m_assignment[m_units[i].first.var()] = l_undef; m_units.shrink(num_units); bool ok = m_inconsistent; m_inconsistent = false; return ok; } bool drat::is_drat(unsigned n, literal const* c) { return false; if (m_inconsistent || n == 0) return true; for (unsigned i = 0; i < n; ++i) if (is_drat(n, c, i)) return true; return false; } void drat::validate_propagation() const { for (auto const& [c, st] : m_proof) { if (c.size() > 1 && !st.is_deleted()) { unsigned num_undef = 0, num_true = 0; for (unsigned j = 0; j < c.size(); ++j) { switch (value(c[j])) { case l_false: break; case l_true: num_true++; break; case l_undef: num_undef++; break; } } CTRACE("sat_drat", num_true == 0 && num_undef == 1, display(tout);); VERIFY(num_true != 0 || num_undef != 1); } } } bool drat::is_drat(unsigned n, literal const* c, unsigned pos) { SASSERT(pos < n); literal l = c[pos]; literal_vector lits(n, c); SASSERT(lits.size() == n); for (auto const& [c, st] : m_proof) { if (c.size() > 1 && st.is_asserted()) { unsigned j = 0; for (; j < c.size() && c[j] != ~l; ++j) {} if (j != c.size()) { lits.append(j, c.begin()); lits.append(c.size() - j - 1, c.begin() + j + 1); if (!is_drup(lits.size(), lits.data())) return false; lits.resize(n); } } } return true; } void drat::verify(unsigned n, literal const* c) { if (!m_check_unsat) return; if (m_inconsistent) return; for (unsigned i = 0; i < n; ++i) declare(c[i]); if (is_drup(n, c)) { ++m_stats.m_num_drup; return; } if (is_drat(n, c)) { ++m_stats.m_num_drat; return; } literal_vector lits(n, c); IF_VERBOSE(0, verbose_stream() << "Verification of " << lits << " failed\n"); // s.display(std::cout); UNREACHABLE(); #if 0 SASSERT(false); INVOKE_DEBUGGER(); exit(0); UNREACHABLE(); //display(std::cout); TRACE("sat_drat", tout << literal_vector(n, c) << "\n"; display(tout); s.display(tout);); UNREACHABLE(); #endif } bool drat::contains(literal c, justification const& j) { if (!m_check_sat) { return true; } switch (j.get_kind()) { case justification::NONE: for (auto const& [u, j] : m_units) if (u == c) return true; return false; case justification::BINARY: return contains(c, j.get_literal()); case justification::CLAUSE: return contains(s.get_clause(j)); default: return true; } } bool drat::contains(unsigned n, literal const* lits) { if (!m_check) return true; unsigned num_add = 0; unsigned num_del = 0; for (unsigned i = m_proof.size(); i-- > 0; ) { auto const & [c, st] = m_proof[i]; if (match(n, lits, c)) { if (st.is_deleted()) num_del++; else num_add++; } } return num_add > num_del; } bool drat::match(unsigned n, literal const* lits, clause const& c) const { if (n != c.size()) return false; for (unsigned i = 0; i < n; ++i) { literal lit1 = lits[i]; bool found = false; for (literal lit2 : c) { if (lit1 == lit2) { found = true; break; } } if (!found) return false; } return true; } void drat::display(std::ostream& out) const { out << "units: "; for (auto const& [u, c] : m_units) out << u << " "; out << "\n"; for (unsigned i = 0; i < m_assignment.size(); ++i) { lbool v = value(literal(i, false)); if (v != l_undef) out << i << ": " << v << "\n"; } unsigned i = 0; for (auto const& [c, st] : m_proof) { ++i; if (st.is_deleted()) continue; unsigned num_true = 0; unsigned num_undef = 0; for (literal lit : c) { switch (value(lit)) { case l_true: num_true++; break; case l_undef: num_undef++; break; default: break; } } if (num_true == 0 && num_undef == 0) out << "False "; if (num_true == 0 && num_undef == 1) out << "Unit "; pp(out, st) << " " << i << ": " << c << "\n"; } for (unsigned i = 0; i < m_assignment.size(); ++i) { watch const& w1 = m_watches[2 * i]; watch const& w2 = m_watches[2 * i + 1]; if (!w1.empty()) { out << i << " |-> "; for (unsigned i = 0; i < w1.size(); ++i) out << *(m_watched_clauses[w1[i]].m_clause) << " "; out << "\n"; } if (!w2.empty()) { out << "-" << i << " |-> "; for (unsigned i = 0; i < w2.size(); ++i) out << *(m_watched_clauses[w2[i]].m_clause) << " "; out << "\n"; } } } lbool drat::value(literal l) const { lbool val = m_assignment.get(l.var(), l_undef); return val == l_undef || !l.sign() ? val : ~val; } void drat::assign(literal l, clause* c) { lbool new_value = l.sign() ? l_false : l_true; lbool old_value = value(l); // TRACE("sat_drat", tout << "assign " << l << " := " << new_value << " from " << old_value << "\n";); switch (old_value) { case l_false: m_inconsistent = true; break; case l_true: break; case l_undef: m_assignment.setx(l.var(), new_value, l_undef); m_units.push_back({l, c}); break; } } void drat::assign_propagate(literal l, clause* c) { if (!m_check_unsat) return; unsigned num_units = m_units.size(); assign(l, c); for (unsigned i = num_units; !m_inconsistent && i < m_units.size(); ++i) propagate(m_units[i].first); } void drat::propagate(literal l) { watch& clauses = m_watches[l.index()]; watch::iterator it = clauses.begin(); watch::iterator it2 = it; watch::iterator end = clauses.end(); for (; it != end; ++it) { unsigned idx = *it; watched_clause& wc = m_watched_clauses[idx]; clause& c = *wc.m_clause; //TRACE("sat_drat", tout << "Propagate " << l << " " << c << " watch: " << wc.m_l1 << " " << wc.m_l2 << "\n";); if (wc.m_l1 == ~l) { std::swap(wc.m_l1, wc.m_l2); } SASSERT(wc.m_l2 == ~l); if (value(wc.m_l1) == l_true) { *it2 = *it; it2++; } else { bool done = false; for (unsigned i = 0; !done && i < c.size(); ++i) { literal lit = c[i]; if (lit != wc.m_l1 && lit != wc.m_l2 && value(lit) != l_false) { wc.m_l2 = lit; m_watches[(~lit).index()].push_back(idx); done = true; } } if (done) { continue; } else if (value(wc.m_l1) == l_false) { m_inconsistent = true; goto end_process_watch; } else { *it2 = *it; it2++; assign(wc.m_l1, &c); } } } end_process_watch: for (; it != end; ++it, ++it2) *it2 = *it; clauses.set_end(it2); } status drat::get_status(bool learned) const { if (learned || s.m_searching) return status::redundant(); return status::asserted(); } void drat::add() { ++m_stats.m_num_add; if (m_out) (*m_out) << "0\n"; if (m_bout) bdump(0, nullptr, status::redundant()); if (m_check_unsat) { verify(0, nullptr); SASSERT(m_inconsistent); } 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; status st = get_status(learned); if (m_out) dump(1, &l, st); if (m_bout) bdump(1, &l, st); if (m_check) append(l, st); 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()) ++m_stats.m_num_del; else ++m_stats.m_num_add; literal ls[2] = { l1, l2 }; if (m_out) dump(2, ls, st); if (m_bout) bdump(2, ls, st); if (m_check) append(l1, l2, st); if (m_clause_eh) m_clause_eh->on_clause(2, ls, st); } void drat::add(clause& c, status st) { if (st.is_deleted()) ++m_stats.m_num_del; else ++m_stats.m_num_add; 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_clause_eh) m_clause_eh->on_clause(c.size(), c.begin(), st); } void drat::add(literal_vector const& lits, status st) { add(lits.size(), lits.data(), st); } void drat::add(unsigned sz, literal const* lits, status st) { if (st.is_deleted()) ++m_stats.m_num_del; else ++m_stats.m_num_add; if (m_check) { switch (sz) { case 0: if (st.is_input()) m_inconsistent = true; else add(); break; case 1: append(lits[0], st); break; default: append(mk_clause(sz, lits, st.is_redundant()), st); break; } } if (m_out) dump(sz, lits, st); if (m_clause_eh) m_clause_eh->on_clause(sz, lits, st); } void drat::add(literal_vector const& c) { ++m_stats.m_num_add; if (m_out) dump(c.size(), c.begin(), status::redundant()); if (m_bout) bdump(c.size(), c.begin(), status::redundant()); if (m_check) { for (literal lit : c) declare(lit); switch (c.size()) { case 0: add(); break; case 1: append(c[0], status::redundant()); break; default: { verify(c.size(), c.begin()); append(mk_clause(c.size(), c.data(), true), status::redundant()); break; } } } if (m_clause_eh) m_clause_eh->on_clause(c.size(), c.data(), status::redundant()); } void drat::del(literal l) { ++m_stats.m_num_del; 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) { ++m_stats.m_num_del; literal ls[2] = { l1, l2 }; 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) { #if 0 // check_duplicates: for (literal lit : c) { VERIFY(!m_seen[lit.index()]); m_seen[lit.index()] = true; } for (literal lit : c) { m_seen[lit.index()] = false; } #endif ++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_clause_eh) m_clause_eh->on_clause(c.size(), c.begin(), status::deleted()); } clause& drat::mk_clause(clause& c) { return mk_clause(c.size(), c.begin(), c.is_learned()); } clause& drat::mk_clause(unsigned n, literal const* lits, bool is_learned) { return *m_alloc.mk_clause(n, lits, is_learned); } void drat::del(literal_vector const& c) { ++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.size(), c.begin(), true), status::deleted()); if (m_clause_eh) m_clause_eh->on_clause(c.size(), c.begin(), status::deleted()); } void drat::check_model(model const& m) { } void drat::collect_statistics(statistics& st) const { st.update("num-drup", m_stats.m_num_drup); st.update("num-drat", m_stats.m_num_drat); st.update("num-add", m_stats.m_num_add); st.update("num-del", m_stats.m_num_del); } std::ostream& operator<<(std::ostream& out, sat::status const& st) { std::function th = [&](int id) { return symbol(id); }; return out << sat::status_pp(st, th); } std::ostream& operator<<(std::ostream& out, sat::status_pp const& p) { auto st = p.st; if (st.is_deleted()) out << "d"; else if (st.is_input()) out << "i"; else if (st.is_asserted()) out << "a"; else if (st.is_redundant() && !st.is_sat()) out << "r"; if (!st.is_sat()) out << " " << p.th(st.get_th()); return out; } }