Skip to main content

fugue_evo/operators/
traits.rs

1//! Operator traits
2//!
3//! This module defines the core operator traits for genetic algorithms.
4
5use rand::Rng;
6
7use crate::error::OperatorResult;
8use crate::genome::bounds::MultiBounds;
9use crate::genome::traits::EvolutionaryGenome;
10
11/// Selection operator trait
12///
13/// Selects individuals from a population for reproduction.
14pub trait SelectionOperator<G: EvolutionaryGenome>: Send + Sync {
15    /// Select a single individual from the population
16    ///
17    /// Returns the index of the selected individual.
18    fn select<R: Rng>(
19        &self,
20        population: &[(G, f64)], // (genome, fitness) pairs
21        rng: &mut R,
22    ) -> usize;
23
24    /// Select multiple individuals from the population
25    fn select_many<R: Rng>(
26        &self,
27        population: &[(G, f64)],
28        count: usize,
29        rng: &mut R,
30    ) -> Vec<usize> {
31        (0..count).map(|_| self.select(population, rng)).collect()
32    }
33}
34
35/// Crossover operator trait
36///
37/// Combines genetic material from two parents to create offspring.
38pub trait CrossoverOperator<G: EvolutionaryGenome>: Send + Sync {
39    /// Apply crossover to two parents and produce two offspring
40    fn crossover<R: Rng>(&self, parent1: &G, parent2: &G, rng: &mut R) -> OperatorResult<(G, G)>;
41
42    /// Get the probability of crossover being applied
43    fn crossover_probability(&self) -> f64 {
44        1.0
45    }
46}
47
48/// Mutation operator trait
49///
50/// Applies random changes to a genome.
51pub trait MutationOperator<G: EvolutionaryGenome>: Send + Sync {
52    /// Apply mutation to a genome in place
53    fn mutate<R: Rng>(&self, genome: &mut G, rng: &mut R);
54
55    /// Report the per-gene mutation probability *actually applied* by this
56    /// operator.
57    ///
58    /// Returns `None` when the operator uses a genome-length-dependent default
59    /// (the canonical `1/n` per-gene rate) that cannot be expressed as a single
60    /// constant here — this is the honest signal that the effective rate is
61    /// `1 / genome.len()`, not the misleading `1.0` this method used to report
62    /// (audit EV-103). Operators that always act on the whole genome (or have
63    /// no per-gene rate) return `Some(1.0)`.
64    fn mutation_probability(&self) -> Option<f64> {
65        Some(1.0)
66    }
67}
68
69/// Bounded mutation operator trait
70///
71/// Mutation operator that respects bounds on gene values.
72pub trait BoundedMutationOperator<G: EvolutionaryGenome>: MutationOperator<G> {
73    /// Apply bounded mutation to a genome
74    fn mutate_bounded<R: Rng>(&self, genome: &mut G, bounds: &MultiBounds, rng: &mut R);
75}
76
77/// Bounded crossover operator trait
78///
79/// Crossover operator that respects bounds on gene values.
80pub trait BoundedCrossoverOperator<G: EvolutionaryGenome>: CrossoverOperator<G> {
81    /// Apply bounded crossover to two parents
82    fn crossover_bounded<R: Rng>(
83        &self,
84        parent1: &G,
85        parent2: &G,
86        bounds: &MultiBounds,
87        rng: &mut R,
88    ) -> OperatorResult<(G, G)>;
89}
90
91#[cfg(test)]
92mod tests {
93    use super::*;
94    use crate::error::OperatorResult;
95    use crate::genome::real_vector::RealVector;
96    use crate::genome::traits::{EvolutionaryGenome, RealValuedGenome};
97
98    // Mock selection operator for testing
99    struct MockSelection;
100
101    impl SelectionOperator<RealVector> for MockSelection {
102        fn select<R: Rng>(&self, population: &[(RealVector, f64)], rng: &mut R) -> usize {
103            rng.gen_range(0..population.len())
104        }
105    }
106
107    // Mock crossover operator for testing
108    struct MockCrossover;
109
110    impl CrossoverOperator<RealVector> for MockCrossover {
111        fn crossover<R: Rng>(
112            &self,
113            parent1: &RealVector,
114            parent2: &RealVector,
115            _rng: &mut R,
116        ) -> OperatorResult<(RealVector, RealVector)> {
117            // Just swap parents as a simple crossover
118            OperatorResult::Success((parent2.clone(), parent1.clone()))
119        }
120    }
121
122    // Mock mutation operator for testing
123    struct MockMutation;
124
125    impl MutationOperator<RealVector> for MockMutation {
126        fn mutate<R: Rng>(&self, genome: &mut RealVector, rng: &mut R) {
127            if let Some(genes) = genome.as_mut_slice() {
128                for gene in genes.iter_mut() {
129                    *gene += rng.gen_range(-0.1..0.1);
130                }
131            }
132        }
133    }
134
135    #[test]
136    fn test_mock_selection() {
137        let mut rng = rand::thread_rng();
138        let population: Vec<(RealVector, f64)> = (0..10)
139            .map(|i| (RealVector::new(vec![i as f64]), i as f64))
140            .collect();
141
142        let selection = MockSelection;
143        let idx = selection.select(&population, &mut rng);
144        assert!(idx < population.len());
145    }
146
147    #[test]
148    fn test_mock_selection_many() {
149        let mut rng = rand::thread_rng();
150        let population: Vec<(RealVector, f64)> = (0..10)
151            .map(|i| (RealVector::new(vec![i as f64]), i as f64))
152            .collect();
153
154        let selection = MockSelection;
155        let indices = selection.select_many(&population, 5, &mut rng);
156        assert_eq!(indices.len(), 5);
157        for idx in indices {
158            assert!(idx < population.len());
159        }
160    }
161
162    #[test]
163    fn test_mock_crossover() {
164        let mut rng = rand::thread_rng();
165        let parent1 = RealVector::new(vec![1.0, 2.0, 3.0]);
166        let parent2 = RealVector::new(vec![4.0, 5.0, 6.0]);
167
168        let crossover = MockCrossover;
169        let result = crossover.crossover(&parent1, &parent2, &mut rng);
170        assert!(result.is_ok());
171
172        let (child1, child2) = result.genome().unwrap();
173        assert_eq!(child1.genes(), parent2.genes());
174        assert_eq!(child2.genes(), parent1.genes());
175    }
176
177    #[test]
178    fn test_mock_mutation() {
179        let mut rng = rand::thread_rng();
180        let original = RealVector::new(vec![1.0, 2.0, 3.0]);
181        let mut genome = original.clone();
182
183        let mutation = MockMutation;
184        mutation.mutate(&mut genome, &mut rng);
185
186        // Genes should have changed
187        // (with very high probability, they won't all be exactly the same)
188        assert_ne!(genome, original);
189    }
190}