Skip to main content

radiate_alters/crossovers/
blend.rs

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