Lab Manual of Ai
Lab Manual of Ai
2
Write the following programs using PROLOG
SOURCE CODE:
domains
disease, indication, name = symbol
predicates
hypothesis(name,disease)
symptom(name,indication)
clauses
symptom(charlie,fever).
symptom(charlie,rash).
symptom(charlie,headache).
symptom(charlie,runny_nose).
hypothesis(Patient,measles):-
clearwindow, symptom(Patient,fever),
symptom(Patient,cough),
symptom(Patient,conjunctivities),
symptom(Patient,runny_nose),
symptom(Patient,rash).
hypothesis(Patient,german_measles):-
symptom(Patient,fever), symptom(Patient,headache), symptom(Patient,runny_nose), symptom(Patient,rash).
hypothesis(Patient,flu):-
symptom(Patient,fever),
symptom(Patient,headache),
symptom(Patient,body_ache),
symptom(Patient,conjunctivities),
symptom(Patient,chills),
symptom(Patient,sore_throat),
symptom(Patient,cough),
symptom(Patient,runny_nose).
hypothesis(Patient,common_cold):-
symptom(Patient,headache),
symptom(Patient,sneezing),
symptom(Patient,sore_throat),
symptom(Patient,chills),
symptom(Patient,runny_nose).
hypothesis(Patient,mumps):-
symptom(Patient,fever),
symptom(Patient,swollen_glands).
hypothesis(Patient,chicken_pox):-
symptom(Patient,fever),
symptom(Patient,rash),
symptom(Patient,body_ache),
symptom(Patient,chills).
hypothesis(Patient,whooping_cough):-
symptom(Patient,cough),
symptom(Patient,sneezing),
symptom(Patient,runny_nose).
OUTPUT:
Goal: hypothesis(Patient,Disease)
Patient=charlie, Disease=german_measles
1 Solution
Expt. No. 3
Map colorings
A famous problem in mathematics concerns coloring adjacent planar regions. Like cartographic
maps, it is required that, whatever colors are actually used, no two adjacent regions may not have
the same color. Two regions are considered adjacent provided they share some boundary line
segment. Consider the following map.
We have given numerical names to the regions. To represent which regions are adjacent,
consider also the following graph.
Here we have erased the original boundaries and have instead drawn an arc between the names
of two regions, provided they were adjacent in the original drawing. In fact, the adjacency graph
will convey all of the original adjacency information. A Prolog representation for the adjacency
information could be represented by the following unit clauses, or facts.
adjacent(1,2). adjacent(2,1).
adjacent(1,3). adjacent(3,1).
adjacent(1,4). adjacent(4,1).
adjacent(1,5). adjacent(5,1).
adjacent(2,3). adjacent(3,2).
adjacent(2,4). adjacent(4,2).
adjacent(3,4). adjacent(4,3).
adjacent(4,5). adjacent(5,4).
If these clauses were loaded into Prolog, we could observe the following behavior for some
goals.
?- adjacent(2,3).
yes
?- adjacent(5,3).
no
?- adjacent(3,R).
R=1;
R=2;
R=4;
Yes
One could declare colorings for the regions in Prolog also using unit clauses.
color(1,red,a). color(1,red,b).
color(2,blue,a). color(2,blue,b).
color(3,green,a). color(3,green,b).
color(4,yellow,a). color(4,blue,b).
color(5,blue,a). color(5,green,b).
Here we have encoded 'a' and 'b' colorings. We want to write a Prolog definition of a conflictive
coloring, meaning that two adjacent regions have the same color. For example, here is a Prolog
clause, or rule to that effect.
conflict(Coloring) :-
adjacent(X,Y),
color(X,Color,Coloring),
color(Y,Color,Coloring).
For example,
?- conflict(a).
no
?- conflict(b).
yes
?- conflict(Which).
Which = b
Here is another version of 'conflict' that has more logical parameters.
conflict(R1,R2,Coloring) :-
adjacent(R1,R2),
color(R1,Color,Coloring),
color(R2,Color,Coloring).
Prolog allows and distinguishes the two definitions of 'conflict'; one has one logical parameter
('conflict/1') and the other has three ('conflict/3'). Now we have
?- conflict(R1,R2,b).
R1 = 2 R2 = 4
?- conflict(R1,R2,b),color(R1,C,b).
R1 = 2 R2 = 4 C = blue
The last goal means that regions 2 and 4 are adjacent and both are blue. Grounded instances like
'conflict(2,4,b)' are said to be consequences of the Prolog program. One way to demonstrate such
a consequence is to draw a program clause tree having the consequence as the root of the tree,
use clauses of the program to branch the tree, and eventually produce a finite tree having all true
leaves. For example, the following clause tree can be constructed using fully grounded instances
(no variables) of clauses of the program.
The bottom leftmost branch drawn in the tree corresponds to the unit clause
adjacent(2,4).
which is equivalent in Prolog to the clause
adjacent(2,4) :- true.
Now, on the other hand, 'conflict(1,3,b)' is not a consequence of the Prolog program because it is
not possible to construct a finite finite clause tree using grounded clauses of P containing all
'true' leaves. Likewise, ‘conflict (a)' is not a consequence of the program, as one would expect.
We will have more to say about program clause trees in subsequent sections.
We will revisit the coloring problem again in Section 2.9, where we will develop a Prolog
program that can compute all possible colorings (given colors to color with). The famous Four
Color Conjecture was that no planar map requires more than four different colors. This was
proved by Appel and Haken (1976). The solution used a computer program (not Prolog) to check
on many specific cases of planar maps, in order to rule out possible troublesome cases. The map
in in Fig. 2.1.1 does require at least four colors; for example ...
/* Prolog Program for Map Colorings */
adjacent(1,2). adjacent(2,1).
adjacent(1,3). adjacent(3,1).
adjacent(1,4). adjacent(4,1).
adjacent(1,5). adjacent(5,1).
adjacent(2,3). adjacent(3,2).
adjacent(2,4). adjacent(4,2).
adjacent(3,4). adjacent(4,3).
adjacent(4,5). adjacent(5,4).
/*------------------------------------*/
color(1,red,a). color(1,red,b).
color(2,blue,a). color(2,blue,b).
color(3,green,a). color(3,green,b).
color(4,yellow,a). color(4,blue,b).
color(5,blue,a). color(5,green,b).
/*------------------------------------*/
conflict(Coloring) :-
adjacent(X,Y),
color(X,Color,Coloring),
color(Y,Color,Coloring).
/*-------------------------------------*/
conflict(R1,R2,Coloring) :-
adjacent(R1,R2),
color(R1,Color,Coloring),
color(R2,Color,Coloring).
OUTPUT:
RESULT:
Expt. No. 4 Towers of Hanoi puzzle
This object of this famous puzzle is to move N disks from the left peg to the right peg using the
center peg as an auxiliary holding peg. At no time can a larger disk be placed upon a smaller
disk. The following diagram depicts the starting setup for N=3 disks.
Here is a recursive Prolog program that solves the puzzle. It consists of two clauses.
move(1,X,Y,_) :-
write('Move top disk from '),
write(X),
write(' to '),
write(Y),
nl.
move(N,X,Y,Z) :-
N>1,
M is N-1,
move(M,X,Z,Y),
move(1,X,Y,_),
move(M,Z,Y,X).
The variables filled in by '_' (or any variables beginning with underscore) are 'don't-care'
variables. Prolog allows these variables to freely match any structure, but no variable binding
results from this gratuitous matching.
Here is what happens when Prolog solves the case N=3.
?- move(3,left,right,center).
Move top disk from left to right
Move top disk from left to center
Move top disk from right to center
Move top disk from left to right
Move top disk from center to left
Move top disk from center to right
Move top disk from left to right
yes
The first clause in the program describes the move of a single disk. The second clause declares
how a solution could be obtained, recursively. For example, a declarative reading of the second
clause for N=3, X=left, Y=right, and Z=center amounts to the following:
move(3,left,right,center) if
move(2,left,center,right) and ] *
move(1,left,right,center) and
move(2,center,right,left). ] **
This declarative reading of the clause is obviously correct. The procedural reading is closely
related to the declarative interpretation of the recursive clause. The procedural interpretation
would go something like this:
In order to satisfy the goal ?- move(3,left,right,center) do this :
satisfy the goal ?-move(2,left,center,right), and then
satisfy the goal ?-move(1,left,right,center), and then
satisfy the goal ?-move(2,center,right,left).
Also, we could write the declarative readings for N=2:
move(2,left,center,right) if ] *
move(1,left,right,center) and
move(1,left,center,right) and
move(1,right,center,left).
move(2,center,right,left) if ] **
move(1,center,left,right) and
move(1,center,right,left) and
move(1,left,right,center).
Now substitute the bodies of these last two implications for the heads and one can "see" the
solution that the prolog goal generates.
move(3,left,right,center) if
move(1,left,right,center) and
move(1,left,center,right) and *
move(1,right,center,left) and
---------------------------
move(1,left,right,center) and
---------------------------
move(1,center,left,right) and
move(1,center,right,left) and **
move(1,left,right,center).
A procedural reading for this last big implication should be obvious. This example illustrates
well three major operations of Prolog:
1) Goals are matched against the head of a rule, and
2) the body of the rule (with variables appropriately bound) becomes a new sequence of goals,
repeatedly, until 3) some base goal or condition is satisfied, or some simple action is taken (like
printing something).
/* prolog program for Towers of Hanoi puzzle */
move(1,X,Y,_) :- write('Move top disk from '),
write(X),
write(' to '),
write(Y),
nl.
move(N,X,Y,Z) :-
N>1,
M is N-1,
move(M,X,Z,Y),
move(1,X,Y,_),
move(M,Z,Y,X).
OUTPUT:
RESULT :
Expt. No 5. Chess queens challenge puzzle
The challenge is to set N queens on an NxN grid so that no queen can "take" any other queen.
Queens can move horizontally, vertically, or along a (45%) diagonal. The following diagram
shows a solution for N=4 queens.
________________
| | | Q | |
|___|___|___|___|
| Q | | | |
|___|___|___|___|
| | | | Q |
|___|___|___|___|
| | Q | | |
|___|___|___|___|
A solution to this puzzle can be represented as a special permutation of the list [1,2,3,4]. For
example, the solution pictured above can be represented as [3,1,4,2], meaning that, in the first
row place a queen in column 3, in the second row place a queen in column 1, etc. To test whether
a given permutation is a solution, one needs to calculate whether the permutation has (or
represents a situation where) two or more queens lie on the same diagonal. The representation
itself prevents two or more queens in the same row or column. Two queens are on the same
diagonal if and only if the sum of the row and column is the same for each; they are on the same
diagonal if and only if the difference of their row and column is the same number. The following
Prolog program has the details;
solve(P) :-
perm([1,2,3,4,5,6,7,8],P),
combine([1,2,3,4,5,6,7,8],P,S,D),
all_diff(S),
all_diff(D).
combine([X1|X],[Y1|Y],[S1|S],[D1|D]) :-
S1 is X1 +Y1,
D1 is X1 - Y1,
combine(X,Y,S,D).
combine([],[],[],[]).
?- solve(P).
P = [5,2,6,1,7,4,8,3] ;
P = [6,3,5,7,1,4,2,8] ;
...
?- setof(P,solve(P),Set), length(Set,L).
...
L = 92
The last goal reflects the fact that there are 92 distinct solutions to the queens challenge puzzle
for an 8x8 board. One inefficiency that this program suffers is that each permutation is
completely calculated before it is checked to see whether it represents a solution to the puzzle. It
is easy to see that this is not necessary. For example, suppose that a "partial solution" P =
[1,3,2, ...] is up for consideration. The row and column calculations show already the "2" is not a
safe move!
takeout(X,[X|R],R).
takeout(X,[F|R],[F|S]) :- takeout(X,R,S).
solve(P) :-
perm([1,2,3,4,5,6,7,8],P),
combine([1,2,3,4,5,6,7,8],P,S,D),
all_diff(S),
all_diff(D).
combine([X1|X],[Y1|Y],[S1|S],[D1|D]) :-
S1 is X1 +Y1,
D1 is X1 - Y1,
combine(X,Y,S,D).
combine([],[],[],[]).
OUTPUT:
RESULT:
Expt No.6 Graph structures and paths
Fig. 2.15
edge(1,2).
edge(1,4).
edge(1,3).
edge(2,3).
edge(2,5).
edge(3,4).
edge(3,5).
edge(4,5).
To represent the fact that the edges are bi-directional we could either add eight more 'edge'
clauses (edge(2,1),etc.) or we could try a rule like:
This is not a good idea, however. To see why it is not a good idea, try the following goal.
?- edge(5,1).
Notice that the rule (*) will be tried over and over in an infinite loop, so the goal will not
terminate. Try it! A better way to handle this is to use a rule such as the following.
Note the use of disjunction ';' in this rule. This rule could have been written as two rules:
connected(X,Y) :- edge(X,Y).
connected(X,Y) :- edge(Y,X).
We wish to develop a Prolog definition which generates paths between any two nodes of the
graph. More specifically, we require the following (kind of) behavior for the predicate 'paths'.
?- path(1,5,P).
P = [1,2,5] ;
P = [1,2,3,5] ;
P = [1,2,3,4,5] ;
P = [1,4,5] ;
P = [1,4,3,5] ;
P = [1,4,3,2,5] ;
P = [1,3,5] ;
P = [1,3,4,5] ;
P = [1,3,2,5] ;
no
The paths are represented by the list of nodes through which one must travel to get from node 1
to node 5. Here is a definition for paths:
path(A,B,Path) :-
travel(A,B,[A],Q),
reverse(Q,Path).
travel(A,B,P,[B|P]) :-
connected(A,B).
travel(A,B,Visited,Path) :-
connected(A,C),
C \== B,
\+member(C,Visited),
travel(C,B,[C|Visited],Path).
A declarative reading for the first clause amounts to "A path from A to B is obtained if A and B
are connected". A declarative reading for the second clause amounts to "A path from A to B is
obtained provided that A is connected to a node C different from B that is not on the previously
visited part of the path, and one continues finding a path from C to B". Avoiding repeated nodes
ensures that the program will not cycle endlessly.
/* prolog program Graph structures and paths */
edge(1,2).
edge(1,4).
edge(1,3).
edge(2,3).
edge(2,5).
edge(3,4).
edge(3,5).
edge(4,5).
path(A,B,Path) :-
travel(A,B,[A],Q),
reverse(Q,Path).
travel(A,B,P,[B|P]) :-
connected(A,B).
travel(A,B,Visited,Path) :-
connected(A,C),
C \== B,
\+member(C,Visited),
travel(C,B,[C|Visited],Path).
OUTPUT :
RESULT:
Expt. No. 7 The A* algorithm in Prolog
This section discusses heuristic search using the A* algorithm, due to Nilsson (1980).
Heuristic search uses a heuristic function to help guide the search. When a node is expanded,
each of its children is evaluated using a search function. Each child is placed into a list of nodes
-- the so-called open list -- in order determined by the search function evaluation (smaller values
first). The heuristic function estimates how much work must be done to reach a goal from the
node in question. Typically, the search function f is expressed as
Here is some simple pseudocode from which the Prolog program will be developed.
1. Start with the start node, place it in the (previously empty) list open.
2. Let n be the first node on open. Remove n from open. Fail if open is empty.
3. If n is the goal, then a solution has been found. (One could stop here.)
4. Expand n, obtaining all of its children, and evaluate f(-) for each of them. Insert each of
these children into open, maintaining order where the smallest f(-) values come first.
5. Repeat from step 2.
When a goal has been reached, one would certainly like to be able to return the solution path
from the start to the goal. The pseudocode ignored this feature, but it will be included as the
Prolog prototype program is developed.
A common cost function g(-) is path length. The cost of getting from the start node to a current
node is the length of relevant path. This can be computed incrementally, as will be seen.
It is important to realize that this kind of search can follow a contiguous path for a while, until
some previously unchosen node n has the current smallest f(-) value, in which case this node n is
expanded, and its children considered.
Node = State#Depth#F#A
for a node. When Node is expanded to find its children...
In general, if the Depth is replace by some other cost, the node representation would be similar;
just replace Depth by a cost, and compute it appropriately. Also, we will see in the next section
(8-puzzle) that the ancestor list might be more conveniently saved as a list of symbolic actions
(used to achieve successive states), rather that as a list of the actual full nodes themselves. Other
modifications of the prototypical A* algorithm presented in this section might be made,
depending on the application.
solve(Start,Soln) :- f_function(Start,0,F),
search([Start#0#F#[]],S),
reverse(S,Soln).
f_function(State,D,F) :- h_function(State,H),
F is D + H.
The 'Start' variable refers to the starting state description. The first parameter for the search
predicate represents the open list. The 'h_function' definition needs to be supplied with the
particular application.
search([State#_#_#Soln | _], Soln) :- goal(State).
search([B|R],S) :- expand(B, Children),
insert_all(Children, R, NewOpen),
search(NewOpen,S).
The version of the 'expand' predicate given here simply uses Prolog's bagof computation (thus
bundling up a lot of work).
expand(State#D#_#A, All_My_Children) :-
bagof(Child#D1#F#[Move|A],
( D1 is D + 1,
move(State,Child,Move),
f_function(Child,D1,F) ) ,
All_My_Children).
The (application dependent) 'move' predicate should generate the 'Child' states, in such a way as
to obtain them all by backtracking. (See the 8-puzzle example in the next section.) As previously
stated, the 'Move' can either be the the whole parent node itself or some appropriate substitute.
(Actually, one should rewrite the 'expand' clause if one is going to use the whole node, rather
some symbolic representation, as we do in the next section.)
Here is the code for insert_all. It is a familiar kind of insertion-sort algorithm ...
insert_all([F|R],Open1,Open3) :- insert(F,Open1,Open2),
insert_all(R,Open2,Open3).
insert_all([],Open,Open).
insert(B,Open,Open) :- repeat_node(B,Open), ! .
insert(B,[C|R],[B,C|R]) :- cheaper(B,C), ! .
insert(B,[B1|R],[B1|S]) :- insert(B,R,S), !.
insert(B,[],[B]).
repeat_node(P#_#_#_, [P#_#_#_|_]).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%
%%%
%%% A* Algorithm
%%%
%%%
%%% Nodes have form S#D#F#A
%%% where S describes the state or configuration
%%% D is the depth of the node
%%% F is the evaluation function value
%%% A is the ancestor list for the node
solve(State,Soln) :- f_function(State,0,F),
search([State#0#F#[]],S), reverse(S,Soln).
f_function(State,D,F) :- h_function(State,H),
F is D + H.
insert_all([F|R],Open1,Open3) :- insert(F,Open1,Open2),
insert_all(R,Open2,Open3).
insert_all([],Open,Open).
insert(B,Open,Open) :- repeat_node(B,Open), ! .
insert(B,[C|R],[B,C|R]) :- cheaper(B,C), ! .
insert(B,[B1|R],[B1|S]) :- insert(B,R,S), !.
insert(B,[],[B]).
repeat_node(P#_#_#_, [P#_#_#_|_]).
expand(State#D#_#S,All_My_Children) :-
bagof(Child#D1#F#[Move|S],
(D1 is D+1,
move(State,Child,Move),
f_function(Child,D1,F)),
All_My_Children).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%
%%%
%%% 8-puzzle solver
%%%
%%%
%%% State have form A/B/C/D/E/F/G/H/I
%%% where {A,...,I} = {0,...,8}
%%% 0 represents the empty tile
%%%
goal(1/2/3/8/0/4/7/6/5).
s_aux(0,0) :- !.
s_aux(_,1).
s_aux(X,Y,0) :- Y is X+1, !.
s_aux(8,1,0) :- !.
s_aux(_,_,2).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%
%%%
%%% 8-puzzle animation -- using VT100 character graphics
%%%
%%%
%%%
puzzle(P) :- solve(P,S),
animate(P,S),
message.
animate(P,S) :- initialize(P),
cursor(1,2), write(S),
cursor(1,22), write('Hit ENTER to step solver.'),
get0(_X),
play_back(S).
initialize(A/B/C/D/E/F/H/I/J) :-
cls,
retractall(location(_,_,_)),
assert(location(A,20,5)),
assert(location(B,30,5)),
assert(location(C,40,5)),
assert(location(F,40,10)),
assert(location(J,40,15)),
assert(location(I,30,15)),
assert(location(H,20,15)),
assert(location(D,20,10)),
assert(location(E,30,10)), draw_all.
%%% play_back([left,right,up,...]).
play_back([M|R]) :- call(M), get0(_X), play_back(R).
play_back([]) :- cursor(1,24). %%% Put cursor out of the way
message :- nl,nl,
write(' ********************************************'), nl,
write(' * Enter 8-puzzle goals in the form ... *'), nl,
write(' * ?- puzzle(0/8/1/2/4/3/7/6/5). *'), nl,
write(' * Enter goal ''message'' to reread this. *'), nl,
write(' ********************************************'), nl, nl.
up :- retract(location(0,X0,Y0)),
Ynew is Y0 - 5,
location(Tile,X0,Ynew),
assert(location(0,X0,Ynew)),
down(Tile),down(Tile),down(Tile),down(Tile),down(Tile).
right :- retract(location(0,X0,Y0)),
Xnew is X0 + 10,
location(Tile,Xnew,Y0),
assert(location(0,Xnew,Y0)),
left(Tile),left(Tile),left(Tile),left(Tile),left(Tile),
left(Tile),left(Tile),left(Tile),left(Tile),left(Tile).
down :- retract(location(0,X0,Y0)),
Ynew is Y0 + 5,
location(Tile,X0,Ynew),
assert(location(0,X0,Ynew)),
up(Tile),up(Tile),up(Tile),up(Tile),up(Tile).
hide(_,_,[]).
hide(X,Y,[R|G]) :- hide_row(X,Y,R),
Y1 is Y + 1,
hide(X,Y1,G).
hide_row(_,_,[]).
hide_row(X,Y,[_|R]) :- cursor(X,Y),
write(' '),
X1 is X + 1,
hide_row(X1,Y,R).
draw_row(_,_,[]).
draw_row(X,Y,[P|R]) :- cursor(X,Y),
write(P),
X1 is X + 1,
draw_row(X1,Y,R).
down(Obj) :- hide(Obj),
retract(location(Obj,X,Y)),
Y1 is Y + 1,
assert(location(Obj,X,Y1)),
draw(Obj).
left(Obj) :- hide(Obj),
retract(location(Obj,X,Y)),
X1 is X - 1,
assert(location(Obj,X1,Y)),
draw(Obj).
right(Obj) :- hide(Obj),
retract(location(Obj,X,Y)),
X1 is X + 1,
assert(location(Obj,X1,Y)),
draw(Obj).
:- message.
OUTPUT :
RESULT
Expt. No. 8 Alpha & beta search in Prolog, tic tac toe example
This section adapts the α&beta search framework in The Art of Prolog, by L. Sterling and E.
Shapiro (1986) for playing the game of tic tac toe (noughts and crosses). One purpose is to study
that framework by testing the adaptation to the TicTacToe game. Another intended purpose is to
have a Prolog tic tac toe expert agent that can play against the GUI interface
_|_|_
_|_|_ ~ [_Z1,_Z2,_Z3,_Z4,_Z5,_Z6,_Z7,_Z8,_Z9]
_|_|_ | 1st row | 2nd row | third row |
o|_|_
o|x|x ~ [o,_Z2,_Z3,o,x,x,_Z7,_Z8,_Z9]
_|_|_
The reason for this somewhat obscure representation is so as to avoid having to process a list
representation for the board. Instead, a player marks the board by binding a free variable.
:- dynamic board/1.
:- retractall(board(_)).
:- assert(board([_Z1,_Z2,_Z3,_Z4,_Z5,_Z6,_Z7,_Z8,_Z9])).
%%%%%
%% Generate possible marks on a free spot on the board.
%% Use mark(+,+,-X,-Y) to query/generate possible moves (X,Y).
%%%%%
mark(Player, [X|_],1,1) :- var(X), X=Player.
mark(Player, [_,X|_],2,1) :- var(X), X=Player.
mark(Player, [_,_,X|_],3,1) :- var(X), X=Player.
mark(Player, [_,_,_,X|_],1,2) :- var(X), X=Player.
mark(Player, [_,_,_,_,X|_],2,2) :- var(X), X=Player.
mark(Player, [_,_,_,_,_,X|_],3,2) :- var(X), X=Player.
mark(Player, [_,_,_,_,_,_,X|_],1,3) :- var(X), X=Player.
mark(Player, [_,_,_,_,_,_,_,X|_],2,3) :- var(X), X=Player.
mark(Player, [_,_,_,_,_,_,_,_,X|_],3,3) :- var(X), X=Player.
%%%%%
%% Record a move: record(+,+,+).
%%%%%
record(Player,X,Y) :-
retract(board(B)),
mark(Player,B,X,Y),
assert(board(B)).
For example, after this code is loaded, consider the following goal ...
?- board(B),mark(o,B,X,Y).
B = [o, _G286, _G289, _G292, _G295, _G298, _G301, _G304, _G307] X = 1 Y = 1 ;
B = [_G283, o, _G289, _G292, _G295, _G298, _G301, _G304, _G307] X = 2 Y = 1 ;
B = [_G283, _G286, o, _G292, _G295, _G298, _G301, _G304, _G307] X = 3 Y = 1 ;
B = [_G283, _G286, _G289, o, _G295, _G298, _G301, _G304, _G307] X = 1 Y = 2 ;
B = [_G283, _G286, _G289, _G292, o, _G298, _G301, _G304, _G307] X = 2 Y = 2 ;
B = [_G283, _G286, _G289, _G292, _G295, o, _G301, _G304, _G307] X = 3 Y = 2 ;
B = [_G283, _G286, _G289, _G292, _G295, _G298, o, _G304, _G307] X = 1 Y = 3 ;
B = [_G283, _G286, _G289, _G292, _G295, _G298, _G301, o, _G307] X = 2 Y = 3 ;
B = [_G283, _G286, _G289, _G292, _G295, _G298, _G301, _G304, o] X = 3 Y = 3 ;
No
This illustrates that all the moves can be be generated by backtracking on the mark clauses. Now,
let's first record an xin the center and then generate all possible subsequent moves for o ...
Yes
?- board(B).
Yes
Notice carefully that record does in fact record (assert) a move, but that mark simply finds all
possible moves(without actually recording them).
We need an evaluation function that measures how good a board is for a player. We use the well
known one that measures the difference between the open lines of play for each player,
Larger values favor o, smaller values favor x. Our champion is o (computer) whose algorithms
below will search to maximixe a move's value. The algoritms assume that x would search to
minimize value. A winning board for o has value 100, a winning board for x has value -100.
%%%%%
%% Calculate the value of a position, o maximizes, x minimizes.
%%%%%
value(Board,100) :- win(Board,o), !.
value(Board,-100) :- win(Board,x), !.
value(Board,E) :-
findall(o,open(Board,o),MAX),
length(MAX,Emax), % # lines open to o
findall(x,open(Board,x),MIN),
length(MIN,Emin), % # lines open to x
E is Emax - Emin.
%%%%%
%% A winning line is ALREADY bound to Player.
%% win(+Board,+Player) is true or fail.
%% e.g., win([P,P,P|_],P). is NOT correct, because could bind
%%%%%
win([Z1,Z2,Z3|_],P) :- Z1==P, Z2==P, Z3==P.
win([_,_,_,Z1,Z2,Z3|_],P) :- Z1==P, Z2==P, Z3==P.
win([_,_,_,_,_,_,Z1,Z2,Z3],P) :- Z1==P, Z2==P, Z3==P.
win([Z1,_,_,Z2,_,_,Z3,_,_],P) :- Z1==P, Z2==P, Z3==P.
win([_,Z1,_,_,Z2,_,_,Z3,_],P) :- Z1==P, Z2==P, Z3==P.
win([_,_,Z1,_,_,Z2,_,_,Z3],P) :- Z1==P, Z2==P, Z3==P.
win([Z1,_,_,_,Z2,_,_,_,Z3],P) :- Z1==P, Z2==P, Z3==P.
win([_,_,Z1,_,Z2,_,Z3,_,_],P) :- Z1==P, Z2==P, Z3==P.
%%%%%
%% A line is open if each position is either free or equals the Player
%%%%%
open([Z1,Z2,Z3|_],Player) :- (var(Z1) | Z1 == Player),(var(Z2) | Z2 == Player), (var(Z3) | Z3 ==
Player).
open([_,_,_,Z1,Z2,Z3|_],Player) :- (var(Z1) | Z1 == Player),(var(Z2) | Z2 == Player), (var(Z3) |
Z3 == Player).
open([_,_,_,_,_,_,Z1,Z2,Z3],Player) :- (var(Z1) | Z1 == Player),(var(Z2) | Z2 == Player),
(var(Z3) | Z3 == Player).
open([Z1,_,_,Z2,_,_,Z3,_,_],Player) :- (var(Z1) | Z1 == Player),(var(Z2) | Z2 == Player),
(var(Z3) | Z3 == Player).
open([_,Z1,_,_,Z2,_,_,Z3,_],Player) :- (var(Z1) | Z1 == Player),(var(Z2) | Z2 == Player),
(var(Z3) | Z3 == Player).
open([_,_,Z1,_,_,Z2,_,_,Z3],Player) :- (var(Z1) | Z1 == Player),(var(Z2) | Z2 == Player),
(var(Z3) | Z3 == Player).
open([Z1,_,_,_,Z2,_,_,_,Z3],Player) :- (var(Z1) | Z1 == Player),(var(Z2) | Z2 == Player),
(var(Z3) | Z3 == Player).
open([_,_,Z1,_,Z2,_,Z3,_,_],Player) :- (var(Z1) | Z1 == Player),(var(Z2) | Z2 == Player),
(var(Z3) | Z3 == Player).
%%%%%
%% Calculate the value of a position, o maximizes, x minimizes.
%%%%%
value(Board,100) :- win(Board,o), !.
value(Board,-100) :- win(Board,x), !.
value(Board,E) :-
findall(o,open(Board,o),MAX),
length(MAX,Emax), % # lines open to o
findall(x,open(Board,x),MIN),
length(MIN,Emin), % # lines open to x
E is Emax - Emin.
For example,
?- value([_X,o,o,_Y,o,x,x,_Z,x],V).
V=0
The reader should try several example boards, and also compute the value by inspection.
The αβ search algorithm looks ahead in order to calculate what move will be best for a player.
The algorithm maximizes the value for o, and minimizes the value for x. Here is the basic code
framework ...
alpha_beta(Player,0,Position,_Alpha,_Beta,_NoMove,Value) :-
value(Position,Value).
alpha_beta(Player,D,Position,Alpha,Beta,Move,Value) :-
D > 0,
findall((X,Y),mark(Player,Position,X,Y),Moves),
Alpha1 is -Beta, % max/min
Beta1 is -Alpha,
D1 is D-1,
evaluate_and_choose(Player,Moves,Position,D1,Alpha1,Beta1,nil,(Move,Value)).
evaluate_and_choose(Player,[Move|Moves],Position,D,Alpha,Beta,Record,BestMove) :-
move(Player,Move,Position,Position1),
other_player(Player,OtherPlayer),
alpha_beta(OtherPlayer,D,Position1,Alpha,Beta,_OtherMove,Value),
Value1 is -Value,
cutoff(Player,Move,Value1,D,Alpha,Beta,Moves,Position,Record,BestMove).
evaluate_and_choose(_Player,[],_Position,_D,Alpha,_Beta,Move,(Move,Alpha)).
cutoff(_Player,Move,Value,_D,_Alpha,Beta,_Moves,_Position,_Record,(Move,Value)) :-
Value >= Beta, !.
cutoff(Player,Move,Value,D,Alpha,Beta,Moves,Position,_Record,BestMove) :-
Alpha < Value, Value < Beta, !,
evaluate_and_choose(Player,Moves,Position,D,Value,Beta,Move,BestMove).
cutoff(Player,_Move,Value,D,Alpha,Beta,Moves,Position,Record,BestMove) :-
Value =< Alpha, !,
evaluate_and_choose(Player,Moves,Position,D,Alpha,Beta,Record,BestMove).
other_player(o,x).
other_player(x,o).
To test the code from the Prolog command line, we add a few instructions ...
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%
%%% For testing, use h(+,+) to record human move,
%%% supply coordinates. Then call c (computer plays).
%%% Use s to show board.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%
h(X,Y) :- record(x,X,Y), showBoard.
c :-
board(B),
alpha_beta(o,2,B,-200,200,(X,Y),_Value), % <=== NOTE
record(o,X,Y), showBoard.
showBoard :-
board([Z1,Z2,Z3,Z4,Z5,Z6,Z7,Z8,Z9]),
write(' '),mark(Z1),write(' '),mark(Z2),write(' '),mark(Z3),nl,
write(' '),mark(Z4),write(' '),mark(Z5),write(' '),mark(Z6),nl,
write(' '),mark(Z7),write(' '),mark(Z8),write(' '),mark(Z9),nl.
s :- showBoard.
Yes
?- h(2,1). % Human marks x at 2,1 (not best)
#x#
###
###
Yes
?- c. % computer thinks, moves to 2,2
#x#
#o#
###
Yes
?- h(1,1). % Human moves to 1,1
xx#
#o#
###
Yes
?- c. % computer's move. SEE ANALYSIS BELOW
xxo
#o#
###
... etc.
... a cat's game.
The Prolog program has been designed so that the state of the tic tac toe board is stored after
each board, rather than a continuing program that alternately interacts with the players. The
reason for this design choice is that we will connect the Prolog tic tac toe player up with a Java
GUI in Section 8.4. The Java program will also store the state of the current board. In this way,
Prolog and Java will merely have to communicate with each other by telling the other player
what the move is: a pair of numbers X,Y.
Let us look at what happens when Prolog computes the last move shown in the game above. The
following diagram graphically illustrates what happens for o's choices other than the first
possible (and critically best) move. All of the moves are expanded in the order generated by the
program. The second possible move [x,x,_,o,o,_,_,_,_] gives x the opportunity to choose her
win [x,x,x,o,o,_,_,_,_]. Our maximizer, o, will not tolerate this and searches no further. The α-
cutoff is the red dashed edge from the root to the second tier choice: Notice that cuttoff is called
by evaluate_and_choose, which can thus truncate the possibities! The backed-up value Alpha ==
0 was obtained from the leftmost search: The best that o can be guarateed from the ist move.
The αβ algorithm brings much more power to bear than is be actually needed to play
The real advantage that αβ search brings to a more complex game derives from its ability to
cutoff the search tree for search which looks ahead by many more moves. Sterling and Shapiro
(1986) provide an example for the game of Kalah. The complexity of Kalah takes better
advantage of αβ search, and so too can other more complicated games.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Prolog TicTacToe alpha-beta expert
%% Design to play against human (Java GUI)
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% The empty Tac Tac Toe board
%% Z1 Z2 Z3
%% Z4 Z5 Z6 ~ [Z1,Z2,Z3,Z4,Z5,Z6,Z7,Z8,Z9]
%% Z7 Z8 Z9
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
:- dynamic board/1.
init:-
retractall(board(_)),
assert(board([_Z1,_Z2,_Z3,_Z4,_Z5,_Z6,_Z7,_Z8,_Z9])).
:- init.
%%%%%
%% Generate possible marks on a free spot on the board.
%% Use mark(+,+,-X,-Y) to query/generate possible moves (X,Y).
%%%%%
mark(Player, [X|_],1,1) :- var(X), X=Player.
mark(Player, [_,X|_],2,1) :- var(X), X=Player.
mark(Player, [_,_,X|_],3,1) :- var(X), X=Player.
mark(Player, [_,_,_,X|_],1,2) :- var(X), X=Player.
mark(Player, [_,_,_,_,X|_],2,2) :- var(X), X=Player.
mark(Player, [_,_,_,_,_,X|_],3,2) :- var(X), X=Player.
mark(Player, [_,_,_,_,_,_,X|_],1,3) :- var(X), X=Player.
mark(Player, [_,_,_,_,_,_,_,X|_],2,3) :- var(X), X=Player.
mark(Player, [_,_,_,_,_,_,_,_,X|_],3,3) :- var(X), X=Player.
%%%%%
%% Move
%%%%%
move(P,(1,1),[X1|R],[P|R]) :- var(X1).
move(P,(2,1),[X1,X2|R],[X1,P|R]) :- var(X2).
move(P,(3,1),[X1,X2,X3|R],[X1,X2,P|R]) :- var(X3).
move(P,(1,2),[X1,X2,X3,X4|R],[X1,X2,X3,P|R]) :- var(X4).
move(P,(2,2),[X1,X2,X3,X4,X5|R],[X1,X2,X3,X4,P|R]) :- var(X5).
move(P,(3,2),[X1,X2,X3,X4,X5,X6|R],[X1,X2,X3,X4,X5,P|R]) :- var(X6).
move(P,(1,3),[X1,X2,X3,X4,X5,X6,X7|R],[X1,X2,X3,X4,X5,X6,P|R]) :- var(X7).
move(P,(2,3),[X1,X2,X3,X4,X5,X6,X7,X8|R],[X1,X2,X3,X4,X5,X6,X7,P|R]) :- var(X8).
move(P,(3,3),[X1,X2,X3,X4,X5,X6,X7,X8,X9|R],[X1,X2,X3,X4,X5,X6,X7,X8,P|R]) :- var(X9).
%%%%%
%% Record a move: record(+,+,+).
%%%%%
record(Player,X,Y) :-
retract(board(B)),
mark(Player,B,X,Y),
assert(board(B)).
%%%%%
%% A winning line is ALREADY bound to Player.
%% win(+Board,+Player) is true or fail.
%% e.g., win([P,P,P|_],P). is NOT correct, because could bind
%%%%%
win([Z1,Z2,Z3|_],P) :- Z1==P, Z2==P, Z3==P.
win([_,_,_,Z1,Z2,Z3|_],P) :- Z1==P, Z2==P, Z3==P.
win([_,_,_,_,_,_,Z1,Z2,Z3],P) :- Z1==P, Z2==P, Z3==P.
win([Z1,_,_,Z2,_,_,Z3,_,_],P) :- Z1==P, Z2==P, Z3==P.
win([_,Z1,_,_,Z2,_,_,Z3,_],P) :- Z1==P, Z2==P, Z3==P.
win([_,_,Z1,_,_,Z2,_,_,Z3],P) :- Z1==P, Z2==P, Z3==P.
win([Z1,_,_,_,Z2,_,_,_,Z3],P) :- Z1==P, Z2==P, Z3==P.
win([_,_,Z1,_,Z2,_,Z3,_,_],P) :- Z1==P, Z2==P, Z3==P.
%%%%%
%% A line is open if each position is either free or equals the Player
%%%%%
open([Z1,Z2,Z3|_],Player) :- (var(Z1) | Z1 == Player),(var(Z2) | Z2 == Player), (var(Z3) | Z3 == Player).
open([_,_,_,Z1,Z2,Z3|_],Player) :- (var(Z1) | Z1 == Player),(var(Z2) | Z2 == Player), (var(Z3) | Z3 == Player).
open([_,_,_,_,_,_,Z1,Z2,Z3],Player) :- (var(Z1) | Z1 == Player),(var(Z2) | Z2 == Player), (var(Z3) | Z3 == Player).
open([Z1,_,_,Z2,_,_,Z3,_,_],Player) :- (var(Z1) | Z1 == Player),(var(Z2) | Z2 == Player), (var(Z3) | Z3 == Player).
open([_,Z1,_,_,Z2,_,_,Z3,_],Player) :- (var(Z1) | Z1 == Player),(var(Z2) | Z2 == Player), (var(Z3) | Z3 == Player).
open([_,_,Z1,_,_,Z2,_,_,Z3],Player) :- (var(Z1) | Z1 == Player),(var(Z2) | Z2 == Player), (var(Z3) | Z3 == Player).
open([Z1,_,_,_,Z2,_,_,_,Z3],Player) :- (var(Z1) | Z1 == Player),(var(Z2) | Z2 == Player), (var(Z3) | Z3 == Player).
open([_,_,Z1,_,Z2,_,Z3,_,_],Player) :- (var(Z1) | Z1 == Player),(var(Z2) | Z2 == Player), (var(Z3) | Z3 == Player).
%%%%%
%% Calculate the value of a position, o maximizes, x minimizes.
%%%%%
value(Board,100) :- win(Board,o), !.
value(Board,-100) :- win(Board,x), !.
value(Board,E) :-
findall(o,open(Board,o),MAX),
length(MAX,Emax), % # lines open to o
findall(x,open(Board,x),MIN),
length(MIN,Emin), % # lines open to x
E is Emax - Emin.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% using minimax procedure with alpha-beta cutoff.
% Computer (o) searches for best tic tac toe move,
% Human player is x.
% Adapted from L. Sterling and E. Shapiro, The Art of Prolog, MIT Press, 1986.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
:- assert(lookahead(2)).
:- dynamic spy/0. % debug calls to alpha_beta
:- assert(spy). % Comment out stop spy.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
search(Position,Depth,(Move,Value)) :-
alpha_beta(o,Depth,Position,-100,100,Move,Value).
alpha_beta(Player,0,Position,_Alpha,_Beta,_NoMove,Value) :-
value(Position,Value),
spy(Player,Position,Value).
alpha_beta(Player,D,Position,Alpha,Beta,Move,Value) :-
D > 0,
findall((X,Y),mark(Player,Position,X,Y),Moves),
Alpha1 is -Beta, % max/min
Beta1 is -Alpha,
D1 is D-1,
evaluate_and_choose(Player,Moves,Position,D1,Alpha1,Beta1,nil,(Move,Value)).
evaluate_and_choose(Player,[Move|Moves],Position,D,Alpha,Beta,Record,BestMove) :-
move(Player,Move,Position,Position1),
other_player(Player,OtherPlayer),
alpha_beta(OtherPlayer,D,Position1,Alpha,Beta,_OtherMove,Value),
Value1 is -Value,
cutoff(Player,Move,Value1,D,Alpha,Beta,Moves,Position,Record,BestMove).
evaluate_and_choose(_Player,[],_Position,_D,Alpha,_Beta,Move,(Move,Alpha)).
cutoff(_Player,Move,Value,_D,_Alpha,Beta,_Moves,_Position,_Record,(Move,Value)) :-
Value >= Beta, !.
cutoff(Player,Move,Value,D,Alpha,Beta,Moves,Position,_Record,BestMove) :-
Alpha < Value, Value < Beta, !,
evaluate_and_choose(Player,Moves,Position,D,Value,Beta,Move,BestMove).
cutoff(Player,_Move,Value,D,Alpha,Beta,Moves,Position,Record,BestMove) :-
Value =< Alpha, !,
evaluate_and_choose(Player,Moves,Position,D,Alpha,Beta,Record,BestMove).
other_player(o,x).
other_player(x,o).
spy(Player,Position,Value) :-
spy, !,
write(Player),
write(' '),
write(Position),
write(' '),
writeln(Value).
spy(_,_,_). % do nothing
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% For testing, use h(+,+) to record human move,
%%% supply coordinates. Then call c (computer plays).
%%% Use s to show board.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
h(X,Y) :-
record(x,X,Y),
showBoard.
c :-
board(B),
alpha_beta(o,2,B,-200,200,(X,Y),_Value),
record(o,X,Y),
showBoard.
showBoard :-
board([Z1,Z2,Z3,Z4,Z5,Z6,Z7,Z8,Z9]),
write(' '),mark(Z1),write(' '),mark(Z2),write(' '),mark(Z3),nl,
write(' '),mark(Z4),write(' '),mark(Z5),write(' '),mark(Z6),nl,
write(' '),mark(Z7),write(' '),mark(Z8),write(' '),mark(Z9),nl.
s :- showBoard.
mark(X) :-
var(X),
write('#').
mark(X) :-
\+var(X),
write(X).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% Play tic tac toe with the Java GUI
%%% using port 54321.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
connect(Port) :-
tcp_socket(Socket),
gethostname(Host), % local host
tcp_connect(Socket,Host:Port),
tcp_open_socket(Socket,INs,OUTs),
assert(connectedReadStream(INs)),
assert(connectedWriteStream(OUTs)).
ttt :-
connectedReadStream(IStream),
read(IStream,(X,Y)),
record(x,X,Y),
board(B),
alpha_beta(o,2,B,-200,200,(U,V),_Value),
record(o,U,V),
connectedWriteStream(OStream),
write(OStream,(U,V)),
nl(OStream), flush_output(OStream),
ttt.
OUTPUT :
RESULT:
Expt No. 9 Convert first-order logic expressions to normal form
This section of Logic Topics presents a Prolog program that translates well-formed formulas
(wff's) of first-order logic into so-called normal program clauses. The next section of Logic
Topics presents a Prolog-like meta-interpreter (in XSB Prolog) for normal programs.
Wffs
The well-formed formulas will be Prolog terms formed according to the following recursive
characterization. Each of the following kinds of terms in the left column of the table are wffs,
provided that W, A, and B are also wffs.
~W negation not
A /\ B conjunction and
A \/ B disjunction or
and provided that variable X (in the last two expressions) does not occur in W inside an
expression of the form all(X,---) or exists(X,---).
Here are some logical English expressions and corresponding wffs. There is, of course, not a
uniquely determined wff corresponding to every natural language sentence. The author has
chosen symbolic expression for the nouns, verbs, etc., and provided an "intuitive" translation to a
wff. These translations might not be sufficiently detailed for purposes of inference in larger
contexts of meaning. The numbering of the examples will be for later reference.
3. Bill goes to the party if either Jane or Sandra goes, but not if they both go.
7. If it rains tuesday then I will wear rain gear or carry my ubrella, but not both.
8. Either Sam and Bill or else Sally and Nancy will go.
9. Sam will go, either Bill or Sally will go, and also Nancy will go.
a \/ b \/ c means a \/ (b \/ c)
a /\ b \/ c means (a /\ b) \/ c
a => b <=> c means (a => b) <=> c
a => b => c is meaningless -- need parens
according to the op rules. Notice again that bigger Prolog op-precedence means that the operator
has less power to grab surrounding terms. Most of this relative precendece ordering is standard in
logic and programming. When in doubt, use parentheses.
Normal clauses
Recall that a Prolog clause can be characterized as having the form
where A and each Bi is a Prolog predicate expression and where n >= 0. When n = 0 the
convention is to write the Prolog clause in the simplified "fact" form
Normal clauses have a more generalized form than Prolog clauses, but we use a notation which
is in many ways quite similar to the Prolog clause notation. A normal clause has one of the
following forms
where false is a special reserved term that can only occur in this special context. When n = 0 (and
consequently m >= 1) the convention will be to write the normal clause in the "state" form
A1 | A2 | ... | Am .
The "denial" form is intended to say that B1, B2, ..., Bn cannot all be true together. The "state"
form is intended to say that at least one of A1, A2, ..., Am is true.
As with Prolog clauses, a normal clause has a head (to left of ':-') and a body (to the right of ':-').
The special false head is equivalent to an empty clause head.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%
%%%
%%% normalize.P
%%% SWI-Prolog version
%%% Convert wffs to list of normal logic clauses
%%%
%%% and /\
%%% or \/
%%% not ~
%%% xor xor
%%% implies =>
%%% iff <=>
%%% all all(X,-)
%%% some exists(Y,-)
%%%
%%% all(X,p(X) => exists(Y, r(Y) /\ q(X,Y)))
%%% --------------------------------------------
%%% p(X) => r(sk1(X)) /\ q(X,sk1(X))
%%% --------------------------------------------
%%% r(sk1(X)) :- p(X).
%%% q(X,sk1(X)) :- p(X).
:- op(300,fx,'~').
:- op(400,yfx,'/\\').
:- op(500,yfx,'\\/').
:- op(600,xfx,'=>').
:- op(650,xfx,'<=>').
:- op(350,xfx,'xor').
%%%%%%%%%%%%%%%%%%%%%%
%%% generate a skolem
:- dynamic skolems/1.
:- assert(skolems([sk1,sk2,sk3,sk4,sk5,sk6,sk7,sk8,sk9,sk10,sk11,
sk12,sk13,sk14,sk15,sk16,sk17,sk18,sk19,sk20])).
genskolem(SK) :- retract(skolems([SK|R])),
assert(skolems(R)).
some_occurs([F|R],B) :-
occurs(F,B) | some_occurs(R,B).
occurs(A,[F|_]) :-
A == F,
!.
occurs(A,[_|R]) :-
occurs(A,R).
make_clauses((A,B),C) :-
!,
flatten_or(A,F),
separate(F,P,N),
(tautology(P,N) ->
make_clauses(B,C)
;
make_clause(P,N,D),
C = [D|R],
make_clauses(B,R) ).
make_clauses(A,C) :-
flatten_or(A,F),
separate(F,P,N),
(tautology(P,N) ->
C = []
;
make_clause(P,N,D),
C = [D] ).
make_sequence([A],A,_) :- !.
make_sequence([F|R],(F|S),'|') :-
make_sequence(R,S,'|').
make_sequence([F|R],(F,S),',') :-
make_sequence(R,S,',').
write_list([F|R]) :-
write(F), write('.'), nl,
write_list(R).
write_list([]).
OUTPUT:
RESULT:
member(X,[X|_]).
member(X,[_|R]) :- member(X,R).
Consider the goal
?- clause_tree(member(X,[a,b,c]).
X=a;
X=b;
X=c;
no
So the meta-interpreter grows clause trees, and finds the same answers that Prolog itself would
calculate. Here is a program clause tree rooted at 'clause_tree(membr(b,[a,b,c]))':
One can add evaluation to the 'clause_tree' program. The way this is done depends upon the kind
of Prolog one is using.
clause_tree(true) :- !.
clause_tree((G,R)) :-
!,
clause_tree(G),
clause_tree(R).
clause_tree(G) :-
(predicate_property(G,built_in) ;
predicate_property(G,compiled) ),
call(G). %% let Prolog do it
clause_tree(G) :- clause(G,Body), clause_tree(Body).
The new third clause says that if the goal G is built_in (e.g., arithmetic) then call the underlying
Prolog goal to do the evaluation; and similarly if G is compiled into memory. So, for example,
with the new definition
Meta-interpreters are very useful for redesigning the control mechanisms of Prolog. For example,
consider the following program:
p :- q.
q :- p.
p :- r.
r.
If one were to try the Prolog goal ?- p the first two clauses would be the cause of infinite looping,
like in the following derivation
Fig. 3.3.2
However, r, p, and q are all consequences of the program. Draw a program clause tree rooted at
r, p, and q, respectively, to demonstrate that each is a consequence. The fact that Prolog cannot
compute these consequences is an example of the incompleteness of Prolog. For the sake of
general programming efficiency, Prolog does not try to detect any loops. There is also a
theoretical limitation: There is no general loop-detecting algorithm that could be applied to
Prolog that would succeed in detecting all loops. If there were, one would have, in effect, solved
the halting problem.
However, it is still a very interesting problem to try to detect some loops. Here is a modification
of the 'clause_tree/1' program that can detect some loops:
clause_tree(true,_) :- !.
clause_tree((G,R),Trail) :-
!,
clause_tree(G,Trail),
clause_tree(R,Trail).
clause_tree(G,Trail) :-
loop_detect(G,Trail),
!,
fail.
clause_tree(G,Trail) :-
clause(G,Body),
clause_tree(Body,[G|Trail]).
loop_detect(G,[G1,_]) :- G == G1.
loop_detect(G,[_,R]) :- loop_detect(G,R).
We have added a Trail parameter to the meta-interpreter, and a 'loop_detect'. It is instructive to
compare this program with the search program in section 2.13. Now, we have
?- clause_tree(p,[]).
yes
The third clause "catches" the loop and allows the other choice 'p :- r' to be tried.
Now consider the following modification of the program. This version also generates a clause
tree parameter value as it interprets a program.
clause_tree(true,_,true) :- !.
clause_tree((G,R),Trail,(TG,TR)) :-
!,
clause_tree(G,Trail,TG),
clause_tree(R,Trail,TR).
clause_tree(G,_,prolog(G)) :-
(predicate_property(G,built_in) ;
predicate_property(G,compiled) ),
call(G). %% let Prolog do it
clause_tree(G,Trail,_) :-
loop_detect(G,Trail),
!,
fail.
clause_tree(G,Trail,tree(G,T)) :-
clause(G,Body),
clause_tree(Body,[G|Trail],T).
Load both this last program and the simple test program
?- clause_tree(p(X),[],Tree)
No
Here is a program to draw the clause tree that is generated ...
why(G) :- clause_tree(G,[],T),
nl,
draw_tree(T,5).
draw_tree(tree(Root,Branches),Tab) :- !,
tab(Tab),
write('|-- '),
write(Root),
nl,
Tab5 is Tab + 5,
draw_tree(Branches,Tab5).
draw_tree((B,Bs),Tab) :- !,
draw_tree(B,Tab),
draw_tree(Bs,Tab).
draw_tree(Node,Tab) :-
tab(Tab),
write('|-- '),
write(Node),
nl.
Now, interpreting the same sample program as above ...
?- why(p(X)).
|-- p(3)
|-- q(3)
|-- true
|-- r(5)
|-- true
|-- prolog(3 < 5)
X=3;
|-- p(3)
|-- q(3)
|-- true
|-- r(10)
|-- true
|-- prolog(3 < 10)
X=3;
No
The tree corresponding to the first answer would be drawn ("vertically oriented") as follows...
Iterative deepening
To motivate the last topic in this section, consider the following "bad" program
?- connected(1,2).
...
This goal causes Prolog to go into an infinite subgoal descent. (Try this yourself!) Of course, we
could have prevented this -- for this goal -- if we had put the "rule" after the "facts" in the bad
program. But, even with that change, a goal like '?- connected(1,What)' would have caused a
problem if we forced backtracking to try to find all solutions. (Again, try this yourself.)
The looping here is not like the looping discussed above. To explain this, consider the original
"bad" program, and consider the Prolog derivation tree generated by attempting the goal '?-
connected(1,What)'
It should be clear from Fig. that the problem is really not looping in a way that repeats a goal,
although the same clause is repeatedly used. The 'connected' rule itself does not know how many
links there might be between '1' and 'What', so one might say that this rule is (all by itself) trying
to allow for there being 1 link, 2 links, 3 links, ..., etc. This is sometimes referred to as
a prediction loop. The previous loop checking tried to detect a repeated goal that was
"equivalent" to a previous goal (or identical to the previous goal). The current repetitious
behavior is not really generating goals that are "fully equivalent" to previous goals.
One method for avoiding this kind of infinite descent is called "iterative deepening", which
means that one still uses depth-first search, but one "iteratively" searches to a certain depth, then
deeper, then deeper still, ..., etc. Here is a meta-interpreter that does this. This meta-interpreter is
quite similar to the previous ones. However, this one has only two extra parameters: one for
current depth of a goal, and one for the current depth limit. This interpreter does not do the
previous kind of loop check nor does it generate the symbolic tree, but it does call Prolog for
evaluation goals. (Of course, these features could be easily added, but here we concentrate just
on the iterative deepening concept.)
clause_tree(true,_,_) :- !.
clause_tree(_,D,Limit) :- D > Limit,
!,
fail. %% reached depth limit
clause_tree((A,B),D,Limit) :- !,
clause_tree(A,D,Limit),
clause_tree(B,D,Limit).
clause_tree(A,_,_) :- predicate_property(A,built_in),
!,
call(A).
clause_tree(A,D,Limit) :- clause(A,B),
D1 is D+1,
clause_tree(B,D1,Limit).
iterative_deepening(G,D) :- clause_tree(G,0,D).
iterative_deepening(G,D) :- write('limit='),
write(D),
write('(Hit Enter to Continue.)'),
get0(C),
( C == 10 ->
D1 is D + 5,
iterative_deepening(G,D1) ).
?- iterative_deepening(connected(1,What), 1).
What=3 ;
What=2 ;
Limit=1(Hit Enter to Continue)
What=5 ;
What=5 ;
What=5 ;
What=4 ;
What=5 ;
What=5 ;
What=4 ;
What=3 ;
What=2 ;
Limit=6(Hit Enter to Continue.)
What=5 %% stop
Yes
Note how the 'iterative_deepening' meta-interpreter finds solutions first that are near the current
depth limit, and then proceeds to discover shallower solutions. A good graphical description of
this phenomenon is that the meta-interpreter searches the lower left corner of a triangle of depth
equal to the current depth limit and then searches shallower depths in the right portion of the
triangle, as suggested by the following diagram. The diagram shows solutions for
connected(1,What) that are "accessible" at depth 1, which was the first stage for the goal above.
Theoretically, iterative deepening has a kind of optimal behavior for "blind" search: Iterative
deepening will find any possible solution in a stage deep enough to include the clause tree
justifying that solution -- space O(d), d=depth -- in time O(b**d), b= average branching factor.
Other kinds of complete search, such as breadth-first (or iterative broadening), have to search
through a larger expected number of nodes.
iterative_deepening(G,L) :- clause_tree(G,0,L).
iterative_deepening(G,L) :- write('limit='),
write(L),
write('(Hit Enter to Continue.)'),
get0(C),
( C == 10 ->
L1 is L + 5,
iterative_deepening(G,L1) ).
connected(X,Y) :- connected(X,Z), connected(Z,Y).
connected(1,2).
connected(2,3).
connected(3,4).
connected(4,5).
OUTPUT :
RESULT: