fix(hyperopt): PSO premature convergence — tune inertia/cognitive/social

Argmin defaults: inertia=0.72, cognitive=1.19, social=1.19
Problem: social >> inertia causes particles to collapse toward the
global best immediately. With only 1 LHS trial as the initial best,
the entire swarm clusters around that point and can't explore.
Every PSO trial was worse than the random LHS trial.

Fix: inertia=0.9 (high momentum, maintains exploration),
cognitive=1.5 (strong personal best memory),
social=0.8 (weak global pull, prevents premature convergence).

Applied to both sequential and parallel optimizer paths.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
jgrusewski
2026-03-29 19:05:16 +02:00
parent ba97a6e819
commit ad4e22e87d

View File

@@ -374,8 +374,18 @@ impl ArgminOptimizer {
let lower_bounds: Vec<f64> = bounds.iter().map(|(min, _)| *min).collect();
let upper_bounds: Vec<f64> = bounds.iter().map(|(_, max)| *max).collect();
// Create Particle Swarm solver
let solver = ParticleSwarm::new((lower_bounds, upper_bounds), self.n_particles);
// Create Particle Swarm solver with tuned parameters.
// Default argmin: inertia=0.72, cognitive=1.19, social=1.19
// Problem: social >> inertia → premature convergence to global best.
// Fix: higher inertia (0.9) for exploration, lower social (0.8) to
// reduce collapse toward the single LHS best point.
let solver = ParticleSwarm::new((lower_bounds, upper_bounds), self.n_particles)
.with_inertia_factor(0.9)
.map_err(|e| anyhow::anyhow!("PSO inertia: {e}"))?
.with_cognitive_factor(1.5)
.map_err(|e| anyhow::anyhow!("PSO cognitive: {e}"))?
.with_social_factor(0.8)
.map_err(|e| anyhow::anyhow!("PSO social: {e}"))?;
// Run optimization (parallel execution enabled via rayon feature)
// CRITICAL FIX (2025-11-03): Removed .target_cost(0.0) to prevent early termination
@@ -798,7 +808,13 @@ impl ArgminOptimizer {
let lower_bounds: Vec<f64> = bounds.iter().map(|(min, _)| *min).collect();
let upper_bounds: Vec<f64> = bounds.iter().map(|(_, max)| *max).collect();
let solver = ParticleSwarm::new((lower_bounds, upper_bounds), effective_particles);
let solver = ParticleSwarm::new((lower_bounds, upper_bounds), effective_particles)
.with_inertia_factor(0.9)
.map_err(|e| anyhow::anyhow!("PSO inertia: {e}"))?
.with_cognitive_factor(1.5)
.map_err(|e| anyhow::anyhow!("PSO cognitive: {e}"))?
.with_social_factor(0.8)
.map_err(|e| anyhow::anyhow!("PSO social: {e}"))?;
let res = Executor::new(cost_fn, solver)
.configure(|state| state.max_iters(max_iters as u64))