#![allow(clippy::float_cmp)]
use crate::evolution::Chromosome;
use crate::evolution::fitness::stats::GeneStatRecord;
#[must_use]
pub fn weighted_fitness(chromosome: &Chromosome) -> f64 {
if chromosome.evaluations == 0 {
return 0.0;
}
let confidence = 1.0 - (-f64::from(chromosome.evaluations) / 10.0).exp();
chromosome.fitness * confidence
}
#[must_use]
pub fn evolutionary_fitness(chromosome: &Chromosome, gene_stats: &[GeneStatRecord]) -> f64 {
if chromosome.evaluations == 0 {
return 0.0;
}
let active_genes: Vec<_> = chromosome
.genes
.iter()
.filter(|(_, value)| value != "None")
.collect();
if active_genes.is_empty() {
return (chromosome.fitness * 0.35).min(0.35);
}
let historical_support = active_genes
.iter()
.map(|(name, value)| smoothed_gene_success_rate(gene_stats, name, value))
.sum::<f64>()
/ active_genes.len() as f64;
let confidence = (1.0 - (-f64::from(chromosome.evaluations) / 6.0).exp()).clamp(0.6, 1.0);
let modifier = 0.80 + (historical_support * 0.30);
(chromosome.fitness * modifier * confidence).clamp(0.0, 1.0)
}
fn smoothed_gene_success_rate(gene_stats: &[GeneStatRecord], name: &str, value: &str) -> f64 {
gene_stats
.iter()
.find(|(stat_name, stat_value, _, _)| stat_name == name && stat_value == value)
.map_or(0.5, |(_, _, successes, attempts)| {
f64::from(*successes + 1) / f64::from(*attempts + 2)
})
}
#[must_use]
pub fn confidence_weighted_average_fitness(population: &[Chromosome]) -> f64 {
let mut weighted_sum = 0.0;
let mut total_weight = 0.0;
for chromosome in population {
if chromosome.evaluations > 0 {
let weight = f64::from(chromosome.evaluations).sqrt();
weighted_sum += chromosome.fitness * weight;
total_weight += weight;
}
}
if total_weight == 0.0 {
0.0
} else {
weighted_sum / total_weight
}
}
#[must_use]
pub fn average_evaluated_fitness(population: &[Chromosome]) -> f64 {
let mut total_fitness = 0.0;
let mut evaluated_count = 0_u32;
for chromosome in population {
if chromosome.evaluations > 0 {
total_fitness += chromosome.fitness;
evaluated_count += 1;
}
}
if evaluated_count == 0 {
0.0
} else {
total_fitness / f64::from(evaluated_count)
}
}
#[must_use]
pub fn has_converged(population: &[Chromosome], threshold: f64) -> bool {
if population.is_empty() {
return false;
}
let stats = crate::evolution::fitness::summary::fitness_statistics(population);
stats.std_dev < threshold
}
#[must_use]
pub fn is_stagnant(fitness_history: &[f64], window_size: usize, threshold: f64) -> bool {
if fitness_history.len() < window_size {
return false;
}
let recent = &fitness_history[fitness_history.len().saturating_sub(window_size)..];
if recent.len() < 2 {
return false;
}
let max_change = recent
.windows(2)
.map(|w| (w[1] - w[0]).abs())
.fold(0.0_f64, |a: f64, b: f64| a.max(b));
max_change < threshold
}
#[must_use]
pub fn fitness_improvement_rate(current: f64, previous: f64) -> f64 {
if previous == 0.0 {
if current > 0.0 { f64::INFINITY } else { 0.0 }
} else {
(current - previous) / previous
}
}