From 49d82c7ba9f4df79e1133a4971b230f63a908b4a Mon Sep 17 00:00:00 2001 From: Rutherther Date: Sat, 18 Oct 2025 21:06:09 +0200 Subject: [PATCH] feat: add tournament selection --- env/src/comparison/mod.rs | 8 ++++ env/src/main.rs | 1 + env/src/selection.rs | 90 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 99 insertions(+) create mode 100644 env/src/selection.rs diff --git a/env/src/comparison/mod.rs b/env/src/comparison/mod.rs index 28f241581d09caaded070c9809187f6a318f63f2..deaca8f469caa181a9a4a2e18b95fa7f52b208b7 100644 --- a/env/src/comparison/mod.rs +++ b/env/src/comparison/mod.rs @@ -1,5 +1,13 @@ pub trait BetterThanOperator { fn better_than(self: &Self, a: &T, b: &T) -> bool; + + fn ordering(&self, a: &T, b: &T) -> std::cmp::Ordering { + if self.better_than(a, b) { + std::cmp::Ordering::Less + } else { + std::cmp::Ordering::Greater + } + } } pub struct MinimizingOperator; diff --git a/env/src/main.rs b/env/src/main.rs index 09b95fcacdf4a39a36c980236230a3954e05548d..ece1053c649b82f126ae8011e45bd94adf4827c2 100644 --- a/env/src/main.rs +++ b/env/src/main.rs @@ -1,6 +1,7 @@ pub mod fitness; pub mod crossover; pub mod bounded; +pub mod selection; pub mod initializer; pub mod terminating; pub mod perturbation; diff --git a/env/src/selection.rs b/env/src/selection.rs new file mode 100644 index 0000000000000000000000000000000000000000..54963a6a8958d04562bcdd9c173adcdc3481b3e2 --- /dev/null +++ b/env/src/selection.rs @@ -0,0 +1,90 @@ +// pub struct EvaluatedChromosome { +// chromosome: TInput, +// evaluation: TResult, +// } + +use rand::{Rng, RngCore}; + +use crate::comparison::BetterThanOperator; + +pub trait Selection { + fn select(&mut self, count: usize, evaluations: &Vec, better_than: &dyn BetterThanOperator) -> impl Iterator; +} + +pub struct TournamentSelection { + rng: Box, + p: f64, + k: usize +} + +impl TournamentSelection { + pub fn new(k: usize, p: f64) -> Self { + assert!(0.0 <= p && p <= 1.0); + assert!(k > 0); + + Self { + rng: Box::new(rand::rng()), + p, + k + } + } + + fn tournament(&mut self, idxs: &mut Vec, evaluations: &Vec, better_than: &dyn BetterThanOperator) -> usize { + idxs.sort_by(|&i, &j| better_than.ordering(&evaluations[i], &evaluations[j])); + + let mut p_selector = self.rng.random_range(0.0..=1.0f64); + let p = self.p; + let k = self.k; + + let mut selected = idxs[k - 1]; + // let's say p = 0.7 + // the best has probability 0.7 of being selected + // if the best is not selected, the second has 0.7 probability of being selected... (that's 0.7 * 0.3 without conditions) + // and so on. The last element has the remaining probability. + for i in 0..k-1 { + if p_selector <= p { + selected = i; + break; + } + + p_selector -= p; + // 'Expand' the rest to '100%' again + p_selector /= 1.0 - p; + } + + selected + } +} + +impl Selection for TournamentSelection { + fn select(&mut self, count: usize, evaluations: &Vec, better_than: &dyn BetterThanOperator) -> impl Iterator { + // 1. Rank + // fn rank(l: &Vec) -> Vec { + // let mut indices = (0..l.len()).collect::>(); + // let mut ranks = vec![0; l.len()]; + + // // argsort... + // indices.sort_by_key(|&i| &l[i]); + + // for (rank, idx) in indices.into_iter().enumerate() { + // ranks[idx] = rank; + // } + + // ranks + // } + + // let ranks = rank(evaluations); + + // 2. Let's choose k random 'count' times + // let mut already_selected = vec![false; evaluations.len()]; + + let mut k_selected_idxs = vec![0; self.k]; + (0..count).map(move |_| { + for selected_idx in k_selected_idxs.iter_mut() { + *selected_idx = self.rng.random_range(0..evaluations.len()); + } + + self.tournament(&mut k_selected_idxs, evaluations, better_than) + }) + } +}