@@ -25,24 +25,45 @@ def __init__(self, start, lowerLimit, upperLimit, resolution):
2525 self .start = start
2626 self .lowerLimit = lowerLimit
2727 self .upperLimit = upperLimit
28+ self .dimension = len (lowerLimit )
29+
30+ # compute the number of grid cells based on the limits and
31+ # resolution given
2832 for idx in range (len (lowerLimit )):
2933 self .num_cells [idx ] = np .ceil ((upperLimit [idx ] - lowerLimit [idx ])/ resolution )
3034
3135 def getRootId (self ):
36+ # return the id of the root of the tree
3237 return 0
3338
3439 def addVertex (self , vertex ):
40+ # add a vertex to the tree
3541 vertex_id = self .gridCoordinateToNodeId (vertex )
3642 self .vertices [vertex_id ] = []
3743 return vertex_id
3844
3945 def addEdge (self , v , x ):
46+ # create an edge between v and x vertices
4047 if (v , x ) not in self .edges :
4148 self .edges .append ((v ,x ))
49+ # since the tree is undirected
4250 self .vertices [v ].append (x )
4351 self .vertices [x ].append (v )
4452
53+ def realCoordsToGridCoord (self , real_coord ):
54+ # convert real world coordinates to grid space
55+ # depends on the resolution of the grid
56+ # the output is the same as real world coords if the resolution
57+ # is set to 1
58+ coord = [0 ] * self .dimension
59+ for i in xrange (0 , len (coord )):
60+ start = self .lower_limits [i ] # start of the grid space
61+ coord [i ] = np .around ((real_coord [i ] - start )/ self .resolution )
62+ return coord
63+
4564 def gridCoordinateToNodeId (self , coord ):
65+ # This function maps a grid coordinate to a unique
66+ # node id
4667 nodeId = 0
4768 for i in range (len (coord ) - 1 , - 1 , - 1 ):
4869 product = 1
@@ -51,6 +72,39 @@ def gridCoordinateToNodeId(self, coord):
5172 node_id = node_id + coord [i ] * product
5273 return node_id
5374
75+ def realWorldToNodeId (self , real_coord ):
76+ # first convert the given coordinates to grid space and then
77+ # convert the grid space coordinates to a unique node id
78+ return self .gridCoordinateToNodeId (self .realCoordsToGridCoord (real_coord ))
79+
80+ def gridCoordToRealWorldCoord (self , coord ):
81+ # This function smaps a grid coordinate in discrete space
82+ # to a configuration in the full configuration space
83+ config = [0 ] * self .dimension
84+ for i in range (0 , len (coord )):
85+ start = self .lower_limits [i ] # start of the real world / configuration space
86+ grid_step = self .resolution * coord [i ] # step from the coordinate in the grid
87+ half_step = self .resolution / 2 # To get to middle of the grid
88+ config [i ] = start + grid_step # + half_step
89+ return config
90+
91+ def nodeIdToGridCoord (self , node_id ):
92+ # This function maps a node id to the associated
93+ # grid coordinate
94+ coord = [0 ] * len (self .lowerLimit )
95+ for i in range (len (coord ) - 1 , - 1 , - 1 ):
96+ # Get the product of the grid space maximums
97+ prod = 1
98+ for j in range (0 , i ):
99+ prod = prod * self .num_cells [j ]
100+ coord [i ] = np .floor (node_id / prod )
101+ node_id = node_id - (coord [i ] * prod )
102+ return coord
103+
104+ def nodeIdToRealWorldCoord (self , nid ):
105+ # This function maps a node in discrete space to a configuraiton
106+ # in the full configuration space
107+ return self .gridCoordToRealWorldCoord (self .nodeIdToGridCoord (nid ))
54108
55109class Node ():
56110
@@ -65,14 +119,16 @@ class BITStar():
65119 def __init__ (self , start , goal ,
66120 obstacleList , randArea , eta = 2.0 ,
67121 expandDis = 0.5 , goalSampleRate = 10 , maxIter = 200 ):
68- self .start = Node (start [0 ], start [1 ])
69- self .goal = Node (goal [0 ], goal [1 ])
122+ self .start = start
123+ self .goal = goal
124+
70125 self .minrand = randArea [0 ]
71126 self .maxrand = randArea [1 ]
72127 self .expandDis = expandDis
73128 self .goalSampleRate = goalSampleRate
74129 self .maxIter = maxIter
75130 self .obstacleList = obstacleList
131+
76132 self .vertex_queue = []
77133 self .edge_queue = []
78134 self .samples = dict ()
@@ -84,12 +140,26 @@ def __init__(self, start, goal,
84140 self .old_vertices = []
85141
86142 def plan (self , animation = True ):
143+ # initialize tree
144+ self .tree = Tree (self .start ,[self .minrand , self .minrand ],
145+ [self .maxrand , self .maxrand ], 1.0 )
146+
147+ self .startId = self .tree .realWorldToNodeId (self .start )
148+ self .goalId = self .tree .realWorldToNodeId (self .goal )
149+
150+ # add goal to the samples
151+ self .samples [self .goalId ] = self .goal
152+ self .g_scores [self .goalId ] = float ('inf' )
153+ self .f_scores [self .goalId ] = 0
154+
155+ # add the start id to the tree
156+ self .tree .addVertex (self .start )
157+ self .g_scores [self .startId ] = 0
158+ self .f_scores [self .startId ] = self .computeHeuristicCost (self .startId , self .goalId )
87159
88- self .nodeList = [self .start ]
89- plan = None
90160 iterations = 0
91161 # max length we expect to find in our 'informed' sample space, starts as infinite
92- cBest = float ( 'inf' )
162+ cBest = self . g_scores [ self . goalId ]
93163 pathLen = float ('inf' )
94164 solutionSet = set ()
95165 path = None
@@ -113,9 +183,68 @@ def plan(self, animation=True):
113183 # run until done
114184 while (iterations < self .maxIter ):
115185 if len (self .vertex_queue ) == 0 and len (self .edge_queue ) == 0 :
116- samples = self .informedSample (100 , cBest , cMin , xCenter , C )
186+ # Using informed rrt star way of computing the samples
187+ self .samples .update (self .informedSample (200 , cBest , cMin , xCenter , C ))
117188 # prune the tree
118189
190+ if iterations != 0 :
191+ self .samples .update (self .informedSample (200 , cBest , cMin , xCenter , C ))
192+
193+ # make the old vertices the new vertices
194+ self .old_vertices += self .tree .vertices .keys ()
195+ # add the vertices to the vertex queue
196+ for nid in self .tree .vertices .keys ():
197+ if nid not in self .vertex_queue :
198+ self .vertex_queue .append (nid )
199+ # expand the best vertices until an edge is better than the vertex
200+ # this is done because the vertex cost represents the lower bound
201+ # on the edge cost
202+ while (self .bestVertexQueueValue () <= self .bestEdgeQueueValue ()):
203+ self .expandVertex (self .bestInVertexQueue ())
204+
205+ # add the best edge to the tree
206+ bestEdge = self .bestInEdgeQueue ()
207+ self .edge_queue .remove (bestEdge )
208+
209+ # Check if this can improve the current solution
210+ estimatedCostOfVertex = self .g_scores [bestEdge [0 ]] +
211+ self .computeDistanceCost (bestEdge [0 ], bestEdge [1 ]) +
212+ self .computeHeuristicCost (bestEdge [1 ], self .goalId )
213+ estimatedCostOfEdge = self .computeDistanceCost (self .startId , bestEdge [0 ]) +
214+ self .computeHeuristicCost (bestEdge [0 ], bestEdge [1 ]) +
215+ self .computeHeuristicCost (bestEdge [1 ], self .goalId )
216+ actualCostOfEdge = self .g_scores [bestEdge [0 ]] + + self .computeDistanceCost (best_edge [0 ], best_edge [1 ])
217+
218+ if (estimatedCostOfVertex < self .g_scores [self .goalId ]):
219+ if (estimatedCostOfEdge < self .g_scores [self .goalId ]):
220+ if (actualCostOfEdge < self .g_scores [self .goalId ]):
221+ # connect this edge
222+ firstCoord = self .tree .nodeIdToRealWorldCoord (bestEdge [0 ])
223+ secondCoord = self .tree .nodeIdToRealWorldCoord (bestEdge [1 ])
224+ path = self .connect (firstCoord , secondCoord )
225+ if path == None or len (path ) = 0 :
226+ continue
227+ nextCoord = path [len (path ) - 1 , :]
228+ nextCoordPathId = self .tree .realWorldToNodeId (nextCoord )
229+ bestEdge = (bestEdge [0 ], nextCoordPathId )
230+ try :
231+ del self .samples [bestEdge [1 ]]
232+ except (KeyError ):
233+ pass
234+ eid = self .tree .addVertex (nextCoordPathId )
235+ self .vertex_queue .append (eid )
236+ if eid == self .goalId or bestEdge [0 ] == self .goalId or
237+ bestEdge [1 ] == self .goalId :
238+ print ("Goal found" )
239+ foundGoal = True
240+
241+ self .tree .addEdge (bestEdge [0 ], bestEdge [1 ])
242+
243+ g_score = self .computeDistanceCost (bestEdge [0 ], bestEdge [1 ])
244+ self .g_scores [bestEdge [1 ]] = g_score + self .g_scores [best_edge [0 ]]
245+ self .f_scores [bestEdge [1 ]] = g_score + self .computeHeuristicCost (bestEdge [1 ], self .goalId )
246+ self .updateGraph ()
247+
119248
120249
121250
@@ -131,6 +260,20 @@ def plan(self, animation=True):
131260
132261 # def prune(self, c):
133262
263+ def computeHeuristicCost (self , start_id , goal_id ):
264+ # Using Manhattan distance as heuristic
265+ start = np .array (self .tree .nodeIdToRealWorldCoord (start_id ))
266+ goal = np .array (self .tree .nodeIdToRealWorldCoord (goal_id ))
267+
268+ return np .linalg .norm (start - goal , 2 )
269+
270+ def computeDistanceCost (self , vid , xid ):
271+ # L2 norm distance
272+ start = np .array (self .tree .nodeIdToRealWorldCoord (vid ))
273+ stop = np .array (self .tree .nodeIdToRealWorldCoord (xid ))
274+
275+ return np .linalg .norm (stop - start , 2 )
276+
134277 def radius (self , q ):
135278 dim = len (start ) #dimensions
136279 space_measure = self .minrand * self .maxrand # volume of the space
@@ -144,21 +287,22 @@ def radius(self, q):
144287
145288 # Sample free space confined in the radius of ball R
146289 def informedSample (self , m , cMax , cMin , xCenter , C ):
147- samples = []
148- if cMax < float ('inf' ):
149- for i in range (m ):
150- r = [cMax / 2.0 ,
151- math .sqrt (cMax ** 2 - cMin ** 2 ) / 2.0 ,
152- math .sqrt (cMax ** 2 - cMin ** 2 ) / 2.0 ]
153- L = np .diag (r )
154- xBall = self .sampleUnitBall ()
155- rnd = np .dot (np .dot (C , L ), xBall ) + xCenter
156- rnd = [rnd [(0 , 0 )], rnd [(1 , 0 )]]
157- samples .append (rnd )
158- else :
159- for i in range (m ):
160- rnd = self .sampleFreeSpace ()
161- samples .append (rnd )
290+ samples = dict ()
291+ for i in range (m + 1 ):
292+ if cMax < float ('inf' ):
293+ r = [cMax / 2.0 ,
294+ math .sqrt (cMax ** 2 - cMin ** 2 ) / 2.0 ,
295+ math .sqrt (cMax ** 2 - cMin ** 2 ) / 2.0 ]
296+ L = np .diag (r )
297+ xBall = self .sampleUnitBall ()
298+ rnd = np .dot (np .dot (C , L ), xBall ) + xCenter
299+ rnd = [rnd [(0 , 0 )], rnd [(1 , 0 )]]
300+ random_id = self .tree .realWorldToNodeId (rnd )
301+ samples [random_id ] = rnd
302+ else :
303+ rnd = self .sampleFreeSpace ()
304+ random_id = self .tree .realWorldToNodeId (rnd )
305+ samples [random_id ] = rnd
162306 return samples
163307
164308 # Sample point in a unit ball
@@ -174,21 +318,72 @@ def sampleUnitBall(self):
174318 return np .array ([[sample [0 ]], [sample [1 ]], [0 ]])
175319
176320 def sampleFreeSpace (self ):
177- if random .randint (0 , 100 ) > self .goalSampleRate :
178- rnd = [random .uniform (self .minrand , self .maxrand ),
321+ rnd = [random .uniform (self .minrand , self .maxrand ),
179322 random .uniform (self .minrand , self .maxrand )]
180- else :
181- rnd = [self .goal .x , self .goal .y ]
182323
183324 return rnd
184325
185- # def bestVertexQueueValue(self):
186-
187- # def bestEdgeQueueValue(self):
188-
189- # def bestInEdgeQueue(self):
326+ def bestVertexQueueValue (self ):
327+ if (len (self .vertex_queue ) == 0 ):
328+ return float ('inf' )
329+ values = [self .g_scores [v ] + self .computeHeuristicCost (v , self .goalId ) for v in self .vertex_queue ]
330+ values .sort ()
331+ return values [0 ]
332+
333+ def bestEdgeQueueValue (self ):
334+ if (len (self .edge_queue )== 0 ):
335+ return float ('inf' )
336+ # return the best value in the queue by score g_tau[v] + c(v,x) + h(x)
337+ values = [self .g_scores [e [0 ]] + self .computeDistanceCost (e [0 ], e [1 ]) +
338+ self .computeHeuristicCost (e [1 ], self .goalId ) for e in self .edge_queue ]
339+ values .sort (reverse = True )
340+ return values [0 ]
341+
342+ def bestInVertexQueue (self ):
343+ # return the best value in the vertex queue
344+ v_plus_vals = [(v , self .g_scores [v ] + self .computeHeuristicCost (v , self .goalId )) for v in self .vertex_queue ]
345+ v_plus_vals = sorted (v_plus_vals , key = lambda x : x [1 ])
346+
347+ return v_plus_vals [0 ][0 ]
348+
349+ def bestInEdgeQueue (self ):
350+ e_and_values = [(e [0 ], e [1 ], self .g_scores [e [0 ]] + self .computeDistanceCost (e [0 ], e [1 ]) + self .computeHeuristicCost (e [1 ], self .goalId )) for e in self .edge_queue ]
351+ e_and_values = sorted (e_and_values , key = lambda x : x [2 ])
352+
353+ return (e_and_values [0 ][0 ], e_and_values [0 ][1 ])
354+
355+ def expandVertex (self , vid ):
356+ self .vertex_queue .remove (vid )
357+
358+ # get the coordinates for given vid
359+ currCoord = np .array (self .nodeIdToRealWorldCoord (vid ))
360+
361+ # get the nearest value in vertex for every one in samples where difference is
362+ # less than the radius
363+ neigbors = []
364+ for sid , scoord in self .samples .items ():
365+ scoord = np .array (scoord )
366+ if (np .linalg .norm (scoord - currCoord , 2 ) <= self .r and sid != vid ):
367+ neigbors .append ((sid , scoord ))
368+
369+ # add the vertex to the edge queue
370+ if vid not in self .old_vertices :
371+ neigbors = []
372+ for v , edges in self .tree .vertices .items ():
373+ if v != vid and (v , vid ) not in self .edge_queue :
374+ vcoord = self .tree .nodeIdToRealWorldCoord (v )
375+ if (np .linalg .norm (vcoord - currCoord , 2 ) <= self .r and v != vid ):
376+ neigbors .append ((vid , vcoord ))
377+
378+ # add an edge to the edge queue is the path might improve the solution
379+ for neighbor in neighbors :
380+ sid = neighbor [0 ]
381+ estimated_f_score = self .computeDistanceCost (self .startId , vid ) +
382+ self .computeHeuristicCost (sif , self .goalId ) +
383+ self .computeDistanceCost (vid , sid )
384+ if estimated_f_score < self .g_scores [self .goalId ]:
385+ self .edge_queue .append ((vid , sid ))
190386
191- # def bestInVertexQueue(self):
192387
193388 # def updateGraph(self):
194389
0 commit comments