Modern SAT solvers use conflict-driven clause learning (CDCL): when a guess leads to a contradiction, they learn a new clause that rules the guess out. This example replays one such episode on a three-clause formula as relations over immutable terms, and proves that the final assignment satisfies every clause.
Run it in the playground · Source
The formula has three clauses:
c1: not a, or cc2: not cc3: a, or bGuessing a = true forces c = true through c1, which contradicts c2.
What does the solver learn, and what assignment follows?
sat_clause(c1, [neg(a), pos(c)]).
sat_clause(c2, [neg(c)]).
sat_clause(c3, [pos(a), pos(b)]).
initial_value(a, true, decision(level1)).
initial_value(c, true, implied_by(c1)).
A clause is a list of literals. The trail records each assignment with its reason: a decision, or an implication by a clause.
conflict(Trail, Clause) :-
sat_clause(Clause, Literals),
\+ nonfalse_literal(Trail, Literals).
nonfalse_literal(Trail, Literals) :-
member(Literal, Literals),
\+ lit_false(Literal, Trail).
A clause is in conflict when none of its literals can still be true. The
learned clause not(a), obtained by resolving c2 with c1 on c, and the
final trail are stated as facts; the rules check that they are consistent.
eyeprolog examples/cdcl-sat-solver.pl
cdclAnswer(conflict_clause, c2).
cdclAnswer(learned_clause, [neg(a)]).
cdclAnswer(learned_from, resolve(c2, c1, pivot(c))).
cdclAnswer(final_value(a), false).
cdclAnswer(final_value(c), false).
cdclAnswer(final_value(b), true).
cdclAnswer(note, "one learned clause makes the final model satisfy all original clauses").
After learning not(a), the solver sets a false, c2 forces c false, and
c3 forces b true.
step(conflict(initial, c2),
rule(12),
['Trail' = initial, 'Clause' = c2, 'Literals' = [neg(c)]],
[sat_clause(c2, [neg(c)]), \+ nonfalse_literal(initial, [neg(c)])]).
step(\+ nonfalse_literal(initial, [neg(c)]), absent, [], []).
c2 is the conflict because its only literal, not c, is false on the
initial trail: the search for a literal that is not false came up empty.
condition('C1', resolution, ok, 15).
condition('C4', coverage, ok, 20).
obligation(absent, theory_scoped, \+ nonfalse_literal(initial, [neg(c)])).
obligation(absent, theory_scoped, \+ unsatisfied_final_clause).
verdict(checked_with_obligations).
The rule steps are replayed. The two negations, “no literal of c2 survives”
and “no clause is left unsatisfied”, are obligations: a checker cannot
prove an absence from the proof alone, so it states what the verdict rests on.
c3 is no
longer satisfied.c4: not(b). Is the formula still satisfiable?Learn more in The Art of EyeProlog, Chapters 7 and 24.