1use burn::tensor::{backend::Backend, Int, Tensor, TensorData};
11
12use crate::ops::selection::truncation_indices_host;
13
14#[must_use]
16pub fn generational<B: Backend>(
17 _current_pop: Tensor<B, 2>,
18 _current_fitness: &[f32],
19 offspring_pop: Tensor<B, 2>,
20 offspring_fitness: Vec<f32>,
21) -> (Tensor<B, 2>, Vec<f32>) {
22 (offspring_pop, offspring_fitness)
23}
24
25#[must_use]
35pub fn elitist<B: Backend>(
36 current_pop: Tensor<B, 2>,
37 current_fitness: &[f32],
38 offspring_pop: Tensor<B, 2>,
39 offspring_fitness: &[f32],
40 k: usize,
41 device: &B::Device,
42) -> (Tensor<B, 2>, Vec<f32>) {
43 let pop_size = current_fitness.len();
44 assert!(k <= pop_size, "elite count must be <= population size");
45 let elite_idx = truncation_indices_host(current_fitness, k);
46 let elites = current_pop
47 .select(0, Tensor::<B, 1, Int>::from_data(TensorData::new(elite_idx.clone(), [k]), device));
48
49 let n_offspring_to_keep = pop_size - k;
50 let offspring_keep_idx = truncation_indices_host(offspring_fitness, n_offspring_to_keep);
51 let kept_offspring = offspring_pop.select(
52 0,
53 Tensor::<B, 1, Int>::from_data(
54 TensorData::new(offspring_keep_idx.clone(), [n_offspring_to_keep]),
55 device,
56 ),
57 );
58
59 let combined = Tensor::cat(vec![elites, kept_offspring], 0);
60
61 let mut combined_fitness = Vec::with_capacity(pop_size);
62 for i in elite_idx {
63 #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
64 combined_fitness.push(current_fitness[i as usize]);
65 }
66 for i in offspring_keep_idx {
67 #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
68 combined_fitness.push(offspring_fitness[i as usize]);
69 }
70
71 (combined, combined_fitness)
72}
73
74#[must_use]
83pub fn mu_plus_lambda<B: Backend>(
84 parents: Tensor<B, 2>,
85 parent_fitness: &[f32],
86 offspring: Tensor<B, 2>,
87 offspring_fitness: &[f32],
88 mu: usize,
89 device: &B::Device,
90) -> (Tensor<B, 2>, Vec<f32>) {
91 let combined = Tensor::cat(vec![parents, offspring], 0);
92 let combined_fitness: Vec<f32> = parent_fitness
93 .iter()
94 .chain(offspring_fitness.iter())
95 .copied()
96 .collect();
97 let winners = truncation_indices_host(&combined_fitness, mu);
98 let next_fitness: Vec<f32> = winners
99 .iter()
100 .map(|&i| {
101 #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
102 combined_fitness[i as usize]
103 })
104 .collect();
105 let indices = Tensor::<B, 1, Int>::from_data(TensorData::new(winners, [mu]), device);
106 (combined.select(0, indices), next_fitness)
107}
108
109#[must_use]
116pub fn mu_comma_lambda<B: Backend>(
117 offspring: Tensor<B, 2>,
118 offspring_fitness: &[f32],
119 mu: usize,
120 device: &B::Device,
121) -> (Tensor<B, 2>, Vec<f32>) {
122 assert!(
123 mu <= offspring_fitness.len(),
124 "(μ, λ): lambda must be >= mu",
125 );
126 let winners = truncation_indices_host(offspring_fitness, mu);
127 let next_fitness: Vec<f32> = winners
128 .iter()
129 .map(|&i| {
130 #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
131 offspring_fitness[i as usize]
132 })
133 .collect();
134 let indices = Tensor::<B, 1, Int>::from_data(TensorData::new(winners, [mu]), device);
135 (offspring.select(0, indices), next_fitness)
136}
137
138#[cfg(test)]
139mod tests {
140 use super::*;
141 use burn::backend::NdArray;
142 type TestBackend = NdArray;
143
144 #[test]
145 fn generational_discards_current() {
146 let device = Default::default();
147 let current = Tensor::<TestBackend, 2>::from_data(
148 TensorData::new(vec![0.0_f32; 4], [2, 2]),
149 &device,
150 );
151 let offspring = Tensor::<TestBackend, 2>::from_data(
152 TensorData::new(vec![1.0_f32; 4], [2, 2]),
153 &device,
154 );
155 let (next, f) = generational::<TestBackend>(
156 current,
157 &[0.0, 0.0],
158 offspring,
159 vec![1.0, 1.0],
160 );
161 let values = next.into_data().into_vec::<f32>().unwrap();
162 for v in values {
163 approx::assert_relative_eq!(v, 1.0, epsilon = 1e-6);
164 }
165 assert_eq!(f, vec![1.0, 1.0]);
166 }
167
168 #[test]
169 fn mu_plus_lambda_keeps_best_overall() {
170 let device = Default::default();
171 let parents = Tensor::<TestBackend, 2>::from_data(
172 TensorData::new(vec![10.0_f32, 10.0, 10.0, 10.0], [2, 2]),
173 &device,
174 );
175 let offspring = Tensor::<TestBackend, 2>::from_data(
176 TensorData::new(vec![1.0_f32, 1.0, 5.0, 5.0], [2, 2]),
177 &device,
178 );
179 let (next, f) = mu_plus_lambda::<TestBackend>(
180 parents,
181 &[0.5, 100.0],
182 offspring,
183 &[0.1, 50.0],
184 2,
185 &device,
186 );
187 let rows = next.into_data().into_vec::<f32>().unwrap();
188 assert_eq!(rows.len(), 4);
190 let mut f_sorted = f;
192 f_sorted.sort_by(|a, b| a.partial_cmp(b).unwrap());
193 approx::assert_relative_eq!(f_sorted[0], 0.1, epsilon = 1e-6);
194 approx::assert_relative_eq!(f_sorted[1], 0.5, epsilon = 1e-6);
195 }
196
197 #[test]
198 fn mu_comma_lambda_keeps_best_of_offspring() {
199 let device = Default::default();
200 let offspring = Tensor::<TestBackend, 2>::from_data(
201 TensorData::new(vec![1.0_f32, 1.0, 2.0, 2.0, 3.0, 3.0], [3, 2]),
202 &device,
203 );
204 let (next, f) = mu_comma_lambda::<TestBackend>(
205 offspring,
206 &[5.0, 1.0, 3.0],
207 2,
208 &device,
209 );
210 assert_eq!(next.shape().dims, vec![2, 2]);
211 let mut fs = f;
212 fs.sort_by(|a, b| a.partial_cmp(b).unwrap());
213 approx::assert_relative_eq!(fs[0], 1.0, epsilon = 1e-6);
214 approx::assert_relative_eq!(fs[1], 3.0, epsilon = 1e-6);
215 }
216}