Skip to main content

forge_core/algo/
mod.rs

1//! Optimization algorithms.
2//!
3//! Each algorithm is a small `Config` struct plus a runner that takes a
4//! [`Problem`] and a [`Termination`] and returns a [`Report`]. All of them
5//! minimize, count objective evaluations as the budget unit, reject non-finite
6//! candidates, and are deterministic for a given seed.
7//!
8//! - [`Dds`] — Dynamically Dimensioned Search (Tolson & Shoemaker 2007).
9//! - [`Sce`] — Shuffled Complex Evolution, SCE-UA (Duan et al. 1992).
10//! - [`De`] — Differential Evolution (Storn & Price 1997).
11//! - [`LShade`] — Success-History adaptive DE + LPSR (Tanabe & Fukunaga 2014).
12//! - [`LSrtde`] — Success-Rate adaptive DE, CEC 2024 winner (Stanovov & Semenkin).
13//! - [`Pso`] — Particle Swarm Optimization (Kennedy & Eberhart 1995).
14//! - [`CmaEs`] — Covariance Matrix Adaptation ES (Hansen & Ostermeier 2001).
15//! - [`NsgaII`] — multi-objective NSGA-II (Deb et al. 2002).
16//! - [`NsgaIII`] — reference-point many-objective NSGA-III (Deb & Jain 2014).
17//! - [`SmsEmoa`] — hypervolume-indicator MOEA for 2-3 objectives (Beume et al. 2007).
18//! - [`Sa`] — Simulated Annealing for combinatorial problems (via [`Anneal`]).
19//! - [`ParallelTempering`] — replica-exchange annealing (via [`Anneal`]).
20//! - [`Glue`] — Generalized Likelihood Uncertainty Estimation (Beven & Binley 1992).
21//!
22//! [`Problem`]: crate::problem::Problem
23//! [`Termination`]: crate::termination::Termination
24//! [`Report`]: crate::solution::Report
25//! [`Anneal`]: sa::Anneal
26
27pub mod cmaes;
28pub mod dds;
29pub mod de;
30pub mod epsilon_lshade;
31pub mod glue;
32pub mod lshade;
33pub mod lsrtde;
34pub mod moead;
35pub mod nsga2;
36pub mod nsga3;
37pub mod padds;
38pub mod parallel;
39pub mod pso;
40pub mod pt;
41pub mod sa;
42pub mod sce;
43pub mod sms_emoa;
44
45pub use cmaes::{CmaEs, Restart, RestartCmaEs};
46pub use dds::Dds;
47pub use de::De;
48pub use epsilon_lshade::EpsilonLShade;
49pub use glue::{Behavioral, Glue, GlueResult, GlueSample};
50pub use lshade::LShade;
51pub use lsrtde::LSrtde;
52pub use moead::Moead;
53pub use nsga2::NsgaII;
54pub use nsga3::NsgaIII;
55pub use padds::{Padds, PaddsSelection};
56pub use parallel::ensemble;
57pub use pso::Pso;
58pub use pt::{ParallelTempering, TemperingResult};
59pub use sa::{Anneal, Sa, SaResult, Schedule};
60pub use sce::Sce;
61pub use sms_emoa::SmsEmoa;
62
63use crate::problem::{Bound, Problem};
64use crate::rng::Rng;
65use crate::solution::{Report, Solution, StopReason};
66use crate::termination::Termination;
67
68/// The uniform interface every optimizer implements: run on a [`Problem`]
69/// under a [`Termination`], and produce a copy of itself with a different RNG
70/// seed (the hook ensembles use to give each island an independent stream).
71///
72/// `Sync` is a supertrait so optimizers can be shared across island workers.
73pub trait Optimizer: Sync {
74    /// Minimizes `problem` within its bounds under `term`.
75    fn optimize(&self, problem: &dyn Problem, term: &Termination) -> Report;
76
77    /// Returns the same configuration with its RNG seed replaced.
78    fn with_seed(&self, seed: u64) -> Self
79    where
80        Self: Sized;
81}
82
83/// A configured algorithm, for code that selects one at runtime (mirrors the
84/// CLI / client use where the optimizer is chosen by a flag).
85///
86/// Marked `#[non_exhaustive]`: new algorithms are added over time, so matches
87/// need a wildcard arm and adding a variant is not a breaking change.
88#[derive(Debug, Clone, Copy)]
89#[non_exhaustive]
90pub enum Algorithm {
91    /// Dynamically Dimensioned Search (Tolson & Shoemaker 2007).
92    Dds(Dds),
93    /// Shuffled Complex Evolution, SCE-UA (Duan et al. 1992).
94    Sce(Sce),
95    /// Differential Evolution `rand/1/bin` (Storn & Price 1997).
96    De(De),
97    /// Success-history adaptive DE with LPSR (Tanabe & Fukunaga 2014).
98    LShade(LShade),
99    /// Success-rate adaptive DE, CEC 2024 winner (Stanovov & Semenkin 2024).
100    LSrtde(LSrtde),
101    /// Particle Swarm Optimization (Kennedy & Eberhart 1995).
102    Pso(Pso),
103    /// Covariance Matrix Adaptation ES (Hansen & Ostermeier 2001).
104    CmaEs(CmaEs),
105    /// CMA-ES with IPOP/BIPOP restarts (Auger & Hansen 2005; Hansen 2009).
106    RestartCmaEs(RestartCmaEs),
107}
108
109impl Optimizer for Algorithm {
110    /// Runs the selected algorithm.
111    fn optimize(&self, problem: &dyn Problem, term: &Termination) -> Report {
112        match self {
113            Algorithm::Dds(c) => c.optimize(problem, term),
114            Algorithm::Sce(c) => c.optimize(problem, term),
115            Algorithm::De(c) => c.optimize(problem, term),
116            Algorithm::LShade(c) => c.optimize(problem, term),
117            Algorithm::LSrtde(c) => c.optimize(problem, term),
118            Algorithm::Pso(c) => c.optimize(problem, term),
119            Algorithm::CmaEs(c) => c.optimize(problem, term),
120            Algorithm::RestartCmaEs(c) => c.optimize(problem, term),
121        }
122    }
123
124    fn with_seed(&self, seed: u64) -> Self {
125        match self {
126            Algorithm::Dds(c) => Algorithm::Dds(c.with_seed(seed)),
127            Algorithm::Sce(c) => Algorithm::Sce(c.with_seed(seed)),
128            Algorithm::De(c) => Algorithm::De(c.with_seed(seed)),
129            Algorithm::LShade(c) => Algorithm::LShade(c.with_seed(seed)),
130            Algorithm::LSrtde(c) => Algorithm::LSrtde(c.with_seed(seed)),
131            Algorithm::Pso(c) => Algorithm::Pso(c.with_seed(seed)),
132            Algorithm::CmaEs(c) => Algorithm::CmaEs(c.with_seed(seed)),
133            Algorithm::RestartCmaEs(c) => Algorithm::RestartCmaEs(c.with_seed(seed)),
134        }
135    }
136}
137
138/// Draws a uniform random point inside the bounds.
139pub(crate) fn sample(bounds: &[Bound], rng: &mut Rng) -> Vec<f64> {
140    bounds
141        .iter()
142        .map(|&(lo, hi)| rng.uniform_in(lo, hi))
143        .collect()
144}
145
146/// Clamps `x` into `[lo, hi]`.
147#[inline]
148pub(crate) fn clamp(x: f64, lo: f64, hi: f64) -> f64 {
149    x.max(lo).min(hi)
150}
151
152/// A budget-aware objective wrapper: counts evaluations and tracks the running
153/// best, so every algorithm shares one consistent accounting and stop policy.
154pub(crate) struct Evaluator<'a> {
155    problem: &'a dyn Problem,
156    term: &'a Termination,
157    pub evaluations: usize,
158    pub best: Solution,
159}
160
161impl<'a> Evaluator<'a> {
162    /// Starts accounting with an already-evaluated initial solution.
163    pub fn new(problem: &'a dyn Problem, term: &'a Termination, first: Solution) -> Self {
164        Evaluator {
165            problem,
166            term,
167            evaluations: 1,
168            best: first,
169        }
170    }
171
172    /// Evaluates `x`, counts it, and updates the best when it is at least as
173    /// good (ties are accepted, matching the DDS/SCE-UA papers, so greedy
174    /// searches may drift along plateaus; a non-finite value never wins).
175    /// Returns the objective value.
176    pub fn eval(&mut self, x: &[f64]) -> f64 {
177        let v = self.problem.objective(x);
178        self.evaluations += 1;
179        if v.is_finite() && v <= self.best.value {
180            self.best = Solution {
181                x: x.to_vec(),
182                value: v,
183            };
184        }
185        v
186    }
187
188    /// True once the budget is spent or the target is met.
189    #[inline]
190    pub fn done(&self) -> bool {
191        self.term
192            .reason(self.evaluations, self.best.value)
193            .is_some()
194    }
195
196    /// Builds the final report.
197    pub fn finish(self) -> Report {
198        let stop = self
199            .term
200            .reason(self.evaluations, self.best.value)
201            .unwrap_or(StopReason::BudgetExhausted);
202        Report {
203            solution: self.best,
204            stop,
205            evaluations: self.evaluations,
206        }
207    }
208}