Theme: async/await for IO-bound evaluations. Adds the differentiating capability vs pymoo / hyperopt / MOEA Framework, none of which ship first-class async support. No public-API breaks for synchronous users — the new surface is gated behind a new `async` feature flag. Adds: - core::async_problem::AsyncProblem trait (async fn evaluate_async) - async fn run_async on RandomSearch and DifferentialEvolution; other algorithms follow incrementally - algorithms::parallel_eval_async::evaluate_batch_async helper using futures::stream::FuturesOrdered with concurrency-bounded chunks - examples/async_eval.rs worked example with simulated 20 ms remote service: concurrency=1 → 4.2 s, concurrency=4 → 2.1 s (2× speedup) Bumps Cargo.toml to 0.7.0; CHANGELOG entry covers the above. Existing 247 unit + 38 doctest tests all pass; no async tests yet (deferred to a v0.7.x patch with tokio dev-deps wired in).
24 KiB
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.7.0 — 2026-05-05
Theme: async evaluation. 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 / MOEA Framework, none of which ship first-class
async support.
No public-API breaks for synchronous users. The new surface is
gated behind a new async feature flag.
Added
- New optional feature
async, gated onfutures. core::async_problem::AsyncProblemtrait — mirrorsProblembut withasync fn evaluate_async(&self, decision). Adapt an existing syncProblemwith a one-line wrapper.- Per-algorithm
run_async(&problem, concurrency).awaitmethods onRandomSearchandDifferentialEvolution— drives evaluations through whichever async runtime the caller is using (typically tokio).concurrencybounds in-flight evaluations. - Internal
algorithms::parallel_eval_async::evaluate_batch_asynchelper — usesfutures::stream::FuturesOrderedwith concurrency- bounded chunks, preserves 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.
0.6.0 — 2026-05-05
Theme: production lifecycle. heuropt becomes deployable for long- running, real-world optimization workloads — callbacks, stop conditions, tracing, and two new performance indicators.
No breaking changes to the public API. Existing Optimizer<P> impls
keep compiling — run_with is added as a default-impl method that
falls back to run plus a single final notification.
Added
Observer + stop-conditions API
A new module heuropt::observer introduces:
Snapshot<'a, D>— per-generation observation payload withiteration,evaluations,elapsed,population,pareto_front,best, andobjectives.Observer<D>trait — single methodobserve(&Snapshot) -> ControlFlow<()>. Closures of the right shape implement it automatically.()is the no-op observer.Optimizer::run_with(problem, observer)— new method on theOptimizertrait with a default impl that falls back torun. Algorithms that overriderun_with(so far:Nsga2,RandomSearch,DifferentialEvolution) call the observer once per generation; others call it once at the end. ReturningControlFlow::Breakhalts the optimizer and returns the partial result.
Built-in observers (observer::builtin)
MaxTime(Duration)— wall-clock cap.MaxIterations(usize)— generation cap.TargetFitness(f64)— direction-aware single-objective target.Stagnation { window, tolerance }— halt when the best fitness hasn't improved bytoleranceoverwindowgenerations.Periodic::new(every, |snap| { … })— call a user closure everyeverygenerations.AnyOf/AllOfplusObserver::or/Observer::andfor composition.TracingObserver(behind the newtracingfeature) — emits structureddebug!events per generation.
Tracing feature
New optional feature tracing, gated on the
tracing crate. Adds
TracingObserver to the prelude when enabled.
Performance indicators
metrics::igd::igd— Inverted Generational Distance against a reference set (typically the true Pareto front).metrics::igd::igd_plus— Pareto-compliant IGD+ variant; adding a dominated point never improves the score.metrics::r2::r2— R2 indicator using the weighted Tchebycheff utility. Pair withpareto::das_dennisfor the canonical weight set.
Constrained example
examples/constrained.rs — solves the BNH constrained 2-objective
problem (Binh & Korn 1996) with NSGA-II + the new observer API,
demonstrating Periodic progress logging and MaxTime /
composition.
Changed
Population::as_slice()— new convenience accessor.
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
Problemtrait 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 viaProjectToSimplex. Pareto front of return-vs-risk trade-offs, plus a-posteriori weighted decision.examples/hyperparam_tuning.rs— sample-efficient hyperparameter tuning withBayesianOptandTpe, 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 viaSimulatedAnnealing+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 aconfig.ymlthat 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 onmainpushes 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.rscrate-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 inhypervolume_nd. The original cloned the active set into a fresh Vec<Vec> at the top of every recursive call, used a linear-scanpositionlookup 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 skipnon_dominated_projectionwhen recursing into the M=2 base case (whose sweep already filters dominated points internally).perf(non_dominated_sort)— Cacheas_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 calledpareto_comparetwice per pair, each call allocating two fresh Vecs — 4N(N-1) allocations per sort. Propagates to every Pareto-based MOEA.perf(age_moea)— Cachelp_norm(translated[i], p)once per candidate at function entry; maintain anearest[]array updated incrementally on each pick (singleminper 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_archivetruncation: 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 perinsert, 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/Hyperbandhave 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 ofiai-callgrind) atbenches/hot_paths.rs. Coversnon_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(everyVariation/Initializer/Repairimpl),tests/metric_properties.rs(HV / spacing invariants), andtests/numerical_stability.rs(empty / singleton / duplicate / flat-fitness / zero-width-bounds populations). - Coverage-guided fuzz harness at
fuzz/(cargo-fuzz + libFuzzer). Eight targets coveringpareto_compare,non_dominated_sort,hypervolume_2d,ParetoArchive,crowding_distance,spacing, SBX/PolyMut, and theRepairoperators. Runs in CI for a short soak per PR; longer runs locally viacargo +nightly fuzz run <target>. - cargo-mutants config at
.cargo/mutants.tomlfor advisory mutation testing. Not gated in CI; run withcargo mutantsto surface tests that don't actually check the behavior they look like they do. - GitHub Actions CI at
.github/workflows/ci.ymlwith fmt / clippy / test (4-feature matrix) / doc / MSRV / fuzz-smoke jobs, all gated on-D warnings.
Fixed
pareto::sort::non_dominated_sortpreviously dropped indices when the dominance graph contained a cycle (which arises when objectives contain NaN —pareto_comparebecomes intransitive). Fuzzing the partition invariant surfaced the bug; orphans now go into a final residual front.operators::repair::ProjectToSimplexcould silently return the all-zero vector when the input vector's magnitude dwarfedtotal(the standard Duchi/Held-Wolfe τ computation lost precision and τ ≈ max(x), somax(x_i - τ, 0)rounded to zero everywhere). Detected by theclamp_to_boundsfuzzer; 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 50–500 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 newPartialProblemtrait 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 reusableVariationoperator.
New traits + impls
PartialProblem— multi-fidelity problem contract:evaluate_at_budget(decision, budget) -> Evaluation. Used byHyperband. Intentionally not a sub-trait ofProblem.Repair<D>— in-place projection trait for restoring decisions to feasibility. Pair withVariationoperators to get bounds-aware variants. Provided impls:ClampToBoundsforVec<f64>per-axis clampingProjectToSimplexfor 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 inBayesianOpt.
Changed
CmaEsConfiggainedinitial_mean: Option<Vec<f64>>.Nonepreserves the existing midpoint-of-bounds default;IpopCmaEssets 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 forVec<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 forVec<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 twoVariationoperators (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 thejigglyUSB-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 inRandomSearch,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: SyncandP::Decision: Sendso the same impl serves bothparalleland serial feature builds. AnyProblem/ decision type without exotic interior mutability already satisfies these.
0.1.0 — 2026-05-04
Initial release.
Core types and traits
Direction,Objective,ObjectiveSpace(withas_minimizationdirection conversion).Evaluationwith feasibility (constraint_violation <= 0.0).Candidate<D>,Population<D>,OptimizationResult<D>.type Rng = rand::rngs::StdRngandrng_from_seedso 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. CompositeVariationpipeline (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 thejigglyUSB-mouse-jiggler firmware with an a-posteriori weighted-decision step that picks one recommendation off the Pareto front.
Optional features
serde—Serialize/Deserializederives on the core data types.parallel— rayon-backed parallel population evaluation inRandomSearch,Nsga2, andDifferentialEvolution. Seeded runs stay bit-identical to serial mode.