Skip to content

Commit 53268f2

Browse files
author
Karan
committed
substantial progress with plan method, todo: updateGraph()
1 parent fd8a5fc commit 53268f2

File tree

1 file changed

+226
-31
lines changed

1 file changed

+226
-31
lines changed

PathPlanning/BatchInformedRRTStar/batch_informed_rrtstar.py

Lines changed: 226 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -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

55109
class 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

Comments
 (0)