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