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.
This commit is contained in:
2026-05-13 19:43:17 -06:00
parent 6368db74bb
commit a773a1eaf6
+576
View File
@@ -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<u64>`. 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<RealBounds, Vec<f64>> = 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<Vec<i32>> for StartAtZero {
fn initialize(
&mut self,
_size: usize,
_rng: &mut heuropt::core::rng::Rng,
) -> Vec<Vec<i32>> {
vec![vec![0]]
}
}
let neighbors = |x: &Vec<i32>, _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));
}