metaheurustics/algorithm/
de.rs1use ndarray::{Array1, Array2};
2use rand::Rng;
3use rand::seq::SliceRandom;
4use crate::{MetaheuristicError, Result};
5use super::{ObjectiveFunction, OptimizationParams, OptimizationResult, argmin};
6
7#[derive(Debug, Clone, Copy)]
9pub enum DEStrategy {
10 RandOneBin,
11 BestOneBin,
12 RandTwoBin,
13}
14
15#[derive(Debug, Clone)]
17pub struct DEParams {
18 pub opt_params: OptimizationParams,
20 pub strategy: DEStrategy,
22 pub crossover_rate: f64,
24 pub mutation_factor: f64,
26}
27
28impl Default for DEParams {
29 fn default() -> Self {
30 Self {
31 opt_params: OptimizationParams::default(),
32 strategy: DEStrategy::RandOneBin,
33 crossover_rate: 0.9,
34 mutation_factor: 0.8,
35 }
36 }
37}
38
39pub struct DE<'a> {
41 objective: &'a dyn ObjectiveFunction,
42 params: DEParams,
43}
44
45impl<'a> DE<'a> {
46 pub fn new(objective: &'a dyn ObjectiveFunction, params: DEParams) -> Self {
48 Self { objective, params }
49 }
50
51 pub fn optimize(&self) -> Result<OptimizationResult> {
53 let mut rng = rand::thread_rng();
54
55 let mut population = self.initialize_population(&mut rng)?;
57 let mut fitness = self.evaluate_population(&population);
58
59 let best_idx = argmin(&fitness).expect("Failed to find minimum fitness");
60 let mut best_solution = population.row(best_idx).to_owned();
61 let mut best_fitness = fitness[best_idx];
62
63 let mut evaluations = self.params.opt_params.population_size;
64
65 for _iteration in 0..self.params.opt_params.max_iterations {
66 let mut new_population = Array2::zeros(population.raw_dim());
68
69 for i in 0..self.params.opt_params.population_size {
70 let trial_vector = match self.params.strategy {
71 DEStrategy::RandOneBin => self.rand_1_bin(i, &population, &mut rng),
72 DEStrategy::BestOneBin => self.best_1_bin(i, &population, best_idx, &mut rng),
73 DEStrategy::RandTwoBin => self.rand_2_bin(i, &population, &mut rng),
74 };
75
76 let trial_fitness = self.objective.evaluate(&trial_vector);
78 evaluations += 1;
79
80 if trial_fitness <= fitness[i] {
82 new_population.row_mut(i).assign(&trial_vector);
83 fitness[i] = trial_fitness;
84
85 if trial_fitness < best_fitness {
87 best_fitness = trial_fitness;
88 best_solution = trial_vector.clone();
89 }
90 } else {
91 new_population.row_mut(i).assign(&population.row(i));
92 }
93 }
94
95 population = new_population;
96
97 if let Some(target) = self.params.opt_params.target_value {
99 if best_fitness <= target {
100 break;
101 }
102 }
103 }
104
105 Ok(OptimizationResult {
106 best_solution,
107 best_fitness,
108 iterations: self.params.opt_params.max_iterations,
109 evaluations,
110 })
111 }
112
113 fn initialize_population(&self, rng: &mut impl Rng) -> Result<Array2<f64>> {
115 let (lower_bounds, upper_bounds) = self.objective.bounds();
116 let dims = self.objective.dimensions();
117
118 if lower_bounds.len() != dims || upper_bounds.len() != dims {
119 return Err(MetaheuristicError::InvalidDimension {
120 expected: dims,
121 got: lower_bounds.len(),
122 });
123 }
124
125 let mut population = Array2::zeros((self.params.opt_params.population_size, dims));
126 for i in 0..self.params.opt_params.population_size {
127 for j in 0..dims {
128 population[[i, j]] = rng.gen_range(lower_bounds[j]..=upper_bounds[j]);
129 }
130 }
131
132 Ok(population)
133 }
134
135 fn evaluate_population(&self, population: &Array2<f64>) -> Array1<f64> {
137 let mut fitness = Array1::zeros(population.nrows());
138 for (i, row) in population.rows().into_iter().enumerate() {
139 fitness[i] = self.objective.evaluate(&row.to_owned());
140 }
141 fitness
142 }
143
144 fn rand_1_bin(&self, target_idx: usize, population: &Array2<f64>, rng: &mut impl Rng) -> Array1<f64> {
146 let dims = self.objective.dimensions();
147 let (lower_bounds, upper_bounds) = self.objective.bounds();
148
149 let mut indices: Vec<usize> = (0..self.params.opt_params.population_size)
151 .filter(|&x| x != target_idx)
152 .collect();
153 indices.shuffle(rng);
154 let (r1, r2, r3) = (indices[0], indices[1], indices[2]);
155
156 let mut trial = population.row(target_idx).to_owned();
158 let j_rand = rng.gen_range(0..dims);
159
160 for j in 0..dims {
161 if j == j_rand || rng.gen::<f64>() < self.params.crossover_rate {
162 trial[j] = population[[r1, j]] + self.params.mutation_factor * (population[[r2, j]] - population[[r3, j]]);
163 trial[j] = trial[j].clamp(lower_bounds[j], upper_bounds[j]);
164 }
165 }
166
167 trial
168 }
169
170 fn best_1_bin(&self, target_idx: usize, population: &Array2<f64>, best_idx: usize, rng: &mut impl Rng) -> Array1<f64> {
172 let dims = self.objective.dimensions();
173 let (lower_bounds, upper_bounds) = self.objective.bounds();
174
175 let mut indices: Vec<usize> = (0..self.params.opt_params.population_size)
177 .filter(|&x| x != target_idx && x != best_idx)
178 .collect();
179 indices.shuffle(rng);
180 let (r1, r2) = (indices[0], indices[1]);
181
182 let mut trial = population.row(target_idx).to_owned();
184 let j_rand = rng.gen_range(0..dims);
185
186 for j in 0..dims {
187 if j == j_rand || rng.gen::<f64>() < self.params.crossover_rate {
188 trial[j] = population[[best_idx, j]] + self.params.mutation_factor * (population[[r1, j]] - population[[r2, j]]);
189 trial[j] = trial[j].clamp(lower_bounds[j], upper_bounds[j]);
190 }
191 }
192
193 trial
194 }
195
196 fn rand_2_bin(&self, target_idx: usize, population: &Array2<f64>, rng: &mut impl Rng) -> Array1<f64> {
198 let dims = self.objective.dimensions();
199 let (lower_bounds, upper_bounds) = self.objective.bounds();
200
201 let mut indices: Vec<usize> = (0..self.params.opt_params.population_size)
203 .filter(|&x| x != target_idx)
204 .collect();
205 indices.shuffle(rng);
206 let (r1, r2, r3, r4, r5) = (indices[0], indices[1], indices[2], indices[3], indices[4]);
207
208 let mut trial = population.row(target_idx).to_owned();
210 let j_rand = rng.gen_range(0..dims);
211
212 for j in 0..dims {
213 if j == j_rand || rng.gen::<f64>() < self.params.crossover_rate {
214 trial[j] = population[[r1, j]] +
215 self.params.mutation_factor * (population[[r2, j]] - population[[r3, j]]) +
216 self.params.mutation_factor * (population[[r4, j]] - population[[r5, j]]);
217 trial[j] = trial[j].clamp(lower_bounds[j], upper_bounds[j]);
218 }
219 }
220
221 trial
222 }
223}
224
225#[cfg(test)]
226mod tests {
227 use super::*;
228 use crate::test_function::{Sphere, TestFunction};
229
230 #[test]
231 fn test_de_optimization() {
232 let problem = Sphere::new(2);
233 let params = DEParams::default();
234 let optimizer = DE::new(&problem, params);
235
236 let result = optimizer.optimize().unwrap();
237
238 assert!(result.best_fitness < 1e-2); let global_min = problem.global_minimum_position();
243 for (x, x_min) in result.best_solution.iter().zip(global_min.iter()) {
244 assert!((x - x_min).abs() < 1e-1);
245 }
246 }
247
248 #[test]
249 fn test_de_strategies() {
250 let problem = Sphere::new(2);
251
252 let strategies = [
254 DEStrategy::RandOneBin,
255 DEStrategy::BestOneBin,
256 DEStrategy::RandTwoBin,
257 ];
258
259 for strategy in strategies.iter() {
260 let params = DEParams {
261 strategy: *strategy,
262 ..DEParams::default()
263 };
264 let optimizer = DE::new(&problem, params);
265 let result = optimizer.optimize().unwrap();
266
267 assert!(result.best_fitness < 1e-2);
269 }
270 }
271}