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
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
use crate::{Chromosome, Genome};
use rand::Rng;
/// A helper struct for performing the mutation operation on genomes.
pub struct Mutator<R: Rng> {
rate: f64,
rng: R,
}
impl<R: Rng> Mutator<R> {
#[inline(always)]
pub(crate) fn new(rate: f64, rng: R) -> Self {
Self { rate, rng }
}
/// Instructs the mutation helper to mutate a single chromosome.
#[inline(always)]
pub fn chromosome<'a, Ch: Chromosome + ?Sized>(
&'a mut self,
chromosome: &mut Ch,
) -> &'a mut Self {
chromosome.mutate(self.rate, &mut self.rng);
self
}
/// Instructs the mutation helper to mutate a sub-genome.
/// This is currently equivalent to calling `genome.mutate(helper)`,
/// but using this method is more idiomatic and future-proof.
#[inline(always)]
pub fn genome<'a, G: Genome + ?Sized>(&'a mut self, genome: &mut G) -> &'a mut Self {
genome.mutate(self);
self
}
/// Instructs the mutation helper to mutate an iterator of sub-genomes.
#[inline(always)]
pub fn iter<'a, 'b, G: Genome + ?Sized + 'b>(
&'a mut self,
genomes: impl IntoIterator<Item = &'b mut G>,
) -> &'a mut Self {
genomes.into_iter().for_each(|item| item.mutate(self));
self
}
/// Defines a group of chromosomes that have a lower rate of mutation than the other chromosomes.
#[inline(always)]
pub fn multiply_rate<'a, 'b, F: for<'c> FnOnce(&'c mut Self) + 'b>(
&'a mut self,
rate_multiplier: f64,
callback: F,
) -> &'a mut Self {
let new_rate = (self.rate * rate_multiplier).clamp(0.0, 1.0);
let old_rate = std::mem::replace(&mut self.rate, new_rate);
callback(self);
self.rate = old_rate;
self
}
/// Wraps a chromosome in one of the wrapped types defined in [chromosome.rs]
pub fn wrap_ch<'a, W, T>(&'a mut self, mut wrapper: W, value: &'_ mut T) -> &'a mut Self
where
T: From<W> + Clone,
W: Chromosome,
{
wrapper.mutate(self.rate, &mut self.rng);
*value = wrapper.into();
self
}
/// Lets you define a set of mutations as mutating a single, virtual chromosome.
///
/// This method mainly exists as a counterpart of [Crossover::group].
/// A call to this method should only account for a value of `1` in [Chromosome::size_hint],
/// no matter how many chromosomes and sub-genomes are mutated within the callback.
pub fn group<'a, 'b, F: for<'c> FnOnce(&'c mut Self) + 'b>(
&'a mut self,
callback: F,
) -> &'a mut Self {
callback(self);
self
}
}
/// A helper struct for performing the crossover operation on genomes.
pub struct Crossover<R: Rng> {
rng: R,
method: CrossoverState,
}
/// The crossover type, used for the [crate::crossover] function.
/// This determines how chromosomes of two individuals will be mixed.
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum CrossoverMethod {
/// Rolls a random number for each chromosome to determine whether it should be swapped or not.
/// The `f64` determines the rate at which chromosomes will be swapped.
/// A rate of `1.0` means that half the chromosomes will be swapped, while a rate of `0.0` means that nothing will happen.
Uniform(f64),
/// Splits the genome in `k` points (with `k` being passed to this enum variant),
/// and swaps all of the chromosomes in the even segments, leaving the odd segments as-is.
KPoint(u64),
// TODO: add more crossover operators
}
pub(crate) enum CrossoverState {
Uniform(f64),
KPoint {
count: u64,
length: u64,
swapped: u64,
desired: u64,
},
Fixed(bool),
}
impl<R: Rng> Crossover<R> {
#[inline(always)]
pub(crate) fn new(rng: R, method: CrossoverState) -> Self {
Self { rng, method }
}
fn should_flip(&mut self) -> bool {
match self.method {
CrossoverState::Uniform(rate) => self.rng.gen_bool(rate / 2.0),
CrossoverState::KPoint {
ref mut count,
length,
ref mut swapped,
desired,
} => {
// NOTE: I am unsure whether this method is enough to guarantee that there will be `k` splits
let rate = (desired - *swapped) as f64 / (length - *count) as f64;
if *count < length && self.rng.gen_bool(rate.min(1.0)) {
*swapped += 1;
}
*swapped % 2 == 1
}
CrossoverState::Fixed(res) => res,
}
}
/// Instructs the helper to perform its crossover operation on `ch_left` and `ch_right`.
/// This is the direct equivalent of [Mutator::chromosome].
#[inline(always)]
pub fn chromosome<'a, Ch>(&'a mut self, ch_left: &mut Ch, ch_right: &mut Ch) -> &'a mut Self {
if self.should_flip() {
std::mem::swap(ch_left, ch_right);
}
self
}
/// Instructs the helper to perform the crossover operation on a sub-genome.
///
/// This is the direct equivalent of [Mutator::genome].
#[inline(always)]
pub fn genome<'a, G: Genome + ?Sized>(
&'a mut self,
genome_left: &mut G,
genome_right: &mut G,
) -> &'a mut Self {
genome_left.crossover(genome_right, self);
self
}
/// Instructs the helper to perform the crossover operation on a list of sub-genomes.
///
/// This is the direct equivalent of [Mutator::iter].
#[inline(always)]
pub fn iter<'a, 'b, G: Genome + ?Sized + 'b>(
&'a mut self,
genomes_left: impl IntoIterator<Item = &'b mut G>,
genomes_right: impl IntoIterator<Item = &'b mut G>,
) -> &'a mut Self {
genomes_left
.into_iter()
.zip(genomes_right.into_iter())
.for_each(|(item_left, item_right)| item_left.crossover(item_right, self));
self
}
/// Groups together multiple operations as if it was a single chromosome.
///
/// This means that you can define multiple `Chromosome`s as needing to be mutated all together,
/// without explicitely bundling them together in your struct.
///
/// As with [Mutator::group], a call to this method should account for exactly `1` chromosome in [Chromosome::size_hint],
/// no matter how many operations are performed in the callback.
pub fn group<'a, 'b, F: for<'c> FnOnce(&'c mut Crossover<&'c mut R>) + 'b>(
&'a mut self,
callback: F,
) -> &'a mut Self {
let should_flip = self.should_flip();
let mut fixed = Crossover {
rng: &mut self.rng,
method: CrossoverState::Fixed(should_flip),
};
callback(&mut fixed);
self
}
}
#[cfg(test)]
mod test {
use std::collections::HashSet;
use crate::chromosome::UniformCh;
use super::*;
#[test]
fn test_wrap_ch() {
struct MyStruct(i32);
impl Genome for MyStruct {
fn mutate(&mut self, mutator: &mut Mutator<impl Rng>) {
mutator.wrap_ch(UniformCh::new(self.0, -1, 1), &mut self.0);
}
fn crossover(&mut self, _other: &mut Self, _crossover: &mut Crossover<impl Rng>) {
unimplemented!()
}
fn size_hint(&self) -> usize {
1
}
}
let mut instance = MyStruct(0);
let mut rng = rand::thread_rng();
let mut values = HashSet::new();
for _ in 0..100 {
crate::mutate(&mut instance, 1.0, &mut rng);
values.insert(instance.0);
assert!(instance.0 >= -1);
assert!(instance.0 <= 1);
}
assert_eq!(values.len(), 3);
}
#[test]
fn test_group() {
struct MyStruct(Vec<i32>);
impl Genome for MyStruct {
fn mutate(&mut self, mutator: &mut Mutator<impl Rng>) {
mutator
.group(|m| {
m.genome(&mut self.0[0..2]);
})
.group(|m| {
m.genome(&mut self.0[2..4]);
});
}
fn crossover(&mut self, other: &mut Self, crossover: &mut Crossover<impl Rng>) {
crossover
.group(|c| {
c.genome(&mut self.0[0..2], &mut other.0[0..2]);
})
.group(|c| {
c.genome(&mut self.0[2..4], &mut other.0[2..4]);
});
}
fn size_hint(&self) -> usize {
2
}
}
fn test_with_method(method: CrossoverMethod) {
let mut rng = rand::thread_rng();
for _ in 0..100 {
let mut instance_a = MyStruct(vec![0, 1, 2, 3]);
let mut instance_b = MyStruct(vec![4, 5, 6, 7]);
crate::crossover(&mut instance_a, &mut instance_b, method, &mut rng);
assert_eq!(instance_a.0[1], instance_a.0[0] + 1);
assert_eq!(instance_b.0[1], instance_b.0[0] + 1);
assert_eq!(instance_a.0[3], instance_a.0[2] + 1);
assert_eq!(instance_b.0[3], instance_b.0[2] + 1);
for i in 0..4 {
assert_ne!(instance_a.0[i], instance_b.0[i]);
}
}
}
test_with_method(CrossoverMethod::Uniform(0.5));
test_with_method(CrossoverMethod::KPoint(1));
}
}