fugue_evo/fitness/
traits.rs1use std::fmt::Debug;
6
7use serde::{de::DeserializeOwned, Serialize};
8
9use crate::genome::traits::EvolutionaryGenome;
10
11pub trait FitnessValue:
17 PartialOrd + Clone + Send + Sync + Debug + Serialize + DeserializeOwned + 'static
18{
19 fn to_f64(&self) -> f64;
21
22 fn is_better_than(&self, other: &Self) -> bool;
24
25 fn is_worse_than(&self, other: &Self) -> bool {
27 other.is_better_than(self)
28 }
29
30 fn cmp_by_quality(&self, other: &Self) -> std::cmp::Ordering {
45 use std::cmp::Ordering;
46 let self_nan = self.to_f64().is_nan();
47 let other_nan = other.to_f64().is_nan();
48 match (self_nan, other_nan) {
49 (true, true) => Ordering::Equal,
50 (true, false) => Ordering::Less,
52 (false, true) => Ordering::Greater,
53 (false, false) => {
54 if self.is_better_than(other) {
55 Ordering::Greater
56 } else if other.is_better_than(self) {
57 Ordering::Less
58 } else {
59 Ordering::Equal
60 }
61 }
62 }
63 }
64}
65
66impl FitnessValue for f64 {
67 fn to_f64(&self) -> f64 {
68 *self
69 }
70
71 fn is_better_than(&self, other: &Self) -> bool {
72 self > other
73 }
74}
75
76impl FitnessValue for f32 {
77 fn to_f64(&self) -> f64 {
78 *self as f64
79 }
80
81 fn is_better_than(&self, other: &Self) -> bool {
82 self > other
83 }
84}
85
86impl FitnessValue for i64 {
87 fn to_f64(&self) -> f64 {
88 *self as f64
89 }
90
91 fn is_better_than(&self, other: &Self) -> bool {
92 self > other
93 }
94}
95
96impl FitnessValue for i32 {
97 fn to_f64(&self) -> f64 {
98 *self as f64
99 }
100
101 fn is_better_than(&self, other: &Self) -> bool {
102 self > other
103 }
104}
105
106impl FitnessValue for usize {
107 fn to_f64(&self) -> f64 {
108 *self as f64
109 }
110
111 fn is_better_than(&self, other: &Self) -> bool {
112 self > other
113 }
114}
115
116#[derive(Clone, Debug, PartialEq, Serialize, serde::Deserialize)]
118pub struct ParetoFitness {
119 pub objectives: Vec<f64>,
121 pub rank: usize,
123 pub crowding_distance: f64,
125}
126
127impl ParetoFitness {
128 pub fn new(objectives: Vec<f64>) -> Self {
130 Self {
131 objectives,
132 rank: usize::MAX,
133 crowding_distance: 0.0,
134 }
135 }
136
137 pub fn dominates(&self, other: &Self) -> bool {
140 let dominated = self
141 .objectives
142 .iter()
143 .zip(other.objectives.iter())
144 .all(|(a, b)| a >= b);
145 let strictly_better = self
146 .objectives
147 .iter()
148 .zip(other.objectives.iter())
149 .any(|(a, b)| a > b);
150 dominated && strictly_better
151 }
152
153 pub fn num_objectives(&self) -> usize {
155 self.objectives.len()
156 }
157}
158
159impl PartialOrd for ParetoFitness {
160 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
161 match self.rank.partial_cmp(&other.rank) {
163 Some(std::cmp::Ordering::Equal) => {
164 self.crowding_distance.partial_cmp(&other.crowding_distance)
166 }
167 ord => ord.map(|o| o.reverse()), }
169 }
170}
171
172impl FitnessValue for ParetoFitness {
173 fn to_f64(&self) -> f64 {
174 -(self.rank as f64) + self.crowding_distance * 0.001
177 }
178
179 fn is_better_than(&self, other: &Self) -> bool {
180 self.rank < other.rank
181 || (self.rank == other.rank && self.crowding_distance > other.crowding_distance)
182 }
183}
184
185#[cfg(feature = "parallel")]
205pub trait Fitness: Send + Sync {
206 type Genome: EvolutionaryGenome;
208
209 type Value: FitnessValue;
211
212 fn evaluate(&self, genome: &Self::Genome) -> Self::Value;
214
215 fn as_log_likelihood(&self, genome: &Self::Genome, temperature: f64) -> f64 {
219 let fitness = self.evaluate(genome).to_f64();
220 fitness / temperature
221 }
222
223 fn gradient(&self, _genome: &Self::Genome) -> Option<Vec<f64>> {
225 None
226 }
227}
228
229#[cfg(not(feature = "parallel"))]
237pub trait Fitness {
238 type Genome: EvolutionaryGenome;
240
241 type Value: FitnessValue;
243
244 fn evaluate(&self, genome: &Self::Genome) -> Self::Value;
246
247 fn as_log_likelihood(&self, genome: &Self::Genome, temperature: f64) -> f64 {
251 let fitness = self.evaluate(genome).to_f64();
252 fitness / temperature
253 }
254
255 fn gradient(&self, _genome: &Self::Genome) -> Option<Vec<f64>> {
257 None
258 }
259}
260
261pub struct MinimizeFitness<F> {
263 inner: F,
264}
265
266impl<F> MinimizeFitness<F> {
267 pub fn new(fitness: F) -> Self {
269 Self { inner: fitness }
270 }
271}
272
273impl<F: Fitness<Value = f64>> Fitness for MinimizeFitness<F> {
274 type Genome = F::Genome;
275 type Value = f64;
276
277 fn evaluate(&self, genome: &Self::Genome) -> f64 {
278 -self.inner.evaluate(genome)
279 }
280}
281
282pub struct FnFitness<G, F, V>
284where
285 F: Fn(&G) -> V,
286{
287 f: F,
288 _marker: std::marker::PhantomData<(G, V)>,
289}
290
291impl<G, F, V> FnFitness<G, F, V>
292where
293 F: Fn(&G) -> V,
294{
295 pub fn new(f: F) -> Self {
297 Self {
298 f,
299 _marker: std::marker::PhantomData,
300 }
301 }
302}
303
304impl<G, F, V> Fitness for FnFitness<G, F, V>
305where
306 G: EvolutionaryGenome,
307 F: Fn(&G) -> V + Send + Sync,
308 V: FitnessValue,
309{
310 type Genome = G;
311 type Value = V;
312
313 fn evaluate(&self, genome: &Self::Genome) -> Self::Value {
314 (self.f)(genome)
315 }
316}
317
318#[cfg(test)]
319mod tests {
320 use super::*;
321 use crate::genome::real_vector::RealVector;
322 use crate::genome::traits::RealValuedGenome;
323
324 #[test]
325 fn test_f64_fitness_value() {
326 let a: f64 = 10.0;
327 let b: f64 = 5.0;
328
329 assert!(a.is_better_than(&b));
330 assert!(!b.is_better_than(&a));
331 assert!(b.is_worse_than(&a));
332 assert_eq!(a.to_f64(), 10.0);
333 }
334
335 #[test]
336 fn test_i32_fitness_value() {
337 let a: i32 = 10;
338 let b: i32 = 5;
339
340 assert!(a.is_better_than(&b));
341 assert!(!b.is_better_than(&a));
342 assert_eq!(a.to_f64(), 10.0);
343 }
344
345 #[test]
346 fn test_usize_fitness_value() {
347 let a: usize = 10;
348 let b: usize = 5;
349
350 assert!(a.is_better_than(&b));
351 assert!(!b.is_better_than(&a));
352 assert_eq!(a.to_f64(), 10.0);
353 }
354
355 #[test]
356 fn test_pareto_fitness_dominates() {
357 let a = ParetoFitness::new(vec![5.0, 5.0]);
358 let b = ParetoFitness::new(vec![3.0, 3.0]);
359 let c = ParetoFitness::new(vec![6.0, 3.0]); assert!(a.dominates(&b)); assert!(!b.dominates(&a)); assert!(!a.dominates(&c)); assert!(!c.dominates(&a)); }
366
367 #[test]
368 fn test_pareto_fitness_is_better_than() {
369 let mut a = ParetoFitness::new(vec![5.0, 5.0]);
370 a.rank = 0;
371 a.crowding_distance = 1.0;
372
373 let mut b = ParetoFitness::new(vec![3.0, 3.0]);
374 b.rank = 1;
375 b.crowding_distance = 2.0;
376
377 assert!(a.is_better_than(&b)); let mut c = ParetoFitness::new(vec![4.0, 4.0]);
380 c.rank = 0;
381 c.crowding_distance = 0.5;
382
383 assert!(a.is_better_than(&c)); }
385
386 #[test]
387 fn test_fn_fitness() {
388 let fitness = FnFitness::new(|g: &RealVector| -> f64 {
389 -g.genes().iter().map(|x| x * x).sum::<f64>()
390 });
391
392 let genome = RealVector::new(vec![1.0, 2.0, 3.0]);
393 let value = fitness.evaluate(&genome);
394 assert_eq!(value, -14.0);
395 }
396
397 #[test]
398 fn test_minimize_fitness() {
399 let fitness = FnFitness::new(|g: &RealVector| -> f64 {
400 g.genes().iter().map(|x| x * x).sum::<f64>()
401 });
402 let minimize = MinimizeFitness::new(fitness);
403
404 let genome = RealVector::new(vec![1.0, 2.0, 3.0]);
405 let value = minimize.evaluate(&genome);
406 assert_eq!(value, -14.0);
407 }
408
409 #[test]
410 fn test_as_log_likelihood() {
411 let fitness = FnFitness::new(|g: &RealVector| -> f64 {
412 -g.genes().iter().map(|x| x * x).sum::<f64>()
413 });
414
415 let genome = RealVector::new(vec![1.0, 2.0, 3.0]);
416 let log_likelihood = fitness.as_log_likelihood(&genome, 1.0);
417 assert_eq!(log_likelihood, -14.0);
418
419 let log_likelihood_scaled = fitness.as_log_likelihood(&genome, 2.0);
420 assert_eq!(log_likelihood_scaled, -7.0);
421 }
422}