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