1use std::collections::VecDeque;
11
12use crate::fitness::{Fitness, Objective};
13use crate::rng::Rng;
14
15#[derive(Clone, Debug)]
17pub struct DeResult {
18 pub x: Vec<f64>,
19 pub y: f64,
20 pub evaluations: u64,
21 pub iterations: i32,
22 pub stop: i32,
23}
24
25#[derive(Clone, Debug)]
27pub struct DeParams {
28 pub popsize: i32,
29 pub max_evaluations: u64,
30 pub keep: f64,
31 pub stop_fitness: f64,
32 pub f: f64,
33 pub cr: f64,
34 pub min_mutate: f64,
35 pub max_mutate: f64,
36 pub min_sigma: f64,
37 pub seed: u64,
38 pub runid: i64,
39}
40
41impl Default for DeParams {
42 fn default() -> Self {
43 Self {
44 popsize: 31,
45 max_evaluations: 100_000,
46 keep: 200.0,
47 stop_fitness: f64::NEG_INFINITY,
48 f: 0.5,
49 cr: 0.9,
50 min_mutate: 0.1,
51 max_mutate: 0.5,
52 min_sigma: 0.0,
53 seed: 0,
54 runid: 0,
55 }
56 }
57}
58
59pub struct De {
60 fitfun: Fitness,
61 rng: Rng,
62 dim: usize,
63 popsize: usize,
64 max_evaluations: u64,
65 keep: f64,
66 stopfitness: f64,
67 f0: f64,
68 cr0: f64,
69 f: f64,
70 cr: f64,
71 min_mutate: f64,
72 max_mutate: f64,
73 is_int: Option<Vec<bool>>,
74
75 use_normal: bool,
77 mean: Vec<f64>,
78 sigma: Vec<f64>,
79 max_sigma: Vec<f64>,
80 min_sigma_vec: Vec<f64>,
81 min_sigma_val: f64,
82 mean_hist: Vec<Vec<f64>>, mean_hist_index: usize,
84
85 pop_x: Vec<Vec<f64>>,
87 pop_x0: Vec<Vec<f64>>,
88 pop_y: Vec<f64>,
89 pop_iter: Vec<i32>,
90 best_i: usize,
91 best_x: Vec<f64>,
92 best_y: f64,
93
94 iterations: i32,
95 stop: i32,
96 pos: usize,
97
98 improves_x: VecDeque<Vec<f64>>,
100 improves_p: VecDeque<usize>,
101 asked_x: Vec<Vec<f64>>,
102 asked_p: Vec<usize>,
103 external_evaluations: u64,
104}
105
106impl De {
107 pub fn new(
111 mut fitfun: Fitness,
112 guess: &[f64],
113 sigma: &[f64],
114 ints: Option<Vec<bool>>,
115 p: &DeParams,
116 ) -> Self {
117 let dim = fitfun.dim();
118 fitfun.reset_evaluations();
119 let popsize = if p.popsize > 0 {
120 p.popsize as usize
121 } else {
122 15 * dim
123 };
124 let keep = if p.keep > 0.0 { p.keep } else { 30.0 };
125 let f0 = if p.f > 0.0 { p.f } else { 0.5 };
126 let cr0 = if p.cr > 0.0 { p.cr } else { 0.9 };
127 let min_mutate = if p.min_mutate > 0.0 {
128 p.min_mutate
129 } else {
130 0.1
131 };
132 let max_mutate = if p.max_mutate > 0.0 {
133 p.max_mutate
134 } else {
135 0.5
136 };
137
138 let use_normal = !guess.is_empty();
139 let mean = guess.to_vec();
140 let sigma_v = sigma.to_vec();
141
142 let mut de = De {
143 dim,
144 popsize,
145 max_evaluations: if p.max_evaluations > 0 {
146 p.max_evaluations
147 } else {
148 50_000
149 },
150 keep,
151 stopfitness: p.stop_fitness,
152 f0,
153 cr0,
154 f: f0,
155 cr: cr0,
156 min_mutate,
157 max_mutate,
158 is_int: ints,
159 use_normal,
160 mean,
161 sigma: sigma_v,
162 max_sigma: vec![],
163 min_sigma_vec: vec![],
164 min_sigma_val: p.min_sigma,
165 mean_hist: vec![],
166 mean_hist_index: 0,
167 pop_x: vec![],
168 pop_x0: vec![],
169 pop_y: vec![],
170 pop_iter: vec![],
171 best_i: 0,
172 best_x: vec![],
173 best_y: f64::MAX,
174 iterations: 0,
175 stop: 0,
176 pos: 0,
177 improves_x: VecDeque::new(),
178 improves_p: VecDeque::new(),
179 asked_x: vec![],
180 asked_p: vec![],
181 external_evaluations: 0,
182 rng: Rng::new(p.seed.wrapping_add(p.runid as u64)),
183 fitfun,
184 };
185 de.init();
186 de
187 }
188
189 fn init(&mut self) {
190 let dim = self.dim;
191 self.mean_hist = (0..10).map(|_| self.mean.clone()).collect();
192 self.mean_hist_index = 0;
193 if self.use_normal {
194 self.max_sigma = self
195 .sigma
196 .iter()
197 .map(|s| s / (0.1 + self.min_sigma_val))
198 .collect();
199 self.min_sigma_vec = self.sigma.iter().map(|s| self.min_sigma_val * s).collect();
200 }
201 self.pop_x = Vec::with_capacity(self.popsize);
202 self.pop_x0 = Vec::with_capacity(self.popsize);
203 self.pop_y = vec![f64::MAX; self.popsize];
204 for _ in 0..self.popsize {
205 let s = self.sample();
206 self.pop_x0.push(s.clone());
207 self.pop_x.push(s);
208 }
209 self.best_i = 0;
210 self.best_x = self.pop_x[0].clone();
211 self.pop_iter = vec![0; self.popsize];
212 self.asked_x = vec![vec![0.0; dim]; self.popsize];
213 self.asked_p = vec![0; self.popsize];
214 }
215
216 pub fn dim(&self) -> usize {
217 self.dim
218 }
219 pub fn popsize(&self) -> usize {
220 self.popsize
221 }
222 pub fn stop(&self) -> i32 {
223 self.stop
224 }
225
226 fn sample(&mut self) -> Vec<f64> {
227 if self.use_normal {
228 let raw: Vec<f64> = (0..self.dim)
229 .map(|i| self.mean[i] + self.rng.gaussian() * self.sigma[i])
230 .collect();
231 self.fitfun.closest_feasible(&raw)
232 } else {
233 self.fitfun.sample(&mut self.rng)
234 }
235 }
236
237 fn sample_i(&mut self, i: usize) -> f64 {
238 if self.use_normal {
239 let v = self.rng.normreal(self.mean[i], self.sigma[i]);
240 self.fitfun.closest_feasible_i(i, v)
241 } else {
242 self.fitfun.sample_i(i, &mut self.rng)
243 }
244 }
245
246 fn update_mean(&mut self) {
247 if !self.use_normal {
248 return;
249 }
250 self.mean_hist[self.mean_hist_index] = self.pop_x[self.best_i].clone();
251 self.mean_hist_index = (self.mean_hist_index + 1) % self.mean_hist.len();
252 let mut sigma_new = vec![0.0; self.dim];
255 for (i, sn) in sigma_new.iter_mut().enumerate() {
256 let mut lo = f64::MAX;
257 let mut hi = f64::MIN;
258 for col in &self.mean_hist {
259 lo = lo.min(col[i]);
260 hi = hi.max(col[i]);
261 }
262 *sn = (hi - lo).min(self.max_sigma[i]).max(self.min_sigma_vec[i]);
263 }
264 let mean_new: f64 = sigma_new.iter().sum::<f64>() / self.dim as f64;
265 let mean_old: f64 = self.sigma.iter().sum::<f64>() / self.dim as f64;
266 if mean_new > mean_old {
267 self.sigma = sigma_new;
268 } else {
269 for (s, sn) in self.sigma.iter_mut().zip(&sigma_new) {
270 *s = 0.9 * *s + 0.1 * sn;
271 }
272 }
273 let best = self.pop_x[self.best_i].clone();
274 for (m, b) in self.mean.iter_mut().zip(&best) {
275 *m = 0.9 * *m + 0.1 * b;
276 }
277 }
278
279 fn modify(&mut self, x: &mut [f64]) {
280 let Some(is_int) = self.is_int.clone() else {
281 return;
282 };
283 let n_ints = is_int.iter().filter(|&&b| b).count() as f64;
284 if n_ints == 0.0 {
285 return;
286 }
287 let to_mutate =
288 self.min_mutate + self.rng.uniform01() * (self.max_mutate - self.min_mutate);
289 for i in 0..self.dim {
290 if is_int[i] && self.rng.uniform01() < to_mutate / n_ints {
291 x[i] = self.sample_i(i).trunc();
292 }
293 }
294 }
295
296 fn next_improve(&mut self, xb: &[f64], x: &[f64], xi: &[f64]) -> Vec<f64> {
297 let raw: Vec<f64> = (0..self.dim)
298 .map(|j| xb[j] + (x[j] - xi[j]) * self.f0)
299 .collect();
300 let mut nextx = self.fitfun.closest_feasible(&raw);
301 self.modify(&mut nextx);
302 nextx
303 }
304
305 fn oscillate(&mut self) {
306 self.cr = if self.iterations % 2 == 0 {
307 0.5 * self.cr0
308 } else {
309 self.cr0
310 };
311 self.f = if self.iterations % 2 == 0 {
312 0.5 * self.f0
313 } else {
314 self.f0
315 };
316 }
317
318 fn pick_two(&mut self, p: usize) -> (usize, usize) {
319 let mut r1;
320 loop {
321 r1 = self.rng.int_below(self.popsize as i64) as usize;
322 if r1 != p && r1 != self.best_i {
323 break;
324 }
325 }
326 let mut r2;
327 loop {
328 r2 = self.rng.int_below(self.popsize as i64) as usize;
329 if r2 != p && r2 != self.best_i && r2 != r1 {
330 break;
331 }
332 }
333 (r1, r2)
334 }
335
336 fn next_x(&mut self, p: usize, xp: &[f64], xb: &[f64]) -> Vec<f64> {
338 if p == 0 {
339 self.iterations += 1;
340 self.oscillate();
341 if self.iterations > 2 {
342 self.update_mean();
343 }
344 }
345 let (r1, r2) = self.pick_two(p);
346 let x1 = self.pop_x[r1].clone();
347 let x2 = self.pop_x[r2].clone();
348 let mut x: Vec<f64> = (0..self.dim)
349 .map(|j| xb[j] + (x1[j] - x2[j]) * self.f)
350 .collect();
351 let r = self.rng.int_below(self.dim as i64) as usize;
352 for j in 0..self.dim {
353 if j != r && self.rng.uniform01() > self.cr {
354 x[j] = xp[j];
355 }
356 }
357 let mut nextx = self.fitfun.closest_feasible(&x);
358 self.modify(&mut nextx);
359 nextx
360 }
361
362 fn ask_one(&mut self) -> (usize, Vec<f64>) {
363 if self.improves_x.is_empty() {
364 let p = self.pos;
365 let xp = self.pop_x[p].clone();
366 let xb = self.pop_x[self.best_i].clone();
367 let x = self.next_x(p, &xp, &xb);
368 self.pos = (self.pos + 1) % self.popsize;
369 (p, x)
370 } else {
371 let p = self.improves_p.pop_front().unwrap();
372 let x = self.improves_x.pop_front().unwrap();
373 (p, x)
374 }
375 }
376
377 fn tell_one(&mut self, y: f64, x: &[f64], p: usize) -> i32 {
378 if y.is_finite() && y < self.pop_y[p] {
379 if self.iterations > 1 {
380 let xb = self.pop_x[self.best_i].clone();
381 let xi = self.pop_x0[p].clone();
382 let improved = self.next_improve(&xb, x, &xi);
383 self.improves_p.push_back(p);
384 self.improves_x.push_back(improved);
385 }
386 self.pop_x0[p] = self.pop_x[p].clone();
387 self.pop_x[p] = x.to_vec();
388 self.pop_y[p] = y;
389 self.pop_iter[p] = self.iterations;
390 if y < self.pop_y[self.best_i] {
391 self.best_i = p;
392 if y < self.best_y {
393 self.best_y = y;
394 self.best_x = x.to_vec();
395 if self.stopfitness.is_finite() && self.best_y < self.stopfitness {
396 self.stop = 1;
397 }
398 }
399 }
400 } else if self.keep * self.rng.uniform01() < (self.iterations - self.pop_iter[p]) as f64 {
401 self.pop_x[p] = self.sample();
402 self.pop_y[p] = f64::MAX;
403 }
404 self.stop
405 }
406
407 pub fn optimize(&mut self, obj: &impl Objective) -> DeResult {
410 self.iterations = 1;
411 self.fitfun.reset_evaluations();
412 while self.fitfun.evaluations() < self.max_evaluations && !self.fitfun.terminate() {
413 if self.iterations > 2 {
414 self.update_mean();
415 }
416 self.oscillate();
417 for p in 0..self.popsize {
418 let xp = self.pop_x[p].clone();
419 let xb = self.pop_x[self.best_i].clone();
420 let (r1, r2) = self.pick_two(p);
421 let x1 = self.pop_x[r1].clone();
422 let x2 = self.pop_x[r2].clone();
423 let r = self.rng.int_below(self.dim as i64) as usize;
424 let mut x = xp.clone();
425 for j in 0..self.dim {
426 if j == r || self.rng.uniform01() < self.cr {
427 x[j] = xb[j] + self.f * (x1[j] - x2[j]);
428 if !self.fitfun.feasible_i(j, x[j]) {
429 x[j] = self.sample_i(j);
430 }
431 }
432 }
433 self.modify(&mut x);
434 let mut y = self.fitfun.eval_encoded_scalar(&x, obj);
435 if y.is_finite() && y < self.pop_y[p] {
436 let x2t = self.next_improve(&xb, &x, &xp);
438 let y2 = self.fitfun.eval_encoded_scalar(&x2t, obj);
439 if y2.is_finite() && y2 < y {
440 y = y2;
441 x = x2t;
442 }
443 self.pop_x[p] = x.clone();
444 self.pop_y[p] = y;
445 self.pop_iter[p] = self.iterations;
446 if y < self.pop_y[self.best_i] {
447 self.best_i = p;
448 if y < self.best_y {
449 self.best_y = y;
450 self.best_x = x.clone();
451 if self.stopfitness.is_finite() && self.best_y < self.stopfitness {
452 self.stop = 1;
453 return self.make_result(self.fitfun.evaluations());
454 }
455 }
456 }
457 } else if self.keep * self.rng.uniform01()
458 < (self.iterations - self.pop_iter[p]) as f64
459 {
460 self.pop_x[p] = self.sample();
461 self.pop_y[p] = f64::MAX;
462 }
463 }
464 self.iterations += 1;
465 }
466 self.make_result(self.fitfun.evaluations())
467 }
468
469 fn make_result(&self, evaluations: u64) -> DeResult {
470 DeResult {
471 x: self.best_x.clone(),
472 y: self.best_y,
473 evaluations,
474 iterations: self.iterations,
475 stop: self.stop,
476 }
477 }
478
479 pub fn ask(&mut self) -> Vec<Vec<f64>> {
483 for i in 0..self.popsize {
484 let (p, x) = self.ask_one();
485 self.asked_p[i] = p;
486 self.asked_x[i] = x;
487 }
488 self.asked_x.clone()
489 }
490
491 pub fn tell(&mut self, ys: &[f64]) -> i32 {
493 for (i, &y) in ys.iter().enumerate() {
494 let x = self.asked_x[i].clone();
495 self.tell_one(y, &x, self.asked_p[i]);
496 }
497 self.external_evaluations += ys.len() as u64;
498 self.stop
499 }
500
501 pub fn population(&self) -> Vec<Vec<f64>> {
502 self.pop_x.clone()
503 }
504
505 pub fn result(&self) -> DeResult {
506 self.make_result(self.external_evaluations)
507 }
508}
509
510#[cfg(test)]
511mod tests {
512 use super::*;
513
514 fn sphere(x: &[f64]) -> f64 {
515 x.iter().map(|v| v * v).sum()
516 }
517 fn rosen(x: &[f64]) -> f64 {
518 (0..x.len() - 1)
519 .map(|i| 100.0 * (x[i + 1] - x[i] * x[i]).powi(2) + (1.0 - x[i]).powi(2))
520 .sum()
521 }
522
523 fn optimize(obj: impl Objective, dim: usize, seed: u64, evals: u64) -> f64 {
524 let fit = Fitness::bounded(dim, 1, &vec![-5.0; dim], &vec![5.0; dim]);
525 let params = DeParams {
526 popsize: 31,
527 max_evaluations: evals,
528 seed,
529 ..Default::default()
530 };
531 let mut de = De::new(fit, &[], &[], None, ¶ms);
532 de.optimize(&obj).y
533 }
534
535 #[test]
536 fn minimizes_sphere() {
537 assert!(optimize(sphere as fn(&[f64]) -> f64, 5, 1, 8000) < 1e-6);
538 }
539
540 #[test]
541 fn minimizes_rosenbrock() {
542 let mut v: Vec<f64> = (0..5)
543 .map(|s| optimize(rosen as fn(&[f64]) -> f64, 5, s, 12000))
544 .collect();
545 v.sort_by(|a, b| a.partial_cmp(b).unwrap());
546 assert!(v[2] < 1.0, "rosen median too large: {v:?}");
547 }
548
549 #[test]
550 fn ask_tell_converges() {
551 let fit = Fitness::bounded(5, 1, &[-5.0; 5], &[5.0; 5]);
552 let params = DeParams {
553 popsize: 24,
554 max_evaluations: 8000,
555 seed: 3,
556 ..Default::default()
557 };
558 let mut de = De::new(fit, &[], &[], None, ¶ms);
559 for _ in 0..400 {
560 let pop = de.ask();
561 let ys: Vec<f64> = pop.iter().map(|x| sphere(x)).collect();
562 if de.tell(&ys) != 0 {
563 break;
564 }
565 }
566 assert!(de.result().y < 1e-3, "ask/tell did not converge");
567 }
568
569 #[test]
570 fn integer_modify_runs() {
571 let fit = Fitness::bounded(4, 1, &[-5.0; 4], &[5.0; 4]);
572 let params = DeParams {
573 popsize: 20,
574 max_evaluations: 4000,
575 seed: 5,
576 ..Default::default()
577 };
578 let ints = Some(vec![true, false, true, false]);
579 let mut de = De::new(fit, &[], &[], ints, ¶ms);
580 let r = de.optimize(&(sphere as fn(&[f64]) -> f64));
581 assert!(r.evaluations > 0);
582 }
583
584 #[test]
585 fn normal_sampling_default_parameters_getters_and_early_stop() {
586 let fit = Fitness::bounded(2, 1, &[-1.0; 2], &[1.0; 2]);
587 let params = DeParams {
588 popsize: 0,
589 max_evaluations: 10,
590 keep: 0.0,
591 stop_fitness: 1.0,
592 f: 0.0,
593 cr: 0.0,
594 min_mutate: 0.0,
595 max_mutate: 0.0,
596 min_sigma: 0.1,
597 seed: 11,
598 runid: -2,
599 };
600 let mut optimizer = De::new(fit, &[0.0, 0.0], &[0.2, 0.2], None, ¶ms);
601 assert_eq!(optimizer.dim(), 2);
602 assert_eq!(optimizer.popsize(), 30);
603 assert_eq!(optimizer.population().len(), 30);
604 assert_eq!(optimizer.stop(), 0);
605 let result = optimizer.optimize(&(sphere as fn(&[f64]) -> f64));
606 assert_eq!(result.stop, 1);
607 assert!(result.evaluations > 0);
608 }
609}