Skip to main content

ecrs/ga/operators/
selection.rs

1use std::{iter::Sum, ops::Index};
2
3use num_traits::{identities::Zero, NumAssignOps};
4use rand::{distributions::Standard, prelude::Distribution, rngs::ThreadRng, Rng};
5
6use crate::ga::{individual::IndividualTrait, GAMetadata};
7
8/// ### Selection operator
9///
10/// This trait defines common behaviour for selection operators.
11/// You can implement this trait to provide your custom crossover opertator to the GA.
12///
13/// Following operators are implemented in the library:
14///
15/// * [RouletteWheel]
16/// * [Random]
17/// * [Rank]
18/// * [RankR]
19/// * [Tournament]
20/// * [StochasticUniversalSampling]
21/// * [Boltzmann]
22///
23/// See their respecitve docs for details.
24pub trait SelectionOperator<IndividualT: IndividualTrait> {
25    /// Returns a vector of references to individuals selected to mating pool
26    ///
27    /// ### Arguments
28    ///
29    /// * `metadata` - [crate::ga::GAMetadata] information on current stage of the algorithm (iteration, elapsed time, etc.)
30    /// * `population` - individuals to choose mating pool from
31    /// * `count` - target number of individuals in mating pool
32    fn apply<'a>(
33        &mut self,
34        metadata: &GAMetadata,
35        population: &'a [IndividualT],
36        count: usize,
37    ) -> Vec<&'a IndividualT>;
38}
39
40/// ### Routelle wheel selection operator
41///
42/// This struct implements [SelectionOperator] trait and can be used with GA.
43///
44/// **Note 1**: This selection operator requires positive fitness function. No runtime checks are performed
45/// to assert this invariant. If aggregated fitness in whole population is <= 0 the behaviour is undefined,
46/// implementation dependent and might change without any notice.
47///
48/// **Note 2**: The same individual can be selected multiple times.
49///
50/// Individuals are selected with probability proportional to their fitness value. More specifically:
51/// probability of selecting chromosome `C` from population `P` is `fitness(C)` / `sum_of_fitness_in_whole_population`.
52pub struct RouletteWheel<R: Rng> {
53    rng: R,
54}
55
56impl RouletteWheel<ThreadRng> {
57    /// Returns new instance of [RouletteWheel] selection operator with default RNG
58    pub fn new() -> Self {
59        RouletteWheel::with_rng(rand::thread_rng())
60    }
61}
62
63impl<R: Rng> RouletteWheel<R> {
64    /// Returns new instance of [RouletteWheel] selection operator with custom RNG
65    pub fn with_rng(rng: R) -> Self {
66        RouletteWheel { rng }
67    }
68}
69
70// FIXME: It will return empty vector if total_fitness == 0
71// WORKING CHANGE: crt >= threshold instead of crt_sum > threshold
72// But this should be resolved some other way
73impl<IndividualT: IndividualTrait, R: Rng> SelectionOperator<IndividualT> for RouletteWheel<R>
74where
75    IndividualT::FitnessValueT: NumAssignOps + Sum<IndividualT::FitnessValueT> + PartialOrd + Copy,
76    Standard: Distribution<IndividualT::FitnessValueT>,
77{
78    /// Returns a vector of references to individuals selected to mating pool
79    ///
80    /// **Note 1**: This selection operator requires positive fitness function. No runtime checks are performed
81    /// to assert this invariant. If aggregated fitness in whole population is <= 0 the behaviour is undefined,
82    /// implementation dependent and might change without any notice.
83    ///
84    /// **Note 2**: The same individual can be selected multiple times.
85    ///
86    /// Individuals are selected with probability proportional to their fitness value. More specifically:
87    /// probability of selecting chromosome `C` from population `P` is `fitness(C)` / `sum_of_fitness_in_whole_population`.
88    ///
89    /// ### Arguments
90    ///
91    /// * `metadata` - [crate::ga::GAMetadata] information on current stage of the algorithm (iteration, elapsed time, etc.)
92    /// * `population` - individuals to choose mating pool from
93    /// * `count` - target number of individuals in mating pool
94    fn apply<'a>(
95        &mut self,
96        _metadata: &GAMetadata,
97        population: &'a [IndividualT],
98        count: usize,
99    ) -> Vec<&'a IndividualT> {
100        let total_fitness: IndividualT::FitnessValueT = population.iter().map(|indiv| indiv.fitness()).sum();
101
102        let mut selected: Vec<&IndividualT> = Vec::with_capacity(count);
103
104        for _ in 0..count {
105            let threshold = total_fitness * self.rng.gen::<IndividualT::FitnessValueT>();
106
107            let mut crt_sum: IndividualT::FitnessValueT = IndividualT::FitnessValueT::zero();
108            for indiv in population {
109                crt_sum += indiv.fitness();
110
111                if crt_sum >= threshold {
112                    selected.push(indiv);
113                    break;
114                }
115            }
116        }
117        selected
118    }
119}
120
121/// ### Random selection operator
122///
123/// This struct implements [SelectionOperator] trait and can be used with GA.
124///
125/// Individuals are selected with uniform probability.
126///
127/// **Note**: The same individual *can not* be selected mutiple times.
128pub struct Random<R: Rng> {
129    rng: R,
130}
131
132impl Random<ThreadRng> {
133    /// Returns new instance of [Random] selection operator with default RNG
134    pub fn new() -> Self {
135        Random::with_rng(rand::thread_rng())
136    }
137}
138
139impl<R: Rng> Random<R> {
140    /// Returns new instance of [Random] selection operator with custom RNG
141    pub fn with_rng(rng: R) -> Self {
142        Random { rng }
143    }
144}
145
146impl<IndividualT: IndividualTrait, R: Rng> SelectionOperator<IndividualT> for Random<R> {
147    /// Returns a vector of references to individuals selected to mating pool.
148    ///
149    /// Individuals are selected with uniform probability.
150    ///
151    /// **Note**: The same individual *can not* be selected multiple times.
152    ///
153    /// ### Arguments
154    ///
155    /// * `metadata` - [crate::ga::GAMetadata] information on current stage of the algorithm (iteration, elapsed time, etc.)
156    /// * `population` - individuals to choose mating pool from
157    /// * `count` - target number of individuals in mating pool
158    fn apply<'a>(
159        &mut self,
160        _metadata: &GAMetadata,
161        population: &'a [IndividualT],
162        count: usize,
163    ) -> Vec<&'a IndividualT> {
164        // We must use index API, as we want to return vector of references, not vector of actual items
165        let indices = rand::seq::index::sample(&mut self.rng, population.len(), count);
166        let mut selected: Vec<&IndividualT> = Vec::with_capacity(count);
167
168        for i in indices {
169            selected.push(&population[i]);
170        }
171        selected
172    }
173}
174
175/// ### Rank selection operator
176///
177/// This struct implements [SelectionOperator] trait and can be used with GA.
178///
179/// Individuals are selected by randomly (uniform distribution) choosing pairs of individuals - better
180/// rated individual from selected pair goes to mating pool. In case of equal fitness - only one goes to mating pool.
181///
182/// **Note**: The same individual *can* be selected multiple times.
183pub struct Rank<R: Rng = ThreadRng> {
184    rng: R,
185}
186
187impl Rank<ThreadRng> {
188    /// Returns new instance of [Rank] selection operator with default RNG
189    pub fn new() -> Self {
190        Rank::with_rng(rand::thread_rng())
191    }
192}
193
194impl<R: Rng> Rank<R> {
195    /// Returns new instance of [Rank] selection operator with custom RNG
196    pub fn with_rng(rng: R) -> Self {
197        Rank { rng }
198    }
199}
200
201impl<IndividualT: IndividualTrait, R: Rng> SelectionOperator<IndividualT> for Rank<R>
202where
203    IndividualT::FitnessValueT: PartialOrd,
204{
205    /// Returns a vector of references to individuals selected to mating pool.
206    ///
207    /// Individuals are selected by randomly (uniform distribution) choosing pairs of individuals - better
208    /// rated individual from selected pair goes to mating pool. In case of equal fitness - only one goes to mating pool.
209    ///
210    /// **Note**: The same individual *can* be selected multiple times.
211    ///
212    /// ### Arguments
213    ///
214    /// * `metadata` - [crate::ga::GAMetadata] information on current stage of the algorithm (iteration, elapsed time, etc.)
215    /// * `population` - individuals to choose mating pool from
216    /// * `count` - target number of individuals in mating pool
217    fn apply<'a>(
218        &mut self,
219        _metadata: &GAMetadata,
220        population: &'a [IndividualT],
221        count: usize,
222    ) -> Vec<&'a IndividualT> {
223        let mut selected: Vec<&IndividualT> = Vec::with_capacity(count);
224
225        let population_len = population.len();
226        for _ in 0..count {
227            // TODO: Consider creating two random index permutations and then iterating over them
228            // instead of N times using random.
229            let p1 = &population[self.rng.gen_range(0..population_len)];
230            let p2 = &population[self.rng.gen_range(0..population_len)];
231
232            selected.push(if p1.fitness() >= p2.fitness() { p1 } else { p2 })
233        }
234
235        selected
236    }
237}
238
239/// ### RankR selection operator
240///
241/// This struct implements [SelectionOperator] trait and can be used with GA
242///
243/// Individuals are selected in following process:
244///
245/// 1. Select two random individuals (uniform distribution)
246/// 2. Select random number `R` from [0, 1] (uniform distribution)
247/// 3. If `R` < `r` then select first individual, second otherwise
248/// 4. Repeat 1-3 necessary number of times to create mating pool of demanded size
249///
250/// **Note**: The same individual can be selected multiple times
251pub struct RankR<R: Rng> {
252    r: f64,
253    rng: R,
254}
255
256impl RankR<ThreadRng> {
257    /// Returns new instance of [RankR] selection operator with default RNG
258    ///
259    /// ### Arguments
260    ///
261    /// * `r` - threshold in range [0, 1]; see [RankR] description for explaination
262    pub fn new(r: f64) -> Self {
263        RankR::with_rng(r, rand::thread_rng())
264    }
265}
266
267impl<R: Rng> RankR<R> {
268    /// Returns new instance of [RankR] selection operator with custom RNG
269    ///
270    /// ### Arguments
271    ///
272    /// * `r` - threshold in range [0, 1]; see [RankR] description for details
273    /// * `rng` - custom random number generator
274    pub fn with_rng(r: f64, rng: R) -> Self {
275        assert!((0.0..=1.0).contains(&r));
276        RankR { r, rng }
277    }
278}
279
280impl<IndividualT: IndividualTrait, R: Rng> SelectionOperator<IndividualT> for RankR<R> {
281    /// Returns a vector of references to individuals selected to mating pool.
282    ///
283    /// Individuals are selected in following process:
284    ///
285    /// 1. Select two random individuals (uniform distribution)
286    /// 2. Select random number `R` from [0, 1] (uniform distribution)
287    /// 3. If `R` < `r` then select first individual, second otherwise
288    /// 4. Repeat 1-3 necessary number of times to create mating pool of demanded size
289    ///
290    /// **Note**: The same individual can be selected multiple times
291    ///
292    /// ### Arguments
293    ///
294    /// * `metadata` - [crate::ga::GAMetadata] information on current stage of the algorithm (iteration, elapsed time, etc.)
295    /// * `population` - individuals to choose mating pool from
296    /// * `count` - target number of individuals in mating pool
297    fn apply<'a>(
298        &mut self,
299        _metadata: &GAMetadata,
300        population: &'a [IndividualT],
301        count: usize,
302    ) -> Vec<&'a IndividualT> {
303        let mut selected: Vec<&IndividualT> = Vec::with_capacity(count);
304        let population_len = population.len();
305        let distribution_for_ind = rand::distributions::Uniform::from(0..population_len);
306        let distribution_for_rand = rand::distributions::Uniform::from(0.0..1.0);
307
308        for _ in 0..count {
309            // TODO: Consider creating two random index permutations and then iterating over them
310            // instead of N times using random.
311            let p1 = &population[self.rng.sample(distribution_for_ind)];
312            let p2 = &population[self.rng.sample(distribution_for_ind)];
313
314            selected.push(if self.rng.sample(distribution_for_rand) < self.r {
315                p1
316            } else {
317                p2
318            })
319        }
320        selected
321    }
322}
323
324/// ### Tournament selection operator
325///
326/// This struct implements [SelectionOperator] and can be used with GA
327///
328/// Individuals are selected by conducting given number of tournaments with single winner:
329///
330/// *Note*: The same individual can be selected multiple times
331///
332/// 1. Select `ceil(size_factor * population_size)` distinct, random individuals
333/// 2. Select one with the highest fitness
334/// 3. Repeat 1-2 number of times necessary to fill mating pool
335pub struct Tournament<R: Rng = ThreadRng> {
336    size_factor: f64,
337    rng: R,
338}
339
340impl Tournament<ThreadRng> {
341    /// Returns new instance of [Tournament] selection operator with default RNG
342    ///
343    /// ### Arguments
344    ///
345    /// * `size_factor` - part of population to take part in tournament for choosing single individual; must be in range [0, 1]
346    pub fn new(size_factor: f64) -> Self {
347        Tournament::with_rng(size_factor, rand::thread_rng())
348    }
349}
350
351impl<R: Rng> Tournament<R> {
352    /// Returns new instance of [Tournament] selection operator with custom RNG
353    ///
354    /// ### Arguments
355    ///
356    /// * `size_factor` - part of population to take part in tournament for choosing single individual; must be in range [0, 1]
357    pub fn with_rng(size_factor: f64, rng: R) -> Self {
358        assert!((0.0..=1.0).contains(&size_factor));
359        Tournament { size_factor, rng }
360    }
361}
362
363impl<IndividualT: IndividualTrait, R: Rng> SelectionOperator<IndividualT> for Tournament<R> {
364    /// Returns a vector of references to individuals selected to mating pool
365    ///
366    /// Individuals are selected by conducting given number of tournaments with single winner:
367    ///
368    /// 1. Select `ceil(size_factor * population_size)` distinct, random individuals
369    /// 2. Select one with the highest fitness
370    /// 3. Repeat 1-2 number of times necessary to fill mating pool
371    ///
372    /// *Note*: The same individual can be selected multiple times
373    ///
374    /// ### Arguments
375    ///
376    /// * `metadata` - [crate::ga::GAMetadata] information on current stage of the algorithm (iteration, elapsed time, etc.)
377    /// * `population` - individuals to choose mating pool from
378    /// * `count` - target number of individuals in mating pool
379    fn apply<'a>(
380        &mut self,
381        _metadata: &GAMetadata,
382        population: &'a [IndividualT],
383        count: usize,
384    ) -> Vec<&'a IndividualT> {
385        let tournament_size = (population.len() as f64 * self.size_factor) as usize;
386
387        assert!(tournament_size > 0);
388
389        let mut selected: Vec<&IndividualT> = Vec::with_capacity(count);
390
391        for _ in 0..count {
392            let tournament_indices =
393                rand::seq::index::sample(&mut self.rng, population.len(), tournament_size);
394            // FIXME: Check wheter the tournament_indices is empty or handle option below.
395            let best_idv = tournament_indices
396                .into_iter()
397                .map(|i| &population[i])
398                .max()
399                .unwrap();
400            selected.push(best_idv);
401        }
402
403        selected
404    }
405}
406
407/// ### Stochastic universal sampling selection operator
408///
409/// This struct implements [SelectionOperator] trati and can be used with GA
410///
411/// **Note**: This selection operator requires positive fitenss function. No runtime checks
412/// are performed to assert this invariant. If aggregated fitenss in whole population is <= the
413/// behaviour is undefined, implementation dependent and might change without any notice.
414///
415/// **Note**: The same individual can be selected multiple times
416///
417/// Individuals are selected in process similar to described below:
418///
419/// 1. Individuals are laid on real axis, in order they appear in population,
420/// to interval \[0, `total_fitness`\]; each individual is represented by sub
421/// interval of lengths equal to its fitness
422/// 2. `count` virtual pointers are placed along interval \[0, `total_fitness`\];
423/// distance between pointers `d` is `total_fitness` / `mating_pool_size`;
424/// first pointer position is selected randomly from interval \[0, `d`\]
425/// 3. Iterate over the pointers and select the individuals they point to
426///
427/// See the source code for implemenation details
428pub struct StochasticUniversalSampling<R: Rng> {
429    rng: R,
430}
431
432impl StochasticUniversalSampling<ThreadRng> {
433    /// Returns new instance of [StochasticUniversalSampling] selection operator with default RNG
434    pub fn new() -> Self {
435        Self::with_rng(rand::thread_rng())
436    }
437}
438
439impl<R: Rng> StochasticUniversalSampling<R> {
440    /// Returns new instance of [StochasticUniversalSampling] selection operator with custom RNG
441    pub fn with_rng(rng: R) -> Self {
442        Self { rng }
443    }
444}
445
446// FIXME: Panics then total_fitness == 0
447// Should this be expected or do we want to handle this?
448impl<IndividualT: IndividualTrait<FitnessValueT = f64>, R: Rng> SelectionOperator<IndividualT>
449    for StochasticUniversalSampling<R>
450{
451    /// Returns a vector of references to individuals selected to mating pool
452    ///
453    /// **Note**: This selection operator requires positive fitenss function. No runtime checks
454    /// are performed to assert this invariant. If aggregated fitenss in whole population is <= the
455    /// behaviour is undefined, implementation dependent and might change without any notice.
456    ///
457    /// **Note**: The same individual can be selected multiple times
458    ///
459    /// Individuals are selected in process similar to described below:
460    ///
461    /// 1. Individuals are laid on real axis, in order they appear in population,
462    /// to interval \[0, `total_fitness`\]; each individual is represented by sub
463    /// interval of lengths equal to its fitness
464    /// 2. `count` virtual pointers are placed along interval \[0, `total_fitness`\];
465    /// distance between pointers `d` is `total_fitness` / `mating_pool_size`;
466    /// first pointer position is selected randomly from interval \[0, `d`\]
467    /// 3. Iterate over the pointers and select the individuals they point to
468    ///
469    /// See the source code for implemenation details
470    ///
471    /// ### Arguments
472    ///
473    /// * `metadata` - [crate::ga::GAMetadata] information on current stage of the algorithm (iteration, elapsed time, etc.)
474    /// * `population` - individuals to choose mating pool from
475    /// * `count` - target number of individuals in mating pool
476    fn apply<'a>(
477        &mut self,
478        _metadata: &GAMetadata,
479        population: &'a [IndividualT],
480        count: usize,
481    ) -> Vec<&'a IndividualT> {
482        let total_fitness: f64 = population.iter().map(|indiv| indiv.fitness()).sum();
483
484        let mut selected: Vec<&IndividualT> = Vec::with_capacity(count);
485
486        let distance_between_pointers = total_fitness / (count as f64);
487
488        assert!(distance_between_pointers > 0.0);
489
490        let mut pointer_pos = self.rng.gen_range(0.0..=distance_between_pointers);
491
492        let mut curr_sum = 0.0;
493        for idv in population {
494            curr_sum += idv.fitness();
495
496            while curr_sum >= pointer_pos {
497                selected.push(idv);
498                pointer_pos += distance_between_pointers;
499            }
500        }
501
502        assert_eq!(selected.len(), count);
503
504        selected
505    }
506}
507
508/// ### Boltzmann selection operator
509///
510/// This struct implements [SelectionOperator] trait and can be used with GA
511///
512pub struct Boltzmann<R: Rng = ThreadRng> {
513    alpha: f64,
514    max_gen_count: usize, // FIXME: This should be removed after operators are passed whole algorithm state & config
515    temp_0: f64,
516    elitism: bool, // FIXME: Make use of elitism strategy
517    rng: R,
518}
519
520impl Boltzmann<ThreadRng> {
521    /// Returns new instance of [Boltzmann] selection operator with default RNG
522    ///
523    /// ### Arguments
524    ///
525    /// * `alpha` - prameter that controlls temperature scaling; must be in [0, 1] range
526    /// * `temp_0` - initial temperature for the operator
527    /// * `max_gen_count` - maximum number of generations GA can run; this param will be removed in future version of the library
528    /// * `elitism` - set to true to ensure that best individuals end in mating pool no matter operator results; **not supported yet**
529    pub fn new(alpha: f64, temp_0: f64, max_gen_count: usize, elitism: bool) -> Self {
530        Self::with_rng(alpha, temp_0, max_gen_count, elitism, rand::thread_rng())
531    }
532}
533
534impl<R: Rng> Boltzmann<R> {
535    /// Returns new instance of [Boltzmann] selection operator with default RNG
536    ///
537    /// ### Arguments
538    ///
539    /// * `alpha` - prameter that controlls temperature scaling; must be in [0, 1] range
540    /// * `temp_0` - initial temperature for the operator
541    /// * `max_gen_count` - maximum number of generations GA can run; this param will be removed in future version of the library
542    /// * `elitism` - set to true to ensure that best individuals end in mating pool no matter operator results; **not supported yet**
543    /// * `rng` - custom random number generator
544    pub fn with_rng(alpha: f64, temp_0: f64, max_gen_count: usize, elitism: bool, rng: R) -> Self {
545        assert!(
546            (0.0..=1.0).contains(&alpha),
547            "Alpha parameter must be a value from [0, 1] interval"
548        );
549        assert!(
550            (5.0..=100.0).contains(&temp_0),
551            "Starting temperature must be a value from [5, 100] interval"
552        );
553
554        Boltzmann {
555            alpha,
556            max_gen_count,
557            temp_0,
558            elitism,
559            rng,
560        }
561    }
562}
563
564impl<IndividualT, R> SelectionOperator<IndividualT> for Boltzmann<R>
565where
566    IndividualT: IndividualTrait<FitnessValueT = f64>,
567    IndividualT::ChromosomeT: Index<usize, Output = IndividualT::FitnessValueT>,
568    R: Rng,
569{
570    fn apply<'a>(
571        &mut self,
572        metadata: &GAMetadata,
573        population: &'a [IndividualT],
574        count: usize,
575    ) -> Vec<&'a IndividualT> {
576        let mut selected: Vec<&IndividualT> = Vec::with_capacity(count);
577        let mut weights: Vec<f64> = Vec::with_capacity(count);
578
579        let k = 1.0 + 100.0 * (metadata.generation as f64) / (self.max_gen_count as f64);
580        let temp = self.temp_0 * (1.0 - self.alpha).powf(k);
581
582        for idv in population {
583            weights.push((-idv.fitness() / temp).exp())
584        }
585
586        let Ok(indices) = rand::seq::index::sample_weighted(&mut self.rng, population.len(), |i| weights[i], count) else {
587			panic!("Some error occured while generating indices. This is most likely an library implementation error. Please file an issue: https://github.com/kkafar/evolutionary-algorithms");
588		};
589
590        for i in indices {
591            selected.push(&population[i]);
592        }
593
594        selected
595    }
596}
597
598#[cfg(test)]
599mod test {
600    use super::{Boltzmann, RankR, Tournament};
601
602    #[test]
603    #[should_panic]
604    fn boltzman_panics_on_too_big_alpha() {
605        let _operator = Boltzmann::new(5.0, 10.0, 300, false);
606    }
607
608    #[test]
609    #[should_panic]
610    fn boltzman_panics_on_too_small_alpha() {
611        let _operator = Boltzmann::new(-0.1, 10.0, 300, false);
612    }
613
614    #[test]
615    #[should_panic]
616    fn boltzman_panics_on_too_low_temp() {
617        let _operator = Boltzmann::new(0.5, 4.0, 300, false);
618    }
619
620    #[test]
621    #[should_panic]
622    fn boltzman_panics_on_too_high_temp() {
623        let _operator = Boltzmann::new(0.5, 400.0, 300, false);
624    }
625
626    #[test]
627    #[should_panic]
628    fn tournament_panics_on_wrong_size_factor() {
629        let _operator = Tournament::new(2.0);
630    }
631
632    #[test]
633    #[should_panic]
634    fn rankr_panics_on_wrong_r() {
635        let _operator = RankR::new(1.1);
636    }
637}