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