ConstrainedPlanningTorus.py
1 #!/usr/bin/env python3
2 
3 
36 
37 # Author: Mark Moll
38 
39 from __future__ import print_function
40 
41 from os.path import dirname, join
42 import argparse
43 import math
44 from functools import partial
45 import numpy as np
46 from ConstrainedPlanningCommon import *
47 
48 PI2 = 2 * math.pi
49 
50 # Torus manifold.
51 
52 
54 
55  def __init__(self, outer, inner, maze):
56  super(TorusConstraint, self).__init__(3, 1)
57  self.outer = outer
58  self.inner = inner
59  self.ppm = ou.PPM()
60  self.ppm.loadFile(maze)
61 
62  def getStartAndGoalStates(self):
63  h = self.ppm.getHeight()
64  w = self.ppm.getWidth()
65 
66  for x in range(w):
67  for y in range(h):
68  p = np.array([x / (w - 1.), y / (h - 1.)], dtype=np.float64)
69  c = self.ppm.getPixel(x, y)
70  if c.red == 255 and c.blue == 0 and c.green == 0:
71  start = self.mazeToAmbient(p)
72  elif c.green == 255 and c.blue == 0 and c.red == 0:
73  goal = self.mazeToAmbient(p)
74  return start, goal
75 
76  def function(self, x, out):
77  c = np.array([x[0], x[1], 0])
78  nrm = math.sqrt(x[0] * x[0] + x[1] * x[1])
79  if not np.isfinite(nrm) or nrm == 0:
80  nrm = 1
81  out[0] = np.linalg.norm(x - self.outer * c / nrm) - self.inner
82 
83  def jacobian(self, x, out):
84  xySquaredNorm = x[0] * x[0] + x[1] * x[1]
85  xyNorm = math.sqrt(xySquaredNorm)
86  denom = math.sqrt(x[2] * x[2] + (xyNorm - self.outer)
87  * (xyNorm - self.outer))
88  c = (xyNorm - self.outer) * (xyNorm * xySquaredNorm) / \
89  (xySquaredNorm * xySquaredNorm * denom)
90  out[0, :] = [x[0] * c, x[1] * c, x[2] / denom]
91 
92  def ambientToMaze(self, x):
93  nrm = math.sqrt(x[0] * x[0] + x[1] * x[1])
94  h = self.ppm.getHeight()
95  w = self.ppm.getWidth()
96 
97  mx = math.atan2(x[2], nrm - self.outer) / PI2
98  mx += (mx < 0)
99  mx *= h
100  my = math.atan2(x[1], x[0]) / PI2
101  my += (my < 0)
102  my *= w
103  return mx, my
104 
105  def mazeToAmbient(self, x):
106  a = x * PI2
107  b = [math.cos(a[0]), 0, math.sin(a[0])] * self.inner
108  b[0] += self.outer
109 
110  norm = math.sqrt(b[0] * b[0] + b[1] * b[1])
111  out = np.array([math.cos(a[1]), math.sin(a[1]), 0], dtype=np.float64)
112  out *= norm
113  out[2] = b[2]
114  return out
115 
116  def mazePixel(self, x):
117  h = self.ppm.getHeight()
118  w = self.ppm.getWidth()
119 
120  if x[0] < 0 or x[0] >= w or x[1] < 0 or x[1] >= h:
121  return False
122 
123  c = self.ppm.getPixel(int(x[0]), int(x[1]))
124  return not (c.red == 0 and c.blue == 0 and c.green == 0)
125 
126  def isValid(self, state):
127  return self.mazePixel(self.ambientToMaze(state))
128 
129 
130 def torusPlanningBench(cp, planners):
131  print(planners)
132  cp.setupBenchmark(planners, "torus")
133  cp.runBenchmark()
134 
135 
136 def torusPlanningOnce(cp, planner, output):
137  cp.setPlanner(planner)
138 
139  # Solve the problem
140  stat = cp.solveOnce(output, "torus")
141 
142  if output:
143  ou.OMPL_INFORM("Dumping problem information to `torus_info.txt`.")
144  with open("torus_info.txt", "w") as infofile:
145  print(cp.spaceType, file=infofile)
146 
147  cp.atlasStats()
148 
149  if output:
150  cp.dumpGraph("torus")
151 
152  return stat
153 
154 
155 def torusPlanning(options):
156  # Create the ambient space state space for the problem.
157  rvss = ob.RealVectorStateSpace(3)
158 
159  bounds = ob.RealVectorBounds(3)
160  bounds.setLow(-(options.outer + options.inner))
161  bounds.setHigh(options.outer + options.inner)
162 
163  rvss.setBounds(bounds)
164 
165  # Create our constraint.
166  constraint = TorusConstraint(options.outer, options.inner, options.maze)
167 
168  cp = ConstrainedProblem(options.space, rvss, constraint, options)
169 
170  start, goal = constraint.getStartAndGoalStates()
171  print("Start = ", start)
172  print("Goal = ", goal)
173 
174  sstart = ob.State(cp.css)
175  sgoal = ob.State(cp.css)
176  for i in range(3):
177  sstart[i] = start[i]
178  sgoal[i] = goal[i]
179  cp.setStartAndGoalStates(sstart, sgoal)
180  cp.ss.setStateValidityChecker(ob.StateValidityCheckerFn(partial(
181  TorusConstraint.isValid, constraint)))
182 
183  planners = options.planner.split(",")
184  if not options.bench:
185  torusPlanningOnce(cp, planners[0], options.output)
186  else:
187  torusPlanningBench(cp, planners)
188 
189 if __name__ == "__main__":
190  defaultMaze = join(join(dirname(__file__), "mazes"), "normal.ppm")
191  parser = argparse.ArgumentParser()
192  parser.add_argument("-o", "--output", action="store_true",
193  help="Dump found solution path (if one exists) in plain text and planning "
194  "graph in GraphML to `torus_path.txt` and `torus_graph.graphml` "
195  "respectively.")
196  parser.add_argument("--bench", action="store_true",
197  help="Do benchmarking on provided planner list.")
198  parser.add_argument("--outer", type=float, default=2,
199  help="Outer radius of torus.")
200  parser.add_argument("--inner", type=float, default=1,
201  help="Inner radius of torus.")
202  parser.add_argument("--maze", default=defaultMaze,
203  help="Filename of maze image (in .ppm format) to use as obstacles on the "
204  "surface of the torus.")
205  addSpaceOption(parser)
206  addPlannerOption(parser)
207  addConstrainedOptions(parser)
208  addAtlasOptions(parser)
209 
210  torusPlanning(parser.parse_args())
Definition of a differentiable holonomic constraint on a configuration space. See Constrained Plannin...
Definition: Constraint.h:107
Definition of an abstract state.
Definition: State.h:113
A state space representing Rn. The distance function is the L2 norm.
std::function< bool(const State *)> StateValidityCheckerFn
If no state validity checking class is specified (StateValidityChecker), a std::function can be speci...
The lower and upper bounds for an Rn space.