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.
This commit is contained in:
@@ -0,0 +1,244 @@
|
||||
# Defining a problem
|
||||
|
||||
Everything in heuropt starts with the [`Problem`] trait. This chapter
|
||||
walks through every shape it can take.
|
||||
|
||||
## The trait
|
||||
|
||||
```rust,ignore
|
||||
pub trait Problem {
|
||||
type Decision: Clone;
|
||||
fn objectives(&self) -> ObjectiveSpace;
|
||||
fn evaluate(&self, decision: &Self::Decision) -> Evaluation;
|
||||
}
|
||||
```
|
||||
|
||||
Three things you decide:
|
||||
|
||||
1. **`Decision`** — the type of the thing you're optimizing.
|
||||
`Vec<f64>` is by far the most common; `Vec<bool>` for binary
|
||||
search, `Vec<usize>` for permutations, your own struct for
|
||||
anything else.
|
||||
2. **`objectives`** — how many objectives you have, what they're
|
||||
called, and whether each is minimized or maximized. Returned as
|
||||
an [`ObjectiveSpace`].
|
||||
3. **`evaluate`** — given one decision, score it. Returns an
|
||||
[`Evaluation`] with a vector of objective values (and optionally
|
||||
a constraint-violation scalar).
|
||||
|
||||
`evaluate` takes `&self`, so caches and lookup tables are easy. It
|
||||
is called many thousands of times during a typical run, so keep it
|
||||
fast.
|
||||
|
||||
## Single-objective continuous
|
||||
|
||||
The Rosenbrock banana — minimize a smooth non-convex valley.
|
||||
|
||||
```rust,no_run
|
||||
use heuropt::prelude::*;
|
||||
|
||||
struct Rosenbrock;
|
||||
|
||||
impl Problem for Rosenbrock {
|
||||
type Decision = Vec<f64>;
|
||||
|
||||
fn objectives(&self) -> ObjectiveSpace {
|
||||
ObjectiveSpace::new(vec![Objective::minimize("f")])
|
||||
}
|
||||
|
||||
fn evaluate(&self, x: &Vec<f64>) -> Evaluation {
|
||||
let f: f64 = x.windows(2)
|
||||
.map(|w| 100.0 * (w[1] - w[0].powi(2)).powi(2) + (1.0 - w[0]).powi(2))
|
||||
.sum();
|
||||
Evaluation::new(vec![f])
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Multi-objective
|
||||
|
||||
ZDT1 — two objectives that conflict. The Pareto front is the set of
|
||||
non-dominated trade-offs.
|
||||
|
||||
```rust,no_run
|
||||
use heuropt::prelude::*;
|
||||
|
||||
struct Zdt1 { dim: usize }
|
||||
|
||||
impl Problem for Zdt1 {
|
||||
type Decision = Vec<f64>;
|
||||
|
||||
fn objectives(&self) -> ObjectiveSpace {
|
||||
ObjectiveSpace::new(vec![
|
||||
Objective::minimize("f1"),
|
||||
Objective::minimize("f2"),
|
||||
])
|
||||
}
|
||||
|
||||
fn evaluate(&self, x: &Vec<f64>) -> Evaluation {
|
||||
let n = x.len() as f64;
|
||||
let f1 = x[0];
|
||||
let g = 1.0 + 9.0 * x[1..].iter().sum::<f64>() / (n - 1.0);
|
||||
let h = 1.0 - (f1 / g).sqrt();
|
||||
let f2 = g * h;
|
||||
Evaluation::new(vec![f1, f2])
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
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
|
||||
disconnected fronts. See [choosing-an-algorithm](./choosing-an-algorithm.md).
|
||||
|
||||
## Maximizing instead of minimizing
|
||||
|
||||
heuropt's internals normalize everything to minimization, but you
|
||||
declare your objective with the orientation that's natural for your
|
||||
problem. A scoring problem might want to maximize:
|
||||
|
||||
```rust,no_run
|
||||
use heuropt::prelude::*;
|
||||
let space = ObjectiveSpace::new(vec![
|
||||
Objective::minimize("cost"),
|
||||
Objective::maximize("accuracy"),
|
||||
]);
|
||||
```
|
||||
|
||||
`Objective::maximize` is a convenience for `Direction::Maximize`. Mix
|
||||
freely; the Pareto-comparison machinery handles the orientation.
|
||||
|
||||
## Constraints
|
||||
|
||||
heuropt models constraints as a single non-negative scalar
|
||||
**`constraint_violation`** on each `Evaluation`. The convention:
|
||||
|
||||
- `0.0` (or negative) means **feasible**.
|
||||
- Any positive value means **infeasible**, and bigger numbers are
|
||||
worse violations.
|
||||
|
||||
Pareto-comparison and tournament-selection helpers prefer feasible
|
||||
candidates and break ties on the violation magnitude — so the rule
|
||||
"feasibility comes first" is enforced automatically.
|
||||
|
||||
```rust,no_run
|
||||
use heuropt::prelude::*;
|
||||
|
||||
struct Constrained;
|
||||
impl Problem for Constrained {
|
||||
type Decision = Vec<f64>;
|
||||
|
||||
fn objectives(&self) -> ObjectiveSpace {
|
||||
ObjectiveSpace::new(vec![Objective::minimize("f")])
|
||||
}
|
||||
|
||||
fn evaluate(&self, x: &Vec<f64>) -> Evaluation {
|
||||
let f: f64 = x.iter().map(|v| v * v).sum();
|
||||
|
||||
// Constraint: x[0] + x[1] >= 1. Violation = how much we miss it by.
|
||||
let g1 = (1.0 - (x[0] + x[1])).max(0.0);
|
||||
let total_violation: f64 = g1; // sum of max(0, gᵢ) for each constraint
|
||||
|
||||
Evaluation::constrained(vec![f], total_violation)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If your constraints are very tight and the search keeps hitting them,
|
||||
see [Constrain your search with `Repair`](./cookbook/constraints.md).
|
||||
|
||||
## Decision types beyond `Vec<f64>`
|
||||
|
||||
### Binary (`Vec<bool>`)
|
||||
|
||||
```rust,no_run
|
||||
use heuropt::prelude::*;
|
||||
|
||||
struct OneMax { bits: usize }
|
||||
impl Problem for OneMax {
|
||||
type Decision = Vec<bool>;
|
||||
fn objectives(&self) -> ObjectiveSpace {
|
||||
ObjectiveSpace::new(vec![Objective::maximize("ones")])
|
||||
}
|
||||
fn evaluate(&self, x: &Vec<bool>) -> Evaluation {
|
||||
Evaluation::new(vec![x.iter().filter(|b| **b).count() as f64])
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
For `Vec<bool>` problems, [`Umda`] is a parameter-free EDA;
|
||||
[`GeneticAlgorithm`] with [`BitFlipMutation`] is the GA route.
|
||||
|
||||
### Permutations (`Vec<usize>`)
|
||||
|
||||
```rust,no_run
|
||||
use heuropt::prelude::*;
|
||||
|
||||
struct Tsp { distances: Vec<Vec<f64>> }
|
||||
impl Problem for Tsp {
|
||||
type Decision = Vec<usize>;
|
||||
fn objectives(&self) -> ObjectiveSpace {
|
||||
ObjectiveSpace::new(vec![Objective::minimize("length")])
|
||||
}
|
||||
fn evaluate(&self, tour: &Vec<usize>) -> Evaluation {
|
||||
let mut len = 0.0;
|
||||
for w in tour.windows(2) {
|
||||
len += self.distances[w[0]][w[1]];
|
||||
}
|
||||
len += self.distances[*tour.last().unwrap()][tour[0]];
|
||||
Evaluation::new(vec![len])
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
For permutations, [`AntColonyTsp`] specializes on TSP-style problems;
|
||||
[`TabuSearch`] takes a user-supplied neighbor function for arbitrary
|
||||
discrete neighborhoods; [`SimulatedAnnealing`] with [`SwapMutation`]
|
||||
is the simplest baseline.
|
||||
|
||||
### Custom decision types
|
||||
|
||||
Any `Clone` type works. If you have a struct, just implement `Clone`
|
||||
and you can use it. You'll need to write your own `Variation` impl
|
||||
to mutate it; see [Write your own algorithm](./cookbook/custom-optimizer.md).
|
||||
|
||||
## What `Evaluation` carries
|
||||
|
||||
```rust,ignore
|
||||
pub struct Evaluation {
|
||||
pub objectives: Vec<f64>, // one entry per objective
|
||||
pub constraint_violation: f64, // 0.0 = feasible
|
||||
}
|
||||
```
|
||||
|
||||
That's it. Construct with [`Evaluation::new`] for unconstrained
|
||||
problems or [`Evaluation::constrained`] when you have a violation.
|
||||
|
||||
## Summary
|
||||
|
||||
- Implement [`Problem`] with your decision type.
|
||||
- Declare objectives via [`ObjectiveSpace`] (mix minimize/maximize
|
||||
freely).
|
||||
- Return an [`Evaluation`] from `evaluate`.
|
||||
- For constraints, set `constraint_violation > 0` for infeasible
|
||||
decisions; heuropt's selection helpers prefer feasibles
|
||||
automatically.
|
||||
|
||||
Next: [Choosing an algorithm](./choosing-an-algorithm.md) walks
|
||||
through the decision tree.
|
||||
|
||||
[`Problem`]: https://docs.rs/heuropt/latest/heuropt/core/problem/trait.Problem.html
|
||||
[`ObjectiveSpace`]: https://docs.rs/heuropt/latest/heuropt/core/objective/struct.ObjectiveSpace.html
|
||||
[`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
|
||||
[`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
|
||||
[`SwapMutation`]: https://docs.rs/heuropt/latest/heuropt/operators/struct.SwapMutation.html
|
||||
Reference in New Issue
Block a user