% Author: Martin Pilat
% Date: 4. 4. 2017
% Update: 17. 3. 2020

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%			7. cviceni -- =.., SAT solver
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

% Pohrajme si na rozehrati jeste jednou se stromy. Napisme proceduru, ktera
% vrati seznam, ve kterem bude binarni strom ulozeny po vrstvach.

% vrstvy(+T, -S) :- S je seznam, ktery vznikl ze stromu T tak, ze do nej byly 
%				    prvky dane po vrstvach
vrstvy(T, S):-vrstvy2([T|A]-A, S).
vrstvy2([A]-A,[]):-!.   %Rez je potreba, abysme nasli reseni jen jednou
vrstvy2([nil|A]-B, S):-vrstvy2(A-B,S).
vrstvy2([t(L,U,P)|A]-B, [U|S]):-B=[L,P|C],vrstvy2(A-C, S).

% V Prologu uz umime skoro vsechno. Zbylo nam jen par veci, se kterymi si
% pohrajeme dnes. Prvni z veci je operator `=..`. O tom jsme se sice jiz
% strucne zminili, ale poradne jsme se mu zatim nevenovali, protoze jsme to
% nestihli.

% Operator `=..` Term na leve strane prevadi na seznam, pripadne seznam na
% prave strane prevadi na term. Prvni polozka seznamu je vzdy funktor, na
% dalsich mistech potom jsou jednotlive parametry. Napr. na dotaz
%  `?- T =.. [f, x, y]`, dostanete odpoved `T = f(x,y)`, a naopak, na dotaz
% `?- f(x,y) =.. S`, dostanete odpoved `S = [f,x,y]`.

% Zkusme hned tento operator vyuzit a napsat substituci promennych v termech.
% Tedy, kdyz zadame na vstupu term `f(x,y)`, a chceme substituovat `z` za `x`,
% tak dostaneme `f(z,y)`. Zacneme napred s jednoduchymi termy, vnorene udelame
% pozdeji.

% substituceJTerm(+Term, +PromennaVTermu, +PromennaNova, -VysledkyTerm) :-
% 				VyslednyTerm je Term, ve kterem promenna PromennaVTermu byla nahrazena
%				promennou PromennaNova	
substituceJTerm(T, P, N, X) :- T =.. [F|V], substituceSeznam(V, P, N, X1), X =..[F|X1].

% Potrebujeme substituci v seznamech, kterou jsme psali na jednech z drivejsich
% cviceni.

%substituceSeznam(+S, +P, +N, -X) :- seznam X vznikne ze seznamu S nahrazenim vsech vyskytu
                                    P promennou N.
substituceSeznam([], _, _, []).
substituceSeznam([P|Ss], P, N, [N|S1]):-!,substituceSeznam(Ss,P,N,S1).
substituceSeznam([S|Ss], P, N, [S|S1]):-substituceSeznam(Ss,P,N,S1).

% Zkusme ted napsat substituci pro slozene termy.

% substituceTerm(+T, +P, +N, -X) :- term X vznikne z termu T tak, ze se kazdy vyskyt
%									P nahradi N
substituceTerm(T,P,N,X):-T =.. [F|S], substituceTerm2(S,P,N,X1), X =.. [F|X1]. %Odebereme funktor a zpracujeme zbytek parametru
substituceTerm2([],_,_,[]).	
substituceTerm2([T|Ts],P,N,[T1|X]):-T =.. S, S = [P], !, T1 =.. [N], substituceTerm2(Ts,P,N,X). %Parametr je jen jeden znak a je to ten, ktery chceme nahradit.
substituceTerm2([T|Ts],P,N,[T1|X]):-T =.. S, S = [_], !, T1 = T, substituceTerm2(Ts,P,N,X). %Parametr je jen jeden znak a neni to ten, ktery chceme nahradit.
substituceTerm2([T|Ts],P,N,[T1|Ts]):-substituceTerm(T,P,N,T1). %Parametr je vnoreny term.

% Na zaver jeste zkusime napsat SAT solver. Vstupem bude formule v CNF zadana 
% jako seznam klauzuli. Promenne jsou oznacene prirozenymi cisly, negovane 
% literaly jsou zaporna cisla (takze komplementarni literal k A je -A). Napr. 
% formule `((A nebo B nebo C) and (not A nebo B))` je zapsana jako 
% `[[1,2,3],[-1,2]]`. Cilem je napsat proceduru `sat/2`, ktera vraci vsechna
% ohodnoceni, ve kterych plati zadana vstupni formule, ohodnoceni je seznam
% dvojic `P-E`, kde `P` je promenna a `E` je jeji hodnota `(0, 1)`. Priklad
% vstupu a vystupu je nize.

% ?- sat([[1,2,3],[-2,4],[3]], A).
% A = [1-1, 2-1, 3-1, 4-1] ;
% A = [1-1, 2-0, 3-1, 4-1] ;
% A = [1-1, 2-0, 3-1, 4-0] ;
% A = [1-0, 2-1, 3-1, 4-1] ;
% A = [1-0, 2-0, 3-1, 4-1] ;
% A = [1-0, 2-0, 3-1, 4-0] ;
% false.

% sat(+F, -E):-E je ohodnoceni, ve kterem plati fromule F
sat(F, E):-sat(F, [], E).
sat([], E, E).
sat([C|F], A, E):-splnKlauzuli(C, A, E1), sat(F, E1, E).

% splnKlauzuli(C, A, E):-E je rozsireni ohodnoceni A takove, ze v nem plati klauzule C
splnKlauzuli(C, A, E):-ohodnotPromenne(C, A, E1), append(A, E1, E), platna(C, E).

% platna(C, E):-klauzule C je platna pri ohodnoceni E
platna([L|_], E):-L<0, Lc is -L, member(Lc-0, E), !.
platna([L|_], E):-L>0, member(L-1, E), !.
platna([_|Ls], E):-platna(Ls, E).

% ohodnotPromenne(C, A, E):-E je libovolne ohodnoceni promennych, ktere jeste nejsou 
%							ohodnocene v A
ohodnotPromenne([], _, []).
ohodnotPromenne([L | Ls], A, E):-L > 0, member(L-_, A), !, ohodnotPromenne(Ls, A, E).
ohodnotPromenne([L | Ls], A, E):-L < 0, Lc is -L, member(Lc-_, A), !, ohodnotPromenne(Ls, A, E).

ohodnotPromenne([L | Ls], A, [L-1|E1]):-L > 0, \+member(L-0, A), ohodnotPromenne(Ls, [L-1|A], E1).
ohodnotPromenne([L | Ls], A, [L-0|E1]):-L > 0, \+member(L-1, A), ohodnotPromenne(Ls, [L-0|A], E1).
ohodnotPromenne([L | Ls], A, [Lc-0|E1]):-L < 0, Lc is -L, \+member(Lc-1, A), ohodnotPromenne(Ls, [Lc-0|A], E1).
ohodnotPromenne([L | Ls], A, [Lc-1|E1]):-L < 0, Lc is -L, \+member(Lc-0, A), ohodnotPromenne(Ls, [Lc-1|A], E1).