OptimalPlanning.py
1 #!/usr/bin/env python
2 
3 ######################################################################
4 # Software License Agreement (BSD License)
5 #
6 # Copyright (c) 2010, Rice University
7 # All rights reserved.
8 #
9 # Redistribution and use in source and binary forms, with or without
10 # modification, are permitted provided that the following conditions
11 # are met:
12 #
13 # * Redistributions of source code must retain the above copyright
14 # notice, this list of conditions and the following disclaimer.
15 # * Redistributions in binary form must reproduce the above
16 # copyright notice, this list of conditions and the following
17 # disclaimer in the documentation and/or other materials provided
18 # with the distribution.
19 # * Neither the name of the Rice University nor the names of its
20 # contributors may be used to endorse or promote products derived
21 # from this software without specific prior written permission.
22 #
23 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
24 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
25 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
26 # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
27 # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
28 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
29 # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
30 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
31 # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
33 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
34 # POSSIBILITY OF SUCH DAMAGE.
35 ######################################################################
36 
37 # Author: Luis G. Torres, Mark Moll
38 
39 try:
40  from ompl import util as ou
41  from ompl import base as ob
42  from ompl import geometric as og
43 except:
44  # if the ompl module is not in the PYTHONPATH assume it is installed in a
45  # subdirectory of the parent directory called "py-bindings."
46  from os.path import abspath, dirname, join
47  import sys
48  sys.path.insert(0, join(dirname(dirname(abspath(__file__))),'py-bindings'))
49  from ompl import util as ou
50  from ompl import base as ob
51  from ompl import geometric as og
52 from math import sqrt
53 from sys import argv
54 import argparse
55 
56 ## @cond IGNORE
57 # Our "collision checker". For this demo, our robot's state space
58 # lies in [0,1]x[0,1], with a circular obstacle of radius 0.25
59 # centered at (0.5,0.5). Any states lying in this circular region are
60 # considered "in collision".
61 class ValidityChecker(ob.StateValidityChecker):
62  def __init__(self, si):
63  super(ValidityChecker, self).__init__(si)
64 
65  # Returns whether the given state's position overlaps the
66  # circular obstacle
67  def isValid(self, state):
68  return self.clearance(state) > 0.0
69 
70  # Returns the distance from the given state's position to the
71  # boundary of the circular obstacle.
72  def clearance(self, state):
73  # Extract the robot's (x,y) position from its state
74  x = state[0]
75  y = state[1]
76 
77  # Distance formula between two points, offset by the circle's
78  # radius
79  return sqrt((x-0.5)*(x-0.5) + (y-0.5)*(y-0.5)) - 0.25
80 
81 
82 ## Returns a structure representing the optimization objective to use
83 # for optimal motion planning. This method returns an objective
84 # which attempts to minimize the length in configuration space of
85 # computed paths.
86 def getPathLengthObjective(si):
88 
89 ## Returns an optimization objective which attempts to minimize path
90 # length that is satisfied when a path of length shorter than 1.51
91 # is found.
92 def getThresholdPathLengthObj(si):
94  obj.setCostThreshold(ob.Cost(1.51))
95  return obj
96 
97 ## Defines an optimization objective which attempts to steer the
98 # robot away from obstacles. To formulate this objective as a
99 # minimization of path cost, we can define the cost of a path as a
100 # summation of the costs of each of the states along the path, where
101 # each state cost is a function of that state's clearance from
102 # obstacles.
103 #
104 # The class StateCostIntegralObjective represents objectives as
105 # summations of state costs, just like we require. All we need to do
106 # then is inherit from that base class and define our specific state
107 # cost function by overriding the stateCost() method.
108 #
109 class ClearanceObjective(ob.StateCostIntegralObjective):
110  def __init__(self, si):
111  super(ClearanceObjective, self).__init__(si, True)
112  self.si_ = si
113 
114  # Our requirement is to maximize path clearance from obstacles,
115  # but we want to represent the objective as a path cost
116  # minimization. Therefore, we set each state's cost to be the
117  # reciprocal of its clearance, so that as state clearance
118  # increases, the state cost decreases.
119  def stateCost(self, s):
120  return ob.Cost(1 / self.si_.getStateValidityChecker().clearance(s))
121 
122 ## Return an optimization objective which attempts to steer the robot
123 # away from obstacles.
124 def getClearanceObjective(si):
125  return ClearanceObjective(si)
126 
127 ## Create an optimization objective which attempts to optimize both
128 # path length and clearance. We do this by defining our individual
129 # objectives, then adding them to a MultiOptimizationObjective
130 # object. This results in an optimization objective where path cost
131 # is equivalent to adding up each of the individual objectives' path
132 # costs.
133 #
134 # When adding objectives, we can also optionally specify each
135 # objective's weighting factor to signify how important it is in
136 # optimal planning. If no weight is specified, the weight defaults to
137 # 1.0.
138 def getBalancedObjective1(si):
139  lengthObj = ob.PathLengthOptimizationObjective(si)
140  clearObj = ClearanceObjective(si)
141 
143  opt.addObjective(lengthObj, 5.0)
144  opt.addObjective(clearObj, 1.0)
145 
146  return opt
147 
148 ## Create an optimization objective equivalent to the one returned by
149 # getBalancedObjective1(), but use an alternate syntax.
150 # THIS DOESN'T WORK YET. THE OPERATORS SOMEHOW AREN'T EXPORTED BY Py++.
151 # def getBalancedObjective2(si):
152 # lengthObj = ob.PathLengthOptimizationObjective(si)
153 # clearObj = ClearanceObjective(si)
154 #
155 # return 5.0*lengthObj + clearObj
156 
157 
158 ## Create an optimization objective for minimizing path length, and
159 # specify a cost-to-go heuristic suitable for this optimal planning
160 # problem.
161 def getPathLengthObjWithCostToGo(si):
163  obj.setCostToGoHeuristic(ob.CostToGoHeuristic(ob.goalRegionCostToGo))
164  return obj
165 
166 
167 # Keep these in alphabetical order and all lower case
168 def allocatePlanner(si, plannerType):
169  if plannerType.lower() == "bitstar":
170  return og.BITstar(si)
171  elif plannerType.lower() == "fmtstar":
172  return og.FMT(si)
173  elif plannerType.lower() == "bfmtstar":
174  return og.BFMT(si)
175  elif plannerType.lower() == "informedrrtstar":
176  return og.InformedRRTstar(si)
177  elif plannerType.lower() == "prmstar":
178  return og.PRMstar(si)
179  elif plannerType.lower() == "rrtstar":
180  return og.RRTstar(si)
181  else:
182  OMPL_ERROR("Planner-type is not implemented in allocation function.");
183 
184 
185 # Keep these in alphabetical order and all lower case
186 def allocateObjective(si, objectiveType):
187  if objectiveType.lower() == "pathclearance":
188  return getClearanceObjective(si)
189  elif objectiveType.lower() == "pathlength":
190  return getPathLengthObjective(si)
191  elif objectiveType.lower() == "thresholdpathlength":
192  return getThresholdPathLengthObj(si)
193  elif objectiveType.lower() == "weightedlengthandclearancecombo":
194  return getBalancedObjective1(si)
195  else:
196  OMPL_ERROR("Optimization-objective is not implemented in allocation function.");
197 
198 
199 
200 def plan(runTime, plannerType, objectiveType, fname):
201  # Construct the robot state space in which we're planning. We're
202  # planning in [0,1]x[0,1], a subset of R^2.
203  space = ob.RealVectorStateSpace(2)
204 
205  # Set the bounds of space to be in [0,1].
206  space.setBounds(0.0, 1.0)
207 
208  # Construct a space information instance for this state space
209  si = ob.SpaceInformation(space)
210 
211  # Set the object used to check which states in the space are valid
212  validityChecker = ValidityChecker(si)
213  si.setStateValidityChecker(validityChecker)
214 
215  si.setup()
216 
217  # Set our robot's starting state to be the bottom-left corner of
218  # the environment, or (0,0).
219  start = ob.State(space)
220  start[0] = 0.0
221  start[1] = 0.0
222 
223  # Set our robot's goal state to be the top-right corner of the
224  # environment, or (1,1).
225  goal = ob.State(space)
226  goal[0] = 1.0
227  goal[1] = 1.0
228 
229  # Create a problem instance
230  pdef = ob.ProblemDefinition(si)
231 
232  # Set the start and goal states
233  pdef.setStartAndGoalStates(start, goal)
234 
235  # Create the optimization objective specified by our command-line argument.
236  # This helper function is simply a switch statement.
237  pdef.setOptimizationObjective(allocateObjective(si, objectiveType))
238 
239  # Construct the optimal planner specified by our command line argument.
240  # This helper function is simply a switch statement.
241  optimizingPlanner = allocatePlanner(si, plannerType)
242 
243  # Set the problem instance for our planner to solve
244  optimizingPlanner.setProblemDefinition(pdef)
245  optimizingPlanner.setup()
246 
247  # attempt to solve the planning problem in the given runtime
248  solved = optimizingPlanner.solve(runTime)
249 
250  if solved:
251  # Output the length of the path found
252  print("{0} found solution of path length {1:.4f} with an optimization objective value of {2:.4f}".format(optimizingPlanner.getName(), pdef.getSolutionPath().length(), pdef.getSolutionPath().cost(pdef.getOptimizationObjective()).value()))
253 
254  # If a filename was specified, output the path as a matrix to
255  # that file for visualization
256  if fname:
257  with open(fname,'w') as outFile:
258  outFile.write(pdef.getSolutionPath().printAsMatrix())
259  else:
260  print("No solution found.")
261 
262 if __name__ == "__main__":
263  # Create an argument parser
264  parser = argparse.ArgumentParser(description='Optimal motion planning demo program.')
265 
266  # Add a filename argument
267  parser.add_argument('-t', '--runtime', type=float, default=1.0, help='(Optional) Specify the runtime in seconds. Defaults to 1 and must be greater than 0.')
268  parser.add_argument('-p', '--planner', default='RRTstar', choices=['BITstar', 'FMTstar', 'BFMTstar', 'InformedRRTstar', 'PRMstar', 'RRTstar'], help='(Optional) Specify the optimal planner to use, defaults to RRTstar if not given.') # Alphabetical order
269  parser.add_argument('-o', '--objective', default='PathLength', choices=['PathClearance', 'PathLength', 'ThresholdPathLength', 'WeightedLengthAndClearanceCombo'], help='(Optional) Specify the optimization objective, defaults to PathLength if not given.') # Alphabetical order
270  parser.add_argument('-f', '--file', default=None, help='(Optional) Specify an output path for the found solution path.')
271  parser.add_argument('-i', '--info', type=int, default=0, choices=[0, 1, 2], help='(Optional) Set the OMPL log level. 0 for WARN, 1 for INFO, 2 for DEBUG. Defaults to WARN.')
272 
273  # Parse the arguments
274  args = parser.parse_args()
275 
276  # Check that time is positive
277  if args.runtime <= 0:
278  raise argparse.ArgumentTypeError("argument -t/--runtime: invalid choice: %r (choose a positive number greater than 0)"%(args.runtime,))
279 
280  # Set the log level
281  if args.info == 0:
282  ou.setLogLevel(ou.LOG_WARN)
283  elif args.info == 1:
284  ou.setLogLevel(ou.LOG_INFO)
285  elif args.info == 2:
286  ou.setLogLevel(ou.LOG_DEBUG)
287  else:
288  OMPL_ERROR("Invalid log-level integer.");
289 
290  # Solve the planning problem
291  plan(args.runtime, args.planner, args.objective, args.file)
292 
293 ## @endcond
Optimal Rapidly-exploring Random Trees.
Definition: RRTstar.h:79
This class allows for the definition of multiobjective optimal planning problems. Objectives are adde...
PRM* planner.
Definition: PRMstar.h:67
Asymptotically Optimal Fast Marching Tree algorithm developed by L. Janson and M. Pavone...
Definition: FMT.h:91
#define OMPL_ERROR(fmt,...)
Log a formatted error string.
Definition: Console.h:64
Bidirectional Asymptotically Optimal Fast Marching Tree algorithm developed by J. Starek...
Definition: BFMT.h:82
Defines optimization objectives where path cost can be represented as a path integral over a cost fun...
Abstract definition for a class checking the validity of states. The implementation of this class mus...
Batch Informed Trees (BIT*)
Definition: BITstar.h:111
A state space representing Rn. The distance function is the L2 norm.
The base class for space information. This contains all the information about the space planning is d...
An optimization objective which corresponds to optimizing path length.
Definition of an abstract state.
Definition: State.h:50
Definition of a problem to be solved. This includes the start state(s) for the system and a goal spec...
Definition of a cost value. Can represent the cost of a motion or the cost of a state.
Definition: Cost.h:47
std::function< Cost(const State *, const Goal *)> CostToGoHeuristic
The definition of a function which returns an admissible estimate of the optimal path cost from a giv...