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}