ecrs/ga/operators/replacement.rs
1//! Replacement operators module
2//!
3//! Purpose of the replacement operator is to merge two populations:
4//! original one and the result of crossover phase to a single one,
5//! which will be the next generation
6
7use crate::ga::individual::IndividualTrait;
8
9/// # Replacement Operator
10///
11/// This trait defines common behaviour for crossover operators.
12/// You can implement this trait to provide your custom replacement
13/// operator to the genetic algorithm.
14///
15/// **NOTE**: In current implementation, all library-implemented operators assume that
16/// at indices i, i+1 in `population` collection there are parents of children i, i+1
17/// from `children` collection. Any violation of this invariant may lead to bugs - it can
18/// be considered an undefined behaviour. We'll work towards improving this case in the future.
19pub trait ReplacementOperator<IndividualT: IndividualTrait> {
20 /// Merges `children` - output of crossover operator with current population.
21 ///
22 /// **NOTE**: In current implementation, all library-implemented operators assume that
23 /// at indices i, i+1 in `population` collection there are parents of children i, i+1
24 /// from `children` collection. Any violation of this invariant may lead to bugs - it can
25 /// be considered an undefined behaviour. We'll work towards improving this case in the future.
26 ///
27 /// ### Arguments
28 ///
29 /// * `population` - Original population, input to the crossover phase.
30 /// This collection should be modified in place by the operator.
31 /// * `children` - Result of the crossover phase.
32 fn apply(&self, population: Vec<IndividualT>, children: Vec<IndividualT>) -> Vec<IndividualT>;
33
34 /// Returns `true` when the operator requires children to possess valid fitness values.
35 ///
36 /// Default implementation returns `true`
37 fn requires_children_fitness(&self) -> bool {
38 true
39 }
40}
41
42/// # BothParents replacement operator
43///
44/// This struct implements [ReplacementOperator] trait and can be used with genetic algorithm.
45///
46/// It works simply by replacing parents with their children. In effect, each individual
47/// only gets to breed once.
48///
49/// **NOTE**: In current implementation, all library-implemented operators assume that
50/// at indices i, i+1 in `population` collection there are parents of children i, i+1
51/// from `children` collection. Any violation of this invariant may lead to bugs - it can
52/// be considered an undefined behaviour. We'll work towards improving this case in the future.
53pub struct BothParents;
54
55impl BothParents {
56 /// Returns new instance of [BothParents] replacement operator.
57 pub fn new() -> Self {
58 Self
59 }
60}
61
62impl<IndividualT: IndividualTrait> ReplacementOperator<IndividualT> for BothParents {
63 /// Works simply by replacing parents with their children
64 ///
65 /// **NOTE**: In current implementation, all library-implemented operators assume that
66 /// at indices i, i+1 in `population` collection there are parents of children i, i+1
67 /// from `children` collection. Any violation of this invariant may lead to bugs - it can
68 /// be considered an undefined behaviour. We'll work towards improving this case in the future.
69 ///
70 /// ### Arguments
71 ///
72 /// * `population` - Original population, input to the crossover phase.
73 /// This collection should be modified in place by the operator.
74 /// * `children` - Result of the crossover phase
75 #[inline(always)]
76 fn apply(&self, _population: Vec<IndividualT>, children: Vec<IndividualT>) -> Vec<IndividualT> {
77 children
78 }
79
80 /// Returns `true` when the operator requires children to possess valid fitness values.
81 ///
82 /// This implementation returns `false`.
83 #[inline(always)]
84 fn requires_children_fitness(&self) -> bool {
85 false
86 }
87}
88
89/// # Noop replacement operator
90///
91/// This struct implements [ReplacementOperator] trait and can be used with genetic algorithm.
92///
93/// It does nothing. Implementation is a noop.
94pub struct Noop;
95
96impl Noop {
97 /// Returns new instance of [Noop] replacement operator
98 pub fn new() -> Self {
99 Self
100 }
101}
102
103impl<IndividualT: IndividualTrait> ReplacementOperator<IndividualT> for Noop {
104 /// Returns input `population`.
105 #[inline(always)]
106 fn apply(&self, population: Vec<IndividualT>, _children: Vec<IndividualT>) -> Vec<IndividualT> {
107 population
108 }
109
110 /// Returns `true` when the operator requires children to possess valid fitness values.
111 ///
112 /// This implementation returns `false`.
113 #[inline(always)]
114 fn requires_children_fitness(&self) -> bool {
115 false
116 }
117}
118
119/// # WeakParent replacement operator
120///
121/// This struct implements [ReplacementOperator] trait and can be used with genetic algorithm.
122///
123/// Works by taking two out of four individuals (two parents and two children) with the largest fitness.
124///
125/// **NOTE**: In current implementation, all library-implemented operators assume that
126/// at indices i, i+1 in `population` collection there are parents of children i, i+1
127/// from `children` collection. Any violation of this invariant may lead to bugs - it can
128/// be considered an undefined behaviour. We'll work towards improving this case in the future.
129///
130/// **NOTE**: This operator assumes that the size of `population` and `children` are of same size.
131/// Assertion is performed only in debug build. Breaking this condition may lead to bugs and can be thought
132/// of as undefined behaviour.
133///
134/// **NOTE**: This operator assumes that the size of `population` and `children` is a even number.
135/// Assertion is performed only in debug build. Breaking this condition may lead to bugs and can be thought
136/// of as undefined behaviour. This restriction will be removed in future versions
137/// of the library.
138pub struct WeakParent;
139
140impl WeakParent {
141 /// Returns new instance of [WeakParent] replacement operator.
142 pub fn new() -> Self {
143 Self
144 }
145}
146
147impl<IndividualT: IndividualTrait> ReplacementOperator<IndividualT> for WeakParent {
148 /// Works by taking two out of four individuals (two parents and two children) with the largest fitness.
149 ///
150 /// **NOTE**: In current implementation, all library-implemented operators assume that
151 /// at indices i, i+1 in `population` collection there are parents of children i, i+1
152 /// from `children` collection. Any violation of this invariant may lead to bugs - it can
153 /// be considered an undefined behaviour. We'll work towards improving this case in the future.
154 ///
155 /// **NOTE**: This operator assumes that the size of `population` and `children` are of same size.
156 /// Assertion is performed only in debug build. Breaking this condition may lead to bugs and can be thought
157 /// of as undefined behaviour.
158 ///
159 /// **NOTE**: This operator assumes that the size of `population` and `children` is a even number.
160 /// Assertion is performed only in debug build. Breaking this condition may lead to bugs and can be thought
161 /// of as undefined behaviour. This restriction will be removed in future versions
162 /// of the library.
163 ///
164 /// ### Arguments
165 ///
166 /// * `population` - Original population, input to the crossover phase.
167 /// This collection should be modified in place by the operator.
168 /// * `children` - Result of the crossover phase
169 fn apply(&self, mut population: Vec<IndividualT>, mut children: Vec<IndividualT>) -> Vec<IndividualT> {
170 debug_assert_eq!(
171 population.len(),
172 children.len(),
173 "Population and children must be of the same size"
174 );
175 debug_assert!(population.len() % 2 == 0, "Population size must be even");
176
177 // Unfortunately array windowing is not in stable Rust yet, I believe
178 // https://doc.rust-lang.org/std/slice/struct.ArrayChunks.html
179
180 // There is for sure a nicer way to implement this ;D
181 for i in (0..(population.len() - 1)).step_by(2) {
182 if population[i] < population[i + 1] {
183 population.swap(i, i + 1);
184 }
185
186 if children[i] < children[i + 1] {
187 children.swap(i, i + 1);
188 }
189
190 if children[i] > population[i] {
191 population.swap(i, i + 1);
192 std::mem::swap(&mut children[i], &mut population[i]);
193
194 if children[i + 1] > population[i + 1] {
195 std::mem::swap(&mut children[i + 1], &mut population[i + 1]);
196 }
197 } else if children[i] > population[i + 1] {
198 std::mem::swap(&mut children[i], &mut population[i + 1]);
199 }
200 }
201 population
202 }
203}
204
205#[cfg(test)]
206mod tests {
207 use crate::ga::Individual;
208
209 use super::{BothParents, Noop, ReplacementOperator, WeakParent};
210
211 #[test]
212 fn noop_has_new_method() {
213 let _ = Noop::new();
214 }
215
216 #[test]
217 fn both_parents_has_new_method() {
218 let _ = BothParents::new();
219 }
220
221 #[test]
222 fn weak_parent_swaps_when_children_are_stronger() {
223 let parents = vec![
224 Individual {
225 chromosome: 0.0,
226 fitness: 60.0,
227 },
228 Individual {
229 chromosome: 0.0,
230 fitness: 40.0,
231 },
232 ];
233
234 let children = vec![
235 Individual {
236 chromosome: 0.0,
237 fitness: 120.0,
238 },
239 Individual {
240 chromosome: 0.0,
241 fitness: 100.0,
242 },
243 ];
244
245 let children_clone = children.clone();
246
247 let result = WeakParent::new().apply(parents, children);
248
249 assert_eq!(result, children_clone);
250 }
251
252 #[test]
253 fn weak_parent_does_not_swap_when_parents_are_stronger() {
254 let parents = vec![
255 Individual {
256 chromosome: 0.0,
257 fitness: 60.0,
258 },
259 Individual {
260 chromosome: 0.0,
261 fitness: 40.0,
262 },
263 ];
264
265 let children = vec![
266 Individual {
267 chromosome: 0.0,
268 fitness: 10.0,
269 },
270 Individual {
271 chromosome: 0.0,
272 fitness: 12.0,
273 },
274 ];
275
276 let parents_clone = parents.clone();
277
278 let result = WeakParent::new().apply(parents, children);
279
280 assert_eq!(result, parents_clone);
281 }
282
283 #[test]
284 fn weak_parent_cross_swaps_child_1() {
285 let parents = vec![
286 Individual {
287 chromosome: 0.0,
288 fitness: 60.0,
289 },
290 Individual {
291 chromosome: 0.0,
292 fitness: 40.0,
293 },
294 ];
295
296 let children = vec![
297 Individual {
298 chromosome: 0.0,
299 fitness: 50.0,
300 },
301 Individual {
302 chromosome: 0.0,
303 fitness: 30.0,
304 },
305 ];
306
307 let expected_result = vec![
308 Individual {
309 chromosome: 0.0,
310 fitness: 60.0,
311 },
312 Individual {
313 chromosome: 0.0,
314 fitness: 50.0,
315 },
316 ];
317
318 let result = WeakParent::new().apply(parents, children);
319
320 assert_eq!(result, expected_result);
321 }
322
323 #[test]
324 fn weak_parent_cross_swaps_child_2() {
325 let parents = vec![
326 Individual {
327 chromosome: 0.0,
328 fitness: 60.0,
329 },
330 Individual {
331 chromosome: 0.0,
332 fitness: 40.0,
333 },
334 ];
335
336 let children = vec![
337 Individual {
338 chromosome: 0.0,
339 fitness: 30.0,
340 },
341 Individual {
342 chromosome: 0.0,
343 fitness: 50.0,
344 },
345 ];
346
347 let expected_result = vec![
348 Individual {
349 chromosome: 0.0,
350 fitness: 60.0,
351 },
352 Individual {
353 chromosome: 0.0,
354 fitness: 50.0,
355 },
356 ];
357
358 let result = WeakParent::new().apply(parents, children);
359
360 assert_eq!(result, expected_result);
361 }
362
363 #[test]
364 fn weak_parent_takes_two_best() {
365 let parents = vec![
366 Individual {
367 chromosome: 0.0,
368 fitness: 60.0,
369 },
370 Individual {
371 chromosome: 0.0,
372 fitness: 40.0,
373 },
374 ];
375
376 let children = vec![
377 Individual {
378 chromosome: 0.0,
379 fitness: 70.0,
380 },
381 Individual {
382 chromosome: 0.0,
383 fitness: 50.0,
384 },
385 ];
386
387 let expected_result = vec![
388 Individual {
389 chromosome: 0.0,
390 fitness: 70.0,
391 },
392 Individual {
393 chromosome: 0.0,
394 fitness: 60.0,
395 },
396 ];
397
398 let result = WeakParent::new().apply(parents, children);
399
400 assert_eq!(result, expected_result);
401 }
402}