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}