- 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
148 lines
4.5 KiB
Markdown
148 lines
4.5 KiB
Markdown
# heuropt
|
|
|
|
[](https://crates.io/crates/heuropt)
|
|
[](https://docs.rs/heuropt)
|
|
[](LICENSE)
|
|
|
|
A practical Rust toolkit for implementing heuristic single-objective,
|
|
multi-objective, and many-objective optimization algorithms.
|
|
|
|
`heuropt` is **not** a research framework full of abstract machinery — it is a
|
|
small set of concrete types, a handful of simple traits, and a few reference
|
|
algorithms. The goal: an entry-level Rust engineer can define a problem, run a
|
|
built-in optimizer, or implement a new optimizer without learning any
|
|
framework concepts.
|
|
|
|
## Installation
|
|
|
|
```toml
|
|
[dependencies]
|
|
heuropt = "0.1"
|
|
|
|
# Optional features:
|
|
# - "serde": derive Serialize/Deserialize on the core data types.
|
|
# - "parallel": evaluate populations across rayon's thread pool.
|
|
# Seeded runs stay bit-identical to serial mode.
|
|
# heuropt = { version = "0.1", features = ["serde", "parallel"] }
|
|
```
|
|
|
|
## Define a problem
|
|
|
|
```rust
|
|
use heuropt::prelude::*;
|
|
|
|
struct SchafferN1;
|
|
|
|
impl Problem for SchafferN1 {
|
|
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 v = x[0];
|
|
Evaluation::new(vec![v * v, (v - 2.0).powi(2)])
|
|
}
|
|
}
|
|
```
|
|
|
|
## Run NSGA-II
|
|
|
|
```rust
|
|
use heuropt::prelude::*;
|
|
|
|
# struct SchafferN1;
|
|
# impl Problem for SchafferN1 {
|
|
# type Decision = Vec<f64>;
|
|
# fn objectives(&self) -> ObjectiveSpace {
|
|
# ObjectiveSpace::new(vec![Objective::minimize("f1"), Objective::minimize("f2")])
|
|
# }
|
|
# fn evaluate(&self, x: &Vec<f64>) -> Evaluation {
|
|
# Evaluation::new(vec![x[0] * x[0], (x[0] - 2.0).powi(2)])
|
|
# }
|
|
# }
|
|
let initializer = RealBounds::new(vec![(-5.0, 5.0)]);
|
|
let variation = GaussianMutation { sigma: 0.2 };
|
|
let config = Nsga2Config { population_size: 60, generations: 80, seed: 42 };
|
|
let mut optimizer = Nsga2::new(config, initializer, variation);
|
|
let result = optimizer.run(&SchafferN1);
|
|
|
|
println!("Pareto front size: {}", result.pareto_front.len());
|
|
```
|
|
|
|
See `examples/toy_nsga2.rs` for the full version.
|
|
|
|
## Implement a custom optimizer
|
|
|
|
A new optimizer is just an implementation of `Optimizer<P>`:
|
|
|
|
```rust
|
|
use heuropt::prelude::*;
|
|
|
|
struct MyOptimizer { /* state */ }
|
|
|
|
impl<P> Optimizer<P> for MyOptimizer
|
|
where
|
|
P: Problem<Decision = Vec<f64>>,
|
|
{
|
|
fn run(&mut self, problem: &P) -> OptimizationResult<P::Decision> {
|
|
// Generate candidates.
|
|
// Evaluate them with `problem.evaluate(...)`.
|
|
// Keep the best, or maintain a Pareto archive.
|
|
// Return an OptimizationResult.
|
|
# OptimizationResult::new(
|
|
# Population::new(Vec::new()),
|
|
# Vec::new(),
|
|
# None,
|
|
# 0,
|
|
# 0,
|
|
# )
|
|
}
|
|
}
|
|
```
|
|
|
|
A complete worked example is in `examples/custom_optimizer.rs`.
|
|
|
|
## Current algorithms
|
|
|
|
- `RandomSearch` — sample-evaluate-keep baseline.
|
|
- `Paes` — a small (1+1) Pareto Archived Evolution Strategy.
|
|
- `Nsga2` — the canonical Pareto-based evolutionary algorithm.
|
|
- `DifferentialEvolution` — DE/rand/1/bin for single-objective real-valued
|
|
problems.
|
|
|
|
Plus reusable utilities: `pareto_compare`, `pareto_front`, `best_candidate`,
|
|
`non_dominated_sort`, `crowding_distance`, `ParetoArchive`, and the metrics
|
|
`spacing` and `hypervolume_2d`.
|
|
|
|
## Design philosophy
|
|
|
|
- **Concrete data, small trait surface.** `Problem`, `Optimizer`, `Initializer`,
|
|
`Variation` are the only traits a user interacts with day-to-day. Everything
|
|
else is plain structs.
|
|
- **No type hell.** No trait objects in the core path, no GATs, no HRTBs in
|
|
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
|
|
writing your own optimizer.
|
|
- **One crate first.** No premature splitting into `-core`/`-algorithms`/
|
|
`-operators`. Split later if the crate grows.
|
|
- **Panic on programmer error.** Invalid configuration panics with a clear
|
|
message in v1; the API may grow `Result`-returning variants later if the
|
|
base API proves useful.
|
|
|
|
See `docs/heuropt_tech_design_spec.md` for the full design rationale.
|
|
|
|
## License
|
|
|
|
MIT — see [LICENSE](LICENSE).
|
|
|
|
## Changelog
|
|
|
|
See [CHANGELOG.md](CHANGELOG.md).
|