Skip to content

Commit c20cf35

Browse files
committed
fix: Resolve linting issues in PSO implementation
1 parent 94b0c1a commit c20cf35

File tree

1 file changed

+54
-12
lines changed

1 file changed

+54
-12
lines changed

PathPlanning/ParticleSwarmOptimization/particle_swarm_optimization.py

Lines changed: 54 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@
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
1515
search space to minimize distance to target while avoiding obstacles.
1616
"""
1717
import 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

184205
def 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

306348
if __name__ == "__main__":

0 commit comments

Comments
 (0)