SHOP (Simple Hierarchical Ordered Planner) is a Hierarchical Task-Network (HTN) planner in which all task decompositions produce totally ordered sets of subtasks. SHOP plans for these subtasks in the same order that they will later be executed. Thus, whenever it plans for a task, SHOP already knows the task's input state, because it has already completely planned everything that comes before the task. This keeps the implementation of the planner very simple, while allowing it to use a more expressive state representation than most other AI planners.
SHOP is distributed under the terms of the GNU General Public License (a copy of which is included here). It is free software, it comes with absolutely no warranty, and you are allowed to redistribute it under certain conditions. See the GNU General Public License for details.
In the expressions defined below, there are five kinds of symbols: variable symbols, constant symbols, function symbols, primitive task symbols, and compound task symbols. To distinguish among these symbols, SHOP uses the following conventions:
In everything that follows, a ground expression is one that contains no variable symbols.
A term is either a variable symbol, a constant symbol, or a list of the form
(f t1 t2 ... tn)
where f is a function symbol and each ti is a term. A logical atom is a list of the form
(p t1 t2 ... tn)
where p is a predicate symbol and each ti is a term. A literal is any of the following:
A conjunct is a list of literals (l1 l2 l3 ... ln). A tagged conjunct is a list of the form (:first . C) where C is a conjunct. (The :first tag is a way to indicate to the theorem prover that we only want to see the first proof of C, rather than every possible proof. For details, see the discussion of tagged conjuncts at the end of Section 2.3, and the description of find-satisfiers in Section 3.)
An axiom is an expression of the following form, where a is a logical atom and each Ci is a conjunct or a tagged conjunct:
(:- a C1 C2 C3 ... Cn)
The axiom's head is the atom a, and its tail is the list (C1 C2 C3 ... Cn). The intended meaning of an axiom is that a is true if C1 is true, or if C1 is false but C2 is true, or if both C1 and C2 are false but C3 is true, ..., or if all of C1, C2, C3, ..., Cn-1 are false but Cn is true. For example, the following axiom says that a location is in walking distance if the weather is good and the location is within two miles of home, or if the weather is not good and the location is within one mile of home:
(:- (walking-distance ?x) ((weather-is good) (distance home ?x ?d) (eval (<= '?d 2))) ((distance home ?x ?d) (eval (<= '?d 1))))
The quote marks in the expressions (<= '?d 2) and (<= '?d 2) are to prevent the Lisp evaluator from trying to evaluate the value of ?d.
A substitution is a list of dotted pairs of the form
((x1 . t1) (x2 . t2) ... (xk . tk))
where every xi is a variable symbol and every ti is a term. If u is a substitution and e is an expression, then eu is the substitution instance produced by taking e and replacing each occurrence of each variable symbol xi with the corresponding term ti. If u and v are two substitutions, then u is a generalization of v if for every expression e, ev is a substitution instance of eu.
If e is an expression and x1, x2, ..., xk are the variable symbols in e, then a standardizer for e is a substitution of the form
((x1 . y1) (x2 . y2) ... (xk . yk))
where each yi is a new variable symbol that is not used anywhere else.
If d and e be two expressions and there is a substitution u such that du = eu, then we say that d and e are unifiable and u is a unifier of d and e. If the unifier u is a generalization of every unifier of d and e, then u is a most general unifier (or mgu) of d and e.
A state is a list of ground atoms intended to represent some "state of the world". An axiom list is a list of axioms intended to represent what we can infer from a state. If C is a conjunct or a tagged conjunct, then C is a consequent of a state S and an axiom list X if there for every literal l in C, l is a consequent from S and X. The literal l is a consequent of S and X if any of the following is true:
If C is a consequent of S and X, then it is a most general consequent of S and X for every consequent C' of S and X, if C is a substitution instance of C' then C' is also a substitution instance of C.
Let S be a state, X be an axiom list, and C be an ordinary (i.e., non-tagged) conjunct. If there is a substitution u such that Cu is a consequent of S and X, then we say that S and X satisfy C, and that u is a satisfier for C from S and X. The satisfier u is a most general satisfier (or mgs) if for every generalization v of u such that Cv is a consequent of S and X, u is also a generalization of v. Note that if u is an mgs for C from S and X, then Cu is a most general consequent of S and X.
A conjunct can have several distinct mgs's. For example, suppose X contains the "walking distance" axiom given earlier, and S is the state
((weather-is good) (distance home convenience-store 1) (distance home gas-station 2))
Then there are two mgs's for the conjunct ((walking-distance ?y)) from S and X: ((?y . convenience-store)) and ((?y . gas-station)).
Let S be a state, X be an axiom list, and C = (:first C') be a tagged conjunct, and suppose let u1, u2, ..., uk be all of the mgs's for C' from S and X, listed in the order that a left-to-right search would find them. Then the first one of these mgs's (i.e., u1) is the mgs for C' from S and X. For example, if S and X are as in the previous example, then the mgs for the tagged conjunct (:first (walking-distance ?y)) from S and X is ((?y . convenience-store)).
A task atom is an expression of the form
(s t1 t2 ... tn)
where s is a task symbol, and the arguments t1, t2, ..., tn are terms. The task atom is primitive if s is a primitive task symbol, and it is compound if s is a compound task symbol.
An operator is a list of the form
(:operator h D A)
where h is a primitive task atom (called the operator's head), and D (the operator's deletions) and A (the operator's additions) are condition lists that contain no variable symbols other than those in h. The intent of an operator is to specify that the task h can be accomplished by modifying the current state of the world to remove every logical atom in D and add every logical atom in A.
Let S be a state, t be a primitive task atom, and o be the operator (:operator h D A). Suppose that there is an mgu u for t and h, and that hu is ground. Then we say that o matches t, and that the list (ou) is a simple plan for t. If we execute ou in the state S, it produces the state ou(S) = (S - Au) U Au. Here is an example:
S =
((has-money john 40) (has-money mary 30))t =
(!set-money john 40 35)o =
(:operator (!set-money ?person ?old ?new) ((has-money ?person ?old)) ((has-money ?person ?new)))u =
((?person . john) (?old . 40) (?new . 35))
ou =
(:operator (!set-money john 40 35) ((has-money john 40)) ((has-money john 35)))ou(s) =
((has-money john 35) (has-money mary 30))
A task list is a list of task atoms. A method is a list of the form
(:method h C1 T1 C2 T2 ... Ck Tk)
where
The purpose of a method is to specify the following:
Let m = (:method h C1 T1 C2 T2 ... Ck Tk) be a method, S be a state, and t be a task atom (which may or may not be ground). Suppose there is an mgu u that unifies t with h (in which case we say that m matches t). Furthermore, suppose m has a precondition Ci such that S satisfies Ciu (if there is more than one such Ci, then let Ci be the first one). Then Ci and Ti are m's active precondition and m's active tail, respectively. Let V = {v1, v2, ..., vn} be the set of all mgs's for Ciu. There are two cases:
The result of applying (mu)v to (gu)v is the task list returned by eval((Tiu)v), where eval is the Lisp eval function. This task list is called a simple reduction of g by m. Here is an example:
s =
((has-money john 40) (has-money mary 30))g =
(transfer-money john mary 5)m =
(:method (transfer-money ?p1 ?p2 ?amount) ((has-money ?p1 ?m1) (has-money ?p2 ?m2) (eval (>= ?m1 ?amount))) `((!set-money ?p1 ?m1 ,(- ?m1 ?amount)) (!set-money ?p2 ?m2 ,(+ ?m2 ?amount))))h =
(transfer-money ?p1 ?p2 ?amount)C1 =
((has-money ?p1 ?m1) (has-money ?p2 ?m2) (eval (>= ?m1 ?amount)))T1 =
`((!set-money ?p1 ?m1 ,(- ?m1 ?amount)) (!set-money ?p2 ?m2 ,(+ ?m2 ?amount))))u =
((?p1 . john) (?p2 . mary) (?amount . 5))v =
((?m1 . 40) (?m2 . 30))(C1u)v =
((has-money john 40) (has-money mary 30) (eval (>= 40 30)))(Tu)v =
`((!set-money john 40 ,(- 40 5)) (!set-money mary 30 ,(+ 30 5)))eval((Tu)v) =
((!set-money john 40 35) (!set-money mary 30 35))
In m's tail, the backquote and commas are Lisp constructs that produce selective evaluation of portions of the expression.
A plan is a list of simple plans (or equivalently, a list of heads of ground operator instances). If p is a plan and S is a state, then p(S) is the state produced by starting with S and executing the ground operator instances in the order that their heads appear in p.
A planning domain is a list of axioms, operators, and methods. A planning problem is a triple (S,T,D), where S is a state T is a list of tasks to be accomplished in S, and D is a planning domain. If (S, T, D) is a planning problem, then plans(S, T, D), the set of all plans for T from s in D, is defined recursively as follows.
Case 1: T is empty. Then plans(S, T, D) contains exactly one plan, namely the empty plan.
Case 2: T is nonempty. Then let t = car(T) be the first task atom in T, and T' = cdr(T) be the remaining task atoms.
- Case 2a: t is primitive. Then
- plans(S, T, D) = {cons(t, q) : p is a simple plan for t and q is in plans(p(S), T', D)}
- Case 2b: t is compound. Then
- plans(S, T, D) = U {plans(S, append(r, T'), D) : r is a simple reduction of t in S}.
Here is an example:
S =
nilT =
((do-both op1 op2))D =
((:operator (do ?operation) nil ((did ?operation)))) (:method (do-both ?x ?y) nil ((do ?x) (do ?y))) (:method (do-both ?x ?y) nil ((do ?y) (do ?x))))plans(S, T, D) =
(((do op1) (do op2) (did op1))) ((do op2) (do op1) (did op2))))
The keyword arguments are as follows:
:all |
do a depth-first search for all plans in plans(S, T, M) |
:first (the default) |
do a depth first search, stopping after the first plan found |
:shallowest |
do a depth-first search for the shallowest plan in
the search space (or the |
:all-shallowest |
do a depth-first search for all shallowest plans in the search space |
:id-all |
do an iterative-deepening search for all shallowest plans in plans(S,T,M) |
:id-first |
do an iterative-deepening search, stopping after the first plan found |
0 |
print nothing |
1 (the |
print the plans found, plus some statistics
about |
2 |
print the above, plus information about success
or |
3 |
print the above, plus a message each time the |
4 |
print the above, plus the task at each node of
the |
5 |
print the above, plus information about success
or |
6 |
print the above, plus the goal at each node of |
X1 = |
((:- (a ?x) ((b ?x)) ((c ?x))))) |
X2 = |
((:- (a ?x) ((b ?x))) (:- (a ?x) ((c ?x)))) |
In X1, the single axiom acts like an if-then-else: if ((b ?x)) is true then find-satisfiers returns the satisfiers for (b ?x); otherwise if ((c ?x)) is true then it returns the satisfiers for (c ?x). In X2, the set of axioms acts like a logical "or": find-satisfiers returns every satisfier for (b ?x) and every satisfier for (c ?x). For example,
would return (((?u . 2))), but
would return (((?u . 2)) ((?u . 3))).
Likewise, the following two calls to the planner will both find the same shallowest plan, but will search for it in different ways:
Which call runs faster depends on the shape of the search space. In the examples that I have tried, depth-first search ran more quickly than the equivalent iterative-deepening search. However, it certainly is possible to construct planning problems in which iterative deepening runs faster than depth-first search, as well as problems in which iterative deepening terminates but depth-first search doesn't.