1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
use ndarray::Array1;
use crate::operators::CrossoverOperator;
use crate::random::RandomGenerator;
#[derive(Debug, Clone)]
/// Uniform binary crossover operator for genetic algorithms.
pub struct UniformBinaryCrossover;
impl UniformBinaryCrossover {
pub fn new() -> Self {
Self {}
}
}
impl CrossoverOperator for UniformBinaryCrossover {
fn crossover(
&self,
parent_a: &Array1<f64>,
parent_b: &Array1<f64>,
rng: &mut impl RandomGenerator,
) -> (Array1<f64>, Array1<f64>) {
assert_eq!(
parent_a.len(),
parent_b.len(),
"Parents must have the same number of genes"
);
let num_genes = parent_a.len();
let mut offspring_a = Array1::<f64>::zeros(num_genes);
let mut offspring_b = Array1::<f64>::zeros(num_genes);
for i in 0..num_genes {
if rng.gen_proability() < 0.5 {
// Swap genes
offspring_a[i] = parent_b[i];
offspring_b[i] = parent_a[i];
} else {
// Keep genes
offspring_a[i] = parent_a[i];
offspring_b[i] = parent_b[i];
}
}
(offspring_a, offspring_b)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::random::{RandomGenerator, TestDummyRng};
use ndarray::{Array1, array};
/// A controlled fake random generator that returns predetermined values for `gen_proability()`.
struct ControlledFakeProbabilityGenerator {
/// A list of predetermined f64 responses.
responses: Vec<f64>,
/// Current index into the responses vector.
index: usize,
/// Dummy RNG used to satisfy the `rng()` method.
dummy: TestDummyRng,
}
impl ControlledFakeProbabilityGenerator {
fn new(responses: Vec<f64>) -> Self {
Self {
responses,
index: 0,
dummy: TestDummyRng,
}
}
}
impl RandomGenerator for ControlledFakeProbabilityGenerator {
type R = TestDummyRng;
fn rng(&mut self) -> &mut TestDummyRng {
&mut self.dummy
}
// Override the default `gen_proability()` to return controlled values.
fn gen_proability(&mut self) -> f64 {
let value = self.responses[self.index];
self.index += 1;
value
}
}
#[test]
fn test_uniform_binary_crossover_controlled() {
// Define two parent individuals.
let parent_a: Array1<f64> = array![0.0, 1.0, 1.0, 0.0, 1.0];
let parent_b: Array1<f64> = array![1.0, 0.0, 0.0, 1.0, 0.0];
let crossover_operator = UniformBinaryCrossover::new();
// Create a controlled fake RNG with predetermined probability values.
// For each gene index, the decision is made by comparing the generated probability with 0.5:
// Index 0: 0.6 (>= 0.5 → no swap)
// Index 1: 0.7 (>= 0.5 → no swap)
// Index 2: 0.8 (>= 0.5 → no swap)
// Index 3: 0.3 (< 0.5 → swap)
// Index 4: 0.4 (< 0.5 → swap)
let responses = vec![0.6, 0.7, 0.8, 0.3, 0.4];
let mut fake_rng = ControlledFakeProbabilityGenerator::new(responses);
// Perform the uniform binary crossover.
let (offspring_a, offspring_b) =
crossover_operator.crossover(&parent_a, &parent_b, &mut fake_rng);
// Expected offspring:
// For index 0: no swap → offspring_a[0] = parent_a[0] = 0.0, offspring_b[0] = parent_b[0] = 1.0.
// For index 1: no swap → offspring_a[1] = parent_a[1] = 1.0, offspring_b[1] = parent_b[1] = 0.0.
// For index 2: no swap → offspring_a[2] = parent_a[2] = 1.0, offspring_b[2] = parent_b[2] = 0.0.
// For index 3: swap → offspring_a[3] = parent_b[3] = 1.0, offspring_b[3] = parent_a[3] = 0.0.
// For index 4: swap → offspring_a[4] = parent_b[4] = 0.0, offspring_b[4] = parent_a[4] = 1.0.
let expected_offspring_a: Array1<f64> = array![0.0, 1.0, 1.0, 1.0, 0.0];
let expected_offspring_b: Array1<f64> = array![1.0, 0.0, 0.0, 0.0, 1.0];
assert_eq!(
offspring_a, expected_offspring_a,
"Offspring A is not as expected"
);
assert_eq!(
offspring_b, expected_offspring_b,
"Offspring B is not as expected"
);
}
}