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