swarmkit 0.1.0

Composable particle swarm optimization with nested searches
Documentation
//! Per-particle storage ([`Particle`], [`Group`]) and the disjoint-borrow
//! view [`ParticleRefMut`] that movers operate on.

use crate::Unit;
use std::ops::{Deref, DerefMut};

/// One particle in the swarm. Storage is AoS; the swarm is a [`Group<T>`].
///
/// Movers see a [`ParticleRefMut`] view rather than `Particle` directly.
/// Invariant: `best_fit` is the running maximum of `fit`. Movers may
/// break it transiently; the post-move `evaluate_pbests` restores it.
#[derive(PartialEq, Debug, Clone, Copy)]
pub struct Particle<T: Copy> {
    /// Current position.
    pub pos: T,
    /// Current velocity.
    pub vel: T,
    /// Fitness at `pos`.
    pub fit: f64,
    /// Best position observed.
    pub best_pos: T,
    /// Fitness at `best_pos`. Monotone non-decreasing.
    pub best_fit: f64,
}

/// Disjoint mutable view into one [`Particle<T>`].
///
/// The same shape works whether a mover is acting on the parent unit or
/// a sub-unit projected via [`crate::ParticleRefFrom`].
pub struct ParticleRefMut<'a, T> {
    /// Borrow of `pos`.
    pub pos: &'a mut T,
    /// Borrow of `vel`.
    pub vel: &'a mut T,
    /// Borrow of `fit`.
    pub fit: &'a mut f64,
    /// Borrow of `best_pos`.
    pub best_pos: &'a mut T,
    /// Borrow of `best_fit`.
    pub best_fit: &'a mut f64,
}

impl<T: Copy> Particle<T> {
    /// Disjoint-borrow view of all five fields.
    pub fn as_ref_mut(&mut self) -> ParticleRefMut<'_, T> {
        ParticleRefMut {
            pos: &mut self.pos,
            vel: &mut self.vel,
            fit: &mut self.fit,
            best_pos: &mut self.best_pos,
            best_fit: &mut self.best_fit,
        }
    }
}

/// The swarm: a flat collection of [`Particle<T>`].
///
/// Thin newtype around `Vec<Particle<T>>`. `Deref` / `DerefMut` to
/// `[Particle<T>]` expose every slice operation (read and per-element
/// mutate); `From<Vec<Particle<T>>>` and `FromIterator` build one in bulk;
/// [`Self::push`] is the only growth method.
#[derive(Clone, Debug)]
pub struct Group<T: Copy> {
    particles: Vec<Particle<T>>,
}

impl<T: Copy> Group<T> {
    /// An empty `Group`.
    #[must_use]
    pub fn new() -> Self {
        Self {
            particles: Vec::new(),
        }
    }

    /// An empty `Group` with capacity for at least `n` particles.
    #[must_use]
    pub fn with_capacity(n: usize) -> Self {
        Self {
            particles: Vec::with_capacity(n),
        }
    }

    /// Append a particle.
    pub fn push(&mut self, particle: Particle<T>) {
        self.particles.push(particle);
    }
}

impl<T: Copy> Default for Group<T> {
    fn default() -> Self {
        Self::new()
    }
}

impl<T: Copy> Deref for Group<T> {
    type Target = [Particle<T>];
    fn deref(&self) -> &[Particle<T>] {
        &self.particles
    }
}

impl<T: Copy> DerefMut for Group<T> {
    fn deref_mut(&mut self) -> &mut [Particle<T>] {
        &mut self.particles
    }
}

impl<T: Copy> From<Vec<Particle<T>>> for Group<T> {
    fn from(particles: Vec<Particle<T>>) -> Self {
        Self { particles }
    }
}

impl<T: Copy> FromIterator<Particle<T>> for Group<T> {
    fn from_iter<I: IntoIterator<Item = Particle<T>>>(iter: I) -> Self {
        Self {
            particles: iter.into_iter().collect(),
        }
    }
}

/// Per-iteration particle snapshots, for trajectory recording.
///
/// Each `push_snapshot` is `O(N)` in particle count, so this is intended
/// for visualization, replay, or post-hoc analysis — not the search hot
/// path.
#[derive(Clone, Debug)]
#[must_use]
pub struct Evolution<T: Unit> {
    frames: Vec<Vec<Particle<T>>>,
}

impl<T: Unit> Evolution<T> {
    /// Empty.
    pub fn new() -> Self {
        Self { frames: Vec::new() }
    }

    /// Wrap existing snapshots.
    pub fn from_frames(frames: Vec<Vec<Particle<T>>>) -> Self {
        Self { frames }
    }

    /// `frames[i]` is the swarm at the end of iteration `i + 1`; the
    /// init pass is not snapshotted.
    pub fn frames(&self) -> &[Vec<Particle<T>>] {
        &self.frames
    }

    /// Don't mutate while a live search is pushing.
    pub fn frames_mut(&mut self) -> &mut [Vec<Particle<T>>] {
        &mut self.frames
    }

    /// O(N) clone per call.
    pub fn push_snapshot(&mut self, group: &Group<T>) {
        self.frames.push(group.to_vec());
    }
}

impl<T: Unit> Default for Evolution<T> {
    fn default() -> Self {
        Self::new()
    }
}