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