Iterated Nodes and variables

Question:
I have begun using oplan for a robot planning task and I have a question about the foreach operator. I am unable to get foreach to work with anything but an explicit set. In the task formalism manual it states that since the expansion of the iteration set is done while planning, the set may be read from an external source or instantiated through variables. I would be very interested in doing this. For example i would like to have a team move schema that would move each member of the team. Where the particular team is variable of the schema.
While it's true that the expansion of the iteration set is done while planning, it is not fully general. The values of all variables involved must be known at the time the schema containing the iteration is selected for expansion. So "plan state variables" (PSVs), whose value is determined later on, cannot occur.

This is a subtle and non-obvious property of O-Plan that comes up from time to time. Some things can be done only with so-called "schema variables", but not with PSVs.

When O-Plan selects a schema to use when expanding a node, it's possible to determine the values of some of the schema's variables right then and there. The remaining variables each become a sub-problem of the overall planning problem. O-Plan accumulates constraints about these variables - while working on all the rest of the plan - until it becomes clear what values the variables must have. If more than one possibility remains when the plan is complete - complete except for PSVs that still lack values - O-Plan tries to find a consistent set of values for all such PSVs by picking from among the remaining possible values.

There are two reasons why the expansion of iteration sets cannot involve PSVs. One is that O-Plan wants to know right then and there what nodes the schema will introduce into the plan. In principle, O-Plan could wait until any PSVs involved are given values, and introduce the iteration nodes then, but that way of doing things has not been implemented and its consequences have not been fully explored.

The second reason is that a PSV must have a single object - from a declared type - as a value. But the iteration needs a variable that has a set as a value instead. That's something that's possible for variables whose values can be determined at "expansion time", but not for PSVs. Again, something more general could be done in principle, but we haven't yet done so, and the consequences have not been fully explored.

So what can you do?

I'll give a couple of examples below. In the first, we assert a team-membership fact that is picked up by an only_use_if condition. (Technically, only_use_for_query might be more correct, but the behaviour of the planner is much less predictable / explainable / understandable in that case.)

A compute condition might be used instead if, say, the team membership were stored in a database somewhere. That's illustrated by the second example which is just a demonstration of recursive expansion. It uses a Lisp procedure called enumerate that returns a list of integers as in the following examples:

  (enumerate 1 3)  returns (1 2 3)

  (enumerate 1 10) returns (1 2 3 4 5 6 7 8 9 10)

The difficult case is when team-membership varies in "interesting" ways as a result of actions in the plan, such as when an action moves someone from one team to another. Some cases of that sort will work using the only_use_if technique while others will not, and it's difficult to explain in a simple way which cases are in each category.

The way it ought to be is something like this: it will work so long as the membership is assigned at less detailed levels of the plan than the level at which the membership is picked up by an only_use_if and used in an iteration, or at the same level but at a node that has already been constrained to be earlier than the node at which the value is picked up by the only_use_if.

In the example below, the membership is asserted at the least detailed level and at the earliest node, so it should (and does) work.

In other cases, it will depend on the order in which O-Plan expands nodes, which in the current implementation is not quite what the above "way it ought to work" rule requires.


;;; An iteration test

;;; Author: Jeff Dlaton

types team = (team1 team2),
      member = (m1 m2 m3 m4 m5),
      place = (paris london berlin);

task move_team1_from_london_to_paris;
  nodes 1 start,
        2 finish,
        3 action {move_team team1 london paris};
  orderings 1 ---> 3, 3 ---> 2;
  effects {team_members team1} = {m1 m3} at 1;
end_task;

schema move_team;
   vars ?team = ?{type team},
        ?from = ?{type place},
        ?to   = ?{type place},
        ?members;
   expands {move_team ?team ?from ?to};
   nodes 
      1 foreach action {move_individual ?m ?from ?to}
           for ?m over ?members;
   conditions
      only_use_if {team_members ?team} = ?members;
end_schema;

schema move_individual;
   vars ?individual = ?{type member},
        ?from       = ?{type place},
        ?to         = ?{type place};
   expands {move_individual ?individual ?from ?to};
end_schema;

Note that the variable ?members is not given a type. It could have been declared "?member = ?{satisfies listp}", because its value will be a list. However, such declarations seem more appropriate when using compute conditions that explicitly provide lists. It's not possible to define an O-Plan type that has lists (or sets) as its members, so there is no ?{type T} declaration that could be used.


;;; Recursive expansion test

;;; Author: Jeff Dalton

;;; Updated: Thu May 28 15:14:31 1998 by Jeff Dalton

task expand_2_3;
  nodes 1 start,
        2 finish,
        3 action {expand top 2 3};
  orderings 1 ---> 3, 3 ---> 2;
end_task;

;;; ... other tasks omitted ...

schema expand;
  vars ?branching = ?{satisfies numberp},
       ?depth     = ?{satisfies numberp},
       ?d         = ?{satisfies numberp},
       ?children  = ?{satisfies listp};
  expands {expand ?? ?branching ?depth};
  nodes 
    1 foreach action {expand ?c ?branching ?d}
        for ?c over ?children;
  conditions 
    compute {> ?depth 0},
    compute {- ?depth 1} = ?d,
    compute {enumerate 1 ?branching} = ?children;
end_schema;

schema leaf;
  expands {expand ?? ?? 0};
end_schema;

language lisp;

  (defun enumerate (fr to) (loop for i from fr to to collect i))

end_language;

The first compute condition in the expand schema, "compute {> ?depth 0}", is required to ensure that only the leaf schema applies at depth 0.

AIAI Author: Jeff Dalton, April 27, 2001
Page maintained by oplan@ed.ac.uk, Last updated: Fri Apr 27 18:59:07 2001