#![allow(
clippy::print_stdout,
clippy::unwrap_used,
reason = "stand-alone demo: stdout is the user-facing surface; .unwrap() on a finite iterator is harmless"
)]
use rand::rngs::ThreadRng;
use rand::{Rng, RngExt as _};
use swarmkit::{
Boundary, Contextful, Evolution, FieldwiseClamp as _, FitCalc, Floats, GBestMover,
IntoGBestSearcher as _, PSOCoeffs, ParticleInit, ParticleMover as _, Searcher as _,
};
type Vec2 = Floats<2>;
#[derive(Copy, Clone, Default, Debug)]
struct Bounds {
min: Vec2,
max: Vec2,
}
#[derive(Copy, Clone, Default, Debug)]
struct RectBoundary {
bounds: Bounds,
}
impl Contextful for RectBoundary {
type TContext = ();
}
impl Boundary for RectBoundary {
type T = Vec2;
fn handle(&self, pos: Vec2) -> Vec2 {
pos.clamp(self.bounds.min, self.bounds.max)
}
}
struct RandomInit {
particle_count: usize,
bounds: Bounds,
}
impl ParticleInit for RandomInit {
type T = Vec2;
fn init_pos<R: Rng>(&self, rng: &mut R) -> Vec<Vec2> {
(0..self.particle_count)
.map(|_| sample(&self.bounds, rng))
.collect()
}
fn init_vel<R: Rng>(&self, rng: &mut R) -> Vec<Vec2> {
(0..self.particle_count)
.map(|_| sample(&self.bounds, rng) * 0.5)
.collect()
}
}
fn sample<R: Rng>(b: &Bounds, rng: &mut R) -> Vec2 {
Floats([
rng.random_range(b.min[0]..b.max[0]),
rng.random_range(b.min[1]..b.max[1]),
])
}
struct EllipticalFitCalc;
impl Contextful for EllipticalFitCalc {
type TContext = ();
}
impl FitCalc for EllipticalFitCalc {
type T = Vec2;
fn calculate_fit(&self, v: Vec2) -> f64 {
std::f64::consts::E.powf(-0.001 * elliptical_eq(v).abs())
}
}
fn elliptical_eq(v: Vec2) -> f64 {
let (x, y) = (v[0], v[1]);
(x + y) * (x + y) - x * y - 100.0
}
fn main() {
let bounds = Bounds {
min: Floats([-100.0, -100.0]),
max: Floats([100.0, 100.0]),
};
let fit_calc = EllipticalFitCalc;
let init = RandomInit {
particle_count: 30,
bounds,
};
let coeffs = PSOCoeffs::new(0.2, 2.0, 1.0);
let mut group = init.init(&mut ThreadRng::default());
let mut searcher = GBestMover::<Vec2>::new(coeffs)
.bounded_by(RectBoundary { bounds })
.into_gbest_searcher(fit_calc);
let mut evolution: Evolution<Vec2> = Evolution::default();
let best = searcher
.iter(80, &mut group, Some(&mut evolution))
.last()
.unwrap();
let residual = elliptical_eq(best.best_pos).abs();
println!(
"best position : ({:.4}, {:.4})",
best.best_pos[0], best.best_pos[1]
);
println!("best fitness : {:.6}", best.best_fit);
println!("|f(x, y)| : {residual:.4} (0 = on ellipse)");
println!("iterations : {}", evolution.frames().len());
}