use crate::species::SpeciesTracker;
#[derive(Clone, Debug)]
pub struct ConvergenceStatus {
pub converged: bool,
pub reason: Option<String>,
pub plateau_generations: usize,
}
#[derive(Clone, Debug)]
pub struct ConvergenceDetector {
pub plateau_threshold: usize,
pub fitness_tolerance: f64,
pub species_stability_threshold: usize,
pub fitness_history: Vec<f64>,
}
impl ConvergenceDetector {
pub fn new(plateau_threshold: usize, fitness_tolerance: f64) -> Self {
Self {
plateau_threshold,
fitness_tolerance,
species_stability_threshold: plateau_threshold,
fitness_history: Vec::new(),
}
}
pub fn default_detector() -> Self {
Self::new(15, 0.001)
}
pub fn record(&mut self, avg_fitness: f64) {
self.fitness_history.push(avg_fitness);
}
pub fn check(&self, species: &SpeciesTracker) -> ConvergenceStatus {
let mut plateau_gens = 0;
let tol = self.fitness_tolerance;
if self.fitness_history.len() >= 2 {
for i in (1..self.fitness_history.len()).rev() {
let prev = self.fitness_history[i - 1];
let curr = self.fitness_history[i];
if prev != 0.0 && (curr - prev).abs() / prev.abs() < tol {
plateau_gens += 1;
} else if prev == 0.0 && curr.abs() < tol {
plateau_gens += 1;
} else {
break;
}
}
}
let fitness_plateau = plateau_gens >= self.plateau_threshold;
let species_stable = species.is_stable(self.species_stability_threshold);
let (converged, reason) = match (fitness_plateau, species_stable) {
(true, true) => (true, Some("Both fitness plateau and species stability".into())),
(true, false) => (true, Some("Fitness plateau".into())),
(false, true) => (true, Some("Species stabilization".into())),
(false, false) => (false, None),
};
ConvergenceStatus {
converged,
reason,
plateau_generations: plateau_gens,
}
}
}