Adds heuropt-plot, a tiny SVG-only plotter that takes heuropt results and emits scatter plots (pareto_front_svg) and line plots (convergence_svg). No heavy 'plotters' or 'tiny-skia' dep — hand- rolled SVG so the crate adds <100 KB to a build. Workspace setup: root Cargo.toml gains [workspace] with members = ['.', 'heuropt-plot']. heuropt-plot has its own version (0.1.0) and publishes independently against heuropt 0.8+. Adds examples/visualize.rs that wires it up: NSGA-II on Schaffer N.1, plain run() (no observer plumbing), final-front SVG written to disk.
22 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
Added
heuropt-plotcompanion crate (v0.1.0) atheuropt-plot/, published independently. Lightweight SVG-only plotter for Pareto fronts (pareto_front_svg) and convergence traces (convergence_svg) — hand-rolled SVG output, noplotters/tiny-skiadep so the crate stays a tiny optional addition.examples/visualize.rs— runs NSGA-II on Schaffer N.1 and writespareto_front.svgvia the newpareto_front_svghelper.- Workspace setup at the repo root:
[workspace] members = [".", "heuropt-plot"]so both crates share a target dir and onecargoinvocation builds the lot.
0.8.0 — 2026-05-06
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.
Note on version numbers. Versions 0.6.0 and 0.7.0 were published on crates.io but contained experimental observability APIs and metrics that were rolled back. Both are yanked. 0.8.0 picks up cleanly from 0.5.0 with just the async additions; if you were on 0.5.x, upgrading to 0.8 is a feature-additive bump.
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.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.