tutorials

scripts.run_tutorial module

This tutorial considers a 1D task and motion planning domain with 1 robot and 3 blocks. Robot configurations and block poses are nonnegative integers from 0 to num_poses. The robot can move to other configurations, pick blocks, and place blocks. For simplicity, assume that the robot cannot collide with blocks when it moves, picks, or places. Thus, the only collisions are between pairs of blocks.

We consider the following problem specification. The robot starts at configuration 0. Each block i starts at pose i. The goal conditions require each block i be shifted right one pose to pose i+1. There are num_poses = 10^10 possible poses for each block.

blocks = ['block%i'%i for i in range(3)]
num_poses = pow(10, 10) # 10^10 possible poses!

initial_config = 0 # the initial robot configuration is 0
initial_poses = {block: i for i, block in enumerate(blocks)} # the initial pose for block i is i

goal_poses = {block: i+1 for i, block in enumerate(blocks)} # the goal pose for block i is i+1

We start by creating the object types for configurations (CONF), blocks (BLOCK), and poses (POSE). Types are used to restrict the domain of predicates to only apply to sensible objects. These can be alternatively thought of as implicit unary predicates.

CONF, BLOCK, POSE = Type(), Type(), Type()

Next, we define the predicates (Pred) we will use. A predicate is specified by a list of types for its arguments. For our domain, we use the fluent predicates AtConf to model the robot’s configuration, AtPose to model the pose of each block, HandEmpty to indicate the robot is not holding anything, and Holding to specify the block the robot is holding.

We will later define an axiom that uses the derived predicate Safe to compactly handle collision checking. Safe indicates that the first block is at some pose that is not in collision with the second block at the specified pose.

Finally, we define two static predicates, predicates that have constant truth value throughout a problem. LegalKin indicates that a block pose and robot configuration satisfy a kinematic constraint. Namely, the robot must be at the same pose as the block in order to pick or place the block. CollisionFree is true if and only if the first block at the first pose is not in collision with the second block at the second pose.

# Fluent predicates
AtConf = Pred(CONF)
AtPose = Pred(BLOCK, POSE)
HandEmpty = Pred()
Holding = Pred(BLOCK)

# Derived predicates
Safe = Pred(BLOCK, BLOCK, POSE)

# Static predicates
LegalKin = Pred(POSE, CONF)
CollisionFree = Pred(BLOCK, POSE, BLOCK, POSE)

We define several parameters (Param) that will be arguments of several actions, axioms, and conditional streams. Each parameter is of the type specified by its argument and had a unique, anonymous name. Parameters are only meaningful within the local scope of a action, axiom, or conditional stream. Sharing parameters simply results in more compact code.

B1, B2 = Param(BLOCK), Param(BLOCK)
P1, P2 = Param(POSE), Param(POSE)
Q1, Q2 = Param(CONF), Param(CONF)

Next, we identify three actions (Action). Each action is specified by a name, list of parameters, condition logical formula, and effect logical formula. Logical formulas are nested combinations of Atom, Not, And, Or, Exists, ForAll, Equal, and When.

The 'pick' action has a block B1, pose P1, and configuration Q1 parameter. Its condition is that block B1 be at pose P1, the robot’s hand is empty, the robot be at configuration Q1, and pose P1 and configuration Q1 satisfy the kinematic constraint. Its effect is that the robot is now holding B1, block B1 is no longer at pose P1, and the robot’s hand is no longer empty.

The 'place' action is the inverse of a the 'pick' action. It has an additional condition that each of the other blocks B2 is at a safe pose with respect to block B1 at pose P1.

The 'move' action has parameters Q1 and Q2 for the start and end configurations. Its condition is that the robot starts at configuration Q1 and its effect is that the robot is at configuration Q2 instead of configuration Q1.

actions = [
   Action(name='pick', parameters=[B1, P1, Q1],
     condition=And(AtPose(B1, P1), HandEmpty(), AtConf(Q1), LegalKin(P1, Q1)),
     effect=And(Holding(B1), Not(AtPose(B1, P1)), Not(HandEmpty()))),

   Action(name='place', parameters=[B1, P1, Q1],
     condition=And(Holding(B1), AtConf(Q1), LegalKin(P1, Q1),
       ForAll([B2], Or(Equal(B1, B2), Safe(B2, B1, P1)))), # TODO - convert to finite blocks case?
     effect=And(AtPose(B1, P1), HandEmpty(), Not(Holding(B1)))),

   Action(name='move', parameters=[Q1, Q2],
     condition=AtConf(Q1),
     effect=And(AtConf(Q2), Not(AtConf(Q1)))),
 ]

We define an axiom (Axiom) that automatically infers Safe at each state before an action is performed. Safe is true if B2 at its current pose P2 is not in collision with B1 at pose P1.

As an aside, one could avoid using this axiom by substituting Safe for condition within the 'place' action. However, this would greatly increase the number of implicit parameters for 'place' as it would then involve a pose parameter for each object. Thus, the number of instantiations would grow exponentially in the number of blocks which is undesirable for practical performance. Axioms allow us to avoid exponential growth by creating an intermediate rule which infers safety for each block B2 separately.

axioms = [
  Axiom(effect=Safe(B2, B1, P1),
        condition=Exists([P2], And(AtPose(B2, P2), CollisionFree(B1, P1, B2, P2)))), # Infers B2 is at a safe pose wrt B1 at P1
]

In order to represent the set of poses as well as evaluate LegalKin and CollisionFree, we define several conditional streams. A GeneratorStream is given by a list of parameter inputs, parameter outputs, conjunctive conditions that input values must satisfy, conjunctive effects which inputs + outputs will satisfy, and a function generator from values of inputs to a generator producing values of outputs.

The first GeneratorStream enumerates the set of poses from 0 to num_poses-1. It has no inputs and therefore no conditions, a single pose output P1, but no effects. Its generator simply enumerates poses ``0, 1, ..., num_poses.

The second GeneratorStream enumerates the configuration kinematic solutions for a given pose. It has a single pose input P1 and a single configuration output Q1. P1 and Q1 are guarantee to satisfy LegalKin as indicated using outputs. Its generator just returns p as there is only one possible configuration per pose.

A TestStream is a special type of GeneratorStream which has no outputs. Thus, it can be specified using a boolean test function which determines if effects hold instead of a generator.

The TestStream evaluates collision checks between block B1 at pose P1 and block B2 at pose P2. In our simplified model, two blocks are not in collision if they are at different poses.

# Conditional stream declarations
cond_streams = [
  GeneratorStream(inputs=[], outputs=[P1], conditions=[], effects=[],
                  generator=lambda: xrange(num_poses)), # Enumerating all poses

  GeneratorStream(inputs=[P1], outputs=[Q1], conditions=[], effects=[LegalKin(P1, Q1)],
                  generator=lambda p: [p]), # Inverse kinematics

  TestStream(inputs=[B1, P1, B2, P2], conditions=[], effects=[CollisionFree(B1, P1, B2, P2)],
             test=lambda b1, p1, b2, p2: p1 != p2, eager=True), # Collision checking
]

The initial state is represented as a list of initial_atoms. For our problem, the robot starts at initial_config with its hand empty. Each block starts at its corresponding initial pose.

initial_atoms = [
  AtConf(initial_config),
  HandEmpty()
] + [
  AtPose(block, pose) for block, pose in initial_poses.iteritems()
]

The set of goal states is represented as a list of goal_literals. For our problem, each block has a goal condition that it ends up at its corresponding goal pose.

goal_literals = [AtPose(block, pose) for block, pose in goal_poses.iteritems()]

STRIPStream automatically infers the type of and stores any objects passed to predicates. In the event in which an object is not already included in the initial_atoms, goal_literals, actions, or axioms, it can be specified using constants. Because STRIPStream cannot infer the type of a standalone object, it must be wrapped with the desired type. Suppose that the robot can move off the line to a None configuration. We can indicate this by including CONF(None) in constants.

constants = [
  CONF(None) # Any additional objects
]

Putting this all together, we arrive at the definition of a STRIPStreamProblem instance.

problem = STRIPStreamProblem(initial_atoms, goal_literals, actions + axioms, cond_streams, constants)

Finally, we can solve this problem by calling incremental_planner() or focused_planner(). Each planner has an argument search which specifies a search subroutine to use within the planner. This can currently be either get_bfs() to use an inefficient Python breadth-first search or get_fast_downward() to use an efficient PDDL planner. However, one must install FastDownward to use get_fast_downward().

#search = get_fast_downward('eager') # dijkstra | astar | wastar1 | wastar2 | wastar3 | eager | lazy
search = get_bfs()

plan, _ = incremental_planner(problem, search=search, frequency=1)
#plan, _ = focused_planner(problem, search=search, greedy=False)
print
print 'Plan:', convert_plan(plan)

You can run this tutorial using the following command.

python -m scripts.run_tutorial
scripts.run_tutorial.create_problem()[source]

Creates the 1D task and motion planning STRIPStream problem.

Returns:a STRIPStreamProblem
scripts.run_tutorial.main()[source]

Creates and solves the 1D task and motion planning STRIPStream problem.