Skip to main content

fugue_evo/population/
individual.rs

1//! Individual wrapper type
2//!
3//! This module provides the Individual type that wraps a genome with its fitness.
4
5use std::cmp::Ordering;
6
7use serde::{Deserialize, Serialize};
8
9use crate::fitness::traits::FitnessValue;
10use crate::genome::traits::EvolutionaryGenome;
11
12/// An individual in the population
13///
14/// Wraps a genome with its computed fitness value and additional metadata.
15#[derive(Clone, Debug, Serialize, Deserialize)]
16#[serde(bound = "")]
17pub struct Individual<G, F = f64>
18where
19    G: EvolutionaryGenome,
20    F: FitnessValue,
21{
22    /// The genome of this individual
23    pub genome: G,
24    /// The fitness value (None if not yet evaluated)
25    pub fitness: Option<F>,
26    /// Generation when this individual was created
27    pub birth_generation: usize,
28    /// Number of offspring produced by this individual
29    pub offspring_count: usize,
30}
31
32impl<G, F> Individual<G, F>
33where
34    G: EvolutionaryGenome,
35    F: FitnessValue,
36{
37    /// Create a new individual with an unevaluated genome
38    pub fn new(genome: G) -> Self {
39        Self {
40            genome,
41            fitness: None,
42            birth_generation: 0,
43            offspring_count: 0,
44        }
45    }
46
47    /// Create a new individual with a known fitness
48    pub fn with_fitness(genome: G, fitness: F) -> Self {
49        Self {
50            genome,
51            fitness: Some(fitness),
52            birth_generation: 0,
53            offspring_count: 0,
54        }
55    }
56
57    /// Create a new individual with birth generation
58    pub fn with_generation(genome: G, generation: usize) -> Self {
59        Self {
60            genome,
61            fitness: None,
62            birth_generation: generation,
63            offspring_count: 0,
64        }
65    }
66
67    /// Check if this individual has been evaluated
68    pub fn is_evaluated(&self) -> bool {
69        self.fitness.is_some()
70    }
71
72    /// Get the fitness value, panicking if not evaluated
73    pub fn fitness_value(&self) -> &F {
74        self.fitness
75            .as_ref()
76            .expect("Individual has not been evaluated")
77    }
78
79    /// Get the fitness as f64
80    pub fn fitness_f64(&self) -> f64 {
81        self.fitness_value().to_f64()
82    }
83
84    /// Set the fitness value
85    pub fn set_fitness(&mut self, fitness: F) {
86        self.fitness = Some(fitness);
87    }
88
89    /// Take the genome out of this individual
90    pub fn into_genome(self) -> G {
91        self.genome
92    }
93
94    /// Get a reference to the genome
95    pub fn genome(&self) -> &G {
96        &self.genome
97    }
98
99    /// Get a mutable reference to the genome
100    pub fn genome_mut(&mut self) -> &mut G {
101        &mut self.genome
102    }
103
104    /// Check if this individual is better than another
105    pub fn is_better_than(&self, other: &Self) -> bool {
106        match (&self.fitness, &other.fitness) {
107            (Some(f1), Some(f2)) => f1.is_better_than(f2),
108            (Some(_), None) => true,
109            (None, Some(_)) => false,
110            (None, None) => false,
111        }
112    }
113
114    /// Age of this individual (generations since birth)
115    pub fn age(&self, current_generation: usize) -> usize {
116        current_generation.saturating_sub(self.birth_generation)
117    }
118}
119
120impl<G, F> PartialEq for Individual<G, F>
121where
122    G: EvolutionaryGenome + PartialEq,
123    F: FitnessValue + PartialEq,
124{
125    fn eq(&self, other: &Self) -> bool {
126        self.genome == other.genome && self.fitness == other.fitness
127    }
128}
129
130impl<G, F> PartialOrd for Individual<G, F>
131where
132    G: EvolutionaryGenome + PartialEq,
133    F: FitnessValue + PartialEq,
134{
135    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
136        match (&self.fitness, &other.fitness) {
137            (Some(f1), Some(f2)) => f1.partial_cmp(f2),
138            (Some(_), None) => Some(Ordering::Greater),
139            (None, Some(_)) => Some(Ordering::Less),
140            (None, None) => Some(Ordering::Equal),
141        }
142    }
143}
144
145/// A pair of individuals (for crossover results)
146pub type IndividualPair<G, F = f64> = (Individual<G, F>, Individual<G, F>);
147
148#[cfg(test)]
149mod tests {
150    use super::*;
151    use crate::genome::real_vector::RealVector;
152    use crate::genome::traits::RealValuedGenome;
153
154    #[test]
155    fn test_individual_new() {
156        let genome = RealVector::new(vec![1.0, 2.0, 3.0]);
157        let individual: Individual<RealVector> = Individual::new(genome);
158
159        assert!(!individual.is_evaluated());
160        assert_eq!(individual.birth_generation, 0);
161        assert_eq!(individual.offspring_count, 0);
162    }
163
164    #[test]
165    fn test_individual_with_fitness() {
166        let genome = RealVector::new(vec![1.0, 2.0, 3.0]);
167        let individual = Individual::with_fitness(genome, 42.0);
168
169        assert!(individual.is_evaluated());
170        assert_eq!(individual.fitness_f64(), 42.0);
171    }
172
173    #[test]
174    fn test_individual_set_fitness() {
175        let genome = RealVector::new(vec![1.0, 2.0, 3.0]);
176        let mut individual: Individual<RealVector> = Individual::new(genome);
177
178        assert!(!individual.is_evaluated());
179        individual.set_fitness(100.0);
180        assert!(individual.is_evaluated());
181        assert_eq!(individual.fitness_f64(), 100.0);
182    }
183
184    #[test]
185    fn test_individual_is_better_than() {
186        let g1 = RealVector::new(vec![1.0]);
187        let g2 = RealVector::new(vec![2.0]);
188
189        let ind1 = Individual::with_fitness(g1, 100.0);
190        let ind2 = Individual::with_fitness(g2, 50.0);
191
192        assert!(ind1.is_better_than(&ind2));
193        assert!(!ind2.is_better_than(&ind1));
194    }
195
196    #[test]
197    fn test_individual_is_better_than_unevaluated() {
198        let g1 = RealVector::new(vec![1.0]);
199        let g2 = RealVector::new(vec![2.0]);
200
201        let ind1 = Individual::with_fitness(g1, 100.0);
202        let ind2: Individual<RealVector> = Individual::new(g2);
203
204        assert!(ind1.is_better_than(&ind2));
205        assert!(!ind2.is_better_than(&ind1));
206    }
207
208    #[test]
209    fn test_individual_age() {
210        let genome = RealVector::new(vec![1.0]);
211        let individual: Individual<RealVector> = Individual::with_generation(genome, 10);
212
213        assert_eq!(individual.age(10), 0);
214        assert_eq!(individual.age(15), 5);
215        assert_eq!(individual.age(5), 0); // saturating sub
216    }
217
218    #[test]
219    fn test_individual_partial_ord() {
220        let g1 = RealVector::new(vec![1.0]);
221        let g2 = RealVector::new(vec![2.0]);
222
223        let ind1 = Individual::with_fitness(g1, 100.0);
224        let ind2 = Individual::with_fitness(g2, 50.0);
225
226        assert!(ind1 > ind2);
227        assert!(ind2 < ind1);
228    }
229
230    #[test]
231    fn test_individual_into_genome() {
232        let genome = RealVector::new(vec![1.0, 2.0, 3.0]);
233        let individual = Individual::with_fitness(genome.clone(), 42.0);
234
235        let recovered = individual.into_genome();
236        assert_eq!(recovered, genome);
237    }
238
239    #[test]
240    fn test_individual_genome_mut() {
241        let genome = RealVector::new(vec![1.0, 2.0, 3.0]);
242        let mut individual: Individual<RealVector> = Individual::new(genome);
243
244        individual.genome_mut().genes_mut()[0] = 100.0;
245        assert_eq!(individual.genome()[0], 100.0);
246    }
247}