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.
43 lines
1.7 KiB
Rust
43 lines
1.7 KiB
Rust
//! The user-implemented `Problem` trait.
|
|
|
|
use crate::core::decision_variable::DecisionVariable;
|
|
use crate::core::evaluation::Evaluation;
|
|
use crate::core::objective::ObjectiveSpace;
|
|
|
|
/// An optimization problem.
|
|
///
|
|
/// Implement this trait to describe what the optimizer is allowed to vary
|
|
/// (`Decision`), how many objectives it has (`objectives`), and how to score a
|
|
/// decision (`evaluate`).
|
|
///
|
|
/// Example decision types: `Vec<f64>`, `Vec<bool>`, `Vec<i64>`, custom domain
|
|
/// structs, or permutations represented as `Vec<usize>`.
|
|
pub trait Problem {
|
|
/// The thing the optimizer changes. Must be `Clone` because heuristic
|
|
/// algorithms routinely clone decisions.
|
|
type Decision: Clone;
|
|
|
|
/// Return the objectives for this problem.
|
|
///
|
|
/// Returned by value for ergonomics — problems do not need to store an
|
|
/// `ObjectiveSpace` field.
|
|
fn objectives(&self) -> ObjectiveSpace;
|
|
|
|
/// Evaluate a decision. Must not mutate `self`.
|
|
fn evaluate(&self, decision: &Self::Decision) -> Evaluation;
|
|
|
|
/// Optional schema describing each decision variable — names,
|
|
/// labels, units, and bounds. Used by the explorer JSON export
|
|
/// to label decision-variable axes with the user's preferred
|
|
/// names and units. Default: empty (the exporter generates
|
|
/// fallback names like `x[0]`, `x[1]`).
|
|
///
|
|
/// Override this on your `Problem` impl to provide pretty
|
|
/// metadata. The returned vector should have one entry per
|
|
/// element of the decision; if its length doesn't match, the
|
|
/// exporter fills the remainder with `x[i]` defaults.
|
|
fn decision_schema(&self) -> Vec<DecisionVariable> {
|
|
Vec::new()
|
|
}
|
|
}
|