use super::*;
use rand::distributions::Uniform;
use rayon::prelude::*;
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum SelectionStrategy {
Tournament(usize ),
RouletteWheel,
Boltzmann(f64 , f64 ),
Rank,
Linear(f64 ),
Elitist(f64 ),
}
impl SelectionStrategy {
pub fn create_selector<I: Individual, R: Rng>(&self) -> Box<dyn SelectionMechanism<I, R>> {
match self {
SelectionStrategy::Tournament(size) => Box::new(TournamentSelection::new(*size)),
SelectionStrategy::RouletteWheel => Box::new(RouletteWheelSelection::new()),
SelectionStrategy::Rank => Box::new(RankSelection::new()),
SelectionStrategy::Linear(bias) => Box::new(LinearSelection::new(*bias)),
SelectionStrategy::Elitist(ratio) => Box::new(ElitistSelection::new(*ratio)),
SelectionStrategy::Boltzmann(temperature, cooling_rate) => {
Box::new(BoltzmannSelection::new(*temperature, *cooling_rate))
}
}
}
}
pub trait SelectionMechanism<I, R>: Sync
where
I: Individual,
R: Rng,
{
fn prepare(&mut self, population: &Population<I>);
fn select<'a>(&self, population: &'a Population<I>, rng: &mut R) -> (usize, &'a I);
#[inline]
fn select_distinct<'a>(
&self,
population: &'a Population<I>,
rng: &mut R,
max_tries: usize,
excluded: &I,
) -> Option<(usize, &'a I)>
where
I: Individual,
R: Rng,
{
for _ in 0..max_tries {
let (index, candidate) = self.select(population, rng);
if candidate != excluded {
return Some((index, candidate));
}
}
None
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct TournamentSelection {
size: usize,
}
impl TournamentSelection {
pub fn new(size: usize) -> Self {
assert!(size != 0);
Self { size }
}
pub fn size(&self) -> usize {
self.size
}
pub fn set_size(&mut self, size: usize) {
assert!(size != 0);
self.size = size
}
}
impl<I, R> SelectionMechanism<I, R> for TournamentSelection
where
I: Individual,
R: Rng,
{
#[inline]
fn prepare(&mut self, _population: &Population<I>) {}
#[inline]
fn select<'a>(&self, population: &'a Population<I>, rng: &mut R) -> (usize, &'a I) {
rng.sample_iter(Uniform::new(0, population.len()))
.take(self.size)
.max() .map(|i| (i, &population[i]))
.unwrap()
}
}
#[derive(Default, Clone, Copy, PartialEq)]
pub struct RouletteWheelSelection {
total_fitness: f64,
}
impl RouletteWheelSelection {
pub fn new() -> Self {
Self { total_fitness: 0.0 }
}
}
impl<I, R> SelectionMechanism<I, R> for RouletteWheelSelection
where
I: Individual,
R: Rng,
{
#[inline]
fn prepare(&mut self, population: &Population<I>) {
self.total_fitness = population.par_iter().map(|i| i.fitness()).sum();
}
#[inline]
fn select<'a>(&self, population: &'a Population<I>, rng: &mut R) -> (usize, &'a I) {
let target_fitness = rng.gen::<f64>() * self.total_fitness;
let mut cumulative_fitness = 0.0;
for (index, individual) in population.iter().enumerate() {
cumulative_fitness += individual.fitness();
if cumulative_fitness >= target_fitness {
return (index, individual);
}
}
(population.len() - 1, population.last().unwrap())
}
}
#[derive(Clone, Copy, PartialEq)]
pub struct BoltzmannSelection {
temperature: f64,
cooling_rate: f64,
total_scaled_fitness: f64,
}
impl BoltzmannSelection {
pub fn new(temperature: f64, cooling_rate: f64) -> Self {
assert!(cooling_rate != 0.0);
Self {
temperature,
cooling_rate,
total_scaled_fitness: 0.0,
}
}
pub fn temperature(&self) -> f64 {
self.temperature
}
pub fn set_temperature(&mut self, temperature: f64) {
self.temperature = temperature
}
pub fn cooling_rate(&self) -> f64 {
self.cooling_rate
}
pub fn set_cooling_rate(&mut self, cooling_rate: f64) {
assert!(cooling_rate != 0.0);
self.cooling_rate = cooling_rate
}
#[inline]
fn scaled_fitness<I: Individual>(&self, individual: &I) -> f64 {
(individual.fitness() / self.temperature).exp()
}
}
impl<I, R> SelectionMechanism<I, R> for BoltzmannSelection
where
I: Individual,
R: Rng,
{
#[inline]
fn prepare(&mut self, population: &Population<I>) {
self.temperature *= self.cooling_rate;
self.total_scaled_fitness = population
.par_iter()
.map(|i| self.scaled_fitness(i))
.sum();
}
#[inline]
fn select<'a>(&self, population: &'a Population<I>, rng: &mut R) -> (usize, &'a I) {
let target_fitness = rng.gen::<f64>() * self.total_scaled_fitness;
let mut cumulative_fitness = 0.0;
for (index, individual) in population.iter().enumerate() {
cumulative_fitness += self.scaled_fitness(individual);
if cumulative_fitness >= target_fitness {
return (index, individual);
}
}
(population.len() - 1, population.last().unwrap())
}
}
#[derive(Default, Clone, Copy, PartialEq, Eq)]
pub struct RankSelection {}
impl RankSelection {
pub fn new() -> Self {
Self {}
}
}
impl<I, R> SelectionMechanism<I, R> for RankSelection
where
I: Individual,
R: Rng,
{
#[inline]
fn prepare(&mut self, _population: &Population<I>) {
}
#[inline]
fn select<'a>(&self, population: &'a Population<I>, rng: &mut R) -> (usize, &'a I) {
let n = population.len();
let total_ranks = n * (n + 1) / 2;
let target_rank = rng.gen_range(1..=total_ranks);
let mut cumulative_rank = 0;
for (index, individual) in population.iter().enumerate() {
cumulative_rank += index + 1;
if cumulative_rank >= target_rank {
return (index, individual);
}
}
(0, population.first().unwrap())
}
}
pub struct ElitistSelection {
ratio: f64,
}
impl ElitistSelection {
pub fn new(ratio: f64) -> Self {
assert!((0.0..1.0).contains(&ratio));
Self { ratio }
}
pub fn ratio(&self) -> f64 {
self.ratio
}
pub fn set_ratio(&mut self, ratio: f64) {
assert!((0.0..1.0).contains(&ratio));
self.ratio = ratio
}
}
impl<I, R> SelectionMechanism<I, R> for ElitistSelection
where
I: Individual,
R: Rng,
{
#[inline]
fn prepare(&mut self, _population: &Population<I>) {
}
#[inline]
fn select<'a>(&self, population: &'a Population<I>, rng: &mut R) -> (usize, &'a I) {
let selection_bound = (population.len() as f64 * self.ratio).floor() as usize;
let index = rng.gen_range(selection_bound..population.len());
(index, &population[index])
}
}
#[derive(Clone, Copy, PartialEq)]
pub struct LinearSelection {
bias: f64,
}
impl LinearSelection {
pub fn new(bias: f64) -> Self {
Self { bias }
}
pub fn bias(&self) -> f64 {
self.bias
}
pub fn set_bias(&mut self, bias: f64) {
self.bias = bias
}
}
impl<I, R> SelectionMechanism<I, R> for LinearSelection
where
I: Individual,
R: Rng,
{
#[inline]
fn prepare(&mut self, _population: &Population<I>) {
}
#[inline]
fn select<'a>(&self, population: &'a Population<I>, rng: &mut R) -> (usize, &'a I) {
let index = population.len()
- ((population.len() as f64)
* (self.bias
- ((self.bias * self.bias - 4.0 * (self.bias - 1.0) * rng.gen::<f64>())
.sqrt()))
/ 2.0
/ (self.bias - 1.0)) as usize
- 1;
(index, &population[index])
}
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Debug, Clone, Copy, PartialEq)]
struct MockIndividual {
fitness: f64,
}
impl Individual for MockIndividual {
fn fitness(&self) -> f64 {
self.fitness
}
}
const POPULATION_SIZE: usize = 10;
const ITERATIONS: usize = 100000;
const CONFIDENCE: f64 = 0.01;
fn create_test_population() -> Population<MockIndividual> {
(0..POPULATION_SIZE)
.map(|i| MockIndividual {
fitness: i as f64 / POPULATION_SIZE as f64,
})
.collect::<Vec<_>>()
.try_into()
.unwrap()
}
fn collect_samples<S>(strategy: &mut S) -> (Vec<MockIndividual>, Vec<u32>)
where
S: SelectionMechanism<MockIndividual, SmallRng>,
{
let mut rng = SmallRng::seed_from_u64(0);
let population = create_test_population();
let mut counts = vec![0; POPULATION_SIZE];
strategy.prepare(&population);
for _ in 0..ITERATIONS {
let (index, _) = strategy.select(&population, &mut rng);
counts[index] += 1;
}
(population.into(), counts)
}
#[test]
fn test_tournament_selection_probabilities() {
let mut tournament = TournamentSelection::new(2);
let (population, observed_counts) = collect_samples(&mut tournament);
for (index, count) in observed_counts.into_iter().enumerate() {
let observed_probability = count as f64 / ITERATIONS as f64;
let expected_probability = (1.0
- (index as f64 / population.len() as f64).powi(tournament.size as i32))
- (1.0
- ((index as f64 + 1.0) / population.len() as f64)
.powi(tournament.size as i32));
assert!((observed_probability - expected_probability).abs() < CONFIDENCE);
}
}
#[test]
fn test_roulette_wheel_selection_probabilities() {
let mut roulette_wheel = RouletteWheelSelection::new();
let (population, observed_counts) = collect_samples(&mut roulette_wheel);
let total_fitness = population.iter().map(|i| i.fitness).sum::<f64>();
for (index, count) in observed_counts.into_iter().enumerate() {
let observed_probability = count as f64 / ITERATIONS as f64;
let expected_probability = population[index].fitness / total_fitness;
assert!((observed_probability - expected_probability).abs() < CONFIDENCE);
}
}
#[test]
fn test_rank_selection_probabilities() {
let mut rank = RankSelection::new();
let (population, observed_counts) = collect_samples(&mut rank);
let total_ranks = population.len() * (population.len() + 1) / 2;
for (index, count) in observed_counts.into_iter().enumerate() {
let observed_probability = count as f64 / ITERATIONS as f64;
let expected_probability = (index + 1) as f64 / total_ranks as f64;
assert!((observed_probability - expected_probability).abs() < CONFIDENCE);
}
}
#[test]
fn test_elitist_selection_probabilities() {
let mut elitist = ElitistSelection::new(0.7);
let (population, observed_counts) = collect_samples(&mut elitist);
let selection_bound = (population.len() as f64 * elitist.ratio).floor() as usize;
for (index, count) in observed_counts.into_iter().enumerate() {
let observed_probability = count as f64 / ITERATIONS as f64;
let expected_probability = if index >= selection_bound {
1.0 / (population.len() - selection_bound) as f64
} else {
0.0
};
assert!((observed_probability - expected_probability).abs() < CONFIDENCE);
}
}
#[test]
fn test_boltzmann_selection_probabilities() {
let mut boltzmann = BoltzmannSelection::new(5.0, 1.0);
let (population, observed_counts) = collect_samples(&mut boltzmann);
let total_scaled_fitness = population
.iter()
.map(|i| (i.fitness / boltzmann.temperature).exp())
.sum::<f64>();
for (index, count) in observed_counts.into_iter().enumerate() {
let observed_probability = count as f64 / ITERATIONS as f64;
let expected_probability =
(population[index].fitness / boltzmann.temperature).exp() / total_scaled_fitness;
assert!((observed_probability - expected_probability).abs() < CONFIDENCE);
}
}
#[test]
fn test_linear_selection_probabilities() {
let mut linear = LinearSelection::new(1.25);
let (_, observed_counts) = collect_samples(&mut linear);
let mut last_observed_probability: Option<f64> = None;
for count in observed_counts {
let observed_probability = count as f64 / ITERATIONS as f64;
if let Some(previous_observed_probability) = last_observed_probability {
assert!(observed_probability + CONFIDENCE > previous_observed_probability);
}
last_observed_probability = Some(observed_probability);
}
}
}