home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Power-Programmierung
/
CD1.mdf
/
magazine
/
aijournl
/
1986_11
/
chain.nov
< prev
next >
Wrap
Text File
|
1986-09-25
|
18KB
|
666 lines
Forward Chaining in PROLOG
by Dennis Merrit
November 1986 AI EXPERT magazine
OOPS - A Toy Production System
This is an interpreter for files containing rules coded in the
OOPS format.
The => prompt accepts three commands:
load. - prompts for name of rules file
enclose in single quotes
exit. - does what you'd expect
go. - starts the inference
hit any key to continue
=>load.
File name? 'room.ari'.
=>go.
Enter a single item of furniture at each prompt.
Include the width (in feet) of each item.
The format is Item:Length.
The legal values are:
[couch,chair,table_lamp,end_table,coffee_table,tv,standing_lamp,end]
When there is no more furniture, enter "end:end".
adding - goal(read_furniture)
Rule fired 1
furniture> couch:6.
adding - furniture(couch,6)
Rule fired 3
furniture> chair:4.
adding - furniture(chair,4)
Rule fired 3
furniture> chair:3.
adding - furniture(chair,3)
Rule fired 3
furniture> coffee_table:5.
adding - furniture(coffee_table,5)
Rule fired 3
furniture> end_table:2.
adding - furniture(end_table,2)
Rule fired 3
furniture> end_table:3.
adding - furniture(end_table,3)
Rule fired 3
furniture> tv:4.
adding - furniture(tv,4)
Rule fired 3
furniture> sofa:5.
Unknown piece of furniture, must be one of:
[couch,chair,table_lamp,end_table,coffee_table,tv,standing_lamp,end]
Rule fired 4
furniture> table_lamp:2.
adding - furniture(table_lamp,2)
Rule fired 3
furniture> end:end.
adding - furniture(end,end)
Rule fired 3
adding - goal(read_walls)
Rule fired 2
What is the length of the north and south sides? 10.
What is the length of the east and west sides? 7.
adding - wall(north,10)
adding - wall(south,10)
adding - wall(east,7)
adding - wall(west,7)
adding - goal(find_door)
Rule fired 5
Which wall has the door? east.
What is the width of the door? 4.
adding - wall(east,3)
adding - position(door,east)
adding - goal(find_plugs)
Which walls have plugs? "end" when no more plugs:
Rule fired 6
Side: west.
adding - position(plug,west)
Rule fired 8
Side: end.
adding - position(plug,end)
Rule fired 8
Rule fired 7
adding - position(couch,north)
adding - wall(north,4)
Rule fired f2
adding - position(tv,south)
adding - wall(south,6)
Rule fired f3
adding - position(coffee_table,front_of_couch : north)
Rule fired f4
adding - position(chair,west)
adding - wall(west,4)
Rule fired f6
adding - position(chair,west)
adding - wall(west,0)
Rule fired f6
adding - position(end_table,north,nolamp)
adding - wall(north,1)
Rule fired f9
adding - position(table_lamp,north)
adding - position(end_table,north,lamp)
Rule fired f11
adding - buy(extension_cord,south)
adding - position(plug,south)
Rule fired f12
adding - buy(extension_cord,north)
adding - position(plug,north)
Rule fired f13
Recommendations:
furniture positions:
position(plug,north)
position(plug,south)
position(table_lamp,north)
position(chair,west)
position(chair,west)
position(coffee_table,front_of_couch : north)
position(tv,south)
position(couch,north)
position(plug,west)
position(door,east)
position(end_table,north,lamp)
purchase recommendations:
buy(extension_cord,north)
buy(extension_cord,south)
furniture which wouldn't fit:
furniture(end_table,2)
Rule fired f14
=>exit.
From AI EXPERT:
Listing 1
% ROOM is an expert system for placing furniture in a living room.
% It is written using the OOPS production system rules language.
% It is only designed to illustrate the use of a forward chaining
% rules based language for solving configuration problems. As such
% it makes many simplifying assumptions (such as furniture has no
% width). It just decides which wall each item goes on, and does
% not decide the relative placement on the wall.
% Furniture to be placed in the room is stored in terms of the form
% "furniture(item,length)". The rules look for unplaced furniture,
% and if found attempt to place it according to the rules of thumb.
% Once placed, the available space on a wall is updated, the furniture
% is recorded on a wall with a term of the form "position(item,wall)",
% and the original "furniture" term is removed.
% These are the terms which are initially stored in working storage.
% They set a goal used to force firing of certain preliminary rules,
% and various facts about the problem domain used by the actual
% configuration rules.
initial_data([goal(place_furniture),
not_end_yet,
legal_furniture([couch, chair, table_lamp, end_table,
coffee_table, tv, standing_lamp, end]),
opposite(north,south),
opposite(south,north),
opposite(east,west),
opposite(west,east),
right(north,west),
right(west,south),
right(south,east),
right(east,north),
left(north,east),
left(east,south),
left(south,west),
left(west,north)]).
% Rules 1-8 are an example of how to generate procedural behavior
% from a non-procedural rule language. These rules force a series
% of prompts and gather data from the user on the room and furniture
% to be configured. They are included to illustrate the kludgy
% nature production systems in a conventional setting.
% This is in contrast to rules f1-f14 which elegantly configure the room.
rule 1:
[1: goal(place_furniture), % The initial goal causes a rule to
2: legal_furniture(LF)] % to fire with introductory information.
==> % It will set a new goal.
[retract(1),
cls,nl,
write('Enter a single item of furniture at each prompt.'),nl,
write('Include the width (in feet) of each item.'),nl,
write('The format is Item:Length.'),nl,nl,
write('The legal values are:'),nl,
write(LF),nl,nl,
write('When there is no more furniture, enter "end:end".'),nl,
assert(goal(read_furniture))].
rule 2:
[1: furniture(end,end), % When the furniture is read
2: goal(read_furniture)] % set the new goal of reading
==> % reading wall sizes
[retract(all),
assert(goal(read_walls))].
rule 3:
[1: goal(read_furniture), % Loop to read furniture.
2: legal_furniture(LF)]
==>
[prompt('furniture> ', F:L),
member(F,LF),
assert(furniture(F,L))].
rule 4: % If rule 3 matched and failed
[1: goal(read_furniture), % the action, then member must
2: legal_furniture(LF)] % have failed.
==>
[write('Unknown piece of furniture, must be one of:'),nl,
write(LF),nl].
rule 5:
[1: goal(read_walls)]
==>
[retract(1),
prompt('What is the length of the north and south sides? ', LengthNS),
prompt('What is the length of the east and west sides? ', LengthEW),
assert(wall(north,LengthNS)),
assert(wall(south,LengthNS)),
assert(wall(east,LengthEW)),
assert(wall(west,LengthEW)),
assert(goal(find_door))].
rule 6:
[1: goal(find_door)]
==>
[retract(1),
prompt('Which wall has the door? ', DoorWall),
prompt('What is the width of the door? ', DoorWidth),
retract(wall(DoorWall,X)),
NewWidth = X - DoorWidth,
assert(wall(DoorWall, NewWidth)),
assert(position(door,DoorWall)),
assert(goal(find_plugs)),
write('Which walls have plugs? "end" when no more plugs:'),nl].
rule 7:
[1: goal(find_plugs),
2: position(plug,end)]
==>
[retract(all)].
rule 8:
[1: goal(find_plugs)]
==>
[prompt('Side: ', Wall),
assert(position(plug,Wall))].
% Rules f1-f13 illustrate the strength of rule based programming.
% Each rule captures a rule of thumb used in configuring furniture
% in a living room. The rules are all independent, transparent,
% and can be easily maintained. Complexity can be added without
% concern for the flow of control.
% f1, f2 - place the couch first, it should be either opposite the
% door, or to its right, depending on which wall is longer.
rule f1:
[1: furniture(couch,LenC), % an unplaced couch
position(door, DoorWall), % find the wall with the door
opposite(DoorWall, OW), % the wall opposite the door
right(DoorWall, RW), % the wall to the right of the door
2: wall(OW, LenOW), % available space opposite
wall(RW, LenRW), % available space to the right
LenOW >= LenRW, % if opposite wall bigger than right
LenC =< LenOW] % length of couch less than wall space
==>
[retract(1), % remove the furniture term
assert(position(couch, OW)), % assert the new position
retract(2), % remove the old wall,length
NewSpace = LenOW - LenC, % calculate the space now available
assert(wall(OW, NewSpace))]. % assert the wall with new space left
rule f2:
[1: furniture(couch,LenC),
2: position(door, DoorWall),
3: opposite(DoorWall, OW),
4: right(DoorWall, RW),
5: wall(OW, LenOW),
6: wall(RW, LenRW),
LenOW =< LenRW,
LenC =< LenRW]
==>
[retract(1),
assert(position(couch, RW)),
retract(6),
NewSpace = LenRW - LenC,
assert(wall(RW, NewSpace))].
% f3 - the tv should be opposite the couch
rule f3:
[1: furniture(tv,LenTV),
2: position(couch, CW),
3: opposite(CW, W),
4: wall(W, LenW),
LenW >= LenTV]
==>
[retract(1),
assert(position(tv, W)),
retract(4),
NewSpace = LenW - LenTV,
assert(wall(W, NewSpace))].
% f4, f5 - the coffee table should be in front of the couch or if there
% is no couch, in front of a chair.
rule f4:
[1: furniture(coffee_table,_),
2: position(couch, CW)]
==>
[retract(1),
assert(position(coffee_table, front_of_couch:CW))].
rule f5:
[1: furniture(coffee_table,_),
2: position(chair, CW)]
==>
[retract(1),
assert(position(coffee_table, front_of_chair:CW))].
% f6, f7 - chairs should be on adjacent walls from the couch
rule f6:
[1: furniture(chair,LC),
position(couch, CW),
right(CW, ChWa),
left(CW, ChWb),
4: wall(ChWa, La),
wall(ChWb, Lb),
La >= Lb,
La >= LC]
==>
[retract(1),
assert(position(chair, ChWa)),
NewSpace = La - LC,
retract(4),
assert(wall(ChWa, NewSpace))].
rule f7:
[1: furniture(chair,LC),
position(couch, CW),
right(CW, ChWa),
left(CW, ChWb),
wall(ChWa, La),
4: wall(ChWb, Lb),
La =< Lb,
Lb >= LC]
==>
[retract(1),
assert(position(chair, ChWb)),
NewSpace = Lb - LC,
retract(4),
assert(wall(ChWb, NewSpace))].
rule f8:
[1: furniture(chair,LC),
2: position(couch, CW),
3: left(CW, ChW),
4: wall(ChW, L),
L >= LC]
==>
[retract(1),
assert(position(chair, ChW)),
NewSpace = L - LC,
retract(4),
assert(wall(ChW, NewSpace))].
% put end_tables next to the couch first, then on the walls with
% the chairs
rule f9:
[1: furniture(end_table,TL),
2: position(couch, W),
3: not(position(end_table, W)),
4: wall(W, L),
L >= TL]
==>
[retract(1),
assert(position(end_table, W, nolamp)),
NewSpace = L - TL,
retract(4),
assert(wall(W, NewSpace))].
rule f10:
[1: furniture(end_table,TL),
2: position(chair, W),
3: not(position(end_table, W)),
4: wall(W, L),
L >= TL]
==>
[retract(1),
assert(position(end_table, W, nolamp)),
NewSpace = L - TL,
retract(4),
assert(wall(W, NewSpace))].
% put the table lamps on the end tables
rule f11:
[1: furniture(table_lamp,_),
2: position(end_table, W, nolamp)]
==>
[retract(all),
assert(position(table_lamp, W)),
assert(position(end_table, W, lamp))].
% get extension cords if needed
rule f12:
[1: position(tv, W),
2: not(position(plug, W))]
==>
[assert(buy(extension_cord, W)),
assert(position(plug, W))].
rule f13:
[1: position(table_lamp, W),
2: not(position(plug, W))]
==>
[assert(buy(extension_cord, W)),
assert(position(plug, W))].
% When no other rules fire, here is the summary
rule f14:
[1: not_end_yet]
==>
[retract(1),
write('Recommendations:'),nl,nl,
write('furniture positions:'),nl,nl,
list(position(_,_)),
list(position(_,_,_)),nl,
write('purchase recommendations:'),nl,nl,
list(buy(_,_)),nl,
write('furniture which wouldn''t fit:'),nl,nl,
list(furniture(_,_)),nl,nl].
Listing 2
% OOPS - A toy production system interpreter. It uses a forward chaining,
% data driven, rule based approach for expert system development.
%
% author Dennis Merritt
% Copyright (c) Hathaway Software, 1986
:-public main/0, restart/0.
% operator definitions
:-op(800,xfx,==>). % used to separate LHS and RHS of rule
:-op(500,xfy,:). % used to separate attributes and values
:-op(810,fx,rule). % used to define rule
:-op(700,xfy,#). % used for unification instead of =
main:- welcome, supervisor.
restart:-halt.
welcome:-
cls,
tmove(5,0),
write($ OOPS - A Toy Production System$),nl,nl,
write($This is an interpreter for files containing rules coded in the$),nl,
write($OOPS format.$),nl,nl,
write($The => prompt accepts three commands:$),nl,nl,
write($ load. - prompts for name of rules file$),nl,
write($ enclose in single quotes$),nl,
write($ exit. - does what you'd expect$),nl,
write($ go. - starts the inference$),nl,nl,
write($hit any key to continue$),nl,nl,
keyb(_,_),cls.
% the supervisor, uses a repeat fail loop to read and process commands
% from the user
supervisor:-
cls,
repeat,
write('=>'),
read(X),
do(X),
fail.
% actions to take based on commands
do(exit):-halt,!.
do(go):-initialize,go,!.
do(load):-load,!.
do(list):- list,!. % lists all of working storage
do(list(X)):- list(X),!. % lists all which match the pattern
% loads the rules (Prolog terms) into the Prolog database
load:-
write('File name? '),
read(F),
[F]. % loads a rule file into interpreter work space
% assert each of the initial conditions into working storage
initialize:-
call(initial_data(X)),
assert_list(X).
% working storage is represented by database terms stored
% under the key "fact"
assert_list([]):-!.
assert_list([H|T]):-
recordz(fact,H,_),
!,assert_list(T).
% the main inference loop, find a rule and try it. if it fired, say so
% and repeat the process. if not go back and try the next rule. when
% no rules succeed, stop the inference
go:-
call(rule ID: LHS ==> RHS),
try(LHS,RHS),
write('Rule fired '),write(ID),nl,
!,go.
go.
% match the LHS against working storage, if it succeeds process the
% actions from the RHS
try(LHS,RHS):-
match(LHS,Lrefs),
process(RHS,Lrefs),!.
% recursively go through the LHS list, matching conditions agains
% working storage
match([],[]):-!.
match([N:Prem|Rest],[N:Lref|Lrest]):-
!,
(recorded(fact,Prem,Lref);
test(Prem),Lref=0), % a comparison test rather than a fact
match(Rest,Lrest).
match([Prem|Rest],[x:Lref|Lrest]):-
(recorded(fact,Prem,Lref); % condition number not specified
test(Prem),Lref=0),
match(Rest,Lrest).
% various tests allowed on the LHS
test(not(X)):-
recorded(fact,X,_),
!,fail.
test(not(X)):- !.
test(X#Y):- X=Y,!.
test(X>Y):- X>Y,!.
test(X>=Y):- X>=Y,!.
test(X<Y):- X<Y,!.
test(X=<Y):- X=<Y,!.
test(X = Y):- X is Y,!.
test(member(X,Y)):- member(X,Y),!.
% recursively execute each of the actions in the RHS list
process([],_):-!.
process([Action|Rest],Lrefs):-
take(Action,Lrefs),
!,process(Rest,Lrefs).
% if its retract, use the reference numbers stored in the Lrefs list,
% otherwise just take the action
take(retract(N),Lrefs):-
(N == all; integer(N)),
retr(N,Lrefs),!.
take(A,_):-take(A),!.
take(retract(X)):- recorded(fact,X,R), erase(R), !.
take(assert(X)):- recorda(fact,X,_),write(adding-X),nl,!.
take(X # Y):- X=Y,!.
take(X = Y):- X is Y,!.
take(write(X)):- write(X),!.
take(nl):- nl,!.
take(read(X)):- read(X),!.
take(prompt(X,Y)):- nl,write(X),read(Y),!.
take(cls):- cls, !.
take(member(X,Y)):- member(X,Y), !.
take(list(X)):- list(X), !.
% logic for retraction
retr(all,Lrefs):-retrall(Lrefs),!.
retr(N,[]):-write('retract error, no '-N),nl,!.
retr(N,[N:Lref|_]):- erase(Lref),!.
retr(N,[_|Rest]):- !,retr(N,Rest).
retrall([]):-!.
retrall([N:Lref|Rest]):-
(Lref==0;
erase(Lref)),
!,retrall(Rest).
% list all of the terms in working storage
list:-
recorded(fact,X,_),
write(X),nl,
fail.
list:-!.
% lists all of the terms which match the pattern
list(X):-
recorded(fact,X,_),
write(X),nl,
fail.
list(_):-!.
member(X,[X|_]):-!.
member(X,[H|T]):-
member(X,T).
-
recorded(fact,X,_),
write(X),nl,
fail.
li