Exctraction of the Proof from Fitting's leanTap Prolog Prover - algorithm

Here is the SWI-Prolog code of Fitting's leanTap revisited:
:- use_module(library(lists)).
:- use_module(library(statistics)).
% :- use_module(library(dom)).
% operator definitions (TPTP syntax)
:- op( 500, fy, ~). % negation
:- op(1000, xfy, &). % conjunction
:- op(1100, xfy, '|'). % disjunction
:- op(1110, xfy, =>). % conditional
:- op(1120, xfy, <=>). % biconditional
/*
Next, a classification of formula types,
& instances.
*/
type(X & Y, conj, X, Y).
type(~(X & Y), disj, ~ X, ~ Y).
type(X | Y, disj, X, Y).
type(~(X | Y), conj, ~ X, ~ Y).
type(X => Y, disj, ~ X, Y).
type(~(X => Y), conj, X, ~ Y).
type(X <=> Y, disj, X & Y, ~ X & ~ Y).
type(~(X <=> Y), disj, X & ~ Y, ~ X & Y).
type(~ (~ (X)), doub, X, _).
/*
Now the heart of the matter.
thm(Lambda, Gamma) :-
the sequent Lambda --> Gamma is provable.
*/
thm(Lambda, [Doubleneg | Gamma]) :-
type(Doubleneg, doub, X, _), !,
thm(Lambda, [X | Gamma]).
thm(Lambda, [Beta | Gamma]) :-
type(Beta, disj, Beta1, Beta2), !,
thm(Lambda, [Beta1, Beta2 | Gamma]).
thm(Lambda, [Alpha | Gamma]) :-
type(Alpha, conj, Alpha1, Alpha2), !,
thm(Lambda, [Alpha1 | Gamma]), !,
thm(Lambda, [Alpha2 | Gamma]).
thm([L1|Lambda], [L2|_]) :-
(
L1 = L2, !
;
thm(Lambda, [L2])
).
thm(Lambda, [~ L | Gamma]) :-
thm([L | Lambda], Gamma), !.
thm(Lambda, [L | Gamma]) :-
thm([~ L | Lambda], Gamma), !.
/*
Finally, the driver.
*/
prove(X) :-
time(thm([], [X])).
This code according to Fitting provides a sequent calculus. I have tried to change minimally this code to get a Prolog Print of each proof, with input prove(X, Proof), following the structure of Jen Otten's prover (online here
and here):
% -----------------------------------------------------------------
% leanseq.pl - A sequent calculus prover implemented in Prolog
% -----------------------------------------------------------------
:- use_module(library(lists)).
% operator definitions (TPTP syntax)
:- op( 500, fy, ~). % negation
:- op(1000, xfy, &). % conjunction
:- op(1100, xfy, '|'). % disjunction
:- op(1110, xfy, =>). % implication
% -----------------------------------------------------------------
provable(F, P) :- time(prove([] > [F], P)).
% -----------------------------------------------------------------
% axiom
prove(G > D, ax(G > D, A)) :- member(A,G), member(B,D), A == B, !.
% conjunction
prove(G > D, land(G > D, P) ) :- select1( (A & B) ,G,G1), !,
prove([A , B | G1] > D, P).
prove(G > D, rand(G > D, P1,P2)) :- select1( (A & B) ,D,D1), !,
prove(G > [A|D1], P1), prove(G > [B|D1], P2).
% disjunction
prove(G > D, lor(G > D, P1,P2)) :- select1((A | B),G,G1), !,
prove([A|G1] > D, P1), prove([B|G1] > D, P2).
prove(G > D, ror(G > D, P)) :- select1( (A | B),D,D1), !,
prove(G > [A,B|D1], P ).
% implication
prove(G > D, limpl(G > D, P1,P2)) :- select1((A => B),G,G1), !,
prove(G1 > [A|D], P1), prove([B|G1] > D, P2).
prove(G > D, rimpl(G > D, P)) :- select1((A => B),D,D1), !,
prove([A|G] > [B|D1], P).
% negation
prove(G > D, lneg(G > D, P)) :- select1( ~A,G,G1), !,
prove(G1 > [A|D], P).
prove(G > D, rneg(G > D, P)) :- select1(~A ,D,D1), !,
prove([A|G] > D1, P).
% -----------------------------------------------------------------
select1(X,L,L1) :- append(L2,[X|L3],L), append(L2,L3,L1).
% -----------------------------------------------------------------
For example :
provable((p => p), Proof).
% 22 inferences, 0.000 CPU in 0.000 seconds (95% CPU, 1132503 Lips)
Proof = rimpl([]>[(p=>p)], ax([p]>[p], p))
But all my tentatives to get from Fitting's prover (that is complete) a prover that provides a proof like Proof above have failed. Any help that could put me on the right track would be appreciated.

The Fitting code has some silly placement of cuts,
generating spurious choice points, and an unnecessary
recursion redoing all the pattern matching, instead of
directly using member/2. If you implement it more closely to
the original Wang McCarthy from the LISP 1.5 Manual at
page 44 ff, you get a little bit more speed:
/* Fitting */
?- time((between(1,100,_), test, fail; true)).
% 3,358,200 inferences, 0.297 CPU in 0.295 seconds (101% CPU, 11311832 Lips)
true.
/* Wang McCarthy */
?- time((between(1,100,_), test2, fail; true)).
% 2,802,900 inferences, 0.203 CPU in 0.209 seconds (97% CPU, 13798892 Lips)
true.
To arrive at Wang McCarthy replace this here from Fitting:
/* Fitting */
thm([L1|Lambda], [L2|_]) :-
(
L1 = L2, !
;
thm(Lambda, [L2])
).
thm(Lambda, [~ L | Gamma]) :-
thm([L | Lambda], Gamma), !.
thm(Lambda, [L | Gamma]) :-
thm([~ L | Lambda], Gamma), !.
By this here:
/* Wang McCarthy */
thm2(Lambda, [L|_]) :- member(L, Lambda), !.
thm2(Lambda, [~ L | Gamma]) :- !,
thm2([L | Lambda], Gamma).
thm2(Lambda, [L | Gamma]) :-
thm2([~ L | Lambda], Gamma).
As a test case I was running a collection of
principia mathematica tautologies.

The following solution works smoothly and is very fast, with label for sequent rules corresponding to Fitting's sequent calculus that Fitting calls dirseq :
:- use_module(library(lists)).
:- use_module(library(statistics)).
% :- use_module(library(dom)).
% operator definitions (TPTP syntax)
:- op( 500, fy, ~). % negation
:- op(1000, xfy, &). % conjunction
:- op(1100, xfy, '|'). % disjunction
:- op(1110, xfy, =>). % conditional
:- op(1120, xfy, <=>). % biconditional
/*
Next, a classification of formula types,
& instances.
*/
type((X & Y), conj, X, Y).
type(~((X | Y)), conj, ~ X, ~ Y).
type(~((X => Y)), conj, X, ~ Y).
type((X <=> Y), conj, (~ X | Y), (X | ~ Y)).
type(~((X <=> Y)), conj, (X | Y), (~ X | ~ Y)).
type(~ (~ (X)), doub, X, _).
type((X => Y), disj, ~ X, Y).
type(~((X & Y)), disj, ~ X, ~ Y).
type((X | Y), disj, X, Y).
/*
Now the heart of the matter.
thm(Lambda, Gamma) :-
the sequent Lambda --> Gamma is provable.
*/
thm(Lambda > [Alpha | Gamma], R) :-
type(Alpha, conj, Alpha1, Alpha2), !,
thm(Lambda > [Alpha1 | Gamma],P), !,
thm(Lambda > [Alpha2 | Gamma],Q),
R = alpha(Lambda > [Alpha | Gamma],(P & Q)).
thm(Lambda > [Beta | Gamma], R) :-
type(Beta, disj, Beta1, Beta2), !,
thm(Lambda > [Beta1, Beta2 | Gamma],P),
R = beta(Lambda > [Beta | Gamma], P).
thm(Lambda > [Doubleneg | Gamma], R) :-
type(Doubleneg, doub, X, Gamma), !,
thm(Lambda > [X | Gamma], P),
R = dn(Lambda > [Doubleneg | Gamma], P).
thm(Lambda > [L|Gamma], R) :-
member(L, Lambda), !,
R = ax(Lambda > [L|Gamma], ax).
thm(Lambda > [~ L | Gamma], R) :- !,
thm([L | Lambda] > Gamma, P),
R = duality(Lambda > [~ L | Gamma], P).
thm(Lambda > [L | Gamma], R) :-
thm([~ L | Lambda] > Gamma, P),
R = duality(Lambda > [L | Gamma], P).
/*
Finally, the driver.
*/
provable(X, R) :-
time(thm([] > [X], R)).
Many thanks for the help that I have received !

Interestingly you can easily add look-ahead (forward
checking, unit propagation) to Melvin Fittings prover.
Just take this end-phase:
/* Fitting */
thm([L1|Lambda], [L2|_]) :-
(
L1 = L2, !
;
thm(Lambda, [L2])
).
thm(Lambda, [~ L | Gamma]) :-
thm([L | Lambda], Gamma), !.
thm(Lambda, [L | Gamma]) :-
thm([~ L | Lambda], Gamma), !.
And replace it by this end-phase:
/* Fitting + fCube Simplification */
thm(_, [1 | _)] :- !.
thm(_, [0 | Gamma]) :- !,
thm2(_, Gamma).
thm2(_, [L| Gamma]) :-
opposite2(L, R),
reduce(Gamma, R, Gamma2),
thm2(_, Gamma2).
As can be seen in the above, the list Lambda
is even not anymore used. The predicate reduce/2 is
supposed to partially evaluate the list Gamma, under
the assumption that R is true. Here are some timings
for the test case SYN007+1.014.p:
/* Fitting */
% 12,779,502 inferences, 0.813 CPU in 0.826 seconds (98% CPU, 15728618 Lips)
/* Fitting + fCube Simplification */
% 1,203,958 inferences, 0.109 CPU in 0.098 seconds (112% CPU, 11007616 Lips)

Related

Is there always an accumulator to replace append/3? [SOLVED]

Can append/3 at the end of this code
provable(F, P) :- prove([] > [F], P).
% axiom
prove(G > D, ax(G > D, A)) :- member(A,G), member(B,D), A == B, !.
% implication
prove(G > D, limpl(G > D, P1,P2)) :- select1((A => B),G,G1), !,
prove(G1 > [A|D], P1), prove([B|G1] > D, P2).
prove(G > D, rimpl(G > D, P)) :- select1((A => B),D,D1), !,
prove([A|G] > [B|D1], P).
% negation
prove(G > D, lneg(G > D, P)) :- select1( ~A,G,G1), !,
prove(G1 > [A|D], P).
prove(G > D, rneg(G > D, P)) :- select1(~A ,D,D1), !,
prove([A|G] > D1, P).
% -----------------------------------------------------------------
select1(X,L,L1) :- append(L2,[X|L3],L), append(L2,L3,L1).
% -----------------------------------------------------------------
be replaced by an accumulator? (This code is a part of leanseq.pl a A sequent calculus prover implemented in Prolog, by Jens Otten, and I am surprised by the choice of append, instead of an accumulator, hence my question, because my all my tentatives to replace append failed.)

Clausify Prolog Error

I have a project in PROLOG to convert an FBF function to a CNF function, where fbf2cnf is the predicate to change the function.
When I run this instruction:
fbf2cnf(every(X, exist(Y, foo(Y, X))), F).
... it throws this error:
ERROR: =../2: Type error: `atomic' expected, found `foo(skf1(_G1758),_G1758)' (a compound)
Exception: (7) fbf2cnf(every(_G1558, exist(_G1557, foo(_G1557, _G1558))), _G1567) ?
How can I avoid throwing this exception?
The code is here:
exist(V, FBF) :-
compound(FBF),
skolem_function([], SK),
exist_private(V, SK, [FBF], [], _),
V = SK.
exist_private(V, SK, [H | []], NL, Final) :-
variabile(H),
H == V,
append([SK], NL, NL2),
Final = NL2, !.
exist_private(V, SK, [H | T], NL, Final) :-
variabile(H),
H == V,
exist_private(V, SK, T, NL, F2),
append([SK], F2, NL2),
Final = NL2, !.
exist_private(_, _, [H | []], NL, Final) :-
termine(H),
append([H], NL, NL2),
Final = NL2, !.
exist_private(_, _, [H | T], NL, Final) :-
termine(H),
exist_private(_, _, T, NL, F2),
append([H], F2, NL2),
Final = NL2, !.
exist_private(V, SK, [H | []], _, Final) :-
compound(H),
H =.. [H1 | L],
exist_private(V, SK, L, [], F2),
append([H1], F2, NH),
FH =.. NH,
append([FH], [], NL2),
Final = NL2, !.
exist_private(V, SK, [H | T], NL, Final) :-
compound(H),
H =.. [H1 | L],
exist_private(V, SK, L, [], F2),
append([H1], F2, NH),
FH =.. NH,
exist_private(V, SK, T, NL, F3),
append([FH], F3, NL2),
Final = NL2, !.
fbf2cnf(FBF, CNFFBF) :-
termine(FBF),
CNFFBF = FBF, !.
fbf2cnf(FBF, CNFFBF) :-
predicato(FBF),
FBF =.. [H | T],
H \= not,
H \= and,
H \= or,
H \= implies,
H \= exist,
H \= every,
T \=[],
valid_fbf(FBF),
CNFFBF = FBF ,!.
fbf2cnf(FBF, CNFFBF) :-
compound(FBF),
FBF =.. [H | _],
H \= exist,
valid_fbf(FBF),
fbf2cnf_private([FBF], N),
CNFFBF = N, !.
fbf2cnf(FBF, CNFFBF) :-
compound(FBF),
FBF =.. [H | T],
H = exist,
T = [V | T2],
NFBF =.. T2,
fbf2cnf(NFBF, FBF2),
FBF2 =.. L,
skolem_function([], SK),
exist_private(V, SK, L, [], F),
V = SK,
Final =.. F,
fbf2cnf(Final, CNF),
CNFFBF = CNF, !.
fbf2cnf(FBF, CNFFBF) :-
compound(FBF),
FBF =.. [H | T],
H = every,
T = [V | T2],
T2 = [H2 | _ ],
H2 =.. [Op | T3],
T3 = [H3 | T4],
Op = exist,
skolem_function([V], SF),
exist_private(H3, SF, T4, [], NL),
H3 = SF,
NFBF =.. NL,
fbf2cnf(NFBF, NL2),
CNFFBF = NL2, !.
fbf2cnf(FBF, CNFFBF) :-
compound(FBF),
FBF =.. [H | T],
H = every,
T = [_ | T2],
T2 = [H2 | _ ],
H2 =.. [Op | _],
Op \= exist,
fbf2cnf(H2, CNF),
CNFFBF = CNF, !.
/*----------------------------------------- */
fbf2cnf_private([H | _], CNFFBF) :-
termine(H),
CNFFBF = H, !.
fbf2cnf_private([H | _], CNF) :-
H =.. [H1 | T],
H1 = exist,
T \= [],
T = [_ | T2],
FBF =.. T2,
exist(V, FBF),
CNF = V.
fbf2cnf_private(L, CNF) :-
L = [H | _],
H =.. [H1 | Ls],
H1 = and,
valid_fbf(H),
and(H1, Ls, CNF1),
CNF2 =.. CNF1,
flatten_and(CNF2, NL),
append([and], NL, Temp),
Final =.. Temp,
CNF = Final, !.
fbf2cnf_private(L, CNF) :-
L = [H | _],
H =.. [H1 | Ls],
H1 = or,
valid_fbf(H),
or(H1, Ls, CNF1),
CNF2 =.. CNF1,
flatten_or(CNF2, NL),
append([or], NL, Temp),
Final =.. Temp,
CNF = Final, !.
fbf2cnf_private([H | _], CNF) :-
H =.. [H1 | _],
H1 = not,
not_counter(H, M),
CNF = M.
fbf2cnf_private([H | _], CNF) :-
H =.. [H1 | T],
H1 = implies,
implies(T, M),
fbf2cnf(M, M1),
CNF = M1, !.

Select the smallest value from all the repeated elements of a list

An example will explain better what I'm trying to do.
For example, I have this prolog list:
L=[(d,15),(e,16),(g,23),(e,14),(h,23),(d,19)]
And I want to generate this list:
L'=[(d,15),(g,23),(e,14),(h,23)]
This is, from all occurrences of element (X,_), leave the one with the smallest Y.
Not really elegant but... what about the following code?
getFirst((X, _), X).
isMinor(_, []).
isMinor((X1, Y1), [(X2, _) | T]) :-
X1 \= X2,
isMinor((X1, Y1), T).
isMinor((X, Y1), [(X, Y2) | T]) :-
Y1 =< Y2,
isMinor((X, Y1), T).
purgeList(_, [], []).
purgeList(X1, [(X2, Y2) | Tin], [(X2, Y2) | Tout]) :-
X1 \= X2,
purgeList(X1, Tin, Tout).
purgeList(X, [(X, _) | Tin], Tout) :-
purgeList(X, Tin, Tout).
filterList([], []).
filterList([H1 | Tin1], [H1 | Tout]) :-
isMinor(H1, Tin1),
getFirst(H1, X),
purgeList(X, Tin1, Tin2),
filterList(Tin2, Tout).
filterList([H1 | Tin], Tout) :-
\+ isMinor(H1, Tin),
filterList(Tin, Tout).
From
filterList([(d,15),(e,16),(g,23),(e,14),(h,23),(d,19)], L)
I obtain (L is unified with)
[(d,15),(g,23),(e,14),(h,23)]
You could also write:
select_elements(L,Lout):-
sort(L,L1),
reverse(L1,L2),
remove(L2,L3),
output_list(L,L3,Lout).
remove([],[]).
remove([H],[H]).
remove([(X,Y1),(X,Y2)|T],[(X,Y1)|T1]):-remove([(X,Y2)|T],T1).
remove([(X1,Y1),(X2,Y2)|T],[(X1,Y1)|T1]):-
dif(X1,X2),\+member((X2,_),T),
remove([(X2,Y2)|T],T1).
remove([(X1,Y1),(X2,_)|T],[(X1,Y1)|T1]):-
dif(X1,X2),member((X2,_),T),
remove(T,T1).
output_list([],_,[]).
output_list([H|T],L,[H|T1]):-member(H,L),output_list(T,L,T1).
output_list([H|T],L,T1):- \+member(H,L),output_list(T,L,T1).
Example:
?- select_elements([(d,15),(e,16),(g,23),(e,14),(h,23),(d,19)],L).
L = [ (d, 15), (g, 23), (e, 14), (h, 23)] ;
false.
You can try
test((V, N), Y, Z) :-
( member((V,N1), Y)
-> ( N < N1
-> select((V,N1), Y, (V,N), Z)
; Z = Y)
; append(Y, [(V,N)], Z)).
my_select(In, Out) :-
foldl(test, In, [], Out).
For example
?- my_select([(d,15),(e,16),(g,23),(e,14),(h,23),(d,19)], Out).
Out = [(d,15),(e,14),(g,23),(h,23)] ;
false.

Matching in SICStus prolog

This is my code, which is for Satchmo theorem proving. It does some unification.
:- op(700, xfx, ==>).
:- op(400, yfx, &).
:- op(400, yfx, or).
fact([a, 9]).
fact([b, 9]).
rule([a, X] & [b, X] ==> [c, X]). %% horn bit
rule([c, X] ==> [r, X] or [s, X]). %% non horn bit
rule([r, X] ==> [t, X]).
rule([s, X] ==> [t, X]).
horn(A & B) :-
!,
horn(A),
horn(B).
horn(A or B) :-
!,
(horn(A); horn(B)).
horn(P) :-
fact(P).
horn(P) :-
temp(P).
horn(P) :-
rule(SUBGOALS ==> P),
\+ P = (_A or _B),
horn(SUBGOALS).
satchmo(P) :-
retractall(temp(_)),
prove(P).
prove(P) :-
horn(P).
prove(P) :-
rule(LHS ==> (A or B)),
horn(LHS),
\+ horn(A or B),
cprove(A ==> P),
cprove(B ==> P).
cprove(A ==> P) :-
try(A),
(prove(P) ->
untry(A);
(untry(A), fail)).
try(A & B) :-
!,
try(A),
try(B).
try(A) :-
assert(temp(A)).
untry(A & B) :-
!,
untry(A),
untry(B).
untry(A) :-
retract(temp(A)).
To understand how is it working, we can do that by ?- spy([satchmo]).
1- if the given query is fact as:
?- satchmo([a, 9]).
yes.
or
?- satchmo([b, 9]).
yes.
the program will prove it as it is a fact.
2- If the query in the horn bit as:
?- satchmo([c, 9]).
yes.
the program will prove it as it is the horn rule.
3- If the query in the non-horn bit as:
?- satchmo([t, 9]).
yes.
It'll be proved as well.
This is working perfectly. But when I have tried to change it a bit. Instead of unification I need to do another kind of matching which can prove the following:
if I have:
rule[living, X] ==> [mortal, X].
[man, socrates].
I would like to prove:
?- satchmo([mortal, socrates]).
yes.
To do this I have amend my code a bit, so instead of having:
horn(P):-
fact(P).
I put something very similar:
horn(P):-
match(P, P0),
fact(P0).
and I have defined match as:
match(X, Y):-
X=Y.
I know that I have not do anything by this move, but I am thinking to amend the definition of match a bit to be able to prove what I need.
I am stuck somewhere here, look at my current code.
:- op(700, xfx, ==>).
:- op(400, yfx, &).
:- op(400, yfx, or).
:- op(400, yfx, <<<).
fact([a, 9]).
fact([b, 9]).
rule([a, X] & [b, X] ==> [c, X]). %% horn bit
rule([c, X] ==> [r, X] or [s, X]). %% non horn bit
rule([r, X] ==> [t, X]).
rule([s, X] ==> [t, X]).
man <<< human.
human <<< animal.
animal <<< living.
subset(X, X).
subset(X, Y) :-
X <<< Y.
subset(X, Z) :-
X <<< Y,
subset(Y, Z).
horn(A & B) :-
!,
horn(A),
horn(B).
horn(A or B) :-
!,
(horn(A); horn(B)).
horn(P) :-
fact(P).
horn(P) :-
temp(P).
horn(P) :-
rule(SUBGOALS ==> P),
\+ P = (_A or _B),
horn(SUBGOALS).
satchmo(P) :-
retractall(temp(_)),
prove(P).
prove(P) :-
horn(P).
prove(P) :-
rule(LHS ==> (A or B)),
horn(LHS),
\+ horn(A or B),
cprove(A ==> P),
cprove(B ==> P).
cprove(A ==> P) :-
try(A),
(prove(P) ->
untry(A);
(untry(A), fail)).
try(A & B) :-
!,
try(A),
try(B).
try(A) :-
assert(temp(A)).
untry(A & B) :-
!,
untry(A),
untry(B).
untry(A) :-
retract(temp(A)).
Here, we can test the subset:
?- subset(human, man).
yes.
The problem here, is I do not know how to achieve my goal by proving:
?- satchmo([mortal, socrates]).
yes.
from:
[living, X] ==> [mortal, X].
[man, socrates].
Could it be done by changing the definition of match? If not, is there any other method to do it?
Ok, done,
The solution of this problem is simply by changing the definition of match, so it will be as follows:
match(P, P0) :-
P = [X, _],
P0 = [Y, _],
subset(Y, X).
subset(X, X).
subset(X, Y) :-
X <<< Y.
subset(X, Z) :-
X <<< Y,
subset(Y, Z).
and when I have this facts:
fact([man, socrates]).
man <<< human.
human <<< animal.
animal <<< living.
and this rule:
rule([living, X] ==> [mortal, X]).
I can prove:
?- satchmo([mortal, socrates]).

How to convert a propositional formula to disjunctive normal form (DNF) in Prolog?

I'm new at Prolog, and i need to convert from the truth table the result into disjunctive normal form.
I have been able to produce the truth table as given:
?- table(p or(q and not r) or not s or r).
[p,q,r,s] | (p or (q and not r) or not s or r) ----------------------------------------------|[0,0,0,0] | 1 |[0,0,0,1] | 0 |[0,0,1,0] | 1 |[0,0,1,1] | 1 |[0,1,0,0] | 1 |[0,1,0,1] | 1 |[0,1,1,0] | 1 |[0,1,1,1] | 1 |[1,0,0,0] | 1 |[1,0,0,1] | 1 |[1,0,1,0] | 1 |[1,0,1,1] | 1 |[1,1,0,0] | 1 |[1,1,0,1] | 1 |[1,1,1,0] | 1 |[1,1,1,1] | 1 |-----------------------------------------------
if anyone can help me make from this table to the disjunctive normal form i would apreciate it.
let's implement a generic truth table evaluator, translating to Prolog evaluable formula in CDNF, then, by definition, we will disjoin each minterm:
:- op(900, fy, neg).
:- op(1000, xfy, and).
:- op(1100, xfy, or).
formula(p or (q and neg r) or neg s or r).
cnf(F, CNF) :-
setof(V, literal(F, V), Ls),
setof(La, T^(assign(Ls, La), translate(F, La, T), T), CNF).
literal((X or Y), L) :- literal(X,L) ; literal(Y,L).
literal((X and Y), L) :- literal(X,L) ; literal(Y,L).
literal(neg X, L) :- literal(X,L).
literal(L, L) :- atom(L).
assign(Ls, La) :- maplist(assign_literal, Ls, La).
assign_literal(L, L=true).
assign_literal(L, L=false).
translate((X or Y), Ls, (A;B)) :- translate(X, Ls, A), translate(Y, Ls, B).
translate((X and Y), Ls, (A,B)) :- translate(X, Ls, A), translate(Y, Ls, B).
translate(neg X, Ls, \+ A) :- translate(X, Ls, A).
translate(L, Ls, V) :- memberchk(L=V, Ls).
yields:
?- formula(F),cnf(F,CNF),maplist(writeln,CNF).
[p=false,q=false,r=false,s=false]
[p=false,q=false,r=true,s=false]
[p=false,q=false,r=true,s=true]
[p=false,q=true,r=false,s=false]
[p=false,q=true,r=false,s=true]
[p=false,q=true,r=true,s=false]
[p=false,q=true,r=true,s=true]
[p=true,q=false,r=false,s=false]
[p=true,q=false,r=false,s=true]
[p=true,q=false,r=true,s=false]
[p=true,q=false,r=true,s=true]
[p=true,q=true,r=false,s=false]
[p=true,q=true,r=false,s=true]
[p=true,q=true,r=true,s=false]
[p=true,q=true,r=true,s=true]
F = or(p, or(and(q, neg(r)), or(neg(s), r))),
CNF = [[p=false, q=false, r=false, s=false], [p=false, q=false, r=true, s=false], [p=false, q=false, r=true, s=true], [p=false, q=true, r=false, s=false], [p=false, q=true, r=false, ... = ...], [p=false, q=true, ... = ...|...], [p=false, ... = ...|...], [... = ...|...], [...|...]|...].
Sorry the output it's a bit verbose. Can be easily tailored on further spec.
I used neg/1 instead of not/1 (that's already a valid Prolog operator), just to make clear the distinction...
Edit
Here is a simplification, resulting in a syntactic generalization. Just literal/2 and translate/3 have changed, and translate/2 has been added:
literal(F, L) :- F =.. [_,X,Y], (literal(X,L) ; literal(Y,L)).
literal(F, L) :- F =.. [_,X], literal(X,L).
literal(L, L) :- atom(L).
translate(and, (,)).
translate(or, (;)).
translate(neg, (\+)).
translate(F, Ls, T) :-
F =.. [S,X,Y],
translate(S,O),
T =.. [O,A,B],
translate(X, Ls, A), translate(Y, Ls, B).
translate(F, Ls, T) :-
F =.. [S,X],
translate(S,O),
T =.. [O,A],
translate(X, Ls, A).
translate(F, Ls, T) :- memberchk(F=T, Ls).
More Edit
The code above can be made more efficient, just moving the translation out of the cycle
cnf(F, CNF) :-
setof(V, literal(F, V), Ls),
translate(F, La, T),
setof(La, (assign(Ls, La), T), CNF).
a minor modification is required in last translate/3 clause: use member/2 instead of memberchk
...
translate(F, Ls, T) :- member(F=T, Ls).
Timing: with the old version
4 ?- formula(F),time(cnf(F,CNF)).
% 1,788 inferences, 0.002 CPU in 0.002 seconds (98% CPU, 834727 Lips)
With the new one:
5 ?- formula(F),time(cnf(F,CNF)).
% 282 inferences, 0.001 CPU in 0.001 seconds (99% CPU, 315768 Lips)
about 6x better.
Old with memberchk:
6 ?- formula(F),time(cnf(F,CNF)).
% 1,083 inferences, 0.002 CPU in 0.002 seconds (98% CPU, 561426 Lips)
Well, still almost 4x better.
Edit Some more step is required to get a true Prolog formula
cdnf(F, CNDF, Prolog) :-
cdnf(F, CNDF), % well, was cnf/2, I renamed to be more precise
maplist(cj, CNDF, CJs),
reverse(CJs, [H|T]),
foldl(dj, T, H, Prolog).
dj(A, B, (A;B)).
cj(A, J) :-
maplist(tf, A, B),
reverse(B, [H|T]),
foldl(cj, T, H, J).
cj(A, B, (A,B)).
tf(S=true,S).
tf(S=false,\+S).
now, the result is more usable
?- formula(_,F), cdnf(F,_,P).
F = or(p, or(and(q, neg(r)), or(neg(s), r))),
P = (\+p, \+q, \+r, \+s;\+p, \+q, r, \+s;\+p, \+q, r, s;\+p, q, \+r, \+s;\+p, q, \+r, s;\+p, q, r, \+ ...;\+p, q, ..., ...;p, ..., ...;..., ...;...;...)

Resources