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//! - [`Sa`] — Simulated Annealing for combinatorial problems (via [`Anneal`]).
18//! - [`ParallelTempering`] — replica-exchange annealing (via [`Anneal`]).
19//! - [`Glue`] — Generalized Likelihood Uncertainty Estimation (Beven & Binley 1992).
20//!
21//! [`Problem`]: crate::problem::Problem
22//! [`Termination`]: crate::termination::Termination
23//! [`Report`]: crate::solution::Report
24//! [`Anneal`]: sa::Anneal
25
26pub mod cmaes;
27pub mod dds;
28pub mod de;
29pub mod glue;
30pub mod lshade;
31pub mod lsrtde;
32pub mod nsga2;
33pub mod nsga3;
34pub mod parallel;
35pub mod pso;
36pub mod pt;
37pub mod sa;
38pub mod sce;
39
40pub use cmaes::CmaEs;
41pub use dds::Dds;
42pub use de::De;
43pub use glue::{Behavioral, Glue, GlueResult, GlueSample};
44pub use lshade::LShade;
45pub use lsrtde::LSrtde;
46pub use nsga2::NsgaII;
47pub use nsga3::NsgaIII;
48pub use parallel::ensemble;
49pub use pso::Pso;
50pub use pt::{ParallelTempering, TemperingResult};
51pub use sa::{Anneal, Sa, SaResult, Schedule};
52pub use sce::Sce;
53
54use crate::problem::{Bound, Problem};
55use crate::rng::Rng;
56use crate::solution::{Report, Solution, StopReason};
57use crate::termination::Termination;
58
59/// The uniform interface every optimizer implements: run on a [`Problem`]
60/// under a [`Termination`], and produce a copy of itself with a different RNG
61/// seed (the hook ensembles use to give each island an independent stream).
62///
63/// `Sync` is a supertrait so optimizers can be shared across island workers.
64pub trait Optimizer: Sync {
65    /// Minimizes `problem` within its bounds under `term`.
66    fn optimize(&self, problem: &dyn Problem, term: &Termination) -> Report;
67
68    /// Returns the same configuration with its RNG seed replaced.
69    fn with_seed(&self, seed: u64) -> Self
70    where
71        Self: Sized;
72}
73
74/// A configured algorithm, for code that selects one at runtime (mirrors the
75/// CLI / client use where the optimizer is chosen by a flag).
76#[derive(Debug, Clone, Copy)]
77pub enum Algorithm {
78    Dds(Dds),
79    Sce(Sce),
80    De(De),
81    LShade(LShade),
82    LSrtde(LSrtde),
83    Pso(Pso),
84    CmaEs(CmaEs),
85}
86
87impl Optimizer for Algorithm {
88    /// Runs the selected algorithm.
89    fn optimize(&self, problem: &dyn Problem, term: &Termination) -> Report {
90        match self {
91            Algorithm::Dds(c) => c.optimize(problem, term),
92            Algorithm::Sce(c) => c.optimize(problem, term),
93            Algorithm::De(c) => c.optimize(problem, term),
94            Algorithm::LShade(c) => c.optimize(problem, term),
95            Algorithm::LSrtde(c) => c.optimize(problem, term),
96            Algorithm::Pso(c) => c.optimize(problem, term),
97            Algorithm::CmaEs(c) => c.optimize(problem, term),
98        }
99    }
100
101    fn with_seed(&self, seed: u64) -> Self {
102        match self {
103            Algorithm::Dds(c) => Algorithm::Dds(c.with_seed(seed)),
104            Algorithm::Sce(c) => Algorithm::Sce(c.with_seed(seed)),
105            Algorithm::De(c) => Algorithm::De(c.with_seed(seed)),
106            Algorithm::LShade(c) => Algorithm::LShade(c.with_seed(seed)),
107            Algorithm::LSrtde(c) => Algorithm::LSrtde(c.with_seed(seed)),
108            Algorithm::Pso(c) => Algorithm::Pso(c.with_seed(seed)),
109            Algorithm::CmaEs(c) => Algorithm::CmaEs(c.with_seed(seed)),
110        }
111    }
112}
113
114/// Draws a uniform random point inside the bounds.
115pub(crate) fn sample(bounds: &[Bound], rng: &mut Rng) -> Vec<f64> {
116    bounds
117        .iter()
118        .map(|&(lo, hi)| rng.uniform_in(lo, hi))
119        .collect()
120}
121
122/// Clamps `x` into `[lo, hi]`.
123#[inline]
124pub(crate) fn clamp(x: f64, lo: f64, hi: f64) -> f64 {
125    x.max(lo).min(hi)
126}
127
128/// A budget-aware objective wrapper: counts evaluations and tracks the running
129/// best, so every algorithm shares one consistent accounting and stop policy.
130pub(crate) struct Evaluator<'a> {
131    problem: &'a dyn Problem,
132    term: &'a Termination,
133    pub evaluations: usize,
134    pub best: Solution,
135}
136
137impl<'a> Evaluator<'a> {
138    /// Starts accounting with an already-evaluated initial solution.
139    pub fn new(problem: &'a dyn Problem, term: &'a Termination, first: Solution) -> Self {
140        Evaluator {
141            problem,
142            term,
143            evaluations: 1,
144            best: first,
145        }
146    }
147
148    /// Evaluates `x`, counts it, and updates the best when it improves (a
149    /// non-finite value never wins). Returns the objective value.
150    pub fn eval(&mut self, x: &[f64]) -> f64 {
151        let v = self.problem.objective(x);
152        self.evaluations += 1;
153        if v.is_finite() && v < self.best.value {
154            self.best = Solution {
155                x: x.to_vec(),
156                value: v,
157            };
158        }
159        v
160    }
161
162    /// True once the budget is spent or the target is met.
163    #[inline]
164    pub fn done(&self) -> bool {
165        self.term
166            .reason(self.evaluations, self.best.value)
167            .is_some()
168    }
169
170    /// Builds the final report.
171    pub fn finish(self) -> Report {
172        let stop = self
173            .term
174            .reason(self.evaluations, self.best.value)
175            .unwrap_or(StopReason::BudgetExhausted);
176        Report {
177            solution: self.best,
178            stop,
179            evaluations: self.evaluations,
180        }
181    }
182}