Files
swaits a0f5b9a660 chore(release): v0.11.0
Bump Cargo.toml to 0.11.0, pin the README install snippet to "0.11",
and add the 0.11.0 CHANGELOG section.

Release highlights:
  - a full permutation crossover/mutation toolkit (OX, PMX, CX, ERX
    crossovers; Inversion, Insertion, Scramble mutations);
  - a micro-benchmark-guided performance pass over the combinatorial
    operators and the Pareto/metrics machinery;
  - a whole-program profiling campaign that cut the `compare`
    workload's instruction count 357.06B -> 165.31B (-53.7%), all
    bit-identical.

No public-API breaks — the release is purely additive.
2026-05-14 14:32:39 -06:00

34 KiB
Raw Permalink Blame History

Changelog

All notable changes to this project will be documented in this file.

The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.

Unreleased

0.11.0 — 2026-05-14

Theme: a full permutation-operator toolkit, plus two sweeping performance passes. The first is a micro-benchmark-guided pass over the combinatorial operators and the Pareto/metrics machinery; the second is a whole-program profiling campaign that roughly halved the instruction count of the compare example workload. Every performance change is bit-identical — verified against per-algorithm exact-output snapshot tests — so results are unchanged, only faster.

No public-API breaks. The release is purely additive: new permutation operators, plus internal-only performance work.

Added

  • A full permutation crossover/mutation toolkit in heuropt::operators, all re-exported from the prelude: OrderCrossover (OX), PartiallyMappedCrossover (PMX), CycleCrossover (CX), and EdgeRecombinationCrossover (ERX) crossovers, and InversionMutation, InsertionMutation, and ScrambleMutation mutations — joining the pre-existing SwapMutation. The mutations preserve both strict permutations and multisets.
  • Combinatorial problems in the compare example: a bi-objective ring TSP, a 3-objective FT06 job-shop schedule, and a bi-objective knapsack — plus standalone Ulysses16 TSP and FT06 JSS benchmark examples and a bi-objective TSP crossover-comparison demo.
  • Many-objective problems in the compare example: DTLZ at 4, 8, and 10 objectives.
  • benches/compare_profile.rs — a gungraun/callgrind benchmark that profiles the entire compare workload as one unit; the harness behind this release's profiling campaign.
  • A permutation-toolkit and multi-objective-combinatorial cookbook chapter in the mdbook.

Performance

All changes below are bit-identical — outputs are byte-for-byte unchanged, verified by the per-algorithm snapshot tests.

  • Whole-program profiling campaign. Profiling the full compare workload under callgrind cut its instruction count from 357.06B to 165.31B (53.7%):
    • pareto_compare is now allocation-free — it no longer materializes two minimization-oriented Vec<f64>s per call. This alone was 38%, the single biggest win.
    • pareto_front precomputes its oriented buffers once and skips candidates already known to be dominated.
    • ibea pre-exponentiates its indicator matrix, turning the survival loop's exp sweep into plain additions.
    • hype reuses its per-Monte-Carlo-sample scratch buffer instead of reallocating it thousands of times per call.
    • age_moea scores only the splitting front rather than the whole combined population.
  • Combinatorial-operator pass. CycleCrossover, PartiallyMappedCrossover, and OrderCrossover are now O(n) via position-index tables; EdgeRecombinationCrossover removes edges in O(degree) per step.
  • Pareto / metrics pass. non_dominated_sort halves its dominance comparisons and reads from a flattened objective buffer; crowding_distance sorts without Vec<Vec<f64>> indirection; hypervolume_nd no longer re-sorts prefixes per slice.
  • Algorithm hot paths. ant_colony_tsp hoists powf out of its tour-building loop; tpe computes KDE bandwidths once per iteration instead of once per call; bayesian_opt reuses scratch buffers in the expected-improvement acquisition loop.

Changed

  • Documentation now recommends MOEA/D as the default multi- and many-objective algorithm, with disconnected-front and sequencing guidance corrected against fresh compare results.
  • The compare example's result tables are realigned and sorted, and its workload now lives in a reusable module shared with the profiling benchmark.

Internal

  • A large mutation-testing-driven test-hardening pass: per-algorithm exact-output snapshots and pinned helper-function tests across the whole algorithm catalog and operator set, raising the cargo-mutants catch rate from ~74% to ~85%. See .cargo/mutants.toml for the campaign notes and the residual equivalent-mutant categories.

0.10.0 — 2026-05-06

Theme: every algorithm now returns its canonical name as it appears in the literature, with an academic long form available alongside, and the docs use those names everywhere. Plus the explorer JSON export now carries both forms so display tools can show the short name with a hover tooltip for the long one.

No public-API breaks beyond the value of AlgorithmInfo::name(), which previously returned the Rust type name and now returns the literature short name ("NSGA-II" vs "Nsga2"). If your code matched on those strings you'll need to update — but the trait shape itself is unchanged and algorithm.name() continues to be the way to read it.

Added

  • AlgorithmInfo::full_name(&self) -> &'static str — academic long form, e.g. "Non-dominated Sorting Genetic Algorithm II". Defaults to name() for algorithms whose short and long forms coincide (Random Search, Hill Climber, Tabu Search).
  • Every built-in algorithm overrides full_name() with its expanded literature name. Mapping table is in the cookbook recipe at docs/book/src/cookbook/explorer.md.
  • ExplorerExport's RunMeta gained an optional algorithm_full_name: Option<String> field. The with_algorithm_info() builder populates both that and algorithm from the same AlgorithmInfo source. Schema version stays at 1 — the new field is #[serde(default)], so older readers tolerate it and older writers' output still loads cleanly.

Changed

  • AlgorithmInfo::name() return values for every built-in algorithm. Examples: "Nsga2""NSGA-II", "Cmaes""CMA-ES", "Mopso""MOPSO", "Moead""MOEA/D", "EpsilonMoea""ε-MOEA". Full table in the cookbook recipe.
  • README, mdbook chapters, decision tree, choosing-an-algorithm guide, comparison page, getting-started, defining-problems, cookbook recipes, and migration notes now all use the canonical algorithm names in body prose. Code blocks (which reference the Rust types like Nsga2::new(...) or Nsga2Config { … }) unchanged — those are still the API.
  • Default cargo run --release --example pick_a_car output now reads "algorithm": "NSGA-III", "algorithm_full_name": "Non-dominated Sorting Genetic Algorithm III" in the JSON envelope instead of "Nsga3".

Migration

If you display optimizer.name() in your own UI, you'll suddenly get the proper short name for free — usually a strict improvement. The only break: code that pattern-matched on the Rust-type-shaped strings (e.g. if name == "Nsga3") needs updating to the new canonical strings. The names are stable now (they match the literature), so this is a one-time fix.

0.9.0 — 2026-05-06

Theme: explorer JSON export. Real Pareto fronts have 50200+ candidates spanning 27+ objectives — too many to read as numbers in a terminal. 0.9.0 adds a tiny additive surface that turns any OptimizationResult into a self-describing JSON file you can drop into heuropt-explorer to filter, brush, pin, and rank candidates interactively.

No public-API breaks. The new surface lives behind the existing serde feature and the new methods on Problem / the new AlgorithmInfo trait have working defaults so existing impls compile untouched.

Added

Explorer export (the headline feature)

  • New heuropt::explorer module (gated on the serde feature). Defines ExplorerExport, ExplorerCandidate, RunMeta, the ToDecisionValues adapter trait, and free functions to_json / to_writer / to_file.
  • Schema is versioned (SCHEMA_VERSION = 1); the explorer webapp refuses to load files with an unknown version.
  • front_rank is computed once via non_dominated_sort at export time and attached to every candidate so downstream tools don't have to re-derive it.
  • ToDecisionValues is implemented for Vec<f64>, Vec<bool>, Vec<usize>, and Vec<i64> out of the box; users with custom decision types implement it themselves (one method).

Problem-side metadata (single source of truth, no duplication)

  • Objective gained optional label: Option<String> and unit: Option<String> fields plus fluent builders .with_label("Price") / .with_unit("$k"). Existing Objective::minimize("name") / Objective::maximize("name") unchanged. Backwards-compatible at source level and at the JSON level (the new fields use #[serde(default, skip_serializing_if = "Option::is_none")]).
  • Problem trait gained an optional fn decision_schema(&self) -> Vec<DecisionVariable> with default empty impl. Override it to provide pretty names / labels / units / bounds for the explorer; the default produces fallback x[0], x[1], … names.
  • New DecisionVariable type at heuropt::core::DecisionVariable, re-exported via the prelude. Builder methods: with_label, with_unit, with_bounds.

Algorithm metadata for the export header

  • New heuropt::traits::AlgorithmInfo trait with name() -> &'static str (required) and seed() -> Option<u64> (default None). Every built-in algorithm — all 33 — implements it. Separate from Optimizer<P> so multi-fidelity algorithms (Hyperband, which uses PartialProblem) implement it uniformly.
  • ExplorerExport::with_algorithm_info(&optimizer) pulls the algorithm name and seed from this trait into the export's run metadata.

Worked example

  • New examples/pick_a_car.rs (gated on serde). Implements the README's PickACar multi-objective problem with a fully enriched decision_schema and labelled / unit-tagged objectives, runs NSGA-III, and writes pick_a_car.json ready to drop into the explorer.

Documentation

  • New cookbook recipe at docs/book/src/cookbook/explorer.md covering Problem enrichment, the export call, the JSON schema, and custom decision-type handling.

Notes

  • The explorer webapp itself lives in a separate repo (heuropt-explorer) on its own release cadence. The schema in heuropt::explorer is the contract between them; bumping SCHEMA_VERSION is reserved for breaking changes.
  • Phase 1 is additive only. No existing test breaks; the lib test count went from 229 to 242 (10 new explorer tests + 3 from the new Objective / DecisionVariable builders).

0.8.0 — 2026-05-06

Theme: async evaluation, plus the docs / governance / CI catch-up that came with finalizing the release.

heuropt now supports problems where each evaluation is a .await-able operation — HTTP services, RPC clients, spawned subprocesses. This is the differentiating capability vs. pymoo / hyperopt / optuna / DEAP / MOEA Framework, none of which ship first-class async support at the evaluation level.

No public-API breaks for synchronous users. The new surface is gated behind a new async feature flag.

Added

Async evaluation (the headline feature)

  • New optional feature async, gated on futures.
  • core::async_problem::AsyncProblem trait — mirrors Problem but with async fn evaluate_async(&self, decision). Adapt an existing sync Problem with a one-line wrapper.
  • core::async_problem::AsyncPartialProblem trait — mirrors PartialProblem for multi-fidelity (Hyperband) workloads with async fn evaluate_at_budget_async(decision, budget).
  • Per-algorithm run_async(&problem, concurrency).await methods on every algorithm in the catalog — all 33 of them — driving evaluations through whichever async runtime the caller is using (typically tokio). concurrency bounds in-flight evaluations. Population-based algorithms (NSGA-II, NSGA-III, SPEA2, MOEA/D, CMA-ES, DE, GA, PSO, IBEA, SMS-EMOA, HypE, ε-MOEA, PESA-II, AGE-MOEA, KnEA, GrEA, RVEA, MOPSO, TLBO, IPOP-CMA-ES, sNES, UMDA, Ant Colony, GA, Random Search) fan out per generation. Steady-state algorithms (Hill Climber, SA, (1+1)-ES, PAES, Nelder-Mead, Tabu Search) await each step sequentially. Surrogate algorithms (BO, TPE) batch the initial design and then await per-iteration acquisitions. Hyperband fans out each Successive-Halving rung through AsyncPartialProblem.
  • Internal algorithms::parallel_eval_async::evaluate_batch_async and evaluate_batch_at_budget_async helpers — use futures::stream::FuturesOrdered with concurrency-bounded chunks, preserve input order so seeded determinism is preserved when evaluations are themselves deterministic.
  • examples/async_eval.rs — worked example with a simulated 20 ms remote service. At concurrency = 1 it's serial; at concurrency = 4 it's 2× faster; demonstrates DifferentialEvolution under tokio.

Documentation

  • New cookbook recipe Async evaluation — implementing AsyncProblem, picking concurrency, determinism guarantees, async vs. parallel.
  • Comparison-with-other-libraries chapter updated: heuropt 0.8 row, Async ✅ AsyncProblem + run_async column, "When to pick heuropt" gains an explicit IO-bound bullet.
  • Stability chapter rewritten: removes the speculative "Observer / Checkpoint planned" bullet (those didn't ship), documents the new async feature flag.
  • Migration guide: new "To 0.8" section covering both 0.5.x → 0.8 (feature-additive — opt in by enabling the async feature) and 0.7 → 0.8 (the partial async surface from 0.7 is superseded by complete coverage; existing run_async callers keep working).
  • Runnable cargo test --doc examples added to every public operator (10), metric (3), and Pareto utility (7) — every public item across the crate now ships with at least one example. 55 doctests in total (was 45).

CI / build

  • .github/workflows/docs.yml builds the mdbook user guide on every push and deploys to GitHub Pages on main / tag pushes.
  • mdbook book now uses [rust] edition = "2021" to satisfy mdbook 0.4.40.
  • clamp_to_bounds cargo-fuzz target tolerance loosened to 1e-4 · max(simplex_total, max_abs_x, 1) so the fuzzer doesn't flag ULP-level slop in the simplex projection's max(x_i τ, 0) clamp boundary.

0.5.0 — 2026-05-05

Theme: comprehensive documentation and project polish. No public-API changes — bumping heuropt = "0.5" in your Cargo.toml is enough.

Added

User guide (mdbook)

A new mdbook user guide at docs/book/, deployed to https://swaits.github.io/heuropt/ via a CI workflow on tag pushes. Chapters:

  • Introduction — what heuropt is, who it's for, what's in the box.
  • Five-minute walkthrough — install, define a problem, run an optimizer, look at the result.
  • Defining a problem — the Problem trait in depth: single- vs multi-objective, constraints, custom decision types (Vec<f64>, Vec<bool>, Vec<usize>, custom structs).
  • Choosing an algorithm — the README's decision tree, expanded to a full chapter with the reasoning behind every branch.
  • Cookbook — seven recipes covering parallelism, expensive evaluations, comparison harnesses, permutation problems, constraint repair, picking one answer off a Pareto front, and writing your own optimizer.
  • Comparison with other libraries — heuropt vs pymoo, hyperopt, optuna, MOEA Framework, metaheuristics-rs, argmin. Honest about when not to pick heuropt.
  • Stability and SemVer — explicit guarantees about which surfaces are stable; what's likely to change before 1.0; bit-identical determinism contract.
  • Migration guides — per-release upgrade notes.

Runnable rustdoc examples

Every algorithm now has a runnable ```rust example block in its rustdoc — 35 algorithms, all exercised by cargo test --doc. Plus the existing crate-level example in lib.rs and the CompositeVariation operator example.

Real-world examples

Three new polished examples covering distinct domains:

  • examples/portfolio.rs — multi-objective portfolio optimization with budget constraint via ProjectToSimplex. Pareto front of return-vs-risk trade-offs, plus a-posteriori weighted decision.
  • examples/hyperparam_tuning.rs — sample-efficient hyperparameter tuning with BayesianOpt and Tpe, demonstrating mixed-scale decoding (log-uniform learning rate, integer depth) and a 60-eval budget.
  • examples/scheduling.rs — single-machine weighted-completion-time scheduling: permutation decisions optimized via SimulatedAnnealing + SwapMutation, comparing against the Smith's-rule oracle.

Governance docs

  • CONTRIBUTING.md — local-test checklist, conventional-commits requirement, contribution areas that land easily vs. those that need prior discussion.
  • SECURITY.md — disclosure policy, supported versions, what counts as a security issue.
  • CODE_OF_CONDUCT.md — adopts the Builder's Code of Conduct (CC0).
  • .github/ISSUE_TEMPLATE/ — bug, feature, docs templates plus a config.yml that points security reports to the private vulnerability-disclosure flow.
  • .github/PULL_REQUEST_TEMPLATE.md — short, opinionated PR template.

CI / tooling

  • .github/workflows/docs.yml — builds the mdbook user guide and deploys it to GitHub Pages on main pushes and tag pushes.

Changed

  • README hero block expanded with badges and a punchier opening; added explicit links to the user guide, the docs.rs API reference, and the testing-coverage breakdown.
  • lib.rs crate-level docs polished — better intro, points readers at the user guide and the design spec.

0.4.0 — 2026-05-05

Theme: testing infrastructure, two real bug fixes surfaced by that infrastructure, and a CPU-time optimization pass that made the comparison harness 3.27× faster end-to-end. No breaking changes to the v0.3.0 public API.

Performance

A focused, measure-and-iterate optimization pass on the Pareto-based multi-objective hot paths. Every change verified bit-identical against the v0.3.0 comparison-harness snapshot — quality metrics (hypervolume, spacing, mean L2, mean dist, front size) match to the last decimal in every benchmark.

Cumulative wall-clock impact (compare harness, 10-seed mean):

Algorithm / Problem v0.3.0 v0.4.0 Speedup
AGE-MOEA / DTLZ1 2299 ms 229 ms 10×
SPEA2 / DTLZ2 4304 ms 513 ms 8.4×
AGE-MOEA / ZDT3 932 ms 193 ms 4.8×
NSGA-II / ZDT1 268 ms 65 ms 4.1×
NSGA-II / ZDT3 267 ms 65 ms 4.1×
SMS-EMOA / DTLZ2 5643 ms 1369 ms 4.1×
NSGA-II / Rastrigin 260 ms 71 ms 3.7×
NSGA-II / DTLZ2 344 ms 106 ms 3.2×
NSGA-III / DTLZ2 318 ms 122 ms 2.6×
NSGA-III / DTLZ1 303 ms 122 ms 2.5×
HypE / DTLZ2 80 ms 44 ms 1.8×
Total compare 18 629 ms 5688 ms 3.27×

Hot-path instruction counts (gungraun):

Benchmark v0.3.0 v0.4.0 Speedup
hypervolume_nd_3d n=100 13 523 760 367 767 37×
hypervolume_nd_3d n=30 676 902 70 334 9.6×
non_dominated_sort_2d n=200 13 513 271 2 601 813 5.2×
non_dominated_sort_2d n=50 852 317 198 574 4.3×
spea2_short 179 113 133 783 1.34×

Changes (in commit order):

  • perf(hypervolume) — Rewrote the M≥3 HSO recursion in hypervolume_nd. The original cloned the active set into a fresh Vec<Vec> at the top of every recursive call, used a linear-scan position lookup to remove the just-processed point each band, and re-projected onto M-1 axes inside every band. Now: sort-by-index, pre-project once, slice prefixes for the active set, and skip non_dominated_projection when recursing into the M=2 base case (whose sweep already filters dominated points internally).
  • perf(non_dominated_sort) — Cache as_minimization / feasibility / violation per individual once at the top of the Deb fast-non-dominated-sort, then inline the dominance test against those arrays. The naïve formulation called pareto_compare twice per pair, each call allocating two fresh Vecs — 4N(N-1) allocations per sort. Propagates to every Pareto-based MOEA.
  • perf(age_moea) — Cache lp_norm(translated[i], p) once per candidate at function entry; maintain a nearest[] array updated incrementally on each pick (single min per remaining instead of a fresh full scan over the keep list). Cuts the splitting-front scoring loop from O(R · K · M) per iteration to O(R · M).
  • perf(spea2) — Two wins. (1) compute_fitness (called twice per generation): inline dominance against cached oriented arrays, symmetric distance matrix built once. (2) build_archive truncation: compute pairwise distances + sorted neighbor vectors once, then on victim removal use binary-search-remove on every survivor's still-sorted vector — total truncation cost O(K³ log K) → O(K² log K).
  • perf(hypervolume) — Index-sort instead of cloning point vectors in the M≥3 recursion. The N inner-Vec clones per HV call were redundant once we'd already sorted by last-axis. Big bench win (32×→37× cumulative on n=100/3D), modest wall-clock impact because SMS-EMOA's worst-front HV calls operate on small fronts.
  • build(release) — Enable thin LTO + codegen-units=1 in the release profile. Worth ~150 ms across the harness; only applies when heuropt is the workspace root, so downstream consumers see whatever profile their own Cargo.toml configures.
  • perf(pareto_archive) — Cache the candidate's oriented + feasibility once per insert, build each member's oriented vector once, and inline the two-pass dominance checks. Used by PESA-II (most impact), PAES, ε-MOEA, and any user code working through the archive directly.

Added

  • Decision tree update in README to cover all v0.3.0 algorithms, with a new top-level branch on "is each evaluation expensive?" so BayesianOpt / Tpe / Hyperband have a clear home.
  • Comparison results snapshot at examples/compare-results.md — reference output of the harness across 7 benchmark problems and ~20 algorithms, captured after v0.3.0 landed.
  • Instruction-count benchmarks via gungraun (the Rust 2026 rename of iai-callgrind) at benches/hot_paths.rs. Covers non_dominated_sort, crowding_distance, hypervolume_2d, hypervolume_nd (HSO), and one-generation costs of NSGA-II and CMA-ES, plus a short-run bench for every algorithm. Stable across machines via callgrind.
  • Property-based test suite expansion: tests/properties.rs (Pareto-comparison antisymmetry, partitioning, operator bounds), tests/algorithm_properties.rs (per-algorithm determinism + population-size invariants — 32 tests, one per algorithm), tests/operator_properties.rs (every Variation / Initializer / Repair impl), tests/metric_properties.rs (HV / spacing invariants), and tests/numerical_stability.rs (empty / singleton / duplicate / flat-fitness / zero-width-bounds populations).
  • Coverage-guided fuzz harness at fuzz/ (cargo-fuzz + libFuzzer). Eight targets covering pareto_compare, non_dominated_sort, hypervolume_2d, ParetoArchive, crowding_distance, spacing, SBX/PolyMut, and the Repair operators. Runs in CI for a short soak per PR; longer runs locally via cargo +nightly fuzz run <target>.
  • cargo-mutants config at .cargo/mutants.toml for advisory mutation testing. Not gated in CI; run with cargo mutants to surface tests that don't actually check the behavior they look like they do.
  • GitHub Actions CI at .github/workflows/ci.yml with fmt / clippy / test (4-feature matrix) / doc / MSRV / fuzz-smoke jobs, all gated on -D warnings.

Fixed

  • pareto::sort::non_dominated_sort previously dropped indices when the dominance graph contained a cycle (which arises when objectives contain NaN — pareto_compare becomes intransitive). Fuzzing the partition invariant surfaced the bug; orphans now go into a final residual front.
  • operators::repair::ProjectToSimplex could silently return the all-zero vector when the input vector's magnitude dwarfed total (the standard Duchi/Held-Wolfe τ computation lost precision and τ ≈ max(x), so max(x_i - τ, 0) rounded to zero everywhere). Detected by the clamp_to_bounds fuzzer; now falls through to a degenerate "all mass on argmax" projection above a 1e15 magnitude ratio, and is robust to floating-point precision loss in the algorithm's inner loop.

0.3.0 — 2026-05-05

Theme: filling heuropt's expensive-evaluation, gradient-free, and constraint-handling gaps. No breaking changes to the v0.2.0 public API.

Added

New algorithms (9)

Sample-efficient / surrogate-based:

  • BayesianOpt — Gaussian-process Bayesian Optimization with Expected Improvement acquisition. heuropt's first sample-efficient algorithm: targets the 50500 evaluation regime.
  • Tpe — Bergstra et al. 2011 Tree-structured Parzen Estimator (workhorse of Hyperopt and Optuna). KDE-based surrogate; cheaper per-step than BO and more robust without hyperparameter tuning.

Classical and modern evolution strategies:

  • OnePlusOneEs — Rechenberg 1973 (1+1)-ES with the one-fifth success rule. Smallest possible self-adapting evolution strategy.
  • IpopCmaEs — Auger & Hansen 2005 increasing-population CMA-ES with restart. Specifically fixes vanilla CMA-ES's known weakness on multimodal problems.
  • SeparableNes — Wierstra et al. 2008/2014 Natural Evolution Strategy with diagonal covariance (sNES). Different theoretical foundation than CMA-ES; cheaper per-step at the cost of being unable to model rotated landscapes.

Direct search:

  • NelderMead — Nelder & Mead 1965 simplex method. Classical gradient- free local optimizer; superb on low-dim smooth problems (Rosenbrock 5-D: f = 0 exactly).

Multi-fidelity:

  • Hyperband — Li et al. 2017 multi-fidelity hyperparameter optimizer built on Successive Halving. Operates on a new PartialProblem trait so configurations can be evaluated at adjustable fidelity budgets.

New operators

  • LevyMutation — heavy-tailed Lévy-flight mutation via Mantegna's algorithm. The actual algorithmic contribution from Cuckoo Search packaged as a reusable Variation operator.

New traits + impls

  • PartialProblem — multi-fidelity problem contract: evaluate_at_budget(decision, budget) -> Evaluation. Used by Hyperband. Intentionally not a sub-trait of Problem.
  • Repair<D> — in-place projection trait for restoring decisions to feasibility. Pair with Variation operators to get bounds-aware variants. Provided impls:
    • ClampToBounds for Vec<f64> per-axis clamping
    • ProjectToSimplex for L1-budget / probability-simplex projection

New selection helpers

  • stochastic_ranking_select — Runarsson & Yao 2000 stochastic ranking. Better than strict feasibility-first tournament selection on heavily-constrained problems.

Internal helpers

  • internal::cholesky — Cholesky factorization + triangular solves for SPD matrices, used by the GP posterior in BayesianOpt.

Changed

  • CmaEsConfig gained initial_mean: Option<Vec<f64>>. None preserves the existing midpoint-of-bounds default; IpopCmaEs sets it to inject restart diversity without shrinking the search box.

0.2.0 — 2026-05-05

A substantial expansion of the algorithm catalog (21 new algorithms), five new operators, an n-D hypervolume utility, an algorithm-selection guide in the README, and a multi-seed comparison harness covering seven benchmark problems. No breaking changes to the v0.1.0 public API.

Added

New algorithms

Single-objective:

  • HillClimber — simplest greedy local search.
  • SimulatedAnnealing — Kirkpatrick et al. 1983, generic over decision type.
  • GeneticAlgorithm — generational SO GA with tournament selection + elitism.
  • ParticleSwarm — Eberhart & Kennedy 1995 PSO for Vec<f64>.
  • CmaEs — Hansen & Ostermeier 2001 covariance-matrix adaptation.
  • TabuSearch — Glover 1986, with a user-supplied neighbor generator.
  • AntColonyTsp — Dorigo Ant System for permutation problems.
  • Umda — Mühlenbein 1997 univariate marginal-distribution EDA for Vec<bool>.
  • Tlbo — Rao 2011 Teaching-Learning-Based Optimization (parameter-free).

Multi-objective:

  • Mopso — Coello, Pulido & Lechuga 2004 multi-objective PSO.
  • Ibea — Zitzler & Künzli 2004 indicator-based EA.
  • SmsEmoa — Beume, Naujoks & Emmerich 2007 S-metric selection EMOA.
  • Hype — Bader & Zitzler 2011 Hypervolume Estimation Algorithm.
  • Rvea — Cheng et al. 2016 Reference Vector-guided EA.
  • PesaII — Corne et al. 2001 Pareto Envelope-based Selection II.
  • EpsilonMoea — Deb, Mohan & Mishra 2003 ε-dominance MOEA.
  • AgeMoea — Panichella 2019 Adaptive Geometry Estimation MOEA.
  • Grea — Yang et al. 2013 Grid-based EA.
  • Knea — Zhang, Tian & Jin 2015 Knee point-driven EA.

New operators

  • BoundedGaussianMutation — Gaussian noise + per-axis clamping.
  • SimulatedBinaryCrossover (SBX) — Deb & Agrawal 1995 canonical real-valued crossover.
  • PolynomialMutation — Deb's polynomial mutation, the standard NSGA-II pair to SBX.
  • CompositeVariation — pipeline two Variation operators (typically crossover → mutation).
  • LevyMutation — heavy-tailed Lévy-flight mutation via Mantegna's algorithm.

New metrics / utilities

  • hypervolume_nd — exact N-dimensional dominated hypervolume via the Hypervolume-by-Slicing-Objectives (HSO) algorithm, plus an internal Jacobi symmetric eigendecomposition helper used by CMA-ES.

New examples

  • compare — multi-seed comparison harness running every applicable algorithm across ZDT1, ZDT3, DTLZ1, DTLZ2 (multi/many-objective) and Rastrigin, Rosenbrock, Ackley (single-objective). Reports hypervolume, spacing, mean L2/dist, front size, and wall-clock ms.
  • benchmarks — canonical reference runs of NSGA-II on ZDT1 and DE on Rastrigin.
  • jiggly_tuning — real-world 4-objective NSGA-III firmware tuning for the jiggly USB-mouse-jiggler, with an a-posteriori weighted-decision step that picks one recommendation off the Pareto front.

New optional feature

  • parallel — rayon-backed parallel population evaluation in RandomSearch, Nsga2, DifferentialEvolution, Spea2, Ibea, Mopso, and most other algorithms with batchable inner loops. Seeded runs stay bit-identical to serial mode.

Documentation

  • README gained an explanatory algorithm-selection decision tree that walks newcomers through choosing an optimizer, defining the terminology (multi-objective, Pareto front, dominance, multimodality, evaluation cost) as it goes.

Changed

  • Minimum supported Rust version remains 1.85 (edition 2024).
  • Algorithm impls now require P: Sync and P::Decision: Send so the same impl serves both parallel and serial feature builds. Any Problem / decision type without exotic interior mutability already satisfies these.

0.1.0 — 2026-05-04

Initial release.

Core types and traits

  • Direction, Objective, ObjectiveSpace (with as_minimization direction conversion).
  • Evaluation with feasibility (constraint_violation <= 0.0).
  • Candidate<D>, Population<D>, OptimizationResult<D>.
  • type Rng = rand::rngs::StdRng and rng_from_seed so no public trait is generic over the RNG.
  • Problem, Optimizer<P>, Initializer<D>, Variation<D>.

Pareto utilities

  • pareto_compare, pareto_front, best_candidate, non_dominated_sort (Deb fast non-dominated sort), crowding_distance, ParetoArchive<D>, das_dennis (structured reference points for NSGA-III and MOEA/D).

Operators

  • Real: RealBounds, GaussianMutation, BoundedGaussianMutation, SimulatedBinaryCrossover (SBX), PolynomialMutation.
  • Binary: BitFlipMutation.
  • Permutation: SwapMutation.
  • CompositeVariation pipeline (typically crossover → mutation).

Selection helpers

  • select_random, tournament_select_single_objective.

Reference algorithms

  • RandomSearch — sample-evaluate-keep baseline.
  • Paes — small (1+1) Pareto Archived Evolution Strategy.
  • Nsga2 — canonical Pareto-based EA with crowding distance.
  • Nsga3 — many-objective NSGA-III with reference-point niching.
  • Spea2 — Strength Pareto Evolutionary Algorithm 2.
  • Moead — decomposition-based MOEA/D with the Tchebycheff scalar.
  • DifferentialEvolution — single-objective DE/rand/1/bin.

Metrics

  • spacing (Schott), hypervolume_2d (exact 2-D dominated hypervolume).

Examples

  • random_search, toy_nsga2, custom_optimizer — minimum-viable walkthroughs.
  • benchmarks — ZDT1 and Rastrigin reference runs.
  • compare — multi-seed comparison harness running every applicable algorithm on ZDT1 (2-obj), DTLZ2 (3-obj), and Rastrigin (single-obj), reporting hypervolume, spacing, mean L2, front size, and wall time.
  • jiggly_tuning — 4-objective NSGA-III tuning of the jiggly USB-mouse-jiggler firmware with an a-posteriori weighted-decision step that picks one recommendation off the Pareto front.

Optional features

  • serdeSerialize / Deserialize derives on the core data types.
  • parallel — rayon-backed parallel population evaluation in RandomSearch, Nsga2, and DifferentialEvolution. Seeded runs stay bit-identical to serial mode.