%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % File: % dpll.pl % % Description: % Implementation of DPLL algorithm described in Russell and Norvig % AIMA, 2nd edition on page 222. This implementation takes advantage % of the natural depth first search available in Prolog. % % Author: % Craig A. Struble % % Last Modified: % Wed Apr 28 08:37:37 CDT 2004 % % Changes: % Wed Apr 28, 2004: % - Bug fixes. The implementation used to introduce new anonymous % variables when building new models. While logically this is % OK, it makes application of the algorithm difficult. Only the % first model found is now returned. % - Speed improvements. Clauses which are true after extending % the model are removed. % - Symbol extraction. It is no longer necessary to include a list % of symbols when calling the DPLL predicate. % - Simplified DPPL interface. A new 2-arity version of DPLL is % provided. % % Notes: % - This code relies on having a flatten predicate implemented. This is % in YAP version 4.4.x, but appears to be absent in earlier versions. % - This code relies on an association list library. One is available % with YAP. % - This implementation is still very slow. Improvements in the % implementation are welcome. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% :- use_module(library(lists)). :- use_module(library(assoc)). % Execute DPLL algorithm with clauses in C, symbols in S, and % model in M. Clauses are expected to be from a CNF propositional % sentence. We represent clauses as a list of lists, like % % [ [x1, x2], [x3, neg(x4)] ] % % which would represent the CNF sentence % % (x1 V x2) ^ (x3 V ~x4) % % where V is OR, ^ is AND, and ~ is NOT. % % To use the algorithm, execute the Prolog query: % % dpll([ [x1, x2], [x3, neg(x4)] ], FM). % % The first parameter is the list of clauses, and FM is a variable % which will be unified with a model of the CNF sentence, if it % is satisfiable. % The main predicate to call. dpll(C, FM) :- flatten(C, CF), symbols(CF, S), empty_assoc(X), dpll(C, S, X, FM). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % Below, M is the partial model found so far. Initially, it should be % an empty association list. It may be possible to call the 4-arity % version with a partial model constructed, but this has not been % tested. % If some clause is false in the model, then we're done by failure. dpll(C, S, M, FM) :- % evaluate the clauses in C with M. contains_false_clause(C, M), !, fail. % If every clause is true, then we're done. dpll(C, S, M, FM) :- % evaluate the clauses in C with M. all_clauses_true(C, M), assoc_to_list(M, FM), !. % Work with a pure symbol dpll(C, S, M, FM) :- find_pure_symbol(C, S, M, P, V), extend(P, V, M, M1), remove_true_clauses(C, M1, [], C1), delete(S, P, S1), !, dpll(C1, S1, M1, FM). % Work with a unit clause dpll(C, S, M, FM) :- find_unit_clause(C, S, M, P, V), extend(P, V, M, M1), remove_true_clauses(C, M1, [], C1), delete(S, P, S1), !, dpll(C1, S1, M1, FM). % Default cases dpll(C, [P | R], M, FM) :- extend(P, t, M, M1), remove_true_clauses(C, M1, [], C1), dpll(C1, R, M1, FM). dpll(C, [P | R], M, FM) :- extend(P, f, M, M1), remove_true_clauses(C, M1, [], C1), dpll(C1, R, M1, FM). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % Models are association lists. Extending the model is accomplished by % adding the (key, value) pair to the association list. See the YAP % documentation for more details. extend(P, V, M, M1) :- ground(P), put_assoc(P, M, V, M1). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % Determine if all clauses are true. all_clauses_true([], M). all_clauses_true([H | T], M) :- is_true(H, M), all_clauses_true(T, M). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % Is a clause C true in model M. is_true([], M) :- !, fail. % Current symbol in clause is negated, so check for false value is_true([neg(H) | T], M) :- get_assoc(H, M, f), !. % Current symbol is not negated, so check for true value is_true([H | T], M) :- get_assoc(H, M, t), !. % Neither of the previous cases passed, so check the rest of the clause. is_true([H | T], M) :- is_true(T, M). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % Some clause is false in model M. contains_false_clause([], M) :- !, fail. contains_false_clause([H | T], M) :- is_false(H, M). contains_false_clause([H | T], M) :- contains_false_clause(T, M). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % If a clause is false, then every symbol is false and every negated symbol % is true. is_false([H | T], M) :- get_assoc(H, M, f), is_false(T, M). is_false([neg(H) | T], M) :- get_assoc(H, M, t), is_false(T, M). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % A pure symbol is one that is positive in all clauses or negated in all % clauses. Can we find one? % % NOTE: This could be improved by using the partial model M with % unit propogation. find_pure_symbol(C, S, M, P, V) :- remove_true_clauses(C, M, [], C1), find_pure_symbol_nt(C1, S, M, P, V). % For non-true clauses find_pure_symbol_nt(_, [], _, _, _) :- !, fail. find_pure_symbol_nt(C, [H | T], M, H, t) :- flatten(C, CF), member(H, CF), not(member(neg(H), CF)). find_pure_symbol_nt(C, [H | T], M, H, f) :- flatten(C, CF), member(neg(H), CF), not(member(H, CF)). find_pure_symbol_nt(C, [H | T], M, P, V) :- find_pure_symbol_nt(C, T, M, P, V). % Remove true clauses remove_true_clauses([], _, S, S). remove_true_clauses([H | T], M, S, C1) :- is_true(H, M), remove_true_clauses(T, M, S, C1). remove_true_clauses([H | T], M, S, C1) :- \+(is_true(H, M)), remove_true_clauses(T, M, [H | S], C1). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % A unit clause is one of length 1. find_unit_clause([], _, _, _, _) :- !, fail. find_unit_clause([H | T], S, M, P, f) :- length(H, 1), [neg(P) | _] = C. find_unit_clause([H | T], S, M, P, t) :- length(H, 1), [P | _] = C, neg(_) \= P. find_unit_clause([H | T], S, M, P, V) :- find_unit_clause(T, S, M, P, V). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % Extract a list of propositional symbols S used in C. symbols(C, S) :- symbols1(C, [], S). symbols1([], S, S). symbols1([H | T], L, S) :- H \= neg(X), \+(member(H, L)), !, symbols1(T, [H | L], S). symbols1([neg(H) | T], L, S) :- \+(member(H, L)), !, symbols1(T, [H | L], S). symbols1([H | T], L, S) :- !, symbols1(T, L, S).