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