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