Logic Programming – Part 2 Lists Backtracking Optimization (via the cut operator) Meta-Circular Interpreters
Lists – Basic Examples [] – The empty list [X,2,f(Y)] – A 3 element list [X|Xs] – A list starting with X. Xs is a list as well. Example - [3,5] may be written as [3|5|[]]
Lists – CFG with Prolog Question: Which sentences can be constructed using this grammar? s -> np vp np -> det n vp -> v np | v det -> a | the n -> woman | man v -> shoots
Lists – CFG with Prolog Lets make relations out of it: s(Z) :- np(X), vp(Y), append(X,Y,Z). np(Z) :- det(X), n(Y), append(X,Y,Z). vp(Z) :- v(X), np(Y), append(X,Y,Z). vp(Z) :- v(Z). det([the]). det([a]). n([woman]). n([man]). v([shoots]). s -> np vp np -> det n vp -> v np | v det -> a | the n -> woman | man v -> shoots
Lists – CFG with Prolog We can ask simple queries like: Prolog generates entire sentences! s([a,woman,shoots,a,man]). true ?-s(X). X = [a,woman,shoots,a,woman] ; X = [a,woman,shoots,a,man] ; X = [a,woman,shoots,the,woman] ; X = [a,woman,shoots,the,man] ; X = [a,woman,shoots] … ?-s(X). X = [a,woman,shoots,a,woman] ; X = [a,woman,shoots,a,man] ; X = [a,woman,shoots,the,woman] ; X = [a,woman,shoots,the,man] ; X = [a,woman,shoots] … ?-s([the,man|X]). X = [shoots,a,woman] ; X = [shoots,a,man] ; X = [shoots,the,woman] … ?-s([the,man|X]). X = [shoots,a,woman] ; X = [shoots,a,man] ; X = [shoots,the,woman] …
Lists – CFG with Prolog Question: Add a few rules to the grammar What should we change in the code? Answer: we add the following code s -> np vp np -> det n | det adj n vp -> v np | v det -> a | the n -> woman | man v -> shoots adj -> vicious | marvelous np(Z) :- det(X), adj(W), n(Y), append([X,W,Y],Z). adj([vicious]). adj([marvelous]). np(Z) :- det(X), adj(W), n(Y), append([X,W,Y],Z). adj([vicious]). adj([marvelous]).
In this example we’ll work with dates We assume, for simplicity that a date comprises of a week day and an hour We define the possible week days and hours with lists: week_day(['Sun', 'Mon', 'Tue','Wed','Thu','Fri','Sat']). hour([0,1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23]). Lists – The date Relation
Q: How can we tell if hour 2 is before 9? A: 1.A < relation isn’t really possible to implement. (More details in the PS document) 2.We can only do so by checking precedence in the lists above. week_day(['Sun', 'Mon', 'Tue','Wed','Thu','Fri','Sat']). hour([0,1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23]). Lists – The date Relation
Example queries: date([H,W]) :- hour(Hour_list), member(H, Hour_list), week_day(Weekday_list), member(W, Weekday_list). dateLT( date([_,W1]), date([_,W2]) ) :- week_day(Weekday_list), precedes(W1,W2,Weekday_list). dateLT( date([H1,W]), date([H2,W]) ) :- hour(Hour_list), precedes(H1,H2,Hour_list). ?- date([1,'Sun']). true dateLT(date([5,'Mon']), date([1,'Tue'])). true ?- date([1,'Sun']). true dateLT(date([5,'Mon']), date([1,'Tue'])). true
precedes is defined using append /2 precedes(X,Y,Z) :- append( [_,[X],_,[Y],_], Z ). Lists – The date Relation Notice that the first argument is a list of lists This version of append is a strong pattern matcher
Lists – Merging date Lists Merge 2 ordered date-lists % Signature: merge(Xs, Ys, Zs)/3 % purpose: Zs is an ordered list of dates obtained % by merging the ordered lists of dates Xs and Ys. merge([X|Xs], [Y|Ys], [X|Zs]) :- dateLT(X,Y), merge(Xs, [Y|Ys],Zs). merge([X|Xs], [X|Ys], [X,X|Zs]) :- merge(Xs, Ys, Zs). merge([X|Xs],[Y|Ys],[Y|Zs]) :- dateLT(Y,X), merge( [X|Xs],Ys, Zs). merge(Xs,[ ], Xs). merge([ ],Ys, Ys). % Signature: merge(Xs, Ys, Zs)/3 % purpose: Zs is an ordered list of dates obtained % by merging the ordered lists of dates Xs and Ys. merge([X|Xs], [Y|Ys], [X|Zs]) :- dateLT(X,Y), merge(Xs, [Y|Ys],Zs). merge([X|Xs], [X|Ys], [X,X|Zs]) :- merge(Xs, Ys, Zs). merge([X|Xs],[Y|Ys],[Y|Zs]) :- dateLT(Y,X), merge( [X|Xs],Ys, Zs). merge(Xs,[ ], Xs). merge([ ],Ys, Ys). ?- merge( [date([5,'Sun']), date([5,'Mon'])], X, [date([2, 'Sun']), date([5,'Sun']), date([5, 'Mon'])]). X = [date([2, 'Sun'])] ?- merge( [date([5,'Sun']), date([5,'Mon'])], X, [date([2, 'Sun']), date([5,'Sun']), date([5, 'Mon'])]). X = [date([2, 'Sun'])]
merge([d1,d3,d5],[d2,d3],Xs) { X_1=d1,Xs_1=[d3,d5], Y_1=d2,Ys_1=[d3], Xs=[d1|Zs_1] } Rule 1 dateLT(d1,d2), merge([d3,d5], [d2,d3],Zs_1) true merge([d3,d5], [d3],Zs_2) dateLT(d2,d3), merge([d3,d5], [d3],Zs_2) Rule 2 – failure branch… Rule 1 – failure branch… { X_3=d3,Xs_3=[d5],Ys_3=[], Zs_2=[d3,d3|Zs_3] } Rule 2 Rule 1 – failure branch… merge([d5], [],Zs_3) { Xs_4=[d5], Zs_3=[d5] } Fact 4 Rule 2 – failure branch… Rule 3 – failure branch… { X_2=d3,Xs_2=[d5], Y_2=d2,Ys_2=[d3], Zs_1=[d2|Zs_2] } Rule 3
Backtracking Optimization - Cut The cut operator (denoted ‘!’) allows to prune trees from unwanted branches. A cut prunes all the goals below it A cut prunes all alternative solutions of goals to the left of it A cut does not affect the goals to it’s right The cut operator is a goal that always succeeds
Backtracking Optimization - Cut
Example - Merge with Cut In the merge example, only 1 of the 3 first rules can be true. There is no reason to try the others. Modify rule 1: merge([X|Xs],[Y|Ys], [X|Zs]) :- dateLT(X,Y), !, merge (Xs, [Y |Ys],Zs). merge([d1,d3,d5],[d2,d3],Xs) dateLT(d1,d2), !, merge([d3,d5], [d2,d3],Zs_1) !, merge([d3,d5], [d2,d3],Zs_1) Rule 2 – failure branch… Rule 3 – failure branch…
Another Example How many results does this query return? Why does this happen? The query fits both rules 4 and 5 How can we avoid this? Add cut to rule 4 ?- merge([],[],X). merge(Xs, [ ],Xs) :- !. X = []; No X = []; No
Meta-Circular Interpreter Version 1 clause finds the first rule unifying with A with body B % Signature: solve(Goal)/1 % Purpose: Goal is true if it is true when posed to the original program P. solve(true) :- !. solve( (A, B) ) :- solve(A), solve(B). solve(A) :- clause(A, B), solve(B). % Signature: solve(Goal)/1 % Purpose: Goal is true if it is true when posed to the original program P. solve(true) :- !. solve( (A, B) ) :- solve(A), solve(B). solve(A) :- clause(A, B), solve(B). ?- clause( parent(X,isaac),Body). X = abrahamBody = true ?- clause(ancestor(abraham, P),Body). P = Y, Body = parent(abraham, Y) ; P = Z, Body = parent(abraham, Y), ancestor(Y, Z) ?- clause( parent(X,isaac),Body). X = abrahamBody = true ?- clause(ancestor(abraham, P),Body). P = Y, Body = parent(abraham, Y) ; P = Z, Body = parent(abraham, Y), ancestor(Y, Z)
{A_1 = ancestor(abraham, P)} Rule 3 solve solve(ancestor(abraham, P)) clause(ancestor(abraham, P), B_1), solve(B_1) { B_1 = parent(abraham, P), X_2 = abraham, Y_2 = P } Rule 1 ancestor solve(parent(abraham, P)) {A_3 = parent(abraham, P)} Rule 3 solve {P = issac, B_3 =true} Fact 1 parent solve(parent(abraham,Y_2), ancestor(Y_2, P)) { B_1 = parent(abraham,Y_2), ancestor(Y_2, P) } Rule 2 ancestor {A_3 = parent(abraham,Y_2) B_3 = ancestor(Y_2, P} Rule 2 solve clause(parent(abraham, P), B_3), solve(B_3). solve(true) ! solve( parent(abraham,Y_2)), solve(ancestor(Y_2, P)) clause(parent(abraham, Y_2), B_4), solve(B_4) solve(ancestor(Y_2, P)) Fact 1 solve {P = issac} {A_4 = parent(abraham,Y_2)} Rule 3 solve {Y_2 = issac, B_4 =true} Fact 1 parent solve(true), solve(ancestor(issac, P)) true
Meta-Circular Interpreter Version 2 In this version we control the goal selection order by using a stack of goals Preprocessing – The given program is converted into a program with a single predicate rule containing only facts Queries are checked against the new program
Meta-Circular Interpreter Version 2 Sample converted program: % Signature: solve(Goal)/1 % Purpose: Goal is true if it is true when posed to the original program P. 1. solve(Goal) :- solve(Goal, []). % Signature: solve(Goal, Rest_of_goals)/2 1.solve([],[]). 2.solve([],[G | Goals]):- solve(G, Goals). 3.solve([A|B],Goals):- append(B, Goals, Goals1), solve(A, Goals1). 4.solve( A, Goals) :- rule(A, B), solve(B, Goals). % Signature: solve(Goal)/1 % Purpose: Goal is true if it is true when posed to the original program P. 1. solve(Goal) :- solve(Goal, []). % Signature: solve(Goal, Rest_of_goals)/2 1.solve([],[]). 2.solve([],[G | Goals]):- solve(G, Goals). 3.solve([A|B],Goals):- append(B, Goals, Goals1), solve(A, Goals1). 4.solve( A, Goals) :- rule(A, B), solve(B, Goals). %rule (Head, BodyList)/2 1. rule( member(X, [X|_] ), [] ). 2. rule( member(X, [_|Ys] ), [member(X, Ys)] ).
{ X_3=X,Ys_3=[b, c],B_2 = [member(X, [b,c])]} Rule 2 rule { A_4= member(X, [b,c]), B_4=[], Goals_4=[] } Rule 3 rule { Goals1_4=[]} Rule of append { A_5=member(X,[b,c]), Goals_5=[]} Rule 4 solve { X=b,X_6 = b,B_5 = [] } Rule 1 rule {Goal_1 = member(X, [a, b, c])} Rule 1 solve solve(member(X, [a, b, c])) solve(member(X, [a, b, c]), []) { A_2 = member(X, [a, b, c], Goals_1 = [] } Rule 4 solve rule(member(X, [a, b, c], B_2), solve(B_2, [] ) solve([],[]) { X=a,X_3 = a, Xs_3=[b, c],B_2 = [] } Rule 1 rule true {X=a} Rule 1 solve solve( [member(X, [b,c])], [] ) append([], [], Goals1_4), solve(member(X, [b,c]), Goals1_4). solve(member(X, [b,c]), []). rule(member(X,[b,c]), B_5), solve(B_5, []) solve([],[]) true {X=b} Rule 1 solve