swarmkit 0.1.0

Composable particle swarm optimization with nested searches
Documentation
# swarmkit

[![Crates.io](https://img.shields.io/crates/v/swarmkit.svg)](https://crates.io/crates/swarmkit)
[![docs.rs](https://img.shields.io/docsrs/swarmkit)](https://docs.rs/swarmkit)

Composable Particle Swarm Optimization for Rust.

"Kit" because it's a framework for building PSO driven solutions, as opposed to a batteries included approach.

Full API: [docs.rs/swarmkit](https://docs.rs/swarmkit).

## Quick start

```rust
use swarmkit::{Floats, GBestMover, IntoGBestSearcher, ParticleInit, PSOCoeffs, Searcher};

// `Floats<N>` ships with `Add`/`Sub`/`Mul<f64>`/`FieldwiseClamp` and `Index`
// pre-implemented — drop it in as your particle and skip the per-type boilerplate.
type Vec2 = Floats<2>;

let mut searcher = GBestMover::<Vec2>::new(PSOCoeffs::default())
    .bounded_by(my_boundary)         // your Boundary impl
    .into_gbest_searcher(my_fit);    // your FitCalc impl

let mut group = my_init.init(&mut rng);
let best = searcher.iter(80, &mut group, None).last().unwrap();
```

Full runnable version at `examples/elliptical.rs` — run it with `cargo run --example elliptical`.

## What you implement

Three traits, regardless of topology:

- `FitCalc` — score a candidate; higher is better.
- `Boundary` — what to do when a particle leaves the search space (typically clamp or reflect).
- `ParticleInit` — seed initial positions and velocities.

For nested PSO, also `ParticleInitDependent` — same shape as `ParticleInit`, but seeds the inner swarm around an outer particle's position.

## Particle type

Any `Copy + Default + Debug + Send + Sync + 'static` type qualifies (the `Unit` marker is auto-implemented for the bound). The PSO mover additionally needs `Add`, `Sub`, `Mul<f64>`; bounded movers need `FieldwiseClamp`.

- Fixed-size numeric vectors: `Floats<N>` has all four pre-implemented. `type MyUnit = Floats<10>;` and go.
- Compound types (e.g. waypoints + times, with sub-unit projection via `ParticleRefFrom`/`SetTo`): implement the four ops on your own newtype.

## Available topologies

- **GBest** — one swarm-wide attractor.
- **LBest** — per-neighbourhood attractors (`Ring { k }` or `VonNeumann`); preserves diversity on multi-basin landscapes.
- **Niched** — static multi-swarm partition; no coordination across niches.

The same mover plugs into all three — switching topology is a one-method-call change:

```rust
mover.into_gbest_searcher(fit);
mover.into_lbest_searcher(fit, LBestKind::Ring { k: 1 });
mover.into_niched_searcher(fit, partition);
```

## Searchers and movers

A `ParticleMover` mutates one particle's position and velocity per iteration. A `Searcher` owns a mover, evaluates fitness, computes whichever per-iteration `Best` its topology defines, and feeds it to the mover. You drive the search by calling `searcher.iter(max_iterations, &mut group, evolution)` and consuming the resulting iterator.

- **`GBestMover`** — the standard PSO velocity update. Works inside any of the three topologies; the searcher decides what `Best` it sees each step.
- **`BoundedMover`** — wraps a mover and applies a `Boundary` after each update. Build via `mover.bounded_by(b)`.
- **`ChainedMover`** — runs two movers in sequence on the same particle, sharing the RNG. Build via `m1.chain(m2)`.
- **`AdapterMover`** — lifts a mover written against a sub-unit (e.g. just the XY of a `Path<N>`) onto the parent unit. Build via `mover.adapt::<ParentCommon>()`.
- **`NestedMover`** — runs an entire inner PSO search inside one outer mover step. Used for nested PSO.

All the combinators stack into a single fluent expression. Pared down from
swarmkit-sailing — an outer spatial PSO with a nested per-segment timing PSO:

```rust
let space = SphericalPSOMover::<N, Path<N>>::new(coeffs)   // custom mover (tangent-frame update)
    .chain(CauchyKickMover::new(gamma))                    // sequence a second mover
    .adapt::<Best<Path<N>>>()                              // lift sub-unit mover onto parent unit
    .bounded_by(SailingBoundary::new(/* ... */));          // clamp after each step

let time = time_searcher.nested(8, TimeInit);              // an inner PSO, every outer step

let searcher = space.chain(time)                           // outer + inner movers in sequence
    .into_gbest_searcher(fit);                             // pick a topology
```

## A larger example

[`swarmkit-sailing`](https://github.com/Anvoker/bywind/tree/master/swarmkit-sailing) (in the `bywind` repo) is a substantive use — sailboat route optimization on a spherical Earth. It showcases nested PSO (an outer spatial PSO over route waypoints with a nested per-segment timing PSO inside each outer step) and how to wire a non-trivial physical model into swarmkit.

## License

Licensed under either of

- Apache License, Version 2.0 ([`LICENSE-APACHE`]LICENSE-APACHE or <http://www.apache.org/licenses/LICENSE-2.0>)
- MIT license ([`LICENSE-MIT`]LICENSE-MIT or <http://opensource.org/licenses/MIT>)

at your option.

### Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this work by you, as defined in the Apache-2.0 license, shall be dual-licensed as above, without any additional terms or conditions.