use crate::searcher_impl::{SearcherCore, evaluate_pbests, move_particles, reduce_best};
use crate::{Best, Contextful, FitCalc, Group, ParticleMover, ParticleRefMut, Searcher, Unit};
use rand::distr::StandardUniform;
use rand::{Rng, RngExt as _};
use std::marker::PhantomData;
use std::ops::{Add, Mul, Sub};
#[derive(Copy, Clone, Debug, PartialEq)]
#[non_exhaustive]
pub struct PSOCoeffs {
pub inertia: f64,
pub cognitive_coeff: f64,
pub social_coeff: f64,
}
impl Default for PSOCoeffs {
fn default() -> Self {
Self::new(0.5, 1.5, 1.5)
}
}
impl PSOCoeffs {
#[must_use]
#[track_caller]
pub fn new(inertia: f64, cognitive_coeff: f64, social_coeff: f64) -> Self {
assert!(
inertia.is_finite() && inertia >= 0.0,
"inertia must be finite and non-negative, got {inertia}",
);
assert!(
cognitive_coeff.is_finite() && cognitive_coeff >= 0.0,
"cognitive_coeff must be finite and non-negative, got {cognitive_coeff}",
);
assert!(
social_coeff.is_finite() && social_coeff >= 0.0,
"social_coeff must be finite and non-negative, got {social_coeff}",
);
Self {
inertia,
cognitive_coeff,
social_coeff,
}
}
}
#[derive(Clone, Debug)]
pub struct GBestSearcher<TUnit, TFit, TMover>
where
TUnit: Unit,
TFit: FitCalc,
TMover: ParticleMover,
{
core: SearcherCore<TUnit, TFit, TMover>,
}
impl<TUnit, TFit, TMover> GBestSearcher<TUnit, TFit, TMover>
where
TUnit: Unit,
TFit: FitCalc<T = TUnit>,
TMover: ParticleMover<TUnit = TUnit>,
{
#[must_use]
pub fn new(fit_calc: TFit, mover: TMover) -> Self {
Self {
core: SearcherCore::new(fit_calc, mover),
}
}
}
pub trait IntoGBestSearcher: ParticleMover + Sized {
#[must_use]
fn into_gbest_searcher<TFit>(self, fit_calc: TFit) -> GBestSearcher<Self::TUnit, TFit, Self>
where
TFit: FitCalc<T = Self::TUnit>,
{
GBestSearcher::new(fit_calc, self)
}
}
impl<TUnit, M> IntoGBestSearcher for M
where
TUnit: Unit,
M: ParticleMover<TUnit = TUnit, TCommon = Best<TUnit>>,
{
}
#[derive(Copy, Clone, Debug)]
pub struct GBestMover<TUnit, TContext = ()>
where
TUnit: Unit,
TContext: Copy,
{
phantom: PhantomData<fn() -> (TUnit, TContext)>,
config: PSOCoeffs,
}
impl<TUnit, TContext> GBestMover<TUnit, TContext>
where
TUnit: Unit,
TContext: Copy,
{
#[must_use]
pub fn new(config: PSOCoeffs) -> Self {
Self {
phantom: PhantomData,
config,
}
}
}
impl<TUnit, TContext> Contextful for GBestMover<TUnit, TContext>
where
TUnit: Unit,
TContext: Copy,
{
type TContext = TContext;
}
impl<TUnit, TContext> ParticleMover for GBestMover<TUnit, TContext>
where
TUnit: Unit + Add<Output = TUnit> + Sub<Output = TUnit> + Mul<f64, Output = TUnit>,
TContext: Copy,
{
type TUnit = TUnit;
type TCommon = Best<TUnit>;
fn update<R: Rng>(
&self,
common: &Self::TCommon,
rng: &mut R,
_idx: usize,
p: &mut ParticleRefMut<'_, Self::TUnit>,
) {
let p_diff = (*p.best_pos - *p.pos) * self.config.cognitive_coeff;
let g_diff = (common.best_pos - *p.pos) * self.config.social_coeff;
let a = *p.vel * self.config.inertia;
let b = p_diff * rng.sample::<f64, StandardUniform>(StandardUniform);
let c = g_diff * rng.sample::<f64, StandardUniform>(StandardUniform);
*p.vel = a + b + c;
*p.pos = *p.pos + *p.vel;
}
}
impl<TUnit, TFit, TMover, TContext> Contextful for GBestSearcher<TUnit, TFit, TMover>
where
TFit: FitCalc<T = TUnit, TContext = TContext>,
TMover: ParticleMover<TUnit = TUnit, TCommon = Best<TUnit>, TContext = TContext>,
TUnit: Unit,
TContext: Copy,
{
type TContext = TContext;
fn set_context(&mut self, context: Self::TContext) {
self.core.set_context(context);
}
fn set_iteration(&mut self, iteration: usize, max_iteration: usize) {
self.core.set_iteration(iteration, max_iteration);
}
}
impl<TUnit, TFit, TMover, TContext> Searcher for GBestSearcher<TUnit, TFit, TMover>
where
TFit: FitCalc<T = TUnit, TContext = TContext>,
TMover: ParticleMover<TUnit = TUnit, TCommon = Best<TUnit>, TContext = TContext>,
TUnit: Unit,
TContext: Copy,
{
type TUnit = TUnit;
fn init(&mut self, particles: &mut Group<Self::TUnit>) {
self.core.swarm_best = Best::new();
self.core.sync_rngs(particles.len());
evaluate_pbests(&self.core.fit_calc, particles);
reduce_best(particles, &mut self.core.swarm_best);
}
fn next(&mut self, particles: &mut Group<Self::TUnit>) -> &Best<TUnit> {
self.core.sync_rngs(particles.len());
move_particles(
&self.core.mover,
&self.core.swarm_best,
particles,
&mut self.core.particle_rngs,
);
evaluate_pbests(&self.core.fit_calc, particles);
reduce_best(particles, &mut self.core.swarm_best);
&self.core.swarm_best
}
fn reseed<R: Rng>(&mut self, rng: &mut R) {
self.core.reseed(rng);
}
}