docs: add README and crate-level //! docs

Adds:
- README.md following spec §19.1 (what / install / define problem /
  run NSGA-II / custom optimizer / current algorithms / design
  philosophy).
- A short-but-runnable crate-level //! example in lib.rs for
  `cargo doc` (spec §19.2).
This commit is contained in:
2026-05-04 19:27:38 -06:00
parent c58d0241f9
commit 2298abdc72
2 changed files with 180 additions and 3 deletions
+136
View File
@@ -0,0 +1,136 @@
# heuropt
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: derive serde::{Serialize, Deserialize} on the core data types.
# heuropt = { version = "0.1", features = ["serde"] }
```
## 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.
+44 -3
View File
@@ -1,6 +1,47 @@
//! `heuropt` — a practical Rust toolkit for heuristic single-, multi-, and
//! many-objective optimization. See `docs/heuropt_tech_design_spec.md` for the
//! full design.
//! `heuropt` — a practical Rust toolkit for implementing heuristic
//! single-objective, multi-objective, and many-objective optimization
//! algorithms.
//!
//! The crate aims to make three things obvious:
//!
//! 1. Define an optimization problem by implementing [`Problem`](crate::core::Problem).
//! 2. Run a built-in optimizer such as [`Nsga2`](crate::algorithms::Nsga2) or
//! [`RandomSearch`](crate::algorithms::RandomSearch).
//! 3. Implement a new optimizer by implementing
//! [`Optimizer`](crate::traits::Optimizer).
//!
//! See `docs/heuropt_tech_design_spec.md` for the full design rationale.
//!
//! # Quick example
//!
//! ```
//! use heuropt::prelude::*;
//!
//! struct Toy;
//!
//! impl Problem for Toy {
//! 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: 30, generations: 10, seed: 42 };
//! let mut opt = Nsga2::new(config, initializer, variation);
//! let result = opt.run(&Toy);
//! assert_eq!(result.population.len(), 30);
//! assert!(!result.pareto_front.is_empty());
//! ```
pub mod algorithms;
pub mod core;