1use crate::{DspError, invalid, numeric::sum, random::RandomState};
7
8#[derive(Debug, Clone, Copy)]
12pub struct DifferentialEvolutionOptions {
13 pub maxiter: usize,
15 pub popsize: usize,
17 pub tol: f64,
19 pub seed: u32,
21}
22
23impl Default for DifferentialEvolutionOptions {
24 fn default() -> Self {
25 Self {
26 maxiter: 60,
27 popsize: 12,
28 tol: 1e-6,
29 seed: 1,
30 }
31 }
32}
33
34#[derive(Debug, Clone)]
36pub struct DifferentialEvolutionResult {
37 pub x: Vec<f64>,
38 pub fun: f64,
39 pub nit: usize,
40 pub nfev: usize,
41 pub success: bool,
42 pub message: String,
43}
44
45pub fn differential_evolution<F>(
59 mut cost: F,
60 bounds: &[(f64, f64)],
61 x0: Option<&[f64]>,
62 options: DifferentialEvolutionOptions,
63) -> Result<DifferentialEvolutionResult, DspError>
64where
65 F: FnMut(&[f64]) -> f64,
66{
67 let n = bounds.len();
68 if n == 0 || bounds.iter().any(|(a, b)| !a.is_finite() || !b.is_finite()) {
69 return Err(invalid("bounds must contain finite real valued pairs"));
70 }
71 if x0.is_some_and(|x| x.len() != n) {
72 return Err(invalid("x0 length must match bounds"));
73 }
74 let center: Vec<_> = bounds.iter().map(|(a, b)| 0.5 * (a + b)).collect();
75 let width: Vec<_> = bounds.iter().map(|(a, b)| (a - b).abs()).collect();
76 let varying = bounds.iter().filter(|(a, b)| a != b).count().max(1);
77 let members = options
78 .popsize
79 .checked_mul(varying)
80 .expect("population size overflow")
81 .max(5);
82 members.checked_mul(n).expect("population shape overflow");
83 let mut rng = RandomState::new(options.seed);
84 let segment = 1.0 / members as f64;
85 let samples: Vec<Vec<_>> = (0..members)
87 .map(|i| {
88 (0..n)
89 .map(|_| segment * rng.random_sample() + i as f64 * segment)
90 .collect()
91 })
92 .collect();
93 let mut population = vec![vec![0.0; n]; members];
94 for j in 0..n {
95 for (i, source) in rng.permutation(members).into_iter().enumerate() {
96 population[i][j] = samples[source][j];
97 }
98 }
99 if let Some(x0) = x0 {
100 for j in 0..n {
101 let reciprocal = 1.0 / width[j];
102 let reciprocal = if reciprocal.is_finite() {
103 reciprocal
104 } else {
105 0.0
106 };
107 let value = (x0[j] - center[j]) * reciprocal + 0.5;
108 if value > 1.0 || value < 0.0 {
109 return Err(invalid(
110 "Some entries in x0 lay outside the specified bounds",
111 ));
112 }
113 population[0][j] = value;
114 }
115 }
116 let scale = |unit: &[f64]| -> Vec<f64> {
117 unit.iter()
118 .enumerate()
119 .map(|(j, x)| center[j] + (x - 0.5) * width[j])
120 .collect()
121 };
122 let mut energies: Vec<_> = population.iter().map(|x| cost(&scale(x))).collect();
123 let mut nfev = members;
124 promote(&mut population, &mut energies);
125 let mut indices: Vec<_> = (0..members).collect();
126 let mut nit = 0;
127 let mut success = false;
128 for generation in 1..=options.maxiter {
129 nit = generation;
130 if energies.iter().all(|x| x.is_infinite()) {
132 for (energy, x) in energies.iter_mut().zip(&population) {
133 *energy = cost(&scale(x));
134 nfev += 1;
135 }
136 promote(&mut population, &mut energies);
137 }
138 let mutation = rng.uniform(0.5, 1.0)?;
139 for candidate in 0..members {
140 let fill = rng.randint(n)?;
141 rng.shuffle(&mut indices);
144 let mut selected = indices.iter().copied().filter(|&i| i != candidate);
145 let r0 = selected.next().unwrap();
146 let r1 = selected.next().unwrap();
147 let mut trial = population[candidate].clone();
148 for j in 0..n {
149 let crossover = rng.random_sample() < 0.7;
150 if crossover || j == fill {
151 trial[j] =
152 population[0][j] + mutation * (population[r0][j] - population[r1][j]);
153 }
154 }
155 for value in &mut trial {
157 if *value > 1.0 || *value < 0.0 {
158 *value = rng.random_sample();
159 }
160 }
161 let energy = cost(&scale(&trial));
162 nfev += 1;
163 if energy <= energies[candidate] {
164 population[candidate] = trial;
165 energies[candidate] = energy;
166 if energy <= energies[0] {
167 promote(&mut population, &mut energies);
170 }
171 }
172 }
173 if converged(&energies, options.tol) {
174 success = true;
175 break;
176 }
177 }
178 Ok(DifferentialEvolutionResult {
179 x: scale(&population[0]),
180 fun: energies[0],
181 nit,
182 nfev,
183 success,
184 message: if success {
185 "Optimization terminated successfully."
186 } else {
187 "Maximum number of iterations has been exceeded."
188 }
189 .to_owned(),
190 })
191}
192
193fn promote(population: &mut [Vec<f64>], energies: &mut [f64]) {
194 let mut best = 0;
195 for i in 1..energies.len() {
196 if energies[best].is_nan() {
198 break;
199 }
200 if energies[i].is_nan() || energies[i] < energies[best] {
201 best = i;
202 }
203 }
204 energies.swap(0, best);
205 population.swap(0, best);
206}
207
208fn converged(energies: &[f64], tol: f64) -> bool {
209 if energies.iter().any(|x| x.is_infinite()) {
210 return false;
211 }
212 let mean = sum(energies) / energies.len() as f64;
213 let squares: Vec<_> = energies
214 .iter()
215 .map(|e| {
216 let d = e - mean;
217 d * d
218 })
219 .collect();
220 (sum(&squares) / energies.len() as f64).sqrt() <= tol * mean.abs()
221}