1010 - Shi, Y.; Eberhart, R. (1998). "A Modified Particle Swarm Optimizer"
1111 - https://machinelearningmastery.com/a-gentle-introduction-to-particle-swarm-optimization/
1212
13- This implementation uses PSO to find collision-free paths by treating
14- path planning as an optimization problem where particles explore the
13+ This implementation uses PSO to find collision-free paths by treating
14+ path planning as an optimization problem where particles explore the
1515search space to minimize distance to target while avoiding obstacles.
1616"""
1717import numpy as np
@@ -107,7 +107,16 @@ def fitness(self, pos):
107107 return dist + penalty
108108
109109 def check_collision (self , start , end , obstacle ):
110- """Check if path from start to end hits obstacle using line-circle intersection"""
110+ """Check if path from start to end hits obstacle using line-circle intersection
111+
112+ Args:
113+ start: Starting position (numpy array)
114+ end: Ending position (numpy array)
115+ obstacle: Tuple (ox, oy, r) representing obstacle center and radius
116+
117+ Returns:
118+ bool: True if collision detected, False otherwise
119+ """
111120 ox , oy , r = obstacle
112121 center = np .array ([ox , oy ])
113122
@@ -116,6 +125,12 @@ def check_collision(self, start, end, obstacle):
116125 f = start - center
117126
118127 a = np .dot (d , d )
128+
129+ # FIX: Guard against zero-length steps to prevent ZeroDivisionError
130+ if a < 1e-10 : # Near-zero length step
131+ # Check if start point is inside obstacle
132+ return np .linalg .norm (f ) <= r
133+
119134 b = 2 * np .dot (f , d )
120135 c = np .dot (f , f ) - r * r
121136
@@ -125,13 +140,18 @@ def check_collision(self, start, end, obstacle):
125140 return False
126141
127142 # Check if intersection on segment
128- t1 = (- b - np .sqrt (discriminant )) / (2 * a )
129- t2 = (- b + np .sqrt (discriminant )) / (2 * a )
143+ sqrt_discriminant = np .sqrt (discriminant )
144+ t1 = (- b - sqrt_discriminant ) / (2 * a )
145+ t2 = (- b + sqrt_discriminant ) / (2 * a )
130146
131147 return (0 <= t1 <= 1 ) or (0 <= t2 <= 1 )
132148
133149 def step (self ):
134- """Run one PSO iteration"""
150+ """Run one PSO iteration
151+
152+ Returns:
153+ bool: True if algorithm should continue, False if completed
154+ """
135155 if self .iteration >= self .max_iter :
136156 return False
137157
@@ -178,18 +198,31 @@ def step(self):
178198 self .iteration += 1
179199 if show_animation and self .iteration % 20 == 0 :
180200 print (f"Iteration { self .iteration } /{ self .max_iter } , Best: { self .gbest_value :.2f} " )
201+
181202 return True
182203
183204
184205def main ():
185- """Test PSO path planning algorithm"""
206+ """Run PSO path planning algorithm demonstration.
207+
208+ This function demonstrates PSO-based path planning with the following setup:
209+ - 15 particles exploring a (-50,50) x (-50,50) search space
210+ - Start zone: (-45,-35) to (-35,-35)
211+ - Target: (40, 35)
212+ - 4 circular obstacles with collision avoidance
213+ - Real-time visualization showing particle convergence (if show_animation=True)
214+ - Headless mode support for testing (if show_animation=False)
215+
216+ The algorithm runs for up to 150 iterations, displaying particle movement,
217+ personal/global best positions, and the evolving optimal path.
218+ """
186219 print (__file__ + " start!!" )
187220
188221 # Set matplotlib backend for headless environments
189222 if not show_animation :
190223 matplotlib .use ('Agg' ) # Use non-GUI backend for testing
191224
192- # Setup
225+ # Setup parameters
193226 N_PARTICLES = 15
194227 MAX_ITER = 150
195228 SEARCH_BOUNDS = [(- 50 , 50 ), (- 50 , 50 )]
@@ -211,8 +244,9 @@ def main():
211244 obstacles = OBSTACLES
212245 )
213246
214- if show_animation : # pragma: no cover
215- # Visualization
247+ # pragma: no cover
248+ if show_animation :
249+ # Visualization setup
216250 fig , ax = plt .subplots (figsize = (10 , 10 ))
217251 ax .set_xlim (SEARCH_BOUNDS [0 ])
218252 ax .set_ylim (SEARCH_BOUNDS [1 ])
@@ -250,8 +284,10 @@ def main():
250284 ax .legend (loc = 'upper right' )
251285
252286 def animate (frame ):
287+ """Animation function for matplotlib FuncAnimation"""
253288 if not swarm .step ():
254- return
289+ return (particles_scatter , gbest_scatter , gbest_path_line ,
290+ iteration_text , * particle_paths )
255291
256292 # Update particle positions
257293 positions = np .array ([p .position for p in swarm .particles ])
@@ -281,13 +317,17 @@ def animate(frame):
281317 return (particles_scatter , gbest_scatter , gbest_path_line ,
282318 iteration_text , * particle_paths )
283319
284- ani = animation .FuncAnimation (
320+ # Create animation and store reference to prevent garbage collection
321+ animation_ref = animation .FuncAnimation (
285322 fig , animate , frames = MAX_ITER ,
286323 interval = 100 , blit = True , repeat = False
287324 )
288325
289326 plt .tight_layout ()
290327 plt .show ()
328+
329+ # Keep reference to prevent garbage collection
330+ return animation_ref
291331 else :
292332 # Run without animation for testing
293333 print ("Running PSO algorithm without animation..." )
@@ -301,6 +341,8 @@ def animate(frame):
301341 print (f"Best fitness: { swarm .gbest_value :.2f} " )
302342 if swarm .gbest_position is not None :
303343 print (f"Best position: [{ swarm .gbest_position [0 ]:.2f} , { swarm .gbest_position [1 ]:.2f} ]" )
344+
345+ return None
304346
305347
306348if __name__ == "__main__" :
0 commit comments