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