sciforge_lib/maths/optimization/
metaheuristic.rs1type 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}