From a773a1eaf63375a4807e01633d767fbccdcf46c2 Mon Sep 17 00:00:00 2001 From: Stephen Waits Date: Wed, 13 May 2026 18:37:47 -0600 Subject: [PATCH] test(algorithm_info): pin name/full_name/seed for every algorithm Phase 0.1 of the mutation-testing campaign: a sweep test per algorithm (33 total) asserting the exact strings returned by AlgorithmInfo::name() and AlgorithmInfo::full_name() plus the seed propagated through AlgorithmInfo::seed(). Before: cargo mutants survived dozens of mutants per algorithm replacing the name/full_name return values with "" or "xyzzy", and the seed return with None/Some(0)/Some(1). After: every such mutant is caught by an exact-equality assertion. NelderMead is deterministic and has no seed override (intentionally); its test asserts seed() == None to pin the default-trait-impl behavior. --- tests/algorithm_properties.rs | 576 ++++++++++++++++++++++++++++++++++ 1 file changed, 576 insertions(+) diff --git a/tests/algorithm_properties.rs b/tests/algorithm_properties.rs index 7f1f034..886514d 100644 --- a/tests/algorithm_properties.rs +++ b/tests/algorithm_properties.rs @@ -731,3 +731,579 @@ proptest! { ); } } + +// ----------------------------------------------------------------------------- +// AlgorithmInfo sweep — exact name / full_name / seed per algorithm +// ----------------------------------------------------------------------------- +// +// Why this exists: every algorithm has three trivial trait methods returning +// `&'static str` and `Option`. A `cargo mutants` run discovers that +// these are unconstrained — replacing `"NSGA-II"` with `""` or `"xyzzy"` +// survives because no test reads the string. The constants below pin every +// algorithm's identifying strings exactly. Updating an algorithm's name +// requires updating its test, by design. + +#[test] +fn age_moea_algorithm_info_is_correct() { + let opt = AgeMoea::new( + AgeMoeaConfig { population_size: 4, generations: 1, seed: 42 }, + RealBounds::new(mo_bounds()), + mo_variation(), + ); + assert_eq!(opt.name(), "AGE-MOEA"); + assert_eq!( + opt.full_name(), + "Adaptive Geometry Estimation Multi-Objective Evolutionary Algorithm", + ); + assert_eq!(opt.seed(), Some(42)); +} + +#[test] +fn ant_colony_tsp_algorithm_info_is_correct() { + let opt = AntColonyTsp::new( + AntColonyTspConfig { + ants: 2, + generations: 1, + alpha: 1.0, + beta: 2.0, + evaporation: 0.5, + deposit: 1.0, + initial_pheromone: 1.0, + seed: 42, + }, + vec![vec![0.0, 1.0], vec![1.0, 0.0]], + ); + assert_eq!(opt.name(), "Ant Colony"); + assert_eq!(opt.full_name(), "Ant Colony System for TSP"); + assert_eq!(opt.seed(), Some(42)); +} + +#[test] +fn bayesian_opt_algorithm_info_is_correct() { + let opt = BayesianOpt::new( + BayesianOptConfig { + initial_samples: 2, + iterations: 1, + length_scales: None, + signal_variance: 1.0, + noise_variance: 1e-3, + acquisition_samples: 4, + seed: 42, + }, + so_bounds(), + ); + assert_eq!(opt.name(), "Bayesian Optimization"); + assert_eq!( + opt.full_name(), + "Gaussian Process Bayesian Optimization with Expected Improvement", + ); + assert_eq!(opt.seed(), Some(42)); +} + +#[test] +fn cma_es_algorithm_info_is_correct() { + let opt = CmaEs::new( + CmaEsConfig { + population_size: 4, + generations: 1, + initial_sigma: 0.5, + eigen_decomposition_period: 1, + initial_mean: None, + seed: 42, + }, + so_bounds(), + ); + assert_eq!(opt.name(), "CMA-ES"); + assert_eq!(opt.full_name(), "Covariance Matrix Adaptation Evolution Strategy"); + assert_eq!(opt.seed(), Some(42)); +} + +#[test] +fn differential_evolution_algorithm_info_is_correct() { + let opt = DifferentialEvolution::new( + DifferentialEvolutionConfig { + population_size: 4, + generations: 1, + differential_weight: 0.5, + crossover_probability: 0.9, + seed: 42, + }, + so_bounds(), + ); + assert_eq!(opt.name(), "DE"); + assert_eq!(opt.full_name(), "Differential Evolution"); + assert_eq!(opt.seed(), Some(42)); +} + +#[test] +fn epsilon_moea_algorithm_info_is_correct() { + let opt = EpsilonMoea::new( + EpsilonMoeaConfig { + population_size: 4, + evaluations: 4, + epsilon: vec![0.1, 0.1], + seed: 42, + }, + RealBounds::new(mo_bounds()), + mo_variation(), + ); + assert_eq!(opt.name(), "ε-MOEA"); + assert_eq!( + opt.full_name(), + "ε-dominance Multi-Objective Evolutionary Algorithm", + ); + assert_eq!(opt.seed(), Some(42)); +} + +#[test] +fn genetic_algorithm_algorithm_info_is_correct() { + let bounds = mo_bounds(); + let opt = GeneticAlgorithm::new( + GeneticAlgorithmConfig { + population_size: 4, + generations: 1, + tournament_size: 2, + elitism: 1, + seed: 42, + }, + RealBounds::new(bounds.clone()), + CompositeVariation { + crossover: SimulatedBinaryCrossover::new(bounds.clone(), 15.0, 0.5), + mutation: PolynomialMutation::new(bounds, 20.0, 1.0), + }, + ); + assert_eq!(opt.name(), "GA"); + assert_eq!(opt.full_name(), "Genetic Algorithm"); + assert_eq!(opt.seed(), Some(42)); +} + +#[test] +fn grea_algorithm_info_is_correct() { + let opt = Grea::new( + GreaConfig { + population_size: 4, + generations: 1, + grid_divisions: 4, + seed: 42, + }, + RealBounds::new(mo_bounds()), + mo_variation(), + ); + assert_eq!(opt.name(), "GrEA"); + assert_eq!(opt.full_name(), "Grid-based Evolutionary Algorithm"); + assert_eq!(opt.seed(), Some(42)); +} + +#[test] +fn hill_climber_algorithm_info_is_correct() { + let opt = HillClimber::new( + HillClimberConfig { iterations: 1, seed: 42 }, + so_bounds(), + GaussianMutation { sigma: 0.1 }, + ); + assert_eq!(opt.name(), "Hill Climber"); + assert_eq!(opt.full_name(), "Hill Climbing"); + assert_eq!(opt.seed(), Some(42)); +} + +#[test] +fn hyperband_algorithm_info_is_correct() { + let opt: Hyperband> = Hyperband::new( + HyperbandConfig { + max_budget: 8.0, + eta: 2.0, + max_brackets: 2, + seed: 42, + }, + so_bounds(), + ); + assert_eq!(opt.name(), "Hyperband"); + assert_eq!(opt.full_name(), "Hyperband multi-fidelity bandit search"); + assert_eq!(opt.seed(), Some(42)); +} + +#[test] +fn hype_algorithm_info_is_correct() { + let opt = Hype::new( + HypeConfig { + population_size: 4, + generations: 1, + reference_point: vec![10.0, 10.0], + mc_samples: 4, + seed: 42, + }, + RealBounds::new(mo_bounds()), + mo_variation(), + ); + assert_eq!(opt.name(), "HypE"); + assert_eq!(opt.full_name(), "Hypervolume Estimation Algorithm"); + assert_eq!(opt.seed(), Some(42)); +} + +#[test] +fn ibea_algorithm_info_is_correct() { + let opt = Ibea::new( + IbeaConfig { + population_size: 4, + generations: 1, + kappa: 0.05, + seed: 42, + }, + RealBounds::new(mo_bounds()), + mo_variation(), + ); + assert_eq!(opt.name(), "IBEA"); + assert_eq!(opt.full_name(), "Indicator-Based Evolutionary Algorithm"); + assert_eq!(opt.seed(), Some(42)); +} + +#[test] +fn ipop_cma_es_algorithm_info_is_correct() { + let opt = IpopCmaEs::new( + IpopCmaEsConfig { + initial_population_size: 4, + total_generations: 1, + initial_sigma: 0.5, + eigen_decomposition_period: 1, + stall_generations: None, + seed: 42, + }, + so_bounds(), + ); + assert_eq!(opt.name(), "IPOP-CMA-ES"); + assert_eq!(opt.full_name(), "Increasing-Population CMA-ES with Restarts"); + assert_eq!(opt.seed(), Some(42)); +} + +#[test] +fn knea_algorithm_info_is_correct() { + let opt = Knea::new( + KneaConfig { population_size: 4, generations: 1, seed: 42 }, + RealBounds::new(mo_bounds()), + mo_variation(), + ); + assert_eq!(opt.name(), "KnEA"); + assert_eq!(opt.full_name(), "Knee point-driven Evolutionary Algorithm"); + assert_eq!(opt.seed(), Some(42)); +} + +#[test] +fn moead_algorithm_info_is_correct() { + let opt = Moead::new( + MoeadConfig { + generations: 1, + reference_divisions: 3, + neighborhood_size: 2, + seed: 42, + }, + RealBounds::new(mo_bounds()), + mo_variation(), + ); + assert_eq!(opt.name(), "MOEA/D"); + assert_eq!( + opt.full_name(), + "Multi-Objective Evolutionary Algorithm based on Decomposition", + ); + assert_eq!(opt.seed(), Some(42)); +} + +#[test] +fn mopso_algorithm_info_is_correct() { + let opt = Mopso::new( + MopsoConfig { + swarm_size: 4, + generations: 1, + archive_size: 4, + inertia: 0.5, + cognitive: 1.0, + social: 1.0, + seed: 42, + }, + RealBounds::new(mo_bounds()), + ); + assert_eq!(opt.name(), "MOPSO"); + assert_eq!(opt.full_name(), "Multi-Objective Particle Swarm Optimization"); + assert_eq!(opt.seed(), Some(42)); +} + +#[test] +fn nelder_mead_algorithm_info_is_correct() { + let opt = NelderMead::new( + NelderMeadConfig { iterations: 1, ..NelderMeadConfig::default() }, + so_bounds(), + ); + assert_eq!(opt.name(), "Nelder-Mead"); + assert_eq!(opt.full_name(), "Nelder-Mead simplex direct search"); + // NelderMead is deterministic — no seed. Matches the default AlgorithmInfo + // impl which returns None. + assert_eq!(opt.seed(), None); +} + +#[test] +fn nsga2_algorithm_info_is_correct() { + let opt = Nsga2::new( + Nsga2Config { population_size: 4, generations: 1, seed: 42 }, + RealBounds::new(mo_bounds()), + mo_variation(), + ); + assert_eq!(opt.name(), "NSGA-II"); + assert_eq!(opt.full_name(), "Non-dominated Sorting Genetic Algorithm II"); + assert_eq!(opt.seed(), Some(42)); +} + +#[test] +fn nsga3_algorithm_info_is_correct() { + let opt = Nsga3::new( + Nsga3Config { + population_size: 4, + generations: 1, + reference_divisions: 4, + seed: 42, + }, + RealBounds::new(mo_bounds()), + mo_variation(), + ); + assert_eq!(opt.name(), "NSGA-III"); + assert_eq!(opt.full_name(), "Non-dominated Sorting Genetic Algorithm III"); + assert_eq!(opt.seed(), Some(42)); +} + +#[test] +fn one_plus_one_es_algorithm_info_is_correct() { + let opt = OnePlusOneEs::new( + OnePlusOneEsConfig { + iterations: 1, + initial_sigma: 0.5, + adaptation_period: 4, + step_increase: 1.5, + seed: 42, + }, + so_bounds(), + ); + assert_eq!(opt.name(), "(1+1)-ES"); + assert_eq!( + opt.full_name(), + "(1+1) Evolution Strategy with one-fifth success rule", + ); + assert_eq!(opt.seed(), Some(42)); +} + +#[test] +fn paes_algorithm_info_is_correct() { + let opt = Paes::new( + PaesConfig { iterations: 1, archive_size: 4, seed: 42 }, + RealBounds::new(mo_bounds()), + GaussianMutation { sigma: 0.1 }, + ); + assert_eq!(opt.name(), "PAES"); + assert_eq!(opt.full_name(), "Pareto Archived Evolution Strategy"); + assert_eq!(opt.seed(), Some(42)); +} + +#[test] +fn particle_swarm_algorithm_info_is_correct() { + let opt = ParticleSwarm::new( + ParticleSwarmConfig { + swarm_size: 4, + generations: 1, + inertia: 0.5, + cognitive: 1.0, + social: 1.0, + seed: 42, + }, + so_bounds(), + ); + assert_eq!(opt.name(), "PSO"); + assert_eq!(opt.full_name(), "Particle Swarm Optimization"); + assert_eq!(opt.seed(), Some(42)); +} + +#[test] +fn pesa_ii_algorithm_info_is_correct() { + let opt = PesaII::new( + PesaIIConfig { + population_size: 4, + archive_size: 4, + generations: 1, + grid_divisions: 4, + seed: 42, + }, + RealBounds::new(mo_bounds()), + mo_variation(), + ); + assert_eq!(opt.name(), "PESA-II"); + assert_eq!(opt.full_name(), "Pareto Envelope-based Selection Algorithm II"); + assert_eq!(opt.seed(), Some(42)); +} + +#[test] +fn random_search_algorithm_info_is_correct() { + let opt = RandomSearch::new( + RandomSearchConfig { iterations: 1, batch_size: 1, seed: 42 }, + so_bounds(), + ); + assert_eq!(opt.name(), "Random Search"); + // No `full_name` override — defaults to `name`. + assert_eq!(opt.full_name(), "Random Search"); + assert_eq!(opt.seed(), Some(42)); +} + +#[test] +fn rvea_algorithm_info_is_correct() { + let opt = Rvea::new( + RveaConfig { + population_size: 4, + generations: 1, + reference_divisions: 4, + alpha: 2.0, + seed: 42, + }, + RealBounds::new(mo_bounds()), + mo_variation(), + ); + assert_eq!(opt.name(), "RVEA"); + assert_eq!(opt.full_name(), "Reference Vector-guided Evolutionary Algorithm"); + assert_eq!(opt.seed(), Some(42)); +} + +#[test] +fn simulated_annealing_algorithm_info_is_correct() { + let opt = SimulatedAnnealing::new( + SimulatedAnnealingConfig { + iterations: 1, + initial_temperature: 1.0, + final_temperature: 0.1, + seed: 42, + }, + so_bounds(), + GaussianMutation { sigma: 0.1 }, + ); + assert_eq!(opt.name(), "Simulated Annealing"); + // No `full_name` override — defaults to `name`. + assert_eq!(opt.full_name(), "Simulated Annealing"); + assert_eq!(opt.seed(), Some(42)); +} + +#[test] +fn sms_emoa_algorithm_info_is_correct() { + let opt = SmsEmoa::new( + SmsEmoaConfig { + population_size: 4, + generations: 1, + reference_point: vec![100.0, 100.0], + seed: 42, + }, + RealBounds::new(mo_bounds()), + mo_variation(), + ); + assert_eq!(opt.name(), "SMS-EMOA"); + assert_eq!( + opt.full_name(), + "S-Metric Selection Evolutionary Multi-Objective Algorithm", + ); + assert_eq!(opt.seed(), Some(42)); +} + +#[test] +fn separable_nes_algorithm_info_is_correct() { + let opt = SeparableNes::new( + SeparableNesConfig { + population_size: 4, + generations: 1, + initial_sigma: 0.5, + mean_learning_rate: 1.0, + sigma_learning_rate: Some(0.1), + seed: 42, + }, + so_bounds(), + ); + assert_eq!(opt.name(), "sNES"); + assert_eq!(opt.full_name(), "Separable Natural Evolution Strategy"); + assert_eq!(opt.seed(), Some(42)); +} + +#[test] +fn spea2_algorithm_info_is_correct() { + let opt = Spea2::new( + Spea2Config { + population_size: 4, + archive_size: 4, + generations: 1, + seed: 42, + }, + RealBounds::new(mo_bounds()), + mo_variation(), + ); + assert_eq!(opt.name(), "SPEA2"); + assert_eq!(opt.full_name(), "Strength Pareto Evolutionary Algorithm 2"); + assert_eq!(opt.seed(), Some(42)); +} + +#[test] +fn tabu_search_algorithm_info_is_correct() { + struct StartAtZero; + impl Initializer> for StartAtZero { + fn initialize( + &mut self, + _size: usize, + _rng: &mut heuropt::core::rng::Rng, + ) -> Vec> { + vec![vec![0]] + } + } + let neighbors = |x: &Vec, _rng: &mut heuropt::core::rng::Rng| { + vec![vec![x[0] - 1], vec![x[0] + 1]] + }; + let opt = TabuSearch::new( + TabuSearchConfig { iterations: 1, tabu_tenure: 4, seed: 42 }, + StartAtZero, + neighbors, + ); + assert_eq!(opt.name(), "Tabu Search"); + // No `full_name` override — defaults to `name`. + assert_eq!(opt.full_name(), "Tabu Search"); + assert_eq!(opt.seed(), Some(42)); +} + +#[test] +fn tlbo_algorithm_info_is_correct() { + let opt = Tlbo::new( + TlboConfig { population_size: 4, generations: 1, seed: 42 }, + so_bounds(), + ); + assert_eq!(opt.name(), "TLBO"); + assert_eq!(opt.full_name(), "Teaching-Learning-Based Optimization"); + assert_eq!(opt.seed(), Some(42)); +} + +#[test] +fn tpe_algorithm_info_is_correct() { + let opt = Tpe::new( + TpeConfig { + initial_samples: 2, + iterations: 1, + good_fraction: 0.25, + candidate_samples: 4, + bandwidth_factor: 0.1, + seed: 42, + }, + so_bounds(), + ); + assert_eq!(opt.name(), "TPE"); + assert_eq!(opt.full_name(), "Tree-structured Parzen Estimator"); + assert_eq!(opt.seed(), Some(42)); +} + +#[test] +fn umda_algorithm_info_is_correct() { + let opt = Umda::new(UmdaConfig { + bits: 4, + population_size: 4, + selected_size: 2, + generations: 1, + seed: 42, + }); + assert_eq!(opt.name(), "UMDA"); + assert_eq!(opt.full_name(), "Univariate Marginal Distribution Algorithm"); + assert_eq!(opt.seed(), Some(42)); +}