home *** CD-ROM | disk | FTP | other *** search
-
-
- 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