Skip to content

Commit 74c07e5

Browse files
committed
Added separate LockedPlanningMethod and ClonedPlanningMethod calls.
This separates the @PlanningMethod decorator into two decorators: - @LockedPlanningMethod locks the current robot environment and executes the wrapped method. - @ClonedPlanningMethod clones the current robot environment and executes the wrapped method. This distinction is useful for planning calls that are so fast that they take less time than the cloning operation itself, such as optimistic plans like `SnapPlanner` and fast mathematical solutions such as `KunzCircularBlender`. This commit refactors all existing planning calls except `SnapPlanner` to use `@ClonedPlanningMethod` and updates the README to explain the distinction. `@PlanningMethod` is changed to inherit from `@ClonedPlanningMethod` but print a warning if it is used.
1 parent 39a7f70 commit 74c07e5

File tree

17 files changed

+150
-133
lines changed

17 files changed

+150
-133
lines changed

README.md

Lines changed: 27 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,16 @@ two subclasses:
2525
2. `prpy.planning.base.MetaPlanner`: combines the output of multiple motion
2626
planners, each of which is a `BasePlanner` or another `MetaPlanner`
2727

28-
Each planner has one or more *planning methods*, annotated with the
29-
`@PlanningMethod` decorator, that look like ordinary functions. However, unlike
30-
an ordinary function, calling a planning method clones the robot's environment
28+
Each planner has one or more *planning methods*, annotated with either the
29+
`@LockedPlanningMethod` or `@ClonedPlanningMethod decorator, that look like
30+
ordinary functions. However, unlike an ordinary function, calling a planning
31+
method either locks the robot environment or clones the robot's environment
3132
into a *planning environment* associated with the planner. Planning occurs in
32-
the cloned environment to allow PrPy to run multiple planners in parallel and
33-
to paralellize planning and execution.
33+
the locked or cloned environment to allow PrPy to run multiple planners in
34+
parallel and to paralellize planning and execution. In general, locked
35+
planning methods are used for calls that will terminate extremely quickly,
36+
while cloned planning methods are used for calls that might take a significant
37+
amount of time.
3438

3539
For example, the following code will use OMPL to plan `robot`'s active DOFs
3640
from their current values to to the `goal_config` configuration:
@@ -40,10 +44,10 @@ planner = OMPLPlanner('RRTConnect')
4044
output_path = planner.PlanToConfiguration(robot, goal_config)
4145
```
4246

43-
First, `robot.GetEnv()` is cloned into the the `planner.env` planning
44-
environment. Next, planning occurs in the cloned environment. Finally, the
45-
output path is cloned back into `robot.GetEnv()` and is returned by the
46-
planner.
47+
As this is a `@ClonedPlanningMethod`, `robot.GetEnv()` is cloned into the
48+
the `planner.env` planning environment. Planning occurs within this cloned
49+
environment. Finally, the output path is cloned back into `robot.GetEnv()`
50+
and is returned by the planner.
4751

4852
See the following sub-sections for more information about the built-in planners
4953
provided with PrPy, information about writing your own planner, and several
@@ -86,7 +90,7 @@ See the Python docstrings the above classes for more information.
8690

8791
### Common Planning Methods
8892

89-
There is no formal list of `@PlanningMethod`s or their arguments. However, we
93+
There is no formal list of `@*PlanningMethod`s or their arguments. However, we
9094
have found these methods to be useful:
9195

9296
- `PlanToConfiguration(robot, goal_config)`: plan the robot's active DOFs from
@@ -112,25 +116,25 @@ of these methods accept planner-specific keyword arguments.
112116
### Writing a Custom Planner
113117

114118
Implementing a custom planner requires extending the `BasePlanner` class and
115-
decorating one or more methods with the `@PlanningMethod` decorator. Extending
116-
the `BasePlanner` class constructs the planning environment `self.env` and
117-
allows PrPy to identify your planner as a base planner class, as opposed to a
118-
meta-planner. The `@PlanningMethod` decorator handles environment cloning and
119-
allows meta-planners to query the list of planning methods that the planner
120-
supports (e.g. to generate docstrings).
119+
decorating one or more methods with the `@LockedPlanningMethod` or
120+
`@ClonedPlanningMethod` decorator. Extending the `BasePlanner` class constructs
121+
the planning environment `self.env` and allows PrPy to identify your planner
122+
as a base planner class, as opposed to a meta-planner. The `@*PlanningMethod`
123+
decorators handle environment cloning or locking and allows meta-planners to
124+
query the list of planning methods that the planner supports (e.g. to generate
125+
docstrings).
121126

122127
Please obey the following guidelines:
123128

124-
- Assume that the cloned environment is locked during the entire call.
125-
- Subclass constructor **must** call `BasePlanner.__init__`.
126-
- A `@PlanningMethod` **must not** call another `@PlanningMethod`.
127-
- Each `@PlanningMethod` **must** accept the first argument `robot`, which is a
129+
- Assume that the environment is locked during the entire call.
130+
- Subclass constructors **must** call `BasePlanner.__init__`.
131+
- Each `@*PlanningMethod` **must** accept the first argument `robot`, which is a
128132
robot in the cloned environment.
129-
- Each `@PlanningMethod` **must** accept `**kwargs` to ignore arguments that
133+
- Each `@*PlanningMethod` **must** accept `**kwargs` to ignore arguments that
130134
are not supported by the planner.
131-
- Each `@PlanningMethod` **must** return a `Trajectory` which was created in
135+
- Each `@*PlanningMethod` **must** return a `Trajectory` which was created in
132136
the cloned environment.
133-
- When possible, use one of the defacto-standard `@PlanningMethod` names listed
137+
- When possible, use one of the defacto-standard `@*PlanningMethod` names listed
134138
below.
135139
- Raise a `PlanningError` to indicate an expected, but fatal, error (e.g.
136140
timeout with no collision-free path).

src/prpy/planning/base.py

Lines changed: 45 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -91,10 +91,41 @@ def __init__(self, message, errors):
9191
# TODO: Print the inner exceptions.
9292

9393

94-
class PlanningMethod(object):
94+
class LockedPlanningMethod(object):
95+
"""
96+
Decorate a planning method that locks the calling environment.
97+
"""
9598
def __init__(self, func):
9699
self.func = func
97100

101+
def __call__(self, instance, robot, *args, **kw_args):
102+
with robot.GetEnv():
103+
# Perform the actual planning operation.
104+
traj = self.func(instance, robot, *args, **kw_args)
105+
106+
# Tag the trajectory with the planner and planning method
107+
# used to generate it. We don't overwrite these tags if
108+
# they already exist.
109+
tags = GetTrajectoryTags(traj)
110+
tags.setdefault(Tags.PLANNER, instance.__class__.__name__)
111+
tags.setdefault(Tags.METHOD, self.func.__name__)
112+
SetTrajectoryTags(traj, tags, append=False)
113+
114+
return traj
115+
116+
def __get__(self, instance, instancetype):
117+
# Bind the self reference and use update_wrapper to propagate the
118+
# function's metadata (e.g. name and docstring).
119+
wrapper = functools.partial(self.__call__, instance)
120+
functools.update_wrapper(wrapper, self.func)
121+
wrapper.is_planning_method = True
122+
return wrapper
123+
124+
125+
class ClonedPlanningMethod(LockedPlanningMethod):
126+
"""
127+
Decorate a planning method that clones the calling environment.
128+
"""
98129
def __call__(self, instance, robot, *args, **kw_args):
99130
env = robot.GetEnv()
100131

@@ -110,10 +141,11 @@ def __call__(self, instance, robot, *args, **kw_args):
110141
joint_values[1] = cloned_robot.GetActiveDOFValues()
111142

112143
# Check for mismatches in the cloning and hackily reset them.
113-
# (This is due to a possible bug in OpenRAVE environment cloning where in
114-
# certain situations, the Active DOF ordering and values do not match the
115-
# parent environment. It seems to be exacerbated by multirotation joints,
116-
# but the exact cause and repeatability is unclear at this point.)
144+
# (This is due to a possible bug in OpenRAVE environment cloning
145+
# where in certain situations, the Active DOF ordering and values
146+
# do not match the parent environment. It seems to be exacerbated
147+
# by multirotation joints, but the exact cause and repeatability
148+
# is unclear at this point.)
117149
if not numpy.array_equal(joint_indices[0], joint_indices[1]):
118150
logger.warning("Cloned Active DOF index mismatch: %s != %s",
119151
str(joint_indices[0]),
@@ -126,27 +158,16 @@ def __call__(self, instance, robot, *args, **kw_args):
126158
str(joint_values[1]))
127159
cloned_robot.SetActiveDOFValues(joint_values[0])
128160

129-
# Perform the actual planning operation.
130-
planner_traj = self.func(instance, cloned_robot,
131-
*args, **kw_args)
161+
traj = super(ClonedPlanningMethod, self).__call__(
162+
instance, cloned_robot, *args, **kw_args)
163+
return CopyTrajectory(traj, env=env)
132164

133-
# Tag the trajectory with the planner and planning method
134-
# used to generate it. We don't overwrite these tags if
135-
# they already exist.
136-
tags = GetTrajectoryTags(planner_traj)
137-
tags.setdefault(Tags.PLANNER, instance.__class__.__name__)
138-
tags.setdefault(Tags.METHOD, self.func.__name__)
139-
SetTrajectoryTags(planner_traj, tags, append=False)
140165

141-
return CopyTrajectory(planner_traj, env=env)
142-
143-
def __get__(self, instance, instancetype):
144-
# Bind the self reference and use update_wrapper to propagate the
145-
# function's metadata (e.g. name and docstring).
146-
wrapper = functools.partial(self.__call__, instance)
147-
functools.update_wrapper(wrapper, self.func)
148-
wrapper.is_planning_method = True
149-
return wrapper
166+
class PlanningMethod(ClonedPlanningMethod):
167+
def __init__(self, func):
168+
logger.warn("Please explicitly declare a ClonedPlanningMethod "
169+
"instead of using PlanningMethod.")
170+
super(ClonedPlanningMethod, self).__init__(func)
150171

151172

152173
class Planner(object):

src/prpy/planning/cbirrt.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
#
77
# Redistribution and use in source and binary forms, with or without
88
# modification, are permitted provided that the following conditions are met:
9-
#
9+
#
1010
# - Redistributions of source code must retain the above copyright notice, this
1111
# list of conditions and the following disclaimer.
1212
# - Redistributions in binary form must reproduce the above copyright notice,
@@ -32,7 +32,7 @@
3232
import openravepy
3333
from ..util import SetTrajectoryTags
3434
from base import (BasePlanner, PlanningError, UnsupportedPlanningError,
35-
PlanningMethod, Tags)
35+
ClonedPlanningMethod, Tags)
3636
import prpy.kin
3737
import prpy.tsr
3838

@@ -48,7 +48,7 @@ def __init__(self):
4848
def __str__(self):
4949
return 'CBiRRT'
5050

51-
@PlanningMethod
51+
@ClonedPlanningMethod
5252
def PlanToConfigurations(self, robot, goals, **kw_args):
5353
"""
5454
Plan to multiple goal configurations with CBiRRT. This adds each goal
@@ -61,7 +61,7 @@ def PlanToConfigurations(self, robot, goals, **kw_args):
6161
kw_args.setdefault('smoothingitrs', 0)
6262
return self.Plan(robot, jointgoals=goals, **kw_args)
6363

64-
@PlanningMethod
64+
@ClonedPlanningMethod
6565
def PlanToConfiguration(self, robot, goal, **kw_args):
6666
"""
6767
Plan to a single goal configuration with CBiRRT.
@@ -72,7 +72,7 @@ def PlanToConfiguration(self, robot, goal, **kw_args):
7272
kw_args.setdefault('smoothingitrs', 0)
7373
return self.Plan(robot, jointgoals=[goal], **kw_args)
7474

75-
@PlanningMethod
75+
@ClonedPlanningMethod
7676
def PlanToEndEffectorPose(self, robot, goal_pose, **kw_args):
7777
"""
7878
Plan to a desired end-effector pose.
@@ -90,7 +90,7 @@ def PlanToEndEffectorPose(self, robot, goal_pose, **kw_args):
9090

9191
return self.Plan(robot, tsr_chains=[tsr_chain], **kw_args)
9292

93-
@PlanningMethod
93+
@ClonedPlanningMethod
9494
def PlanToEndEffectorOffset(self, robot, direction, distance,
9595
smoothingitrs=100, **kw_args):
9696
"""
@@ -153,7 +153,7 @@ def PlanToEndEffectorOffset(self, robot, direction, distance,
153153
**kw_args
154154
)
155155

156-
@PlanningMethod
156+
@ClonedPlanningMethod
157157
def PlanToTSR(self, robot, tsr_chains, smoothingitrs=100, **kw_args):
158158
"""
159159
Plan to a goal specified as a list of TSR chains. CBiRRT supports an

src/prpy/planning/chomp.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,9 @@
3232
import logging
3333
import numpy
3434
import openravepy
35-
from .. import tsr
3635
from ..util import SetTrajectoryTags
3736
from base import (BasePlanner, PlanningError, UnsupportedPlanningError,
38-
PlanningMethod, Tags)
37+
ClonedPlanningMethod, Tags)
3938
import prpy.tsr
4039

4140
logger = logging.getLogger(__name__)
@@ -192,7 +191,7 @@ def ComputeDistanceField(self, robot):
192191
logger.warning('ComputeDistanceField is deprecated. Distance fields are'
193192
' now implicity created by DistanceFieldManager.')
194193

195-
@PlanningMethod
194+
@ClonedPlanningMethod
196195
def OptimizeTrajectory(self, robot, traj, lambda_=100.0, n_iter=50,
197196
**kw_args):
198197
self.distance_fields.sync(robot)
@@ -215,7 +214,7 @@ def OptimizeTrajectory(self, robot, traj, lambda_=100.0, n_iter=50,
215214
SetTrajectoryTags(traj, {Tags.SMOOTH: True}, append=True)
216215
return traj
217216

218-
@PlanningMethod
217+
@ClonedPlanningMethod
219218
def PlanToConfiguration(self, robot, goal, lambda_=100.0, n_iter=15,
220219
**kw_args):
221220
"""
@@ -237,7 +236,7 @@ def PlanToConfiguration(self, robot, goal, lambda_=100.0, n_iter=15,
237236
SetTrajectoryTags(traj, {Tags.SMOOTH: True}, append=True)
238237
return traj
239238

240-
@PlanningMethod
239+
@ClonedPlanningMethod
241240
def PlanToEndEffectorPose(self, robot, goal_pose, lambda_=100.0,
242241
n_iter=100, goal_tolerance=0.01, **kw_args):
243242
"""
@@ -293,7 +292,7 @@ def PlanToEndEffectorPose(self, robot, goal_pose, lambda_=100.0,
293292

294293
# JK - Disabling. This is not working reliably.
295294
'''
296-
@PlanningMethod
295+
@ClonedPlanningMethod
297296
def PlanToTSR(self, robot, tsrchains, lambda_=100.0, n_iter=100,
298297
goal_tolerance=0.01, **kw_args):
299298
"""

src/prpy/planning/ik.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
from .. import ik_ranking
3434
from base import (BasePlanner,
3535
PlanningError,
36-
PlanningMethod)
36+
ClonedPlanningMethod)
3737

3838
logger = logging.getLogger(__name__)
3939

@@ -46,7 +46,7 @@ def __init__(self, delegate_planner=None):
4646
def __str__(self):
4747
return 'IKPlanner'
4848

49-
@PlanningMethod
49+
@ClonedPlanningMethod
5050
def PlanToIK(self, robot, goal_pose, ranker=ik_ranking.JointLimitAvoidance,
5151
num_attempts=1, **kw_args):
5252
from openravepy import (IkFilterOptions,

src/prpy/planning/mac_smoother.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030

3131
from ..util import (CopyTrajectory, SimplifyTrajectory, SetTrajectoryTags,
3232
IsTimedTrajectory)
33-
from base import (BasePlanner, PlanningError, PlanningMethod,
33+
from base import (BasePlanner, PlanningError, ClonedPlanningMethod,
3434
UnsupportedPlanningError, Tags)
3535
from openravepy import (Planner, PlannerStatus, RaveCreatePlanner,
3636
openrave_exception)
@@ -54,7 +54,7 @@ def __init__(self):
5454
' installed and in your OPENRAVE_PLUGIN path?'
5555
)
5656

57-
@PlanningMethod
57+
@ClonedPlanningMethod
5858
def RetimeTrajectory(self, robot, path):
5959
# Copy the input trajectory into the planning environment. This is
6060
# necessary for two reasons: (1) the input trajectory may be in another
@@ -72,8 +72,8 @@ def RetimeTrajectory(self, robot, path):
7272
params = Planner.PlannerParameters()
7373
self.blender.InitPlan(robot, params)
7474
status = self.blender.PlanPath(output_traj)
75-
if status not in [ PlannerStatus.HasSolution,
76-
PlannerStatus.InterruptedWithSolution ]:
75+
if status not in [PlannerStatus.HasSolution,
76+
PlannerStatus.InterruptedWithSolution]:
7777
raise PlanningError('Blending trajectory failed.')
7878
except openrave_exception as e:
7979
raise PlanningError('Blending trajectory failed: ' + str(e))
@@ -84,12 +84,11 @@ def RetimeTrajectory(self, robot, path):
8484
params = Planner.PlannerParameters()
8585
self.retimer.InitPlan(robot, params)
8686
status = self.retimer.PlanPath(output_traj)
87-
if status not in [ PlannerStatus.HasSolution,
88-
PlannerStatus.InterruptedWithSolution ]:
87+
if status not in [PlannerStatus.HasSolution,
88+
PlannerStatus.InterruptedWithSolution]:
8989
raise PlanningError('Timing trajectory failed.')
9090
except openrave_exception as e:
9191
raise PlanningError('Timing trajectory failed: ' + str(e))
9292

9393
SetTrajectoryTags(output_traj, {Tags.SMOOTH: True}, append=True)
9494
return output_traj
95-

src/prpy/planning/mk.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@
3030

3131
import logging, numpy, openravepy, time
3232
from ..util import SetTrajectoryTags
33-
from base import (BasePlanner, PlanningError, UnsupportedPlanningError,
34-
PlanningMethod, Tags)
33+
from base import (BasePlanner, PlanningError,
34+
ClonedPlanningMethod, Tags)
3535

3636
logger = logging.getLogger(__name__)
3737

@@ -100,7 +100,7 @@ def GetStraightVelocity(self, manip, velocity, initial_hand_pose, nullspace_fn,
100100

101101
return numpy.dot(jacobian_pinv, pose_error) + numpy.dot(nullspace_projector, nullspace_goal)
102102

103-
@PlanningMethod
103+
@ClonedPlanningMethod
104104
def PlanToEndEffectorOffset(self, robot, direction, distance, max_distance=None,
105105
nullspace=JointLimitAvoidance, timelimit=5.0, step_size=0.001,
106106
position_tolerance=0.01, angular_tolerance=0.15, **kw_args):

src/prpy/planning/named.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
2929
# POSSIBILITY OF SUCH DAMAGE.
3030
import logging, numpy, openravepy
31-
from base import BasePlanner, PlanningError, UnsupportedPlanningError, PlanningMethod
31+
from base import BasePlanner, PlanningError, ClonedPlanningMethod
3232

3333
class NamedPlanner(BasePlanner):
3434
def __init__(self, delegate_planner=None):
@@ -41,7 +41,7 @@ def __init__(self, delegate_planner=None):
4141
def __str__(self):
4242
return 'NamedPlanner'
4343

44-
@PlanningMethod
44+
@ClonedPlanningMethod
4545
def PlanToNamedConfiguration(self, robot, name, **kw_args):
4646
""" Plan to a named configuration.
4747

0 commit comments

Comments
 (0)