-------------------------------------------------------------------------------
	      PRODIGY/ANALOGY: USING THE CASE-BASED PLANNING MODE
-------------------------------------------------------------------------------

      NOTE  at the current time, this document is intended mainly for the
      users of Prodigy/Analogy in manual mode, rather than within the UI.
      However, a good knowledge of this information facilitates efficient
      use of Prodigy in analogical mode from the UI.


-------------------------------------------------------------------------------

In order to plan for new problems using past cases, Prodigy/Analogy must
retrieve one or more similar plans from memory. This section describes the
retrieval mechanism and the lisp functions used to perform the task. 


				  Preparation

The analogy code must be loaded and PRODIGY must be in Analogical Planning mode
for the system to behave properly. Load the file setup.lisp to perform these
functions. This file automatically loads the binaries (or source files,
depending on the value of *load-analogy-immediately*) and established
additional initialization. These functions may also be performed manually as
explained below. 

The binaries are loaded by the call of (load-analogy), and the source code
files are loaded by calling (load-analogy-source). Analogical Planning mode is
signaled when the global flag *analogical-replay* is set to t. The function
set-for-replay will set this flag and other appropriate variables in
preparation for planning. It set the default planning search mode to SABA and
sets *talk-case-p* to t. This flag adds feedback to the Lisp output so the
users can monitor progress. 


				 Saving Cases

Cases are stored in files located in subdirectories of the domains
directory. Given a particular domain such as the blocks domain, the domain is
found in a subdirectory of domains called "blocks." Problems are found in a
subdirectory of blocks called "probs." The cases are contained in a directory
off of probs called "cases." Finally, case headers are contained in a directory
called "headers" off of the cases directory. You must create the cases and
headers subdirectories for the store-case function to work.

Once a plan has been created in generative mode, the user creates a case by
calling (store-case). The function has two keyword parameters. The :case-name
argument is a string that by default has the prefix "case-", suffix ".lisp",
and the problem name as stem. The :case-dir argument is a path-name that
defaults to the concatenation of the value of *problem-path* and the string
"probs/cases/". After calling the function, appropriate case and header files
will have been generated by PRODIGY so that they can be retrieved and reused in
the future for further planning.


				 Reusing Cases

(replay)

This section describes the detailed step-by-step method for reusing past cases
to solve new problems. The user can forgo this discussion by calling the
function replay. Replay will load a domain and problem, and then find a set of
old cases, and replay them automatically. A call of (replay :domain 'rocket
:problem 'prob2objs-2) will suffice. Further explanation of the use of this
function is provided at the end of this document. 

To exert more control over this problem-solving process we provide the
7-step explanation below.


Step 0. Loading the Problem

(domain 'domain-name)
(problem 'proble-name)

In preparation for analogical planning, a domain and problem must be loaded
with the "domain" and "problem" functions, as is also the case in Generative
Planning mode. Unlike other planning modes, however, a special set of control
rules must be loaded along with the domain theory in order for the analogical
replay mechanism to be in effect. Place the following lines of code at the end
of any domain.lisp file loaded with the intention to incorpoate case reuse:

	(if *analogical-replay*
	    (load (concatenate 'string *analogy-pathname* "replay-crs")))


(p4::load-problem (current-problem) nil) 

Finally, the problem needs to be loaded, so that its objects are created, so
call p4::load-problem as above before calling the function
evaluate-similarities below.


Step 1. Case Headers

NOTE that under each probs directory for a given domain (i.e., the directory
where problem files are located), the user must create a sub-directory called
"cases" so that the system can store plannng solutions. Under the cases
directory, the user must also create a sub-directory called "headers".


(load-all-case-headers)

Load the case headers by calling the function load-all-case-headers. This sets
the global variable *case-headers*. As default, it loads the case headers in
the current case headers directory (i.e., the concatenation of *problem-path*
and "probs/cases/headers/").

To reduce the scope of retrieval, the user can load only a subset of the
existing case headers. Cases not loaded will not be available for match, and
therfore, reuse. A call of (load-case-headers "case1" "case2"), instead of
(load-all-case-headers) will suitably limit the scope to only those two
cases. As an aside, this is what the Manual Retrieval function of the UI
actually does.


Step 2. Matching 

(evaluate-similarities)

The function evaluate-similarities performs a match of the cases whose headers
are currently loaded into the system against the goals of the new problem and
the current initial state. Because the function performs partial matching, some
old cases may match the current problem to various degrees. The user can adjust
a keyword parameter to change the threshold at which a partial match is
accepted. The default is a :match-threshold of 0.6. Another keyword parameter
is :max-goals, that represents the number of interacting sets of top-level
goals in the new problem. Because this may not be known, the default is taken
to be 2, but the user should use a default of the number of goals in the
problem statement (i.e., they are all assumed to be independent). This number
can be obtained by (length (get-lits-no-and (p4::problem-goal
(current-problem)))).

The main side-effect of evaluate-similarities is to establish the value of the
global variable *cover-matches*. This list represents an assoc list of the
goals and the best past case that cover that goal.  If the new-goals are ((g1 a
b) (g2 c)) then *cover-matches* would be something like the following:

	(((g1 a b) . (case-name match length-intersection-initial-state
		      percentage-initial-state-matched))
	 ((g2 c) . same thing))

	Example is: (((at-airplane pl1 a9) case-test-4-2-3
	              ((<a26> . pl1) (<ap90> . a9) (<p97> . ob2)
	               (<t49> . tr5) (<ap20> . a5) (<ap88> . a10)) 1 1.0)
	             ((inside-airplane ob3 pl1) case-test-5-14-4
	              ((<p52> . ob3) (<a91> . pl1) (<p45> . ob1) (<a20> . pl2)
	               (<ap32> . a10) (<ap26> . a3)) 2 0.6666667)
	             ((inside-airplane ob1 pl2) case-test-5-14-4
	              ((<p52> . ob3) (<a91> . pl1) (<p45> . ob1) (<a20> . pl2)
	               (<ap32> . a10) (<ap26> . a3)) 1 0.5)
	             ((inside-truck ob2 tr5) case-test-4-2-3
	              ((<a26> . pl1) (<ap90> . a9) (<p97> . ob2)
	               (<t49> . tr5) (<ap20> . a5) (<ap88> . a10)) 2 0.6666667))


(simple-select-replay-cases)

Given the match structure *cover-matches*, the system then needs to select a
set of best matching cases. The function simple-select-replay-cases sets the
global variable *replay-cases* which contains this set.


Step 3. Retrieval 

(load-cases)

Once the matching has been performed on the case header, those cases selected
for replay are retrieved from memory. A copy of the case with a unique name is
made for use by the analogical planner.


Step 4. Merge Mode Selection

(setf *merge-mode* '<strategy>)

If multiple cases were retreived and initialized for a multi-goal new problem,
then various strategies can be used to determine the way they old cases will be
replayed. Setting the global variable *merge-mode* to one of the options below
will affect the performance ofd the system. The default merge mode is SABA.

saba:    (default) sub-goal always before applying, and apply in right order 
saba-cr: saba and lets other control rules apply to goal choices
user:    by asking the user                                        
serial:  in a pre-defined serial order                          
random:  randomly                                                
smart:   by using a *set-of-interacting-goals*                   


Step 5. Case initialization

(init-guiding)

Initializes the cases before analogical planning (i.e., case replay) is
performed. If *merge-mode* is either serial or user, the case ordering is
determined. If the UI is being used and serial is chosen, the case order is
randomized.

The init-guiding function is also necessary previous to subsequent replays
after an initial replay is performed. That is, if the user does not wish to
re-retrieve a case, and instead, wishes to replay the same case again (perhaps
to examine the replay process in more depth), then a call of init-guiding sets
the case to a reinitialized state suitable for replay.


Step 6. Planning

(run)

For command line switches for the run function and further details, see the
Prodigy 4.0 Manual and Tutorial. 



-------------------------------------------------------------------------------

More on the replay function:

The replay function has a keyword parameter called :max-goals which corresponds
to the arument of the same name in evaluate-similarities. The default here is
the number of goals in the current problem. The user may change this default. 

The user may also leave off the :problem and/or the :domain keyword
arguments. If this is done, the current problem and/or domain is used instead
of reloaded. 

A specific depth-bound to use during the planning can be provided to the
function through the :depth-bound argument.

A specific case merging policy can be provided by the :merge-mode argument;
otherwise, the value of the global variable *merge-mode* is used.

A list of cases can be passed as a list of strings (see load-case-headers
function above) to the keyword :cases; otherwise the function uses the value of
the global variable *case-headers*. If this is nil, the function uses all cases
in the directory specified by *problem-path*.

If the output of the function is too verbose, use :verbose nil.


-------------------------------------------------------------------------------

(auto-use-cases)

Function auto-use-cases is like function replay except that the problem and
domain are assumed to be loaded prior to the invocation. Also, the
initialization function init-guiding is not called, and the planner has not
been invoked by the run function. The User Interface uses this function in the
Analogy-specific button called "Auto Case Retrieval" (along with calling tcl/tk
code to actually load the case display). It assumes that a problem and domain
was previously loaded. The "Replay" button then calls init-guiding and run.

(use-cases)

To perform this procedure while reducing the scope of case retrieval (as
explained with load-case-headers function above), use the function
use-cases. For example, (use-cases 2 0.6 "case-prob1-blancmange"
"case-prob1-hammer" "case-prob1-robot") limits the case base to only three
cases. The first parameter is the number of goals in the new problem whereas
the second is the match-threshold (see above). The UI uses this method in the
Analogy-specific button called "Manual Case Retrieval"


Example:

USER(102): (domain 'rocket)
; Loading /afs/cs/project/prodigy-1/analogy/domains/rocket/domain.lisp.
Properties of 1665 symbols removed.

Subgoal behaviour: SUBGOAL-ALWAYS-BEFORE-APPLY
Apply/Subgoal control rules:
;   Loading /afs/cs/project/prodigy-1/analogy/replay-crs.lisp.
Reading Meta predicate: analogy-get-guidance-goal
Reading Meta predicate: analogy-get-guidance-operator
Reading Meta predicate: analogy-get-guidance-bindings
Reading Meta predicate: analogy-decide-subgoal
Reading Meta predicate: analogy-decide-apply

Subgoal behaviour: SUBGOAL-AFTER-EVERY-TRY-TO-APPLY (default)
Apply/Subgoal control rules:Running load-domain.
0.0
USER(103): (problem 'prob2objs-2)
; Loading /afs/cs/project/prodigy-1/analogy/domains/rocket/probs/prob2objs-2.lisp.
T
USER(104): (use-cases 2 0.6 "case-prob1-blancmange" "case-prob1-hammer" "case-prob1-robot")

case list: ("case-prob1-blancmange" "case-prob1-hammer" "case-prob1-robot")
; Loading /afs/cs/project/prodigy-1/analogy/domains/rocket/probs/cases/headers/case-prob1-blancmange.lisp.

 Creating case header for "case-prob1-blancmange"
 Done loading case header!
 It was pushed into the list *case-headers*.
; Loading /afs/cs/project/prodigy-1/analogy/domains/rocket/probs/cases/headers/case-prob1-hammer.lisp.

 Creating case header for "case-prob1-hammer"
 Done loading case header!
 It was pushed into the list *case-headers*.
; Loading /afs/cs/project/prodigy-1/analogy/domains/rocket/probs/cases/headers/case-prob1-robot.lisp.

 Creating case header for "case-prob1-robot"
 Done loading case header!
 It was pushed into the list *case-headers*.

 Done loading all case headers!
 The global variable *case-headers* points at them.
 case-subst ((LOCA . <L20>) (LOCB . <L15>) (ROBOT . <O77>) (APOLLO . <R31>)) 
 old-goals ((AT <O77> <L15>)) 
 old-footprint-by-goal (((AT <O77> <L15>) (AT <O77> <L20>) (AT <R31> <L20>)))
sorted initial state ((AT OBJ2 <L20>) (AT <R31> <L20>))
old-footprint-goal: ((AT <O77> <L20>) (AT <R31> <L20>))
goal-subst: ((<L15> . LOCB) (<O77> . OBJ2))
old-subst-footprint-state: ((AT OBJ2 <L20>) (AT <R31> <L20>))
Initial state match: ((<L15> . LOCB) (<O77> . OBJ2) (<L20> . LOCA) (<R31> . R1))
New state: ((AT R1 LOCA) (AT OBJ1 LOCA) (AT OBJ2 LOCA))
sublis match old-subst-fooprint-state ((AT OBJ2 LOCA) (AT R1 LOCA))
length-intersection-initial-state: 2
sorted initial state ((AT OBJ1 <L20>) (AT <R31> <L20>))
old-footprint-goal: ((AT <O77> <L20>) (AT <R31> <L20>))
goal-subst: ((<L15> . LOCB) (<O77> . OBJ1))
old-subst-footprint-state: ((AT OBJ1 <L20>) (AT <R31> <L20>))
Initial state match: ((<L15> . LOCB) (<O77> . OBJ1) (<L20> . LOCA) (<R31> . R1))
New state: ((AT R1 LOCA) (AT OBJ1 LOCA) (AT OBJ2 LOCA))
sublis match old-subst-fooprint-state ((AT OBJ1 LOCA) (AT R1 LOCA))
length-intersection-initial-state: 2
 case-subst ((LOCA . <L1>) (LOCB . <L54>) (HAMMER . <O44>) (APOLLO . <R1>)) 
 old-goals ((AT <O44> <L54>)) 
 old-footprint-by-goal (((AT <O44> <L54>)))
 case-subst ((LOCA . <L96>) (LOCB . <L77>) (BLANCMANGE . <O39>) (APOLLO . <R74>)) 
 old-goals ((AT <O39> <L77>)) 
 old-footprint-by-goal (((AT <O39> <L77>) (AT <O39> <L96>) (AT <R74> <L96>)))
sorted initial state ((AT OBJ2 <L96>) (AT <R74> <L96>))
old-footprint-goal: ((AT <O39> <L96>) (AT <R74> <L96>))
goal-subst: ((<L77> . LOCB) (<O39> . OBJ2))
old-subst-footprint-state: ((AT OBJ2 <L96>) (AT <R74> <L96>))
Initial state match: ((<L77> . LOCB) (<O39> . OBJ2) (<L96> . LOCA) (<R74> . R1))
New state: ((AT R1 LOCA) (AT OBJ1 LOCA) (AT OBJ2 LOCA))
sublis match old-subst-fooprint-state ((AT OBJ2 LOCA) (AT R1 LOCA))
length-intersection-initial-state: 2
sorted initial state ((AT OBJ1 <L96>) (AT <R74> <L96>))
old-footprint-goal: ((AT <O39> <L96>) (AT <R74> <L96>))
goal-subst: ((<L77> . LOCB) (<O39> . OBJ1))
old-subst-footprint-state: ((AT OBJ1 <L96>) (AT <R74> <L96>))
Initial state match: ((<L77> . LOCB) (<O39> . OBJ1) (<L96> . LOCA) (<R74> . R1))
New state: ((AT R1 LOCA) (AT OBJ1 LOCA) (AT OBJ2 LOCA))
sublis match old-subst-fooprint-state ((AT OBJ1 LOCA) (AT R1 LOCA))
length-intersection-initial-state: 2
 Resetting (setting to nil) *case-cache* and *case-headers*
; Loading /afs/cs/project/prodigy-1/analogy/domains/rocket/probs/cases/headers/case-prob1-blancmange.lisp.

 Creating case header for "case-prob1-blancmange"
 Done loading case header!
 It was pushed into the list *case-headers*.
#<P-O: BLANCMANGE object> 
#<P-O: APOLLO rocket> 
  2 n2 (done)
; Loading /afs/cs/project/prodigy-1/analogy/domains/rocket/probs/cases/case-prob1-blancmange.lisp.

 Creating case "case-prob1-blancmange"
; Loading /afs/cs/project/prodigy-1/analogy/domains/rocket/probs/cases/headers/case-prob1-robot.lisp.

 Creating case header for "case-prob1-robot"
 Done loading case header!
 It was pushed into the list *case-headers*.
#<P-O: ROBOT object> 
  2 n2 (done)
; Loading /afs/cs/project/prodigy-1/analogy/domains/rocket/probs/cases/case-prob1-robot.lisp.

 Creating case "case-prob1-robot"
"Done !" 
NIL
USER(105): (init-guiding)

List of available cases :
 0: "CASE-PROB1-ROBOT.18685"
 1: "CASE-PROB1-BLANCMANGE.18687"
 Current *merge-mode* is SABA
#S(GUIDING-CASE :NAME "CASE-PROB1-ROBOT.18685" :REAL-NAME "case-prob1-robot"
                :CASE-ROOT #<APPLIED-OP-NODE 1 NIL> :PTR #<GOAL-NODE 5 #<AT ROBOT LOCB>> :AUX-PTR NIL
                :EXHAUSTED NIL :INITIAL-STATE NIL :GOAL NIL :COVERED-GOALS ((AT OBJ2 LOCB))
                :LAST-FIRING-GOAL-RULE-AT-NODE NIL
                :BASE-SUBSTITUTION ((<L15> . LOCB) (<O77> . OBJ2) (<L20> . LOCA) (<R31> . R1))
                :ADDITIONAL-BINDINGS NIL :AUX-BINDINGS NIL
                :OBJS (#<P-O: APOLLO rocket> #<P-O: ROBOT object> #<P-O: LOCA location> #<P-O: LOCB location>))
USER(106): (run)
Creating objects (LOCA LOCB) of type LOCATION
Creating objects (OBJ1 OBJ2) of type OBJECT
Creating object R1 of type ROCKET

  2 n2 (done)
  4 n4 <*finish*>
Following case "CASE-PROB1-ROBOT.18685" 5 -- goal  (AT OBJ2 LOCB)
  5   n5 (at obj2 locb)
Following case "CASE-PROB1-ROBOT.18685" 6 -- operator UNLOAD-ROCKET
Following case "CASE-PROB1-ROBOT.18685" 7 -- bindings ((<OBJECT> . #<P-O: OBJ2 object>)
                                                       (<PLACE> . #<P-O: LOCB location>)
                                                       (<ROCKET> . #<P-O: R1 rocket>))
  7   n7 <unload-rocket obj2 locb r1>
Following case "CASE-PROB1-ROBOT.18685" 8 -- goal  (INSIDE OBJ2 R1)
  8     n8 (inside obj2 r1)
Following case "CASE-PROB1-ROBOT.18685" 9 -- operator LOAD-ROCKET
Following case "CASE-PROB1-ROBOT.18685" 10 -- bindings ((<OBJECT> . #<P-O: OBJ2 object>)
                                                        (<PLACE> . #<P-O: LOCA location>)
                                                        (<ROCKET> . #<P-O: R1 rocket>))
 10     n10 <load-rocket obj2 r1 loca>
Following case "CASE-PROB1-BLANCMANGE.18687" 5 -- goal  (AT OBJ1 LOCB)
 Decided to subgoal.
 11   n11 (at obj1 locb)
Following case "CASE-PROB1-BLANCMANGE.18687" 6 -- operator UNLOAD-ROCKET
Following case "CASE-PROB1-BLANCMANGE.18687" 7 -- bindings ((<OBJECT> . #<P-O: OBJ1 object>)
                                                            (<PLACE> . #<P-O: LOCB location>)
                                                            (<ROCKET> . #<P-O: R1 rocket>))
 13   n13 <unload-rocket obj1 locb r1>
Following case "CASE-PROB1-BLANCMANGE.18687" 8 -- goal  (INSIDE OBJ1 R1)
 Decided to subgoal.
 14     n14 (inside obj1 r1)
Following case "CASE-PROB1-BLANCMANGE.18687" 9 -- operator LOAD-ROCKET
Following case "CASE-PROB1-BLANCMANGE.18687" 10 -- bindings ((<OBJECT> . #<P-O: OBJ1 object>)
                                                             (<PLACE> . #<P-O: LOCA location>)
                                                             (<ROCKET> . #<P-O: R1 rocket>))
 16     n16 <load-rocket obj1 r1 loca>
 Decided to apply guided.
 #<LOAD-ROCKET [<OBJECT> OBJ1] [<PLACE> LOCA] [<ROCKET> R1]>
Following case "CASE-PROB1-BLANCMANGE.18687" 11 -- apply
 17     n17 <LOAD-ROCKET OBJ1 R1 LOCA> ("CASE-PROB1-BLANCMANGE.18687" 10) [a:1]
Following case "CASE-PROB1-BLANCMANGE.18687" 12 -- goal  (AT R1 LOCB)
 Decided to subgoal.
 18     n18 (at r1 locb)
Following case "CASE-PROB1-BLANCMANGE.18687" 13 -- operator MOVE-ROCKET
Following case "CASE-PROB1-BLANCMANGE.18687" 14 -- bindings ((<ROCKET> . #<P-O: R1 rocket>))
 20     n20 <move-rocket r1>
 Decided to apply guided.
 #<LOAD-ROCKET [<OBJECT> OBJ2] [<PLACE> LOCA] [<ROCKET> R1]>
Following case "CASE-PROB1-ROBOT.18685" 11 -- apply
 21     n21 <LOAD-ROCKET OBJ2 R1 LOCA> ("CASE-PROB1-ROBOT.18685" 10) [a:1]
 Goal (AT R1 LOCB):
 Goal causes a goal loop or is true in state.
 Marking all dependent steps to be skipped. Advancing case.
 Decided to apply guided.
 #<MOVE-ROCKET [<ROCKET> R1]>
Following case "CASE-PROB1-BLANCMANGE.18687" 15 -- apply
 22     n22 <MOVE-ROCKET R1> ("CASE-PROB1-BLANCMANGE.18687" 14)
 Decided to apply guided.
 #<UNLOAD-ROCKET [<OBJECT> OBJ1] [<PLACE> LOCB] [<ROCKET> R1]>
Following case "CASE-PROB1-BLANCMANGE.18687" 16 -- apply
 End of current guiding case.
 Switching to the last available case.
 23   n23 <UNLOAD-ROCKET OBJ1 LOCB R1> ("CASE-PROB1-BLANCMANGE.18687" 7) [a:1]
 Decided to apply guided.
 #<UNLOAD-ROCKET [<OBJECT> OBJ2] [<PLACE> LOCB] [<ROCKET> R1]>
Following case "CASE-PROB1-ROBOT.18685" 17 -- apply
 End of current guiding case.
 23   n24 <UNLOAD-ROCKET OBJ2 LOCB R1> ("CASE-PROB1-ROBOT.18685" 7)
Achieved the following top-level goals:
((AT OBJ1 LOCB) (AT OBJ2 LOCB))

Solution:
	<load-rocket obj1 r1 loca> ("CASE-PROB1-BLANCMANGE.18687" 10)
	<load-rocket obj2 r1 loca> ("CASE-PROB1-ROBOT.18685" 10)
	<move-rocket r1> ("CASE-PROB1-BLANCMANGE.18687" 14)
	<unload-rocket obj1 locb r1> ("CASE-PROB1-BLANCMANGE.18687" 7)
	<unload-rocket obj2 locb r1> ("CASE-PROB1-ROBOT.18685" 7)

#<PRODIGY result: T, 0.817 secs, 24 nodes, 1 sol>
USER(107): 
