@@ -25,24 +25,45 @@ def __init__(self, start, lowerLimit, upperLimit, resolution):
25
25
self .start = start
26
26
self .lowerLimit = lowerLimit
27
27
self .upperLimit = upperLimit
28
+ self .dimension = len (lowerLimit )
29
+
30
+ # compute the number of grid cells based on the limits and
31
+ # resolution given
28
32
for idx in range (len (lowerLimit )):
29
33
self .num_cells [idx ] = np .ceil ((upperLimit [idx ] - lowerLimit [idx ])/ resolution )
30
34
31
35
def getRootId (self ):
36
+ # return the id of the root of the tree
32
37
return 0
33
38
34
39
def addVertex (self , vertex ):
40
+ # add a vertex to the tree
35
41
vertex_id = self .gridCoordinateToNodeId (vertex )
36
42
self .vertices [vertex_id ] = []
37
43
return vertex_id
38
44
39
45
def addEdge (self , v , x ):
46
+ # create an edge between v and x vertices
40
47
if (v , x ) not in self .edges :
41
48
self .edges .append ((v ,x ))
49
+ # since the tree is undirected
42
50
self .vertices [v ].append (x )
43
51
self .vertices [x ].append (v )
44
52
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
+
45
64
def gridCoordinateToNodeId (self , coord ):
65
+ # This function maps a grid coordinate to a unique
66
+ # node id
46
67
nodeId = 0
47
68
for i in range (len (coord ) - 1 , - 1 , - 1 ):
48
69
product = 1
@@ -51,6 +72,39 @@ def gridCoordinateToNodeId(self, coord):
51
72
node_id = node_id + coord [i ] * product
52
73
return node_id
53
74
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 ))
54
108
55
109
class Node ():
56
110
@@ -65,14 +119,16 @@ class BITStar():
65
119
def __init__ (self , start , goal ,
66
120
obstacleList , randArea , eta = 2.0 ,
67
121
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
+
70
125
self .minrand = randArea [0 ]
71
126
self .maxrand = randArea [1 ]
72
127
self .expandDis = expandDis
73
128
self .goalSampleRate = goalSampleRate
74
129
self .maxIter = maxIter
75
130
self .obstacleList = obstacleList
131
+
76
132
self .vertex_queue = []
77
133
self .edge_queue = []
78
134
self .samples = dict ()
@@ -84,12 +140,26 @@ def __init__(self, start, goal,
84
140
self .old_vertices = []
85
141
86
142
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 )
87
159
88
- self .nodeList = [self .start ]
89
- plan = None
90
160
iterations = 0
91
161
# 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 ]
93
163
pathLen = float ('inf' )
94
164
solutionSet = set ()
95
165
path = None
@@ -113,9 +183,68 @@ def plan(self, animation=True):
113
183
# run until done
114
184
while (iterations < self .maxIter ):
115
185
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 ))
117
188
# prune the tree
118
189
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
+
119
248
120
249
121
250
@@ -131,6 +260,20 @@ def plan(self, animation=True):
131
260
132
261
# def prune(self, c):
133
262
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
+
134
277
def radius (self , q ):
135
278
dim = len (start ) #dimensions
136
279
space_measure = self .minrand * self .maxrand # volume of the space
@@ -144,21 +287,22 @@ def radius(self, q):
144
287
145
288
# Sample free space confined in the radius of ball R
146
289
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
162
306
return samples
163
307
164
308
# Sample point in a unit ball
@@ -174,21 +318,72 @@ def sampleUnitBall(self):
174
318
return np .array ([[sample [0 ]], [sample [1 ]], [0 ]])
175
319
176
320
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 ),
179
322
random .uniform (self .minrand , self .maxrand )]
180
- else :
181
- rnd = [self .goal .x , self .goal .y ]
182
323
183
324
return rnd
184
325
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 ))
190
386
191
- # def bestInVertexQueue(self):
192
387
193
388
# def updateGraph(self):
194
389
0 commit comments