diff --git a/README.md b/README.md index eb4dcfe..81694c8 100644 --- a/README.md +++ b/README.md @@ -107,17 +107,238 @@ where A complete worked example is in `examples/custom_optimizer.rs`. +## Choosing an algorithm + +Optimization is a noisy field with a lot of jargon. This section walks you +through picking a starting algorithm for a real problem, defining the terms +as they come up. If you already know the vocabulary, jump to the +[quick-reference table](#quick-reference) at the bottom. + +### Step 1: What is your problem? + +Three ingredients describe any optimization problem: + +- A **decision** — the thing the algorithm is allowed to change. Examples: + five real numbers (`Vec`), a yes/no flag for each of 100 features + (`Vec`), or an ordering of cities to visit (`Vec`). +- One or more **objectives** — numbers you want to make small (or large). + Examples: a model's prediction error, a tour's total length, a circuit's + power draw. +- An optional set of **constraints** — conditions a decision must satisfy + to be valid. Examples: "the budget cannot exceed $1M," or "every car + must be visited exactly once." + +Your job is to express the problem; heuropt's job is to search for +decisions that score well on the objectives without violating the +constraints. + +### Step 2: How many objectives? + +The biggest fork in the road. Algorithms specialize sharply by +objective count: + +- **Single-objective (1)** — one number to optimize. There's a clear + "best" answer. Examples: minimize loss, maximize throughput. +- **Multi-objective (2 or 3)** — several conflicting goals. There is no + single best; instead there is a **Pareto front**: the set of decisions + where you cannot improve any objective without sacrificing another. + Each point on the front is a different tradeoff. +- **Many-objective (4+)** — same idea, but classical multi-objective + algorithms break down because almost every pair of points is + *non-dominated* (neither one is strictly better) once you have lots + of objectives. + +> **Dominance:** Decision A *dominates* decision B if A is at least as +> good as B on every objective and strictly better on at least one. The +> Pareto front is what you get after deleting every dominated decision. + +If you found yourself staring at a single composite score that's a +weighted sum of conflicting goals, you probably actually have a +multi-objective problem in disguise. + +### Step 3: What does the search space look like? + +A few questions about the geometry of your problem: + +- Is the **decision continuous** (real numbers), **discrete** (integers, + bits), or a **permutation** (an ordering)? +- Is the landscape **unimodal** (one hill, easy to climb) or + **multimodal** (lots of local optima that aren't the global one)? + Rastrigin and Ackley are classic multimodal traps. +- How **smooth** is it? Smooth landscapes (e.g., a quadratic bowl) + reward gradient-like methods (CMA-ES); jagged or noisy ones reward + population-based methods (DE, GA). + +If you don't know, treat it as multimodal — it's the cautious default. + +### Step 4: How expensive is each evaluation? + +Cheap evaluations (a few microseconds — pure math, simple simulation) +let you afford 100k+ evaluations per run. Expensive evaluations (a +training run, a CFD simulation, a real-world measurement that costs +money) force you to be sample-efficient: 50–500 evaluations total. + +This decides whether you can afford a **population-based** algorithm +that throws hundreds of evaluations at each generation, or whether +you need a **sample-efficient** approach. heuropt's current toolkit +is mostly population-based; for budgets under a few hundred +evaluations you may want Bayesian optimization (out of scope today). + +The `parallel` feature flag also matters here — if your `evaluate` +function takes more than ~50 µs, enabling rayon-backed parallel +population evaluation will speed runs up significantly. + +### Step 5: Are there hard constraints? + +heuropt models constraints as a single scalar **constraint violation** +on each `Evaluation`. The convention: `0.0` (or negative) means +feasible; positive means infeasible, and bigger numbers are worse +violations. Every Pareto-comparison and tournament-selection helper +in the crate prefers feasible candidates and breaks ties on +violation magnitude, so the rule "feasibility comes first" is +enforced automatically. + +If your constraints are very tight and the search keeps hitting them, +consider also adding a **repair operator** (clamping, rounding, or a +greedy fix) inside your `Variation` impl so children come out feasible +in the first place. The example `BoundedGaussianMutation` does this for +real-valued bounds. + +--- + +### The decision tree + +A flow you can run mentally: + +``` +START + │ + ├─ How many objectives? + │ │ + │ ├─ 1 (single-objective) + │ │ │ + │ │ ├─ Decision is Vec (continuous) + │ │ │ ├─ Smooth, low-dim, expensive evals + │ │ │ │ → CmaEs (sample-efficient, + │ │ │ │ invariant to scale & rotation) + │ │ │ ├─ Multimodal or jagged + │ │ │ │ → DifferentialEvolution + │ │ │ │ → ParticleSwarm + │ │ │ │ → SimulatedAnnealing (cheap & generic) + │ │ │ ├─ Just want a strong default + │ │ │ │ → DifferentialEvolution (rarely beaten on cheap + │ │ │ │ evaluators) + │ │ │ └─ Just want a baseline + │ │ │ → RandomSearch + │ │ │ + │ │ ├─ Decision is Vec (binary) + │ │ │ ├─ Independent bits, smooth fitness + │ │ │ │ → Umda (estimates per-bit marginals) + │ │ │ └─ Bit interactions matter + │ │ │ → GeneticAlgorithm with BitFlipMutation + + │ │ │ a bit-string crossover + │ │ │ + │ │ ├─ Decision is Vec (permutation, e.g., TSP) + │ │ │ → AntColonyTsp (with a distance matrix) + │ │ │ → TabuSearch (with your own neighbor function) + │ │ │ → SimulatedAnnealing with SwapMutation + │ │ │ + │ │ └─ Custom decision type (a struct, a tree, …) + │ │ → SimulatedAnnealing or HillClimber + │ │ with your own Variation impl + │ │ (the trait is generic over D) + │ │ + │ ├─ 2 or 3 (multi-objective) + │ │ │ + │ │ ├─ Just want a strong default + │ │ │ → Nsga2 (canonical, fast, well-understood) + │ │ │ + │ │ ├─ Convergence quality matters more than speed + │ │ │ → Spea2 (slower, comparable quality) + │ │ │ → Ibea (often beats Nsga2 on tough fronts) + │ │ │ + │ │ ├─ Want decomposition / weight-vector style + │ │ │ → Moead (very fast per generation, scales well) + │ │ │ + │ │ ├─ Real-valued and want swarm style + │ │ │ → Mopso (good on simple 2-obj fronts) + │ │ │ + │ │ └─ Just one starting decision (no population budget) + │ │ → Paes (1+1 ES with a Pareto archive) + │ │ + │ └─ 4+ (many-objective) + │ │ + │ ├─ Just want a strong default + │ │ → Nsga3 (reference-point niching, the canonical + │ │ many-obj choice) + │ │ → Moead (also scales naturally past 3 objectives) + │ │ + │ └─ Convergence vs diversity tradeoff matters + │ → Ibea (indicator-based, doesn't lose discrimination + │ at high obj count) + │ + └─ Don't forget: + - Set a seed for reproducibility (every Config has one). + - Enable the `parallel` feature if your evaluate is expensive. + - Use `examples/compare.rs` as a template for benchmarking + multiple algorithms on your own problem. +``` + +### Quick reference + +| Algorithm | Objectives | Decision type | Strengths | +|---|---|---|---| +| `RandomSearch` | any | any | sanity baseline | +| `HillClimber` | 1 | any | simplest greedy local search | +| `SimulatedAnnealing` | 1 | any | escapes local optima, decision-type-agnostic | +| `TabuSearch` | 1 | any | combinatorial / discrete, you supply neighbors | +| `GeneticAlgorithm` | 1 | any | classic SO GA with elitism | +| `ParticleSwarm` | 1 | `Vec` | simple swarm, good baseline | +| `DifferentialEvolution` | 1 | `Vec` | strong default for cheap continuous problems | +| `CmaEs` | 1 | `Vec` | sample-efficient, smooth landscapes | +| `Umda` | 1 | `Vec` | independent-bit binary problems | +| `AntColonyTsp` | 1 | `Vec` | TSP / permutation problems | +| `Paes` | 2–3 | any (variation defines) | 1+1 ES with archive | +| `Nsga2` | 2–3 | any | canonical multi-objective EA | +| `Spea2` | 2–3 | any | strength + density-based MOEA | +| `Moead` | 2+ | any | decomposition-based, fast per gen | +| `Mopso` | 2–3 | `Vec` | multi-objective PSO with archive | +| `Ibea` | 2+ | any | indicator-based, scales to many obj | +| `Nsga3` | 4+ | any | reference-point niching for many-obj | + ## 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. +The full list with one-line descriptions: -Plus reusable utilities: `pareto_compare`, `pareto_front`, `best_candidate`, -`non_dominated_sort`, `crowding_distance`, `ParetoArchive`, and the metrics -`spacing` and `hypervolume_2d`. +**Single-objective:** + +- `RandomSearch` — sample-evaluate-keep baseline. +- `HillClimber` — greedy single-step local search. +- `SimulatedAnnealing` — Kirkpatrick et al. 1983, generic over decision type. +- `TabuSearch` — Glover 1986, with a user-supplied neighbor generator. +- `GeneticAlgorithm` — generational GA with tournament selection + elitism. +- `ParticleSwarm` — Eberhart & Kennedy 1995 PSO for `Vec`. +- `DifferentialEvolution` — Storn & Price DE/rand/1/bin for `Vec`. +- `CmaEs` — Hansen & Ostermeier 2001 covariance-matrix adaptation. +- `Umda` — Mühlenbein 1997 univariate marginal-distribution EDA for `Vec`. +- `AntColonyTsp` — Dorigo Ant System for permutation problems. + +**Multi-objective:** + +- `Paes` — Knowles & Corne 1999 Pareto Archived Evolution Strategy. +- `Nsga2` — Deb et al. 2002, the canonical Pareto-based EA. +- `Spea2` — Zitzler, Laumanns & Thiele 2001 strength-Pareto EA. +- `Moead` — Zhang & Li 2007 decomposition-based MOEA with Tchebycheff scalarization. +- `Mopso` — Coello, Pulido & Lechuga 2004 multi-objective PSO. +- `Ibea` — Zitzler & Künzli 2004 indicator-based EA. + +**Many-objective (4+):** + +- `Nsga3` — Deb & Jain 2014 reference-point NSGA-III. + +**Reusable utilities:** `pareto_compare`, `pareto_front`, `best_candidate`, +`non_dominated_sort`, `crowding_distance`, `ParetoArchive`, `das_dennis`, +and the metrics `spacing` and `hypervolume_2d`. ## Design philosophy