use std::cmp::Ordering;
use crate::error::{GeneticError, Result};
use crate::phenotype::Phenotype;
use crate::selection::selection_strategy::SelectionStrategy;
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone)]
pub struct ElitistSelection {
allow_duplicates: bool,
higher_is_better: bool,
}
impl ElitistSelection {
pub fn new(higher_is_better: bool, allow_duplicates: bool) -> Self {
Self {
allow_duplicates,
higher_is_better,
}
}
pub fn with_duplicates(mut self) -> Self {
self.allow_duplicates = true;
self
}
pub fn with_lower_is_better(mut self) -> Self {
self.higher_is_better = false;
self
}
}
impl Default for ElitistSelection {
fn default() -> Self {
Self {
allow_duplicates: false,
higher_is_better: true,
}
}
}
impl<P> SelectionStrategy<P> for ElitistSelection
where
P: Phenotype,
{
fn select(&self, population: &[P], fitness: &[f64], num_to_select: usize) -> Result<Vec<P>> {
if population.is_empty() {
return Err(GeneticError::EmptyPopulation);
}
if fitness.len() != population.len() {
return Err(GeneticError::Configuration(format!(
"Fitness vector length ({}) doesn't match population length ({})",
fitness.len(),
population.len()
)));
}
let mut indexed_fitness: Vec<(usize, f64)> = fitness
.iter()
.enumerate()
.map(|(idx, &score)| (idx, score))
.collect();
indexed_fitness.sort_by(|a, b| {
let cmp = a.1.partial_cmp(&b.1).unwrap_or_else(|| {
if a.1.is_nan() {
Ordering::Less
} else if b.1.is_nan() {
Ordering::Greater
} else {
Ordering::Equal
}
});
if self.higher_is_better {
cmp.reverse()
} else {
cmp
}
});
let mut selected = Vec::with_capacity(num_to_select);
let mut selected_indices = std::collections::HashSet::new();
for (idx, _) in indexed_fitness.iter() {
if !self.allow_duplicates && !selected_indices.insert(*idx) {
continue;
}
selected.push(population[*idx].clone());
if selected.len() >= num_to_select {
break;
}
}
if self.allow_duplicates && selected.len() < num_to_select && !indexed_fitness.is_empty() {
let mut idx = 0;
while selected.len() < num_to_select {
selected.push(population[indexed_fitness[idx % indexed_fitness.len()].0].clone());
idx += 1;
}
}
Ok(selected)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::phenotype::Phenotype;
use crate::rng::RandomNumberGenerator;
#[derive(Clone, Debug)]
struct TestPhenotype {
value: f64,
}
impl Phenotype for TestPhenotype {
fn crossover(&mut self, other: &Self) {
self.value = (self.value + other.value) / 2.0;
}
fn mutate(&mut self, _rng: &mut RandomNumberGenerator) {
self.value += 0.1;
}
}
#[test]
fn test_elitist_selection() {
let population = vec![
TestPhenotype { value: 1.0 },
TestPhenotype { value: 2.0 },
TestPhenotype { value: 3.0 },
TestPhenotype { value: 4.0 },
TestPhenotype { value: 5.0 },
];
let fitness = vec![0.5, 0.8, 0.3, 0.9, 0.1];
let selection = ElitistSelection::default();
let selected = selection.select(&population, &fitness, 3).unwrap();
assert_eq!(selected.len(), 3);
assert!((selected[0].value - 4.0).abs() < f64::EPSILON); assert!((selected[1].value - 2.0).abs() < f64::EPSILON); assert!((selected[2].value - 1.0).abs() < f64::EPSILON); }
#[test]
fn test_elitist_selection_lower_is_better() {
let population = vec![
TestPhenotype { value: 1.0 },
TestPhenotype { value: 2.0 },
TestPhenotype { value: 3.0 },
TestPhenotype { value: 4.0 },
TestPhenotype { value: 5.0 },
];
let fitness = vec![0.5, 0.8, 0.3, 0.9, 0.1];
let selection = ElitistSelection::new(false, false);
let selected = selection.select(&population, &fitness, 3).unwrap();
assert_eq!(selected.len(), 3);
assert!((selected[0].value - 5.0).abs() < f64::EPSILON); assert!((selected[1].value - 3.0).abs() < f64::EPSILON); assert!((selected[2].value - 1.0).abs() < f64::EPSILON); }
#[test]
fn test_elitist_selection_with_duplicates() {
let population = vec![
TestPhenotype { value: 1.0 },
TestPhenotype { value: 2.0 },
TestPhenotype { value: 3.0 },
];
let fitness = vec![0.5, 0.8, 0.3];
let selection = ElitistSelection::new(false, true);
let selected = selection.select(&population, &fitness, 5).unwrap();
assert_eq!(selected.len(), 5);
assert!((selected[0].value - 3.0).abs() < f64::EPSILON); assert!((selected[1].value - 1.0).abs() < f64::EPSILON); assert!((selected[2].value - 2.0).abs() < f64::EPSILON);
assert!((selected[3].value - 3.0).abs() < f64::EPSILON); assert!((selected[4].value - 1.0).abs() < f64::EPSILON); }
#[test]
fn test_elitist_selection_without_duplicates() {
let population = vec![
TestPhenotype { value: 1.0 },
TestPhenotype { value: 2.0 },
TestPhenotype { value: 3.0 },
];
let fitness = vec![0.5, 0.8, 0.3];
let selection = ElitistSelection::new(true, false);
let selected = selection.select(&population, &fitness, 5).unwrap();
assert_eq!(selected.len(), 3);
assert!((selected[0].value - 2.0).abs() < f64::EPSILON); assert!((selected[1].value - 1.0).abs() < f64::EPSILON); assert!((selected[2].value - 3.0).abs() < f64::EPSILON); }
#[test]
fn test_elitist_selection_empty_population() {
let population: Vec<TestPhenotype> = Vec::new();
let fitness: Vec<f64> = Vec::new();
let selection = ElitistSelection::default();
let result = selection.select(&population, &fitness, 3);
assert!(result.is_err());
}
#[test]
fn test_elitist_selection_mismatched_lengths() {
let population = vec![TestPhenotype { value: 1.0 }, TestPhenotype { value: 2.0 }];
let fitness = vec![0.5];
let selection = ElitistSelection::default();
let result = selection.select(&population, &fitness, 1);
assert!(result.is_err());
}
#[test]
fn test_elitist_selection_with_nan() {
let population = vec![
TestPhenotype { value: 1.0 },
TestPhenotype { value: 2.0 },
TestPhenotype { value: 3.0 },
];
let fitness = vec![0.5, f64::NAN, 0.3];
let selection = ElitistSelection::default();
let selected = selection.select(&population, &fitness, 3).unwrap();
assert_eq!(selected.len(), 3);
assert!((selected[0].value - 1.0).abs() < f64::EPSILON); assert!((selected[1].value - 3.0).abs() < f64::EPSILON); assert!((selected[2].value - 2.0).abs() < f64::EPSILON); }
}