use std::{iter::Sum, ops::Index};
use num_traits::{identities::Zero, NumAssignOps};
use rand::{distributions::Standard, prelude::Distribution, rngs::ThreadRng, Rng};
use crate::ga::{individual::IndividualTrait, GAMetadata};
pub trait SelectionOperator<IndividualT: IndividualTrait> {
fn apply<'a>(
&mut self,
metadata: &GAMetadata,
population: &'a [IndividualT],
count: usize,
) -> Vec<&'a IndividualT>;
}
pub struct RouletteWheel<R: Rng> {
rng: R,
}
impl RouletteWheel<ThreadRng> {
pub fn new() -> Self {
RouletteWheel::with_rng(rand::thread_rng())
}
}
impl<R: Rng> RouletteWheel<R> {
pub fn with_rng(rng: R) -> Self {
RouletteWheel { rng }
}
}
impl<IndividualT: IndividualTrait, R: Rng> SelectionOperator<IndividualT> for RouletteWheel<R>
where
IndividualT::FitnessValueT: NumAssignOps + Sum<IndividualT::FitnessValueT> + PartialOrd + Copy,
Standard: Distribution<IndividualT::FitnessValueT>,
{
fn apply<'a>(
&mut self,
_metadata: &GAMetadata,
population: &'a [IndividualT],
count: usize,
) -> Vec<&'a IndividualT> {
let total_fitness: IndividualT::FitnessValueT = population.iter().map(|indiv| indiv.fitness()).sum();
let mut selected: Vec<&IndividualT> = Vec::with_capacity(count);
for _ in 0..count {
let threshold = total_fitness * self.rng.gen::<IndividualT::FitnessValueT>();
let mut crt_sum: IndividualT::FitnessValueT = IndividualT::FitnessValueT::zero();
for indiv in population {
crt_sum += indiv.fitness();
if crt_sum >= threshold {
selected.push(indiv);
break;
}
}
}
selected
}
}
pub struct Random<R: Rng> {
rng: R,
}
impl Random<ThreadRng> {
pub fn new() -> Self {
Random::with_rng(rand::thread_rng())
}
}
impl<R: Rng> Random<R> {
pub fn with_rng(rng: R) -> Self {
Random { rng }
}
}
impl<IndividualT: IndividualTrait, R: Rng> SelectionOperator<IndividualT> for Random<R> {
fn apply<'a>(
&mut self,
_metadata: &GAMetadata,
population: &'a [IndividualT],
count: usize,
) -> Vec<&'a IndividualT> {
let indices = rand::seq::index::sample(&mut self.rng, population.len(), count);
let mut selected: Vec<&IndividualT> = Vec::with_capacity(count);
for i in indices {
selected.push(&population[i]);
}
selected
}
}
pub struct Rank<R: Rng = ThreadRng> {
rng: R,
}
impl Rank<ThreadRng> {
pub fn new() -> Self {
Rank::with_rng(rand::thread_rng())
}
}
impl<R: Rng> Rank<R> {
pub fn with_rng(rng: R) -> Self {
Rank { rng }
}
}
impl<IndividualT: IndividualTrait, R: Rng> SelectionOperator<IndividualT> for Rank<R>
where
IndividualT::FitnessValueT: PartialOrd,
{
fn apply<'a>(
&mut self,
_metadata: &GAMetadata,
population: &'a [IndividualT],
count: usize,
) -> Vec<&'a IndividualT> {
let mut selected: Vec<&IndividualT> = Vec::with_capacity(count);
let population_len = population.len();
for _ in 0..count {
let p1 = &population[self.rng.gen_range(0..population_len)];
let p2 = &population[self.rng.gen_range(0..population_len)];
selected.push(if p1.fitness() >= p2.fitness() { p1 } else { p2 })
}
selected
}
}
pub struct RankR<R: Rng> {
r: f64,
rng: R,
}
impl RankR<ThreadRng> {
pub fn new(r: f64) -> Self {
RankR::with_rng(r, rand::thread_rng())
}
}
impl<R: Rng> RankR<R> {
pub fn with_rng(r: f64, rng: R) -> Self {
assert!((0.0..=1.0).contains(&r));
RankR { r, rng }
}
}
impl<IndividualT: IndividualTrait, R: Rng> SelectionOperator<IndividualT> for RankR<R> {
fn apply<'a>(
&mut self,
_metadata: &GAMetadata,
population: &'a [IndividualT],
count: usize,
) -> Vec<&'a IndividualT> {
let mut selected: Vec<&IndividualT> = Vec::with_capacity(count);
let population_len = population.len();
let distribution_for_ind = rand::distributions::Uniform::from(0..population_len);
let distribution_for_rand = rand::distributions::Uniform::from(0.0..1.0);
for _ in 0..count {
let p1 = &population[self.rng.sample(distribution_for_ind)];
let p2 = &population[self.rng.sample(distribution_for_ind)];
selected.push(if self.rng.sample(distribution_for_rand) < self.r {
p1
} else {
p2
})
}
selected
}
}
pub struct Tournament<R: Rng = ThreadRng> {
size_factor: f64,
rng: R,
}
impl Tournament<ThreadRng> {
pub fn new(size_factor: f64) -> Self {
Tournament::with_rng(size_factor, rand::thread_rng())
}
}
impl<R: Rng> Tournament<R> {
pub fn with_rng(size_factor: f64, rng: R) -> Self {
assert!((0.0..=1.0).contains(&size_factor));
Tournament { size_factor, rng }
}
}
impl<IndividualT: IndividualTrait, R: Rng> SelectionOperator<IndividualT> for Tournament<R> {
fn apply<'a>(
&mut self,
_metadata: &GAMetadata,
population: &'a [IndividualT],
count: usize,
) -> Vec<&'a IndividualT> {
let tournament_size = (population.len() as f64 * self.size_factor) as usize;
assert!(tournament_size > 0);
let mut selected: Vec<&IndividualT> = Vec::with_capacity(count);
for _ in 0..count {
let tournament_indices =
rand::seq::index::sample(&mut self.rng, population.len(), tournament_size);
let best_idv = tournament_indices
.into_iter()
.map(|i| &population[i])
.max()
.unwrap();
selected.push(best_idv);
}
selected
}
}
pub struct StochasticUniversalSampling<R: Rng> {
rng: R,
}
impl StochasticUniversalSampling<ThreadRng> {
pub fn new() -> Self {
Self::with_rng(rand::thread_rng())
}
}
impl<R: Rng> StochasticUniversalSampling<R> {
pub fn with_rng(rng: R) -> Self {
Self { rng }
}
}
impl<IndividualT: IndividualTrait<FitnessValueT = f64>, R: Rng> SelectionOperator<IndividualT>
for StochasticUniversalSampling<R>
{
fn apply<'a>(
&mut self,
_metadata: &GAMetadata,
population: &'a [IndividualT],
count: usize,
) -> Vec<&'a IndividualT> {
let total_fitness: f64 = population.iter().map(|indiv| indiv.fitness()).sum();
let mut selected: Vec<&IndividualT> = Vec::with_capacity(count);
let distance_between_pointers = total_fitness / (count as f64);
assert!(distance_between_pointers > 0.0);
let mut pointer_pos = self.rng.gen_range(0.0..=distance_between_pointers);
let mut curr_sum = 0.0;
for idv in population {
curr_sum += idv.fitness();
while curr_sum >= pointer_pos {
selected.push(idv);
pointer_pos += distance_between_pointers;
}
}
assert_eq!(selected.len(), count);
selected
}
}
pub struct Boltzmann<R: Rng = ThreadRng> {
alpha: f64,
max_gen_count: usize, temp_0: f64,
elitism: bool, rng: R,
}
impl Boltzmann<ThreadRng> {
pub fn new(alpha: f64, temp_0: f64, max_gen_count: usize, elitism: bool) -> Self {
Self::with_rng(alpha, temp_0, max_gen_count, elitism, rand::thread_rng())
}
}
impl<R: Rng> Boltzmann<R> {
pub fn with_rng(alpha: f64, temp_0: f64, max_gen_count: usize, elitism: bool, rng: R) -> Self {
assert!(
(0.0..=1.0).contains(&alpha),
"Alpha parameter must be a value from [0, 1] interval"
);
assert!(
(5.0..=100.0).contains(&temp_0),
"Starting temperature must be a value from [5, 100] interval"
);
Boltzmann {
alpha,
max_gen_count,
temp_0,
elitism,
rng,
}
}
}
impl<IndividualT, R> SelectionOperator<IndividualT> for Boltzmann<R>
where
IndividualT: IndividualTrait<FitnessValueT = f64>,
IndividualT::ChromosomeT: Index<usize, Output = IndividualT::FitnessValueT>,
R: Rng,
{
fn apply<'a>(
&mut self,
metadata: &GAMetadata,
population: &'a [IndividualT],
count: usize,
) -> Vec<&'a IndividualT> {
let mut selected: Vec<&IndividualT> = Vec::with_capacity(count);
let mut weights: Vec<f64> = Vec::with_capacity(count);
let k = 1.0 + 100.0 * (metadata.generation as f64) / (self.max_gen_count as f64);
let temp = self.temp_0 * (1.0 - self.alpha).powf(k);
for idv in population {
weights.push((-idv.fitness() / temp).exp())
}
let Ok(indices) = rand::seq::index::sample_weighted(&mut self.rng, population.len(), |i| weights[i], count) else {
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");
};
for i in indices {
selected.push(&population[i]);
}
selected
}
}
#[cfg(test)]
mod test {
use super::{Boltzmann, RankR, Tournament};
#[test]
#[should_panic]
fn boltzman_panics_on_too_big_alpha() {
let _operator = Boltzmann::new(5.0, 10.0, 300, false);
}
#[test]
#[should_panic]
fn boltzman_panics_on_too_small_alpha() {
let _operator = Boltzmann::new(-0.1, 10.0, 300, false);
}
#[test]
#[should_panic]
fn boltzman_panics_on_too_low_temp() {
let _operator = Boltzmann::new(0.5, 4.0, 300, false);
}
#[test]
#[should_panic]
fn boltzman_panics_on_too_high_temp() {
let _operator = Boltzmann::new(0.5, 400.0, 300, false);
}
#[test]
#[should_panic]
fn tournament_panics_on_wrong_size_factor() {
let _operator = Tournament::new(2.0);
}
#[test]
#[should_panic]
fn rankr_panics_on_wrong_r() {
let _operator = RankR::new(1.1);
}
}