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§
- Adapter
Mover - Lifts a sub-unit mover into a parent-unit mover via
ParticleRefFrom(for the particle view) andFrom<&TParentCommon>(for the per-iteration value). - Best
- A position together with its observed fitness.
- Bounded
Mover - Wraps a mover so
Boundary::handleruns after each update. - Chained
Mover - 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]. - GBest
Mover - Standard PSO velocity update.
TContextis pass-through only — the mover ignores it, but it lets the type line up when chained with contextful neighbours. - GBest
Searcher - Global-best PSO searcher.
- Group
- The swarm: a flat collection of
Particle<T>. - LBest
Searcher - Local-best PSO searcher. Neighbour graph and pbest snapshot buffer are
built lazily on the first
init. - Nested
Mover - Runs an inner search inside one outer mover step.
- Niched
Searcher - Static-partition niched searcher.
partitionranges must cover0..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>. - Particle
RefMut - Disjoint mutable view into one
Particle<T>. - Searcher
Core - Shared bookkeeping for
Searcherimpls. - Searcher
Iter Iterator<Item = Best<TUnit>>over aSearcher.
Enums§
- LBest
Kind - 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.
- Fieldwise
Clamp - Per-field analogue of
f64::clamp(rectangular box constraint). - FitCalc
- Fitness function. Higher return value is better.
- IntoG
Best Searcher - Method-chain wrapper:
mover.into_gbest_searcher(fit_calc). - IntoL
Best Searcher - Method-chain wrapper:
mover.into_lbest_searcher(fit_calc, kind). - Into
Niched Searcher - Method-chain wrapper:
mover.into_niched_searcher(fit_calc, partition). - Particle
Init - Initializes a standalone swarm.
- Particle
Init Dependent - Initializes an inner swarm seeded around the outer particle’s position.
- Particle
Mover - Advances one particle per step.
- Particle
RefFrom - Borrow a sub-unit’s
ParticleRefMutout 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 seesbase_idx + ifor thei-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. Splitssliceandrngsin lock-step; the closure sees each particle’s absolute index. - reduce_
best - Serially reduce
bestagainst a particle slice’s pbests, in place. Pass an existingBestto keep it monotone across iterations, or a freshBest::new()for the slice’s own maximum.