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    /// Get the mutation probability per gene
56    fn mutation_probability(&self) -> f64 {
57        1.0
58    }
59}
60
61/// Bounded mutation operator trait
62///
63/// Mutation operator that respects bounds on gene values.
64pub trait BoundedMutationOperator<G: EvolutionaryGenome>: MutationOperator<G> {
65    /// Apply bounded mutation to a genome
66    fn mutate_bounded<R: Rng>(&self, genome: &mut G, bounds: &MultiBounds, rng: &mut R);
67}
68
69/// Bounded crossover operator trait
70///
71/// Crossover operator that respects bounds on gene values.
72pub trait BoundedCrossoverOperator<G: EvolutionaryGenome>: CrossoverOperator<G> {
73    /// Apply bounded crossover to two parents
74    fn crossover_bounded<R: Rng>(
75        &self,
76        parent1: &G,
77        parent2: &G,
78        bounds: &MultiBounds,
79        rng: &mut R,
80    ) -> OperatorResult<(G, G)>;
81}
82
83#[cfg(test)]
84mod tests {
85    use super::*;
86    use crate::error::OperatorResult;
87    use crate::genome::real_vector::RealVector;
88    use crate::genome::traits::{EvolutionaryGenome, RealValuedGenome};
89
90    // Mock selection operator for testing
91    struct MockSelection;
92
93    impl SelectionOperator<RealVector> for MockSelection {
94        fn select<R: Rng>(&self, population: &[(RealVector, f64)], rng: &mut R) -> usize {
95            rng.gen_range(0..population.len())
96        }
97    }
98
99    // Mock crossover operator for testing
100    struct MockCrossover;
101
102    impl CrossoverOperator<RealVector> for MockCrossover {
103        fn crossover<R: Rng>(
104            &self,
105            parent1: &RealVector,
106            parent2: &RealVector,
107            _rng: &mut R,
108        ) -> OperatorResult<(RealVector, RealVector)> {
109            // Just swap parents as a simple crossover
110            OperatorResult::Success((parent2.clone(), parent1.clone()))
111        }
112    }
113
114    // Mock mutation operator for testing
115    struct MockMutation;
116
117    impl MutationOperator<RealVector> for MockMutation {
118        fn mutate<R: Rng>(&self, genome: &mut RealVector, rng: &mut R) {
119            if let Some(genes) = genome.as_mut_slice() {
120                for gene in genes.iter_mut() {
121                    *gene += rng.gen_range(-0.1..0.1);
122                }
123            }
124        }
125    }
126
127    #[test]
128    fn test_mock_selection() {
129        let mut rng = rand::thread_rng();
130        let population: Vec<(RealVector, f64)> = (0..10)
131            .map(|i| (RealVector::new(vec![i as f64]), i as f64))
132            .collect();
133
134        let selection = MockSelection;
135        let idx = selection.select(&population, &mut rng);
136        assert!(idx < population.len());
137    }
138
139    #[test]
140    fn test_mock_selection_many() {
141        let mut rng = rand::thread_rng();
142        let population: Vec<(RealVector, f64)> = (0..10)
143            .map(|i| (RealVector::new(vec![i as f64]), i as f64))
144            .collect();
145
146        let selection = MockSelection;
147        let indices = selection.select_many(&population, 5, &mut rng);
148        assert_eq!(indices.len(), 5);
149        for idx in indices {
150            assert!(idx < population.len());
151        }
152    }
153
154    #[test]
155    fn test_mock_crossover() {
156        let mut rng = rand::thread_rng();
157        let parent1 = RealVector::new(vec![1.0, 2.0, 3.0]);
158        let parent2 = RealVector::new(vec![4.0, 5.0, 6.0]);
159
160        let crossover = MockCrossover;
161        let result = crossover.crossover(&parent1, &parent2, &mut rng);
162        assert!(result.is_ok());
163
164        let (child1, child2) = result.genome().unwrap();
165        assert_eq!(child1.genes(), parent2.genes());
166        assert_eq!(child2.genes(), parent1.genes());
167    }
168
169    #[test]
170    fn test_mock_mutation() {
171        let mut rng = rand::thread_rng();
172        let original = RealVector::new(vec![1.0, 2.0, 3.0]);
173        let mut genome = original.clone();
174
175        let mutation = MockMutation;
176        mutation.mutate(&mut genome, &mut rng);
177
178        // Genes should have changed
179        // (with very high probability, they won't all be exactly the same)
180        assert_ne!(genome, original);
181    }
182}