~ruther/ctu-fee-eoa

49d82c7ba9f4df79e1133a4971b230f63a908b4a — Rutherther a month ago db3fceb
feat: add tournament selection
3 files changed, 99 insertions(+), 0 deletions(-)

M env/src/comparison/mod.rs
M env/src/main.rs
A env/src/selection.rs
M env/src/comparison/mod.rs => env/src/comparison/mod.rs +8 -0
@@ 1,5 1,13 @@
pub trait BetterThanOperator<T> {
    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;

M env/src/main.rs => env/src/main.rs +1 -0
@@ 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;

A env/src/selection.rs => env/src/selection.rs +90 -0
@@ 0,0 1,90 @@
// pub struct EvaluatedChromosome<TInput, TResult> {
//     chromosome: TInput,
//     evaluation: TResult,
// }

use rand::{Rng, RngCore};

use crate::comparison::BetterThanOperator;

pub trait Selection<T> {
    fn select(&mut self, count: usize, evaluations: &Vec<T>, better_than: &dyn BetterThanOperator<T>) -> impl Iterator<Item = usize>;
}

pub struct TournamentSelection {
    rng: Box<dyn RngCore>,
    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<T: PartialOrd>(&mut self, idxs: &mut Vec<usize>, evaluations: &Vec<T>, better_than: &dyn BetterThanOperator<T>) -> 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<T: Ord> Selection<T> for TournamentSelection {
    fn select(&mut self, count: usize, evaluations: &Vec<T>, better_than: &dyn BetterThanOperator<T>) -> impl Iterator<Item = usize> {
        // 1. Rank
        // fn rank<T: Ord>(l: &Vec<T>) -> Vec<usize> {
        //     let mut indices = (0..l.len()).collect::<Vec<_>>();
        //     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)
        })
    }
}