docs(rustdoc): add runnable examples across operators, metrics, and Pareto utilities

Completes the rustdoc audit — every public item now has at least one
```rust example block in its docstring, exercised by
`cargo test --doc` (55 doctests, all passing).

- Operators: BitFlipMutation, SwapMutation, RealBounds,
  GaussianMutation, BoundedGaussianMutation,
  SimulatedBinaryCrossover, PolynomialMutation, LevyMutation,
  ClampToBounds, ProjectToSimplex.
- Metrics: hypervolume_2d, hypervolume_nd, spacing.
- Pareto utilities: pareto_compare, pareto_front, best_candidate,
  non_dominated_sort, crowding_distance, das_dennis,
  ParetoArchive.

Each example is short (5-15 lines) and self-contained — copy-paste
into a fresh project and it runs.
This commit is contained in:
2026-05-06 08:16:04 -06:00
parent d564f862d7
commit c1bc3b0528
12 changed files with 335 additions and 0 deletions
+13
View File
@@ -9,6 +9,19 @@ use crate::traits::Variation;
///
/// Always returns exactly one child (spec §11.3). Panics if `probability` is
/// outside `[0.0, 1.0]` or if no parents are provided.
///
/// # Example
///
/// ```
/// use heuropt::prelude::*;
///
/// let mut rng = rng_from_seed(42);
/// let mut m = BitFlipMutation { probability: 0.5 };
/// let parent = vec![true, false, true, false];
/// let children = m.vary(std::slice::from_ref(&parent), &mut rng);
/// assert_eq!(children.len(), 1);
/// assert_eq!(children[0].len(), parent.len());
/// ```
#[derive(Debug, Clone)]
pub struct BitFlipMutation {
/// Per-bit flip probability. Must lie in `[0.0, 1.0]`.
+16
View File
@@ -8,6 +8,22 @@ use crate::traits::Variation;
/// Swap two distinct random indices in the first parent (spec §11.4).
///
/// If the parent has length `< 2` the child is returned unchanged.
///
/// # Example
///
/// ```
/// use heuropt::prelude::*;
///
/// let mut rng = rng_from_seed(42);
/// let mut m = SwapMutation;
/// let parent: Vec<usize> = (0..6).collect();
/// let children = m.vary(std::slice::from_ref(&parent), &mut rng);
/// assert_eq!(children.len(), 1);
/// // Still a permutation of [0, 1, 2, 3, 4, 5]:
/// let mut sorted = children[0].clone();
/// sorted.sort();
/// assert_eq!(sorted, vec![0, 1, 2, 3, 4, 5]);
/// ```
#[derive(Debug, Clone, Copy, Default)]
pub struct SwapMutation;
+101
View File
@@ -10,6 +10,23 @@ use crate::traits::{Initializer, Variation};
///
/// Bounds are inclusive `(lo, hi)` ranges per dimension. Panics if any bound
/// has `lo > hi` (spec §11.1).
///
/// # Example
///
/// ```
/// use heuropt::prelude::*;
///
/// let mut rng = rng_from_seed(42);
/// let mut init = RealBounds::new(vec![(-1.0, 1.0); 3]);
/// let decisions = init.initialize(5, &mut rng);
/// assert_eq!(decisions.len(), 5);
/// for d in &decisions {
/// assert_eq!(d.len(), 3);
/// for &v in d {
/// assert!(v >= -1.0 && v <= 1.0);
/// }
/// }
/// ```
#[derive(Debug, Clone)]
pub struct RealBounds {
/// Per-variable inclusive bounds in decision order.
@@ -54,6 +71,19 @@ impl Initializer<Vec<f64>> for RealBounds {
/// Add `Normal(0, sigma)` noise to every variable of the first parent.
///
/// Always returns exactly one child. Does not enforce bounds in v1 (spec §11.2).
///
/// # Example
///
/// ```
/// use heuropt::prelude::*;
///
/// let mut rng = rng_from_seed(42);
/// let mut m = GaussianMutation { sigma: 0.1 };
/// let parent = vec![0.0; 4];
/// let children = m.vary(std::slice::from_ref(&parent), &mut rng);
/// assert_eq!(children.len(), 1);
/// assert_eq!(children[0].len(), parent.len());
/// ```
#[derive(Debug, Clone)]
pub struct GaussianMutation {
/// Standard deviation of the Gaussian noise. Must be positive.
@@ -88,6 +118,26 @@ impl Variation<Vec<f64>> for GaussianMutation {
///
/// Panics on construction if any bound has `lo > hi`, or at run time if
/// `parents.len() < 2` or any parent length differs from `bounds.len()`.
///
/// # Example
///
/// ```
/// use heuropt::prelude::*;
///
/// let bounds = vec![(-1.0, 1.0); 3];
/// let mut sbx = SimulatedBinaryCrossover::new(bounds.clone(), 15.0, 0.5);
/// let mut rng = rng_from_seed(42);
/// let parents = [vec![-0.5, 0.0, 0.5], vec![0.5, 0.5, -0.5]];
/// let children = sbx.vary(&parents, &mut rng);
/// assert_eq!(children.len(), 2);
/// // Children stay in bounds.
/// for c in &children {
/// for (j, &v) in c.iter().enumerate() {
/// let (lo, hi) = bounds[j];
/// assert!(v >= lo && v <= hi);
/// }
/// }
/// ```
#[derive(Debug, Clone)]
pub struct SimulatedBinaryCrossover {
/// Per-variable inclusive bounds. Length must match the parent decisions.
@@ -180,6 +230,23 @@ impl Variation<Vec<f64>> for SimulatedBinaryCrossover {
///
/// This is the simple bound-rescale form; the bound-aware `δ_q` variant from
/// the full paper is left as a future refinement.
///
/// # Example
///
/// ```
/// use heuropt::prelude::*;
///
/// let bounds = vec![(-1.0, 1.0); 3];
/// let mut pm = PolynomialMutation::new(bounds.clone(), 20.0, 1.0 / 3.0);
/// let mut rng = rng_from_seed(42);
/// let parent = vec![0.0, 0.5, -0.5];
/// let children = pm.vary(std::slice::from_ref(&parent), &mut rng);
/// assert_eq!(children.len(), 1);
/// for (j, &v) in children[0].iter().enumerate() {
/// let (lo, hi) = bounds[j];
/// assert!(v >= lo && v <= hi);
/// }
/// ```
#[derive(Debug, Clone)]
pub struct PolynomialMutation {
/// Per-variable inclusive bounds. Length must match the parent decision.
@@ -254,6 +321,23 @@ impl Variation<Vec<f64>> for PolynomialMutation {
/// Always returns exactly one child. Use this when you want feasibility
/// maintained across generations without leaning on
/// clamp-inside-`Problem::evaluate`.
///
/// # Example
///
/// ```
/// use heuropt::prelude::*;
///
/// let bounds = vec![(-1.0, 1.0); 3];
/// let mut m = BoundedGaussianMutation::new(0.3, bounds.clone());
/// let mut rng = rng_from_seed(42);
/// let parent = vec![0.0; 3];
/// let children = m.vary(std::slice::from_ref(&parent), &mut rng);
/// assert_eq!(children.len(), 1);
/// for (j, &v) in children[0].iter().enumerate() {
/// let (lo, hi) = bounds[j];
/// assert!(v >= lo && v <= hi);
/// }
/// ```
#[derive(Debug, Clone)]
pub struct BoundedGaussianMutation {
/// Standard deviation of the Gaussian noise. Must be positive.
@@ -315,6 +399,23 @@ impl Variation<Vec<f64>> for BoundedGaussianMutation {
/// produce a Lévy(α) sample. `alpha` is the tail exponent in `(0, 2]`;
/// typical value is `1.5`. `1.0` gives the Cauchy distribution (very
/// heavy); `2.0` collapses to the Normal.
///
/// # Example
///
/// ```
/// use heuropt::prelude::*;
///
/// let bounds = vec![(-1.0, 1.0); 3];
/// let mut m = LevyMutation::new(1.5, 0.1, bounds.clone());
/// let mut rng = rng_from_seed(42);
/// let parent = vec![0.0; 3];
/// let children = m.vary(std::slice::from_ref(&parent), &mut rng);
/// assert_eq!(children.len(), 1);
/// for (j, &v) in children[0].iter().enumerate() {
/// let (lo, hi) = bounds[j];
/// assert!(v >= lo && v <= hi);
/// }
/// ```
#[derive(Debug, Clone)]
pub struct LevyMutation {
/// Tail exponent `α ∈ (0, 2]`. Smaller = heavier tail.
+24
View File
@@ -8,6 +8,17 @@ use crate::traits::Repair;
/// The simplest possible repair — pair with `GaussianMutation` (which
/// doesn't enforce bounds in v1) to produce a bounds-respecting variant
/// without writing a custom Variation impl.
///
/// # Example
///
/// ```
/// use heuropt::prelude::*;
///
/// let mut r = ClampToBounds::new(vec![(-1.0, 1.0); 3]);
/// let mut x = vec![-2.0, 0.5, 5.0];
/// r.repair(&mut x);
/// assert_eq!(x, vec![-1.0, 0.5, 1.0]);
/// ```
#[derive(Debug, Clone)]
pub struct ClampToBounds {
/// Per-variable inclusive bounds.
@@ -46,6 +57,19 @@ impl Repair<Vec<f64>> for ClampToBounds {
/// Perpiñán 2013. Useful for portfolio-style problems where the
/// decision must sum to a budget, and for normalizing reference
/// directions onto the unit simplex.
///
/// # Example
///
/// ```
/// use heuropt::prelude::*;
///
/// let mut r = ProjectToSimplex::new(1.0);
/// let mut x = vec![0.6, 0.5, -0.1, 0.3];
/// r.repair(&mut x);
/// let sum: f64 = x.iter().sum();
/// assert!((sum - 1.0).abs() < 1e-12);
/// assert!(x.iter().all(|&v| v >= 0.0));
/// ```
#[derive(Debug, Clone)]
pub struct ProjectToSimplex {
/// Target sum (the simplex's "size"). Standard probability simplex