1use burn::tensor::{Tensor, TensorData, backend::Backend};
28
29use rlevo_core::fitness::{FitnessEvaluable, Landscape};
30
31pub trait FitnessFn<G>: Send {
36 fn evaluate_one(&mut self, member: &G) -> f32;
38}
39
40pub trait BatchFitnessFn<B: Backend, G>: Send {
46 fn evaluate_batch(&mut self, population: &G, device: &<B as burn::tensor::backend::BackendTypes>::Device) -> Tensor<B, 1>;
52}
53
54#[derive(Debug)]
80pub struct FromFitnessEvaluable<FE, L> {
81 evaluator: FE,
82 landscape: L,
83}
84
85impl<FE, L> FromFitnessEvaluable<FE, L> {
86 pub fn new(evaluator: FE, landscape: L) -> Self {
88 Self {
89 evaluator,
90 landscape,
91 }
92 }
93
94 pub fn landscape(&self) -> &L {
96 &self.landscape
97 }
98}
99
100impl<FE, L, B> BatchFitnessFn<B, Tensor<B, 2>> for FromFitnessEvaluable<FE, L>
101where
102 B: Backend,
103 FE: FitnessEvaluable<Individual = Vec<f64>, Landscape = L> + Send,
104 L: Send + Sync,
105{
106 fn evaluate_batch(&mut self, population: &Tensor<B, 2>, device: &<B as burn::tensor::backend::BackendTypes>::Device) -> Tensor<B, 1> {
107 let dims = population.dims();
108 assert_eq!(dims.len(), 2, "population tensor must be rank 2");
109 let pop_size = dims[0];
110 let genome_dim = dims[1];
111
112 let flat = population
113 .clone()
114 .into_data()
115 .into_vec::<f32>()
116 .expect("tensor data must be readable as f32");
117 debug_assert_eq!(flat.len(), pop_size * genome_dim);
118
119 let mut fitness = Vec::with_capacity(pop_size);
120 let mut individual = Vec::with_capacity(genome_dim);
121 for row in 0..pop_size {
122 individual.clear();
123 let start = row * genome_dim;
124 individual.extend(
125 flat[start..start + genome_dim]
126 .iter()
127 .map(|&v| f64::from(v)),
128 );
129 let f = self.evaluator.evaluate(&individual, &self.landscape);
130 #[allow(clippy::cast_possible_truncation)]
131 fitness.push(f as f32);
132 }
133
134 let data = TensorData::new(fitness, [pop_size]);
135 Tensor::<B, 1>::from_data(data, device)
136 }
137}
138
139#[derive(Debug)]
152pub struct FromLandscape<L> {
153 landscape: L,
154}
155
156impl<L> FromLandscape<L> {
157 pub fn new(landscape: L) -> Self {
159 Self { landscape }
160 }
161
162 pub fn landscape(&self) -> &L {
164 &self.landscape
165 }
166}
167
168impl<L, B> BatchFitnessFn<B, Tensor<B, 2>> for FromLandscape<L>
169where
170 B: Backend,
171 L: Landscape,
172{
173 fn evaluate_batch(&mut self, population: &Tensor<B, 2>, device: &<B as burn::tensor::backend::BackendTypes>::Device) -> Tensor<B, 1> {
174 let dims = population.dims();
175 assert_eq!(dims.len(), 2, "population tensor must be rank 2");
176 let pop_size = dims[0];
177 let genome_dim = dims[1];
178
179 let flat = population
180 .clone()
181 .into_data()
182 .into_vec::<f32>()
183 .expect("tensor data must be readable as f32");
184 debug_assert_eq!(flat.len(), pop_size * genome_dim);
185
186 let mut fitness = Vec::with_capacity(pop_size);
187 let mut individual = Vec::with_capacity(genome_dim);
188 for row in 0..pop_size {
189 individual.clear();
190 let start = row * genome_dim;
191 individual.extend(
192 flat[start..start + genome_dim]
193 .iter()
194 .map(|&v| f64::from(v)),
195 );
196 let f = self.landscape.evaluate(&individual);
197 #[allow(clippy::cast_possible_truncation)]
198 fitness.push(f as f32);
199 }
200
201 let data = TensorData::new(fitness, [pop_size]);
202 Tensor::<B, 1>::from_data(data, device)
203 }
204}
205
206#[cfg(test)]
207mod tests {
208 use super::*;
209 use burn::backend::Flex;
210 type TestBackend = Flex;
211
212 #[derive(Debug, Clone, Copy)]
213 struct Sphere;
214
215 struct SphereFit;
216 impl FitnessEvaluable for SphereFit {
217 type Individual = Vec<f64>;
218 type Landscape = Sphere;
219 fn evaluate(&self, x: &Self::Individual, _: &Self::Landscape) -> f64 {
220 x.iter().map(|v| v * v).sum()
221 }
222 }
223
224 #[test]
225 fn from_fitness_evaluable_preserves_row_order() {
226 let device = Default::default();
227 let data = TensorData::new(
228 vec![1.0_f32, 0.0, 0.0, 0.0, 2.0, 0.0, 0.0, 0.0, 3.0],
229 [3, 3],
230 );
231 let pop = Tensor::<TestBackend, 2>::from_data(data, &device);
232
233 let mut adapter = FromFitnessEvaluable::new(SphereFit, Sphere);
234 let fitness = adapter.evaluate_batch(&pop, &device);
235
236 let values = fitness.into_data().into_vec::<f32>().unwrap();
237 assert_eq!(values.len(), 3);
238 approx::assert_relative_eq!(values[0], 1.0, epsilon = 1e-6);
239 approx::assert_relative_eq!(values[1], 4.0, epsilon = 1e-6);
240 approx::assert_relative_eq!(values[2], 9.0, epsilon = 1e-6);
241 }
242
243 #[test]
244 fn from_landscape_preserves_row_order() {
245 struct SphereLandscape;
246 impl Landscape for SphereLandscape {
247 fn evaluate(&self, x: &[f64]) -> f64 {
248 x.iter().map(|v| v * v).sum()
249 }
250 }
251
252 let device = Default::default();
253 let data = TensorData::new(
254 vec![1.0_f32, 0.0, 0.0, 0.0, 2.0, 0.0, 0.0, 0.0, 3.0],
255 [3, 3],
256 );
257 let pop = Tensor::<TestBackend, 2>::from_data(data, &device);
258
259 let mut adapter = FromLandscape::new(SphereLandscape);
260 let fitness = adapter.evaluate_batch(&pop, &device);
261
262 let values = fitness.into_data().into_vec::<f32>().unwrap();
263 assert_eq!(values.len(), 3);
264 approx::assert_relative_eq!(values[0], 1.0, epsilon = 1e-6);
265 approx::assert_relative_eq!(values[1], 4.0, epsilon = 1e-6);
266 approx::assert_relative_eq!(values[2], 9.0, epsilon = 1e-6);
267 }
268}