Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added OMPLRangedPlanner class #353

Merged
merged 7 commits into from
Aug 30, 2016
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
128 changes: 90 additions & 38 deletions src/prpy/planning/ompl.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,11 @@
import logging
import numpy
import openravepy
import warnings
from ..util import CopyTrajectory, SetTrajectoryTags
from copy import deepcopy
from ..util import CopyTrajectory, SetTrajectoryTags, GetManipulatorIndex
from ..tsr.tsr import TSR, TSRChain
from base import (BasePlanner, PlanningError, UnsupportedPlanningError,
ClonedPlanningMethod, Tags)
from openravepy import (
Expand Down Expand Up @@ -163,67 +167,115 @@ def _Plan(self, robot, goal=None, tsrchains=None, timeout=30., shortcut_timeout=
return traj


class RRTConnect(OMPLPlanner):
def __init__(self):
OMPLPlanner.__init__(self, algorithm='RRTConnect')

class OMPLRangedPlanner(OMPLPlanner):
""" Construct an OMPL planner with a default 'range' parameter set.

This class wraps OMPLPlanner by setting the default 'range' parameter,
which defines the length of an extension in algorithms like RRT-Connect, to
include an fixed number of state validity checks.

This number may be specified explicitly (using 'multiplier') or as an
approximate fraction of the longest extent of the state space (using
'fraction'). Exactly one of 'multiplier' or 'fraction' is required.

@param algorithm name of the OMPL planner to create
@param robot_checker_factory robot collision checker factory
@param multiplier number of state validity checks per extension
@param fraction approximate fraction of the maximum extent of the state
space per extension
"""
def __init__(self, multiplier=None, fraction=None,
algorithm='RRTConnect', robot_checker_factory=None):
if multiplier is None and fraction is None:
raise ValueError('Either "multiplier" of "fraction" is required.')
if multiplier is not None and fraction is not None:
raise ValueError('"multiplier" and "fraction" are exclusive.')
if multiplier is not None and not (multiplier >= 1):
raise ValueError('"mutiplier" must be a positive integer.')
if fraction is not None and not (0. <= fraction <= 1.):
raise ValueError('"fraction" must be between zero and one.')

OMPLPlanner.__init__(self, algorithm='RRTConnect',
robot_checker_factory=robot_checker_factory)

self.multiplier = multiplier
self.fraction = fraction


""" Set a default 'range' parameter from DOF resolutions.

This function returns a copy of the input 'ompl_args' argument with the
'range' parameter set to a default value, if it was not already set. The
default value is calculated from the active DOFs of 'robot' such that there
will be an fixed number of state validity checks per extension. That number
is configured in the constructor of this class.

@param robot with active DOFs set
@param ompl_args arguments to append to, defaults to an empty dictionary
@return copy of ompl_args with 'range' set to the default value
"""
def _SetPlannerRange(self, robot, ompl_args=None):
from copy import deepcopy
epsilon = 1e-6

if ompl_args is None:
ompl_args = dict()
else:
ompl_args = deepcopy(ompl_args)

# Set the default range to the collision checking resolution.
if 'range' not in ompl_args:
epsilon = 1e-6

# Compute the collision checking resolution as a conservative
# fraction of the state space extents. This mimics the logic inside
# or_ompl used to set collision checking resolution.
# Compute the maximum DOF range, treating circle joints as SO(2).
dof_limit_lower, dof_limit_upper = robot.GetActiveDOFLimits()
dof_ranges = dof_limit_upper - dof_limit_lower
dof_ranges = numpy.zeros(robot.GetActiveDOF())

for index, dof_index in enumerate(robot.GetActiveDOFIndices()):
joint = robot.GetJointFromDOFIndex(dof_index)
axis_index = dof_index - joint.GetDOFIndex()

if joint.IsCircular(axis_index):
dof_ranges[index] = 2 * numpy.pi
else:
dof_ranges[index] = (dof_limit_upper[index]
- dof_limit_lower[index])

# Duplicate the logic in or_ompl to convert the DOF resolutions to
# a fraction of the longest extent of the state space.
maximum_extent = numpy.max(dof_ranges)
Copy link
Contributor

@cdellin cdellin Aug 29, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately, that's not what the maximum extent is ... you should use numpy.linalg.norm instead of numpy.max.

dof_resolution = robot.GetActiveDOFResolutions()
ratios = dof_resolution / dof_ranges
conservative_ratio = numpy.min(ratios)

# Convert the ratio back to a collision checking resolution. Set
# RRT-Connect's range to the same value used for collision
# detection inside OpenRAVE.
longest_extent = numpy.max(dof_ranges)
ompl_range = conservative_ratio * longest_extent - epsilon

ompl_args['range'] = ompl_range
logger.debug('Defaulted RRT-Connect range parameter to %.3f.',
ompl_range)
conservative_resolution = numpy.min(dof_resolution)
conservative_fraction = conservative_resolution / maximum_extent

if self.multiplier is not None:
multiplier = self.multiplier
elif self.fraction is not None:
multiplier = int(self.fraction / conservative_fraction + 0.5)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The C side of me likes this, but perhaps we can just use Python's round function here?

else:
raise ValueError('Either multiplier or fraction must be set.')

# Then, scale this by the user-supplied multiple. Finally, subtract
# a small value to cope with numerical precision issues inside OMPL.
ompl_args['range'] = multiplier * conservative_fraction - epsilon

return ompl_args

@ClonedPlanningMethod
def PlanToConfiguration(self, robot, goal, ompl_args=None, **kw_args):
"""
Plan to a desired configuration with OMPL. This will invoke the OMPL
planner specified in the OMPLPlanner constructor.
@param robot
@param goal desired configuration
@return traj
"""
ompl_args = self._SetPlannerRange(robot, ompl_args=ompl_args)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My only other feedback is that _SetPlannerRange(robot, ompl_args) is poorly named (it doesn't set the value in the passed ompl_args argument, it instead returns a copy with the value set if it doesn't yet exist). My suggestion would be to rename it to _CalculatePlannerRange(robot) which just returns the range float with all dict logic removed, and then replace this line (and the similar line in PlanToTSR below) with something like:

if ompl_args is None:
    ompl_args = dict()
ompl_args.setdefault('range', _CalculatePlannerRange(robot))

return self._Plan(robot, goal=goal, ompl_args=ompl_args, **kw_args)

@ClonedPlanningMethod
def PlanToTSR(self, robot, tsrchains, ompl_args=None, **kw_args):
"""
Plan using the given TSR chains with OMPL.
@param robot
@param tsrchains A list of TSRChain objects to respect during planning
@param ompl_args ompl RRTConnect specific parameters
@return traj
"""
ompl_args = self._SetPlannerRange(robot, ompl_args=ompl_args)
return self._Plan(robot, tsrchains=tsrchains, ompl_args=ompl_args, **kw_args)


# Alias to maintain backwards compatability.
class RRTConnect(OMPLRangedPlanner):
def __init__(self):
super(RRTConnect, self).__init__(algorithm='RRTConnect', fraction=0.2)
warnings.warn('RRTConnect has been replaced by OMPLRangedPlanner.',
DeprecationWarning)


class OMPLSimplifier(BasePlanner):
def __init__(self):
super(OMPLSimplifier, self).__init__()
Expand Down
4 changes: 2 additions & 2 deletions tests/planning/test_OMPL.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
PlanToConfigurationCompleteTest,
)
from planning_helpers import BasePlannerTest
from prpy.planning.ompl import OMPLPlanner
from prpy.planning.ompl import OMPLRangedPlanner
from unittest import TestCase


Expand All @@ -13,4 +13,4 @@ class OMPLPlannerTests(BasePlannerTest,
PlanToConfigurationStraightLineTest,
PlanToConfigurationCompleteTest,
TestCase):
planner_factory = OMPLPlanner
planner_factory = lambda _: OMPLRangedPlanner(fraction=0.2)