Skip to main content

fugue_evo/population/
population.rs

1//! Population type
2//!
3//! This module provides the Population container type.
4
5use rand::Rng;
6
7#[cfg(feature = "parallel")]
8use rayon::prelude::*;
9
10use crate::fitness::traits::{Fitness, FitnessValue};
11use crate::genome::bounds::MultiBounds;
12use crate::genome::traits::EvolutionaryGenome;
13use crate::population::individual::Individual;
14
15/// A population of individuals
16#[derive(Clone, Debug)]
17pub struct Population<G, F = f64>
18where
19    G: EvolutionaryGenome,
20    F: FitnessValue,
21{
22    /// The individuals in this population
23    individuals: Vec<Individual<G, F>>,
24    /// Current generation number
25    generation: usize,
26}
27
28impl<G, F> Population<G, F>
29where
30    G: EvolutionaryGenome,
31    F: FitnessValue,
32{
33    /// Create an empty population
34    pub fn new() -> Self {
35        Self {
36            individuals: Vec::new(),
37            generation: 0,
38        }
39    }
40
41    /// Create a population with the given capacity
42    pub fn with_capacity(capacity: usize) -> Self {
43        Self {
44            individuals: Vec::with_capacity(capacity),
45            generation: 0,
46        }
47    }
48
49    /// Create a population from a vector of individuals
50    pub fn from_individuals(individuals: Vec<Individual<G, F>>) -> Self {
51        Self {
52            individuals,
53            generation: 0,
54        }
55    }
56
57    /// Create a random population
58    pub fn random<R: Rng>(size: usize, bounds: &MultiBounds, rng: &mut R) -> Self {
59        let individuals = (0..size)
60            .map(|_| Individual::new(G::generate(rng, bounds)))
61            .collect();
62        Self {
63            individuals,
64            generation: 0,
65        }
66    }
67
68    /// Get the current generation
69    pub fn generation(&self) -> usize {
70        self.generation
71    }
72
73    /// Increment the generation counter
74    pub fn increment_generation(&mut self) {
75        self.generation += 1;
76    }
77
78    /// Set the generation number
79    pub fn set_generation(&mut self, generation: usize) {
80        self.generation = generation;
81    }
82
83    /// Get the population size
84    pub fn len(&self) -> usize {
85        self.individuals.len()
86    }
87
88    /// Check if the population is empty
89    pub fn is_empty(&self) -> bool {
90        self.individuals.is_empty()
91    }
92
93    /// Get an individual by index
94    pub fn get(&self, index: usize) -> Option<&Individual<G, F>> {
95        self.individuals.get(index)
96    }
97
98    /// Get a mutable reference to an individual by index
99    pub fn get_mut(&mut self, index: usize) -> Option<&mut Individual<G, F>> {
100        self.individuals.get_mut(index)
101    }
102
103    /// Add an individual to the population
104    pub fn push(&mut self, individual: Individual<G, F>) {
105        self.individuals.push(individual);
106    }
107
108    /// Remove and return the last individual
109    pub fn pop(&mut self) -> Option<Individual<G, F>> {
110        self.individuals.pop()
111    }
112
113    /// Clear the population
114    pub fn clear(&mut self) {
115        self.individuals.clear();
116    }
117
118    /// Get an iterator over the individuals
119    pub fn iter(&self) -> impl Iterator<Item = &Individual<G, F>> {
120        self.individuals.iter()
121    }
122
123    /// Get a mutable iterator over the individuals
124    pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut Individual<G, F>> {
125        self.individuals.iter_mut()
126    }
127
128    /// Get the underlying vector of individuals
129    pub fn individuals(&self) -> &[Individual<G, F>] {
130        &self.individuals
131    }
132
133    /// Get mutable access to the underlying vector
134    pub fn individuals_mut(&mut self) -> &mut Vec<Individual<G, F>> {
135        &mut self.individuals
136    }
137
138    /// Take the individuals out of this population
139    pub fn into_individuals(self) -> Vec<Individual<G, F>> {
140        self.individuals
141    }
142
143    /// Get the best individual (by fitness)
144    pub fn best(&self) -> Option<&Individual<G, F>> {
145        self.individuals
146            .iter()
147            .filter(|i| i.is_evaluated())
148            .max_by(|a, b| {
149                let fa = a
150                    .fitness
151                    .as_ref()
152                    .map(|f| f.to_f64())
153                    .unwrap_or(f64::NEG_INFINITY);
154                let fb = b
155                    .fitness
156                    .as_ref()
157                    .map(|f| f.to_f64())
158                    .unwrap_or(f64::NEG_INFINITY);
159                fa.partial_cmp(&fb).unwrap_or(std::cmp::Ordering::Equal)
160            })
161    }
162
163    /// Get the worst individual (by fitness)
164    pub fn worst(&self) -> Option<&Individual<G, F>> {
165        self.individuals
166            .iter()
167            .filter(|i| i.is_evaluated())
168            .min_by(|a, b| {
169                let fa = a
170                    .fitness
171                    .as_ref()
172                    .map(|f| f.to_f64())
173                    .unwrap_or(f64::INFINITY);
174                let fb = b
175                    .fitness
176                    .as_ref()
177                    .map(|f| f.to_f64())
178                    .unwrap_or(f64::INFINITY);
179                fa.partial_cmp(&fb).unwrap_or(std::cmp::Ordering::Equal)
180            })
181    }
182
183    /// Sort the population by fitness (best first)
184    pub fn sort_by_fitness(&mut self) {
185        self.individuals.sort_by(|a, b| {
186            let fa = a
187                .fitness
188                .as_ref()
189                .map(|f| f.to_f64())
190                .unwrap_or(f64::NEG_INFINITY);
191            let fb = b
192                .fitness
193                .as_ref()
194                .map(|f| f.to_f64())
195                .unwrap_or(f64::NEG_INFINITY);
196            fb.partial_cmp(&fa).unwrap_or(std::cmp::Ordering::Equal)
197        });
198    }
199
200    /// Truncate the population to the given size, keeping the best individuals
201    pub fn truncate_to_best(&mut self, size: usize) {
202        self.sort_by_fitness();
203        self.individuals.truncate(size);
204    }
205
206    /// Check if all individuals have been evaluated
207    pub fn all_evaluated(&self) -> bool {
208        self.individuals.iter().all(|i| i.is_evaluated())
209    }
210
211    /// Count the number of evaluated individuals
212    pub fn count_evaluated(&self) -> usize {
213        self.individuals.iter().filter(|i| i.is_evaluated()).count()
214    }
215
216    /// Get genome-fitness pairs for selection
217    pub fn as_selection_pool(&self) -> Vec<(&G, f64)> {
218        self.individuals
219            .iter()
220            .filter_map(|i| i.fitness.as_ref().map(|f| (&i.genome, f.to_f64())))
221            .collect()
222    }
223
224    /// Get genome-fitness pairs as owned tuples
225    pub fn as_fitness_pairs(&self) -> Vec<(G, f64)>
226    where
227        G: Clone,
228    {
229        self.individuals
230            .iter()
231            .filter_map(|i| i.fitness.as_ref().map(|f| (i.genome.clone(), f.to_f64())))
232            .collect()
233    }
234
235    /// Evaluate all individuals using the given fitness function (sequential)
236    pub fn evaluate<Fit>(&mut self, fitness: &Fit)
237    where
238        Fit: Fitness<Genome = G, Value = F>,
239    {
240        for individual in &mut self.individuals {
241            if !individual.is_evaluated() {
242                let f = fitness.evaluate(&individual.genome);
243                individual.set_fitness(f);
244            }
245        }
246    }
247
248    /// Compute mean fitness
249    pub fn mean_fitness(&self) -> Option<f64> {
250        let evaluated: Vec<f64> = self
251            .individuals
252            .iter()
253            .filter_map(|i| i.fitness.as_ref().map(|f| f.to_f64()))
254            .collect();
255
256        if evaluated.is_empty() {
257            None
258        } else {
259            Some(evaluated.iter().sum::<f64>() / evaluated.len() as f64)
260        }
261    }
262
263    /// Compute fitness standard deviation
264    pub fn fitness_std(&self) -> Option<f64> {
265        let mean = self.mean_fitness()?;
266        let evaluated: Vec<f64> = self
267            .individuals
268            .iter()
269            .filter_map(|i| i.fitness.as_ref().map(|f| f.to_f64()))
270            .collect();
271
272        if evaluated.len() < 2 {
273            return None;
274        }
275
276        let variance = evaluated.iter().map(|f| (f - mean).powi(2)).sum::<f64>()
277            / (evaluated.len() - 1) as f64;
278        Some(variance.sqrt())
279    }
280
281    /// Compute population diversity (average pairwise distance)
282    pub fn diversity(&self) -> f64 {
283        if self.len() < 2 {
284            return 0.0;
285        }
286
287        let mut total_distance = 0.0;
288        let mut count = 0;
289
290        for i in 0..self.len() {
291            for j in (i + 1)..self.len() {
292                total_distance += self.individuals[i]
293                    .genome
294                    .distance(&self.individuals[j].genome);
295                count += 1;
296            }
297        }
298
299        if count == 0 {
300            0.0
301        } else {
302            total_distance / count as f64
303        }
304    }
305}
306
307/// Parallel evaluation support (requires `parallel` feature)
308#[cfg(feature = "parallel")]
309impl<G, F> Population<G, F>
310where
311    G: EvolutionaryGenome + Send + Sync,
312    F: FitnessValue + Send,
313{
314    /// Evaluate all individuals using the given fitness function (parallel)
315    pub fn evaluate_parallel<Fit>(&mut self, fitness: &Fit)
316    where
317        Fit: Fitness<Genome = G, Value = F> + Sync,
318    {
319        self.individuals
320            .par_iter_mut()
321            .filter(|i| !i.is_evaluated())
322            .for_each(|individual| {
323                let f = fitness.evaluate(&individual.genome);
324                individual.set_fitness(f);
325            });
326    }
327}
328
329/// Sequential fallback for parallel evaluation (when `parallel` feature is disabled)
330#[cfg(not(feature = "parallel"))]
331impl<G, F> Population<G, F>
332where
333    G: EvolutionaryGenome,
334    F: FitnessValue,
335{
336    /// Evaluate all individuals using the given fitness function (sequential fallback)
337    ///
338    /// Note: This is a sequential implementation used when the `parallel` feature is disabled.
339    pub fn evaluate_parallel<Fit>(&mut self, fitness: &Fit)
340    where
341        Fit: Fitness<Genome = G, Value = F>,
342    {
343        self.evaluate(fitness);
344    }
345}
346
347impl<G, F> Default for Population<G, F>
348where
349    G: EvolutionaryGenome,
350    F: FitnessValue,
351{
352    fn default() -> Self {
353        Self::new()
354    }
355}
356
357impl<G, F> std::ops::Index<usize> for Population<G, F>
358where
359    G: EvolutionaryGenome,
360    F: FitnessValue,
361{
362    type Output = Individual<G, F>;
363
364    fn index(&self, index: usize) -> &Self::Output {
365        &self.individuals[index]
366    }
367}
368
369impl<G, F> std::ops::IndexMut<usize> for Population<G, F>
370where
371    G: EvolutionaryGenome,
372    F: FitnessValue,
373{
374    fn index_mut(&mut self, index: usize) -> &mut Self::Output {
375        &mut self.individuals[index]
376    }
377}
378
379impl<G, F> IntoIterator for Population<G, F>
380where
381    G: EvolutionaryGenome,
382    F: FitnessValue,
383{
384    type Item = Individual<G, F>;
385    type IntoIter = std::vec::IntoIter<Individual<G, F>>;
386
387    fn into_iter(self) -> Self::IntoIter {
388        self.individuals.into_iter()
389    }
390}
391
392impl<G, F> FromIterator<Individual<G, F>> for Population<G, F>
393where
394    G: EvolutionaryGenome,
395    F: FitnessValue,
396{
397    fn from_iter<I: IntoIterator<Item = Individual<G, F>>>(iter: I) -> Self {
398        Self::from_individuals(iter.into_iter().collect())
399    }
400}
401
402#[cfg(test)]
403mod tests {
404    use super::*;
405    use crate::fitness::benchmarks::Sphere;
406    use crate::genome::real_vector::RealVector;
407
408    fn create_test_population() -> Population<RealVector> {
409        let individuals = vec![
410            Individual::with_fitness(RealVector::new(vec![1.0]), 10.0),
411            Individual::with_fitness(RealVector::new(vec![2.0]), 20.0),
412            Individual::with_fitness(RealVector::new(vec![3.0]), 30.0),
413            Individual::with_fitness(RealVector::new(vec![4.0]), 40.0),
414            Individual::with_fitness(RealVector::new(vec![5.0]), 50.0),
415        ];
416        Population::from_individuals(individuals)
417    }
418
419    #[test]
420    fn test_population_new() {
421        let pop: Population<RealVector> = Population::new();
422        assert!(pop.is_empty());
423        assert_eq!(pop.generation(), 0);
424    }
425
426    #[test]
427    fn test_population_random() {
428        let mut rng = rand::thread_rng();
429        let bounds = MultiBounds::symmetric(5.0, 3);
430        let pop: Population<RealVector> = Population::random(10, &bounds, &mut rng);
431
432        assert_eq!(pop.len(), 10);
433        assert!(!pop.all_evaluated());
434    }
435
436    #[test]
437    fn test_population_best_worst() {
438        let pop = create_test_population();
439
440        let best = pop.best().unwrap();
441        assert_eq!(best.fitness_f64(), 50.0);
442
443        let worst = pop.worst().unwrap();
444        assert_eq!(worst.fitness_f64(), 10.0);
445    }
446
447    #[test]
448    fn test_population_sort_by_fitness() {
449        let mut pop = create_test_population();
450        pop.sort_by_fitness();
451
452        let fitnesses: Vec<f64> = pop.iter().map(|i| i.fitness_f64()).collect();
453        assert_eq!(fitnesses, vec![50.0, 40.0, 30.0, 20.0, 10.0]);
454    }
455
456    #[test]
457    fn test_population_truncate_to_best() {
458        let mut pop = create_test_population();
459        pop.truncate_to_best(3);
460
461        assert_eq!(pop.len(), 3);
462        let fitnesses: Vec<f64> = pop.iter().map(|i| i.fitness_f64()).collect();
463        assert_eq!(fitnesses, vec![50.0, 40.0, 30.0]);
464    }
465
466    #[test]
467    fn test_population_mean_fitness() {
468        let pop = create_test_population();
469        let mean = pop.mean_fitness().unwrap();
470        assert_eq!(mean, 30.0); // (10 + 20 + 30 + 40 + 50) / 5
471    }
472
473    #[test]
474    fn test_population_fitness_std() {
475        let pop = create_test_population();
476        let std = pop.fitness_std().unwrap();
477        // Variance = ((10-30)^2 + (20-30)^2 + (30-30)^2 + (40-30)^2 + (50-30)^2) / 4
478        // = (400 + 100 + 0 + 100 + 400) / 4 = 250
479        // Std = sqrt(250) ≈ 15.81
480        assert!((std - 15.81).abs() < 0.1);
481    }
482
483    #[test]
484    fn test_population_evaluate() {
485        let mut rng = rand::thread_rng();
486        let bounds = MultiBounds::symmetric(5.0, 3);
487        let mut pop: Population<RealVector> = Population::random(5, &bounds, &mut rng);
488
489        let fitness = Sphere::new(3);
490        pop.evaluate(&fitness);
491
492        assert!(pop.all_evaluated());
493        assert_eq!(pop.count_evaluated(), 5);
494    }
495
496    #[test]
497    fn test_population_evaluate_parallel() {
498        let mut rng = rand::thread_rng();
499        let bounds = MultiBounds::symmetric(5.0, 3);
500        let mut pop: Population<RealVector> = Population::random(100, &bounds, &mut rng);
501
502        let fitness = Sphere::new(3);
503        pop.evaluate_parallel(&fitness);
504
505        assert!(pop.all_evaluated());
506        assert_eq!(pop.count_evaluated(), 100);
507    }
508
509    #[test]
510    fn test_population_generation() {
511        let mut pop = create_test_population();
512        assert_eq!(pop.generation(), 0);
513
514        pop.increment_generation();
515        assert_eq!(pop.generation(), 1);
516
517        pop.set_generation(100);
518        assert_eq!(pop.generation(), 100);
519    }
520
521    #[test]
522    fn test_population_push_pop() {
523        let mut pop: Population<RealVector> = Population::new();
524
525        pop.push(Individual::with_fitness(RealVector::new(vec![1.0]), 10.0));
526        assert_eq!(pop.len(), 1);
527
528        let ind = pop.pop().unwrap();
529        assert_eq!(ind.fitness_f64(), 10.0);
530        assert!(pop.is_empty());
531    }
532
533    #[test]
534    fn test_population_indexing() {
535        let pop = create_test_population();
536        assert_eq!(pop[0].fitness_f64(), 10.0);
537        assert_eq!(pop[4].fitness_f64(), 50.0);
538    }
539
540    #[test]
541    fn test_population_as_selection_pool() {
542        let pop = create_test_population();
543        let pool = pop.as_selection_pool();
544
545        assert_eq!(pool.len(), 5);
546        assert_eq!(pool[0].1, 10.0);
547        assert_eq!(pool[4].1, 50.0);
548    }
549
550    #[test]
551    fn test_population_diversity() {
552        let individuals = vec![
553            Individual::with_fitness(RealVector::new(vec![0.0, 0.0]), 1.0),
554            Individual::with_fitness(RealVector::new(vec![1.0, 0.0]), 1.0),
555            Individual::with_fitness(RealVector::new(vec![0.0, 1.0]), 1.0),
556        ];
557        let pop = Population::from_individuals(individuals);
558
559        let diversity = pop.diversity();
560        // Average of distances: (1, 1, sqrt(2)) / 3 ≈ 1.14
561        assert!(diversity > 1.0 && diversity < 1.2);
562    }
563
564    #[test]
565    fn test_population_from_iterator() {
566        let individuals = vec![
567            Individual::with_fitness(RealVector::new(vec![1.0]), 10.0),
568            Individual::with_fitness(RealVector::new(vec![2.0]), 20.0),
569        ];
570        let pop: Population<RealVector> = individuals.into_iter().collect();
571
572        assert_eq!(pop.len(), 2);
573    }
574
575    #[test]
576    fn test_population_into_iterator() {
577        let pop = create_test_population();
578        let individuals: Vec<_> = pop.into_iter().collect();
579
580        assert_eq!(individuals.len(), 5);
581    }
582}