evolution_ternary/
convergence.rs1use crate::species::SpeciesTracker;
4
5#[derive(Clone, Debug)]
7pub struct ConvergenceStatus {
8 pub converged: bool,
10 pub reason: Option<String>,
12 pub plateau_generations: usize,
14}
15
16#[derive(Clone, Debug)]
18pub struct ConvergenceDetector {
19 pub plateau_threshold: usize,
21 pub fitness_tolerance: f64,
23 pub species_stability_threshold: usize,
25 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 pub fn default_detector() -> Self {
41 Self::new(15, 0.001)
42 }
43
44 pub fn record(&mut self, avg_fitness: f64) {
46 self.fitness_history.push(avg_fitness);
47 }
48
49 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}