diff --git a/README.md b/README.md new file mode 100644 index 0000000..a0d4d00 --- /dev/null +++ b/README.md @@ -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; + + fn objectives(&self) -> ObjectiveSpace { + ObjectiveSpace::new(vec![ + Objective::minimize("f1"), + Objective::minimize("f2"), + ]) + } + + fn evaluate(&self, x: &Vec) -> 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; +# fn objectives(&self) -> ObjectiveSpace { +# ObjectiveSpace::new(vec![Objective::minimize("f1"), Objective::minimize("f2")]) +# } +# fn evaluate(&self, x: &Vec) -> 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

`: + +```rust +use heuropt::prelude::*; + +struct MyOptimizer { /* state */ } + +impl

Optimizer

for MyOptimizer +where + P: Problem>, +{ + fn run(&mut self, problem: &P) -> OptimizationResult { + // 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. diff --git a/src/lib.rs b/src/lib.rs index 0d211cc..6d3ab31 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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; +//! +//! fn objectives(&self) -> ObjectiveSpace { +//! ObjectiveSpace::new(vec![ +//! Objective::minimize("f1"), +//! Objective::minimize("f2"), +//! ]) +//! } +//! +//! fn evaluate(&self, x: &Vec) -> 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;