Skip to main content

Crate swarmkit

Crate swarmkit 

Source
Expand description

Composable Particle Swarm Optimization.

Pick a Unit type, plug in a FitCalc and a ParticleMover, hand them to a Searcher, iterate.

§Kit

Position space: Unit (any Copy + Default + Debug + Send + Sync + 'static qualifies; Floats<N> is the off-the-shelf element-wise [f64; N] newtype). Scoring: FitCalc (higher = better). Constraints: Boundary. Per-step / per-iteration reparameterization: Contextful. Particle storage: Particle / Group / ParticleRefMut. Initial swarms: ParticleInit + ParticleInitDependent. Sub-unit views for compound particles: ParticleRefFrom + SetTo.

Mover combinators: BoundedMover, ChainedMover, AdapterMover (lift a sub-unit mover to a parent unit), NestedMover (run an inner search inside an outer mover step).

Concrete searchers: GBestSearcher (one swarm-wide attractor), LBestSearcher (ring / Von Neumann neighbourhoods — slower but preserves diversity), NichedSearcher (static multi-swarm partition).

For writing your own Searcher, the shared parallel-pass and bookkeeping helpers are also re-exported: SearcherCore, evaluate_pbests, move_particles, reduce_best, par_for_each_mut_rng.

§Example

Minimal end-to-end: find x = 3 by maximizing -(x - 3)^2 on [-10, 10].

use rand::rngs::SmallRng;
use rand::{Rng, RngExt, SeedableRng};
use swarmkit::{
    Boundary, Contextful, FieldwiseClamp, FitCalc, Floats, GBestMover,
    IntoGBestSearcher, PSOCoeffs, ParticleInit, ParticleMover, Searcher,
};

type X = Floats<1>;

struct Fit;
impl Contextful for Fit { type TContext = (); }
impl FitCalc for Fit {
    type T = X;
    fn calculate_fit(&self, v: X) -> f64 { -(v[0] - 3.0).powi(2) }
}

struct Bound;
impl Contextful for Bound { type TContext = (); }
impl Boundary for Bound {
    type T = X;
    fn handle(&self, v: X) -> X { v.clamp(Floats([-10.0]), Floats([10.0])) }
}

struct Init;
impl ParticleInit for Init {
    type T = X;
    fn init_pos<R: Rng>(&self, r: &mut R) -> Vec<X> {
        (0..16).map(|_| Floats([r.random_range(-10.0..10.0)])).collect()
    }
    fn init_vel<R: Rng>(&self, r: &mut R) -> Vec<X> {
        (0..16).map(|_| Floats([r.random_range(-1.0..1.0)])).collect()
    }
}

fn main() {
    let mut rng = SmallRng::seed_from_u64(0);
    let mut group = Init.init(&mut rng);
    let mut searcher = GBestMover::<X>::new(PSOCoeffs::new(0.5, 1.5, 1.5))
        .bounded_by(Bound)
        .into_gbest_searcher(Fit);

    let best = searcher
        .iter(40, &mut group, None)
        .last()
        .expect("40 iterations produce a final Best");
    assert!((best.best_pos[0] - 3.0).abs() < 0.5);
}

The 2D analogue lives at cargo run --example elliptical. For a non-trivial real-world use, see swarmkit-sailing in the bywind repo: sailboat route optimization on a spherical-Earth grid, exercising NestedMover, ParticleRefFrom / SetTo, iteration-aware schedules, and a custom tangent-frame replacement for the generic gbest update.

§Parallelism

Rayon drives the fitness and mover passes. Each trait carries a PAR_LEAF_SIZE constant; the default usize::MAX keeps cheap work serial. Override per impl when per-particle cost amortizes a split.

Structs§

AdapterMover
Lifts a sub-unit mover into a parent-unit mover via ParticleRefFrom (for the particle view) and From<&TParentCommon> (for the per-iteration value).
Best
A position together with its observed fitness.
BoundedMover
Wraps a mover so Boundary::handle runs after each update.
ChainedMover
Runs two movers in sequence on the same particle, sharing the RNG.
Evolution
Per-iteration particle snapshots, for trajectory recording.
Floats
Element-wise newtype over [f64; N].
GBestMover
Standard PSO velocity update. TContext is pass-through only — the mover ignores it, but it lets the type line up when chained with contextful neighbours.
GBestSearcher
Global-best PSO searcher.
Group
The swarm: a flat collection of Particle<T>.
LBestSearcher
Local-best PSO searcher. Neighbour graph and pbest snapshot buffer are built lazily on the first init.
NestedMover
Runs an inner search inside one outer mover step.
NichedSearcher
Static-partition niched searcher. partition ranges must cover 0..particles.len() contiguously.
PSOCoeffs
Coefficients of the standard PSO velocity update: v ← inertia·v + cognitive·r1·(pbest − pos) + social·r2·(gbest − pos).
Particle
One particle in the swarm. Storage is AoS; the swarm is a Group<T>.
ParticleRefMut
Disjoint mutable view into one Particle<T>.
SearcherCore
Shared bookkeeping for Searcher impls.
SearcherIter
Iterator<Item = Best<TUnit>> over a Searcher.

Enums§

LBestKind
Neighbourhood graph for the lbest searcher.

Traits§

Boundary
Map an out-of-bounds position back into the search domain.
Contextful
Reparameterizable per outer-step or per-iteration.
FieldwiseClamp
Per-field analogue of f64::clamp (rectangular box constraint).
FitCalc
Fitness function. Higher return value is better.
IntoGBestSearcher
Method-chain wrapper: mover.into_gbest_searcher(fit_calc).
IntoLBestSearcher
Method-chain wrapper: mover.into_lbest_searcher(fit_calc, kind).
IntoNichedSearcher
Method-chain wrapper: mover.into_niched_searcher(fit_calc, partition).
ParticleInit
Initializes a standalone swarm.
ParticleInitDependent
Initializes an inner swarm seeded around the outer particle’s position.
ParticleMover
Advances one particle per step.
ParticleRefFrom
Borrow a sub-unit’s ParticleRefMut out of a parent unit’s view.
Searcher
Drives one PSO step over a swarm.
SetTo
Write a sub-unit’s value back into a parent unit (inverse of ParticleRefFrom).
Unit
Marker for the position/velocity space of a particle.

Functions§

evaluate_pbests
Evaluate fitness and update each particle’s personal best, in parallel. Decoupled from the swarm-best reduce so non-gbest topologies can run a per-niche or per-neighbourhood reduce afterwards.
move_particles
Move every particle in parallel.
move_particles_with_offset
Slice-and-offset variant of move_particles: the mover sees base_idx + i for the i-th particle, so a topology iterating over sub-slices still passes absolute swarm indices.
par_for_each_mut_rng
Index- and RNG-aware variant of par_for_each_mut. Splits slice and rngs in lock-step; the closure sees each particle’s absolute index.
reduce_best
Serially reduce best against a particle slice’s pbests, in place. Pass an existing Best to keep it monotone across iterations, or a fresh Best::new() for the slice’s own maximum.