use crate::traits::{ChromosomeT, GeneT};
use std::cmp::Ordering;
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum DistanceMetric {
Fitness {
min_distance: f64,
},
Genotypic {
min_distance: f64,
},
}
impl Default for DistanceMetric {
fn default() -> Self {
DistanceMetric::Fitness { min_distance: 0.0 }
}
}
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct HallOfFameConfig {
pub capacity: usize,
pub distance_metric: DistanceMetric,
}
impl Default for HallOfFameConfig {
fn default() -> Self {
HallOfFameConfig {
capacity: 100,
distance_metric: DistanceMetric::default(),
}
}
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Entry<U: ChromosomeT> {
pub chromosome: U,
pub generation_added: u64,
pub fitness_at_addition: f64,
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct HallOfFame<U: ChromosomeT> {
entries: Vec<Entry<U>>,
capacity: usize,
distance_metric: DistanceMetric,
}
impl<U: ChromosomeT> HallOfFame<U> {
pub fn new(config: HallOfFameConfig) -> Self {
HallOfFame {
entries: Vec::new(),
capacity: config.capacity,
distance_metric: config.distance_metric,
}
}
pub fn try_insert(&mut self, chromosome: &U, generation: u64) -> bool {
let fitness = chromosome.fitness();
let dna = chromosome.dna();
if fitness.is_nan() {
log::debug!("Skipping chromosome with NaN fitness");
return false;
}
if self.capacity == 0 {
return false;
}
let meets_threshold = if self.entries.len() < self.capacity {
true
} else {
fitness >= self.entries.last().unwrap().fitness_at_addition
};
if !meets_threshold {
return false;
}
if self.entries.iter().any(|e| {
let existing_dna = e.chromosome.dna();
let max_len = dna.len().max(existing_dna.len());
if max_len == 0 {
return true; }
(0..max_len).all(|i| {
let id_a = dna.get(i).map(|g| g.id()).unwrap_or(-1);
let id_b = existing_dna.get(i).map(|g| g.id()).unwrap_or(-1);
id_a == id_b
})
}) {
return false; }
if let DistanceMetric::Genotypic { min_distance } = self.distance_metric {
if min_distance > 0.0 {
for entry in self.entries.iter() {
let existing_dna = entry.chromosome.dna();
let distance = genotypic_distance(dna, existing_dna);
if distance < min_distance {
return false; }
}
}
}
let pos = self
.entries
.binary_search_by(|e| {
e.fitness_at_addition
.partial_cmp(&fitness)
.unwrap_or(Ordering::Equal)
.reverse() })
.unwrap_or_else(|e| e);
self.entries.insert(
pos,
Entry {
chromosome: chromosome.clone(),
generation_added: generation,
fitness_at_addition: fitness,
},
);
if self.entries.len() > self.capacity {
self.entries.pop();
}
true
}
pub fn solutions(&self) -> &[Entry<U>] {
&self.entries
}
pub fn top(&self, k: usize) -> &[Entry<U>] {
let end = k.min(self.entries.len());
&self.entries[..end]
}
pub fn would_qualify(&self, chromosome: &U) -> bool {
if self.entries.len() < self.capacity {
true
} else {
chromosome.fitness() >= self.entries.last().unwrap().fitness_at_addition
}
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn capacity(&self) -> usize {
self.capacity
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
pub fn iter(&self) -> impl Iterator<Item = &Entry<U>> {
self.entries.iter()
}
pub fn entries(&self) -> &[Entry<U>] {
&self.entries
}
}
pub fn genotypic_distance<G: GeneT>(dna_a: &[G], dna_b: &[G]) -> f64 {
let max_len = dna_a.len().max(dna_b.len());
if max_len == 0 {
return 0.0;
}
let mut diff = 0usize;
for i in 0..max_len {
let id_a = dna_a.get(i).map(|g| g.id()).unwrap_or(-1);
let id_b = dna_b.get(i).map(|g| g.id()).unwrap_or(-1);
if id_a != id_b {
diff += 1;
}
}
diff as f64 / max_len as f64
}