Skip to main content

evolution_ternary/
convergence.rs

1//! Convergence detection — fitness plateaus and species stabilization.
2
3use crate::species::SpeciesTracker;
4
5/// Result of convergence detection.
6#[derive(Clone, Debug)]
7pub struct ConvergenceStatus {
8    /// Whether the population has converged.
9    pub converged: bool,
10    /// Reason for convergence (if any).
11    pub reason: Option<String>,
12    /// How many generations fitness has been stable.
13    pub plateau_generations: usize,
14}
15
16/// Detects convergence based on fitness plateau and species stability.
17#[derive(Clone, Debug)]
18pub struct ConvergenceDetector {
19    /// How many generations of flat fitness before declaring convergence.
20    pub plateau_threshold: usize,
21    /// Minimum relative change in avg fitness to count as "improving".
22    pub fitness_tolerance: f64,
23    /// How many generations of stable species before declaring convergence.
24    pub species_stability_threshold: usize,
25    /// History of average fitness per generation.
26    pub fitness_history: Vec<f64>,
27}
28
29impl ConvergenceDetector {
30    pub fn new(plateau_threshold: usize, fitness_tolerance: f64) -> Self {
31        Self {
32            plateau_threshold,
33            fitness_tolerance,
34            species_stability_threshold: plateau_threshold,
35            fitness_history: Vec::new(),
36        }
37    }
38
39    /// Default detector: 15 generations, 0.001 tolerance.
40    pub fn default_detector() -> Self {
41        Self::new(15, 0.001)
42    }
43
44    /// Record a generation's average fitness.
45    pub fn record(&mut self, avg_fitness: f64) {
46        self.fitness_history.push(avg_fitness);
47    }
48
49    /// Check convergence based on fitness history and species tracker.
50    pub fn check(&self, species: &SpeciesTracker) -> ConvergenceStatus {
51        let mut plateau_gens = 0;
52        let tol = self.fitness_tolerance;
53
54        if self.fitness_history.len() >= 2 {
55            for i in (1..self.fitness_history.len()).rev() {
56                let prev = self.fitness_history[i - 1];
57                let curr = self.fitness_history[i];
58                if prev != 0.0 && (curr - prev).abs() / prev.abs() < tol {
59                    plateau_gens += 1;
60                } else if prev == 0.0 && curr.abs() < tol {
61                    plateau_gens += 1;
62                } else {
63                    break;
64                }
65            }
66        }
67
68        let fitness_plateau = plateau_gens >= self.plateau_threshold;
69        let species_stable = species.is_stable(self.species_stability_threshold);
70
71        let (converged, reason) = match (fitness_plateau, species_stable) {
72            (true, true) => (true, Some("Both fitness plateau and species stability".into())),
73            (true, false) => (true, Some("Fitness plateau".into())),
74            (false, true) => (true, Some("Species stabilization".into())),
75            (false, false) => (false, None),
76        };
77
78        ConvergenceStatus {
79            converged,
80            reason,
81            plateau_generations: plateau_gens,
82        }
83    }
84}