Skip to main content

radiate_alters/crossovers/
blend.rs

1use radiate_core::{
2    AlterContext, BoundedGene, Chromosome, Crossover, Expr, Gene, RateSet, random_provider,
3};
4use radiate_utils::Float;
5
6/// The [BlendCrossover] is a crossover operator that blends [FloatGene] alleles from two parent chromosomes to create offspring.
7/// The blending is controlled by the `alpha` parameter, which determines the extent of blending between the two alleles.
8/// The formula used for blending is:
9///
10/// ```text
11/// new_allele_one = allele_one - (alpha * (allele_two - allele_one))
12/// new_allele_two = allele_two - (alpha * (allele_one - allele_two))
13/// ```
14#[derive(Debug, Clone, PartialEq)]
15pub struct BlendCrossover {
16    rate: Expr,
17    alpha: f32,
18}
19
20impl BlendCrossover {
21    /// Create a new instance of the [BlendCrossover] with the given rate and alpha.
22    /// The rate must be between 0.0 and 1.0, and the alpha must be between 0.0 and 1.0.
23    pub fn new(rate: impl Into<Expr>, alpha: f32) -> Self {
24        let rate = rate.into();
25
26        if !(0.0..=1.0).contains(&alpha) {
27            panic!("Alpha must be between 0 and 1");
28        }
29
30        BlendCrossover { rate, alpha }
31    }
32}
33
34impl<A, G, C> Crossover<C> for BlendCrossover
35where
36    A: Float,
37    G: Gene<Allele = A> + BoundedGene,
38    C: Chromosome<Gene = G>,
39{
40    fn rates(&self) -> RateSet {
41        RateSet::new(self.rate.clone())
42    }
43
44    #[inline]
45    fn cross_chromosomes(
46        &self,
47        chrom_one: &mut C,
48        chrom_two: &mut C,
49        ctx: &mut AlterContext,
50    ) -> usize {
51        let mut cross_count = 0;
52        let alpha = A::from(self.alpha).unwrap();
53
54        random_provider::with_rng(|rand| {
55            chrom_one.zip(chrom_two).for_each(|one, two| {
56                if rand.bool(ctx.rate()) {
57                    let allele_one = *one.allele();
58                    let allele_two = *two.allele();
59
60                    let new_allele_one = allele_one - (alpha * (allele_two - allele_one));
61                    let new_allele_two = allele_two - (alpha * (allele_one - allele_two));
62
63                    one.set_allele(new_allele_one);
64                    two.set_allele(new_allele_two);
65
66                    cross_count += 1;
67                }
68            });
69        });
70
71        cross_count
72    }
73}
74
75#[cfg(test)]
76mod tests {
77    use super::*;
78    use radiate_core::{Chromosome, FloatChromosome, FloatGene, alter::AlterUpdates};
79
80    #[test]
81    fn test_cross_chromosomes_basic() {
82        let crossover = BlendCrossover::new(1.0, 0.5);
83
84        let genes1 = vec![
85            FloatGene::new(1.0, 0.0..10.0, -10.0..10.0),
86            FloatGene::new(2.0, 0.0..10.0, -10.0..10.0),
87            FloatGene::new(3.0, 0.0..10.0, -10.0..10.0),
88        ];
89        let genes2 = vec![
90            FloatGene::new(4.0, 0.0..10.0, -10.0..10.0),
91            FloatGene::new(5.0, 0.0..10.0, -10.0..10.0),
92            FloatGene::new(6.0, 0.0..10.0, -10.0..10.0),
93        ];
94
95        let mut chrom_one = FloatChromosome::new(genes1);
96        let mut chrom_two = FloatChromosome::new(genes2);
97
98        let original_one: Vec<f32> = chrom_one.iter().map(|g| *g.allele()).collect();
99        let original_two: Vec<f32> = chrom_two.iter().map(|g| *g.allele()).collect();
100
101        let mut updates = AlterUpdates::default();
102        let mut ctx = AlterContext::new(&mut updates, 0, 1.0, &[]);
103
104        let result = crossover.cross_chromosomes(&mut chrom_one, &mut chrom_two, &mut ctx);
105
106        assert_eq!(result, 3);
107
108        // Check that values have been blended according to the formula
109        // new_allele_one = allele_one - (alpha * (allele_two - allele_one))
110        // new_allele_two = allele_two - (alpha * (allele_one - allele_two))
111        let alpha = 0.5;
112
113        for i in 0..chrom_one.len() {
114            let expected_one = original_one[i] - (alpha * (original_two[i] - original_one[i]));
115            let expected_two = original_two[i] - (alpha * (original_one[i] - original_two[i]));
116
117            assert!((chrom_one.get(i).unwrap().allele() - expected_one).abs() < 1e-6);
118            assert!((chrom_two.get(i).unwrap().allele() - expected_two).abs() < 1e-6);
119        }
120    }
121
122    #[test]
123    fn test_cross_chromosomes_zero_rate() {
124        let crossover = BlendCrossover::new(0.0, 0.5);
125
126        let genes1 = vec![
127            FloatGene::new(1.0, 0.0..10.0, 0.0..10.0),
128            FloatGene::new(2.0, 0.0..10.0, 0.0..10.0),
129        ];
130        let genes2 = vec![
131            FloatGene::new(4.0, 0.0..10.0, 0.0..10.0),
132            FloatGene::new(5.0, 0.0..10.0, 0.0..10.0),
133        ];
134
135        let mut chrom_one = FloatChromosome::new(genes1);
136        let mut chrom_two = FloatChromosome::new(genes2);
137
138        let original_one: Vec<f32> = chrom_one.iter().map(|g| *g.allele()).collect();
139        let original_two: Vec<f32> = chrom_two.iter().map(|g| *g.allele()).collect();
140
141        let mut updates = AlterUpdates::default();
142        let mut ctx = AlterContext::new(&mut updates, 0, 0.0, &[]);
143
144        let result = crossover.cross_chromosomes(&mut chrom_one, &mut chrom_two, &mut ctx);
145
146        assert_eq!(result, 0);
147
148        // Values should remain unchanged
149        for i in 0..chrom_one.len() {
150            assert_eq!(*chrom_one.get(i).unwrap().allele(), original_one[i]);
151            assert_eq!(*chrom_two.get(i).unwrap().allele(), original_two[i]);
152        }
153    }
154
155    #[test]
156    fn test_cross_chromosomes_different_lengths() {
157        let crossover = BlendCrossover::new(1.0, 0.3);
158
159        let genes1 = vec![
160            FloatGene::new(1.0, 0.0..10.0, 0.0..10.0),
161            FloatGene::new(2.0, 0.0..10.0, 0.0..10.0),
162            FloatGene::new(3.0, 0.0..10.0, 0.0..10.0),
163        ];
164        let genes2 = vec![
165            FloatGene::new(4.0, 0.0..10.0, 0.0..10.0),
166            FloatGene::new(5.0, 0.0..10.0, 0.0..10.0),
167        ];
168
169        let mut chrom_one = FloatChromosome::new(genes1);
170        let mut chrom_two = FloatChromosome::new(genes2);
171
172        let mut updates = AlterUpdates::default();
173        let mut ctx = AlterContext::new(&mut updates, 0, 1.0, &[]);
174
175        let result = crossover.cross_chromosomes(&mut chrom_one, &mut chrom_two, &mut ctx);
176
177        assert_eq!(result, 2);
178
179        let alpha = 0.3_f32;
180        let expected_one_0 = 1.0_f32 - (alpha * (4.0_f32 - 1.0_f32));
181        let expected_two_0 = 4.0_f32 - (alpha * (1.0_f32 - 4.0_f32));
182        let expected_one_1 = 2.0_f32 - (alpha * (5.0_f32 - 2.0_f32));
183        let expected_two_1 = 5.0_f32 - (alpha * (2.0_f32 - 5.0_f32));
184
185        assert!((*chrom_one.get(0).unwrap().allele() - expected_one_0).abs() < 1e-6);
186        assert!((*chrom_two.get(0).unwrap().allele() - expected_two_0).abs() < 1e-6);
187        assert!((*chrom_one.get(1).unwrap().allele() - expected_one_1).abs() < 1e-6);
188        assert!((*chrom_two.get(1).unwrap().allele() - expected_two_1).abs() < 1e-6);
189
190        assert_eq!(*chrom_one.get(2).unwrap().allele(), 3.0);
191    }
192
193    #[test]
194    fn test_cross_chromosomes_alpha_zero() {
195        let crossover = BlendCrossover::new(1.0, 0.0);
196
197        let genes1 = vec![
198            FloatGene::new(1.0, 0.0..10.0, 0.0..10.0),
199            FloatGene::new(2.0, 0.0..10.0, 0.0..10.0),
200        ];
201        let genes2 = vec![
202            FloatGene::new(4.0, 0.0..10.0, 0.0..10.0),
203            FloatGene::new(5.0, 0.0..10.0, 0.0..10.0),
204        ];
205
206        let mut chrom_one = FloatChromosome::new(genes1);
207        let mut chrom_two = FloatChromosome::new(genes2);
208
209        let original_one: Vec<f32> = chrom_one.iter().map(|g| *g.allele()).collect();
210        let original_two: Vec<f32> = chrom_two.iter().map(|g| *g.allele()).collect();
211
212        let mut updates = AlterUpdates::default();
213        let mut ctx = AlterContext::new(&mut updates, 0, 1.0, &[]);
214
215        let result = crossover.cross_chromosomes(&mut chrom_one, &mut chrom_two, &mut ctx);
216
217        assert_eq!(result, 2);
218
219        // With alpha = 0, values should remain unchanged
220        for i in 0..chrom_one.len() {
221            assert_eq!(*chrom_one.get(i).unwrap().allele(), original_one[i]);
222            assert_eq!(*chrom_two.get(i).unwrap().allele(), original_two[i]);
223        }
224    }
225
226    #[test]
227    fn test_cross_chromosomes_alpha_one() {
228        let crossover = BlendCrossover::new(1.0, 1.0);
229
230        let genes1 = vec![
231            FloatGene::new(1.0, 0.0..10.0, -10.0..10.0),
232            FloatGene::new(2.0, 0.0..10.0, -10.0..10.0),
233        ];
234        let genes2 = vec![
235            FloatGene::new(4.0, 0.0..10.0, -10.0..10.0),
236            FloatGene::new(5.0, 0.0..10.0, -10.0..10.0),
237        ];
238
239        let mut chrom_one = FloatChromosome::new(genes1);
240        let mut chrom_two = FloatChromosome::new(genes2);
241
242        let mut updates = AlterUpdates::default();
243        let mut ctx = AlterContext::new(&mut updates, 0, 1.0, &[]);
244
245        let result = crossover.cross_chromosomes(&mut chrom_one, &mut chrom_two, &mut ctx);
246
247        assert_eq!(result, 2);
248
249        // With alpha = 1, values should be swapped
250        assert_eq!(*chrom_one.get(0).unwrap().allele(), -2.0);
251        assert_eq!(*chrom_two.get(0).unwrap().allele(), 7.0);
252        assert_eq!(*chrom_one.get(1).unwrap().allele(), -1.0);
253        assert_eq!(*chrom_two.get(1).unwrap().allele(), 8.0);
254    }
255
256    #[test]
257    fn test_cross_chromosomes_identical_parents() {
258        let crossover = BlendCrossover::new(1.0, 0.5);
259
260        let genes = vec![
261            FloatGene::new(1.0, 0.0..10.0, 0.0..10.0),
262            FloatGene::new(2.0, 0.0..10.0, 0.0..10.0),
263        ];
264
265        let mut chrom_one = FloatChromosome::new(genes.clone());
266        let mut chrom_two = FloatChromosome::new(genes);
267
268        let mut updates = AlterUpdates::default();
269        let mut ctx = AlterContext::new(&mut updates, 0, 1.0, &[]);
270
271        let result = crossover.cross_chromosomes(&mut chrom_one, &mut chrom_two, &mut ctx);
272
273        assert_eq!(result, 2);
274
275        // With identical parents, values should remain the same
276        for i in 0..chrom_one.len() {
277            assert_eq!(
278                *chrom_one.get(i).unwrap().allele(),
279                *chrom_two.get(i).unwrap().allele()
280            );
281        }
282    }
283
284    #[test]
285    fn test_cross_chromosomes_property_based() {
286        let crossover = BlendCrossover::new(1.0, 0.5);
287
288        for _ in 0..50 {
289            let genes1: Vec<FloatGene<f32>> = (0..5)
290                .map(|_| {
291                    FloatGene::new(
292                        random_provider::random::<f32>() * 10.0,
293                        0.0..10.0,
294                        -10.0..10.0,
295                    )
296                })
297                .collect();
298            let genes2: Vec<FloatGene<f32>> = (0..5)
299                .map(|_| {
300                    FloatGene::new(
301                        random_provider::random::<f32>() * 10.0,
302                        0.0..10.0,
303                        -10.0..10.0,
304                    )
305                })
306                .collect();
307
308            let mut chrom_one = FloatChromosome::new(genes1);
309            let mut chrom_two = FloatChromosome::new(genes2);
310
311            let original_one: Vec<f32> = chrom_one.iter().map(|g| *g.allele()).collect();
312            let original_two: Vec<f32> = chrom_two.iter().map(|g| *g.allele()).collect();
313
314            let mut updates = AlterUpdates::default();
315            let mut ctx = AlterContext::new(&mut updates, 0, 1.0, &[]);
316
317            let result = crossover.cross_chromosomes(&mut chrom_one, &mut chrom_two, &mut ctx);
318
319            assert_eq!(result, 5);
320
321            let alpha = 0.5;
322            for i in 0..chrom_one.len() {
323                let expected_one = original_one[i] - (alpha * (original_two[i] - original_one[i]));
324                let expected_two = original_two[i] - (alpha * (original_one[i] - original_two[i]));
325
326                let gene_one = chrom_one.get(i).unwrap();
327                let gene_two = chrom_two.get(i).unwrap();
328
329                if expected_one < *gene_one.bound_range().0
330                    || expected_one > *gene_one.bound_range().1
331                {
332                    assert!(*gene_one.allele() >= *gene_one.bound_range().0);
333                    assert!(*gene_one.allele() <= *gene_one.bound_range().1);
334                } else {
335                    assert!((gene_one.allele() - expected_one).abs() < 1e-6);
336                }
337
338                if expected_two < *gene_two.bound_range().0
339                    || expected_two > *gene_two.bound_range().1
340                {
341                    assert!(*gene_two.allele() >= *gene_two.bound_range().0);
342                    assert!(*gene_two.allele() <= *gene_two.bound_range().1);
343                } else {
344                    assert!((gene_two.allele() - expected_two).abs() < 1e-6);
345                }
346            }
347        }
348    }
349
350    #[test]
351    fn test_cross_chromosomes_edge_cases() {
352        let crossover = BlendCrossover::new(1.0, 0.5);
353
354        // Test with single gene chromosomes
355        let genes1 = vec![FloatGene::new(1.0, 0.0..10.0, 0.0..10.0)];
356        let genes2 = vec![FloatGene::new(4.0, 0.0..10.0, 0.0..10.0)];
357
358        let mut chrom_one = FloatChromosome::new(genes1);
359        let mut chrom_two = FloatChromosome::new(genes2);
360
361        let mut updates = AlterUpdates::default();
362        let mut ctx = AlterContext::new(&mut updates, 0, 1.0, &[]);
363
364        let result = crossover.cross_chromosomes(&mut chrom_one, &mut chrom_two, &mut ctx);
365
366        assert_eq!(result, 1);
367
368        // Test with empty chromosomes (should not panic)
369        let mut empty_one = FloatChromosome::<f32>::new(vec![]);
370        let mut empty_two = FloatChromosome::<f32>::new(vec![]);
371
372        let result = crossover.cross_chromosomes(&mut empty_one, &mut empty_two, &mut ctx);
373        assert_eq!(result, 0);
374    }
375
376    #[test]
377    fn test_blend_formula_verification() {
378        let crossover = BlendCrossover::new(1.0, 0.3);
379        let alpha = 0.3;
380
381        // Test specific values to verify the blending formula
382        let genes1 = vec![FloatGene::new(2.0, 0.0..10.0, 0.0..10.0)];
383        let genes2 = vec![FloatGene::new(8.0, 0.0..10.0, 0.0..10.0)];
384
385        let mut chrom_one = FloatChromosome::<f32>::new(genes1);
386        let mut chrom_two = FloatChromosome::<f32>::new(genes2);
387
388        let mut updates = AlterUpdates::default();
389        let mut ctx = AlterContext::new(&mut updates, 0, 1.0, &[]);
390
391        crossover.cross_chromosomes(&mut chrom_one, &mut chrom_two, &mut ctx);
392
393        // Manual calculation: allele_one = 2.0, allele_two = 8.0, alpha = 0.3
394        // new_allele_one = 2.0 - (0.3 * (8.0 - 2.0)) = 2.0 - (0.3 * 6.0) = 2.0 - 1.8 = 0.2
395        // new_allele_two = 8.0 - (0.3 * (2.0 - 8.0)) = 8.0 - (0.3 * -6.0) = 8.0 + 1.8 = 9.8
396        let expected_one = 2.0 - (alpha * (8.0 - 2.0));
397        let expected_two = 8.0 - (alpha * (2.0 - 8.0));
398
399        assert!((chrom_one.get(0).unwrap().allele() - expected_one).abs() < 1e-6);
400        assert!((chrom_two.get(0).unwrap().allele() - expected_two).abs() < 1e-6);
401    }
402}