1use nalgebra::{allocator::Allocator, DefaultAllocator, Dim, Dyn, OMatrix, OVector, U1};
2use rand::{rngs::StdRng, Rng, SeedableRng};
3use rayon::prelude::*;
4
5use crate::utils::config::MSPOConf;
6use crate::utils::opt_prob::{FloatNumber as FloatNum, OptProb};
7
8use crate::algorithms::multi_swarm::particle::Particle;
9
10#[derive(Clone)]
11pub struct SwarmConfig<'a, T, D>
12where
13 T: FloatNum,
14 D: Dim,
15 DefaultAllocator: Allocator<D> + Allocator<U1, D> + Allocator<Dyn, D>,
16{
17 pub num_particles: usize,
18 pub dim: usize,
19 pub c1: T,
20 pub c2: T,
21 pub bounds: (T, T),
22 pub opt_prob: &'a OptProb<T, D>,
23 pub init_pop: OMatrix<T, Dyn, D>,
24 pub inertia_start: T,
25 pub inertia_end: T,
26 pub max_iterations: usize,
27}
28
29pub struct Swarm<T, D>
30where
31 T: FloatNum + Send + Sync,
32 D: Dim + Send + Sync,
33 DefaultAllocator: Allocator<D> + Allocator<U1, D>,
34{
35 pub particles: Vec<Particle<T, D>>,
36 pub global_best_position: OVector<T, D>,
37 pub global_best_fitness: T,
38 pub c1: T,
39 pub c2: T,
40 pub x_min: f64,
41 pub x_max: f64,
42 pub iteration_count: usize,
43 pub improvement_history: Vec<T>,
44 pub diversity_history: Vec<f64>,
45 pub inertia_start: T,
46 pub inertia_end: T,
47 pub max_iterations: usize,
48}
49
50impl<T, D> Swarm<T, D>
51where
52 T: FloatNum + Send + Sync,
53 D: Dim + Send + Sync,
54 OVector<T, D>: Send + Sync,
55 OMatrix<T, Dyn, D>: Send + Sync,
56 DefaultAllocator: Allocator<D> + Allocator<U1, D> + Allocator<Dyn, D>,
57{
58 pub fn new(config: SwarmConfig<T, D>, seed: u64) -> Self {
59 let _base_rng = StdRng::seed_from_u64(seed);
60 let particles: Vec<_> = (0..config.num_particles)
61 .into_par_iter()
62 .map_init(
63 || {
64 let thread_id = rayon::current_thread_index().unwrap_or(0);
65 StdRng::seed_from_u64(seed + thread_id as u64)
66 },
67 |rng, i| {
68 let mut position =
69 OVector::<T, D>::zeros_generic(D::from_usize(config.dim), U1);
70 let fitness;
71
72 if i < config.init_pop.nrows() {
73 position = config.init_pop.row(i).transpose();
74 fitness = config.opt_prob.evaluate(&position);
75 } else {
76 loop {
77 let values = (0..config.dim).map(|_| {
78 let r = T::from_f64(rng.random::<f64>()).unwrap();
79 config.bounds.0 + (config.bounds.1 - config.bounds.0) * r
80 });
81 let position: OVector<T, D> = OVector::from_iterator_generic(
82 D::from_usize(config.dim),
83 U1,
84 values,
85 );
86
87 if config.opt_prob.is_feasible(&position) {
88 fitness = config.opt_prob.evaluate(&position);
89 break;
90 }
91 }
92 }
93
94 let values = (0..config.dim).map(|_| {
95 let r = T::from_f64(rng.random::<f64>()).unwrap();
96 (config.bounds.1 - config.bounds.0)
97 * (r - T::from_f64(0.5).unwrap())
98 * T::from_f64(0.1).unwrap()
99 });
100
101 let velocity: OVector<T, D> =
102 OVector::from_iterator_generic(D::from_usize(config.dim), U1, values);
103
104 Particle::new(position, velocity, fitness, rng.clone())
105 },
106 )
107 .collect();
108
109 let mut best_fitness = T::neg_infinity();
110 let mut best_position = OVector::<T, D>::zeros_generic(D::from_usize(config.dim), U1);
111
112 for particle in &particles {
113 if particle.best_fitness > best_fitness {
114 best_fitness = particle.best_fitness;
115 best_position = particle.position.clone();
116 }
117 }
118
119 Self {
120 particles,
121 global_best_position: best_position,
122 global_best_fitness: best_fitness,
123 c1: config.c1,
124 c2: config.c2,
125 x_min: config.bounds.0.to_f64().unwrap(),
126 x_max: config.bounds.1.to_f64().unwrap(),
127 iteration_count: 0,
128 improvement_history: Vec::new(),
129 diversity_history: Vec::new(),
130 inertia_start: config.inertia_start,
131 inertia_end: config.inertia_end,
132 max_iterations: config.max_iterations,
133 }
134 }
135
136 pub fn update(&mut self, opt_prob: &OptProb<T, D>) {
137 let bounds = (
138 T::from_f64(self.x_min).unwrap(),
139 T::from_f64(self.x_max).unwrap(),
140 );
141
142 let adaptive_w = self.compute_adaptive_inertia();
144
145 self.particles.par_iter_mut().for_each(|particle| {
146 particle.update_velocity_and_position(
147 &self.global_best_position,
148 adaptive_w,
149 self.c1,
150 self.c2,
151 opt_prob,
152 bounds,
153 );
154 });
155
156 let best_particle = self
157 .particles
158 .par_iter()
159 .reduce_with(|p1, p2| {
160 if p1.best_fitness > p2.best_fitness {
161 p1
162 } else {
163 p2
164 }
165 })
166 .unwrap();
167
168 if best_particle.best_fitness > self.global_best_fitness {
169 let improvement = best_particle.best_fitness - self.global_best_fitness;
170 self.improvement_history.push(improvement);
171
172 self.global_best_fitness = best_particle.best_fitness;
173 self.global_best_position = best_particle.best_position.clone();
174 }
175
176 let diversity = self.compute_diversity();
177 self.diversity_history.push(diversity);
178
179 self.iteration_count += 1;
180 }
181
182 fn compute_adaptive_inertia(&self) -> T {
184 let progress = (self.iteration_count as f64 / self.max_iterations as f64).min(1.0);
185
186 self.inertia_start
187 + (self.inertia_end - self.inertia_start) * T::from_f64(progress).unwrap()
188 }
189
190 fn compute_diversity(&self) -> f64 {
192 if self.particles.len() < 2 {
193 return 0.0;
194 }
195
196 let pairs: Vec<_> = (0..self.particles.len())
197 .flat_map(|i| (i + 1..self.particles.len()).map(move |j| (i, j)))
198 .collect();
199
200 if pairs.is_empty() {
201 return 0.0;
202 }
203
204 let total_distance: f64 = pairs
205 .par_iter()
206 .map(|&(i, j)| {
207 let distance = self
208 .euclidean_distance(&self.particles[i].position, &self.particles[j].position);
209 distance.to_f64().unwrap()
210 })
211 .sum();
212
213 total_distance / pairs.len() as f64
214 }
215
216 fn euclidean_distance(&self, v1: &OVector<T, D>, v2: &OVector<T, D>) -> T {
217 let diff = v1 - v2;
218 diff.dot(&diff).sqrt()
219 }
220
221 pub fn is_stagnated(&self, threshold: usize) -> bool {
222 if self.improvement_history.len() < threshold {
223 return false;
224 }
225
226 let recent_improvements: Vec<T> = self
227 .improvement_history
228 .iter()
229 .rev()
230 .take(threshold)
231 .cloned()
232 .collect();
233
234 let max_improvement = recent_improvements
235 .iter()
236 .fold(T::neg_infinity(), |a, &b| a.max(b));
237 max_improvement < T::epsilon()
238 }
239
240 pub fn average_improvement(&self, window_size: usize) -> T {
241 if self.improvement_history.is_empty() {
242 return T::zero();
243 }
244
245 let recent_improvements: Vec<T> = self
246 .improvement_history
247 .iter()
248 .rev()
249 .take(window_size.min(self.improvement_history.len()))
250 .cloned()
251 .collect();
252
253 let len = recent_improvements.len();
254 let sum = recent_improvements
255 .into_iter()
256 .fold(T::zero(), |acc, x| acc + x);
257 sum / T::from_usize(len).unwrap()
258 }
259
260 pub fn current_diversity(&self) -> f64 {
261 self.diversity_history.last().copied().unwrap_or(0.0)
262 }
263}
264
265pub fn initialize_swarms<T, N, D>(
266 conf: &MSPOConf,
267 dim: usize,
268 init_pop: &OMatrix<T, N, D>,
269 opt_prob: &OptProb<T, D>,
270 max_iter: usize,
271 seed: u64,
272) -> Vec<Swarm<T, D>>
273where
274 T: FloatNum,
275 N: Dim,
276 D: Dim,
277 OVector<T, D>: Send + Sync,
278 OMatrix<T, N, D>: Send + Sync,
279 DefaultAllocator: Allocator<D> + Allocator<N, D> + Allocator<U1, D> + Allocator<D, D>,
280{
281 let particles_per_swarm = conf.swarm_size;
282 let pop_per_swarm = init_pop.nrows() / conf.num_swarms;
283 let mut promising_centers: Vec<OVector<T, D>> = Vec::new();
284
285 let fitness_values: Vec<_> = (0..init_pop.nrows())
286 .into_par_iter()
287 .map(|i| {
288 let individual = init_pop.row(i).transpose();
289 (i, opt_prob.evaluate(&individual))
290 })
291 .collect();
292
293 let mut sorted_indices: Vec<usize> = (0..init_pop.nrows()).collect();
294 sorted_indices.sort_by(|&i, &j| {
295 fitness_values[i]
296 .1
297 .partial_cmp(&fitness_values[j].1)
298 .unwrap()
299 });
300
301 for &idx in sorted_indices.iter().take(conf.num_swarms) {
303 let center = init_pop.row(idx).transpose();
304
305 let min_distance = T::from_f64(0.3 * (conf.x_max - conf.x_min)).unwrap();
307
308 if promising_centers.is_empty() {
309 promising_centers.push(center);
310 } else {
311 let is_diverse = promising_centers.par_iter().all(|c| {
312 let dist = (c - ¢er).dot(&(c - ¢er)).sqrt();
313 dist > min_distance
314 });
315
316 if is_diverse {
317 promising_centers.push(center);
318 }
319 }
320 }
321
322 let mut fill_rng = StdRng::seed_from_u64(seed + 1000);
323 while promising_centers.len() < conf.num_swarms {
324 let random_center = OVector::<T, D>::from_iterator_generic(
325 D::from_usize(dim),
326 U1,
327 (0..dim).map(|_| {
328 T::from_f64(conf.x_min + fill_rng.random::<f64>() * (conf.x_max - conf.x_min))
329 .unwrap()
330 }),
331 );
332 promising_centers.push(random_center);
333 }
334
335 let _base_rng = StdRng::seed_from_u64(seed + 2000);
337 (0..conf.num_swarms)
338 .into_par_iter()
339 .map_init(
340 || {
341 let thread_id = rayon::current_thread_index().unwrap_or(0);
342 StdRng::seed_from_u64(seed + 2000 + thread_id as u64)
343 },
344 |rng, i| {
345 let center = if i < promising_centers.len() {
346 promising_centers[i].clone()
347 } else {
348 OVector::<T, D>::from_iterator_generic(
349 D::from_usize(dim),
350 U1,
351 (0..dim).map(|_| {
352 T::from_f64(
353 conf.x_min + rng.random::<f64>() * (conf.x_max - conf.x_min),
354 )
355 .unwrap()
356 }),
357 )
358 };
359
360 let radius = T::from_f64(0.15 * (conf.x_max - conf.x_min)).unwrap(); let start_idx = i * pop_per_swarm;
363 let mut swarm_pop: OMatrix<T, Dyn, D> =
364 init_pop.rows(start_idx, particles_per_swarm).into_owned();
365
366 let particle_adjustments: Vec<_> = (0..particles_per_swarm)
367 .into_par_iter()
368 .map_init(
369 || {
370 let thread_id = rayon::current_thread_index().unwrap_or(0);
371 StdRng::seed_from_u64(seed + 3000 + thread_id as u64)
372 },
373 |particle_rng, j| {
374 let mut particle_row = Vec::with_capacity(dim);
375 for k in 0..dim {
376 let r = T::from_f64(particle_rng.random::<f64>()).unwrap();
377 let noise = (r - T::from_f64(0.5).unwrap()) * radius;
378 let adjusted_value = (center[k] + noise).clamp(
379 T::from_f64(conf.x_min).unwrap(),
380 T::from_f64(conf.x_max).unwrap(),
381 );
382 particle_row.push(adjusted_value);
383 }
384 (j, particle_row)
385 },
386 )
387 .collect();
388
389 for (j, particle_row) in particle_adjustments {
390 for (k, &value) in particle_row.iter().enumerate() {
391 swarm_pop[(j, k)] = value;
392 }
393 }
394
395 Swarm::new(
396 SwarmConfig {
397 num_particles: particles_per_swarm,
398 dim,
399 c1: T::from_f64(conf.c1).unwrap(),
400 c2: T::from_f64(conf.c2).unwrap(),
401 bounds: (
402 T::from_f64(conf.x_min).unwrap(),
403 T::from_f64(conf.x_max).unwrap(),
404 ),
405 opt_prob,
406 init_pop: swarm_pop,
407 inertia_start: T::from_f64(conf.inertia_start).unwrap(),
408 inertia_end: T::from_f64(conf.inertia_end).unwrap(),
409 max_iterations: max_iter,
410 },
411 seed,
412 )
413 },
414 )
415 .collect()
416}