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}