1use burn::tensor::{backend::Backend, Int, Tensor, TensorData};
28
29use crate::ops::selection::truncation_indices_host;
30
31#[must_use]
39pub fn generational<B: Backend>(
40 _current_pop: Tensor<B, 2>,
41 _current_fitness: &[f32],
42 offspring_pop: Tensor<B, 2>,
43 offspring_fitness: Vec<f32>,
44) -> (Tensor<B, 2>, Vec<f32>) {
45 (offspring_pop, offspring_fitness)
46}
47
48#[must_use]
66pub fn elitist<B: Backend>(
67 current_pop: Tensor<B, 2>,
68 current_fitness: &[f32],
69 offspring_pop: Tensor<B, 2>,
70 offspring_fitness: &[f32],
71 k: usize,
72 device: &<B as burn::tensor::backend::BackendTypes>::Device,
73) -> (Tensor<B, 2>, Vec<f32>) {
74 let pop_size = current_fitness.len();
75 assert!(k <= pop_size, "elite count must be <= population size");
76 let elite_idx = truncation_indices_host(current_fitness, k);
77 let elites = current_pop
78 .select(0, Tensor::<B, 1, Int>::from_data(TensorData::new(elite_idx.clone(), [k]), device));
79
80 let n_offspring_to_keep = pop_size - k;
81 let offspring_keep_idx = truncation_indices_host(offspring_fitness, n_offspring_to_keep);
82 let kept_offspring = offspring_pop.select(
83 0,
84 Tensor::<B, 1, Int>::from_data(
85 TensorData::new(offspring_keep_idx.clone(), [n_offspring_to_keep]),
86 device,
87 ),
88 );
89
90 let combined = Tensor::cat(vec![elites, kept_offspring], 0);
91
92 let mut combined_fitness = Vec::with_capacity(pop_size);
93 for i in elite_idx {
94 #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
95 combined_fitness.push(current_fitness[i as usize]);
96 }
97 for i in offspring_keep_idx {
98 #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
99 combined_fitness.push(offspring_fitness[i as usize]);
100 }
101
102 (combined, combined_fitness)
103}
104
105#[must_use]
122pub fn mu_plus_lambda<B: Backend>(
123 parents: Tensor<B, 2>,
124 parent_fitness: &[f32],
125 offspring: Tensor<B, 2>,
126 offspring_fitness: &[f32],
127 mu: usize,
128 device: &<B as burn::tensor::backend::BackendTypes>::Device,
129) -> (Tensor<B, 2>, Vec<f32>) {
130 let combined = Tensor::cat(vec![parents, offspring], 0);
131 let combined_fitness: Vec<f32> = parent_fitness
132 .iter()
133 .chain(offspring_fitness.iter())
134 .copied()
135 .collect();
136 let winners = truncation_indices_host(&combined_fitness, mu);
137 let next_fitness: Vec<f32> = winners
138 .iter()
139 .map(|&i| {
140 #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
141 combined_fitness[i as usize]
142 })
143 .collect();
144 let indices = Tensor::<B, 1, Int>::from_data(TensorData::new(winners, [mu]), device);
145 (combined.select(0, indices), next_fitness)
146}
147
148#[must_use]
164pub fn mu_comma_lambda<B: Backend>(
165 offspring: Tensor<B, 2>,
166 offspring_fitness: &[f32],
167 mu: usize,
168 device: &<B as burn::tensor::backend::BackendTypes>::Device,
169) -> (Tensor<B, 2>, Vec<f32>) {
170 assert!(
171 mu <= offspring_fitness.len(),
172 "(μ, λ): lambda must be >= mu",
173 );
174 let winners = truncation_indices_host(offspring_fitness, mu);
175 let next_fitness: Vec<f32> = winners
176 .iter()
177 .map(|&i| {
178 #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
179 offspring_fitness[i as usize]
180 })
181 .collect();
182 let indices = Tensor::<B, 1, Int>::from_data(TensorData::new(winners, [mu]), device);
183 (offspring.select(0, indices), next_fitness)
184}
185
186#[cfg(test)]
187mod tests {
188 use super::*;
189 use burn::backend::Flex;
190 type TestBackend = Flex;
191
192 #[test]
193 fn generational_discards_current() {
194 let device = Default::default();
195 let current = Tensor::<TestBackend, 2>::from_data(
196 TensorData::new(vec![0.0_f32; 4], [2, 2]),
197 &device,
198 );
199 let offspring = Tensor::<TestBackend, 2>::from_data(
200 TensorData::new(vec![1.0_f32; 4], [2, 2]),
201 &device,
202 );
203 let (next, f) = generational::<TestBackend>(
204 current,
205 &[0.0, 0.0],
206 offspring,
207 vec![1.0, 1.0],
208 );
209 let values = next.into_data().into_vec::<f32>().unwrap();
210 for v in values {
211 approx::assert_relative_eq!(v, 1.0, epsilon = 1e-6);
212 }
213 assert_eq!(f, vec![1.0, 1.0]);
214 }
215
216 #[test]
217 fn mu_plus_lambda_keeps_best_overall() {
218 let device = Default::default();
219 let parents = Tensor::<TestBackend, 2>::from_data(
220 TensorData::new(vec![10.0_f32, 10.0, 10.0, 10.0], [2, 2]),
221 &device,
222 );
223 let offspring = Tensor::<TestBackend, 2>::from_data(
224 TensorData::new(vec![1.0_f32, 1.0, 5.0, 5.0], [2, 2]),
225 &device,
226 );
227 let (next, f) = mu_plus_lambda::<TestBackend>(
228 parents,
229 &[0.5, 100.0],
230 offspring,
231 &[0.1, 50.0],
232 2,
233 &device,
234 );
235 let rows = next.into_data().into_vec::<f32>().unwrap();
236 assert_eq!(rows.len(), 4);
238 let mut f_sorted = f;
240 f_sorted.sort_by(|a, b| a.partial_cmp(b).unwrap());
241 approx::assert_relative_eq!(f_sorted[0], 0.1, epsilon = 1e-6);
242 approx::assert_relative_eq!(f_sorted[1], 0.5, epsilon = 1e-6);
243 }
244
245 #[test]
246 fn mu_comma_lambda_keeps_best_of_offspring() {
247 let device = Default::default();
248 let offspring = Tensor::<TestBackend, 2>::from_data(
249 TensorData::new(vec![1.0_f32, 1.0, 2.0, 2.0, 3.0, 3.0], [3, 2]),
250 &device,
251 );
252 let (next, f) = mu_comma_lambda::<TestBackend>(
253 offspring,
254 &[5.0, 1.0, 3.0],
255 2,
256 &device,
257 );
258 assert_eq!(next.dims(), [2, 2]);
259 let mut fs = f;
260 fs.sort_by(|a, b| a.partial_cmp(b).unwrap());
261 approx::assert_relative_eq!(fs[0], 1.0, epsilon = 1e-6);
262 approx::assert_relative_eq!(fs[1], 3.0, epsilon = 1e-6);
263 }
264}