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
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
use ndarray::Array1;
use crate::operators::CrossoverOperator;
use crate::random::RandomGenerator;
#[derive(Debug, Clone)]
/// Crossover operator that combines parent genes based on an exponential distribution.
pub struct ExponentialCrossover {
pub exponential_crossover_rate: f64,
}
impl ExponentialCrossover {
pub fn new(exponential_crossover_rate: f64) -> Self {
Self {
exponential_crossover_rate,
}
}
}
impl CrossoverOperator for ExponentialCrossover {
fn crossover(
&self,
parent_a: &Array1<f64>,
parent_b: &Array1<f64>,
rng: &mut impl RandomGenerator,
) -> (Array1<f64>, Array1<f64>) {
let len = parent_a.len();
assert_eq!(len, parent_b.len());
// We'll do "child_a" from (A,B) and "child_b" from (B,A).
let mut child_a = parent_a.clone();
let mut child_b = parent_b.clone();
// random start [0..len)
let start = rng.gen_range_usize(0, len);
// We'll copy from parent_b into child_a as long as rng < CR
// in a circular manner
let mut i = start;
loop {
child_a[i] = parent_b[i];
// Next index (wrap around)
i = (i + 1) % len;
if i == start {
break; // completed a full circle, stop
}
let r: f64 = rng.gen_proability();
if r >= self.exponential_crossover_rate {
break;
}
}
// Similarly for child_b, but swap roles of A/B
let start_b = rng.gen_range_usize(0, len);
let mut j = start_b;
loop {
child_b[j] = parent_a[j];
j = (j + 1) % len;
if j == start_b {
break;
}
let r: f64 = rng.gen_proability();
if r >= self.exponential_crossover_rate {
break;
}
}
(child_a, child_b)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::random::{RandomGenerator, TestDummyRng};
use ndarray::array;
/// A simple fake random generator for controlled testing of ExponentialCrossover.
/// It returns predetermined values for `gen_range_usize` and `gen_proability`.
struct FakeRandom {
/// Predefined responses for calls to `gen_range_usize`.
range_values: Vec<usize>,
/// Predefined responses for calls to `gen_proability`.
probability_values: Vec<f64>,
/// Dummy RNG to satisfy the trait requirement.
dummy: TestDummyRng,
}
impl FakeRandom {
/// Creates a new instance with the given responses.
fn new(range_values: Vec<usize>, probability_values: Vec<f64>) -> Self {
Self {
range_values,
probability_values,
dummy: TestDummyRng,
}
}
}
impl RandomGenerator for FakeRandom {
/// Returns a mutable reference to the underlying dummy RNG.
type R = TestDummyRng;
fn rng(&mut self) -> &mut TestDummyRng {
&mut self.dummy
}
/// Returns the next predetermined value for a range selection.
fn gen_range_usize(&mut self, _min: usize, _max: usize) -> usize {
self.range_values.remove(0)
}
/// Returns the next predetermined probability value.
fn gen_proability(&mut self) -> f64 {
self.probability_values.remove(0)
}
}
#[test]
fn test_exponential_crossover() {
// Define two parent IndividualGenes as small Array1<f64> vectors.
// For simplicity, we use arrays of length 3.
let parent_a = array![1.0, 2.0, 3.0];
let parent_b = array![4.0, 5.0, 6.0];
// Create the ExponentialCrossover operator with a crossover rate of 0.5.
let operator = ExponentialCrossover::new(0.5);
// Set up the fake random generator:
// - For child_a, gen_range_usize returns 1 (start index = 1) and then
// gen_proability returns 0.7 (>= 0.5) so the loop stops after one replacement.
// - For child_b, gen_range_usize returns 2 (start index = 2) and then
// gen_proability returns 0.8 (>= 0.5) so the loop stops after one replacement.
let mut fake_rng = FakeRandom::new(vec![1, 2], vec![0.7, 0.8]);
// Perform the exponential crossover.
let (child_a, child_b) = operator.crossover(&parent_a, &parent_b, &mut fake_rng);
// Expected outcome:
// For child_a: start index 1 -> replace gene at index 1 with parent_b[1] (5.0),
// so child_a becomes [1.0, 5.0, 3.0].
let expected_child_a = array![1.0, 5.0, 3.0];
// For child_b: start index 2 -> replace gene at index 2 with parent_a[2] (3.0),
// so child_b becomes [4.0, 5.0, 3.0].
let expected_child_b = array![4.0, 5.0, 3.0];
// Check that the results match the expectations.
assert_eq!(
child_a, expected_child_a,
"child_a does not match expected output"
);
assert_eq!(
child_b, expected_child_b,
"child_b does not match expected output"
);
}
}