equiv_receipt.rup
Forward DRAT checking — standard library only.
A DRAT refutation is a sequence of clause additions and deletions. An addition is sound if the added clause is reverse unit propagation (RUP): assuming the negation of every literal in the clause and propagating over the currently active clause set yields a conflict. Failing that, it may still be a resolution asymmetric tautology (RAT) on its first literal, which is what solvers emit when they eliminate variables.
Checking that is a few hundred lines. Producing it is what a SAT solver is for. That asymmetry is the whole reason this file exists: anyone can check, nobody has to trust.
Two implementations, on purpose. bcp() is the naive one: a few dozen
lines, obviously correct, re-scanning every clause until nothing changes. It is
the specification. ClauseDB is the fast one, using two watched literals
per clause and an index for deletion, and it is what forward_rup_check()
actually runs. They are checked against each other on random instances, so the
fast path has an executable oracle rather than a promise.
Unit propagation. Returns (conflict, assignment).
conflict is True when propagation derives a contradiction.
A DRAT text that cannot be parsed. Raised only by strict callers.
Parse DRAT into ("a"|"d", clause) steps. Comment lines (c) are skipped.
Raises MalformedProof on unparseable input. Callers that must not
crash should use forward_rup_check(), which converts this into a
rejection — an unreadable proof proves nothing, which is a verdict, not an
error condition.
A clause store with two watched literals per clause.
The naive checker re-scans every active clause on every propagation round, and the active set grows by one on every lemma — so the cost of checking a proof grows worse than the product of its two dimensions. Watching two literals per clause means a clause is only visited when one of the two literals it is watching becomes false, which is what makes long proofs checkable.
The watch invariant survives between calls because every call propagates from an empty assignment and undoes nothing: there is no backtracking to invalidate.
Mark one clause with exactly these literals deleted. False if absent.
Deletion is lazy: the index entry is dropped and the clause is added to a deleted set that propagation skips. Removing it from its two watch lists eagerly would cost more than skipping it.
Unit propagation from assumed. Returns (conflict, assignment).
Semantics are identical to bcp(); only the cost differs.
Truth is tracked as a set of true literals rather than a variable-to-bool
map. "Is this literal true" becomes one set lookup and "is it false"
becomes one on its negation, which removes an abs() and a second
lookup from the innermost loop — measurably the hottest path in the file.
Verify a DRAT refutation by forward RUP checking.
Returns a dict with verified, n_lemmas, and — on failure — the
failed_lemma and its zero-based failed_index so the defect is
locatable rather than merely reported.
A refutation is accepted when an empty clause is derived, or when the final active set propagates to a conflict with no assumptions.