graphmind_optimization/algorithms/
cuckoo.rs1use crate::common::{Individual, OptimizationResult, Problem, SolverConfig};
2use ndarray::Array1;
3use rand::prelude::*;
4use rand_distr::Distribution;
5use std::f64::consts::PI;
6
7pub struct CuckooSolver {
8 pub config: SolverConfig,
9 pub pa: f64, }
11
12impl CuckooSolver {
13 pub fn new(config: SolverConfig) -> Self {
14 Self { config, pa: 0.25 }
15 }
16
17 pub fn with_pa(config: SolverConfig, pa: f64) -> Self {
18 Self { config, pa }
19 }
20
21 fn levy_flight(&self, dim: usize) -> Array1<f64> {
23 let beta = 1.5;
25 let sigma_u = ((gamma(1.0 + beta) * (PI * beta / 2.0).sin())
26 / (gamma((1.0 + beta) / 2.0) * beta * 2.0f64.powf((beta - 1.0) / 2.0)))
27 .powf(1.0 / beta);
28 let sigma_v = 1.0;
29
30 let mut step = Array1::zeros(dim);
31 let mut rng = thread_rng();
32
33 for i in 0..dim {
34 let _u: f64 = rng.gen_range(0.0..1.0) * sigma_u; let u_n: f64 = rand_distr::Normal::new(0.0, sigma_u)
37 .unwrap()
38 .sample(&mut rng);
39 let v_n: f64 = rand_distr::Normal::new(0.0, sigma_v)
40 .unwrap()
41 .sample(&mut rng);
42
43 let s = u_n / v_n.abs().powf(1.0 / beta);
44 step[i] = s;
45 }
46 step
47 }
48
49 pub fn solve<P: Problem>(&self, problem: &P) -> OptimizationResult {
50 let mut rng = thread_rng();
51 let dim = problem.dim();
52 let (lower, upper) = problem.bounds();
53
54 let mut nests: Vec<Individual> = (0..self.config.population_size)
56 .map(|_| {
57 let mut vars = Array1::zeros(dim);
58 for i in 0..dim {
59 vars[i] = rng.gen_range(lower[i]..upper[i]);
60 }
61 let fitness = problem.fitness(&vars);
62 Individual::new(vars, fitness)
63 })
64 .collect();
65
66 let mut best_idx = 0;
68 for i in 1..self.config.population_size {
69 if nests[i].fitness < nests[best_idx].fitness {
70 best_idx = i;
71 }
72 }
73 let mut best_ind = nests[best_idx].clone();
74
75 let mut history = Vec::with_capacity(self.config.max_iterations);
76
77 for _iter in 0..self.config.max_iterations {
78 history.push(best_ind.fitness);
79
80 for i in 0..self.config.population_size {
82 let step_size = 0.01; let levy = self.levy_flight(dim);
84 let current_vars = &nests[i].variables;
85
86 let mut new_vars = Array1::zeros(dim);
87 for j in 0..dim {
88 let diff = current_vars[j] - best_ind.variables[j];
93 let _delta = step_size * levy[j] * (if diff.abs() > 1e-6 { diff } else { 1.0 }); let delta_simple = step_size * levy[j] * (upper[j] - lower[j]);
98
99 new_vars[j] = (current_vars[j] + delta_simple).clamp(lower[j], upper[j]);
100 }
101
102 let new_fitness = problem.fitness(&new_vars);
103
104 let j = rng.gen_range(0..self.config.population_size);
107 if new_fitness < nests[j].fitness {
108 nests[j] = Individual::new(new_vars, new_fitness);
109 if nests[j].fitness < best_ind.fitness {
110 best_ind = nests[j].clone();
111 }
112 }
113 }
114
115 nests.sort_by(|a, b| a.fitness.partial_cmp(&b.fitness).unwrap());
119
120 let num_abandon = (self.config.population_size as f64 * self.pa) as usize;
122 let start_abandon_idx = self.config.population_size - num_abandon;
123
124 for i in start_abandon_idx..self.config.population_size {
125 let mut vars = Array1::zeros(dim);
128
129 let r1 = rng.gen_range(0..self.config.population_size);
131 let r2 = rng.gen_range(0..self.config.population_size);
132
133 for j in 0..dim {
134 let step = rng.gen::<f64>() * (nests[r1].variables[j] - nests[r2].variables[j]);
135 vars[j] = (nests[i].variables[j] + step).clamp(lower[j], upper[j]);
136 }
137
138 let fitness = problem.fitness(&vars);
139 nests[i] = Individual::new(vars, fitness);
140
141 if fitness < best_ind.fitness {
142 best_ind = nests[i].clone();
143 }
144 }
145 }
146
147 OptimizationResult {
148 best_variables: best_ind.variables,
149 best_fitness: best_ind.fitness,
150 history,
151 }
152 }
153}
154
155fn gamma(x: f64) -> f64 {
157 if (x - 1.5).abs() < 1e-6 {
163 return 0.886227;
164 }
165 if (x - 2.5).abs() < 1e-6 {
166 return 1.32934;
167 }
168
169 let term1 = (2.0 * PI / x).sqrt();
171 let term2 = (x / std::f64::consts::E).powf(x);
172 term1 * term2
173}