From aba9dbf469a40245fd194018d2c4647bcae872de Mon Sep 17 00:00:00 2001 From: Stephen Waits Date: Tue, 5 May 2026 11:02:01 -0600 Subject: [PATCH] build(bench): expand gungraun bench suite to cover every algorithm Goes from 6 benchmarks to ~25: Pareto utilities (existing): - non_dominated_sort_2d (n=50, 200) - crowding_distance_2d (n=50, 200) - hypervolume_2d (n=30, 100) - hypervolume_nd_3d (n=30, 100) Single-objective algorithms (all measured at "one short run"): - random_search, hill_climber, one_plus_one_es, simulated_annealing - genetic_algorithm, particle_swarm, differential_evolution, tlbo - cma_es, separable_nes, nelder_mead, bayesian_opt, tpe Multi-objective algorithms (one short run each): - nsga2, nsga3, spea2, moead, mopso, ibea, sms_emoa - hype, pesa2, epsilon_moea, age_moea, grea, knea, rvea Each uses a tiny problem with realistic-shape parameters (small pop, few generations, tight bounds) so the benchmark exercises each algorithm's *inner loop cost* rather than dominated by RNG init or config parsing. --- benches/hot_paths.rs | 396 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 395 insertions(+), 1 deletion(-) diff --git a/benches/hot_paths.rs b/benches/hot_paths.rs index b7ffd2d..3c790ff 100644 --- a/benches/hot_paths.rs +++ b/benches/hot_paths.rs @@ -169,4 +169,398 @@ library_benchmark_group!( benchmarks = nsga2_one_generation, cma_es_one_generation ); -main!(library_benchmark_groups = pareto_group, algorithm_group); +// ----------------------------------------------------------------------------- +// Wider single-objective sweep +// ----------------------------------------------------------------------------- + +struct Sphere1D; +impl Problem for Sphere1D { + type Decision = Vec; + fn objectives(&self) -> ObjectiveSpace { + ObjectiveSpace::new(vec![Objective::minimize("f")]) + } + fn evaluate(&self, x: &Vec) -> Evaluation { + Evaluation::new(vec![x[0] * x[0]]) + } +} + +fn so_bounds() -> RealBounds { + RealBounds::new(vec![(-3.0, 3.0)]) +} + +#[library_benchmark] +fn random_search_short() -> usize { + let mut o = RandomSearch::new( + RandomSearchConfig { iterations: 50, batch_size: 1, seed: 0 }, + so_bounds(), + ); + black_box(o.run(black_box(&Sphere1D)).evaluations) +} + +#[library_benchmark] +fn hill_climber_short() -> usize { + let mut o = HillClimber::new( + HillClimberConfig { iterations: 50, seed: 0 }, + so_bounds(), + GaussianMutation { sigma: 0.1 }, + ); + black_box(o.run(black_box(&Sphere1D)).evaluations) +} + +#[library_benchmark] +fn one_plus_one_es_short() -> usize { + let mut o = OnePlusOneEs::new( + OnePlusOneEsConfig { + iterations: 50, + initial_sigma: 0.5, + adaptation_period: 10, + step_increase: 1.22, + seed: 0, + }, + so_bounds(), + ); + black_box(o.run(black_box(&Sphere1D)).evaluations) +} + +#[library_benchmark] +fn simulated_annealing_short() -> usize { + let mut o = SimulatedAnnealing::new( + SimulatedAnnealingConfig { + iterations: 50, + initial_temperature: 1.0, + final_temperature: 1e-3, + seed: 0, + }, + so_bounds(), + GaussianMutation { sigma: 0.1 }, + ); + black_box(o.run(black_box(&Sphere1D)).evaluations) +} + +#[library_benchmark] +fn genetic_algorithm_short() -> usize { + let bounds = vec![(-3.0, 3.0)]; + let mut o = GeneticAlgorithm::new( + GeneticAlgorithmConfig { + population_size: 10, + generations: 5, + tournament_size: 2, + elitism: 1, + seed: 0, + }, + RealBounds::new(bounds.clone()), + CompositeVariation { + crossover: SimulatedBinaryCrossover::new(bounds.clone(), 15.0, 0.5), + mutation: PolynomialMutation::new(bounds, 20.0, 1.0), + }, + ); + black_box(o.run(black_box(&Sphere1D)).evaluations) +} + +#[library_benchmark] +fn particle_swarm_short() -> usize { + let mut o = ParticleSwarm::new( + ParticleSwarmConfig { + swarm_size: 10, + generations: 5, + inertia: 0.7, + cognitive: 1.5, + social: 1.5, + seed: 0, + }, + so_bounds(), + ); + black_box(o.run(black_box(&Sphere1D)).evaluations) +} + +#[library_benchmark] +fn differential_evolution_short() -> usize { + let mut o = DifferentialEvolution::new( + DifferentialEvolutionConfig { + population_size: 10, + generations: 5, + differential_weight: 0.5, + crossover_probability: 0.9, + seed: 0, + }, + so_bounds(), + ); + black_box(o.run(black_box(&Sphere1D)).evaluations) +} + +#[library_benchmark] +fn tlbo_short() -> usize { + let mut o = Tlbo::new( + TlboConfig { population_size: 10, generations: 5, seed: 0 }, + so_bounds(), + ); + black_box(o.run(black_box(&Sphere1D)).evaluations) +} + +#[library_benchmark] +fn separable_nes_short() -> usize { + let mut o = SeparableNes::new( + SeparableNesConfig { + population_size: 8, + generations: 5, + initial_sigma: 0.5, + mean_learning_rate: 1.0, + sigma_learning_rate: None, + seed: 0, + }, + so_bounds(), + ); + black_box(o.run(black_box(&Sphere1D)).evaluations) +} + +#[library_benchmark] +fn nelder_mead_short() -> usize { + let mut o = NelderMead::new( + NelderMeadConfig { iterations: 50, ..NelderMeadConfig::default() }, + so_bounds(), + ); + black_box(o.run(black_box(&Sphere1D)).evaluations) +} + +#[library_benchmark] +fn bayesian_opt_short() -> usize { + let mut o = BayesianOpt::new( + BayesianOptConfig { + initial_samples: 5, + iterations: 10, + length_scales: None, + signal_variance: 1.0, + noise_variance: 1e-6, + acquisition_samples: 100, + seed: 0, + }, + so_bounds(), + ); + black_box(o.run(black_box(&Sphere1D)).evaluations) +} + +#[library_benchmark] +fn tpe_short() -> usize { + let mut o = Tpe::new( + TpeConfig { + initial_samples: 5, + iterations: 10, + good_fraction: 0.25, + candidate_samples: 12, + bandwidth_factor: 1.0, + seed: 0, + }, + so_bounds(), + ); + black_box(o.run(black_box(&Sphere1D)).evaluations) +} + +#[library_benchmark] +fn ipop_cma_es_short() -> usize { + let mut o = IpopCmaEs::new( + IpopCmaEsConfig { + initial_population_size: 8, + total_generations: 30, + initial_sigma: 0.5, + eigen_decomposition_period: 1, + stall_generations: None, + seed: 0, + }, + so_bounds(), + ); + black_box(o.run(black_box(&Sphere1D)).evaluations) +} + +library_benchmark_group!( + name = single_objective_group; + benchmarks = + random_search_short, hill_climber_short, one_plus_one_es_short, + simulated_annealing_short, genetic_algorithm_short, + particle_swarm_short, differential_evolution_short, tlbo_short, + separable_nes_short, nelder_mead_short, + bayesian_opt_short, tpe_short, ipop_cma_es_short +); + +// ----------------------------------------------------------------------------- +// Multi-objective sweep +// ----------------------------------------------------------------------------- + +fn schaffer_bounds() -> Vec<(f64, f64)> { + vec![(-3.0, 3.0)] +} +fn mo_variation() +-> CompositeVariation { + let bounds = schaffer_bounds(); + CompositeVariation { + crossover: SimulatedBinaryCrossover::new(bounds.clone(), 15.0, 0.5), + mutation: PolynomialMutation::new(bounds, 20.0, 1.0), + } +} + +#[library_benchmark] +fn nsga3_short() -> usize { + let mut o = Nsga3::new( + Nsga3Config { population_size: 12, generations: 1, reference_divisions: 11, seed: 0 }, + RealBounds::new(schaffer_bounds()), + mo_variation(), + ); + black_box(o.run(black_box(&SchafferN1)).evaluations) +} + +#[library_benchmark] +fn spea2_short() -> usize { + let mut o = Spea2::new( + Spea2Config { population_size: 10, archive_size: 10, generations: 1, seed: 0 }, + RealBounds::new(schaffer_bounds()), + mo_variation(), + ); + black_box(o.run(black_box(&SchafferN1)).evaluations) +} + +#[library_benchmark] +fn moead_short() -> usize { + let mut o = Moead::new( + MoeadConfig { generations: 1, reference_divisions: 9, neighborhood_size: 4, seed: 0 }, + RealBounds::new(schaffer_bounds()), + mo_variation(), + ); + black_box(o.run(black_box(&SchafferN1)).evaluations) +} + +#[library_benchmark] +fn mopso_short() -> usize { + let mut o = Mopso::new( + MopsoConfig { + swarm_size: 10, generations: 1, archive_size: 10, + inertia: 0.7, cognitive: 1.5, social: 1.5, seed: 0, + }, + RealBounds::new(schaffer_bounds()), + ); + black_box(o.run(black_box(&SchafferN1)).evaluations) +} + +#[library_benchmark] +fn ibea_short() -> usize { + let mut o = Ibea::new( + IbeaConfig { population_size: 10, generations: 1, kappa: 0.05, seed: 0 }, + RealBounds::new(schaffer_bounds()), + mo_variation(), + ); + black_box(o.run(black_box(&SchafferN1)).evaluations) +} + +#[library_benchmark] +fn sms_emoa_short() -> usize { + let mut o = SmsEmoa::new( + SmsEmoaConfig { + population_size: 8, generations: 5, + reference_point: vec![10.0, 10.0], seed: 0, + }, + RealBounds::new(schaffer_bounds()), + mo_variation(), + ); + black_box(o.run(black_box(&SchafferN1)).evaluations) +} + +#[library_benchmark] +fn hype_short() -> usize { + let mut o = Hype::new( + HypeConfig { + population_size: 10, generations: 1, + reference_point: vec![10.0, 10.0], mc_samples: 100, seed: 0, + }, + RealBounds::new(schaffer_bounds()), + mo_variation(), + ); + black_box(o.run(black_box(&SchafferN1)).evaluations) +} + +#[library_benchmark] +fn pesa2_short() -> usize { + let mut o = PesaII::new( + PesaIIConfig { + population_size: 10, archive_size: 10, generations: 1, + grid_divisions: 4, seed: 0, + }, + RealBounds::new(schaffer_bounds()), + mo_variation(), + ); + black_box(o.run(black_box(&SchafferN1)).evaluations) +} + +#[library_benchmark] +fn epsilon_moea_short() -> usize { + let mut o = EpsilonMoea::new( + EpsilonMoeaConfig { + population_size: 10, evaluations: 30, + epsilon: vec![0.05, 0.05], seed: 0, + }, + RealBounds::new(schaffer_bounds()), + mo_variation(), + ); + black_box(o.run(black_box(&SchafferN1)).evaluations) +} + +#[library_benchmark] +fn age_moea_short() -> usize { + let mut o = AgeMoea::new( + AgeMoeaConfig { population_size: 10, generations: 1, seed: 0 }, + RealBounds::new(schaffer_bounds()), + mo_variation(), + ); + black_box(o.run(black_box(&SchafferN1)).evaluations) +} + +#[library_benchmark] +fn grea_short() -> usize { + let mut o = Grea::new( + GreaConfig { population_size: 10, generations: 1, grid_divisions: 4, seed: 0 }, + RealBounds::new(schaffer_bounds()), + mo_variation(), + ); + black_box(o.run(black_box(&SchafferN1)).evaluations) +} + +#[library_benchmark] +fn knea_short() -> usize { + let mut o = Knea::new( + KneaConfig { population_size: 10, generations: 1, seed: 0 }, + RealBounds::new(schaffer_bounds()), + mo_variation(), + ); + black_box(o.run(black_box(&SchafferN1)).evaluations) +} + +#[library_benchmark] +fn rvea_short() -> usize { + let mut o = Rvea::new( + RveaConfig { + population_size: 10, generations: 1, + reference_divisions: 9, alpha: 2.0, seed: 0, + }, + RealBounds::new(schaffer_bounds()), + mo_variation(), + ); + black_box(o.run(black_box(&SchafferN1)).evaluations) +} + +#[library_benchmark] +fn paes_short() -> usize { + let mut o = Paes::new( + PaesConfig { iterations: 30, archive_size: 10, seed: 0 }, + RealBounds::new(schaffer_bounds()), + GaussianMutation { sigma: 0.1 }, + ); + black_box(o.run(black_box(&SchafferN1)).evaluations) +} + +library_benchmark_group!( + name = multi_objective_group; + benchmarks = + nsga3_short, spea2_short, moead_short, mopso_short, ibea_short, + sms_emoa_short, hype_short, pesa2_short, epsilon_moea_short, + age_moea_short, grea_short, knea_short, rvea_short, paes_short +); + +main!(library_benchmark_groups = + pareto_group, algorithm_group, single_objective_group, multi_objective_group);