ConstrainedPlanningCommon.py
1 #!/usr/bin/env python3
2 
3 
36 
37 # Author: Mark Moll
38 
39 from __future__ import print_function
40 try:
41  from ompl import util as ou
42  from ompl import base as ob
43  from ompl import geometric as og
44  try:
45  from ompl import tools as ot
46  except ImportError:
47  pass
48 
49 except ImportError:
50  # if the ompl module is not in the PYTHONPATH assume it is installed in a
51  # subdirectory of the parent directory called "py-bindings."
52  from os.path import abspath, dirname, join
53  import sys
54  sys.path.insert(
55  0, join(dirname(dirname(dirname(abspath(__file__)))), 'py-bindings'))
56  from ompl import util as ou
57  from ompl import base as ob
58  from ompl import geometric as og
59  try:
60  from ompl import tools as ot
61  except ImportError:
62  pass
63 
64 import datetime
65 
66 
67 def addSpaceOption(parser):
68  parser.add_argument("-s", "--space", default="PJ",
69  choices=["PJ", "AT", "TB"],
70  help="""Choose which constraint handling methodology to use. One of:
71  PJ - Projection (Default)
72  AT - Atlas
73  TB - Tangent Bundle.""")
74 
75 
76 def addPlannerOption(parser):
77  parser.add_argument("-p", "--planner", default="RRT",
78  help="Comma-separated list of which motion planner to use (multiple if "
79  "benchmarking, one if planning).\n Choose from, e.g.:\n"
80  "RRT (Default), RRTConnect, RRTstar, "
81  "EST, BiEST, ProjEST, "
82  "BITstar, "
83  "PRM, SPARS, "
84  "KPIECE1, BKPIECE1.")
85 
86 
87 def addConstrainedOptions(parser):
88  group = parser.add_argument_group("Constrained planning options")
89  group.add_argument("-d", "--delta", type=float, default=ob.CONSTRAINED_STATE_SPACE_DELTA,
90  help="Step-size for discrete geodesic on manifold.")
91  group.add_argument("--lambda", type=float, dest="lambda_", metavar="LAMBDA",
92  default=ob.CONSTRAINED_STATE_SPACE_LAMBDA,
93  help="Maximum `wandering` allowed during atlas traversal. Must be greater "
94  "than 1.")
95  group.add_argument("--tolerance", type=float, default=ob.CONSTRAINT_PROJECTION_TOLERANCE,
96  help="Constraint satisfaction tolerance.")
97  group.add_argument("--time", type=float, default=5.,
98  help="Planning time allowed.")
99  group.add_argument("--tries", type=int, default=ob.CONSTRAINT_PROJECTION_MAX_ITERATIONS,
100  help="Maximum number sample tries per sample.")
101  group.add_argument("-r", "--range", type=float, default=0.,
102  help="Planner `range` value for planners that support this parameter. "
103  "Automatically determined otherwise (when 0).")
104 
105 def list2vec(l):
106  ret = ou.vectorDouble()
107  for e in l:
108  ret.append(e)
109  return ret
110 
111 def clearSpaceAndPlanner(planner):
112  planner.getSpaceInformation().getStateSpace().clear()
113  planner.clear()
114 
115 
116 def addAtlasOptions(parser):
117  group = parser.add_argument_group("Atlas options")
118  group.add_argument("--epsilon", type=float, default=ob.ATLAS_STATE_SPACE_EPSILON,
119  help="Maximum distance from an atlas chart to the manifold. Must be "
120  "positive.")
121  group.add_argument("--rho", type=float, default=ob.CONSTRAINED_STATE_SPACE_DELTA *
122  ob.ATLAS_STATE_SPACE_RHO_MULTIPLIER,
123  help="Maximum radius for an atlas chart. Must be positive.")
124  group.add_argument("--exploration", type=float, default=ob.ATLAS_STATE_SPACE_EXPLORATION,
125  help="Value in [0, 1] which tunes balance of refinement and exploration in "
126  "atlas sampling.")
127  group.add_argument("--alpha", type=float, default=ob.ATLAS_STATE_SPACE_ALPHA,
128  help="Maximum angle between an atlas chart and the manifold. Must be in "
129  "[0, PI/2].")
130  group.add_argument("--bias", action="store_true",
131  help="Sets whether the atlas should use frontier-biased chart sampling "
132  "rather than uniform.")
133  group.add_argument("--no-separate", action="store_true",
134  help="Sets that the atlas should not compute chart separating halfspaces.")
135  group.add_argument("--charts", type=int, default=ob.ATLAS_STATE_SPACE_MAX_CHARTS_PER_EXTENSION,
136  help="Maximum number of atlas charts that can be generated during one "
137  "manifold traversal.")
138 
139 
140 class ConstrainedProblem(object):
141 
142  def __init__(self, spaceType, space, constraint, options):
143  self.spaceType = spaceType
144  self.space = space
145  self.constraint = constraint
146  self.constraint.setTolerance(options.tolerance)
147  self.constraint.setMaxIterations(options.tries)
148  self.options = options
149  self.bench = None
150  self.request = None
151  self.pp = None
152 
153  if spaceType == "PJ":
154  ou.OMPL_INFORM("Using Projection-Based State Space!")
155  self.css = ob.ProjectedStateSpace(space, constraint)
157  elif spaceType == "AT":
158  ou.OMPL_INFORM("Using Atlas-Based State Space!")
159  self.css = ob.AtlasStateSpace(space, constraint)
161  elif spaceType == "TB":
162  ou.OMPL_INFORM("Using Tangent Bundle-Based State Space!")
163  self.css = ob.TangentBundleStateSpace(space, constraint)
165 
166  self.css.setup()
167  self.css.setDelta(options.delta)
168  self.css.setLambda(options.lambda_)
169  if not spaceType == "PJ":
170  self.css.setExploration(options.exploration)
171  self.css.setEpsilon(options.epsilon)
172  self.css.setRho(options.rho)
173  self.css.setAlpha(options.alpha)
174  self.css.setMaxChartsPerExtension(options.charts)
175  if options.bias:
176  self.css.setBiasFunction(ob.AtlasChartBiasFunction(lambda c, atlas=self.css:
177  atlas.getChartCount() - c.getNeighborCount() + 1.))
178  if spaceType == "AT":
179  self.css.setSeparated(not options.no_separate)
180  self.css.setup()
181  self.ss = og.SimpleSetup(self.csi)
182 
183  def setStartAndGoalStates(self, start, goal):
184  # Create start and goal states
185  if self.spaceType == "AT" or self.spaceType == "TB":
186  self.css.anchorChart(start())
187  self.css.anchorChart(goal())
188 
189  # Setup problem
190  self.ss.setStartAndGoalStates(start, goal)
191 
192  def getPlanner(self, plannerName, projectionName=None):
193  planner = eval('og.%s(self.csi)' % plannerName)
194  try:
195  if self.options.range == 0:
196  if not self.spaceType == "PJ":
197  planner.setRange(self.css.getRho_s())
198  else:
199  planner.setRange(self.options.range)
200  except:
201  pass
202  try:
203  if projectionName:
204  planner.setProjectionEvaluator(projectionName)
205  except:
206  pass
207  return planner
208 
209  def setPlanner(self, plannerName, projectionName=None):
210  self.pp = self.getPlanner(plannerName, projectionName)
211  self.ss.setPlanner(self.pp)
212 
213  def solveOnce(self, output=False, name="ompl"):
214  self.ss.setup()
215  stat = self.ss.solve(self.options.time)
216 
217  if stat:
218  # Get solution and validate
219  path = self.ss.getSolutionPath()
220  if not path.check():
221  ou.OMPL_WARN("Path fails check!")
222 
223  if stat == ob.PlannerStatus.APPROXIMATE_SOLUTION:
224  ou.OMPL_WARN("Solution is approximate.")
225 
226  # Simplify solution and validate simplified solution path.
227  ou.OMPL_INFORM("Simplifying solution...")
228  self.ss.simplifySolution(5.)
229 
230  simplePath = self.ss.getSolutionPath()
231  ou.OMPL_INFORM("Simplified Path Length: %.3f -> %.3f" %
232  (path.length(), simplePath.length()))
233 
234  if not simplePath.check():
235  ou.OMPL_WARN("Simplified path fails check!")
236 
237  # Interpolate and validate interpolated solution path.
238  ou.OMPL_INFORM("Interpolating path...")
239  path.interpolate()
240 
241  if not path.check():
242  ou.OMPL_WARN("Interpolated simplified path fails check!")
243 
244  ou.OMPL_INFORM("Interpolating simplified path...")
245  simplePath.interpolate()
246 
247  if not simplePath.check():
248  ou.OMPL_WARN("Interpolated simplified path fails check!")
249 
250  if output:
251  ou.OMPL_INFORM("Dumping path to `%s_path.txt`." % name)
252  with open('%s_path.txt' % name, 'w') as pathfile:
253  print(path.printAsMatrix(), file=pathfile)
254 
255  ou.OMPL_INFORM(
256  "Dumping simplified path to `%s_simplepath.txt`." % name)
257  with open("%s_simplepath.txt" % name, 'w') as simplepathfile:
258  print(simplePath.printAsMatrix(), file=simplepathfile)
259  else:
260  ou.OMPL_WARN("No solution found.")
261 
262  return stat
263 
264  def setupBenchmark(self, planners, problem):
265  if not ot:
266  print("Benchmarking not available, no ompl.tools")
267  sys.exit(0)
268 
269  self.bench = ot.Benchmark(self.ss, problem)
270 
271  self.bench.addExperimentParameter(
272  "n", "INTEGER", str(self.constraint.getAmbientDimension()))
273  self.bench.addExperimentParameter(
274  "k", "INTEGER", str(self.constraint.getManifoldDimension()))
275  self.bench.addExperimentParameter(
276  "n - k", "INTEGER", str(self.constraint.getCoDimension()))
277  self.bench.addExperimentParameter("space", "INTEGER", self.spaceType)
278 
279  self.request = ot.Benchmark.Request()
280  self.request.maxTime = self.options.time
281  self.request.maxMem = 1e9
282  self.request.runCount = 100
283  self.request.timeBetweenUpdates = 0.1
284  self.request.saveConsoleOutput = False
285  for planner in planners:
286  self.bench.addPlanner(self.getPlanner(planner, problem))
287 
288  self.bench.setPreRunEvent(ot.PreSetupEvent(clearSpaceAndPlanner))
289 
290  def runBenchmark(self):
291  self.bench.benchmark(self.request)
292  filename = str(datetime.datetime.now()) + '_' + \
293  self.bench.getExperimentName() + '_' + self.spaceType
294  self.bench.saveResultsToFile(filename)
295 
296  def atlasStats(self):
297  # For atlas types, output information about size of atlas and amount of
298  # space explored
299  if self.spaceType == "AT" or self.spaceType == "TB":
300  ou.OMPL_INFORM("Atlas has %d charts" % self.css.getChartCount())
301  if self.spaceType == "AT":
302  ou.OMPL_INFORM("Atlas is approximately %.3f%% open" %
303  self.css.estimateFrontierPercent())
304 
305  def dumpGraph(self, name):
306  ou.OMPL_INFORM("Dumping planner graph to `%s_graph.graphml`." % name)
307  data = ob.PlannerData(self.csi)
308  self.pp.getPlannerData(data)
309 
310  with open("%s_graph.graphml" % name, "w") as graphfile:
311  print(data.printGraphML(), file=graphfile)
312 
313  if self.spaceType == "AT" or self.spaceType == "TB":
314  ou.OMPL_INFORM("Dumping atlas to `%s_atlas.ply`." % name)
315  with open("%s_atlas.ply" % name, "w") as atlasfile:
316  print(self.css.printPLY(), file=atlasfile)
Create the set of classes typically needed to solve a geometric problem.
Definition: SimpleSetup.h:126
ConstrainedStateSpace encapsulating a planner-agnostic atlas algorithm for planning on a constraint m...
Object containing planner generated vertex and edge data. It is assumed that all vertices are unique,...
Definition: PlannerData.h:238
Space information for a constrained state space. Implements more direct for getting motion states.
def getPlanner(self, plannerName, projectionName=None)
Benchmark a set of planners on a problem instance.
Definition: Benchmark.h:112
ConstrainedStateSpace encapsulating a projection-based methodology for planning with constraints.
ConstrainedStateSpace encapsulating a planner-agnostic lazy atlas algorithm for planning on a constra...
Space information for a tangent bundle-based state space. Implements more direct for getting motion s...