1
+ """
2
+ Obstacle navigation using A* on a toroidal grid
3
+
4
+ Author: Daniel Ingram (daniel-s-ingram)
5
+ """
6
+ from math import pi
7
+ import numpy as np
8
+ import matplotlib .pyplot as plt
9
+ from matplotlib .colors import from_levels_and_colors
10
+
11
+ plt .ion ()
12
+
13
+ #Simulation parameters
14
+ M = 100
15
+ obstacles = [[1.75 , 0.75 , 0.6 ], [0.55 , 1.5 , 0.5 ], [0 , - 1 , 0.25 ]]
16
+
17
+ def main ():
18
+ arm = NLinkArm ([1 , 1 ], [0 , 0 ])
19
+ grid = get_occupancy_grid (arm , obstacles )
20
+ plt .imshow (grid )
21
+ plt .show ()
22
+ #(50,50) (58,56)
23
+ route = astar_torus (grid , (10 , 50 ), (58 , 56 ))
24
+ for node in route :
25
+ theta1 = 2 * pi * node [0 ]/ M - pi
26
+ theta2 = 2 * pi * node [1 ]/ M - pi
27
+ arm .update_joints ([theta1 , theta2 ])
28
+ arm .plot (obstacles = obstacles )
29
+
30
+ def detect_collision (line_seg , circle ):
31
+ """
32
+ Determines whether a line segment (arm link) is in contact
33
+ with a circle (obstacle).
34
+ Credit to: http://doswa.com/2009/07/13/circle-segment-intersectioncollision.html
35
+ Args:
36
+ line_seg: List of coordinates of line segment endpoints e.g. [[1, 1], [2, 2]]
37
+ circle: List of circle coordinates and radius e.g. [0, 0, 0.5] is a circle centered
38
+ at the origin with radius 0.5
39
+
40
+ Returns:
41
+ True if the line segment is in contact with the circle
42
+ False otherwise
43
+ """
44
+ a_vec = np .array ([line_seg [0 ][0 ], line_seg [0 ][1 ]])
45
+ b_vec = np .array ([line_seg [1 ][0 ], line_seg [1 ][1 ]])
46
+ c_vec = np .array ([circle [0 ], circle [1 ]])
47
+ radius = circle [2 ]
48
+ line_vec = b_vec - a_vec
49
+ line_mag = np .linalg .norm (line_vec )
50
+ circle_vec = c_vec - a_vec
51
+ proj = circle_vec .dot (line_vec / line_mag )
52
+ if proj <= 0 :
53
+ closest_point = a_vec
54
+ elif proj >= line_mag :
55
+ closest_point = b_vec
56
+ else :
57
+ closest_point = a_vec + line_vec * proj / line_mag
58
+ if np .linalg .norm (closest_point - c_vec ) > radius :
59
+ return False
60
+ else :
61
+ return True
62
+
63
+ def get_occupancy_grid (arm , obstacles ):
64
+ """
65
+ Discretizes joint space into M values from -pi to +pi
66
+ and determines whether a given coordinate in joint space
67
+ would result in a collision between a robot arm and obstacles
68
+ in its environment.
69
+
70
+ Args:
71
+ arm: An instance of NLinkArm
72
+ obstacles: A list of obstacles, with each obstacle defined as a list
73
+ of xy coordinates and a radius.
74
+
75
+ Returns:
76
+ Occupancy grid in joint space
77
+ """
78
+ grid = [[0 for _ in range (M )] for _ in range (M )]
79
+ theta_list = [2 * i * pi / M for i in range (- M // 2 , M // 2 + 1 )]
80
+ for i in range (M ):
81
+ for j in range (M ):
82
+ arm .update_joints ([theta_list [i ], theta_list [j ]])
83
+ points = arm .points
84
+ collision_detected = False
85
+ for k in range (len (points )- 1 ):
86
+ for obstacle in obstacles :
87
+ line_seg = [points [k ], points [k + 1 ]]
88
+ collision_detected = detect_collision (line_seg , obstacle )
89
+ if collision_detected :
90
+ break
91
+ if collision_detected :
92
+ break
93
+ grid [i ][j ] = int (collision_detected )
94
+ return np .array (grid )
95
+
96
+ def astar_torus (grid , start_node , goal_node ):
97
+ """
98
+ Finds a path between an initial and goal joint configuration using
99
+ the A* Algorithm on a tororiadal grid.
100
+
101
+ Args:
102
+ grid: An occupancy grid (ndarray)
103
+ start_node: Initial joint configuation (tuple)
104
+ goal_node: Goal joint configuration (tuple)
105
+
106
+ Returns:
107
+ Obstacle-free route in joint space from start_node to goal_node
108
+ """
109
+ colors = ['white' , 'black' , 'red' , 'pink' , 'yellow' , 'green' , 'orange' ]
110
+ levels = [0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 ]
111
+ cmap , norm = from_levels_and_colors (levels , colors )
112
+
113
+ grid [start_node ] = 4
114
+ grid [goal_node ] = 5
115
+
116
+ parent_map = [[() for _ in range (M )] for _ in range (M )]
117
+
118
+ X , Y = np .meshgrid ([i for i in range (M )], [i for i in range (M )])
119
+ heuristic_map = np .abs (X - goal_node [1 ]) + np .abs (Y - goal_node [0 ])
120
+ for i in range (heuristic_map .shape [0 ]):
121
+ for j in range (heuristic_map .shape [1 ]):
122
+ heuristic_map [i ,j ] = min (heuristic_map [i ,j ],
123
+ i + 1 + heuristic_map [M - 1 ,j ],
124
+ M - i + heuristic_map [0 ,j ],
125
+ j + 1 + heuristic_map [i ,M - 1 ],
126
+ M - j + heuristic_map [i ,0 ]
127
+ )
128
+
129
+ explored_heuristic_map = np .full ((M , M ), np .inf )
130
+ distance_map = np .full ((M , M ), np .inf )
131
+ explored_heuristic_map [start_node ] = heuristic_map [start_node ]
132
+ distance_map [start_node ] = 0
133
+ while True :
134
+ grid [start_node ] = 4
135
+ grid [goal_node ] = 5
136
+
137
+ current_node = np .unravel_index (np .argmin (explored_heuristic_map , axis = None ), explored_heuristic_map .shape )
138
+ min_distance = np .min (explored_heuristic_map )
139
+ if (current_node == goal_node ) or np .isinf (min_distance ):
140
+ break
141
+
142
+ grid [current_node ] = 2
143
+ explored_heuristic_map [current_node ] = np .inf
144
+
145
+ i , j = current_node [0 ], current_node [1 ]
146
+
147
+ neighbors = []
148
+ if i - 1 >= 0 :
149
+ neighbors .append ((i - 1 , j ))
150
+ else :
151
+ neighbors .append ((M - 1 , j ))
152
+
153
+ if i + 1 < M :
154
+ neighbors .append ((i + 1 , j ))
155
+ else :
156
+ neighbors .append ((0 , j ))
157
+
158
+ if j - 1 >= 0 :
159
+ neighbors .append ((i , j - 1 ))
160
+ else :
161
+ neighbors .append ((i , M - 1 ))
162
+
163
+ if j + 1 < M :
164
+ neighbors .append ((i , j + 1 ))
165
+ else :
166
+ neighbors .append ((i , 0 ))
167
+
168
+ for neighbor in neighbors :
169
+ if grid [neighbor ] == 0 or grid [neighbor ] == 5 :
170
+ distance_map [neighbor ] = distance_map [current_node ] + 1
171
+ explored_heuristic_map [neighbor ] = heuristic_map [neighbor ]
172
+ parent_map [neighbor [0 ]][neighbor [1 ]] = current_node
173
+ grid [neighbor ] = 3
174
+ '''
175
+ plt.cla()
176
+ plt.imshow(grid, cmap=cmap, norm=norm, interpolation=None)
177
+ plt.show()
178
+ plt.pause(1e-5)
179
+ '''
180
+
181
+ if np .isinf (explored_heuristic_map [goal_node ]):
182
+ route = []
183
+ print ("No route found." )
184
+ else :
185
+ route = [goal_node ]
186
+ while parent_map [route [0 ][0 ]][route [0 ][1 ]] is not ():
187
+ route .insert (0 , parent_map [route [0 ][0 ]][route [0 ][1 ]])
188
+
189
+ print ("The route found covers %d grid cells." % len (route ))
190
+ for i in range (1 , len (route )):
191
+ grid [route [i ]] = 6
192
+ plt .cla ()
193
+ plt .imshow (grid , cmap = cmap , norm = norm , interpolation = None )
194
+ plt .show ()
195
+ plt .pause (1e-2 )
196
+
197
+ return route
198
+
199
+ class NLinkArm (object ):
200
+ """
201
+ Class for controlling and plotting a planar arm with an arbitrary number of links.
202
+ """
203
+ def __init__ (self , link_lengths , joint_angles ):
204
+ self .n_links = len (link_lengths )
205
+ if self .n_links is not len (joint_angles ):
206
+ raise ValueError ()
207
+
208
+ self .link_lengths = np .array (link_lengths )
209
+ self .joint_angles = np .array (joint_angles )
210
+ self .points = [[0 , 0 ] for _ in range (self .n_links + 1 )]
211
+
212
+ self .lim = sum (link_lengths )
213
+ self .update_points ()
214
+
215
+ def update_joints (self , joint_angles ):
216
+ self .joint_angles = joint_angles
217
+ self .update_points ()
218
+
219
+ def update_points (self ):
220
+ for i in range (1 , self .n_links + 1 ):
221
+ self .points [i ][0 ] = self .points [i - 1 ][0 ] + self .link_lengths [i - 1 ]* np .cos (np .sum (self .joint_angles [:i ]))
222
+ self .points [i ][1 ] = self .points [i - 1 ][1 ] + self .link_lengths [i - 1 ]* np .sin (np .sum (self .joint_angles [:i ]))
223
+
224
+ self .end_effector = np .array (self .points [self .n_links ]).T
225
+
226
+ def plot (self , obstacles = []):
227
+ plt .cla ()
228
+
229
+ for obstacle in obstacles :
230
+ circle = plt .Circle ((obstacle [0 ], obstacle [1 ]), radius = 0.5 * obstacle [2 ], fc = 'k' )
231
+ plt .gca ().add_patch (circle )
232
+
233
+ for i in range (self .n_links + 1 ):
234
+ if i is not self .n_links :
235
+ plt .plot ([self .points [i ][0 ], self .points [i + 1 ][0 ]], [self .points [i ][1 ], self .points [i + 1 ][1 ]], 'r-' )
236
+ plt .plot (self .points [i ][0 ], self .points [i ][1 ], 'k.' )
237
+
238
+ plt .xlim ([- self .lim , self .lim ])
239
+ plt .ylim ([- self .lim , self .lim ])
240
+ plt .draw ()
241
+ plt .pause (1e-5 )
242
+
243
+ if __name__ == '__main__' :
244
+ main ()
0 commit comments