Commit Graph
18 Commits
Author SHA1 Message Date
swaits a0f5b9a660 chore(release): v0.11.0
Bump Cargo.toml to 0.11.0, pin the README install snippet to "0.11",
and add the 0.11.0 CHANGELOG section.

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

No public-API breaks — the release is purely additive.
2026-05-14 14:32:39 -06:00
swaitsandClaude Opus 4.7 1cecd44511 test(bench): profile the full compare workload with gungraun
Adds benches/compare_profile.rs: a gungraun library_benchmark that runs
the entire `compare` workload once at seed 0 under callgrind. It path-
includes the shared examples/_shared/compare_workload.rs module and calls
the new profile_workload() entry point, which invokes all 82 algorithm
runners and folds every result into a checksum so nothing is elided.

gungraun reports the whole-program instruction count and diffs it against
the previous run; the saved callgrind.out
(target/gungraun/heuropt/compare_profile/.../callgrind.full_compare_workload.out)
carries the per-function breakdown for callgrind_annotate. This is the
measurement harness for the function-level optimization campaign.

Round-0 baseline: 357,060,633,544 Ir. The shared module also carries the
example's presentation layer, so the bench gets a commented
`#![allow(dead_code)]` -- but the runner functions are deliberately not
allow-listed, so a runner missing from profile_workload still warns (that
check already caught one omission).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 11:24:19 -06:00
swaits 6371d82f40 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.
2026-05-06 22:45:59 -06:00
swaits 729842c260 feat(explorer): JSON export module + supporting metadata + example
Adds a tiny additive surface that turns any OptimizationResult into
a self-describing JSON file the heuropt-explorer webapp can load.
Real Pareto fronts have 50–200+ candidates spanning 2–7+ objectives;
reading them as numbers in a terminal scales badly. This commit
ships the heuropt-side of the explorer — the schema and the export
API. The webapp itself lives in a separate repo on its own cadence.

Three trait/type extensions, all with working defaults so existing
impls compile untouched:

- Objective gains optional `label: Option<String>` and
  `unit: Option<String>` fields, plus fluent builders
  `.with_label("Price").with_unit(\"\$k\")`. Existing
  `Objective::minimize(name)` / `Objective::maximize(name)` are
  unchanged. Both fields are #[serde(default,
  skip_serializing_if = \"Option::is_none\")] so existing JSON
  round-trips cleanly.
- Problem trait gains an optional
  `fn decision_schema(&self) -> Vec<DecisionVariable>` with default
  empty impl. Override it to provide pretty names / labels / units /
  bounds for the explorer; the default produces fallback x[0],
  x[1], … names. New DecisionVariable type at
  `heuropt::core::DecisionVariable` with builder methods.
- New `heuropt::traits::AlgorithmInfo` trait with `name()`
  (required) and `seed()` (default None). Every built-in algorithm
  — all 33 — implements it. Separate from Optimizer<P> so
  multi-fidelity Hyperband (which uses PartialProblem) implements
  it uniformly.

The new explorer module:

- `heuropt::explorer::ExplorerExport` envelope with versioned
  schema (SCHEMA_VERSION = 1).
- ExplorerCandidate per row, with front_rank from
  non_dominated_sort attached at export time so downstream tools
  don't re-derive it.
- ToDecisionValues adapter trait with provided impls for Vec<f64>,
  Vec<bool>, Vec<usize>, Vec<i64>; custom decision types implement
  one method.
- Free functions to_json / to_writer / to_file plus a builder API
  (with_algorithm_info, with_problem_name, with_wall_clock,
  with_timestamp).
- Gated on the existing `serde` feature, which now also pulls in
  `serde_json` as a dep.

The example:

- `examples/pick_a_car.rs` — promotes the README's PickACar to a
  real example, fully enriched with Objective labels/units and a
  decision_schema. Runs NSGA-III for 200 generations, prints a
  sample slice, writes pick_a_car.json. Gated on `serde`.

10 new explorer unit tests cover round-trip serde, fallback
decision-variable names, enriched export, AlgorithmInfo flow,
front-rank correctness, and the ToDecisionValues impls. Lib test
count went from 229 to 242.
2026-05-06 12:48:01 -06:00
swaits af226e3d3b feat: drop heuropt-plot companion crate
Removes the heuropt-plot subcrate, the visualize example that used
it, and the related workspace plumbing (root [workspace] table, the
[workspace] override added to fuzz/Cargo.toml to detach from it,
heuropt-plot dev-dep, CHANGELOG mention).

The visualization concern is better served as an independent third-
party project than as a companion crate in this repo. No effect on
heuropt's public API or the async work in 0.8.0.
2026-05-06 09:04:13 -06:00
swaits 5b5fe50df3 feat(heuropt-plot): v0.1.0 — SVG visualization companion crate
Adds heuropt-plot, a tiny SVG-only plotter that takes heuropt
results and emits scatter plots (pareto_front_svg) and line plots
(convergence_svg). No heavy 'plotters' or 'tiny-skia' dep — hand-
rolled SVG so the crate adds <100 KB to a build.

Workspace setup: root Cargo.toml gains [workspace] with members =
['.', 'heuropt-plot']. heuropt-plot has its own version (0.1.0) and
publishes independently against heuropt 0.8+.

Adds examples/visualize.rs that wires it up: NSGA-II on Schaffer
N.1, plain run() (no observer plumbing), final-front SVG written to
disk.
2026-05-06 07:58:08 -06:00
swaits 6368ca5f3d feat(async): AsyncProblem trait + run_async on RandomSearch and DifferentialEvolution
Adds the headline async/await capability for IO-bound evaluations
(HTTP services, RPC clients, spawned subprocesses) — the
differentiator vs pymoo / hyperopt / MOEA Framework.

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

- core::async_problem::AsyncProblem trait (async fn evaluate_async).
- algorithms::parallel_eval_async::evaluate_batch_async helper using
  futures::stream::FuturesOrdered with concurrency-bounded chunks;
  preserves input order so seeded determinism holds when evaluations
  are themselves deterministic.
- run_async on RandomSearch and DifferentialEvolution.
- examples/async_eval.rs: simulated 20 ms remote service. concurrency=1
  → 4.2 s, concurrency=4 → 2.1 s (2× speedup).

Bumps Cargo.toml to 0.8.0; CHANGELOG entry covers the above plus a
note that 0.6.0/0.7.0 on crates.io are yanked experimentals and 0.8
picks up cleanly from 0.5.
2026-05-06 07:55:56 -06:00
swaits fa3f2e8fb0 feat: v0.5.0 — comprehensive documentation release
Theme: documentation and project polish. No public-API changes; this
is the v0.5 release that elevates heuropt's docs/onboarding/governance
to bar-setting status.

Adds:
- mdbook user guide at docs/book/ with intro, getting-started,
  defining-problems, choosing-an-algorithm, cookbook (7 recipes),
  comparison vs other libraries, stability/SemVer, migration guides.
  Deploys to https://swaits.github.io/heuropt/ via .github/workflows/
  docs.yml.
- Runnable rustdoc examples on every algorithm (35 of them), all
  exercised by cargo test --doc.
- Three real-world examples: portfolio.rs (multi-obj with budget
  constraint), hyperparam_tuning.rs (BO + TPE), scheduling.rs
  (permutation via SA + SwapMutation against Smith's-rule oracle).
- Governance: CONTRIBUTING.md, SECURITY.md, CODE_OF_CONDUCT.md
  (adopting builderscode.org's Builder's Code of Conduct), GitHub
  issue templates, PR template.

Polishes:
- README hero with badges + user-guide link.
- lib.rs crate-level docs.
- CHANGELOG entry for 0.5.0.

Bumps Cargo.toml to 0.5.0.
2026-05-05 14:33:12 -06:00
swaits 232dbc0172 chore(release): bump to v0.4.0
CHANGELOG entry consolidates the unreleased work since v0.3.0:
testing-infrastructure expansion (proptest suites, cargo-fuzz
harness, stability tests, gungraun benches, CI), two real bug
fixes the testing surfaced (NaN-cycle non_dominated_sort, simplex
projection magnitude precision), the README decision-tree update
against the v0.3.0 comparison data, and the v0.4.0 perf pass
(cumulative compare harness 18.6 s → 5.7 s, 3.27×).

`examples/compare-results.md` refreshed with the post-perf-pass
ms numbers; quality metrics are bit-identical to the v0.3.0
snapshot (the perf pass was strictly CPU time, never algorithmic).
2026-05-05 13:28:47 -06:00
swaits 060841d011 build(release): enable thin LTO + codegen-units=1 in release profile
Cuts ~150 ms (-2.5 %) off the compare harness via better cross-crate
inlining of small Pareto/HV helpers. Costs ~20 s extra on a from-
scratch `cargo build --release`, but is essentially free on
incremental rebuilds.

Only applies when this crate is the workspace root (i.e. when
developing heuropt or running its own examples). Downstream users
who consume heuropt as a dependency see whatever profile their own
Cargo.toml configures.
2026-05-05 13:28:18 -06:00
swaits dcf63316f6 test(proptest): add property-based tests for invariants
Adds proptest as a dev-dependency and a `tests/properties.rs`
integration suite that probes invariants on randomly generated
inputs:

Pareto invariants:
- `pareto_compare` is anti-symmetric: A→B is opposite of B→A for
  Dominates / DominatedBy
- `pareto_compare` is reflexive on equal candidates (returns Equal)
- `pareto_front` output is internally non-dominated
- `non_dominated_sort` puts every member into exactly one front
- `crowding_distance` returns Vec same length as front; boundary
  points are infinity for fronts of size ≥ 2 in any axis-sortable
  configuration

Operator invariants:
- `SimulatedBinaryCrossover` returns 2 children of the right length,
  all in bounds
- `PolynomialMutation` returns 1 child of the right length, in bounds
- `BoundedGaussianMutation` returns 1 child in bounds
- `ClampToBounds` repair always lands in bounds
- `ProjectToSimplex` repair always sums to total and is non-negative

Algorithm invariants:
- For any seed, `Optimizer::run` is deterministic across two calls
- Final population has the documented size for population-based
  algorithms

These are the invariants the existing 226 fixed-input unit tests
collectively check; proptest gives us coverage on inputs they don't
cover individually.
2026-05-05 10:38:53 -06:00
swaits 0b31b266ef build(deps): add gungraun (was iai-callgrind) instruction-count benches
Wire `gungraun` 0.18 as a dev-dependency and a `benches/` directory
with instruction-count benchmarks for the algorithmic hot paths.

Why gungraun and not criterion: heuropt's hot paths are deterministic
numerical loops where wall-clock noise dominates real differences.
gungraun runs each benchmark under valgrind/callgrind once and reports
exact instruction counts — stable across machines and CI runners,
detects sub-microsecond regressions cleanly.

Benchmarks added:
- pareto::non_dominated_sort  (the inner loop of every Pareto MOEA)
- pareto::crowding_distance   (NSGA-II survival selection)
- metrics::hypervolume_nd     (HSO recursion, used by SMS-EMOA)
- internal::cholesky          (BO's per-step posterior factorization)
- algorithms::nsga2 single generation (end-to-end smoke check)
- algorithms::cma_es single generation (eigendecomposition cost)

Tracked size only — these aren't part of the regular CI matrix because
they need valgrind installed. Run with `cargo bench` locally.

Wired via the standard `[[bench]]` Cargo entries with `harness = false`
so gungraun's main_macro does the dispatch.
2026-05-05 10:36:29 -06:00
swaits 9ae1df68cb chore(release): roll up v0.3.0 — expensive-eval, gradient-free, multi-fidelity
CHANGELOG entry for the v0.3.0 cohort, version bump in Cargo.toml and
README. Theme: filling heuropt's expensive-evaluation and constraint-
handling gaps.

Algorithms (9 new): OnePlusOneEs, NelderMead, IpopCmaEs, BayesianOpt,
SeparableNes, Tpe, Hyperband.

Operators (1 new): LevyMutation. Repair operators (1 trait + 2 impls):
Repair<D> with ClampToBounds and ProjectToSimplex.

Selection helpers (1 new): stochastic_ranking_select.

Internal helpers: Cholesky factorization (used by BO).

API additions:
- CmaEsConfig.initial_mean: Option<Vec<f64>> (None preserves existing
  midpoint-of-bounds behavior; used by IpopCmaEs to inject restart
  diversity).
- New PartialProblem trait — multi-fidelity contract used by
  Hyperband.

No breaking changes to v0.2.0 public API.
2026-05-05 10:00:00 -06:00
swaits 5a0475c678 chore(release): bump to v0.2.0 and update CHANGELOG
Substantial v0.2.0 release on top of v0.1.0:

**21 new algorithms:**
- Single-objective: HillClimber, SimulatedAnnealing, GeneticAlgorithm,
  ParticleSwarm, CmaEs, TabuSearch, AntColonyTsp, Umda, Tlbo
- Multi-objective: Mopso, Ibea, SmsEmoa, Hype, Rvea, PesaII,
  EpsilonMoea, AgeMoea, Grea, Knea

**5 new operators:**
BoundedGaussianMutation, SimulatedBinaryCrossover (SBX),
PolynomialMutation, CompositeVariation, LevyMutation

**New utility:** `hypervolume_nd` (HSO algorithm) for arbitrary
dimensionality

**New examples:** `compare` (multi-seed harness across 7 benchmark
problems and 19 algorithms), `benchmarks` (canonical reference runs),
`jiggly_tuning` (real-world 4-objective firmware tuning)

**New feature flag:** `parallel` (rayon-backed population evaluation)

**README:** added an explanatory algorithm-selection decision tree

No breaking changes to v0.1.0 public API.
2026-05-05 09:51:12 -06:00
swaits b91c26d86e chore(release): prepare v0.1.0 — Cargo metadata, CHANGELOG, README polish
- Cargo.toml: add `repository`, `homepage`, `documentation`,
  `keywords`, `categories`, `authors`, and `rust-version = "1.85"`
  (the version that stabilized edition 2024).
- CHANGELOG.md: new file in Keep-a-Changelog format with the full
  v0.1.0 inventory (core, traits, pareto utilities, operators,
  algorithms, metrics, examples, and the `serde`/`parallel` features).
- README.md: add crates.io / docs.rs / license badges and a Changelog
  link.

Pre-release verification (all clean):
- cargo build (default + --features parallel)
- cargo test (default, parallel, serde, --all-features) — 111 lib +
  3 doc tests pass under each.
- cargo clippy --all-targets --all-features -- -D warnings
- cargo doc --no-deps
- cargo package --no-verify → 57 files, 261 KB
2026-05-04 20:38:46 -06:00
swaits 9aaa4402a8 feat: add optional parallel feature for population-evaluation parallelism
Adds a `parallel` Cargo feature that pulls in rayon and parallelizes
the only step that's actually expensive in practice — calls to
`Problem::evaluate` — across the population. RNG-driven steps (parent
and donor selection, variation, replacement decisions) stay serial, so
seeded runs remain deterministic regardless of feature state, and the
default and `--features parallel` builds produce bit-identical
results.

Wiring:
- New `algorithms::parallel_eval::evaluate_batch` helper with two
  cfg-gated implementations (rayon's `into_par_iter` when the feature
  is on, plain `into_iter` otherwise). Both preserve input order, so
  pareto_front and crowding-distance decisions remain reproducible.
- `RandomSearch`, `Nsga2`, and `DifferentialEvolution` now route
  population/offspring evaluation through the helper. NSGA-II's main
  loop is restructured into a serial selection-and-variation phase
  followed by a parallel-friendly batch evaluation phase.
- DE's per-target loop is restructured into three phases (serial trial
  construction → batch evaluation → serial replacement). Side effect
  of the restructuring: DE is now the canonical synchronous DE/rand/1/bin
  rather than the asynchronous variant where target `i+1` sees `i`'s
  in-flight update. Synchronous is the textbook formulation, so this
  is a small correctness improvement on top of the parallelism enable.
- PAES stays serial — its main loop has a sequential dependency on the
  current candidate and would gain nothing from rayon.

Cost: algorithm impls now require `P: Sync` and `P::Decision: Send`
unconditionally so a single impl serves both feature modes. This is a
small bound tightening that any plain-data Problem already satisfies; in
return the public `Problem` trait itself stays unchanged and the
default build picks up no new dependencies.

Verified:
- `cargo test` and `cargo test --features parallel` both pass; the
  Nsga2 `deterministic_with_same_seed` test confirms reproducibility.
- `cargo run --release --example benchmarks` and the same with
  `--features parallel` produce bit-identical ZDT1 / Rastrigin
  results.
2026-05-04 19:39:43 -06:00
swaits b827310822 chore: switch license to MIT and add rand/serde dependencies
- License the crate as MIT only.
- Fill in package description and `readme` field.
- Add rand 0.9 and rand_distr 0.5 (the `StdRng` and Normal sampler the
  spec mandates as the single Rng type).
- Add optional serde 1 gated behind a `serde` feature flag for later
  derives on core data types.
2026-05-04 19:18:01 -06:00
swaits 45f9080225 chore: scaffold empty cargo lib crate and add design spec
Initial state from `cargo new --lib` plus the technical design spec at
docs/heuropt_tech_design_spec.md, which is the source of truth for the
crate's public API and v1 acceptance criteria.
2026-05-04 19:18:00 -06:00