11"""
2- Batch Informed Trees based path planning
2+ Batch Informed Trees based path planning:
3+ Uses a heuristic to efficiently search increasingly dense
4+ RGGs while reusing previous information. Provides faster
5+ convergence that RRT*, Informed RRT* and other sampling based
6+ methods.
7+
8+ Uses lazy connecting by combining sampling based methods and A*
9+ like incremental graph search algorithms.
310
411author: Karan Chawla(@karanchawla)
512
1522
1623show_animation = True
1724
25+ # Class to represent the explicit tree created
26+ # while sampling through the state space
27+
1828
1929class RTree (object ):
2030
@@ -113,28 +123,19 @@ def nodeIdToRealWorldCoord(self, nid):
113123 # in the full configuration space
114124 return self .gridCoordToRealWorldCoord (self .nodeIdToGridCoord (nid ))
115125
116-
117- class Node ():
118-
119- def __init__ (self , x , y ):
120- self .x = x
121- self .y = y
122- self .cost = 0.0
123- self .parent = None
126+ # Uses Batch Informed Trees to find a path from start to goal
124127
125128
126129class BITStar (object ):
127130
128131 def __init__ (self , start , goal ,
129132 obstacleList , randArea , eta = 2.0 ,
130- expandDis = 0.5 , goalSampleRate = 10 , maxIter = 200 ):
133+ maxIter = 80 ):
131134 self .start = start
132135 self .goal = goal
133136
134137 self .minrand = randArea [0 ]
135138 self .maxrand = randArea [1 ]
136- self .expandDis = expandDis
137- self .goalSampleRate = goalSampleRate
138139 self .maxIter = maxIter
139140 self .obstacleList = obstacleList
140141
@@ -153,14 +154,12 @@ def __init__(self, start, goal,
153154 lowerLimit = [randArea [0 ], randArea [0 ]]
154155 upperLimit = [randArea [1 ], randArea [1 ]]
155156 self .tree = RTree (start = start , lowerLimit = lowerLimit ,
156- upperLimit = upperLimit , resolution = 0.1 )
157+ upperLimit = upperLimit , resolution = 0.01 )
157158
158159 def plan (self , animation = True ):
159160
160161 self .startId = self .tree .realWorldToNodeId (self .start )
161- print ("startId: " , self .startId )
162162 self .goalId = self .tree .realWorldToNodeId (self .goal )
163- print ("goalId: " , self .goalId )
164163
165164 # add goal to the samples
166165 self .samples [self .goalId ] = self .goal
@@ -182,7 +181,7 @@ def plan(self, animation=True):
182181
183182 # Computing the sampling space
184183 cMin = math .sqrt (pow (self .start [0 ] - self .goal [1 ], 2 ) +
185- pow (self .start [0 ] - self .goal [1 ], 2 ))
184+ pow (self .start [0 ] - self .goal [1 ], 2 )) / 1.5
186185 xCenter = np .matrix ([[(self .start [0 ] + self .goal [0 ]) / 2.0 ],
187186 [(self .goal [1 ] - self .start [1 ]) / 2.0 ], [0 ]])
188187 a1 = np .matrix ([[(self .goal [0 ] - self .start [0 ]) / cMin ],
@@ -201,14 +200,21 @@ def plan(self, animation=True):
201200 # run until done
202201 while (iterations < self .maxIter ):
203202 if len (self .vertex_queue ) == 0 and len (self .edge_queue ) == 0 :
203+ print ("Batch: " , iterations )
204204 # Using informed rrt star way of computing the samples
205- self .samples .update (self .informedSample (
206- 50 , cBest , cMin , xCenter , C ))
207- # prune the tree
208205 self .r = 2.0
209206 if iterations != 0 :
207+ if foundGoal :
208+ # a better way to do this would be to make number of samples
209+ # a function of cMin
210+ m = 200
211+ self .samples = dict ()
212+ self .samples [self .goalId ] = self .goal
213+ else :
214+ m = 100
215+ cBest = self .g_scores [self .goalId ]
210216 self .samples .update (self .informedSample (
211- 200 , cBest , cMin , xCenter , C ))
217+ m , cBest , cMin , xCenter , C ))
212218
213219 # make the old vertices the new vertices
214220 self .old_vertices += self .tree .vertices .keys ()
@@ -231,7 +237,7 @@ def plan(self, animation=True):
231237 bestEdge [0 ], bestEdge [1 ]) + self .computeHeuristicCost (bestEdge [1 ], self .goalId )
232238 estimatedCostOfEdge = self .computeDistanceCost (self .startId , bestEdge [0 ]) + self .computeHeuristicCost (
233239 bestEdge [0 ], bestEdge [1 ]) + self .computeHeuristicCost (bestEdge [1 ], self .goalId )
234- actualCostOfEdge = self .g_scores [bestEdge [0 ]] + + \
240+ actualCostOfEdge = self .g_scores [bestEdge [0 ]] + \
235241 self .computeDistanceCost (bestEdge [0 ], bestEdge [1 ])
236242
237243 if (estimatedCostOfVertex < self .g_scores [self .goalId ]):
@@ -243,18 +249,22 @@ def plan(self, animation=True):
243249 secondCoord = self .tree .nodeIdToRealWorldCoord (
244250 bestEdge [1 ])
245251 path = self .connect (firstCoord , secondCoord )
252+ lastEdge = self .tree .realWorldToNodeId (secondCoord )
246253 if path is None or len (path ) == 0 :
247254 continue
248255 nextCoord = path [len (path ) - 1 , :]
249256 nextCoordPathId = self .tree .realWorldToNodeId (
250257 nextCoord )
251258 bestEdge = (bestEdge [0 ], nextCoordPathId )
252- try :
253- del self .samples [bestEdge [1 ]]
254- except (KeyError ):
255- pass
256- eid = self .tree .addVertex (nextCoord )
257- self .vertex_queue .append (eid )
259+ if (bestEdge [1 ] in self .tree .vertices .keys ()):
260+ continue
261+ else :
262+ try :
263+ del self .samples [bestEdge [1 ]]
264+ except (KeyError ):
265+ pass
266+ eid = self .tree .addVertex (nextCoord )
267+ self .vertex_queue .append (eid )
258268 if eid == self .goalId or bestEdge [0 ] == self .goalId or bestEdge [1 ] == self .goalId :
259269 print ("Goal found" )
260270 foundGoal = True
@@ -273,7 +283,7 @@ def plan(self, animation=True):
273283 if animation :
274284 self .drawGraph (xCenter = xCenter , cBest = cBest ,
275285 cMin = cMin , etheta = etheta , samples = self .samples .values (),
276- start = firstCoord , end = secondCoord , tree = self .nodes )
286+ start = firstCoord , end = secondCoord , tree = self .tree . edges )
277287
278288 for edge in self .edge_queue :
279289 if (edge [0 ] == bestEdge [1 ]):
@@ -283,29 +293,31 @@ def plan(self, animation=True):
283293 (edge [0 ], bestEdge [1 ]))
284294 if (edge [1 ] == bestEdge [1 ]):
285295 if self .g_scores [edge [1 ]] + self .computeDistanceCost (edge [1 ], bestEdge [1 ]) >= self .g_scores [self .goalId ]:
286- if (edge [ 1 ] , bestEdge [1 ]) in self .edge_queue :
296+ if (lastEdge , bestEdge [1 ]) in self .edge_queue :
287297 self .edge_queue .remove (
288- (edge [ 1 ] , bestEdge [1 ]))
298+ (lastEdge , bestEdge [1 ]))
289299 else :
300+ print ("Nothing good" )
290301 self .edge_queue = []
291302 self .vertex_queue = []
292303 iterations += 1
293304
305+ print ("Finding the path" )
294306 plan .append (self .goal )
295307 currId = self .goalId
296308 while (currId != self .startId ):
297309 plan .append (self .tree .nodeIdToRealWorldCoord (currId ))
298310 currId = self .nodes [currId ]
299311
300- plan .append (self .startId )
312+ plan .append (self .start )
301313 plan = plan [::- 1 ] # reverse the plan
302- return np . array ( plan )
314+ return plan
303315
304316 def connect (self , start , end ):
305317 # A function which attempts to extend from a start coordinates
306318 # to goal coordinates
307319 steps = self .computeDistanceCost (self .tree .realWorldToNodeId (
308- start ), self .tree .realWorldToNodeId (end )) * 25
320+ start ), self .tree .realWorldToNodeId (end )) * 10
309321 x = np .linspace (start [0 ], end [0 ], num = steps )
310322 y = np .linspace (start [1 ], end [1 ], num = steps )
311323 for i in range (len (x )):
@@ -314,14 +326,15 @@ def connect(self, start, end):
314326 return None
315327 # if collision, send path until collision
316328 return np .vstack ((x [0 :i ], y [0 :i ])).transpose ()
317- return np .vstack ((x , y )).transpose ()
329+
330+ return np .vstack ((x , y )).transpose ()
318331
319332 def _collisionCheck (self , x , y ):
320333 for (ox , oy , size ) in self .obstacleList :
321334 dx = ox - x
322335 dy = oy - y
323336 d = dx * dx + dy * dy
324- if d <= 1.1 * size ** 2 :
337+ if d <= size ** 2 :
325338 return True # collision
326339 return False
327340
@@ -341,20 +354,10 @@ def computeDistanceCost(self, vid, xid):
341354
342355 return np .linalg .norm (stop - start , 2 )
343356
344- def radius (self , q ):
345- dim = len (start ) # dimensions
346- space_measure = self .minrand * self .maxrand # volume of the space
347-
348- min_radius = self .eta * 2.0 * pow ((1.0 + 1.0 / dim ) *
349- (space_measure / self .unit_ball_measure ), 1.0 / dim )
350- return min_radius * pow (numpy .log (q ) / q , 1 / dim )
351-
352- # Return the closest sample
353- # def getNearestSample(self):
354-
355357 # Sample free space confined in the radius of ball R
356358 def informedSample (self , m , cMax , cMin , xCenter , C ):
357359 samples = dict ()
360+ print ("g_Score goal id: " , self .g_scores [self .goalId ])
358361 for i in range (m + 1 ):
359362 if cMax < float ('inf' ):
360363 r = [cMax / 2.0 ,
@@ -436,53 +439,51 @@ def expandVertex(self, vid):
436439 if (np .linalg .norm (scoord - currCoord , 2 ) <= self .r and sid != vid ):
437440 neigbors .append ((sid , scoord ))
438441
439- # add the vertex to the edge queue
440- if vid not in self .old_vertices :
441- neigbors = []
442- for v , edges in self .tree .vertices .items ():
443- if v != vid and (v , vid ) not in self .edge_queue :
444- vcoord = self .tree .nodeIdToRealWorldCoord (v )
445- if (np .linalg .norm (vcoord - currCoord , 2 ) <= self .r ):
446- neigbors .append ((vid , vcoord ))
447-
448442 # add an edge to the edge queue is the path might improve the solution
449443 for neighbor in neigbors :
450444 sid = neighbor [0 ]
445+ scoord = neighbor [1 ]
451446 estimated_f_score = self .computeDistanceCost (
452447 self .startId , vid ) + self .computeHeuristicCost (sid , self .goalId ) + self .computeDistanceCost (vid , sid )
453448 if estimated_f_score < self .g_scores [self .goalId ]:
454449 self .edge_queue .append ((vid , sid ))
455450
451+ # add the vertex to the edge queue
452+ if vid not in self .old_vertices :
453+ neigbors = []
454+ for v , edges in self .tree .vertices .items ():
455+ if v != vid and (v , vid ) not in self .edge_queue and (vid , v ) not in self .edge_queue :
456+ vcoord = self .tree .nodeIdToRealWorldCoord (v )
457+ if (np .linalg .norm (vcoord - currCoord , 2 ) <= self .r and v != vid ):
458+ neigbors .append ((vid , vcoord ))
459+
460+ for neighbor in neigbors :
461+ sid = neighbor [0 ]
462+ scoord = neighbor [1 ]
463+ estimated_f_score = self .computeDistanceCost (self .startId , vid ) + \
464+ self .computeDistanceCost (
465+ vid , sid ) + self .computeHeuristicCost (sid , self .goalId )
466+ if estimated_f_score < self .g_scores [self .goalId ] and (self .g_scores [vid ] + self .computeDistanceCost (vid , sid )) < self .g_scores [sid ]:
467+ self .edge_queue .append ((vid , sid ))
468+
456469 def updateGraph (self ):
457470 closedSet = []
458471 openSet = []
459472 currId = self .startId
460473 openSet .append (currId )
461474
462- # do some plotting
463-
464475 foundGoal = False
465476
466477 while len (openSet ) != 0 :
467478 # get the element with lowest f_score
468- minn = float ('inf' )
469- min_node = None
470- min_idx = 0
471- for i in range (0 , len (openSet )):
472- try :
473- f_score = self .f_scores [openSet [i ]]
474- except :
475- pass
476- if f_score < minn :
477- minn = f_score
478- min_node = openSet [i ]
479- min_idx = i
480- currId = min_node
481-
482- openSet .pop (min_idx )
479+ currId = min (openSet , key = lambda x : self .f_scores [x ])
480+
481+ # remove element from open set
482+ openSet .remove (currId )
483483
484484 # Check if we're at the goal
485485 if (currId == self .goalId ):
486+ self .nodes [self .goalId ]
486487 foundGoal = True
487488 break
488489
@@ -507,7 +508,7 @@ def updateGraph(self):
507508
508509 # update g and f scores
509510 self .g_scores [succesor ] = g_score
510- self .f_scores [succesor ] = f_score + \
511+ self .f_scores [succesor ] = g_score + \
511512 self .computeHeuristicCost (succesor , self .goalId )
512513
513514 # store the parent and child
@@ -526,12 +527,6 @@ def drawGraph(self, xCenter=None, cBest=None, cMin=None, etheta=None,
526527 if start is not None and end is not None :
527528 plt .plot ([start [0 ], start [1 ]], [end [0 ], end [1 ]], "-g" )
528529
529- if tree is not None and len (tree ) != 0 :
530- for key , value in tree .items ():
531- keyCoord = self .tree .nodeIdToRealWorldCoord (key )
532- valueCoord = self .tree .nodeIdToRealWorldCoord (value )
533- plt .plot (keyCoord , valueCoord , "-r" )
534-
535530 for (ox , oy , size ) in self .obstacleList :
536531 plt .plot (ox , oy , "ok" , ms = 30 * size )
537532
@@ -564,18 +559,23 @@ def plot_ellipse(self, xCenter, cBest, cMin, etheta):
564559def main ():
565560 print ("Starting Batch Informed Trees Star planning" )
566561 obstacleList = [
567- # (5, 5, 0.5),
568- # (9, 6, 1),
569- # (7, 5, 1),
570- # (1, 5, 1),
571- # (3, 6, 1),
572- # (7, 9, 1)
562+ (5 , 5 , 0.5 ),
563+ (9 , 6 , 1 ),
564+ (7 , 5 , 1 ),
565+ (1 , 5 , 1 ),
566+ (3 , 6 , 1 ),
567+ (7 , 9 , 1 )
573568 ]
574569
575- bitStar = BITStar (start = [0 , 0 ], goal = [2 , 4 ], obstacleList = obstacleList ,
576- randArea = [0 , 15 ])
570+ bitStar = BITStar (start = [- 1 , 0 ], goal = [3 , 8 ], obstacleList = obstacleList ,
571+ randArea = [- 2 , 15 ])
577572 path = bitStar .plan (animation = show_animation )
578- print ("Done" )
573+ print (path )
574+ if show_animation :
575+ plt .plot ([x for (x , y ) in path ], [y for (x , y ) in path ], '-r' )
576+ plt .grid (True )
577+ plt .pause (0.05 )
578+ plt .show ()
579579
580580
581581if __name__ == '__main__' :
0 commit comments