Skip to main content

sciforge_lib/maths/optimization/
metaheuristic.rs

1type HillClimbOptimizer = fn(fn(&[f64]) -> f64, &[f64], &[(f64, f64)], f64, usize, u64) -> Vec<f64>;
2
3pub fn simulated_annealing(
4    objective: fn(&[f64]) -> f64,
5    x0: &[f64],
6    bounds: &[(f64, f64)],
7    t_initial: f64,
8    t_min: f64,
9    cooling_rate: f64,
10    steps_per_temp: usize,
11    seed: u64,
12) -> Vec<f64> {
13    let mut rng = SimpleRng::new(seed);
14    let mut x = x0.to_vec();
15    let mut best = x.clone();
16    let mut e = objective(&x);
17    let mut e_best = e;
18    let mut temp = t_initial;
19    while temp > t_min {
20        for _ in 0..steps_per_temp {
21            let mut candidate = x.clone();
22            let idx = rng.next_usize(x.len());
23            let range = bounds[idx].1 - bounds[idx].0;
24            candidate[idx] += (rng.next_f64() - 0.5) * range * 0.1;
25            candidate[idx] = candidate[idx].clamp(bounds[idx].0, bounds[idx].1);
26            let e_new = objective(&candidate);
27            let delta = e_new - e;
28            if delta < 0.0 || rng.next_f64() < (-delta / temp).exp() {
29                x = candidate;
30                e = e_new;
31                if e < e_best {
32                    best = x.clone();
33                    e_best = e;
34                }
35            }
36        }
37        temp *= cooling_rate;
38    }
39    best
40}
41
42pub fn particle_swarm(
43    objective: fn(&[f64]) -> f64,
44    bounds: &[(f64, f64)],
45    n_particles: usize,
46    max_iter: usize,
47    w: f64,
48    c1: f64,
49    c2: f64,
50    seed: u64,
51) -> Vec<f64> {
52    let n = bounds.len();
53    let mut rng = SimpleRng::new(seed);
54    let mut positions: Vec<Vec<f64>> = (0..n_particles)
55        .map(|_| {
56            (0..n)
57                .map(|j| bounds[j].0 + rng.next_f64() * (bounds[j].1 - bounds[j].0))
58                .collect()
59        })
60        .collect();
61    let mut velocities: Vec<Vec<f64>> = (0..n_particles).map(|_| vec![0.0; n]).collect();
62    let mut p_best = positions.clone();
63    let mut p_best_val: Vec<f64> = positions.iter().map(|p| objective(p)).collect();
64    let g_best_idx = p_best_val
65        .iter()
66        .enumerate()
67        .min_by(|a, b| a.1.partial_cmp(b.1).unwrap_or(std::cmp::Ordering::Equal))
68        .unwrap()
69        .0;
70    let mut g_best = positions[g_best_idx].clone();
71    let mut g_best_val = p_best_val[g_best_idx];
72    for _ in 0..max_iter {
73        for i in 0..n_particles {
74            for j in 0..n {
75                let r1 = rng.next_f64();
76                let r2 = rng.next_f64();
77                velocities[i][j] = w * velocities[i][j]
78                    + c1 * r1 * (p_best[i][j] - positions[i][j])
79                    + c2 * r2 * (g_best[j] - positions[i][j]);
80                positions[i][j] =
81                    (positions[i][j] + velocities[i][j]).clamp(bounds[j].0, bounds[j].1);
82            }
83            let val = objective(&positions[i]);
84            if val < p_best_val[i] {
85                p_best_val[i] = val;
86                p_best[i] = positions[i].clone();
87                if val < g_best_val {
88                    g_best_val = val;
89                    g_best = positions[i].clone();
90                }
91            }
92        }
93    }
94    g_best
95}
96
97pub fn nelder_mead(
98    f: fn(&[f64]) -> f64,
99    x0: &[f64],
100    step: f64,
101    max_iter: usize,
102    tol: f64,
103) -> Vec<f64> {
104    let n = x0.len();
105    let mut simplex: Vec<Vec<f64>> = Vec::with_capacity(n + 1);
106    simplex.push(x0.to_vec());
107    for i in 0..n {
108        let mut v = x0.to_vec();
109        v[i] += step;
110        simplex.push(v);
111    }
112    for _ in 0..max_iter {
113        let mut order: Vec<usize> = (0..=n).collect();
114        order.sort_by(|&a, &b| {
115            f(&simplex[a])
116                .partial_cmp(&f(&simplex[b]))
117                .unwrap_or(std::cmp::Ordering::Equal)
118        });
119        let best_val = f(&simplex[order[0]]);
120        let worst_val = f(&simplex[order[n]]);
121        if worst_val - best_val < tol {
122            break;
123        }
124        let centroid: Vec<f64> = (0..n)
125            .map(|j| order[..n].iter().map(|&i| simplex[i][j]).sum::<f64>() / n as f64)
126            .collect();
127        let reflected: Vec<f64> = (0..n)
128            .map(|j| 2.0 * centroid[j] - simplex[order[n]][j])
129            .collect();
130        let fr = f(&reflected);
131        if fr < f(&simplex[order[n - 1]]) && fr >= best_val {
132            simplex[order[n]] = reflected;
133        } else if fr < best_val {
134            let expanded: Vec<f64> = (0..n)
135                .map(|j| 3.0 * centroid[j] - 2.0 * simplex[order[n]][j])
136                .collect();
137            if f(&expanded) < fr {
138                simplex[order[n]] = expanded;
139            } else {
140                simplex[order[n]] = reflected;
141            }
142        } else {
143            let contracted: Vec<f64> = (0..n)
144                .map(|j| 0.5 * (centroid[j] + simplex[order[n]][j]))
145                .collect();
146            if f(&contracted) < worst_val {
147                simplex[order[n]] = contracted;
148            } else {
149                for i in 1..=n {
150                    let best = simplex[order[0]].clone();
151                    for (sij, &bj) in simplex[order[i]].iter_mut().zip(&best) {
152                        *sij = 0.5 * (*sij + bj);
153                    }
154                }
155            }
156        }
157    }
158    let mut order: Vec<usize> = (0..=n).collect();
159    order.sort_by(|&a, &b| {
160        f(&simplex[a])
161            .partial_cmp(&f(&simplex[b]))
162            .unwrap_or(std::cmp::Ordering::Equal)
163    });
164    simplex[order[0]].clone()
165}
166
167struct SimpleRng {
168    state: u64,
169}
170impl SimpleRng {
171    fn new(seed: u64) -> Self {
172        Self { state: seed }
173    }
174    fn next_u64(&mut self) -> u64 {
175        self.state = self
176            .state
177            .wrapping_mul(6364136223846793005)
178            .wrapping_add(1442695040888963407);
179        self.state
180    }
181    fn next_f64(&mut self) -> f64 {
182        (self.next_u64() >> 11) as f64 / (1u64 << 53) as f64
183    }
184    fn next_usize(&mut self, max: usize) -> usize {
185        (self.next_f64() * max as f64) as usize % max
186    }
187}
188
189pub fn tabu_search(
190    objective: fn(&[f64]) -> f64,
191    x0: &[f64],
192    bounds: &[(f64, f64)],
193    tabu_size: usize,
194    n_neighbors: usize,
195    max_iter: usize,
196    seed: u64,
197) -> Vec<f64> {
198    let n = x0.len();
199    let mut rng = SimpleRng::new(seed);
200    let mut best = x0.to_vec();
201    let mut best_val = objective(&best);
202    let mut current = best.clone();
203    let mut tabu_list: Vec<Vec<f64>> = Vec::new();
204    for _ in 0..max_iter {
205        let mut best_neighbor = current.clone();
206        let mut best_neighbor_val = f64::INFINITY;
207        for _ in 0..n_neighbors {
208            let mut neighbor = current.clone();
209            let idx = rng.next_usize(n);
210            let range = bounds[idx].1 - bounds[idx].0;
211            neighbor[idx] = (neighbor[idx] + (rng.next_f64() - 0.5) * range * 0.2)
212                .clamp(bounds[idx].0, bounds[idx].1);
213            let is_tabu = tabu_list.iter().any(|t| {
214                t.iter()
215                    .zip(neighbor.iter())
216                    .all(|(a, b)| (a - b).abs() < 1e-10)
217            });
218            if is_tabu {
219                continue;
220            }
221            let val = objective(&neighbor);
222            if val < best_neighbor_val {
223                best_neighbor = neighbor;
224                best_neighbor_val = val;
225            }
226        }
227        current = best_neighbor.clone();
228        tabu_list.push(best_neighbor.clone());
229        if tabu_list.len() > tabu_size {
230            tabu_list.remove(0);
231        }
232        if best_neighbor_val < best_val {
233            best = best_neighbor;
234            best_val = best_neighbor_val;
235        }
236    }
237    best
238}
239
240pub fn firefly_algorithm(
241    objective: fn(&[f64]) -> f64,
242    bounds: &[(f64, f64)],
243    pop_size: usize,
244    max_iter: usize,
245    alpha: f64,
246    beta0: f64,
247    gamma: f64,
248    seed: u64,
249) -> Vec<f64> {
250    let n = bounds.len();
251    let mut rng = SimpleRng::new(seed);
252    let mut pop: Vec<Vec<f64>> = (0..pop_size)
253        .map(|_| {
254            (0..n)
255                .map(|j| bounds[j].0 + rng.next_f64() * (bounds[j].1 - bounds[j].0))
256                .collect()
257        })
258        .collect();
259    let mut fitness: Vec<f64> = pop.iter().map(|p| objective(p)).collect();
260    for _ in 0..max_iter {
261        for i in 0..pop_size {
262            for j in 0..pop_size {
263                if fitness[j] < fitness[i] {
264                    let dist_sq: f64 = pop[i]
265                        .iter()
266                        .zip(pop[j].iter())
267                        .map(|(a, b)| (a - b) * (a - b))
268                        .sum();
269                    let beta = beta0 * (-gamma * dist_sq).exp();
270                    for (k, bk) in bounds.iter().enumerate() {
271                        pop[i][k] +=
272                            beta * (pop[j][k] - pop[i][k]) + alpha * (rng.next_f64() - 0.5);
273                        pop[i][k] = pop[i][k].clamp(bk.0, bk.1);
274                    }
275                    fitness[i] = objective(&pop[i]);
276                }
277            }
278        }
279    }
280    let best = fitness
281        .iter()
282        .enumerate()
283        .min_by(|a, b| a.1.partial_cmp(b.1).unwrap_or(std::cmp::Ordering::Equal))
284        .unwrap()
285        .0;
286    pop[best].clone()
287}
288
289pub fn grey_wolf_optimizer(
290    objective: fn(&[f64]) -> f64,
291    bounds: &[(f64, f64)],
292    pop_size: usize,
293    max_iter: usize,
294    seed: u64,
295) -> Vec<f64> {
296    let n = bounds.len();
297    let mut rng = SimpleRng::new(seed);
298    let mut pop: Vec<Vec<f64>> = (0..pop_size)
299        .map(|_| {
300            (0..n)
301                .map(|j| bounds[j].0 + rng.next_f64() * (bounds[j].1 - bounds[j].0))
302                .collect()
303        })
304        .collect();
305    let mut fitness: Vec<f64> = pop.iter().map(|p| objective(p)).collect();
306    for iter in 0..max_iter {
307        let mut sorted: Vec<usize> = (0..pop_size).collect();
308        sorted.sort_by(|&a, &b| {
309            fitness[a]
310                .partial_cmp(&fitness[b])
311                .unwrap_or(std::cmp::Ordering::Equal)
312        });
313        let alpha_w = pop[sorted[0]].clone();
314        let beta_w = pop[sorted[1.min(pop_size - 1)]].clone();
315        let delta_w = pop[sorted[2.min(pop_size - 1)]].clone();
316        let a_coeff = 2.0 * (1.0 - iter as f64 / max_iter as f64);
317        for i in 0..pop_size {
318            for j in 0..n {
319                let r1 = rng.next_f64();
320                let r2 = rng.next_f64();
321                let a1 = 2.0 * a_coeff * r1 - a_coeff;
322                let c1 = 2.0 * r2;
323                let d_alpha = (c1 * alpha_w[j] - pop[i][j]).abs();
324                let x1 = alpha_w[j] - a1 * d_alpha;
325
326                let r1 = rng.next_f64();
327                let r2 = rng.next_f64();
328                let a2 = 2.0 * a_coeff * r1 - a_coeff;
329                let c2 = 2.0 * r2;
330                let d_beta = (c2 * beta_w[j] - pop[i][j]).abs();
331                let x2 = beta_w[j] - a2 * d_beta;
332
333                let r1 = rng.next_f64();
334                let r2 = rng.next_f64();
335                let a3 = 2.0 * a_coeff * r1 - a_coeff;
336                let c3 = 2.0 * r2;
337                let d_delta = (c3 * delta_w[j] - pop[i][j]).abs();
338                let x3 = delta_w[j] - a3 * d_delta;
339
340                pop[i][j] = ((x1 + x2 + x3) / 3.0).clamp(bounds[j].0, bounds[j].1);
341            }
342            fitness[i] = objective(&pop[i]);
343        }
344    }
345    let best = fitness
346        .iter()
347        .enumerate()
348        .min_by(|a, b| a.1.partial_cmp(b.1).unwrap_or(std::cmp::Ordering::Equal))
349        .unwrap()
350        .0;
351    pop[best].clone()
352}
353
354pub fn hill_climbing(
355    objective: fn(&[f64]) -> f64,
356    x0: &[f64],
357    bounds: &[(f64, f64)],
358    step_size: f64,
359    max_iter: usize,
360    seed: u64,
361) -> Vec<f64> {
362    let n = x0.len();
363    let mut rng = SimpleRng::new(seed);
364    let mut x = x0.to_vec();
365    let mut fx = objective(&x);
366    for _ in 0..max_iter {
367        let mut candidate = x.clone();
368        let idx = rng.next_usize(n);
369        let range = bounds[idx].1 - bounds[idx].0;
370        candidate[idx] = (candidate[idx] + (rng.next_f64() - 0.5) * range * step_size)
371            .clamp(bounds[idx].0, bounds[idx].1);
372        let fc = objective(&candidate);
373        if fc < fx {
374            x = candidate;
375            fx = fc;
376        }
377    }
378    x
379}
380
381pub fn random_search(
382    objective: fn(&[f64]) -> f64,
383    bounds: &[(f64, f64)],
384    n_samples: usize,
385    seed: u64,
386) -> Vec<f64> {
387    let n = bounds.len();
388    let mut rng = SimpleRng::new(seed);
389    let mut best: Vec<f64> = (0..n)
390        .map(|j| bounds[j].0 + rng.next_f64() * (bounds[j].1 - bounds[j].0))
391        .collect();
392    let mut best_val = objective(&best);
393    for _ in 1..n_samples {
394        let candidate: Vec<f64> = (0..n)
395            .map(|j| bounds[j].0 + rng.next_f64() * (bounds[j].1 - bounds[j].0))
396            .collect();
397        let val = objective(&candidate);
398        if val < best_val {
399            best = candidate;
400            best_val = val;
401        }
402    }
403    best
404}
405
406pub fn multistart(
407    optimizer: HillClimbOptimizer,
408    objective: fn(&[f64]) -> f64,
409    bounds: &[(f64, f64)],
410    step_size: f64,
411    n_starts: usize,
412    iter_per_start: usize,
413    seed: u64,
414) -> Vec<f64> {
415    let n = bounds.len();
416    let mut rng = SimpleRng::new(seed);
417    let mut global_best: Vec<f64> = Vec::new();
418    let mut global_best_val = f64::INFINITY;
419    for _ in 0..n_starts {
420        let x0: Vec<f64> = (0..n)
421            .map(|j| bounds[j].0 + rng.next_f64() * (bounds[j].1 - bounds[j].0))
422            .collect();
423        let s = rng.next_u64();
424        let result = optimizer(objective, &x0, bounds, step_size, iter_per_start, s);
425        let val = objective(&result);
426        if val < global_best_val {
427            global_best = result;
428            global_best_val = val;
429        }
430    }
431    global_best
432}
433
434pub fn whale_optimization(
435    objective: fn(&[f64]) -> f64,
436    bounds: &[(f64, f64)],
437    pop_size: usize,
438    max_iter: usize,
439    seed: u64,
440) -> Vec<f64> {
441    let dim = bounds.len();
442    let mut rng = SimpleRng::new(seed);
443    let mut pop: Vec<Vec<f64>> = (0..pop_size)
444        .map(|_| {
445            (0..dim)
446                .map(|j| bounds[j].0 + rng.next_f64() * (bounds[j].1 - bounds[j].0))
447                .collect()
448        })
449        .collect();
450    let mut fitness: Vec<f64> = pop.iter().map(|p| objective(p)).collect();
451    for t in 0..max_iter {
452        let best_idx = fitness
453            .iter()
454            .enumerate()
455            .min_by(|a, b| a.1.partial_cmp(b.1).unwrap_or(std::cmp::Ordering::Equal))
456            .unwrap()
457            .0;
458        let leader = pop[best_idx].clone();
459        let a = 2.0 * (1.0 - t as f64 / max_iter as f64);
460        for i in 0..pop_size {
461            let p = rng.next_f64();
462            if p < 0.5 {
463                let a_vec = 2.0 * a * rng.next_f64() - a;
464                if a_vec.abs() < 1.0 {
465                    for j in 0..dim {
466                        let d = (leader[j] - pop[i][j]).abs();
467                        pop[i][j] = (leader[j] - a_vec * d).clamp(bounds[j].0, bounds[j].1);
468                    }
469                } else {
470                    let rand_idx = rng.next_usize(pop_size);
471                    for (j, bj) in bounds.iter().enumerate() {
472                        let d = (pop[rand_idx][j] - pop[i][j]).abs();
473                        pop[i][j] = (pop[rand_idx][j] - a_vec * d).clamp(bj.0, bj.1);
474                    }
475                }
476            } else {
477                let l = rng.next_f64() * 2.0 - 1.0;
478                let b_spiral = 1.0;
479                for j in 0..dim {
480                    let d = (leader[j] - pop[i][j]).abs();
481                    pop[i][j] =
482                        (d * (b_spiral * l * std::f64::consts::PI).cos() * (b_spiral * l).exp()
483                            + leader[j])
484                            .clamp(bounds[j].0, bounds[j].1);
485                }
486            }
487            fitness[i] = objective(&pop[i]);
488        }
489    }
490    let best = fitness
491        .iter()
492        .enumerate()
493        .min_by(|a, b| a.1.partial_cmp(b.1).unwrap_or(std::cmp::Ordering::Equal))
494        .unwrap()
495        .0;
496    pop[best].clone()
497}