docs(0.9): release notes, cookbook recipe, README polish

Companion to the feat(explorer) commit. Bumps the version and
brings every cross-referencing doc up to v0.9 currency.

- Cargo.toml: version 0.8.0 -> 0.9.0.
- CHANGELOG: 0.9.0 entry covering the explorer export, the
  Problem-side metadata additions, the AlgorithmInfo trait, the
  pick_a_car example, and the new cookbook recipe.
- README: closing paragraph of the PickACar example points users
  at the explorer with a one-call snippet
  (`ExplorerExport::from_result(...).with_algorithm_info(...)
  .to_file(...)?`). Version snippets bumped 0.8 -> 0.9.
- New cookbook recipe at docs/book/src/cookbook/explorer.md
  covering: enabling the serde feature, enriching Problem with
  labels/units/decision-schema, the export call, the JSON schema,
  and custom decision-type handling.
- SUMMARY.md and cookbook.md link the new recipe.
- migration.md: new "To 0.9" section documenting the additive
  changes (purely backwards-compatible upgrade from 0.8.x).
- introduction.md, comparison.md, choosing-an-algorithm.md,
  stability.md: version refs bumped 0.8 -> 0.9.
- cookbook/parallel.md, cookbook/async.md: version refs bumped
  0.8 -> 0.9.
- getting-started.md: version refs bumped, serde feature
  description expanded to mention the explorer module.
- SECURITY.md: supported-versions table moves to 0.9.x.
This commit is contained in:
2026-05-06 22:45:59 -06:00
parent 729842c260
commit 6371d82f40
55 changed files with 721 additions and 354 deletions
-1
View File
@@ -1 +0,0 @@
{"sessionId":"ac44d107-52ca-4cd4-9586-ae2fe91bc9f7","pid":2366937,"procStart":"77336928","acquiredAt":1778002505967}
+3
View File
@@ -1,2 +1,5 @@
/target
/Cargo.lock
# Generated by `cargo run --example pick_a_car`
/pick_a_car.json
+146 -1
View File
@@ -7,6 +7,151 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
## [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.10.0]: https://github.com/swaits/heuropt/releases/tag/v0.10.0
## [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](https://swaits.github.io/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.9.0]: https://github.com/swaits/heuropt/releases/tag/v0.9.0
## [0.8.0] — 2026-05-06
Theme: async evaluation, plus the docs / governance / CI catch-up
@@ -552,5 +697,5 @@ Initial release.
`RandomSearch`, `Nsga2`, and `DifferentialEvolution`. Seeded runs stay
bit-identical to serial mode.
[Unreleased]: https://github.com/swaits/heuropt/compare/v0.8.0...HEAD
[Unreleased]: https://github.com/swaits/heuropt/compare/v0.10.0...HEAD
[0.1.0]: https://github.com/swaits/heuropt/releases/tag/v0.1.0
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "heuropt"
version = "0.9.0"
version = "0.10.0"
edition = "2024"
rust-version = "1.85"
authors = ["Stephen Waits <steve@waits.net>"]
+137 -111
View File
@@ -12,7 +12,7 @@ sync `run` and an async `run_async`. One small set of traits. Bit-identical
seeded determinism. No trait objects, no GATs, no generic-RNG plumbing in
the public API.
If you can write a `Problem` impl and read `RandomSearch`, you can write your
If you can write a `Problem` impl and read Random Search, you can write your
own optimizer. That's the whole pitch.
Docs: [user guide](https://swaits.github.io/heuropt/) · [API reference](https://docs.rs/heuropt).
@@ -21,7 +21,7 @@ Docs: [user guide](https://swaits.github.io/heuropt/) · [API reference](https:/
```toml
[dependencies]
heuropt = "0.8"
heuropt = "0.10"
# Optional features:
# - "serde": derive Serialize/Deserialize on the core data types.
@@ -30,7 +30,7 @@ heuropt = "0.8"
# - "async": AsyncProblem / AsyncPartialProblem traits and a
# run_async(&problem, concurrency).await method on
# every algorithm — for IO-bound evaluations.
# heuropt = { version = "0.8", features = ["serde", "parallel", "async"] }
# heuropt = { version = "0.10", features = ["serde", "parallel", "async"] }
```
## Define a problem and run an optimizer
@@ -186,6 +186,32 @@ back in 0-60. The optimizer doesn't tell you what to buy — it
hands you the frontier of *every defensible compromise* and lets
you pick by your own priorities.
### Explore it interactively
Six hand-picked rows out of a hundred is a sample, not a search.
With the `serde` feature enabled, the same result becomes one JSON
file you can drop into the [heuropt-explorer](https://swaits.github.io/heuropt-explorer/)
webapp to browse interactively — parallel coordinates, scatter,
range filters, weighted ranking:
```rust,ignore
heuropt::explorer::ExplorerExport::from_result(&PickACar, &result)
.with_algorithm_info(&optimizer)
.with_problem_name("Pick a car")
.to_file("results.json")?;
```
The full worked example (which produces this output verbatim) is at
`examples/pick_a_car.rs`:
```text
cargo run --release --example pick_a_car --features serde
```
See the [Explore your results](https://swaits.github.io/heuropt/cookbook/explorer.html)
cookbook recipe for the export schema and how to enrich your `Problem`
with display labels and units.
## Implement a custom optimizer
A new optimizer is just an implementation of `Optimizer<P>`:
@@ -288,12 +314,12 @@ you need a **sample-efficient** or **multi-fidelity** approach:
- **Cheap (1k+ evals affordable):** any of the population-based
algorithms — DE, GA, CMA-ES, NSGA-II, etc.
- **Expensive (50500 evals):** `BayesianOpt` (Gaussian-process
surrogate + Expected Improvement) or `Tpe` (Parzen-density
- **Expensive (50500 evals):** Bayesian Optimization (Gaussian-process
surrogate + Expected Improvement) or TPE (Parzen-density
surrogate, cheaper per step, more robust without hyperparameter
tuning).
- **Multi-fidelity (each eval has a tunable budget — epochs, sim
steps, MC samples):** `Hyperband`. Implement the `PartialProblem`
steps, MC samples):** Hyperband. Implement the `PartialProblem`
trait on your problem and Hyperband allocates compute aggressively
across promising configs.
@@ -339,12 +365,12 @@ START
│ │
│ ├─ Yes → sample-efficient regime
│ │ ├─ Standard expensive black-box, single-objective
│ │ │ → BayesianOpt (GP + Expected Improvement; gold
│ │ │ → Bayesian Optimization (GP + Expected Improvement; gold
│ │ │ standard *with* per-problem kernel
│ │ │ tuning. The default RBF kernel at
│ │ │ 60 evals is honestly bad — give it
│ │ │ more evals or tune the kernel.)
│ │ │ → Tpe (KDE-based; cheaper per-step,
│ │ │ → TPE (KDE-based; cheaper per-step,
│ │ │ more robust without tuning)
│ │ │
│ │ └─ Each eval has a tunable fidelity (epochs, sim steps, …)
@@ -359,60 +385,60 @@ START
│ │
│ ├─ Decision is Vec<f64> (continuous)
│ │ ├─ Smooth landscape (well-conditioned)
│ │ │ → CmaEs (full-cov adaptive Gaussian)
│ │ │ → SeparableNes (cheaper diag-cov; high-dim)
│ │ │ → NelderMead (low-dim, deterministic, simple)
│ │ │ → CMA-ES (full-cov adaptive Gaussian)
│ │ │ → sNES (cheaper diag-cov; high-dim)
│ │ │ → Nelder-Mead (low-dim, deterministic, simple)
│ │ ├─ Multimodal landscape
│ │ │ → IpopCmaEs (CMA-ES with restart;
│ │ │ → IPOP-CMA-ES (CMA-ES with restart;
│ │ │ fixes vanilla CMA-ES's
│ │ │ multimodal failure)
│ │ │ → DifferentialEvolution (rarely beaten on cheap
│ │ │ → Differential Evolution (rarely beaten on cheap
│ │ │ multimodal continuous)
│ │ │ → SimulatedAnnealing (cheap & generic)
│ │ │ → Simulated Annealing (cheap & generic)
│ │ ├─ Want parameter-free (no F, CR, w, σ to tune)
│ │ │ → Tlbo
│ │ │ → TLBO
│ │ ├─ Want minimum self-adapting baseline
│ │ │ → OnePlusOneEs (one-fifth rule,
│ │ │ → (1+1)-ES (one-fifth rule,
│ │ │ smallest possible ES)
│ │ ├─ Just want a strong default for cheap continuous
│ │ │ → DifferentialEvolution
│ │ │ → Differential Evolution
│ │ └─ Just want a baseline
│ │ → RandomSearch
│ │ → Random Search
│ │
│ ├─ Decision is Vec<bool> (binary)
│ │ ├─ Independent bits, smooth fitness
│ │ │ → Umda (per-bit marginal EDA)
│ │ │ → UMDA (per-bit marginal EDA)
│ │ └─ Bit interactions matter
│ │ → GeneticAlgorithm with BitFlipMutation +
│ │ → GA with BitFlipMutation +
│ │ a bit-string crossover
│ │
│ ├─ Decision is Vec<usize> (permutation, e.g., TSP)
│ │ → AntColonyTsp (with a distance matrix)
│ │ → TabuSearch (with your own neighbor function)
│ │ → SimulatedAnnealing with SwapMutation
│ │ → Ant Colony (with a distance matrix)
│ │ → Tabu Search (with your own neighbor function)
│ │ → Simulated Annealing with SwapMutation
│ │
│ └─ Custom decision type (a struct, a tree, …)
│ → SimulatedAnnealing or HillClimber
│ → Simulated Annealing or Hill Climber
│ with your own Variation impl
├─ 2 or 3 (multi-objective)
│ │
│ ├─ Strong default, fast, well-understood
│ │ → Nsga2
│ │ → NSGA-II
│ │
│ ├─ Real-valued, smooth front, want best convergence
│ │ → Mopso (multi-objective PSO; on the benches
│ │ → MOPSO (multi-objective PSO; on the benches
│ │ here it wins ZDT1 on both HV and
│ │ convergence by 100× over the
│ │ dominance-based methods)
│ │
│ ├─ Want better front quality than NSGA-II
│ │ → Ibea (indicator-based; consistently the best
│ │ → IBEA (indicator-based; consistently the best
│ │ of the dominance-based methods on these
│ │ benches — wins ZDT3 HV and DTLZ2 mean
│ │ dist by 24×)
│ │ → Spea2 (strength + density)
│ │ → SmsEmoa (hypervolume-contribution selection;
│ │ → SPEA2 (strength + density)
│ │ → SMS-EMOA (hypervolume-contribution selection;
│ │ elegant in theory but underperforms
│ │ NSGA-II on these benches at our budgets —
│ │ only worth its higher per-step cost on
@@ -420,42 +446,42 @@ START
│ │ the right discriminator)
│ │
│ ├─ Want decomposition / weight-vector style
│ │ → Moead (very fast per generation, scales well)
│ │ → MOEA/D (very fast per generation, scales well)
│ │
│ ├─ Disconnected or non-convex front
│ │ → AgeMoea (estimates front geometry adaptively)
│ │ → Knea (favors knee points)
│ │ → Ibea
│ │ → AGE-MOEA (estimates front geometry adaptively)
│ │ → KnEA (favors knee points)
│ │ → IBEA
│ │
│ ├─ Want region-based diversity
│ │ → PesaII (grid hyperboxes drive selection)
│ │ → EpsilonMoea (ε-grid archive,
│ │ archive size auto-limits)
│ │ → PESA-II (grid hyperboxes drive selection)
│ │ → ε-MOEA (ε-grid archive,
│ │ archive size auto-limits)
│ │
│ └─ Just one starting decision (no population budget)
│ → Paes (1+1 ES with a Pareto archive)
│ → PAES (1+1 ES with a Pareto archive)
└─ 4+ (many-objective)
├─ Linear / simplex-shaped front (e.g., DTLZ1)
│ → Grea (grid coords drive ranking; on DTLZ1
│ → GrEA (grid coords drive ranking; on DTLZ1
│ here it beats NSGA-III by 3× and
│ AGE-MOEA by 2.5×)
│ → Moead (decomposition shines on linear fronts;
│ → MOEA/D (decomposition shines on linear fronts;
│ second on DTLZ1, also among the
│ fastest per generation)
├─ Curved / unknown front geometry
│ → Nsga3 (reference-point niching, canonical;
│ → NSGA-III (reference-point niching, canonical;
│ a strong default when the front
│ isn't simplex-shaped)
│ → AgeMoea (estimates L_p geometry per generation)
│ → Rvea (reference vectors with adaptive penalty)
│ → AGE-MOEA (estimates L_p geometry per generation)
│ → RVEA (reference vectors with adaptive penalty)
├─ Want indicator-based selection
│ → Ibea (additive ε-indicator; doesn't degrade
│ → IBEA (additive ε-indicator; doesn't degrade
│ at high obj count)
│ → Hype (Monte Carlo HV estimation; scales
│ → HypE (Monte Carlo HV estimation; scales
│ to arbitrary M)
```
@@ -465,54 +491,54 @@ START
| Algorithm | Objectives | Decision | Strengths |
|---|---|---|---|
| `BayesianOpt` | 1 | `Vec<f64>` | GP surrogate + EI; gold standard *with* per-problem kernel tuning (default RBF at 60 evals is honestly bad) |
| `Tpe` | 1 | `Vec<f64>` | KDE surrogate; robust without hyperparameter tuning |
| `Hyperband` | 1 | any | multi-fidelity; needs `PartialProblem` |
| **Bayesian Optimization** | 1 | `Vec<f64>` | GP surrogate + EI; gold standard *with* per-problem kernel tuning (default RBF at 60 evals is honestly bad) |
| **TPE** | 1 | `Vec<f64>` | KDE surrogate; robust without hyperparameter tuning |
| **Hyperband** | 1 | any | multi-fidelity; needs `PartialProblem` |
**Single-objective continuous (`Vec<f64>`):**
| Algorithm | Strengths |
|---|---|
| `RandomSearch` | sanity baseline |
| `HillClimber` | simplest greedy local search |
| `OnePlusOneEs` | one-fifth-rule self-adapting baseline |
| `SimulatedAnnealing` | escapes local optima |
| `GeneticAlgorithm` | classic SO GA with elitism |
| `ParticleSwarm` | simple swarm baseline |
| `DifferentialEvolution` | strong default for cheap continuous |
| `Tlbo` | parameter-free (no F, CR, w, σ) |
| `CmaEs` | smooth landscapes; full covariance |
| `IpopCmaEs` | CMA-ES + restart for multimodal |
| `SeparableNes` | diagonal-cov NES; cheap per-step |
| `NelderMead` | classical simplex; deterministic |
| **Random Search** | sanity baseline |
| **Hill Climber** | simplest greedy local search |
| **(1+1)-ES** | one-fifth-rule self-adapting baseline |
| **Simulated Annealing** | escapes local optima |
| **GA** | classic SO GA with elitism |
| **PSO** | simple swarm baseline |
| **Differential Evolution** | strong default for cheap continuous |
| **TLBO** | parameter-free (no F, CR, w, σ) |
| **CMA-ES** | smooth landscapes; full covariance |
| **IPOP-CMA-ES** | CMA-ES + restart for multimodal |
| **sNES** | diagonal-cov NES; cheap per-step |
| **Nelder-Mead** | classical simplex; deterministic |
**Single-objective other decision types:**
| Algorithm | Decision | Strengths |
|---|---|---|
| `Umda` | `Vec<bool>` | independent-bit EDA |
| `TabuSearch` | any | discrete, you supply neighbors |
| `AntColonyTsp` | `Vec<usize>` | TSP / permutation |
| **UMDA** | `Vec<bool>` | independent-bit EDA |
| **Tabu Search** | any | discrete, you supply neighbors |
| **Ant Colony** | `Vec<usize>` | TSP / permutation |
**Multi-objective (23) and many-objective (4+):**
| Algorithm | Objectives | Strengths |
|---|---|---|
| `Paes` | 23 | 1+1 ES with Pareto archive |
| `Nsga2` | 23 | canonical Pareto-based EA |
| `Spea2` | 23 | strength + density |
| `Mopso` | 23 | multi-objective PSO; best convergence on smooth real-valued 2-obj fronts |
| `Ibea` | 2+ | indicator-based; consistently best of the dominance-based methods |
| `SmsEmoa` | 2+ | exact HV-contribution selection; high per-step cost, modest gain |
| `Hype` | 2+ | Monte Carlo HV estimation |
| `EpsilonMoea` | 2+ | ε-grid archive; auto-sized |
| `PesaII` | 2+ | grid-based region selection |
| `AgeMoea` | 2+ | adaptive front-geometry estimation |
| `Knea` | 2+ | knee-point favored survival |
| `Moead` | 2+ | decomposition; fast per-gen |
| `Nsga3` | 4+ | reference-point niching; strong on curved fronts |
| `Rvea` | 4+ | reference vectors with penalty |
| `Grea` | 4+ | grid coords drive selection; particularly strong on linear/simplex fronts |
| **PAES** | 23 | 1+1 ES with Pareto archive |
| **NSGA-II** | 23 | canonical Pareto-based EA |
| **SPEA2** | 23 | strength + density |
| **MOPSO** | 23 | multi-objective PSO; best convergence on smooth real-valued 2-obj fronts |
| **IBEA** | 2+ | indicator-based; consistently best of the dominance-based methods |
| **SMS-EMOA** | 2+ | exact HV-contribution selection; high per-step cost, modest gain |
| **HypE** | 2+ | Monte Carlo HV estimation |
| **ε-MOEA** | 2+ | ε-grid archive; auto-sized |
| **PESA-II** | 2+ | grid-based region selection |
| **AGE-MOEA** | 2+ | adaptive front-geometry estimation |
| **KnEA** | 2+ | knee-point favored survival |
| **MOEA/D** | 2+ | decomposition; fast per-gen |
| **NSGA-III** | 4+ | reference-point niching; strong on curved fronts |
| **RVEA** | 4+ | reference vectors with penalty |
| **GrEA** | 4+ | grid coords drive selection; particularly strong on linear/simplex fronts |
## Current algorithms
@@ -520,48 +546,48 @@ The full list with one-line descriptions:
**Sample-efficient / multi-fidelity:**
- `BayesianOpt` — Gaussian-process surrogate + Expected Improvement.
- `Tpe` — Bergstra et al. 2011 Tree-structured Parzen Estimator.
- `Hyperband` — Li et al. 2017 multi-fidelity (uses `PartialProblem`).
- **Bayesian Optimization** — Gaussian-process surrogate + Expected Improvement.
- **TPE** — Bergstra et al. 2011 Tree-structured Parzen Estimator.
- **Hyperband** — Li et al. 2017 multi-fidelity (uses `PartialProblem`).
**Single-objective:**
- `RandomSearch` — sample-evaluate-keep baseline.
- `HillClimber` — greedy single-step local search.
- `OnePlusOneEs` — Rechenberg 1973 (1+1)-ES with one-fifth rule.
- `SimulatedAnnealing` — Kirkpatrick et al. 1983, generic over decision type.
- `TabuSearch` — Glover 1986, with a user-supplied neighbor generator.
- `GeneticAlgorithm` — generational GA with tournament selection + elitism.
- `ParticleSwarm` — Eberhart & Kennedy 1995 PSO for `Vec<f64>`.
- `DifferentialEvolution` — Storn & Price DE/rand/1/bin for `Vec<f64>`.
- `Tlbo` — Rao 2011 Teaching-Learning-Based Optimization (parameter-free).
- `CmaEs` — Hansen & Ostermeier 2001 covariance-matrix adaptation.
- `IpopCmaEs` — Auger & Hansen 2005 CMA-ES with restart, for multimodal.
- `SeparableNes` — Wierstra et al. 2008/2014 diagonal-cov NES.
- `NelderMead` — Nelder & Mead 1965 simplex direct search.
- `Umda` — Mühlenbein 1997 univariate marginal-distribution EDA for `Vec<bool>`.
- `AntColonyTsp` — Dorigo Ant System for permutation problems.
- **Random Search** — sample-evaluate-keep baseline.
- **Hill Climber** — greedy single-step local search.
- **(1+1)-ES** — Rechenberg 1973 (1+1)-ES with one-fifth rule.
- **Simulated Annealing** — Kirkpatrick et al. 1983, generic over decision type.
- **Tabu Search** — Glover 1986, with a user-supplied neighbor generator.
- **GA** — generational GA with tournament selection + elitism.
- **PSO** — Eberhart & Kennedy 1995 PSO for `Vec<f64>`.
- **Differential Evolution** — Storn & Price DE/rand/1/bin for `Vec<f64>`.
- **TLBO** — Rao 2011 Teaching-Learning-Based Optimization (parameter-free).
- **CMA-ES** — Hansen & Ostermeier 2001 covariance-matrix adaptation.
- **IPOP-CMA-ES** — Auger & Hansen 2005 CMA-ES with restart, for multimodal.
- **sNES** — Wierstra et al. 2008/2014 diagonal-cov NES.
- **Nelder-Mead** — Nelder & Mead 1965 simplex direct search.
- **UMDA** — Mühlenbein 1997 univariate marginal-distribution EDA for `Vec<bool>`.
- **Ant Colony** — Dorigo Ant System for permutation problems.
**Multi-objective:**
- `Paes` — Knowles & Corne 1999 Pareto Archived Evolution Strategy.
- `Nsga2` — Deb et al. 2002, the canonical Pareto-based EA.
- `Spea2` — Zitzler, Laumanns & Thiele 2001 strength-Pareto EA.
- `Moead` — Zhang & Li 2007 decomposition-based MOEA with Tchebycheff scalarization.
- `Mopso` — Coello, Pulido & Lechuga 2004 multi-objective PSO.
- `Ibea` — Zitzler & Künzli 2004 indicator-based EA.
- `SmsEmoa` — Beume, Naujoks & Emmerich 2007 hypervolume-selection EMOA.
- `Hype` — Bader & Zitzler 2011 Hypervolume Estimation Algorithm.
- `EpsilonMoea` — Deb, Mohan & Mishra 2003 ε-dominance MOEA.
- `PesaII` — Corne et al. 2001 Pareto Envelope Selection II.
- `AgeMoea` — Panichella 2019 Adaptive Geometry Estimation MOEA.
- `Knea` — Zhang, Tian & Jin 2015 Knee point-driven EA.
- **PAES** — Knowles & Corne 1999 Pareto Archived Evolution Strategy.
- **NSGA-II** — Deb et al. 2002, the canonical Pareto-based EA.
- **SPEA2** — Zitzler, Laumanns & Thiele 2001 strength-Pareto EA.
- **MOEA/D** — Zhang & Li 2007 decomposition-based MOEA with Tchebycheff scalarization.
- **MOPSO** — Coello, Pulido & Lechuga 2004 multi-objective PSO.
- **IBEA** — Zitzler & Künzli 2004 indicator-based EA.
- **SMS-EMOA** — Beume, Naujoks & Emmerich 2007 hypervolume-selection EMOA.
- **HypE** — Bader & Zitzler 2011 Hypervolume Estimation Algorithm.
- **ε-MOEA** — Deb, Mohan & Mishra 2003 ε-dominance MOEA.
- **PESA-II** — Corne et al. 2001 Pareto Envelope Selection II.
- **AGE-MOEA** — Panichella 2019 Adaptive Geometry Estimation MOEA.
- **KnEA** — Zhang, Tian & Jin 2015 Knee point-driven EA.
**Many-objective (4+):**
- `Nsga3` — Deb & Jain 2014 reference-point NSGA-III.
- `Rvea` — Cheng et al. 2016 Reference Vector-guided EA.
- `Grea` — Yang et al. 2013 Grid-based EA.
- **NSGA-III** — Deb & Jain 2014 reference-point NSGA-III.
- **RVEA** — Cheng et al. 2016 Reference Vector-guided EA.
- **GrEA** — Yang et al. 2013 Grid-based EA.
**Reusable utilities:** `pareto_compare`, `pareto_front`, `best_candidate`,
`non_dominated_sort`, `crowding_distance`, `ParetoArchive`, `das_dennis`,
@@ -576,7 +602,7 @@ and the metrics `spacing` and `hypervolume_2d`.
user-facing APIs, no generic-RNG plumbing — `Rng` is a single concrete type
alias.
- **Readable algorithms.** Built-ins are written for clarity, not maximum
abstraction reuse. `RandomSearch` is the recommended file to read before
abstraction reuse. Random Search is the recommended file to read before
writing your own optimizer.
- **One crate first.** No premature splitting into `-core`/`-algorithms`/
`-operators`. Split later if the crate grows.
+2 -2
View File
@@ -8,8 +8,8 @@ needed.
| Version | Supported |
|---------|--------------------|
| 0.8.x | ✅ |
| ≤ 0.7.x | ❌ (please upgrade) |
| 0.10.x | ✅ |
| ≤ 0.9.x | ❌ (please upgrade) |
heuropt is pre-1.0; the public API may change between minor versions.
Once 1.0.0 ships, the support window will be at least the latest two
+1
View File
@@ -18,6 +18,7 @@
- [Optimize a permutation (TSP-style)](./cookbook/permutation.md)
- [Constrain your search with `Repair`](./cookbook/constraints.md)
- [Pick one answer off a Pareto front](./cookbook/pick-one.md)
- [Explore your results in a webapp](./cookbook/explorer.md)
- [Write your own algorithm](./cookbook/custom-optimizer.md)
# Reference
+87 -87
View File
@@ -17,9 +17,9 @@ after it.
For the cheap-eval branch, you have the run of the catalog. For the
expensive branch, classical evolutionary methods waste your evaluation
budget — go to [`BayesianOpt`] or [`Tpe`]. For the *very* expensive
budget — go to [Bayesian Optimization][BayesianOpt] or [TPE]. For the *very* expensive
branch where each eval has a tunable budget (epochs, MC samples, sim
steps), [`Hyperband`] over the [`PartialProblem`] trait is the move.
steps), [Hyperband] over the [`PartialProblem`] trait is the move.
## Step 1: How many objectives?
@@ -51,48 +51,48 @@ These all take `Vec<f64>` decisions.
### Smooth, low-to-moderate dimension
[`CmaEs`] is the strong default. It adapts the search distribution's
[CMA-ES][CmaEs] is the strong default. It adapts the search distribution's
covariance to the local landscape. On the comparison harness it
hits machine epsilon on Rosenbrock at 30 000 evaluations.
For very low-dimensional smooth problems (≤ 5 dim), [`NelderMead`] is
For very low-dimensional smooth problems (≤ 5 dim), [Nelder-Mead][NelderMead] is
deterministic and converges to f = 0 exactly on Rosenbrock.
### High dimension, smooth
[`SeparableNes`] uses a diagonal covariance — cheaper per step than
CmaEs at the cost of being unable to model rotated landscapes. Worth
trying when CmaEs's `O(d²)` per-step cost hurts.
[sNES][SeparableNes] uses a diagonal covariance — cheaper per step than
CMA-ES at the cost of being unable to model rotated landscapes. Worth
trying when CMA-ES's `O(d²)` per-step cost hurts.
### Multimodal landscapes
Multimodal = many local minima that aren't the global one. Rastrigin
and Ackley are classic traps.
[`IpopCmaEs`] is CmaEs with an increasing-population restart strategy
specifically designed for this. On the harness it drops vanilla CmaEs's
[IPOP-CMA-ES][IpopCmaEs] is CMA-ES with an increasing-population restart strategy
specifically designed for this. On the harness it drops vanilla CMA-ES's
Rastrigin score from f = 2.35 to f = 0.13.
[`DifferentialEvolution`] is rarely beaten on cheap multimodal
[Differential Evolution][DifferentialEvolution] is rarely beaten on cheap multimodal
continuous problems. On Rastrigin it ties with `(1+1)-ES` at f = 0.
[`SimulatedAnnealing`] is a cheap, generic baseline that escapes local
[Simulated Annealing][SimulatedAnnealing] is a cheap, generic baseline that escapes local
optima via temperature decay.
### Want parameter-free
[`Tlbo`] (Teaching-Learning-Based Optimization) has no `F`, `CR`, `w`,
[TLBO][Tlbo] (Teaching-Learning-Based Optimization) has no `F`, `CR`, `w`,
or `σ` to tune. Often a respectable middle-of-the-pack performer.
### Smallest possible self-adapting baseline
[`OnePlusOneEs`] — Rechenberg's 1973 `(1+1)`-ES with the one-fifth
[(1+1)-ES][OnePlusOneEs] — Rechenberg's 1973 `(1+1)`-ES with the one-fifth
success rule. On the harness it hits f = 0 on Rastrigin in 50 000
evaluations.
### Just want a baseline
[`RandomSearch`]. Useful as a sanity check: if your fancy optimizer
[Random Search][RandomSearch]. Useful as a sanity check: if your fancy optimizer
can't beat random search, something is wrong (with the fancy
optimizer or with the problem).
@@ -100,69 +100,69 @@ optimizer or with the problem).
| Decision type | Algorithm | Notes |
|---|---|---|
| `Vec<bool>` | [`Umda`] | Per-bit marginal EDA. Independent-bit assumption. |
| `Vec<bool>` | [`GeneticAlgorithm`] + [`BitFlipMutation`] | When bit interactions matter. |
| `Vec<usize>` (permutation) | [`AntColonyTsp`] | TSP-style with a distance matrix. |
| `Vec<usize>` (permutation) | [`SimulatedAnnealing`] + [`SwapMutation`] | Generic discrete baseline. |
| `Vec<usize>` or custom | [`TabuSearch`] | You supply the neighbor function. |
| Custom struct | [`SimulatedAnnealing`] / [`HillClimber`] | With your own `Variation` impl. |
| `Vec<bool>` | [UMDA][Umda] | Per-bit marginal EDA. Independent-bit assumption. |
| `Vec<bool>` | [GA][GeneticAlgorithm] + [`BitFlipMutation`] | When bit interactions matter. |
| `Vec<usize>` (permutation) | [Ant Colony][AntColonyTsp] | TSP-style with a distance matrix. |
| `Vec<usize>` (permutation) | [Simulated Annealing][SimulatedAnnealing] + [`SwapMutation`] | Generic discrete baseline. |
| `Vec<usize>` or custom | [Tabu Search][TabuSearch] | You supply the neighbor function. |
| Custom struct | [Simulated Annealing][SimulatedAnnealing] / [Hill Climber][HillClimber] | With your own `Variation` impl. |
## Step 2 — multi-objective (2 or 3)
### Strong default
[`Nsga2`] is the canonical Pareto-based EA. Fast, well-understood,
[NSGA-II][Nsga2] is the canonical Pareto-based EA. Fast, well-understood,
maintains diversity via crowding distance. On the harness it lands
on the Pareto front of every test problem.
### Real-valued, smooth front, want best convergence
[`Mopso`] (multi-objective PSO with archive). On ZDT1 it wins
[MOPSO][Mopso] (multi-objective PSO with archive). On ZDT1 it wins
hypervolume outright and converges 100× tighter than the
dominance-based methods.
### Better front quality than NSGA-II
[`Ibea`] (indicator-based) is consistently the best of the
[IBEA][Ibea] (indicator-based) is consistently the best of the
dominance-based methods on the harness — wins ZDT3 hypervolume and
DTLZ2 mean distance by 24×. It uses an additive ε-indicator for
selection rather than dominance + crowding.
[`Spea2`] (strength + density) — solid alternative; explicit external
[SPEA2][Spea2] (strength + density) — solid alternative; explicit external
archive separate from the population.
[`SmsEmoa`] uses exact hypervolume contribution for selection. Elegant
[SMS-EMOA][SmsEmoa] uses exact hypervolume contribution for selection. Elegant
in theory; in practice on the harness budgets here it underperforms
NSGA-II. Worth the higher per-step cost only when exact HV
contribution is the right discriminator.
### Decomposition / weight-vector style
[`Moead`] decomposes the multi-objective problem into many scalar
[MOEA/D][Moead] decomposes the multi-objective problem into many scalar
sub-problems (Tchebycheff or weighted sum) and solves them in
parallel. Very fast per generation; scales naturally to many
objectives.
### Disconnected or non-convex front
[`AgeMoea`] estimates the front geometry adaptively (the L_p
[AGE-MOEA][AgeMoea] estimates the front geometry adaptively (the L_p
parameter `p` is fit from data each generation).
[`Knea`] favors knee points — the regions of the front where small
[KnEA][Knea] favors knee points — the regions of the front where small
gains in one objective cost large losses in another.
[`Ibea`] also handles disconnected fronts well.
[IBEA][Ibea] also handles disconnected fronts well.
### Region-based diversity
[`PesaII`] uses grid hyperboxes to drive selection — divide the
[PESA-II][PesaII] uses grid hyperboxes to drive selection — divide the
objective space into a grid, pick from the least-crowded boxes.
[`EpsilonMoea`] uses an ε-grid archive that auto-limits its size.
[ε-MOEA][EpsilonMoea] uses an ε-grid archive that auto-limits its size.
### Just one starting decision (no population budget)
[`Paes`] — `(1+1)`-ES with a Pareto archive. Cheap, simple, useful
[PAES][Paes] — `(1+1)`-ES with a Pareto archive. Cheap, simple, useful
when your evaluations are expensive enough that you can't afford a
population.
@@ -170,26 +170,26 @@ population.
### Linear / simplex-shaped front (e.g., DTLZ1)
[`Grea`] — grid coords drive ranking. On DTLZ1 it beats NSGA-III by
[GrEA][Grea] — grid coords drive ranking. On DTLZ1 it beats NSGA-III by
3× and AGE-MOEA by 2.5×.
[`Moead`] — decomposition shines on linear fronts; second on DTLZ1
[MOEA/D][Moead] — decomposition shines on linear fronts; second on DTLZ1
and among the fastest per generation.
### Curved / unknown front geometry
[`Nsga3`] — reference-point niching; canonical many-objective method;
[NSGA-III][Nsga3] — reference-point niching; canonical many-objective method;
strong default when the front isn't simplex-shaped.
[`AgeMoea`] — estimates L_p geometry per generation.
[AGE-MOEA][AgeMoea] — estimates L_p geometry per generation.
[`Rvea`] — reference vectors with adaptive penalty.
[RVEA][Rvea] — reference vectors with adaptive penalty.
### Indicator-based selection
[`Ibea`] — additive ε-indicator; doesn't degrade at high obj count.
[IBEA][Ibea] — additive ε-indicator; doesn't degrade at high obj count.
[`HypE`] — Monte Carlo hypervolume estimation; scales to arbitrary
[HypE][Hype] — Monte Carlo hypervolume estimation; scales to arbitrary
objective count where exact HV is too expensive.
## Step 3: Are there hard constraints?
@@ -216,13 +216,13 @@ for worked examples.
## Step 4: Should you parallelize?
Enable the `parallel` feature flag if your `evaluate` takes more
than ~50 µs. Population-based algorithms ([`RandomSearch`], [`Nsga2`],
[`DifferentialEvolution`], [`Spea2`], [`Ibea`], [`Mopso`], …) batch-
than ~50 µs. Population-based algorithms ([Random Search][RandomSearch], [NSGA-II][Nsga2],
[Differential Evolution][DifferentialEvolution], [SPEA2][Spea2], [IBEA][Ibea], [MOPSO][Mopso], …) batch-
evaluate via rayon when the feature is on. **Seeded runs stay
bit-identical** to serial mode.
```toml
heuropt = { version = "0.8", features = ["parallel"] }
heuropt = { version = "0.10", features = ["parallel"] }
```
If your evaluation is **IO-bound** (HTTP request, RPC, subprocess)
@@ -235,55 +235,55 @@ method on every algorithm in the catalog. See the
| Situation | Pick |
|---|---|
| Smooth single-objective continuous | [`CmaEs`] |
| Multimodal single-objective continuous | [`IpopCmaEs`] or [`DifferentialEvolution`] |
| Expensive single-objective | [`BayesianOpt`] or [`Tpe`] |
| Multi-fidelity single-objective | [`Hyperband`] |
| 2- or 3-objective default | [`Nsga2`] |
| 2-objective real-valued smooth front | [`Mopso`] |
| Disconnected / non-convex front | [`Ibea`] |
| Many-objective default (curved front) | [`Nsga3`] |
| Many-objective linear / simplex front | [`Grea`] |
| Permutation problem | [`AntColonyTsp`] |
| Binary problem | [`Umda`] |
| Custom decision type | [`SimulatedAnnealing`] + your `Variation` |
| Sanity baseline | [`RandomSearch`] |
| Smooth single-objective continuous | [CMA-ES][CmaEs] |
| Multimodal single-objective continuous | [IPOP-CMA-ES][IpopCmaEs] or [Differential Evolution][DifferentialEvolution] |
| Expensive single-objective | [Bayesian Optimization][BayesianOpt] or [TPE] |
| Multi-fidelity single-objective | [Hyperband] |
| 2- or 3-objective default | [NSGA-II][Nsga2] |
| 2-objective real-valued smooth front | [MOPSO][Mopso] |
| Disconnected / non-convex front | [IBEA][Ibea] |
| Many-objective default (curved front) | [NSGA-III][Nsga3] |
| Many-objective linear / simplex front | [GrEA][Grea] |
| Permutation problem | [Ant Colony][AntColonyTsp] |
| Binary problem | [UMDA][Umda] |
| Custom decision type | [Simulated Annealing][SimulatedAnnealing] + your `Variation` |
| Sanity baseline | [Random Search][RandomSearch] |
[`CmaEs`]: https://docs.rs/heuropt/latest/heuropt/algorithms/cma_es/struct.CmaEs.html
[`IpopCmaEs`]: https://docs.rs/heuropt/latest/heuropt/algorithms/ipop_cma_es/struct.IpopCmaEs.html
[`SeparableNes`]: https://docs.rs/heuropt/latest/heuropt/algorithms/snes/struct.SeparableNes.html
[`NelderMead`]: https://docs.rs/heuropt/latest/heuropt/algorithms/nelder_mead/struct.NelderMead.html
[`DifferentialEvolution`]: https://docs.rs/heuropt/latest/heuropt/algorithms/differential_evolution/struct.DifferentialEvolution.html
[`SimulatedAnnealing`]: https://docs.rs/heuropt/latest/heuropt/algorithms/simulated_annealing/struct.SimulatedAnnealing.html
[`Tlbo`]: https://docs.rs/heuropt/latest/heuropt/algorithms/tlbo/struct.Tlbo.html
[`OnePlusOneEs`]: https://docs.rs/heuropt/latest/heuropt/algorithms/one_plus_one_es/struct.OnePlusOneEs.html
[`RandomSearch`]: https://docs.rs/heuropt/latest/heuropt/algorithms/random_search/struct.RandomSearch.html
[`HillClimber`]: https://docs.rs/heuropt/latest/heuropt/algorithms/hill_climber/struct.HillClimber.html
[`BayesianOpt`]: https://docs.rs/heuropt/latest/heuropt/algorithms/bayesian_opt/struct.BayesianOpt.html
[`Tpe`]: https://docs.rs/heuropt/latest/heuropt/algorithms/tpe/struct.Tpe.html
[`Hyperband`]: https://docs.rs/heuropt/latest/heuropt/algorithms/hyperband/struct.Hyperband.html
[CmaEs]: https://docs.rs/heuropt/latest/heuropt/algorithms/cma_es/struct.CmaEs.html
[IpopCmaEs]: https://docs.rs/heuropt/latest/heuropt/algorithms/ipop_cma_es/struct.IpopCmaEs.html
[SeparableNes]: https://docs.rs/heuropt/latest/heuropt/algorithms/snes/struct.SeparableNes.html
[NelderMead]: https://docs.rs/heuropt/latest/heuropt/algorithms/nelder_mead/struct.NelderMead.html
[DifferentialEvolution]: https://docs.rs/heuropt/latest/heuropt/algorithms/differential_evolution/struct.DifferentialEvolution.html
[SimulatedAnnealing]: https://docs.rs/heuropt/latest/heuropt/algorithms/simulated_annealing/struct.SimulatedAnnealing.html
[Tlbo]: https://docs.rs/heuropt/latest/heuropt/algorithms/tlbo/struct.Tlbo.html
[OnePlusOneEs]: https://docs.rs/heuropt/latest/heuropt/algorithms/one_plus_one_es/struct.OnePlusOneEs.html
[RandomSearch]: https://docs.rs/heuropt/latest/heuropt/algorithms/random_search/struct.RandomSearch.html
[HillClimber]: https://docs.rs/heuropt/latest/heuropt/algorithms/hill_climber/struct.HillClimber.html
[BayesianOpt]: https://docs.rs/heuropt/latest/heuropt/algorithms/bayesian_opt/struct.BayesianOpt.html
[TPE]: https://docs.rs/heuropt/latest/heuropt/algorithms/tpe/struct.Tpe.html
[Hyperband]: https://docs.rs/heuropt/latest/heuropt/algorithms/hyperband/struct.Hyperband.html
[`PartialProblem`]: https://docs.rs/heuropt/latest/heuropt/core/partial_problem/trait.PartialProblem.html
[`Umda`]: https://docs.rs/heuropt/latest/heuropt/algorithms/umda/struct.Umda.html
[`GeneticAlgorithm`]: https://docs.rs/heuropt/latest/heuropt/algorithms/genetic_algorithm/struct.GeneticAlgorithm.html
[Umda]: https://docs.rs/heuropt/latest/heuropt/algorithms/umda/struct.Umda.html
[GeneticAlgorithm]: https://docs.rs/heuropt/latest/heuropt/algorithms/genetic_algorithm/struct.GeneticAlgorithm.html
[`BitFlipMutation`]: https://docs.rs/heuropt/latest/heuropt/operators/struct.BitFlipMutation.html
[`AntColonyTsp`]: https://docs.rs/heuropt/latest/heuropt/algorithms/ant_colony_tsp/struct.AntColonyTsp.html
[AntColonyTsp]: https://docs.rs/heuropt/latest/heuropt/algorithms/ant_colony_tsp/struct.AntColonyTsp.html
[`SwapMutation`]: https://docs.rs/heuropt/latest/heuropt/operators/struct.SwapMutation.html
[`TabuSearch`]: https://docs.rs/heuropt/latest/heuropt/algorithms/tabu_search/struct.TabuSearch.html
[`Nsga2`]: https://docs.rs/heuropt/latest/heuropt/algorithms/nsga2/struct.Nsga2.html
[`Nsga3`]: https://docs.rs/heuropt/latest/heuropt/algorithms/nsga3/struct.Nsga3.html
[`Mopso`]: https://docs.rs/heuropt/latest/heuropt/algorithms/mopso/struct.Mopso.html
[`Ibea`]: https://docs.rs/heuropt/latest/heuropt/algorithms/ibea/struct.Ibea.html
[`Spea2`]: https://docs.rs/heuropt/latest/heuropt/algorithms/spea2/struct.Spea2.html
[`SmsEmoa`]: https://docs.rs/heuropt/latest/heuropt/algorithms/sms_emoa/struct.SmsEmoa.html
[`Moead`]: https://docs.rs/heuropt/latest/heuropt/algorithms/moead/struct.Moead.html
[`AgeMoea`]: https://docs.rs/heuropt/latest/heuropt/algorithms/age_moea/struct.AgeMoea.html
[`Knea`]: https://docs.rs/heuropt/latest/heuropt/algorithms/knea/struct.Knea.html
[`PesaII`]: https://docs.rs/heuropt/latest/heuropt/algorithms/pesa2/struct.PesaII.html
[`EpsilonMoea`]: https://docs.rs/heuropt/latest/heuropt/algorithms/epsilon_moea/struct.EpsilonMoea.html
[`Paes`]: https://docs.rs/heuropt/latest/heuropt/algorithms/paes/struct.Paes.html
[`Grea`]: https://docs.rs/heuropt/latest/heuropt/algorithms/grea/struct.Grea.html
[`Rvea`]: https://docs.rs/heuropt/latest/heuropt/algorithms/rvea/struct.Rvea.html
[`HypE`]: https://docs.rs/heuropt/latest/heuropt/algorithms/hype/struct.Hype.html
[TabuSearch]: https://docs.rs/heuropt/latest/heuropt/algorithms/tabu_search/struct.TabuSearch.html
[Nsga2]: https://docs.rs/heuropt/latest/heuropt/algorithms/nsga2/struct.Nsga2.html
[Nsga3]: https://docs.rs/heuropt/latest/heuropt/algorithms/nsga3/struct.Nsga3.html
[Mopso]: https://docs.rs/heuropt/latest/heuropt/algorithms/mopso/struct.Mopso.html
[Ibea]: https://docs.rs/heuropt/latest/heuropt/algorithms/ibea/struct.Ibea.html
[Spea2]: https://docs.rs/heuropt/latest/heuropt/algorithms/spea2/struct.Spea2.html
[SmsEmoa]: https://docs.rs/heuropt/latest/heuropt/algorithms/sms_emoa/struct.SmsEmoa.html
[Moead]: https://docs.rs/heuropt/latest/heuropt/algorithms/moead/struct.Moead.html
[AgeMoea]: https://docs.rs/heuropt/latest/heuropt/algorithms/age_moea/struct.AgeMoea.html
[Knea]: https://docs.rs/heuropt/latest/heuropt/algorithms/knea/struct.Knea.html
[PesaII]: https://docs.rs/heuropt/latest/heuropt/algorithms/pesa2/struct.PesaII.html
[EpsilonMoea]: https://docs.rs/heuropt/latest/heuropt/algorithms/epsilon_moea/struct.EpsilonMoea.html
[Paes]: https://docs.rs/heuropt/latest/heuropt/algorithms/paes/struct.Paes.html
[Grea]: https://docs.rs/heuropt/latest/heuropt/algorithms/grea/struct.Grea.html
[Rvea]: https://docs.rs/heuropt/latest/heuropt/algorithms/rvea/struct.Rvea.html
[Hype]: https://docs.rs/heuropt/latest/heuropt/algorithms/hype/struct.Hype.html
[`Repair<D>`]: https://docs.rs/heuropt/latest/heuropt/traits/trait.Repair.html
[`ClampToBounds`]: https://docs.rs/heuropt/latest/heuropt/operators/struct.ClampToBounds.html
[`ProjectToSimplex`]: https://docs.rs/heuropt/latest/heuropt/operators/struct.ProjectToSimplex.html
+5 -5
View File
@@ -15,7 +15,7 @@ The columns:
| Library | Lang | Algorithms | Multi-obj | Surrogates | Determinism | Async |
|---|---|---|---|---|---|---|
| **heuropt 0.8** | Rust | 33 | ✅ NSGA-II/III, SPEA2, IBEA, MOEA/D, MOPSO, SMS-EMOA, HypE, AGE-MOEA, GrEA, KnEA, RVEA, PESA-II, ε-MOEA, PAES | ✅ BO, TPE, Hyperband | ✅ bit-identical seeded | ✅ `AsyncProblem` + `run_async` on every algorithm |
| **heuropt 0.10** | Rust | 33 | ✅ NSGA-II/III, SPEA2, IBEA, MOEA/D, MOPSO, SMS-EMOA, HypE, AGE-MOEA, GrEA, KnEA, RVEA, PESA-II, ε-MOEA, PAES | ✅ BO, TPE, Hyperband | ✅ bit-identical seeded | ✅ `AsyncProblem` + `run_async` on every algorithm |
| pymoo | Python | ~25 | ✅ extensive | partial (BO via plug-ins) | ✅ | ❌ |
| DEAP | Python | flexible toolbox | ✅ | ❌ | ✅ | ❌ |
| hyperopt | Python | TPE-focused | ❌ | ✅ TPE | partial | partial |
@@ -36,7 +36,7 @@ The columns:
otherwise.
- You want a **small, readable codebase** — every algorithm is
written for clarity, no trait-object plumbing, no GATs in user-
facing APIs. Reading `RandomSearch` should be enough to write a
facing APIs. Reading Random Search should be enough to write a
new optimizer.
- You have **IO-bound evaluations** — calling an HTTP service, an
RPC, or a subprocess — and want first-class `async fn evaluate`
@@ -64,12 +64,12 @@ heuropt covers the same major Pareto MOEAs as pymoo and MOEA Framework:
NSGA-II/III, SPEA2, IBEA, MOEA/D, MOPSO, SMS-EMOA, HypE, AGE-MOEA,
GrEA, KnEA, RVEA, PESA-II, ε-MOEA, PAES.
The expensive-evaluation regime: BayesianOpt + TPE + Hyperband. This
The expensive-evaluation regime: Bayesian Optimization + TPE + Hyperband. This
is comparable to optuna's coverage but in pure Rust.
The single-objective continuous catalog (CMA-ES, IPOP-CMA-ES, sNES,
DE, PSO, GA, TLBO, (1+1)-ES, NelderMead, RandomSearch, HillClimber,
SimulatedAnnealing) covers the canonical baselines and several modern
DE, PSO, GA, TLBO, (1+1)-ES, Nelder-Mead, Random Search, Hill Climber,
Simulated Annealing) covers the canonical baselines and several modern
variants.
What heuropt does **not** ship that some libraries do:
+6 -2
View File
@@ -14,17 +14,21 @@ project.
optimizer await many evaluations concurrently. The differentiating
feature vs other optimization libraries.
- [Tune a model with expensive evaluations](./cookbook/expensive-evaluations.md)
`BayesianOpt`, `Tpe`, and `Hyperband` for the 50500-eval
— Bayesian Optimization, TPE, and Hyperband for the 50500-eval
regime.
- [Compare two algorithms on your problem](./cookbook/compare.md) —
multi-seed harness pattern straight from `examples/compare.rs`.
- [Optimize a permutation (TSP-style)](./cookbook/permutation.md) —
`AntColonyTsp` with a distance matrix.
Ant Colony with a distance matrix.
- [Constrain your search with `Repair`](./cookbook/constraints.md) —
bounds, simplex projection, custom repair.
- [Pick one answer off a Pareto front](./cookbook/pick-one.md) — the
a-posteriori weighted-decision pattern from the `jiggly_tuning`
example.
- [Explore your results in a webapp](./cookbook/explorer.md) — export
an `OptimizationResult` to JSON and browse it interactively at
[heuropt-explorer](https://swaits.github.io/heuropt-explorer/) —
parallel coordinates, scatter, range filters, weighted ranking.
- [Write your own algorithm](./cookbook/custom-optimizer.md) —
implement `Optimizer<P>` from scratch, à la the
`examples/custom_optimizer.rs` walkthrough.
+3 -3
View File
@@ -13,7 +13,7 @@ evaluation path.
```toml
[dependencies]
heuropt = { version = "0.8", features = ["async"] }
heuropt = { version = "0.10", features = ["async"] }
# Pick whatever async runtime you want; heuropt itself depends only on
# `futures`. The example below uses tokio.
@@ -112,8 +112,8 @@ results back to the algorithm.
## What the worked example shows
`examples/async_eval.rs` runs `RandomSearch` (200 evaluations × 20 ms
each) at `concurrency = 1, 4, 16` and `DifferentialEvolution` at
`examples/async_eval.rs` runs Random Search (200 evaluations × 20 ms
each) at `concurrency = 1, 4, 16` and Differential Evolution at
`concurrency = 8`. On a recent machine:
```text
@@ -7,9 +7,9 @@ algorithms aimed at this regime.
| Algorithm | Surrogate | Best for |
|---|---|---|
| [`BayesianOpt`] | Gaussian process + Expected Improvement | The textbook choice; needs kernel tuning to shine |
| [`Tpe`] | Kernel-density estimate of good vs bad points | Cheaper per step; more robust without tuning |
| [`Hyperband`] | (none — it's a multi-fidelity scheduler) | When each eval has a tunable budget (epochs, MC samples) |
| [Bayesian Optimization][BayesianOpt] | Gaussian process + Expected Improvement | The textbook choice; needs kernel tuning to shine |
| [TPE] | Kernel-density estimate of good vs bad points | Cheaper per step; more robust without tuning |
| [Hyperband] | (none — it's a multi-fidelity scheduler) | When each eval has a tunable budget (epochs, MC samples) |
## When each is right
@@ -101,7 +101,7 @@ canonical Bergstra value.
## Hyperband
[`Hyperband`] needs your problem to implement [`PartialProblem`] —
[Hyperband] needs your problem to implement [`PartialProblem`] —
that is, you can evaluate at a tunable fidelity (e.g. number of
training epochs). The algorithm schedules many cheap-fidelity runs
and promotes only the survivors to higher fidelity.
@@ -156,9 +156,9 @@ The state of the art (BOHB) combines BO with Hyperband: TPE picks the
configurations Hyperband then evaluates at increasing fidelity.
heuropt doesn't ship a unified BOHB but the building blocks are
there — wrap your `PartialProblem` with a TPE-driven sampler and
feed the picks into `Hyperband`. PRs welcome.
feed the picks into Hyperband. PRs welcome.
[`BayesianOpt`]: https://docs.rs/heuropt/latest/heuropt/algorithms/bayesian_opt/struct.BayesianOpt.html
[`Tpe`]: https://docs.rs/heuropt/latest/heuropt/algorithms/tpe/struct.Tpe.html
[`Hyperband`]: https://docs.rs/heuropt/latest/heuropt/algorithms/hyperband/struct.Hyperband.html
[BayesianOpt]: https://docs.rs/heuropt/latest/heuropt/algorithms/bayesian_opt/struct.BayesianOpt.html
[TPE]: https://docs.rs/heuropt/latest/heuropt/algorithms/tpe/struct.Tpe.html
[Hyperband]: https://docs.rs/heuropt/latest/heuropt/algorithms/hyperband/struct.Hyperband.html
[`PartialProblem`]: https://docs.rs/heuropt/latest/heuropt/core/partial_problem/trait.PartialProblem.html
+1 -1
View File
@@ -14,7 +14,7 @@ install needed beyond a browser.
```toml
[dependencies]
heuropt = { version = "0.9", features = ["serde"] }
heuropt = { version = "0.10", features = ["serde"] }
```
The export uses `serde_json` under the hood, so the explorer module
+29 -29
View File
@@ -9,7 +9,7 @@ population, and rayon parallelizes that batch.
```toml
[dependencies]
heuropt = { version = "0.8", features = ["parallel"] }
heuropt = { version = "0.10", features = ["parallel"] }
```
There's nothing else to opt into in your code. The
@@ -29,14 +29,14 @@ pass.
Algorithms with a per-generation `evaluate_batch`:
- [`RandomSearch`], [`Nsga2`], [`Nsga3`], [`Spea2`], [`Moead`],
[`Mopso`], [`Ibea`], [`SmsEmoa`], [`HypE`], [`PesaII`],
[`EpsilonMoea`], [`AgeMoea`], [`Knea`], [`Grea`], [`Rvea`].
- [`DifferentialEvolution`] and [`GeneticAlgorithm`] benefit on the
- [Random Search][RandomSearch], [NSGA-II][Nsga2], [NSGA-III][Nsga3], [SPEA2][Spea2], [MOEA/D][Moead],
[MOPSO][Mopso], [IBEA][Ibea], [SMS-EMOA][SmsEmoa], [HypE][Hype], [PESA-II][PesaII],
[ε-MOEA][EpsilonMoea], [AGE-MOEA][AgeMoea], [KnEA][Knea], [GrEA][Grea], [RVEA][Rvea].
- [Differential Evolution][DifferentialEvolution] and [GA][GeneticAlgorithm] benefit on the
initial population and offspring batches.
Steady-state algorithms ([`Paes`], [`SimulatedAnnealing`],
[`HillClimber`], [`OnePlusOneEs`]) only evaluate one or a few
Steady-state algorithms ([PAES][Paes], [Simulated Annealing][SimulatedAnnealing],
[Hill Climber][HillClimber], [(1+1)-ES][OnePlusOneEs]) only evaluate one or a few
candidates per iteration, so the parallel feature gives them
nothing — leave it off if those are your primary optimizers.
@@ -102,7 +102,7 @@ to scope it.
- You're already running multiple seeds in parallel at the harness
level (see [Compare two algorithms](./compare.md)). Stacking
parallelism rarely helps.
- The algorithm is steady-state (Paes, SA, hill climber).
- The algorithm is steady-state (PAES, SA, hill climber).
## `parallel` vs `async`
@@ -114,24 +114,24 @@ to scope it.
Both can be on at once if your evaluation does *both* substantial
CPU work *and* IO. The two features are independent.
[`RandomSearch`]: https://docs.rs/heuropt/latest/heuropt/algorithms/random_search/struct.RandomSearch.html
[`Nsga2`]: https://docs.rs/heuropt/latest/heuropt/algorithms/nsga2/struct.Nsga2.html
[`Nsga3`]: https://docs.rs/heuropt/latest/heuropt/algorithms/nsga3/struct.Nsga3.html
[`Spea2`]: https://docs.rs/heuropt/latest/heuropt/algorithms/spea2/struct.Spea2.html
[`Moead`]: https://docs.rs/heuropt/latest/heuropt/algorithms/moead/struct.Moead.html
[`Mopso`]: https://docs.rs/heuropt/latest/heuropt/algorithms/mopso/struct.Mopso.html
[`Ibea`]: https://docs.rs/heuropt/latest/heuropt/algorithms/ibea/struct.Ibea.html
[`SmsEmoa`]: https://docs.rs/heuropt/latest/heuropt/algorithms/sms_emoa/struct.SmsEmoa.html
[`HypE`]: https://docs.rs/heuropt/latest/heuropt/algorithms/hype/struct.Hype.html
[`PesaII`]: https://docs.rs/heuropt/latest/heuropt/algorithms/pesa2/struct.PesaII.html
[`EpsilonMoea`]: https://docs.rs/heuropt/latest/heuropt/algorithms/epsilon_moea/struct.EpsilonMoea.html
[`AgeMoea`]: https://docs.rs/heuropt/latest/heuropt/algorithms/age_moea/struct.AgeMoea.html
[`Knea`]: https://docs.rs/heuropt/latest/heuropt/algorithms/knea/struct.Knea.html
[`Grea`]: https://docs.rs/heuropt/latest/heuropt/algorithms/grea/struct.Grea.html
[`Rvea`]: https://docs.rs/heuropt/latest/heuropt/algorithms/rvea/struct.Rvea.html
[`DifferentialEvolution`]: https://docs.rs/heuropt/latest/heuropt/algorithms/differential_evolution/struct.DifferentialEvolution.html
[`GeneticAlgorithm`]: https://docs.rs/heuropt/latest/heuropt/algorithms/genetic_algorithm/struct.GeneticAlgorithm.html
[`Paes`]: https://docs.rs/heuropt/latest/heuropt/algorithms/paes/struct.Paes.html
[`SimulatedAnnealing`]: https://docs.rs/heuropt/latest/heuropt/algorithms/simulated_annealing/struct.SimulatedAnnealing.html
[`HillClimber`]: https://docs.rs/heuropt/latest/heuropt/algorithms/hill_climber/struct.HillClimber.html
[`OnePlusOneEs`]: https://docs.rs/heuropt/latest/heuropt/algorithms/one_plus_one_es/struct.OnePlusOneEs.html
[RandomSearch]: https://docs.rs/heuropt/latest/heuropt/algorithms/random_search/struct.RandomSearch.html
[Nsga2]: https://docs.rs/heuropt/latest/heuropt/algorithms/nsga2/struct.Nsga2.html
[Nsga3]: https://docs.rs/heuropt/latest/heuropt/algorithms/nsga3/struct.Nsga3.html
[Spea2]: https://docs.rs/heuropt/latest/heuropt/algorithms/spea2/struct.Spea2.html
[Moead]: https://docs.rs/heuropt/latest/heuropt/algorithms/moead/struct.Moead.html
[Mopso]: https://docs.rs/heuropt/latest/heuropt/algorithms/mopso/struct.Mopso.html
[Ibea]: https://docs.rs/heuropt/latest/heuropt/algorithms/ibea/struct.Ibea.html
[SmsEmoa]: https://docs.rs/heuropt/latest/heuropt/algorithms/sms_emoa/struct.SmsEmoa.html
[Hype]: https://docs.rs/heuropt/latest/heuropt/algorithms/hype/struct.Hype.html
[PesaII]: https://docs.rs/heuropt/latest/heuropt/algorithms/pesa2/struct.PesaII.html
[EpsilonMoea]: https://docs.rs/heuropt/latest/heuropt/algorithms/epsilon_moea/struct.EpsilonMoea.html
[AgeMoea]: https://docs.rs/heuropt/latest/heuropt/algorithms/age_moea/struct.AgeMoea.html
[Knea]: https://docs.rs/heuropt/latest/heuropt/algorithms/knea/struct.Knea.html
[Grea]: https://docs.rs/heuropt/latest/heuropt/algorithms/grea/struct.Grea.html
[Rvea]: https://docs.rs/heuropt/latest/heuropt/algorithms/rvea/struct.Rvea.html
[DifferentialEvolution]: https://docs.rs/heuropt/latest/heuropt/algorithms/differential_evolution/struct.DifferentialEvolution.html
[GeneticAlgorithm]: https://docs.rs/heuropt/latest/heuropt/algorithms/genetic_algorithm/struct.GeneticAlgorithm.html
[Paes]: https://docs.rs/heuropt/latest/heuropt/algorithms/paes/struct.Paes.html
[SimulatedAnnealing]: https://docs.rs/heuropt/latest/heuropt/algorithms/simulated_annealing/struct.SimulatedAnnealing.html
[HillClimber]: https://docs.rs/heuropt/latest/heuropt/algorithms/hill_climber/struct.HillClimber.html
[OnePlusOneEs]: https://docs.rs/heuropt/latest/heuropt/algorithms/one_plus_one_es/struct.OnePlusOneEs.html
+9 -9
View File
@@ -2,11 +2,11 @@
When your decision is "an ordering" — visiting cities, scheduling
jobs, routing — the natural representation is `Vec<usize>` and the
specialized algorithm is [`AntColonyTsp`]. Generic alternatives are
[`SimulatedAnnealing`] + [`SwapMutation`] for any permutation, and
[`TabuSearch`] when you have a custom neighbor function.
specialized algorithm is [Ant Colony][AntColonyTsp]. Generic alternatives are
[Simulated Annealing][SimulatedAnnealing] + [`SwapMutation`] for any permutation, and
[Tabu Search][TabuSearch] when you have a custom neighbor function.
## TSP with `AntColonyTsp`
## TSP with Ant Colony
```rust,no_run
use heuropt::prelude::*;
@@ -138,10 +138,10 @@ println!("schedule: {:?}", best.decision);
`SwapMutation` swaps two random indices in the permutation —
preserves the "every element appears once" invariant for free.
## Custom neighborhoods: `TabuSearch`
## Custom neighborhoods: Tabu Search
When swap isn't the right move set (e.g., 2-opt for TSP, insert /
shift for scheduling), use [`TabuSearch`] with your own neighbor
shift for scheduling), use [Tabu Search][TabuSearch] with your own neighbor
function.
```rust,ignore
@@ -161,7 +161,7 @@ let neighbors = |x: &Vec<usize>, _rng: &mut Rng| -> Vec<Vec<usize>> {
// Pass `neighbors` to TabuSearch::new(...).
```
[`AntColonyTsp`]: https://docs.rs/heuropt/latest/heuropt/algorithms/ant_colony_tsp/struct.AntColonyTsp.html
[`SimulatedAnnealing`]: https://docs.rs/heuropt/latest/heuropt/algorithms/simulated_annealing/struct.SimulatedAnnealing.html
[AntColonyTsp]: https://docs.rs/heuropt/latest/heuropt/algorithms/ant_colony_tsp/struct.AntColonyTsp.html
[SimulatedAnnealing]: https://docs.rs/heuropt/latest/heuropt/algorithms/simulated_annealing/struct.SimulatedAnnealing.html
[`SwapMutation`]: https://docs.rs/heuropt/latest/heuropt/operators/struct.SwapMutation.html
[`TabuSearch`]: https://docs.rs/heuropt/latest/heuropt/algorithms/tabu_search/struct.TabuSearch.html
[TabuSearch]: https://docs.rs/heuropt/latest/heuropt/algorithms/tabu_search/struct.TabuSearch.html
+15 -15
View File
@@ -87,8 +87,8 @@ impl Problem for Zdt1 {
```
For multi-objective problems, pick a Pareto-aware optimizer:
[`Nsga2`] is the canonical default; [`Mopso`] often wins on
smooth-front 2-objective problems; [`Ibea`] often wins on
[NSGA-II][Nsga2] is the canonical default; [MOPSO][Mopso] often wins on
smooth-front 2-objective problems; [IBEA][Ibea] often wins on
disconnected fronts. See [choosing-an-algorithm](./choosing-an-algorithm.md).
## Maximizing instead of minimizing
@@ -166,8 +166,8 @@ impl Problem for OneMax {
}
```
For `Vec<bool>` problems, [`Umda`] is a parameter-free EDA;
[`GeneticAlgorithm`] with [`BitFlipMutation`] is the GA route.
For `Vec<bool>` problems, [UMDA][Umda] is a parameter-free EDA;
[GA][GeneticAlgorithm] with [`BitFlipMutation`] is the GA route.
### Permutations (`Vec<usize>`)
@@ -191,9 +191,9 @@ impl Problem for Tsp {
}
```
For permutations, [`AntColonyTsp`] specializes on TSP-style problems;
[`TabuSearch`] takes a user-supplied neighbor function for arbitrary
discrete neighborhoods; [`SimulatedAnnealing`] with [`SwapMutation`]
For permutations, [Ant Colony][AntColonyTsp] specializes on TSP-style problems;
[Tabu Search][TabuSearch] takes a user-supplied neighbor function for arbitrary
discrete neighborhoods; [Simulated Annealing][SimulatedAnnealing] with [`SwapMutation`]
is the simplest baseline.
### Custom decision types
@@ -232,13 +232,13 @@ through the decision tree.
[`Evaluation`]: https://docs.rs/heuropt/latest/heuropt/core/evaluation/struct.Evaluation.html
[`Evaluation::new`]: https://docs.rs/heuropt/latest/heuropt/core/evaluation/struct.Evaluation.html#method.new
[`Evaluation::constrained`]: https://docs.rs/heuropt/latest/heuropt/core/evaluation/struct.Evaluation.html#method.constrained
[`Nsga2`]: https://docs.rs/heuropt/latest/heuropt/algorithms/nsga2/struct.Nsga2.html
[`Mopso`]: https://docs.rs/heuropt/latest/heuropt/algorithms/mopso/struct.Mopso.html
[`Ibea`]: https://docs.rs/heuropt/latest/heuropt/algorithms/ibea/struct.Ibea.html
[`Umda`]: https://docs.rs/heuropt/latest/heuropt/algorithms/umda/struct.Umda.html
[`GeneticAlgorithm`]: https://docs.rs/heuropt/latest/heuropt/algorithms/genetic_algorithm/struct.GeneticAlgorithm.html
[Nsga2]: https://docs.rs/heuropt/latest/heuropt/algorithms/nsga2/struct.Nsga2.html
[Mopso]: https://docs.rs/heuropt/latest/heuropt/algorithms/mopso/struct.Mopso.html
[Ibea]: https://docs.rs/heuropt/latest/heuropt/algorithms/ibea/struct.Ibea.html
[Umda]: https://docs.rs/heuropt/latest/heuropt/algorithms/umda/struct.Umda.html
[GeneticAlgorithm]: https://docs.rs/heuropt/latest/heuropt/algorithms/genetic_algorithm/struct.GeneticAlgorithm.html
[`BitFlipMutation`]: https://docs.rs/heuropt/latest/heuropt/operators/struct.BitFlipMutation.html
[`AntColonyTsp`]: https://docs.rs/heuropt/latest/heuropt/algorithms/ant_colony_tsp/struct.AntColonyTsp.html
[`TabuSearch`]: https://docs.rs/heuropt/latest/heuropt/algorithms/tabu_search/struct.TabuSearch.html
[`SimulatedAnnealing`]: https://docs.rs/heuropt/latest/heuropt/algorithms/simulated_annealing/struct.SimulatedAnnealing.html
[AntColonyTsp]: https://docs.rs/heuropt/latest/heuropt/algorithms/ant_colony_tsp/struct.AntColonyTsp.html
[TabuSearch]: https://docs.rs/heuropt/latest/heuropt/algorithms/tabu_search/struct.TabuSearch.html
[SimulatedAnnealing]: https://docs.rs/heuropt/latest/heuropt/algorithms/simulated_annealing/struct.SimulatedAnnealing.html
[`SwapMutation`]: https://docs.rs/heuropt/latest/heuropt/operators/struct.SwapMutation.html
+8 -6
View File
@@ -6,19 +6,21 @@ The shortest path from a fresh project to a working optimizer.
```toml
[dependencies]
heuropt = "0.8"
heuropt = "0.10"
```
The default feature set is small. Optional features:
- `parallel` — rayon-backed parallel population evaluation.
- `serde``Serialize` / `Deserialize` derives on the core data
types.
types, plus the `heuropt::explorer` JSON export module for the
[heuropt-explorer](https://swaits.github.io/heuropt-explorer/)
webapp.
- `async``AsyncProblem` trait + per-algorithm `run_async` for
IO-bound evaluations.
```toml
heuropt = { version = "0.8", features = ["parallel"] }
heuropt = { version = "0.10", features = ["parallel"] }
```
## 2. Define a problem and run an optimizer
@@ -31,7 +33,7 @@ how to score one decision.
We'll fit a straight line to a handful of `(x, y)` data points by
finding the slope and intercept that minimize the sum of squared
errors — same objective as least-squares regression. For a smooth
single-objective continuous problem like this, [`CmaEs`] is a strong
single-objective continuous problem like this, [CMA-ES][CmaEs] is a strong
default.
```rust,no_run
@@ -138,7 +140,7 @@ problems this clean in well under that budget.
## 4. What just happened
- [`Problem`] is the **what** you're optimizing.
- [`CmaEs`] (or any other optimizer) is the **how**.
- [CMA-ES][CmaEs] (or any other optimizer) is the **how**.
- [`CmaEsConfig`] is a plain public-field struct: there are no
builders, no chained setters, just public fields you set
directly.
@@ -163,5 +165,5 @@ problems this clean in well under that budget.
[`Problem`]: https://docs.rs/heuropt/latest/heuropt/core/problem/trait.Problem.html
[`Optimizer::run`]: https://docs.rs/heuropt/latest/heuropt/traits/trait.Optimizer.html
[`OptimizationResult`]: https://docs.rs/heuropt/latest/heuropt/core/result/struct.OptimizationResult.html
[`CmaEs`]: https://docs.rs/heuropt/latest/heuropt/algorithms/cma_es/struct.CmaEs.html
[CmaEs]: https://docs.rs/heuropt/latest/heuropt/algorithms/cma_es/struct.CmaEs.html
[`CmaEsConfig`]: https://docs.rs/heuropt/latest/heuropt/algorithms/cma_es/struct.CmaEsConfig.html
+12 -14
View File
@@ -28,7 +28,7 @@ hyperopt, optuna, DEAP). heuropt's design priorities:
1. **Approachable code.** No trait objects in the public API. No
GATs, HRTBs, generic-RNG plumbing. A junior Rust engineer should
be able to read `RandomSearch` and write a new optimizer by
be able to read Random Search and write a new optimizer by
implementing only the `Optimizer<P>` trait.
2. **One concrete RNG type.** Seeded determinism is a property tested
across the crate; identical inputs always produce identical
@@ -44,20 +44,18 @@ hyperopt, optuna, DEAP). heuropt's design priorities:
## What's in the box
heuropt v0.8 ships **33 algorithms** spanning:
heuropt v0.10 ships **33 algorithms** spanning:
- Single-objective continuous: `RandomSearch`, `HillClimber`,
`OnePlusOneEs`, `SimulatedAnnealing`, `GeneticAlgorithm`,
`ParticleSwarm`, `DifferentialEvolution`, `Tlbo`, `CmaEs`,
`IpopCmaEs`, `SeparableNes`, `NelderMead`.
- Single-objective other types: `Umda` (binary), `TabuSearch`
(any), `AntColonyTsp` (permutation).
- Multi-objective (23): `Paes`, `Nsga2`, `Spea2`, `Mopso`, `Ibea`,
`SmsEmoa`, `HypE`, `EpsilonMoea`, `PesaII`, `AgeMoea`, `Knea`,
`Moead`.
- Many-objective (4+): `Nsga3`, `Rvea`, `Grea`.
- Sample-efficient / multi-fidelity: `BayesianOpt`, `Tpe`,
`Hyperband`.
- Single-objective continuous: Random Search, Hill Climber,
(1+1)-ES, Simulated Annealing, GA, PSO, Differential Evolution,
TLBO, CMA-ES, IPOP-CMA-ES, sNES, Nelder-Mead.
- Single-objective other types: UMDA (binary), Tabu Search (any),
Ant Colony (permutation).
- Multi-objective (23): PAES, NSGA-II, SPEA2, MOPSO, IBEA,
SMS-EMOA, HypE, ε-MOEA, PESA-II, AGE-MOEA, KnEA, MOEA/D.
- Many-objective (4+): NSGA-III, RVEA, GrEA.
- Sample-efficient / multi-fidelity: Bayesian Optimization, TPE,
Hyperband.
Plus the operators (SBX, PolynomialMutation, BoundedGaussianMutation,
LevyMutation, BitFlipMutation, SwapMutation, ClampToBounds,
+69 -5
View File
@@ -3,6 +3,70 @@
Per-release notes for upgrading between heuropt versions. Skip the
sections that don't apply to your starting version.
## To 0.10
### From 0.9.x
**Almost additive.** Bumping `heuropt = "0.10"` recompiles
without touching most code. The one breaking change is the value
returned by `AlgorithmInfo::name()`:
| Before (`0.9`) | After (`0.10`) |
|---|---|
| `"Nsga2"` | `"NSGA-II"` |
| `"Nsga3"` | `"NSGA-III"` |
| `"Cmaes"` | `"CMA-ES"` |
| `"Mopso"` | `"MOPSO"` |
| `"Moead"` | `"MOEA/D"` |
| `"EpsilonMoea"` | `"ε-MOEA"` |
| (and 27 more) | … |
If you pattern-matched on those strings (e.g. for branching
display logic), update to the new canonical strings. They now
match the literature and will be stable going forward.
What's new and additive:
- `AlgorithmInfo::full_name(&self) -> &'static str` — academic
long form (`"Non-dominated Sorting Genetic Algorithm II"`).
Defaults to `name()` for algorithms whose long and short
forms coincide.
- `ExplorerExport`'s `RunMeta` gained `algorithm_full_name:
Option<String>`. Schema version stays at **1** (the new field
is `#[serde(default)]`); display tools can use the long form
as a hover tooltip on the short name.
## To 0.9
### From 0.8.x
**Additive only.** Bumping `heuropt = "0.9"` works for all 0.8.x
code untouched. The new surfaces ship behind the existing `serde`
feature.
What's new:
- `heuropt::explorer` module (gated on `serde`) — turns an
`OptimizationResult` into a self-describing JSON file that the
[heuropt-explorer](https://swaits.github.io/heuropt-explorer/)
webapp can load. See the
[Explore your results](./cookbook/explorer.md) recipe.
- `Objective` gained optional `label` and `unit` fields with
fluent builders `.with_label("…")` / `.with_unit("…")`. Existing
`Objective::minimize("…")` / `Objective::maximize("…")` are
unchanged. The serde representation is forward- and backward-
compatible (new fields are `#[serde(default)]`).
- `Problem` trait gained a default-empty
`fn decision_schema(&self) -> Vec<DecisionVariable>` method.
Existing impls compile untouched; override it to provide pretty
names / labels / units / bounds for the explorer.
- `heuropt::traits::AlgorithmInfo` — every built-in algorithm
exposes its short canonical name (`"Nsga3"`, …) and its seed.
Used by the explorer JSON export.
If you don't want any of this, no migration needed — just bump
the version.
## To 0.8
### From 0.5.x
@@ -101,9 +165,9 @@ from v0.3 are still numerically accurate but will run faster.
### From 0.2.x
**Additive only.** New algorithms (`BayesianOpt`, `Tpe`,
`OnePlusOneEs`, `IpopCmaEs`, `SeparableNes`, `NelderMead`,
`Hyperband`), new operators (`LevyMutation`, `ClampToBounds`,
**Additive only.** New algorithms (Bayesian Optimization, TPE,
(1+1)-ES, IPOP-CMA-ES, sNES, Nelder-Mead,
Hyperband), new operators (`LevyMutation`, `ClampToBounds`,
`ProjectToSimplex`), new traits (`PartialProblem`, `Repair<D>`).
`CmaEsConfig` gained an `initial_mean: Option<Vec<f64>>` field;
@@ -114,8 +178,8 @@ existing call sites need a `.. CmaEsConfig { initial_mean: None,
### From 0.1.x
**Additive.** New algorithms across the catalog (HillClimber, SA,
GA, PSO, CMA-ES, TabuSearch, AntColonyTsp, Umda, TLBO, MOPSO, IBEA,
**Additive.** New algorithms across the catalog (Hill Climber, SA,
GA, PSO, CMA-ES, Tabu Search, Ant Colony, UMDA, TLBO, MOPSO, IBEA,
SMS-EMOA, HypE, RVEA, PESA-II, ε-MOEA, AGE-MOEA, GrEA, KnEA), new
operators (`SimulatedBinaryCrossover`, `PolynomialMutation`,
`CompositeVariation`, `BoundedGaussianMutation`), and the
+3 -3
View File
@@ -18,10 +18,10 @@ versions — use them at your own risk.
While we are pre-1.0:
- **Minor bumps (`0.8 → 0.9`) may break the public API.** The
- **Minor bumps (`0.10 → 0.11`) may break the public API.** The
CHANGELOG calls out everything that changed, and a **migration
guide** in this book documents the move.
- **Patch bumps (`0.8.0 → 0.8.1`) only contain bug fixes,
- **Patch bumps (`0.10.0 → 0.10.1`) only contain bug fixes,
performance improvements, and additive non-breaking features.**
No deprecations, no removals.
@@ -61,7 +61,7 @@ optimizations has been bit-identical against the v0.3.0 reference.
## MSRV (minimum supported Rust version)
heuropt's MSRV is **1.85** as of v0.8. This is tested in CI against
heuropt's MSRV is **1.85** as of v0.10. This is tested in CI against
every PR.
MSRV bumps are treated as patch-bump-eligible (they don't break the
+4 -1
View File
@@ -426,7 +426,10 @@ fn estimate_p(front_indices: &[usize], translated: &[Vec<f64>], m: usize) -> f64
impl<I, V> crate::traits::AlgorithmInfo for AgeMoea<I, V> {
fn name(&self) -> &'static str {
"AgeMoea"
"AGE-MOEA"
}
fn full_name(&self) -> &'static str {
"Adaptive Geometry Estimation Multi-Objective Evolutionary Algorithm"
}
fn seed(&self) -> Option<u64> {
Some(self.config.seed)
+4 -1
View File
@@ -431,7 +431,10 @@ fn better_than_so(
impl crate::traits::AlgorithmInfo for AntColonyTsp {
fn name(&self) -> &'static str {
"AntColonyTsp"
"Ant Colony"
}
fn full_name(&self) -> &'static str {
"Ant Colony System for TSP"
}
fn seed(&self) -> Option<u64> {
Some(self.config.seed)
+4 -1
View File
@@ -542,7 +542,10 @@ impl BayesianOpt {
impl crate::traits::AlgorithmInfo for BayesianOpt {
fn name(&self) -> &'static str {
"BayesianOpt"
"Bayesian Optimization"
}
fn full_name(&self) -> &'static str {
"Gaussian Process Bayesian Optimization with Expected Improvement"
}
fn seed(&self) -> Option<u64> {
Some(self.config.seed)
+4 -1
View File
@@ -630,7 +630,10 @@ fn better_than_so(
impl crate::traits::AlgorithmInfo for CmaEs {
fn name(&self) -> &'static str {
"CmaEs"
"CMA-ES"
}
fn full_name(&self) -> &'static str {
"Covariance Matrix Adaptation Evolution Strategy"
}
fn seed(&self) -> Option<u64> {
Some(self.config.seed)
+4 -1
View File
@@ -305,7 +305,10 @@ fn pick_three_distinct(
impl crate::traits::AlgorithmInfo for DifferentialEvolution {
fn name(&self) -> &'static str {
"DifferentialEvolution"
"DE"
}
fn full_name(&self) -> &'static str {
"Differential Evolution"
}
fn seed(&self) -> Option<u64> {
Some(self.config.seed)
+4 -1
View File
@@ -398,7 +398,10 @@ fn box_dominates(a: &[i64], b: &[i64]) -> bool {
impl<I, V> crate::traits::AlgorithmInfo for EpsilonMoea<I, V> {
fn name(&self) -> &'static str {
"EpsilonMoea"
"ε-MOEA"
}
fn full_name(&self) -> &'static str {
"ε-dominance Multi-Objective Evolutionary Algorithm"
}
fn seed(&self) -> Option<u64> {
Some(self.config.seed)
+4 -1
View File
@@ -319,7 +319,10 @@ fn compare_for_fitness<D>(
impl<I, V> crate::traits::AlgorithmInfo for GeneticAlgorithm<I, V> {
fn name(&self) -> &'static str {
"GeneticAlgorithm"
"GA"
}
fn full_name(&self) -> &'static str {
"Genetic Algorithm"
}
fn seed(&self) -> Option<u64> {
Some(self.config.seed)
+4 -1
View File
@@ -336,7 +336,10 @@ fn environmental_selection<D: Clone>(
impl<I, V> crate::traits::AlgorithmInfo for Grea<I, V> {
fn name(&self) -> &'static str {
"Grea"
"GrEA"
}
fn full_name(&self) -> &'static str {
"Grid-based Evolutionary Algorithm"
}
fn seed(&self) -> Option<u64> {
Some(self.config.seed)
+4 -1
View File
@@ -226,7 +226,10 @@ impl<I, V> HillClimber<I, V> {
impl<I, V> crate::traits::AlgorithmInfo for HillClimber<I, V> {
fn name(&self) -> &'static str {
"HillClimber"
"Hill Climber"
}
fn full_name(&self) -> &'static str {
"Hill Climbing"
}
fn seed(&self) -> Option<u64> {
Some(self.config.seed)
+4 -1
View File
@@ -461,7 +461,10 @@ fn binary_tournament(fitness: &[f64], rng: &mut Rng) -> usize {
impl<I, V> crate::traits::AlgorithmInfo for Hype<I, V> {
fn name(&self) -> &'static str {
"Hype"
"HypE"
}
fn full_name(&self) -> &'static str {
"Hypervolume Estimation Algorithm"
}
fn seed(&self) -> Option<u64> {
Some(self.config.seed)
+3
View File
@@ -337,6 +337,9 @@ where
fn name(&self) -> &'static str {
"Hyperband"
}
fn full_name(&self) -> &'static str {
"Hyperband multi-fidelity bandit search"
}
fn seed(&self) -> Option<u64> {
Some(self.config.seed)
}
+4 -1
View File
@@ -387,7 +387,10 @@ fn binary_tournament(fitness: &[f64], rng: &mut Rng) -> usize {
impl<I, V> crate::traits::AlgorithmInfo for Ibea<I, V> {
fn name(&self) -> &'static str {
"Ibea"
"IBEA"
}
fn full_name(&self) -> &'static str {
"Indicator-Based Evolutionary Algorithm"
}
fn seed(&self) -> Option<u64> {
Some(self.config.seed)
+4 -1
View File
@@ -289,7 +289,10 @@ fn better(a: &Evaluation, b: &Evaluation, direction: Direction) -> bool {
impl crate::traits::AlgorithmInfo for IpopCmaEs {
fn name(&self) -> &'static str {
"IpopCmaEs"
"IPOP-CMA-ES"
}
fn full_name(&self) -> &'static str {
"Increasing-Population CMA-ES with Restarts"
}
fn seed(&self) -> Option<u64> {
Some(self.config.seed)
+4 -1
View File
@@ -330,7 +330,10 @@ fn perpendicular_distance(point: &[f64], extremes: &[usize], oriented: &[Vec<f64
impl<I, V> crate::traits::AlgorithmInfo for Knea<I, V> {
fn name(&self) -> &'static str {
"Knea"
"KnEA"
}
fn full_name(&self) -> &'static str {
"Knee point-driven Evolutionary Algorithm"
}
fn seed(&self) -> Option<u64> {
Some(self.config.seed)
+4 -1
View File
@@ -365,7 +365,10 @@ fn weight_distance(a: &[f64], b: &[f64]) -> f64 {
impl<I, V> crate::traits::AlgorithmInfo for Moead<I, V> {
fn name(&self) -> &'static str {
"Moead"
"MOEA/D"
}
fn full_name(&self) -> &'static str {
"Multi-Objective Evolutionary Algorithm based on Decomposition"
}
fn seed(&self) -> Option<u64> {
Some(self.config.seed)
+4 -1
View File
@@ -332,7 +332,10 @@ impl Mopso {
impl crate::traits::AlgorithmInfo for Mopso {
fn name(&self) -> &'static str {
"Mopso"
"MOPSO"
}
fn full_name(&self) -> &'static str {
"Multi-Objective Particle Swarm Optimization"
}
fn seed(&self) -> Option<u64> {
Some(self.config.seed)
+4 -1
View File
@@ -452,7 +452,10 @@ impl NelderMead {
impl crate::traits::AlgorithmInfo for NelderMead {
fn name(&self) -> &'static str {
"NelderMead"
"Nelder-Mead"
}
fn full_name(&self) -> &'static str {
"Nelder-Mead simplex direct search"
}
}
+4 -1
View File
@@ -366,7 +366,10 @@ fn binary_tournament<D>(entries: &[Nsga2Entry<D>], rng: &mut Rng) -> usize {
impl<I, V> crate::traits::AlgorithmInfo for Nsga2<I, V> {
fn name(&self) -> &'static str {
"Nsga2"
"NSGA-II"
}
fn full_name(&self) -> &'static str {
"Non-dominated Sorting Genetic Algorithm II"
}
fn seed(&self) -> Option<u64> {
Some(self.config.seed)
+4 -1
View File
@@ -544,7 +544,10 @@ fn associate(
impl<I, V> crate::traits::AlgorithmInfo for Nsga3<I, V> {
fn name(&self) -> &'static str {
"Nsga3"
"NSGA-III"
}
fn full_name(&self) -> &'static str {
"Non-dominated Sorting Genetic Algorithm III"
}
fn seed(&self) -> Option<u64> {
Some(self.config.seed)
+4 -1
View File
@@ -287,7 +287,10 @@ impl OnePlusOneEs {
impl crate::traits::AlgorithmInfo for OnePlusOneEs {
fn name(&self) -> &'static str {
"OnePlusOneEs"
"(1+1)-ES"
}
fn full_name(&self) -> &'static str {
"(1+1) Evolution Strategy with one-fifth success rule"
}
fn seed(&self) -> Option<u64> {
Some(self.config.seed)
+4 -1
View File
@@ -244,7 +244,10 @@ impl<I, V> Paes<I, V> {
impl<I, V> crate::traits::AlgorithmInfo for Paes<I, V> {
fn name(&self) -> &'static str {
"Paes"
"PAES"
}
fn full_name(&self) -> &'static str {
"Pareto Archived Evolution Strategy"
}
fn seed(&self) -> Option<u64> {
Some(self.config.seed)
+4 -1
View File
@@ -359,7 +359,10 @@ fn best_index(values: &[f64], direction: Direction) -> usize {
impl crate::traits::AlgorithmInfo for ParticleSwarm {
fn name(&self) -> &'static str {
"ParticleSwarm"
"PSO"
}
fn full_name(&self) -> &'static str {
"Particle Swarm Optimization"
}
fn seed(&self) -> Option<u64> {
Some(self.config.seed)
+4 -1
View File
@@ -411,7 +411,10 @@ fn truncate_by_grid<D: Clone>(archive: &mut ParetoArchive<D>, max_size: usize, d
impl<I, V> crate::traits::AlgorithmInfo for PesaII<I, V> {
fn name(&self) -> &'static str {
"PesaII"
"PESA-II"
}
fn full_name(&self) -> &'static str {
"Pareto Envelope-based Selection Algorithm II"
}
fn seed(&self) -> Option<u64> {
Some(self.config.seed)
+1 -1
View File
@@ -160,7 +160,7 @@ impl<I> RandomSearch<I> {
impl<I> crate::traits::AlgorithmInfo for RandomSearch<I> {
fn name(&self) -> &'static str {
"RandomSearch"
"Random Search"
}
fn seed(&self) -> Option<u64> {
Some(self.config.seed)
+4 -1
View File
@@ -468,7 +468,10 @@ fn smallest_neighbor_angle(references: &[Vec<f64>]) -> f64 {
impl<I, V> crate::traits::AlgorithmInfo for Rvea<I, V> {
fn name(&self) -> &'static str {
"Rvea"
"RVEA"
}
fn full_name(&self) -> &'static str {
"Reference Vector-guided Evolutionary Algorithm"
}
fn seed(&self) -> Option<u64> {
Some(self.config.seed)
+1 -1
View File
@@ -335,7 +335,7 @@ impl<I, V> SimulatedAnnealing<I, V> {
impl<I, V> crate::traits::AlgorithmInfo for SimulatedAnnealing<I, V> {
fn name(&self) -> &'static str {
"SimulatedAnnealing"
"Simulated Annealing"
}
fn seed(&self) -> Option<u64> {
Some(self.config.seed)
+4 -1
View File
@@ -291,7 +291,10 @@ fn pick_drop_index<D>(
impl<I, V> crate::traits::AlgorithmInfo for SmsEmoa<I, V> {
fn name(&self) -> &'static str {
"SmsEmoa"
"SMS-EMOA"
}
fn full_name(&self) -> &'static str {
"S-Metric Selection Evolutionary Multi-Objective Algorithm"
}
fn seed(&self) -> Option<u64> {
Some(self.config.seed)
+4 -1
View File
@@ -391,7 +391,10 @@ fn better(a: &Evaluation, b: &Evaluation, direction: Direction) -> bool {
impl crate::traits::AlgorithmInfo for SeparableNes {
fn name(&self) -> &'static str {
"SeparableNes"
"sNES"
}
fn full_name(&self) -> &'static str {
"Separable Natural Evolution Strategy"
}
fn seed(&self) -> Option<u64> {
Some(self.config.seed)
+4 -1
View File
@@ -505,7 +505,10 @@ fn binary_tournament(fitness: &[f64], rng: &mut Rng) -> usize {
impl<I, V> crate::traits::AlgorithmInfo for Spea2<I, V> {
fn name(&self) -> &'static str {
"Spea2"
"SPEA2"
}
fn full_name(&self) -> &'static str {
"Strength Pareto Evolutionary Algorithm 2"
}
fn seed(&self) -> Option<u64> {
Some(self.config.seed)
+1 -1
View File
@@ -338,7 +338,7 @@ where
N: FnMut(&D, &mut Rng) -> Vec<D>,
{
fn name(&self) -> &'static str {
"TabuSearch"
"Tabu Search"
}
fn seed(&self) -> Option<u64> {
Some(self.config.seed)
+4 -1
View File
@@ -327,7 +327,10 @@ fn better(a: &Evaluation, b: &Evaluation, direction: Direction) -> bool {
impl crate::traits::AlgorithmInfo for Tlbo {
fn name(&self) -> &'static str {
"Tlbo"
"TLBO"
}
fn full_name(&self) -> &'static str {
"Teaching-Learning-Based Optimization"
}
fn seed(&self) -> Option<u64> {
Some(self.config.seed)
+4 -1
View File
@@ -481,7 +481,10 @@ impl Tpe {
impl crate::traits::AlgorithmInfo for Tpe {
fn name(&self) -> &'static str {
"Tpe"
"TPE"
}
fn full_name(&self) -> &'static str {
"Tree-structured Parzen Estimator"
}
fn seed(&self) -> Option<u64> {
Some(self.config.seed)
+4 -1
View File
@@ -354,7 +354,10 @@ fn better_than_so(
impl crate::traits::AlgorithmInfo for Umda {
fn name(&self) -> &'static str {
"Umda"
"UMDA"
}
fn full_name(&self) -> &'static str {
"Univariate Marginal Distribution Algorithm"
}
fn seed(&self) -> Option<u64> {
Some(self.config.seed)
+19 -4
View File
@@ -101,10 +101,16 @@ pub struct RunMeta {
/// Optional human-readable problem name (e.g. `"Pick a car"`).
#[serde(default, skip_serializing_if = "Option::is_none")]
pub problem_name: Option<String>,
/// Canonical algorithm name (e.g. `"Nsga3"`). Pulled from
/// [`AlgorithmInfo::name`] when an algorithm is provided.
/// Canonical short algorithm name (e.g. `"NSGA-III"`). Pulled
/// from [`AlgorithmInfo::name`] when an algorithm is provided.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub algorithm: Option<String>,
/// Academic long form (e.g. `"Non-dominated Sorting Genetic
/// Algorithm III"`). Pulled from [`AlgorithmInfo::full_name`]
/// when an algorithm is provided. Display tools render this
/// as a tooltip / aria-label on the short name.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub algorithm_full_name: Option<String>,
/// Seed driving this run, if applicable. Pulled from
/// [`AlgorithmInfo::seed`].
#[serde(default, skip_serializing_if = "Option::is_none")]
@@ -216,10 +222,12 @@ impl ExplorerExport {
}
}
/// Populate `algorithm` and `seed` from anything implementing
/// [`AlgorithmInfo`] — every built-in algorithm does.
/// Populate `algorithm`, `algorithm_full_name`, and `seed`
/// from anything implementing [`AlgorithmInfo`] — every
/// built-in algorithm does.
pub fn with_algorithm_info<A: AlgorithmInfo>(mut self, algorithm: &A) -> Self {
self.run.algorithm = Some(algorithm.name().to_owned());
self.run.algorithm_full_name = Some(algorithm.full_name().to_owned());
self.run.seed = algorithm.seed();
self
}
@@ -412,6 +420,9 @@ mod tests {
fn name(&self) -> &'static str {
"DummyAlgo"
}
fn full_name(&self) -> &'static str {
"Dummy Test Algorithm"
}
fn seed(&self) -> Option<u64> {
Some(123)
}
@@ -513,6 +524,10 @@ mod tests {
let result = make_result(vec![vec![0.0, 1.0]], |d| d.to_vec());
let export = ExplorerExport::from_result(&problem, &result).with_algorithm_info(&DummyAlgo);
assert_eq!(export.run.algorithm.as_deref(), Some("DummyAlgo"));
assert_eq!(
export.run.algorithm_full_name.as_deref(),
Some("Dummy Test Algorithm"),
);
assert_eq!(export.run.seed, Some(123));
}
+35 -15
View File
@@ -1,25 +1,45 @@
//! Lightweight metadata about an algorithm — its short canonical name
//! and the seed driving the current run.
//! Lightweight metadata about an algorithm — its canonical short
//! name, an academic long name, and the seed driving the current
//! run.
//!
//! `AlgorithmInfo` is separate from [`Optimizer<P>`](super::Optimizer)
//! so multi-fidelity algorithms (which use `PartialProblem` instead of
//! `Problem`) can implement it uniformly. Every built-in algorithm in
//! `heuropt` implements `AlgorithmInfo`; the explorer JSON export reads
//! these methods to populate `algorithm` and `seed` fields in the
//! exported run metadata.
//! so multi-fidelity algorithms (which use `PartialProblem` instead
//! of `Problem`) can implement it uniformly. Every built-in
//! algorithm in `heuropt` implements `AlgorithmInfo`; the explorer
//! JSON export reads these methods to populate the `algorithm` and
//! `algorithm_full_name` fields in the exported run metadata.
/// Algorithm metadata used by tooling such as the explorer JSON export.
/// Algorithm metadata used by tooling such as the explorer JSON
/// export.
///
/// Implementors return a short canonical name like `"Nsga3"` or
/// `"DifferentialEvolution"`, and the seed driving their current run
/// when applicable.
/// Implementors return:
/// - **`name`** — the canonical short display name as it appears
/// in the literature: `"NSGA-II"`, `"MOEA/D"`, `"ε-MOEA"`,
/// `"CMA-ES"`. *Not* the Rust type name.
/// - **`full_name`** — the academic long form, e.g.
/// `"Non-dominated Sorting Genetic Algorithm II"`. Defaults to
/// `name()` when not overridden, which is the right answer for
/// algorithms whose short name *is* their full name (Random
/// Search, Hill Climber, Tabu Search, …).
/// - **`seed`** — the deterministic seed driving this run, when
/// the algorithm uses one. Defaults to `None`.
pub trait AlgorithmInfo {
/// Short, canonical algorithm name — e.g. `"Nsga3"`,
/// `"DifferentialEvolution"`, `"BayesianOpt"`.
/// Canonical short algorithm name — e.g. `"NSGA-II"`,
/// `"DE"`, `"CMA-ES"`. This is the form that should appear
/// in tables, plot legends, and exported JSON metadata.
fn name(&self) -> &'static str;
/// The deterministic seed driving this run, if the algorithm uses
/// one. Default: `None`. Built-in algorithms return
/// Academic long name, expanded — 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,
/// Hyperband, …).
fn full_name(&self) -> &'static str {
self.name()
}
/// The deterministic seed driving this run, if the algorithm
/// uses one. Default: `None`. Built-in algorithms return
/// `Some(self.config.seed)`.
fn seed(&self) -> Option<u64> {
None