1use burn::tensor::{Int, Tensor, TensorData, backend::Backend};
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.select(
78 0,
79 Tensor::<B, 1, Int>::from_data(TensorData::new(elite_idx.clone(), [k]), device),
80 );
81
82 let n_offspring_to_keep = pop_size - k;
83 assert!(
84 n_offspring_to_keep <= offspring_fitness.len(),
85 "elitist: not enough offspring ({}) to backfill {} slots after {k} elites",
86 offspring_fitness.len(),
87 n_offspring_to_keep,
88 );
89 let offspring_keep_idx = truncation_indices_host(offspring_fitness, n_offspring_to_keep);
90 let kept_offspring = offspring_pop.select(
91 0,
92 Tensor::<B, 1, Int>::from_data(
93 TensorData::new(offspring_keep_idx.clone(), [n_offspring_to_keep]),
94 device,
95 ),
96 );
97
98 let combined = Tensor::cat(vec![elites, kept_offspring], 0);
99
100 let mut combined_fitness = Vec::with_capacity(pop_size);
101 for i in elite_idx {
102 #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
103 combined_fitness.push(current_fitness[i as usize]);
104 }
105 for i in offspring_keep_idx {
106 #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
107 combined_fitness.push(offspring_fitness[i as usize]);
108 }
109
110 (combined, combined_fitness)
111}
112
113#[must_use]
130pub fn mu_plus_lambda<B: Backend>(
131 parents: Tensor<B, 2>,
132 parent_fitness: &[f32],
133 offspring: Tensor<B, 2>,
134 offspring_fitness: &[f32],
135 mu: usize,
136 device: &<B as burn::tensor::backend::BackendTypes>::Device,
137) -> (Tensor<B, 2>, Vec<f32>) {
138 let combined = Tensor::cat(vec![parents, offspring], 0);
139 let combined_fitness: Vec<f32> = parent_fitness
140 .iter()
141 .chain(offspring_fitness.iter())
142 .copied()
143 .collect();
144 let winners = truncation_indices_host(&combined_fitness, mu);
145 let next_fitness: Vec<f32> = winners
146 .iter()
147 .map(|&i| {
148 #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
149 combined_fitness[i as usize]
150 })
151 .collect();
152 let indices = Tensor::<B, 1, Int>::from_data(TensorData::new(winners, [mu]), device);
153 (combined.select(0, indices), next_fitness)
154}
155
156#[must_use]
172pub fn mu_comma_lambda<B: Backend>(
173 offspring: Tensor<B, 2>,
174 offspring_fitness: &[f32],
175 mu: usize,
176 device: &<B as burn::tensor::backend::BackendTypes>::Device,
177) -> (Tensor<B, 2>, Vec<f32>) {
178 assert!(
179 mu <= offspring_fitness.len(),
180 "(μ, λ): lambda must be >= mu",
181 );
182 let winners = truncation_indices_host(offspring_fitness, mu);
183 let next_fitness: Vec<f32> = winners
184 .iter()
185 .map(|&i| {
186 #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
187 offspring_fitness[i as usize]
188 })
189 .collect();
190 let indices = Tensor::<B, 1, Int>::from_data(TensorData::new(winners, [mu]), device);
191 (offspring.select(0, indices), next_fitness)
192}
193
194#[cfg(test)]
195mod tests {
196 use super::*;
197 use burn::backend::Flex;
198 type TestBackend = Flex;
199
200 #[test]
201 fn generational_discards_current() {
202 let device = Default::default();
203 let current =
204 Tensor::<TestBackend, 2>::from_data(TensorData::new(vec![0.0_f32; 4], [2, 2]), &device);
205 let offspring =
206 Tensor::<TestBackend, 2>::from_data(TensorData::new(vec![1.0_f32; 4], [2, 2]), &device);
207 let (next, f) =
208 generational::<TestBackend>(current, &[0.0, 0.0], offspring, vec![1.0, 1.0]);
209 let values = next
210 .into_data()
211 .into_vec::<f32>()
212 .expect("population host-read of a tensor this test just built");
213 for v in values {
214 approx::assert_relative_eq!(v, 1.0, epsilon = 1e-6);
215 }
216 assert_eq!(f, vec![1.0, 1.0]);
217 }
218
219 #[test]
220 fn mu_plus_lambda_keeps_best_overall() {
221 let device = Default::default();
222 let parents = Tensor::<TestBackend, 2>::from_data(
223 TensorData::new(vec![10.0_f32, 10.0, 10.0, 10.0], [2, 2]),
224 &device,
225 );
226 let offspring = Tensor::<TestBackend, 2>::from_data(
227 TensorData::new(vec![1.0_f32, 1.0, 5.0, 5.0], [2, 2]),
228 &device,
229 );
230 let (next, f) = mu_plus_lambda::<TestBackend>(
231 parents,
232 &[0.5, 100.0],
233 offspring,
234 &[0.1, 50.0],
235 2,
236 &device,
237 );
238 let rows = next
239 .into_data()
240 .into_vec::<f32>()
241 .expect("population host-read of a tensor this test just built");
242 assert_eq!(rows.len(), 4);
244 let mut f_sorted = f;
246 f_sorted.sort_by(f32::total_cmp);
247 approx::assert_relative_eq!(f_sorted[0], 50.0, epsilon = 1e-6);
248 approx::assert_relative_eq!(f_sorted[1], 100.0, epsilon = 1e-6);
249 }
250
251 #[test]
252 fn mu_comma_lambda_keeps_best_of_offspring() {
253 let device = Default::default();
254 let offspring = Tensor::<TestBackend, 2>::from_data(
255 TensorData::new(vec![1.0_f32, 1.0, 2.0, 2.0, 3.0, 3.0], [3, 2]),
256 &device,
257 );
258 let (next, f) = mu_comma_lambda::<TestBackend>(offspring, &[5.0, 1.0, 3.0], 2, &device);
259 assert_eq!(next.dims(), [2, 2]);
260 let mut fs = f;
262 fs.sort_by(f32::total_cmp);
263 approx::assert_relative_eq!(fs[0], 3.0, epsilon = 1e-6);
264 approx::assert_relative_eq!(fs[1], 5.0, epsilon = 1e-6);
265 }
266}