wafrift_evolution/evolution/crossover/
selection.rs1use crate::evolution::Chromosome;
2use rand::Rng;
3use wafrift_types::pick::pick_ref_from_rng;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub enum SelectionStrategy {
8 Standard,
9 Adaptive,
10 Roulette,
11}
12
13#[must_use]
15pub fn tournament_select<'a>(population: &'a [Chromosome], rng: &mut impl Rng) -> &'a Chromosome {
16 let tournament_size = 3_usize.min(population.len());
17 tournament_select_with_size(population, tournament_size, rng)
18}
19
20#[must_use]
28pub fn tournament_select_with_size<'a>(
29 population: &'a [Chromosome],
30 tournament_size: usize,
31 rng: &mut impl Rng,
32) -> &'a Chromosome {
33 assert!(
34 !population.is_empty(),
35 "tournament_select_with_size called with empty population — caller bug"
36 );
37 let size = tournament_size.min(population.len());
38 let mut best = pick_ref_from_rng(population, rng).unwrap_or(&population[0]);
39 for _ in 1..size {
40 let candidate = pick_ref_from_rng(population, rng).unwrap_or(&population[0]);
41 if candidate.fitness > best.fitness {
42 best = candidate;
43 }
44 }
45 best
46}
47
48#[must_use]
53pub fn roulette_select<'a>(population: &'a [Chromosome], rng: &mut impl Rng) -> &'a Chromosome {
54 assert!(
55 !population.is_empty(),
56 "roulette_select called with empty population — caller bug"
57 );
58 if population.len() == 1 {
59 return &population[0];
60 }
61 let total_fitness: f64 = population.iter().map(|c| c.fitness.max(0.0)).sum();
62 if total_fitness <= 0.0 {
63 return pick_ref_from_rng(population, rng).unwrap_or(&population[0]);
64 }
65 let mut spin = rng.gen_range(0.0..total_fitness);
66 for chromosome in population {
67 spin -= chromosome.fitness.max(0.0);
68 if spin <= 0.0 {
69 return chromosome;
70 }
71 }
72 &population[population.len() - 1]
73}
74
75#[must_use]
77pub fn adaptive_select<'a>(
78 population: &'a [Chromosome],
79 diversity: f64,
80 rng: &mut impl Rng,
81) -> &'a Chromosome {
82 let base_size = 3_usize;
83 let max_size = (population.len() / 3).max(base_size);
84 let adjusted_size = base_size + ((max_size - base_size) as f64 * (1.0 - diversity)) as usize;
85 tournament_select_with_size(population, adjusted_size, rng)
86}
87
88#[cfg(test)]
89mod tests {
90 use super::*;
91 use rand::{SeedableRng, rngs::StdRng};
92
93 #[test]
94 fn tournament_select_single_chromosome_returns_it() {
95 let population = vec![Chromosome::new(vec![("encoding".into(), "None".into())])];
96 let mut rng = StdRng::seed_from_u64(29);
97 let selected = tournament_select(&population, &mut rng);
98 assert_eq!(selected.genes, population[0].genes);
99 }
100}