use std::time::Instant;
use rand::SeedableRng;
use rand::rngs::StdRng;
use wafrift_evolution::evolution::{
Chromosome, EvolutionEngine, GenePool, baseline_chromosome, random_chromosome,
};
use wafrift_evolution::types::Budget;
fn engine_with(algo: &str, seed: u64) -> EvolutionEngine {
let pool = GenePool::default_wafrift();
let rng = StdRng::seed_from_u64(seed);
EvolutionEngine::with_algorithm(algo, pool, rng, Budget::default())
.expect("registered algorithm")
}
fn inject_population(engine: &mut EvolutionEngine, population: Vec<Chromosome>) {
engine.seed_population(population);
}
fn mk_chromosome_uniform(pool: &GenePool, value: &str) -> Chromosome {
let genes: Vec<(String, String)> = pool
.pools
.iter()
.map(|(name, _)| (name.clone(), value.to_string()))
.collect();
Chromosome::new(genes)
}
#[test]
fn diversity_in_unit_interval_under_random_population() {
let mut engine = engine_with("novelty_search", 42);
let pool = engine.gene_pool.clone();
let mut rng = StdRng::seed_from_u64(7);
let population: Vec<Chromosome> = (0..16)
.map(|_| random_chromosome(&pool, &mut rng))
.collect();
inject_population(&mut engine, population);
let d = engine.diversity_score();
assert!(
(0.0..=1.0).contains(&d),
"diversity must lie in [0, 1] — got {d}"
);
}
#[test]
fn fresh_engine_returns_one_as_safe_default() {
let engine = engine_with("hill_climbing", 0);
let d = engine.diversity_score();
assert_eq!(d, 1.0, "fresh engine should fall back to 1.0");
}
#[test]
fn identical_population_scores_zero_diversity() {
let mut engine = engine_with("novelty_search", 1);
let pool = engine.gene_pool.clone();
let same = mk_chromosome_uniform(&pool, "0");
inject_population(&mut engine, vec![same.clone(), same.clone(), same]);
let d = engine.diversity_score();
assert_eq!(d, 0.0, "identical population must score 0.0 — got {d}");
}
#[test]
fn fully_disjoint_population_scores_one() {
let mut engine = engine_with("novelty_search", 2);
let pool = engine.gene_pool.clone();
let a = mk_chromosome_uniform(&pool, "alpha");
let b = mk_chromosome_uniform(&pool, "beta");
inject_population(&mut engine, vec![a, b]);
let d = engine.diversity_score();
assert_eq!(d, 1.0, "fully-disjoint pair must score 1.0 — got {d}");
}
#[test]
fn diversity_monotone_in_pairwise_disagreement() {
let pool = GenePool::default_wafrift();
let gene_count = pool.pools.len();
assert!(
gene_count >= 3,
"default pool must have ≥3 genes for this test"
);
fn populate(pool: &GenePool, disagreements: usize) -> Vec<Chromosome> {
let a: Vec<(String, String)> = pool
.pools
.iter()
.map(|(name, _)| (name.clone(), "X".to_string()))
.collect();
let mut b = a.clone();
for (i, item) in b.iter_mut().enumerate().take(disagreements.min(a.len())) {
item.1 = format!("Y{i}");
}
vec![Chromosome::new(a.clone()), Chromosome::new(b)]
}
let mut e0 = engine_with("novelty_search", 11);
let mut e1 = engine_with("novelty_search", 11);
let mut e2 = engine_with("novelty_search", 11);
inject_population(&mut e0, populate(&pool, 0));
inject_population(&mut e1, populate(&pool, 1));
inject_population(&mut e2, populate(&pool, 3));
let d0 = e0.diversity_score();
let d1 = e1.diversity_score();
let d2 = e2.diversity_score();
assert!(d0 < d1, "1 disagreement must beat 0 — d0={d0} d1={d1}");
assert!(d1 < d2, "3 disagreements must beat 1 — d1={d1} d2={d2}");
assert!(d2 <= 1.0);
}
#[test]
fn novelty_search_exposes_population_and_archive_for_diversity() {
let mut engine = engine_with("novelty_search", 3);
let pool = engine.gene_pool.clone();
let mut rng = StdRng::seed_from_u64(0);
let population: Vec<Chromosome> = (0..8).map(|_| random_chromosome(&pool, &mut rng)).collect();
inject_population(&mut engine, population);
let d = engine.diversity_score();
assert!(
d > 0.0,
"novelty search with 8 random chromosomes must report >0 diversity"
);
}
#[test]
fn map_elites_grid_drives_diversity_via_population_snapshot() {
let mut engine = engine_with("map_elites", 4);
let pool = engine.gene_pool.clone();
let mut rng = StdRng::seed_from_u64(0);
let population: Vec<Chromosome> = (0..12)
.map(|_| random_chromosome(&pool, &mut rng))
.collect();
inject_population(&mut engine, population);
let d = engine.diversity_score();
assert!(
(0.0..=1.0).contains(&d),
"map_elites diversity must be valid — got {d}"
);
}
#[test]
fn single_state_algorithm_falls_back_to_safe_default() {
let mut engine = engine_with("hill_climbing", 5);
let pool = engine.gene_pool.clone();
inject_population(&mut engine, vec![baseline_chromosome(&pool)]);
let d = engine.diversity_score();
assert_eq!(
d, 1.0,
"single-state algo with no gene_stats must return safe default"
);
}
#[test]
fn in_flight_chromosomes_lift_diversity_for_single_state_algos() {
let mut engine = engine_with("hill_climbing", 6);
let pool = engine.gene_pool.clone();
let baseline = baseline_chromosome(&pool);
inject_population(&mut engine, vec![baseline.clone()]);
let distinct = mk_chromosome_uniform(&pool, "Z");
engine
.in_flight
.insert(1, (1, distinct, std::time::Instant::now()));
let d = engine.diversity_score();
assert!(
d > 0.0,
"in_flight chromosomes must contribute to diversity — got {d}"
);
}
#[test]
fn gene_stats_diversity_zero_when_only_one_value_per_gene() {
let mut engine = engine_with("hill_climbing", 7);
engine.gene_stats = vec![
("encoder".into(), "url".into(), 5, 10),
("padding".into(), "off".into(), 3, 6),
];
let g = engine.gene_stats_diversity();
assert_eq!(g, 0.0, "all genes have a single value tried — entropy 0");
}
#[test]
fn gene_stats_diversity_one_under_uniform_distribution() {
let mut engine = engine_with("hill_climbing", 8);
engine.gene_stats = vec![
("encoder".into(), "url".into(), 4, 4),
("encoder".into(), "html".into(), 4, 4),
("padding".into(), "low".into(), 2, 2),
("padding".into(), "high".into(), 2, 2),
];
let g = engine.gene_stats_diversity();
assert!(
(g - 1.0).abs() < 1e-9,
"uniform two-value distribution per gene → 1.0; got {g}"
);
}
#[test]
fn gene_stats_diversity_intermediate_under_skewed_distribution() {
let mut engine = engine_with("hill_climbing", 9);
engine.gene_stats = vec![
("encoder".into(), "url".into(), 90, 90),
("encoder".into(), "html".into(), 10, 10),
("padding".into(), "low".into(), 5, 5),
("padding".into(), "high".into(), 5, 5),
];
let g = engine.gene_stats_diversity();
assert!(
g > 0.0 && g < 1.0,
"skewed distribution must yield 0 < g < 1; got {g}"
);
}
#[test]
fn diversity_score_uses_gene_stats_fallback_when_population_empty() {
let mut engine = engine_with("hill_climbing", 10);
engine.in_flight.clear();
engine.gene_stats = vec![
("encoder".into(), "url".into(), 5, 5),
("encoder".into(), "html".into(), 5, 5),
];
let d = engine.diversity_score();
assert!(
(d - 1.0).abs() < 1e-9,
"expected fallback to gene-stats entropy → 1.0; got {d}"
);
}
#[test]
fn does_not_panic_on_zero_attempts_in_gene_stats() {
let mut engine = engine_with("hill_climbing", 11);
engine.gene_stats = vec![
("encoder".into(), "url".into(), 0, 0),
("padding".into(), "low".into(), 0, 0),
];
let _ = engine.diversity_score(); }
#[test]
fn does_not_panic_on_very_large_population() {
let mut engine = engine_with("novelty_search", 12);
let pool = engine.gene_pool.clone();
let mut rng = StdRng::seed_from_u64(0);
let population: Vec<Chromosome> = (0..256)
.map(|_| random_chromosome(&pool, &mut rng))
.collect();
inject_population(&mut engine, population);
let _ = engine.diversity_score();
}
#[test]
fn diversity_score_under_500ms_for_500_chromosome_population() {
let mut engine = engine_with("novelty_search", 13);
let pool = engine.gene_pool.clone();
let mut rng = StdRng::seed_from_u64(0);
let population: Vec<Chromosome> = (0..500)
.map(|_| random_chromosome(&pool, &mut rng))
.collect();
inject_population(&mut engine, population);
let t = Instant::now();
let _d = engine.diversity_score();
let elapsed = t.elapsed();
assert!(
elapsed.as_millis() < 500,
"500-chromosome diversity_score took {elapsed:?} (>500ms budget)"
);
}
#[test]
fn diversity_drives_adaptive_mutation_rate_in_expected_direction() {
use wafrift_evolution::evolution::crossover::diversity::adaptive_mutation_rate;
let base = 0.10;
let stagnation = 0;
let r_high = adaptive_mutation_rate(base, stagnation, 1.0);
let r_low = adaptive_mutation_rate(base, stagnation, 0.0);
assert!(
r_high < r_low,
"high diversity must reduce mutation rate; high={r_high} low={r_low}"
);
}