From 6371d82f40546d8606881a10d12d92f37c603594 Mon Sep 17 00:00:00 2001 From: Stephen Waits Date: Wed, 6 May 2026 12:24:26 -0600 Subject: [PATCH] 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. --- .claude/scheduled_tasks.lock | 1 - .gitignore | 3 + CHANGELOG.md | 147 ++++++++++- Cargo.toml | 2 +- README.md | 248 ++++++++++-------- SECURITY.md | 4 +- docs/book/src/SUMMARY.md | 1 + docs/book/src/choosing-an-algorithm.md | 174 ++++++------ docs/book/src/comparison.md | 10 +- docs/book/src/cookbook.md | 8 +- docs/book/src/cookbook/async.md | 6 +- .../src/cookbook/expensive-evaluations.md | 16 +- docs/book/src/cookbook/explorer.md | 2 +- docs/book/src/cookbook/parallel.md | 58 ++-- docs/book/src/cookbook/permutation.md | 18 +- docs/book/src/defining-problems.md | 30 +-- docs/book/src/getting-started.md | 14 +- docs/book/src/introduction.md | 26 +- docs/book/src/migration.md | 74 +++++- docs/book/src/stability.md | 6 +- src/algorithms/age_moea.rs | 5 +- src/algorithms/ant_colony_tsp.rs | 5 +- src/algorithms/bayesian_opt.rs | 5 +- src/algorithms/cma_es.rs | 5 +- src/algorithms/differential_evolution.rs | 5 +- src/algorithms/epsilon_moea.rs | 5 +- src/algorithms/genetic_algorithm.rs | 5 +- src/algorithms/grea.rs | 5 +- src/algorithms/hill_climber.rs | 5 +- src/algorithms/hype.rs | 5 +- src/algorithms/hyperband.rs | 3 + src/algorithms/ibea.rs | 5 +- src/algorithms/ipop_cma_es.rs | 5 +- src/algorithms/knea.rs | 5 +- src/algorithms/moead.rs | 5 +- src/algorithms/mopso.rs | 5 +- src/algorithms/nelder_mead.rs | 5 +- src/algorithms/nsga2.rs | 5 +- src/algorithms/nsga3.rs | 5 +- src/algorithms/one_plus_one_es.rs | 5 +- src/algorithms/paes.rs | 5 +- src/algorithms/particle_swarm.rs | 5 +- src/algorithms/pesa2.rs | 5 +- src/algorithms/random_search.rs | 2 +- src/algorithms/rvea.rs | 5 +- src/algorithms/simulated_annealing.rs | 2 +- src/algorithms/sms_emoa.rs | 5 +- src/algorithms/snes.rs | 5 +- src/algorithms/spea2.rs | 5 +- src/algorithms/tabu_search.rs | 2 +- src/algorithms/tlbo.rs | 5 +- src/algorithms/tpe.rs | 5 +- src/algorithms/umda.rs | 5 +- src/explorer/mod.rs | 23 +- src/traits/algorithm_info.rs | 50 ++-- 55 files changed, 721 insertions(+), 354 deletions(-) delete mode 100644 .claude/scheduled_tasks.lock diff --git a/.claude/scheduled_tasks.lock b/.claude/scheduled_tasks.lock deleted file mode 100644 index 41d1503..0000000 --- a/.claude/scheduled_tasks.lock +++ /dev/null @@ -1 +0,0 @@ -{"sessionId":"ac44d107-52ca-4cd4-9586-ae2fe91bc9f7","pid":2366937,"procStart":"77336928","acquiredAt":1778002505967} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 4fffb2f..d1c75fb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,5 @@ /target /Cargo.lock + +# Generated by `cargo run --example pick_a_car` +/pick_a_car.json diff --git a/CHANGELOG.md b/CHANGELOG.md index f8bbe87..64b0bab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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` 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 50–200+ +candidates spanning 2–7+ 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`, `Vec`, + `Vec`, and `Vec` 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` and + `unit: Option` 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` 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` (default + `None`). Every built-in algorithm — all 33 — implements it. + Separate from `Optimizer

` 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 diff --git a/Cargo.toml b/Cargo.toml index 7e2429b..d9b06e2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "heuropt" -version = "0.9.0" +version = "0.10.0" edition = "2024" rust-version = "1.85" authors = ["Stephen Waits "] diff --git a/README.md b/README.md index f842b5b..1ce5b78 100644 --- a/README.md +++ b/README.md @@ -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

`: @@ -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 (50–500 evals):** `BayesianOpt` (Gaussian-process - surrogate + Expected Improvement) or `Tpe` (Parzen-density +- **Expensive (50–500 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 (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 (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 (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` | GP surrogate + EI; gold standard *with* per-problem kernel tuning (default RBF at 60 evals is honestly bad) | -| `Tpe` | 1 | `Vec` | KDE surrogate; robust without hyperparameter tuning | -| `Hyperband` | 1 | any | multi-fidelity; needs `PartialProblem` | +| **Bayesian Optimization** | 1 | `Vec` | GP surrogate + EI; gold standard *with* per-problem kernel tuning (default RBF at 60 evals is honestly bad) | +| **TPE** | 1 | `Vec` | KDE surrogate; robust without hyperparameter tuning | +| **Hyperband** | 1 | any | multi-fidelity; needs `PartialProblem` | **Single-objective continuous (`Vec`):** | 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` | independent-bit EDA | -| `TabuSearch` | any | discrete, you supply neighbors | -| `AntColonyTsp` | `Vec` | TSP / permutation | +| **UMDA** | `Vec` | independent-bit EDA | +| **Tabu Search** | any | discrete, you supply neighbors | +| **Ant Colony** | `Vec` | TSP / permutation | **Multi-objective (2–3) and many-objective (4+):** | Algorithm | Objectives | Strengths | |---|---|---| -| `Paes` | 2–3 | 1+1 ES with Pareto archive | -| `Nsga2` | 2–3 | canonical Pareto-based EA | -| `Spea2` | 2–3 | strength + density | -| `Mopso` | 2–3 | 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** | 2–3 | 1+1 ES with Pareto archive | +| **NSGA-II** | 2–3 | canonical Pareto-based EA | +| **SPEA2** | 2–3 | strength + density | +| **MOPSO** | 2–3 | 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`. -- `DifferentialEvolution` — Storn & Price DE/rand/1/bin for `Vec`. -- `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`. -- `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`. +- **Differential Evolution** — Storn & Price DE/rand/1/bin for `Vec`. +- **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`. +- **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. diff --git a/SECURITY.md b/SECURITY.md index a8d58dc..34cf240 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -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 diff --git a/docs/book/src/SUMMARY.md b/docs/book/src/SUMMARY.md index 0b1c747..74be0cb 100644 --- a/docs/book/src/SUMMARY.md +++ b/docs/book/src/SUMMARY.md @@ -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 diff --git a/docs/book/src/choosing-an-algorithm.md b/docs/book/src/choosing-an-algorithm.md index fb472f1..9f25375 100644 --- a/docs/book/src/choosing-an-algorithm.md +++ b/docs/book/src/choosing-an-algorithm.md @@ -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` 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` | [`Umda`] | Per-bit marginal EDA. Independent-bit assumption. | -| `Vec` | [`GeneticAlgorithm`] + [`BitFlipMutation`] | When bit interactions matter. | -| `Vec` (permutation) | [`AntColonyTsp`] | TSP-style with a distance matrix. | -| `Vec` (permutation) | [`SimulatedAnnealing`] + [`SwapMutation`] | Generic discrete baseline. | -| `Vec` or custom | [`TabuSearch`] | You supply the neighbor function. | -| Custom struct | [`SimulatedAnnealing`] / [`HillClimber`] | With your own `Variation` impl. | +| `Vec` | [UMDA][Umda] | Per-bit marginal EDA. Independent-bit assumption. | +| `Vec` | [GA][GeneticAlgorithm] + [`BitFlipMutation`] | When bit interactions matter. | +| `Vec` (permutation) | [Ant Colony][AntColonyTsp] | TSP-style with a distance matrix. | +| `Vec` (permutation) | [Simulated Annealing][SimulatedAnnealing] + [`SwapMutation`] | Generic discrete baseline. | +| `Vec` 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`]: 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 diff --git a/docs/book/src/comparison.md b/docs/book/src/comparison.md index c8919c0..7c299a3 100644 --- a/docs/book/src/comparison.md +++ b/docs/book/src/comparison.md @@ -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: diff --git a/docs/book/src/cookbook.md b/docs/book/src/cookbook.md index b6942f9..d616a06 100644 --- a/docs/book/src/cookbook.md +++ b/docs/book/src/cookbook.md @@ -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 50–500-eval + — Bayesian Optimization, TPE, and Hyperband for the 50–500-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

` from scratch, à la the `examples/custom_optimizer.rs` walkthrough. diff --git a/docs/book/src/cookbook/async.md b/docs/book/src/cookbook/async.md index f569e54..3278799 100644 --- a/docs/book/src/cookbook/async.md +++ b/docs/book/src/cookbook/async.md @@ -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 diff --git a/docs/book/src/cookbook/expensive-evaluations.md b/docs/book/src/cookbook/expensive-evaluations.md index c4fc212..0ba9bb8 100644 --- a/docs/book/src/cookbook/expensive-evaluations.md +++ b/docs/book/src/cookbook/expensive-evaluations.md @@ -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 diff --git a/docs/book/src/cookbook/explorer.md b/docs/book/src/cookbook/explorer.md index 94b2123..6db7771 100644 --- a/docs/book/src/cookbook/explorer.md +++ b/docs/book/src/cookbook/explorer.md @@ -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 diff --git a/docs/book/src/cookbook/parallel.md b/docs/book/src/cookbook/parallel.md index 9be7aaa..834a21a 100644 --- a/docs/book/src/cookbook/parallel.md +++ b/docs/book/src/cookbook/parallel.md @@ -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 diff --git a/docs/book/src/cookbook/permutation.md b/docs/book/src/cookbook/permutation.md index 3ff51a3..2d1ec42 100644 --- a/docs/book/src/cookbook/permutation.md +++ b/docs/book/src/cookbook/permutation.md @@ -2,11 +2,11 @@ When your decision is "an ordering" — visiting cities, scheduling jobs, routing — the natural representation is `Vec` 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, _rng: &mut Rng| -> Vec> { // 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 diff --git a/docs/book/src/defining-problems.md b/docs/book/src/defining-problems.md index febc8f8..6621dbc 100644 --- a/docs/book/src/defining-problems.md +++ b/docs/book/src/defining-problems.md @@ -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` problems, [`Umda`] is a parameter-free EDA; -[`GeneticAlgorithm`] with [`BitFlipMutation`] is the GA route. +For `Vec` problems, [UMDA][Umda] is a parameter-free EDA; +[GA][GeneticAlgorithm] with [`BitFlipMutation`] is the GA route. ### Permutations (`Vec`) @@ -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 diff --git a/docs/book/src/getting-started.md b/docs/book/src/getting-started.md index fda4900..6ff3fea 100644 --- a/docs/book/src/getting-started.md +++ b/docs/book/src/getting-started.md @@ -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 diff --git a/docs/book/src/introduction.md b/docs/book/src/introduction.md index ee11a4c..43f0d6f 100644 --- a/docs/book/src/introduction.md +++ b/docs/book/src/introduction.md @@ -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

` 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 (2–3): `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 (2–3): 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, diff --git a/docs/book/src/migration.md b/docs/book/src/migration.md index 0a6ef7e..d5af716 100644 --- a/docs/book/src/migration.md +++ b/docs/book/src/migration.md @@ -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`. 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` 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`). `CmaEsConfig` gained an `initial_mean: Option>` 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 diff --git a/docs/book/src/stability.md b/docs/book/src/stability.md index d0f26af..e3472d7 100644 --- a/docs/book/src/stability.md +++ b/docs/book/src/stability.md @@ -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 diff --git a/src/algorithms/age_moea.rs b/src/algorithms/age_moea.rs index 6713afb..a936281 100644 --- a/src/algorithms/age_moea.rs +++ b/src/algorithms/age_moea.rs @@ -426,7 +426,10 @@ fn estimate_p(front_indices: &[usize], translated: &[Vec], m: usize) -> f64 impl crate::traits::AlgorithmInfo for AgeMoea { 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 { Some(self.config.seed) diff --git a/src/algorithms/ant_colony_tsp.rs b/src/algorithms/ant_colony_tsp.rs index ea3b419..96cd556 100644 --- a/src/algorithms/ant_colony_tsp.rs +++ b/src/algorithms/ant_colony_tsp.rs @@ -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 { Some(self.config.seed) diff --git a/src/algorithms/bayesian_opt.rs b/src/algorithms/bayesian_opt.rs index f382f4f..a682b51 100644 --- a/src/algorithms/bayesian_opt.rs +++ b/src/algorithms/bayesian_opt.rs @@ -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 { Some(self.config.seed) diff --git a/src/algorithms/cma_es.rs b/src/algorithms/cma_es.rs index f1c05c8..2be83e4 100644 --- a/src/algorithms/cma_es.rs +++ b/src/algorithms/cma_es.rs @@ -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 { Some(self.config.seed) diff --git a/src/algorithms/differential_evolution.rs b/src/algorithms/differential_evolution.rs index 003374d..1fee86d 100644 --- a/src/algorithms/differential_evolution.rs +++ b/src/algorithms/differential_evolution.rs @@ -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 { Some(self.config.seed) diff --git a/src/algorithms/epsilon_moea.rs b/src/algorithms/epsilon_moea.rs index 576531e..429b8a7 100644 --- a/src/algorithms/epsilon_moea.rs +++ b/src/algorithms/epsilon_moea.rs @@ -398,7 +398,10 @@ fn box_dominates(a: &[i64], b: &[i64]) -> bool { impl crate::traits::AlgorithmInfo for EpsilonMoea { fn name(&self) -> &'static str { - "EpsilonMoea" + "ε-MOEA" + } + fn full_name(&self) -> &'static str { + "ε-dominance Multi-Objective Evolutionary Algorithm" } fn seed(&self) -> Option { Some(self.config.seed) diff --git a/src/algorithms/genetic_algorithm.rs b/src/algorithms/genetic_algorithm.rs index 8f78814..ef17741 100644 --- a/src/algorithms/genetic_algorithm.rs +++ b/src/algorithms/genetic_algorithm.rs @@ -319,7 +319,10 @@ fn compare_for_fitness( impl crate::traits::AlgorithmInfo for GeneticAlgorithm { fn name(&self) -> &'static str { - "GeneticAlgorithm" + "GA" + } + fn full_name(&self) -> &'static str { + "Genetic Algorithm" } fn seed(&self) -> Option { Some(self.config.seed) diff --git a/src/algorithms/grea.rs b/src/algorithms/grea.rs index cad99f2..7eb9ee8 100644 --- a/src/algorithms/grea.rs +++ b/src/algorithms/grea.rs @@ -336,7 +336,10 @@ fn environmental_selection( impl crate::traits::AlgorithmInfo for Grea { fn name(&self) -> &'static str { - "Grea" + "GrEA" + } + fn full_name(&self) -> &'static str { + "Grid-based Evolutionary Algorithm" } fn seed(&self) -> Option { Some(self.config.seed) diff --git a/src/algorithms/hill_climber.rs b/src/algorithms/hill_climber.rs index 0e30b99..55dd7e6 100644 --- a/src/algorithms/hill_climber.rs +++ b/src/algorithms/hill_climber.rs @@ -226,7 +226,10 @@ impl HillClimber { impl crate::traits::AlgorithmInfo for HillClimber { fn name(&self) -> &'static str { - "HillClimber" + "Hill Climber" + } + fn full_name(&self) -> &'static str { + "Hill Climbing" } fn seed(&self) -> Option { Some(self.config.seed) diff --git a/src/algorithms/hype.rs b/src/algorithms/hype.rs index 1f45a39..d55ba5e 100644 --- a/src/algorithms/hype.rs +++ b/src/algorithms/hype.rs @@ -461,7 +461,10 @@ fn binary_tournament(fitness: &[f64], rng: &mut Rng) -> usize { impl crate::traits::AlgorithmInfo for Hype { fn name(&self) -> &'static str { - "Hype" + "HypE" + } + fn full_name(&self) -> &'static str { + "Hypervolume Estimation Algorithm" } fn seed(&self) -> Option { Some(self.config.seed) diff --git a/src/algorithms/hyperband.rs b/src/algorithms/hyperband.rs index 94098d7..a32f562 100644 --- a/src/algorithms/hyperband.rs +++ b/src/algorithms/hyperband.rs @@ -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 { Some(self.config.seed) } diff --git a/src/algorithms/ibea.rs b/src/algorithms/ibea.rs index 2916370..bfb6ea2 100644 --- a/src/algorithms/ibea.rs +++ b/src/algorithms/ibea.rs @@ -387,7 +387,10 @@ fn binary_tournament(fitness: &[f64], rng: &mut Rng) -> usize { impl crate::traits::AlgorithmInfo for Ibea { fn name(&self) -> &'static str { - "Ibea" + "IBEA" + } + fn full_name(&self) -> &'static str { + "Indicator-Based Evolutionary Algorithm" } fn seed(&self) -> Option { Some(self.config.seed) diff --git a/src/algorithms/ipop_cma_es.rs b/src/algorithms/ipop_cma_es.rs index 3bfc89c..fd0a552 100644 --- a/src/algorithms/ipop_cma_es.rs +++ b/src/algorithms/ipop_cma_es.rs @@ -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 { Some(self.config.seed) diff --git a/src/algorithms/knea.rs b/src/algorithms/knea.rs index 59bf0ad..87ea1d9 100644 --- a/src/algorithms/knea.rs +++ b/src/algorithms/knea.rs @@ -330,7 +330,10 @@ fn perpendicular_distance(point: &[f64], extremes: &[usize], oriented: &[Vec crate::traits::AlgorithmInfo for Knea { fn name(&self) -> &'static str { - "Knea" + "KnEA" + } + fn full_name(&self) -> &'static str { + "Knee point-driven Evolutionary Algorithm" } fn seed(&self) -> Option { Some(self.config.seed) diff --git a/src/algorithms/moead.rs b/src/algorithms/moead.rs index 78d6e96..4ad19d7 100644 --- a/src/algorithms/moead.rs +++ b/src/algorithms/moead.rs @@ -365,7 +365,10 @@ fn weight_distance(a: &[f64], b: &[f64]) -> f64 { impl crate::traits::AlgorithmInfo for Moead { 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 { Some(self.config.seed) diff --git a/src/algorithms/mopso.rs b/src/algorithms/mopso.rs index 0b07133..3c6ec99 100644 --- a/src/algorithms/mopso.rs +++ b/src/algorithms/mopso.rs @@ -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 { Some(self.config.seed) diff --git a/src/algorithms/nelder_mead.rs b/src/algorithms/nelder_mead.rs index 66f8853..f65865e 100644 --- a/src/algorithms/nelder_mead.rs +++ b/src/algorithms/nelder_mead.rs @@ -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" } } diff --git a/src/algorithms/nsga2.rs b/src/algorithms/nsga2.rs index bb27cef..afe579c 100644 --- a/src/algorithms/nsga2.rs +++ b/src/algorithms/nsga2.rs @@ -366,7 +366,10 @@ fn binary_tournament(entries: &[Nsga2Entry], rng: &mut Rng) -> usize { impl crate::traits::AlgorithmInfo for Nsga2 { fn name(&self) -> &'static str { - "Nsga2" + "NSGA-II" + } + fn full_name(&self) -> &'static str { + "Non-dominated Sorting Genetic Algorithm II" } fn seed(&self) -> Option { Some(self.config.seed) diff --git a/src/algorithms/nsga3.rs b/src/algorithms/nsga3.rs index bdfeaaf..69b2692 100644 --- a/src/algorithms/nsga3.rs +++ b/src/algorithms/nsga3.rs @@ -544,7 +544,10 @@ fn associate( impl crate::traits::AlgorithmInfo for Nsga3 { fn name(&self) -> &'static str { - "Nsga3" + "NSGA-III" + } + fn full_name(&self) -> &'static str { + "Non-dominated Sorting Genetic Algorithm III" } fn seed(&self) -> Option { Some(self.config.seed) diff --git a/src/algorithms/one_plus_one_es.rs b/src/algorithms/one_plus_one_es.rs index f133305..fc932ff 100644 --- a/src/algorithms/one_plus_one_es.rs +++ b/src/algorithms/one_plus_one_es.rs @@ -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 { Some(self.config.seed) diff --git a/src/algorithms/paes.rs b/src/algorithms/paes.rs index fa09dbc..04a5e7c 100644 --- a/src/algorithms/paes.rs +++ b/src/algorithms/paes.rs @@ -244,7 +244,10 @@ impl Paes { impl crate::traits::AlgorithmInfo for Paes { fn name(&self) -> &'static str { - "Paes" + "PAES" + } + fn full_name(&self) -> &'static str { + "Pareto Archived Evolution Strategy" } fn seed(&self) -> Option { Some(self.config.seed) diff --git a/src/algorithms/particle_swarm.rs b/src/algorithms/particle_swarm.rs index 103bf28..8a9798f 100644 --- a/src/algorithms/particle_swarm.rs +++ b/src/algorithms/particle_swarm.rs @@ -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 { Some(self.config.seed) diff --git a/src/algorithms/pesa2.rs b/src/algorithms/pesa2.rs index 83531e4..daf7d61 100644 --- a/src/algorithms/pesa2.rs +++ b/src/algorithms/pesa2.rs @@ -411,7 +411,10 @@ fn truncate_by_grid(archive: &mut ParetoArchive, max_size: usize, d impl crate::traits::AlgorithmInfo for PesaII { fn name(&self) -> &'static str { - "PesaII" + "PESA-II" + } + fn full_name(&self) -> &'static str { + "Pareto Envelope-based Selection Algorithm II" } fn seed(&self) -> Option { Some(self.config.seed) diff --git a/src/algorithms/random_search.rs b/src/algorithms/random_search.rs index 53f7b01..a13a31c 100644 --- a/src/algorithms/random_search.rs +++ b/src/algorithms/random_search.rs @@ -160,7 +160,7 @@ impl RandomSearch { impl crate::traits::AlgorithmInfo for RandomSearch { fn name(&self) -> &'static str { - "RandomSearch" + "Random Search" } fn seed(&self) -> Option { Some(self.config.seed) diff --git a/src/algorithms/rvea.rs b/src/algorithms/rvea.rs index 29ed538..5e4e7ae 100644 --- a/src/algorithms/rvea.rs +++ b/src/algorithms/rvea.rs @@ -468,7 +468,10 @@ fn smallest_neighbor_angle(references: &[Vec]) -> f64 { impl crate::traits::AlgorithmInfo for Rvea { fn name(&self) -> &'static str { - "Rvea" + "RVEA" + } + fn full_name(&self) -> &'static str { + "Reference Vector-guided Evolutionary Algorithm" } fn seed(&self) -> Option { Some(self.config.seed) diff --git a/src/algorithms/simulated_annealing.rs b/src/algorithms/simulated_annealing.rs index 929609a..f021651 100644 --- a/src/algorithms/simulated_annealing.rs +++ b/src/algorithms/simulated_annealing.rs @@ -335,7 +335,7 @@ impl SimulatedAnnealing { impl crate::traits::AlgorithmInfo for SimulatedAnnealing { fn name(&self) -> &'static str { - "SimulatedAnnealing" + "Simulated Annealing" } fn seed(&self) -> Option { Some(self.config.seed) diff --git a/src/algorithms/sms_emoa.rs b/src/algorithms/sms_emoa.rs index b17ddbf..58df323 100644 --- a/src/algorithms/sms_emoa.rs +++ b/src/algorithms/sms_emoa.rs @@ -291,7 +291,10 @@ fn pick_drop_index( impl crate::traits::AlgorithmInfo for SmsEmoa { 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 { Some(self.config.seed) diff --git a/src/algorithms/snes.rs b/src/algorithms/snes.rs index f87cd4c..a1e1916 100644 --- a/src/algorithms/snes.rs +++ b/src/algorithms/snes.rs @@ -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 { Some(self.config.seed) diff --git a/src/algorithms/spea2.rs b/src/algorithms/spea2.rs index 2c867c8..b9fcb32 100644 --- a/src/algorithms/spea2.rs +++ b/src/algorithms/spea2.rs @@ -505,7 +505,10 @@ fn binary_tournament(fitness: &[f64], rng: &mut Rng) -> usize { impl crate::traits::AlgorithmInfo for Spea2 { fn name(&self) -> &'static str { - "Spea2" + "SPEA2" + } + fn full_name(&self) -> &'static str { + "Strength Pareto Evolutionary Algorithm 2" } fn seed(&self) -> Option { Some(self.config.seed) diff --git a/src/algorithms/tabu_search.rs b/src/algorithms/tabu_search.rs index 21d8754..bf53533 100644 --- a/src/algorithms/tabu_search.rs +++ b/src/algorithms/tabu_search.rs @@ -338,7 +338,7 @@ where N: FnMut(&D, &mut Rng) -> Vec, { fn name(&self) -> &'static str { - "TabuSearch" + "Tabu Search" } fn seed(&self) -> Option { Some(self.config.seed) diff --git a/src/algorithms/tlbo.rs b/src/algorithms/tlbo.rs index 2c818d7..04fdea8 100644 --- a/src/algorithms/tlbo.rs +++ b/src/algorithms/tlbo.rs @@ -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 { Some(self.config.seed) diff --git a/src/algorithms/tpe.rs b/src/algorithms/tpe.rs index 2ebaa57..9a7a805 100644 --- a/src/algorithms/tpe.rs +++ b/src/algorithms/tpe.rs @@ -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 { Some(self.config.seed) diff --git a/src/algorithms/umda.rs b/src/algorithms/umda.rs index 71ce958..23f5e1f 100644 --- a/src/algorithms/umda.rs +++ b/src/algorithms/umda.rs @@ -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 { Some(self.config.seed) diff --git a/src/explorer/mod.rs b/src/explorer/mod.rs index 6cbbb1a..b8af6d7 100644 --- a/src/explorer/mod.rs +++ b/src/explorer/mod.rs @@ -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, - /// 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, + /// 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, /// 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(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 { 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)); } diff --git a/src/traits/algorithm_info.rs b/src/traits/algorithm_info.rs index 19b2925..6ed24d4 100644 --- a/src/traits/algorithm_info.rs +++ b/src/traits/algorithm_info.rs @@ -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

`](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 { None