Skip to content

Commit 86c511e

Browse files
authored
Bidirectional Breadth-First Search (AtsushiSakai#316)
1 parent 734f3ae commit 86c511e

File tree

1 file changed

+311
-0
lines changed

1 file changed

+311
-0
lines changed
Lines changed: 311 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,311 @@
1+
"""
2+
3+
Bidirectional Breadth-First grid planning
4+
5+
author: Erwin Lejeune (@spida_rwin)
6+
7+
See Wikipedia article (https://en.wikipedia.org/wiki/Breadth-first_search)
8+
9+
"""
10+
11+
import math
12+
13+
import matplotlib.pyplot as plt
14+
15+
show_animation = True
16+
17+
18+
class BidirectionalBreadthFirstSearchPlanner:
19+
20+
def __init__(self, ox, oy, reso, rr):
21+
"""
22+
Initialize grid map for bfs planning
23+
24+
ox: x position list of Obstacles [m]
25+
oy: y position list of Obstacles [m]
26+
reso: grid resolution [m]
27+
rr: robot radius[m]
28+
"""
29+
30+
self.reso = reso
31+
self.rr = rr
32+
self.calc_obstacle_map(ox, oy)
33+
self.motion = self.get_motion_model()
34+
35+
class Node:
36+
def __init__(self, x, y, cost, pind, parent):
37+
self.x = x # index of grid
38+
self.y = y # index of grid
39+
self.cost = cost
40+
self.pind = pind
41+
self.parent = parent
42+
43+
def __str__(self):
44+
return str(self.x) + "," + str(self.y) + "," + str(
45+
self.cost) + "," + str(self.pind)
46+
47+
def planning(self, sx, sy, gx, gy):
48+
"""
49+
Bidirectional Breadth First search based planning
50+
51+
input:
52+
sx: start x position [m]
53+
sy: start y position [m]
54+
gx: goal x position [m]
55+
gy: goal y position [m]
56+
57+
output:
58+
rx: x position list of the final path
59+
ry: y position list of the final path
60+
"""
61+
62+
nstart = self.Node(self.calc_xyindex(sx, self.minx),
63+
self.calc_xyindex(sy, self.miny), 0.0, -1, None)
64+
ngoal = self.Node(self.calc_xyindex(gx, self.minx),
65+
self.calc_xyindex(gy, self.miny), 0.0, -1, None)
66+
67+
open_set_A, closed_set_A = dict(), dict()
68+
open_set_B, closed_set_B = dict(), dict()
69+
open_set_B[self.calc_grid_index(ngoal)] = ngoal
70+
open_set_A[self.calc_grid_index(nstart)] = nstart
71+
72+
while 1:
73+
if len(open_set_A) == 0:
74+
print("Open set A is empty..")
75+
break
76+
77+
if len(open_set_B) == 0:
78+
print("Open set B is empty")
79+
break
80+
81+
current_A = open_set_A.pop(list(open_set_A.keys())[0])
82+
current_B = open_set_B.pop(list(open_set_B.keys())[0])
83+
84+
c_id_A = self.calc_grid_index(current_A)
85+
c_id_B = self.calc_grid_index(current_B)
86+
87+
closed_set_A[c_id_A] = current_A
88+
closed_set_B[c_id_B] = current_B
89+
90+
# show graph
91+
if show_animation: # pragma: no cover
92+
plt.plot(self.calc_grid_position(current_A.x, self.minx),
93+
self.calc_grid_position(current_A.y, self.miny), "xc")
94+
plt.plot(self.calc_grid_position(current_B.x, self.minx),
95+
self.calc_grid_position(current_B.y, self.miny), "xc")
96+
# for stopping simulation with the esc key.
97+
plt.gcf().canvas.mpl_connect('key_release_event',
98+
lambda event:
99+
[exit(0) if
100+
event.key == 'escape' else None])
101+
if len(closed_set_A.keys()) % 10 == 0:
102+
plt.pause(0.001)
103+
104+
if c_id_A in closed_set_B:
105+
print("Find goal")
106+
meetpointA = closed_set_A[c_id_A]
107+
meetpointB = closed_set_B[c_id_A]
108+
break
109+
110+
elif c_id_B in closed_set_A:
111+
print("Find goal")
112+
meetpointA = closed_set_A[c_id_B]
113+
meetpointB = closed_set_B[c_id_B]
114+
break
115+
116+
# expand_grid search grid based on motion model
117+
for i, _ in enumerate(self.motion):
118+
breakA = False
119+
breakB = False
120+
121+
node_A = self.Node(current_A.x + self.motion[i][0],
122+
current_A.y + self.motion[i][1],
123+
current_A.cost + self.motion[i][2],
124+
c_id_A, None)
125+
node_B = self.Node(current_B.x + self.motion[i][0],
126+
current_B.y + self.motion[i][1],
127+
current_B.cost + self.motion[i][2],
128+
c_id_B, None)
129+
130+
n_id_A = self.calc_grid_index(node_A)
131+
n_id_B = self.calc_grid_index(node_B)
132+
133+
# If the node is not safe, do nothing
134+
if not self.verify_node(node_A):
135+
breakA = True
136+
137+
if not self.verify_node(node_B):
138+
breakB = True
139+
140+
if (n_id_A not in closed_set_A) and (n_id_A not in
141+
open_set_A) and (not
142+
breakA):
143+
node_A.parent = current_A
144+
open_set_A[n_id_A] = node_A
145+
146+
if (n_id_B not in closed_set_B) and (n_id_B not in
147+
open_set_B) and (not
148+
breakB):
149+
node_B.parent = current_B
150+
open_set_B[n_id_B] = node_B
151+
152+
rx, ry = self.calc_final_path_bidir(
153+
meetpointA, meetpointB, closed_set_A, closed_set_B)
154+
return rx, ry
155+
156+
# takes both set and meeting nodes and calculate optimal path
157+
def calc_final_path_bidir(self, n1, n2, setA, setB):
158+
rxA, ryA = self.calc_final_path(n1, setA)
159+
rxB, ryB = self.calc_final_path(n2, setB)
160+
161+
rxA.reverse()
162+
ryA.reverse()
163+
164+
rx = rxA + rxB
165+
ry = ryA + ryB
166+
167+
return rx, ry
168+
169+
def calc_final_path(self, ngoal, closedset):
170+
# generate final course
171+
rx, ry = [self.calc_grid_position(ngoal.x, self.minx)], [
172+
self.calc_grid_position(ngoal.y, self.miny)]
173+
n = closedset[ngoal.pind]
174+
while n is not None:
175+
rx.append(self.calc_grid_position(n.x, self.minx))
176+
ry.append(self.calc_grid_position(n.y, self.miny))
177+
n = n.parent
178+
179+
return rx, ry
180+
181+
def calc_grid_position(self, index, minp):
182+
"""
183+
calc grid position
184+
185+
:param index:
186+
:param minp:
187+
:return:
188+
"""
189+
pos = index * self.reso + minp
190+
return pos
191+
192+
def calc_xyindex(self, position, min_pos):
193+
return round((position - min_pos) / self.reso)
194+
195+
def calc_grid_index(self, node):
196+
return (node.y - self.miny) * self.xwidth + (node.x - self.minx)
197+
198+
def verify_node(self, node):
199+
px = self.calc_grid_position(node.x, self.minx)
200+
py = self.calc_grid_position(node.y, self.miny)
201+
202+
if px < self.minx:
203+
return False
204+
elif py < self.miny:
205+
return False
206+
elif px >= self.maxx:
207+
return False
208+
elif py >= self.maxy:
209+
return False
210+
211+
# collision check
212+
if self.obmap[node.x][node.y]:
213+
return False
214+
215+
return True
216+
217+
def calc_obstacle_map(self, ox, oy):
218+
219+
self.minx = round(min(ox))
220+
self.miny = round(min(oy))
221+
self.maxx = round(max(ox))
222+
self.maxy = round(max(oy))
223+
print("minx:", self.minx)
224+
print("miny:", self.miny)
225+
print("maxx:", self.maxx)
226+
print("maxy:", self.maxy)
227+
228+
self.xwidth = round((self.maxx - self.minx) / self.reso)
229+
self.ywidth = round((self.maxy - self.miny) / self.reso)
230+
print("xwidth:", self.xwidth)
231+
print("ywidth:", self.ywidth)
232+
233+
# obstacle map generation
234+
self.obmap = [[False for _ in range(self.ywidth)]
235+
for _ in range(self.xwidth)]
236+
for ix in range(self.xwidth):
237+
x = self.calc_grid_position(ix, self.minx)
238+
for iy in range(self.ywidth):
239+
y = self.calc_grid_position(iy, self.miny)
240+
for iox, ioy in zip(ox, oy):
241+
d = math.hypot(iox - x, ioy - y)
242+
if d <= self.rr:
243+
self.obmap[ix][iy] = True
244+
break
245+
246+
@staticmethod
247+
def get_motion_model():
248+
# dx, dy, cost
249+
motion = [[1, 0, 1],
250+
[0, 1, 1],
251+
[-1, 0, 1],
252+
[0, -1, 1],
253+
[-1, -1, math.sqrt(2)],
254+
[-1, 1, math.sqrt(2)],
255+
[1, -1, math.sqrt(2)],
256+
[1, 1, math.sqrt(2)]]
257+
258+
return motion
259+
260+
261+
def main():
262+
print(__file__ + " start!!")
263+
264+
# start and goal position
265+
sx = 10.0 # [m]
266+
sy = 10.0 # [m]
267+
gx = 50.0 # [m]
268+
gy = 50.0 # [m]
269+
grid_size = 2.0 # [m]
270+
robot_radius = 1.0 # [m]
271+
272+
# set obstacle positions
273+
ox, oy = [], []
274+
for i in range(-10, 60):
275+
ox.append(i)
276+
oy.append(-10.0)
277+
for i in range(-10, 60):
278+
ox.append(60.0)
279+
oy.append(i)
280+
for i in range(-10, 61):
281+
ox.append(i)
282+
oy.append(60.0)
283+
for i in range(-10, 61):
284+
ox.append(-10.0)
285+
oy.append(i)
286+
for i in range(-10, 40):
287+
ox.append(20.0)
288+
oy.append(i)
289+
for i in range(0, 40):
290+
ox.append(40.0)
291+
oy.append(60.0 - i)
292+
293+
if show_animation: # pragma: no cover
294+
plt.plot(ox, oy, ".k")
295+
plt.plot(sx, sy, "og")
296+
plt.plot(gx, gy, "ob")
297+
plt.grid(True)
298+
plt.axis("equal")
299+
300+
bi_bfs = BidirectionalBreadthFirstSearchPlanner(
301+
ox, oy, grid_size, robot_radius)
302+
rx, ry = bi_bfs.planning(sx, sy, gx, gy)
303+
304+
if show_animation: # pragma: no cover
305+
plt.plot(rx, ry, "-r")
306+
plt.pause(0.01)
307+
plt.show()
308+
309+
310+
if __name__ == '__main__':
311+
main()

0 commit comments

Comments
 (0)