diff --git a/.claude/scheduled_tasks.lock b/.claude/scheduled_tasks.lock new file mode 100644 index 0000000..41d1503 --- /dev/null +++ b/.claude/scheduled_tasks.lock @@ -0,0 +1 @@ +{"sessionId":"ac44d107-52ca-4cd4-9586-ae2fe91bc9f7","pid":2366937,"procStart":"77336928","acquiredAt":1778002505967} \ No newline at end of file diff --git a/benches/hot_paths.rs b/benches/hot_paths.rs index 3c790ff..61bf54e 100644 --- a/benches/hot_paths.rs +++ b/benches/hot_paths.rs @@ -14,10 +14,10 @@ use gungraun::prelude::*; use heuropt::core::candidate::Candidate; use heuropt::core::evaluation::Evaluation; use heuropt::core::objective::{Objective, ObjectiveSpace}; +use heuropt::core::problem::Problem; use heuropt::metrics::hypervolume::{hypervolume_2d, hypervolume_nd}; use heuropt::pareto::crowding::crowding_distance; use heuropt::pareto::sort::non_dominated_sort; -use heuropt::core::problem::Problem; use heuropt::prelude::*; // ----------------------------------------------------------------------------- @@ -53,7 +53,11 @@ fn crowding_distance_2d(n: usize) -> Vec { let pop = make_2d_population(n); let s = space_2d(); let front: Vec = (0..pop.len()).collect(); - black_box(crowding_distance(black_box(&pop), black_box(&front), black_box(&s))) + black_box(crowding_distance( + black_box(&pop), + black_box(&front), + black_box(&s), + )) } #[library_benchmark] @@ -62,7 +66,11 @@ fn crowding_distance_2d(n: usize) -> Vec { fn hypervolume_2d_bench(n: usize) -> f64 { let pop = make_2d_population(n); let s = space_2d(); - black_box(hypervolume_2d(black_box(&pop), black_box(&s), black_box([1.1, 1.1]))) + black_box(hypervolume_2d( + black_box(&pop), + black_box(&s), + black_box([1.1, 1.1]), + )) } fn make_3d_population(n: usize) -> (Vec>, ObjectiveSpace) { @@ -75,10 +83,7 @@ fn make_3d_population(n: usize) -> (Vec>, ObjectiveSpace) { .map(|i| { let t = i as f64 / n as f64; let theta = 0.5 * std::f64::consts::PI * t; - Candidate::new( - (), - Evaluation::new(vec![theta.cos(), theta.sin(), 1.0 - t]), - ) + Candidate::new((), Evaluation::new(vec![theta.cos(), theta.sin(), 1.0 - t])) }) .collect(); (pop, s) @@ -89,7 +94,11 @@ fn make_3d_population(n: usize) -> (Vec>, ObjectiveSpace) { #[bench::n_100(100)] fn hypervolume_nd_bench_3d(n: usize) -> f64 { let (pop, s) = make_3d_population(n); - black_box(hypervolume_nd(black_box(&pop), black_box(&s), black_box(&[2.0, 2.0, 2.0]))) + black_box(hypervolume_nd( + black_box(&pop), + black_box(&s), + black_box(&[2.0, 2.0, 2.0]), + )) } library_benchmark_group!( @@ -128,7 +137,11 @@ fn nsga2_one_generation() -> usize { mutation: PolynomialMutation::new(bounds, 20.0, 1.0), }; let mut opt = Nsga2::new( - Nsga2Config { population_size: 50, generations: 1, seed: 0 }, + Nsga2Config { + population_size: 50, + generations: 1, + seed: 0, + }, initializer, variation, ); @@ -191,7 +204,11 @@ fn so_bounds() -> RealBounds { #[library_benchmark] fn random_search_short() -> usize { let mut o = RandomSearch::new( - RandomSearchConfig { iterations: 50, batch_size: 1, seed: 0 }, + RandomSearchConfig { + iterations: 50, + batch_size: 1, + seed: 0, + }, so_bounds(), ); black_box(o.run(black_box(&Sphere1D)).evaluations) @@ -200,7 +217,10 @@ fn random_search_short() -> usize { #[library_benchmark] fn hill_climber_short() -> usize { let mut o = HillClimber::new( - HillClimberConfig { iterations: 50, seed: 0 }, + HillClimberConfig { + iterations: 50, + seed: 0, + }, so_bounds(), GaussianMutation { sigma: 0.1 }, ); @@ -291,7 +311,11 @@ fn differential_evolution_short() -> usize { #[library_benchmark] fn tlbo_short() -> usize { let mut o = Tlbo::new( - TlboConfig { population_size: 10, generations: 5, seed: 0 }, + TlboConfig { + population_size: 10, + generations: 5, + seed: 0, + }, so_bounds(), ); black_box(o.run(black_box(&Sphere1D)).evaluations) @@ -316,7 +340,10 @@ fn separable_nes_short() -> usize { #[library_benchmark] fn nelder_mead_short() -> usize { let mut o = NelderMead::new( - NelderMeadConfig { iterations: 50, ..NelderMeadConfig::default() }, + NelderMeadConfig { + iterations: 50, + ..NelderMeadConfig::default() + }, so_bounds(), ); black_box(o.run(black_box(&Sphere1D)).evaluations) @@ -388,8 +415,7 @@ library_benchmark_group!( fn schaffer_bounds() -> Vec<(f64, f64)> { vec![(-3.0, 3.0)] } -fn mo_variation() --> CompositeVariation { +fn mo_variation() -> CompositeVariation { let bounds = schaffer_bounds(); CompositeVariation { crossover: SimulatedBinaryCrossover::new(bounds.clone(), 15.0, 0.5), @@ -400,7 +426,12 @@ fn mo_variation() #[library_benchmark] fn nsga3_short() -> usize { let mut o = Nsga3::new( - Nsga3Config { population_size: 12, generations: 1, reference_divisions: 11, seed: 0 }, + Nsga3Config { + population_size: 12, + generations: 1, + reference_divisions: 11, + seed: 0, + }, RealBounds::new(schaffer_bounds()), mo_variation(), ); @@ -410,7 +441,12 @@ fn nsga3_short() -> usize { #[library_benchmark] fn spea2_short() -> usize { let mut o = Spea2::new( - Spea2Config { population_size: 10, archive_size: 10, generations: 1, seed: 0 }, + Spea2Config { + population_size: 10, + archive_size: 10, + generations: 1, + seed: 0, + }, RealBounds::new(schaffer_bounds()), mo_variation(), ); @@ -420,7 +456,12 @@ fn spea2_short() -> usize { #[library_benchmark] fn moead_short() -> usize { let mut o = Moead::new( - MoeadConfig { generations: 1, reference_divisions: 9, neighborhood_size: 4, seed: 0 }, + MoeadConfig { + generations: 1, + reference_divisions: 9, + neighborhood_size: 4, + seed: 0, + }, RealBounds::new(schaffer_bounds()), mo_variation(), ); @@ -431,8 +472,13 @@ fn moead_short() -> usize { 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, + swarm_size: 10, + generations: 1, + archive_size: 10, + inertia: 0.7, + cognitive: 1.5, + social: 1.5, + seed: 0, }, RealBounds::new(schaffer_bounds()), ); @@ -442,7 +488,12 @@ fn mopso_short() -> usize { #[library_benchmark] fn ibea_short() -> usize { let mut o = Ibea::new( - IbeaConfig { population_size: 10, generations: 1, kappa: 0.05, seed: 0 }, + IbeaConfig { + population_size: 10, + generations: 1, + kappa: 0.05, + seed: 0, + }, RealBounds::new(schaffer_bounds()), mo_variation(), ); @@ -453,8 +504,10 @@ fn ibea_short() -> usize { 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, + population_size: 8, + generations: 5, + reference_point: vec![10.0, 10.0], + seed: 0, }, RealBounds::new(schaffer_bounds()), mo_variation(), @@ -466,8 +519,11 @@ fn sms_emoa_short() -> usize { 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, + population_size: 10, + generations: 1, + reference_point: vec![10.0, 10.0], + mc_samples: 100, + seed: 0, }, RealBounds::new(schaffer_bounds()), mo_variation(), @@ -479,8 +535,11 @@ fn hype_short() -> usize { fn pesa2_short() -> usize { let mut o = PesaII::new( PesaIIConfig { - population_size: 10, archive_size: 10, generations: 1, - grid_divisions: 4, seed: 0, + population_size: 10, + archive_size: 10, + generations: 1, + grid_divisions: 4, + seed: 0, }, RealBounds::new(schaffer_bounds()), mo_variation(), @@ -492,8 +551,10 @@ fn pesa2_short() -> usize { fn epsilon_moea_short() -> usize { let mut o = EpsilonMoea::new( EpsilonMoeaConfig { - population_size: 10, evaluations: 30, - epsilon: vec![0.05, 0.05], seed: 0, + population_size: 10, + evaluations: 30, + epsilon: vec![0.05, 0.05], + seed: 0, }, RealBounds::new(schaffer_bounds()), mo_variation(), @@ -504,7 +565,11 @@ fn epsilon_moea_short() -> usize { #[library_benchmark] fn age_moea_short() -> usize { let mut o = AgeMoea::new( - AgeMoeaConfig { population_size: 10, generations: 1, seed: 0 }, + AgeMoeaConfig { + population_size: 10, + generations: 1, + seed: 0, + }, RealBounds::new(schaffer_bounds()), mo_variation(), ); @@ -514,7 +579,12 @@ fn age_moea_short() -> usize { #[library_benchmark] fn grea_short() -> usize { let mut o = Grea::new( - GreaConfig { population_size: 10, generations: 1, grid_divisions: 4, seed: 0 }, + GreaConfig { + population_size: 10, + generations: 1, + grid_divisions: 4, + seed: 0, + }, RealBounds::new(schaffer_bounds()), mo_variation(), ); @@ -524,7 +594,11 @@ fn grea_short() -> usize { #[library_benchmark] fn knea_short() -> usize { let mut o = Knea::new( - KneaConfig { population_size: 10, generations: 1, seed: 0 }, + KneaConfig { + population_size: 10, + generations: 1, + seed: 0, + }, RealBounds::new(schaffer_bounds()), mo_variation(), ); @@ -535,8 +609,11 @@ fn knea_short() -> usize { fn rvea_short() -> usize { let mut o = Rvea::new( RveaConfig { - population_size: 10, generations: 1, - reference_divisions: 9, alpha: 2.0, seed: 0, + population_size: 10, + generations: 1, + reference_divisions: 9, + alpha: 2.0, + seed: 0, }, RealBounds::new(schaffer_bounds()), mo_variation(), @@ -547,7 +624,11 @@ fn rvea_short() -> usize { #[library_benchmark] fn paes_short() -> usize { let mut o = Paes::new( - PaesConfig { iterations: 30, archive_size: 10, seed: 0 }, + PaesConfig { + iterations: 30, + archive_size: 10, + seed: 0, + }, RealBounds::new(schaffer_bounds()), GaussianMutation { sigma: 0.1 }, ); @@ -562,5 +643,9 @@ library_benchmark_group!( 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); +main!( + library_benchmark_groups = pareto_group, + algorithm_group, + single_objective_group, + multi_objective_group +); diff --git a/examples/benchmarks.rs b/examples/benchmarks.rs index 05f8861..72795a9 100644 --- a/examples/benchmarks.rs +++ b/examples/benchmarks.rs @@ -58,7 +58,9 @@ impl Problem for Rastrigin { fn evaluate(&self, x: &Vec) -> Evaluation { let n = self.dim as f64; let value = 10.0 * n - + x.iter().map(|v| v * v - 10.0 * (2.0 * PI * v).cos()).sum::(); + + x.iter() + .map(|v| v * v - 10.0 * (2.0 * PI * v).cos()) + .sum::(); Evaluation::new(vec![value]) } } @@ -104,7 +106,11 @@ fn run_zdt1() { crossover: SimulatedBinaryCrossover::new(bounds.clone(), 15.0, 0.5), mutation: PolynomialMutation::new(bounds, 20.0, 1.0 / dim as f64), }; - let config = Nsga2Config { population_size: 100, generations: 1000, seed: 42 }; + let config = Nsga2Config { + population_size: 100, + generations: 1000, + seed: 42, + }; let mut optimizer = Nsga2::new(config, initializer, variation); let result = optimizer.run(&problem); diff --git a/examples/compare.rs b/examples/compare.rs index 8b50a12..117414c 100644 --- a/examples/compare.rs +++ b/examples/compare.rs @@ -145,8 +145,7 @@ impl Problem for Ackley { let n = self.dim as f64; let sum_sq: f64 = x.iter().map(|v| v * v).sum(); let sum_cos: f64 = x.iter().map(|v| (2.0 * PI * v).cos()).sum(); - let f = -20.0 * (-0.2 * (sum_sq / n).sqrt()).exp() - - (sum_cos / n).exp() + let f = -20.0 * (-0.2 * (sum_sq / n).sqrt()).exp() - (sum_cos / n).exp() + 20.0 + std::f64::consts::E; Evaluation::new(vec![f]) @@ -228,7 +227,9 @@ impl Problem for Rastrigin { fn evaluate(&self, x: &Vec) -> Evaluation { let n = self.dim as f64; let value = 10.0 * n - + x.iter().map(|v| v * v - 10.0 * (2.0 * PI * v).cos()).sum::(); + + x.iter() + .map(|v| v * v - 10.0 * (2.0 * PI * v).cos()) + .sum::(); Evaluation::new(vec![value]) } } @@ -297,7 +298,10 @@ fn zdt1_random(seed: u64) -> MoRun { let mut opt = RandomSearch::new(config, initializer); let t0 = Instant::now(); let result = opt.run(&problem); - MoRun { front: result.pareto_front, wall_ms: t0.elapsed().as_millis() } + MoRun { + front: result.pareto_front, + wall_ms: t0.elapsed().as_millis(), + } } fn zdt1_paes(seed: u64) -> MoRun { @@ -312,7 +316,10 @@ fn zdt1_paes(seed: u64) -> MoRun { let mut opt = Paes::new(config, initializer, variation); let t0 = Instant::now(); let result = opt.run(&problem); - MoRun { front: result.pareto_front, wall_ms: t0.elapsed().as_millis() } + MoRun { + front: result.pareto_front, + wall_ms: t0.elapsed().as_millis(), + } } fn zdt1_spea2(seed: u64) -> MoRun { @@ -336,7 +343,10 @@ fn zdt1_spea2(seed: u64) -> MoRun { let mut opt = Spea2::new(config, initializer, variation); let t0 = Instant::now(); let result = opt.run(&problem); - MoRun { front: result.pareto_front, wall_ms: t0.elapsed().as_millis() } + MoRun { + front: result.pareto_front, + wall_ms: t0.elapsed().as_millis(), + } } fn zdt1_nsga2(seed: u64) -> MoRun { @@ -349,11 +359,18 @@ fn zdt1_nsga2(seed: u64) -> MoRun { }; let pop = 100; let gens = ZDT1_BUDGET / pop; - let config = Nsga2Config { population_size: pop, generations: gens, seed }; + let config = Nsga2Config { + population_size: pop, + generations: gens, + seed, + }; let mut opt = Nsga2::new(config, initializer, variation); let t0 = Instant::now(); let result = opt.run(&problem); - MoRun { front: result.pareto_front, wall_ms: t0.elapsed().as_millis() } + MoRun { + front: result.pareto_front, + wall_ms: t0.elapsed().as_millis(), + } } fn zdt1_sms_emoa(seed: u64) -> MoRun { @@ -376,7 +393,10 @@ fn zdt1_sms_emoa(seed: u64) -> MoRun { let mut opt = SmsEmoa::new(config, initializer, variation); let t0 = Instant::now(); let result = opt.run(&problem); - MoRun { front: result.pareto_front, wall_ms: t0.elapsed().as_millis() } + MoRun { + front: result.pareto_front, + wall_ms: t0.elapsed().as_millis(), + } } fn zdt1_hype(seed: u64) -> MoRun { @@ -402,7 +422,10 @@ fn zdt1_hype(seed: u64) -> MoRun { let mut opt = Hype::new(config, initializer, variation); let t0 = Instant::now(); let result = opt.run(&problem); - MoRun { front: result.pareto_front, wall_ms: t0.elapsed().as_millis() } + MoRun { + front: result.pareto_front, + wall_ms: t0.elapsed().as_millis(), + } } fn zdt1_rvea(seed: u64) -> MoRun { @@ -425,7 +448,10 @@ fn zdt1_rvea(seed: u64) -> MoRun { let mut opt = Rvea::new(config, initializer, variation); let t0 = Instant::now(); let result = opt.run(&problem); - MoRun { front: result.pareto_front, wall_ms: t0.elapsed().as_millis() } + MoRun { + front: result.pareto_front, + wall_ms: t0.elapsed().as_millis(), + } } fn zdt1_pesa2(seed: u64) -> MoRun { @@ -448,7 +474,10 @@ fn zdt1_pesa2(seed: u64) -> MoRun { let mut opt = PesaII::new(config, initializer, variation); let t0 = Instant::now(); let result = opt.run(&problem); - MoRun { front: result.pareto_front, wall_ms: t0.elapsed().as_millis() } + MoRun { + front: result.pareto_front, + wall_ms: t0.elapsed().as_millis(), + } } fn zdt1_epsilon_moea(seed: u64) -> MoRun { @@ -468,7 +497,10 @@ fn zdt1_epsilon_moea(seed: u64) -> MoRun { let mut opt = EpsilonMoea::new(config, initializer, variation); let t0 = Instant::now(); let result = opt.run(&problem); - MoRun { front: result.pareto_front, wall_ms: t0.elapsed().as_millis() } + MoRun { + front: result.pareto_front, + wall_ms: t0.elapsed().as_millis(), + } } fn zdt1_mopso(seed: u64) -> MoRun { @@ -488,7 +520,10 @@ fn zdt1_mopso(seed: u64) -> MoRun { let mut opt = Mopso::new(config, bounds); let t0 = Instant::now(); let result = opt.run(&problem); - MoRun { front: result.pareto_front, wall_ms: t0.elapsed().as_millis() } + MoRun { + front: result.pareto_front, + wall_ms: t0.elapsed().as_millis(), + } } fn zdt1_ibea(seed: u64) -> MoRun { @@ -501,11 +536,19 @@ fn zdt1_ibea(seed: u64) -> MoRun { }; let pop = 100; let gens = ZDT1_BUDGET / pop; - let config = IbeaConfig { population_size: pop, generations: gens, kappa: 0.05, seed }; + let config = IbeaConfig { + population_size: pop, + generations: gens, + kappa: 0.05, + seed, + }; let mut opt = Ibea::new(config, initializer, variation); let t0 = Instant::now(); let result = opt.run(&problem); - MoRun { front: result.pareto_front, wall_ms: t0.elapsed().as_millis() } + MoRun { + front: result.pareto_front, + wall_ms: t0.elapsed().as_millis(), + } } fn zdt1_moead(seed: u64) -> MoRun { @@ -529,7 +572,10 @@ fn zdt1_moead(seed: u64) -> MoRun { let mut opt = Moead::new(config, initializer, variation); let t0 = Instant::now(); let result = opt.run(&problem); - MoRun { front: result.pareto_front, wall_ms: t0.elapsed().as_millis() } + MoRun { + front: result.pareto_front, + wall_ms: t0.elapsed().as_millis(), + } } fn zdt1_nsga3(seed: u64) -> MoRun { @@ -552,7 +598,10 @@ fn zdt1_nsga3(seed: u64) -> MoRun { let mut opt = Nsga3::new(config, initializer, variation); let t0 = Instant::now(); let result = opt.run(&problem); - MoRun { front: result.pareto_front, wall_ms: t0.elapsed().as_millis() } + MoRun { + front: result.pareto_front, + wall_ms: t0.elapsed().as_millis(), + } } // ----------------------------------------------------------------------------- @@ -560,7 +609,10 @@ fn zdt1_nsga3(seed: u64) -> MoRun { // ----------------------------------------------------------------------------- fn dtlz2_problem() -> Dtlz2 { - Dtlz2 { num_objectives: DTLZ2_OBJECTIVES, dim: DTLZ2_DIM } + Dtlz2 { + num_objectives: DTLZ2_OBJECTIVES, + dim: DTLZ2_DIM, + } } fn dtlz2_random(seed: u64) -> MoRun { @@ -574,7 +626,10 @@ fn dtlz2_random(seed: u64) -> MoRun { let mut opt = RandomSearch::new(config, initializer); let t0 = Instant::now(); let result = opt.run(&problem); - MoRun { front: result.pareto_front, wall_ms: t0.elapsed().as_millis() } + MoRun { + front: result.pareto_front, + wall_ms: t0.elapsed().as_millis(), + } } fn dtlz2_nsga2(seed: u64) -> MoRun { @@ -587,11 +642,18 @@ fn dtlz2_nsga2(seed: u64) -> MoRun { }; let pop = 92; // close to the 91-ref-point NSGA-III pop, for fairness let gens = DTLZ2_BUDGET / pop; - let config = Nsga2Config { population_size: pop, generations: gens, seed }; + let config = Nsga2Config { + population_size: pop, + generations: gens, + seed, + }; let mut opt = Nsga2::new(config, initializer, variation); let t0 = Instant::now(); let result = opt.run(&problem); - MoRun { front: result.pareto_front, wall_ms: t0.elapsed().as_millis() } + MoRun { + front: result.pareto_front, + wall_ms: t0.elapsed().as_millis(), + } } fn dtlz2_spea2(seed: u64) -> MoRun { @@ -614,7 +676,10 @@ fn dtlz2_spea2(seed: u64) -> MoRun { let mut opt = Spea2::new(config, initializer, variation); let t0 = Instant::now(); let result = opt.run(&problem); - MoRun { front: result.pareto_front, wall_ms: t0.elapsed().as_millis() } + MoRun { + front: result.pareto_front, + wall_ms: t0.elapsed().as_millis(), + } } fn dtlz2_sms_emoa(seed: u64) -> MoRun { @@ -638,7 +703,10 @@ fn dtlz2_sms_emoa(seed: u64) -> MoRun { let mut opt = SmsEmoa::new(config, initializer, variation); let t0 = Instant::now(); let result = opt.run(&problem); - MoRun { front: result.pareto_front, wall_ms: t0.elapsed().as_millis() } + MoRun { + front: result.pareto_front, + wall_ms: t0.elapsed().as_millis(), + } } fn dtlz2_hype(seed: u64) -> MoRun { @@ -661,7 +729,10 @@ fn dtlz2_hype(seed: u64) -> MoRun { let mut opt = Hype::new(config, initializer, variation); let t0 = Instant::now(); let result = opt.run(&problem); - MoRun { front: result.pareto_front, wall_ms: t0.elapsed().as_millis() } + MoRun { + front: result.pareto_front, + wall_ms: t0.elapsed().as_millis(), + } } fn dtlz2_rvea(seed: u64) -> MoRun { @@ -684,7 +755,10 @@ fn dtlz2_rvea(seed: u64) -> MoRun { let mut opt = Rvea::new(config, initializer, variation); let t0 = Instant::now(); let result = opt.run(&problem); - MoRun { front: result.pareto_front, wall_ms: t0.elapsed().as_millis() } + MoRun { + front: result.pareto_front, + wall_ms: t0.elapsed().as_millis(), + } } fn dtlz2_pesa2(seed: u64) -> MoRun { @@ -707,7 +781,10 @@ fn dtlz2_pesa2(seed: u64) -> MoRun { let mut opt = PesaII::new(config, initializer, variation); let t0 = Instant::now(); let result = opt.run(&problem); - MoRun { front: result.pareto_front, wall_ms: t0.elapsed().as_millis() } + MoRun { + front: result.pareto_front, + wall_ms: t0.elapsed().as_millis(), + } } fn dtlz2_epsilon_moea(seed: u64) -> MoRun { @@ -727,7 +804,10 @@ fn dtlz2_epsilon_moea(seed: u64) -> MoRun { let mut opt = EpsilonMoea::new(config, initializer, variation); let t0 = Instant::now(); let result = opt.run(&problem); - MoRun { front: result.pareto_front, wall_ms: t0.elapsed().as_millis() } + MoRun { + front: result.pareto_front, + wall_ms: t0.elapsed().as_millis(), + } } fn dtlz2_mopso(seed: u64) -> MoRun { @@ -747,7 +827,10 @@ fn dtlz2_mopso(seed: u64) -> MoRun { let mut opt = Mopso::new(config, bounds); let t0 = Instant::now(); let result = opt.run(&problem); - MoRun { front: result.pareto_front, wall_ms: t0.elapsed().as_millis() } + MoRun { + front: result.pareto_front, + wall_ms: t0.elapsed().as_millis(), + } } fn dtlz2_ibea(seed: u64) -> MoRun { @@ -760,11 +843,19 @@ fn dtlz2_ibea(seed: u64) -> MoRun { }; let pop = 92; let gens = DTLZ2_BUDGET / pop; - let config = IbeaConfig { population_size: pop, generations: gens, kappa: 0.05, seed }; + let config = IbeaConfig { + population_size: pop, + generations: gens, + kappa: 0.05, + seed, + }; let mut opt = Ibea::new(config, initializer, variation); let t0 = Instant::now(); let result = opt.run(&problem); - MoRun { front: result.pareto_front, wall_ms: t0.elapsed().as_millis() } + MoRun { + front: result.pareto_front, + wall_ms: t0.elapsed().as_millis(), + } } fn dtlz2_moead(seed: u64) -> MoRun { @@ -787,7 +878,10 @@ fn dtlz2_moead(seed: u64) -> MoRun { let mut opt = Moead::new(config, initializer, variation); let t0 = Instant::now(); let result = opt.run(&problem); - MoRun { front: result.pareto_front, wall_ms: t0.elapsed().as_millis() } + MoRun { + front: result.pareto_front, + wall_ms: t0.elapsed().as_millis(), + } } fn dtlz2_nsga3(seed: u64) -> MoRun { @@ -811,7 +905,10 @@ fn dtlz2_nsga3(seed: u64) -> MoRun { let mut opt = Nsga3::new(config, initializer, variation); let t0 = Instant::now(); let result = opt.run(&problem); - MoRun { front: result.pareto_front, wall_ms: t0.elapsed().as_millis() } + MoRun { + front: result.pareto_front, + wall_ms: t0.elapsed().as_millis(), + } } /// DTLZ2's analytical Pareto front is the unit sphere octant in objective @@ -824,7 +921,13 @@ fn mean_distance_to_dtlz2_front(front: &[Candidate>]) -> f64 { let total: f64 = front .iter() .map(|c| { - let norm: f64 = c.evaluation.objectives.iter().map(|v| v * v).sum::().sqrt(); + let norm: f64 = c + .evaluation + .objectives + .iter() + .map(|v| v * v) + .sum::() + .sqrt(); (norm - 1.0).abs() }) .sum(); @@ -880,7 +983,11 @@ fn rastrigin_nsga2(seed: u64) -> SoRun { }; let pop = 50; let gens = RASTRIGIN_BUDGET / pop; - let config = Nsga2Config { population_size: pop, generations: gens, seed }; + let config = Nsga2Config { + population_size: pop, + generations: gens, + seed, + }; let mut opt = Nsga2::new(config, initializer, variation); let t0 = Instant::now(); let result = opt.run(&problem); @@ -915,7 +1022,10 @@ fn rastrigin_hill_climber(seed: u64) -> SoRun { let problem = Rastrigin { dim: RASTRIGIN_DIM }; let initializer = RealBounds::new(vec![(-5.12, 5.12); RASTRIGIN_DIM]); let variation = BoundedGaussianMutation::new(0.3, vec![(-5.12, 5.12); RASTRIGIN_DIM]); - let config = HillClimberConfig { iterations: RASTRIGIN_BUDGET, seed }; + let config = HillClimberConfig { + iterations: RASTRIGIN_BUDGET, + seed, + }; let mut opt = HillClimber::new(config, initializer, variation); let t0 = Instant::now(); let result = opt.run(&problem); @@ -1137,7 +1247,9 @@ fn ackley_bo(seed: u64) -> SoRun { // ----------------------------------------------------------------------------- fn rosenbrock_problem() -> Rosenbrock { - Rosenbrock { dim: ROSENBROCK_DIM } + Rosenbrock { + dim: ROSENBROCK_DIM, + } } fn ackley_problem() -> Ackley { Ackley { dim: ACKLEY_DIM } @@ -1176,7 +1288,7 @@ macro_rules! so_run_cma { generations: $budget / pop, initial_sigma: 1.0, eigen_decomposition_period: 1, - initial_mean: None, + initial_mean: None, seed: $seed, }; let mut opt = CmaEs::new(config, bounds); @@ -1219,7 +1331,11 @@ macro_rules! so_run_tlbo { let pop = 30; // TLBO does ~2N evaluations per generation. let gens = ($budget - pop) / (2 * pop); - let config = TlboConfig { population_size: pop, generations: gens, seed: $seed }; + let config = TlboConfig { + population_size: pop, + generations: gens, + seed: $seed, + }; let mut opt = Tlbo::new(config, bounds); let t0 = Instant::now(); let result = opt.run(&problem); @@ -1230,21 +1346,95 @@ macro_rules! so_run_tlbo { }}; } -fn rosenbrock_de(seed: u64) -> SoRun { so_run_de!(rosenbrock_problem(), ROSENBROCK_DIM, -5.0, 10.0, ROSENBROCK_BUDGET, seed) } -fn rosenbrock_cma(seed: u64) -> SoRun { so_run_cma!(rosenbrock_problem(), ROSENBROCK_DIM, -5.0, 10.0, ROSENBROCK_BUDGET, seed) } -fn rosenbrock_pso(seed: u64) -> SoRun { so_run_pso!(rosenbrock_problem(), ROSENBROCK_DIM, -5.0, 10.0, ROSENBROCK_BUDGET, seed) } -fn rosenbrock_tlbo(seed: u64) -> SoRun { so_run_tlbo!(rosenbrock_problem(), ROSENBROCK_DIM, -5.0, 10.0, ROSENBROCK_BUDGET, seed) } +fn rosenbrock_de(seed: u64) -> SoRun { + so_run_de!( + rosenbrock_problem(), + ROSENBROCK_DIM, + -5.0, + 10.0, + ROSENBROCK_BUDGET, + seed + ) +} +fn rosenbrock_cma(seed: u64) -> SoRun { + so_run_cma!( + rosenbrock_problem(), + ROSENBROCK_DIM, + -5.0, + 10.0, + ROSENBROCK_BUDGET, + seed + ) +} +fn rosenbrock_pso(seed: u64) -> SoRun { + so_run_pso!( + rosenbrock_problem(), + ROSENBROCK_DIM, + -5.0, + 10.0, + ROSENBROCK_BUDGET, + seed + ) +} +fn rosenbrock_tlbo(seed: u64) -> SoRun { + so_run_tlbo!( + rosenbrock_problem(), + ROSENBROCK_DIM, + -5.0, + 10.0, + ROSENBROCK_BUDGET, + seed + ) +} -fn ackley_de(seed: u64) -> SoRun { so_run_de!(ackley_problem(), ACKLEY_DIM, -32.768, 32.768, ACKLEY_BUDGET, seed) } -fn ackley_cma(seed: u64) -> SoRun { so_run_cma!(ackley_problem(), ACKLEY_DIM, -32.768, 32.768, ACKLEY_BUDGET, seed) } -fn ackley_pso(seed: u64) -> SoRun { so_run_pso!(ackley_problem(), ACKLEY_DIM, -32.768, 32.768, ACKLEY_BUDGET, seed) } -fn ackley_tlbo(seed: u64) -> SoRun { so_run_tlbo!(ackley_problem(), ACKLEY_DIM, -32.768, 32.768, ACKLEY_BUDGET, seed) } +fn ackley_de(seed: u64) -> SoRun { + so_run_de!( + ackley_problem(), + ACKLEY_DIM, + -32.768, + 32.768, + ACKLEY_BUDGET, + seed + ) +} +fn ackley_cma(seed: u64) -> SoRun { + so_run_cma!( + ackley_problem(), + ACKLEY_DIM, + -32.768, + 32.768, + ACKLEY_BUDGET, + seed + ) +} +fn ackley_pso(seed: u64) -> SoRun { + so_run_pso!( + ackley_problem(), + ACKLEY_DIM, + -32.768, + 32.768, + ACKLEY_BUDGET, + seed + ) +} +fn ackley_tlbo(seed: u64) -> SoRun { + so_run_tlbo!( + ackley_problem(), + ACKLEY_DIM, + -32.768, + 32.768, + ACKLEY_BUDGET, + seed + ) +} // ----------------------------------------------------------------------------- // ZDT3 runners (curated MO subset) // ----------------------------------------------------------------------------- -fn zdt3_problem() -> Zdt3 { Zdt3 { dim: ZDT3_DIM } } +fn zdt3_problem() -> Zdt3 { + Zdt3 { dim: ZDT3_DIM } +} fn zdt3_nsga2(seed: u64) -> MoRun { let problem = zdt3_problem(); @@ -1255,11 +1445,18 @@ fn zdt3_nsga2(seed: u64) -> MoRun { mutation: PolynomialMutation::new(bounds, 20.0, 1.0 / ZDT3_DIM as f64), }; let pop = 100; - let config = Nsga2Config { population_size: pop, generations: ZDT3_BUDGET / pop, seed }; + let config = Nsga2Config { + population_size: pop, + generations: ZDT3_BUDGET / pop, + seed, + }; let mut opt = Nsga2::new(config, initializer, variation); let t0 = Instant::now(); let result = opt.run(&problem); - MoRun { front: result.pareto_front, wall_ms: t0.elapsed().as_millis() } + MoRun { + front: result.pareto_front, + wall_ms: t0.elapsed().as_millis(), + } } fn zdt3_moead(seed: u64) -> MoRun { @@ -1280,7 +1477,10 @@ fn zdt3_moead(seed: u64) -> MoRun { let mut opt = Moead::new(config, initializer, variation); let t0 = Instant::now(); let result = opt.run(&problem); - MoRun { front: result.pareto_front, wall_ms: t0.elapsed().as_millis() } + MoRun { + front: result.pareto_front, + wall_ms: t0.elapsed().as_millis(), + } } fn zdt3_ibea(seed: u64) -> MoRun { @@ -1292,11 +1492,19 @@ fn zdt3_ibea(seed: u64) -> MoRun { mutation: PolynomialMutation::new(bounds, 20.0, 1.0 / ZDT3_DIM as f64), }; let pop = 100; - let config = IbeaConfig { population_size: pop, generations: ZDT3_BUDGET / pop, kappa: 0.05, seed }; + let config = IbeaConfig { + population_size: pop, + generations: ZDT3_BUDGET / pop, + kappa: 0.05, + seed, + }; let mut opt = Ibea::new(config, initializer, variation); let t0 = Instant::now(); let result = opt.run(&problem); - MoRun { front: result.pareto_front, wall_ms: t0.elapsed().as_millis() } + MoRun { + front: result.pareto_front, + wall_ms: t0.elapsed().as_millis(), + } } fn zdt3_age_moea(seed: u64) -> MoRun { @@ -1308,11 +1516,18 @@ fn zdt3_age_moea(seed: u64) -> MoRun { mutation: PolynomialMutation::new(bounds, 20.0, 1.0 / ZDT3_DIM as f64), }; let pop = 100; - let config = AgeMoeaConfig { population_size: pop, generations: ZDT3_BUDGET / pop, seed }; + let config = AgeMoeaConfig { + population_size: pop, + generations: ZDT3_BUDGET / pop, + seed, + }; let mut opt = AgeMoea::new(config, initializer, variation); let t0 = Instant::now(); let result = opt.run(&problem); - MoRun { front: result.pareto_front, wall_ms: t0.elapsed().as_millis() } + MoRun { + front: result.pareto_front, + wall_ms: t0.elapsed().as_millis(), + } } // ----------------------------------------------------------------------------- @@ -1320,7 +1535,10 @@ fn zdt3_age_moea(seed: u64) -> MoRun { // ----------------------------------------------------------------------------- fn dtlz1_problem() -> Dtlz1 { - Dtlz1 { num_objectives: DTLZ1_OBJECTIVES, dim: DTLZ1_DIM } + Dtlz1 { + num_objectives: DTLZ1_OBJECTIVES, + dim: DTLZ1_DIM, + } } fn dtlz1_nsga3(seed: u64) -> MoRun { @@ -1341,7 +1559,10 @@ fn dtlz1_nsga3(seed: u64) -> MoRun { let mut opt = Nsga3::new(config, initializer, variation); let t0 = Instant::now(); let result = opt.run(&problem); - MoRun { front: result.pareto_front, wall_ms: t0.elapsed().as_millis() } + MoRun { + front: result.pareto_front, + wall_ms: t0.elapsed().as_millis(), + } } fn dtlz1_moead(seed: u64) -> MoRun { @@ -1362,7 +1583,10 @@ fn dtlz1_moead(seed: u64) -> MoRun { let mut opt = Moead::new(config, initializer, variation); let t0 = Instant::now(); let result = opt.run(&problem); - MoRun { front: result.pareto_front, wall_ms: t0.elapsed().as_millis() } + MoRun { + front: result.pareto_front, + wall_ms: t0.elapsed().as_millis(), + } } fn dtlz1_age_moea(seed: u64) -> MoRun { @@ -1374,11 +1598,18 @@ fn dtlz1_age_moea(seed: u64) -> MoRun { mutation: PolynomialMutation::new(bounds, 20.0, 1.0 / DTLZ1_DIM as f64), }; let pop = 92; - let config = AgeMoeaConfig { population_size: pop, generations: DTLZ1_BUDGET / pop, seed }; + let config = AgeMoeaConfig { + population_size: pop, + generations: DTLZ1_BUDGET / pop, + seed, + }; let mut opt = AgeMoea::new(config, initializer, variation); let t0 = Instant::now(); let result = opt.run(&problem); - MoRun { front: result.pareto_front, wall_ms: t0.elapsed().as_millis() } + MoRun { + front: result.pareto_front, + wall_ms: t0.elapsed().as_millis(), + } } fn dtlz1_grea(seed: u64) -> MoRun { @@ -1399,7 +1630,10 @@ fn dtlz1_grea(seed: u64) -> MoRun { let mut opt = Grea::new(config, initializer, variation); let t0 = Instant::now(); let result = opt.run(&problem); - MoRun { front: result.pareto_front, wall_ms: t0.elapsed().as_millis() } + MoRun { + front: result.pareto_front, + wall_ms: t0.elapsed().as_millis(), + } } /// Mean L2 distance from each front point to the analytical DTLZ1 front @@ -1424,9 +1658,7 @@ fn mean_distance_to_dtlz1_front(front: &[Candidate>]) -> f64 { // ----------------------------------------------------------------------------- fn run_zdt1_comparison() { - println!( - "== ZDT1 (dim={ZDT1_DIM}, {ZDT1_BUDGET} evals/run × {SEEDS} seeds) ==" - ); + println!("== ZDT1 (dim={ZDT1_DIM}, {ZDT1_BUDGET} evals/run × {SEEDS} seeds) =="); println!("metric arrows: hypervolume↑ (higher better), others↓ (lower better)"); println!(); println!( @@ -1461,10 +1693,11 @@ fn run_zdt1_comparison() { .iter() .map(|r| hypervolume_2d(&r.front, &zdt1_objs, ZDT1_REFERENCE)) .collect(); - let sp: Vec = - runs.iter().map(|r| spacing(&r.front, &zdt1_objs)).collect(); - let l2: Vec = - runs.iter().map(|r| mean_l2_to_zdt1_front(&r.front)).collect(); + let sp: Vec = runs.iter().map(|r| spacing(&r.front, &zdt1_objs)).collect(); + let l2: Vec = runs + .iter() + .map(|r| mean_l2_to_zdt1_front(&r.front)) + .collect(); let fs: Vec = runs.iter().map(|r| r.front.len() as f64).collect(); let ms: Vec = runs.iter().map(|r| r.wall_ms as f64).collect(); @@ -1488,9 +1721,7 @@ fn run_zdt1_comparison() { fn run_dtlz2_comparison() { println!(); - println!( - "== DTLZ2 (3-obj, dim={DTLZ2_DIM}, {DTLZ2_BUDGET} evals/run × {SEEDS} seeds) ==" - ); + println!("== DTLZ2 (3-obj, dim={DTLZ2_DIM}, {DTLZ2_BUDGET} evals/run × {SEEDS} seeds) =="); println!("Pareto front: unit sphere octant (Σf²=1, all f≥0); 'mean dist' is |‖f‖−1|"); println!(); println!( @@ -1520,10 +1751,14 @@ fn run_dtlz2_comparison() { for (name, runner) in runners { let runs: Vec = (0..SEEDS).map(runner).collect(); - let dist: Vec = - runs.iter().map(|r| mean_distance_to_dtlz2_front(&r.front)).collect(); - let sp: Vec = - runs.iter().map(|r| spacing(&r.front, &dtlz2_objs)).collect(); + let dist: Vec = runs + .iter() + .map(|r| mean_distance_to_dtlz2_front(&r.front)) + .collect(); + let sp: Vec = runs + .iter() + .map(|r| spacing(&r.front, &dtlz2_objs)) + .collect(); let fs: Vec = runs.iter().map(|r| r.front.len() as f64).collect(); let ms: Vec = runs.iter().map(|r| r.wall_ms as f64).collect(); @@ -1545,9 +1780,7 @@ fn run_dtlz2_comparison() { fn run_rastrigin_comparison() { println!(); - println!( - "== Rastrigin (dim={RASTRIGIN_DIM}, {RASTRIGIN_BUDGET} evals/run × {SEEDS} seeds) ==" - ); + println!("== Rastrigin (dim={RASTRIGIN_DIM}, {RASTRIGIN_BUDGET} evals/run × {SEEDS} seeds) =="); println!("global minimum: f = 0 (lower is better)"); println!(); println!("{:<14} {:>20} {:>10}", "algorithm", "best f", "ms"); @@ -1668,7 +1901,10 @@ fn run_zdt3_comparison() { ]; for (name, runner) in runners { let runs: Vec = (0..SEEDS).map(runner).collect(); - let hv: Vec = runs.iter().map(|r| hypervolume_2d(&r.front, &objs, ZDT3_REFERENCE)).collect(); + let hv: Vec = runs + .iter() + .map(|r| hypervolume_2d(&r.front, &objs, ZDT3_REFERENCE)) + .collect(); let sp: Vec = runs.iter().map(|r| spacing(&r.front, &objs)).collect(); let fs: Vec = runs.iter().map(|r| r.front.len() as f64).collect(); let ms: Vec = runs.iter().map(|r| r.wall_ms as f64).collect(); @@ -1708,7 +1944,10 @@ fn run_dtlz1_comparison() { ]; for (name, runner) in runners { let runs: Vec = (0..SEEDS).map(runner).collect(); - let dist: Vec = runs.iter().map(|r| mean_distance_to_dtlz1_front(&r.front)).collect(); + let dist: Vec = runs + .iter() + .map(|r| mean_distance_to_dtlz1_front(&r.front)) + .collect(); let sp: Vec = runs.iter().map(|r| spacing(&r.front, &objs)).collect(); let fs: Vec = runs.iter().map(|r| r.front.len() as f64).collect(); let ms: Vec = runs.iter().map(|r| r.wall_ms as f64).collect(); diff --git a/examples/custom_optimizer.rs b/examples/custom_optimizer.rs index 162fc20..f4e56dc 100644 --- a/examples/custom_optimizer.rs +++ b/examples/custom_optimizer.rs @@ -26,7 +26,10 @@ where { fn run(&mut self, problem: &P) -> OptimizationResult { let objectives = problem.objectives(); - assert!(objectives.is_single_objective(), "HillClimber needs one objective"); + assert!( + objectives.is_single_objective(), + "HillClimber needs one objective" + ); let mut rng = rng_from_seed(self.seed); let mut variation = GaussianMutation { sigma: self.sigma }; diff --git a/examples/jiggly_tuning.rs b/examples/jiggly_tuning.rs index eb4c9f1..8323591 100644 --- a/examples/jiggly_tuning.rs +++ b/examples/jiggly_tuning.rs @@ -152,7 +152,10 @@ impl JigglyTuning { let mut rng = StdRng::seed_from_u64(day_seed); let mut expire = s + rt; // Boot press at workday start: user presses to begin cycle 1. - let mut o = DayOutcome { presses: 1, ..Default::default() }; + let mut o = DayOutcome { + presses: 1, + ..Default::default() + }; // Allow the loop to extend past the larger of (workday end, last // possible cycle end given any in-loop expire bumps). Cap at one // extra cycle's worth so a long string of presses can't blow the @@ -348,7 +351,11 @@ fn print_header() { } fn print_row(label: &str, r: &Row) { - let prefix = if label.is_empty() { String::new() } else { format!("{label} ") }; + let prefix = if label.is_empty() { + String::new() + } else { + format!("{label} ") + }; println!( "{}{:<6} {:>3} {:>3} {:>3} {:>9} {:>9} {:>7.2}/d {:>8} {:>6.1}%", prefix, @@ -440,7 +447,11 @@ fn main() { println!("=== Pareto front (sorted by lunch sleep, descending) ==="); print_header(); - rows.sort_by(|a, b| b.lunch.partial_cmp(&a.lunch).unwrap_or(std::cmp::Ordering::Equal)); + rows.sort_by(|a, b| { + b.lunch + .partial_cmp(&a.lunch) + .unwrap_or(std::cmp::Ordering::Equal) + }); for r in rows.iter().take(15) { print_row("", r); } @@ -507,8 +518,12 @@ fn main() { let shipping_candidate_idx = candidates.len(); candidates.push(("shipping default".to_string(), shipping_row.clone())); - let scores = - compute_weighted_scores(&candidates.iter().map(|(_, r)| r.clone()).collect::>()); + let scores = compute_weighted_scores( + &candidates + .iter() + .map(|(_, r)| r.clone()) + .collect::>(), + ); let mut ranked: Vec<(usize, f64)> = scores.iter().copied().enumerate().collect(); ranked.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal)); @@ -529,7 +544,10 @@ fn main() { " balance bonus: min(yellow_width, red_width), saturates at {:.0} min", BALANCE_SATURATION_MIN, ); - println!(" candidate set: {} Pareto-front rows + 1 shipping default", rows.len()); + println!( + " candidate set: {} Pareto-front rows + 1 shipping default", + rows.len() + ); println!(); println!("{:>4} {:>5} source", "rank", "score"); print_header(); @@ -548,7 +566,10 @@ fn main() { .map(|p| p + 1) .unwrap_or(0); - let max_work = candidates.iter().map(|(_, r)| r.work_fail).fold(0.0, f64::max); + let max_work = candidates + .iter() + .map(|(_, r)| r.work_fail) + .fold(0.0, f64::max); println!("=== RECOMMENDED PICK ({top_label}) ==="); println!( @@ -591,9 +612,7 @@ fn main() { " • {:.2} button presses/day total — {}", top.presses, press_note, ); - println!( - " (counts: boot + 13:00 retap + warning-phase reactions + death-restarts)" - ); + println!(" (counts: boot + 13:00 retap + warning-phase reactions + death-restarts)"); println!( " • warning phases: yellow {} min, red {} min, fast-red {} min (balance score {:.2})", yellow_w, @@ -629,12 +648,24 @@ fn main() { /// phases, computed as `min(YA - RA, RA - FRA)` saturated at /// `BALANCE_SATURATION_MIN`. fn compute_weighted_scores(rows: &[Row]) -> Vec { - let work_min = rows.iter().map(|r| r.work_fail).fold(f64::INFINITY, f64::min); - let work_max = rows.iter().map(|r| r.work_fail).fold(f64::NEG_INFINITY, f64::max); + let work_min = rows + .iter() + .map(|r| r.work_fail) + .fold(f64::INFINITY, f64::min); + let work_max = rows + .iter() + .map(|r| r.work_fail) + .fold(f64::NEG_INFINITY, f64::max); let lunch_min = rows.iter().map(|r| r.lunch).fold(f64::INFINITY, f64::min); - let lunch_max = rows.iter().map(|r| r.lunch).fold(f64::NEG_INFINITY, f64::max); + let lunch_max = rows + .iter() + .map(|r| r.lunch) + .fold(f64::NEG_INFINITY, f64::max); let after_min = rows.iter().map(|r| r.after).fold(f64::INFINITY, f64::min); - let after_max = rows.iter().map(|r| r.after).fold(f64::NEG_INFINITY, f64::max); + let after_max = rows + .iter() + .map(|r| r.after) + .fold(f64::NEG_INFINITY, f64::max); rows.iter() .map(|r| { @@ -676,12 +707,20 @@ fn balance_score_for(r: &Row) -> f64 { /// Normalize a minimize-direction value to `[0, 1]` (best→1, worst→0). fn norm_min(v: f64, lo: f64, hi: f64) -> f64 { - if (hi - lo).abs() < 1e-12 { 1.0 } else { (hi - v) / (hi - lo) } + if (hi - lo).abs() < 1e-12 { + 1.0 + } else { + (hi - v) / (hi - lo) + } } /// Normalize a maximize-direction value to `[0, 1]` (best→1, worst→0). fn norm_max(v: f64, lo: f64, hi: f64) -> f64 { - if (hi - lo).abs() < 1e-12 { 1.0 } else { (v - lo) / (hi - lo) } + if (hi - lo).abs() < 1e-12 { + 1.0 + } else { + (v - lo) / (hi - lo) + } } /// Render `worst / best` as e.g. "7.5×" for the recommendation rationale. diff --git a/examples/random_search.rs b/examples/random_search.rs index 1739e26..df9e7b0 100644 --- a/examples/random_search.rs +++ b/examples/random_search.rs @@ -24,7 +24,11 @@ impl Problem for Sphere2D { fn main() { let initializer = RealBounds::new(vec![(-5.0, 5.0), (-5.0, 5.0)]); - let config = RandomSearchConfig { iterations: 500, batch_size: 1, seed: 7 }; + let config = RandomSearchConfig { + iterations: 500, + batch_size: 1, + seed: 7, + }; let mut optimizer = RandomSearch::new(config, initializer); let result = optimizer.run(&Sphere2D); diff --git a/examples/toy_nsga2.rs b/examples/toy_nsga2.rs index 795ff33..466a733 100644 --- a/examples/toy_nsga2.rs +++ b/examples/toy_nsga2.rs @@ -26,7 +26,11 @@ impl Problem for SchafferN1 { fn main() { let initializer = RealBounds::new(vec![(-5.0, 5.0)]); let variation = GaussianMutation { sigma: 0.2 }; - let config = Nsga2Config { population_size: 60, generations: 80, seed: 42 }; + let config = Nsga2Config { + population_size: 60, + generations: 80, + seed: 42, + }; let mut optimizer = Nsga2::new(config, initializer, variation); let result = optimizer.run(&SchafferN1); diff --git a/src/algorithms/age_moea.rs b/src/algorithms/age_moea.rs index 8fcbe46..cc53b0b 100644 --- a/src/algorithms/age_moea.rs +++ b/src/algorithms/age_moea.rs @@ -26,7 +26,11 @@ pub struct AgeMoeaConfig { impl Default for AgeMoeaConfig { fn default() -> Self { - Self { population_size: 100, generations: 250, seed: 42 } + Self { + population_size: 100, + generations: 250, + seed: 42, + } } } @@ -49,7 +53,11 @@ pub struct AgeMoea { impl AgeMoea { /// Construct an `AgeMoea`. pub fn new(config: AgeMoeaConfig, initializer: I, variation: V) -> Self { - Self { config, initializer, variation } + Self { + config, + initializer, + variation, + } } } @@ -61,7 +69,10 @@ where V: Variation, { fn run(&mut self, problem: &P) -> OptimizationResult { - assert!(self.config.population_size > 0, "AgeMoea population_size must be > 0"); + assert!( + self.config.population_size > 0, + "AgeMoea population_size must be > 0" + ); let n = self.config.population_size; let objectives = problem.objectives(); let mut rng = rng_from_seed(self.config.seed); @@ -77,10 +88,15 @@ where while offspring_decisions.len() < n { let p1 = rng.random_range(0..population.len()); let p2 = rng.random_range(0..population.len()); - let parents = - vec![population[p1].decision.clone(), population[p2].decision.clone()]; + let parents = vec![ + population[p1].decision.clone(), + population[p2].decision.clone(), + ]; let children = self.variation.vary(&parents, &mut rng); - assert!(!children.is_empty(), "AgeMoea variation returned no children"); + assert!( + !children.is_empty(), + "AgeMoea variation returned no children" + ); for child in children { if offspring_decisions.len() >= n { break; @@ -153,7 +169,11 @@ fn environmental_selection( .iter() .map(|c| { let oriented = objectives.as_minimization(&c.evaluation.objectives); - oriented.iter().enumerate().map(|(k, v)| (v - ideal[k]).max(0.0)).collect() + oriented + .iter() + .enumerate() + .map(|(k, v)| (v - ideal[k]).max(0.0)) + .collect() }) .collect(); @@ -199,15 +219,14 @@ fn lp_norm(v: &[f64], p: f64) -> f64 { } fn lp_distance(a: &[f64], b: &[f64], p: f64) -> f64 { - a.iter().zip(b.iter()).map(|(x, y)| (x - y).abs().powf(p)).sum::().powf(1.0 / p) + a.iter() + .zip(b.iter()) + .map(|(x, y)| (x - y).abs().powf(p)) + .sum::() + .powf(1.0 / p) } -fn nearest_neighbor_distance( - i: usize, - translated: &[Vec], - selected: &[usize], - p: f64, -) -> f64 { +fn nearest_neighbor_distance(i: usize, translated: &[Vec], selected: &[usize], p: f64) -> f64 { if selected.is_empty() { return f64::INFINITY; } @@ -295,7 +314,11 @@ mod tests { mutation: PolynomialMutation::new(bounds, 20.0, 1.0), }; AgeMoea::new( - AgeMoeaConfig { population_size: 20, generations: 15, seed }, + AgeMoeaConfig { + population_size: 20, + generations: 15, + seed, + }, initializer, variation, ) @@ -315,10 +338,16 @@ mod tests { let mut b = make_optimizer(99); let ra = a.run(&SchafferN1); let rb = b.run(&SchafferN1); - let oa: Vec> = - ra.pareto_front.iter().map(|c| c.evaluation.objectives.clone()).collect(); - let ob: Vec> = - rb.pareto_front.iter().map(|c| c.evaluation.objectives.clone()).collect(); + let oa: Vec> = ra + .pareto_front + .iter() + .map(|c| c.evaluation.objectives.clone()) + .collect(); + let ob: Vec> = rb + .pareto_front + .iter() + .map(|c| c.evaluation.objectives.clone()) + .collect(); assert_eq!(oa, ob); } @@ -332,7 +361,11 @@ mod tests { mutation: PolynomialMutation::new(bounds, 20.0, 1.0), }; let mut opt = AgeMoea::new( - AgeMoeaConfig { population_size: 0, generations: 1, seed: 0 }, + AgeMoeaConfig { + population_size: 0, + generations: 1, + seed: 0, + }, initializer, variation, ); diff --git a/src/algorithms/ant_colony_tsp.rs b/src/algorithms/ant_colony_tsp.rs index e25f4b1..424937d 100644 --- a/src/algorithms/ant_colony_tsp.rs +++ b/src/algorithms/ant_colony_tsp.rs @@ -70,10 +70,16 @@ impl AntColonyTsp { /// and has a zero diagonal. pub fn new(config: AntColonyTspConfig, distances: Vec>) -> Self { let n = distances.len(); - assert!(n >= 2, "AntColonyTsp distances matrix must have >= 2 cities"); + assert!( + n >= 2, + "AntColonyTsp distances matrix must have >= 2 cities" + ); for (i, row) in distances.iter().enumerate() { assert_eq!(row.len(), n, "AntColonyTsp distances matrix must be square"); - assert_eq!(row[i], 0.0, "AntColonyTsp distance from city to itself must be 0"); + assert_eq!( + row[i], 0.0, + "AntColonyTsp distance from city to itself must be 0" + ); } Self { config, distances } } @@ -99,12 +105,15 @@ where let eta: Vec> = self .distances .iter() - .map(|row| row.iter().map(|&d| if d > 0.0 { 1.0 / d } else { 0.0 }).collect()) + .map(|row| { + row.iter() + .map(|&d| if d > 0.0 { 1.0 / d } else { 0.0 }) + .collect() + }) .collect(); // Pheromone matrix. - let mut pheromone: Vec> = - vec![vec![self.config.initial_pheromone; n]; n]; + let mut pheromone: Vec> = vec![vec![self.config.initial_pheromone; n]; n]; let mut best_decision: Option> = None; let mut best_eval: Option = None; @@ -153,7 +162,12 @@ where // Pheromone deposit on each ant's tour. for (tour, eval) in tours.iter().zip(tour_evals.iter()) { - let length = eval.objectives.first().copied().unwrap_or(f64::INFINITY).max(1e-12); + let length = eval + .objectives + .first() + .copied() + .unwrap_or(f64::INFINITY) + .max(1e-12); let deposit = self.config.deposit / length; for w in tour.windows(2) { let (i, j) = (w[0], w[1]); @@ -313,10 +327,7 @@ mod tests { type Decision = Vec; fn objectives(&self) -> ObjectiveSpace { - ObjectiveSpace::new(vec![ - Objective::minimize("a"), - Objective::minimize("b"), - ]) + ObjectiveSpace::new(vec![Objective::minimize("a"), Objective::minimize("b")]) } fn evaluate(&self, _tour: &Vec) -> Evaluation { diff --git a/src/algorithms/bayesian_opt.rs b/src/algorithms/bayesian_opt.rs index 8f694b9..1c42b7e 100644 --- a/src/algorithms/bayesian_opt.rs +++ b/src/algorithms/bayesian_opt.rs @@ -85,8 +85,14 @@ where self.config.initial_samples >= 2, "BayesianOpt initial_samples must be >= 2", ); - assert!(self.config.signal_variance > 0.0, "BayesianOpt signal_variance must be > 0"); - assert!(self.config.noise_variance > 0.0, "BayesianOpt noise_variance must be > 0"); + assert!( + self.config.signal_variance > 0.0, + "BayesianOpt signal_variance must be > 0" + ); + assert!( + self.config.noise_variance > 0.0, + "BayesianOpt noise_variance must be > 0" + ); assert!( self.config.acquisition_samples >= 1, "BayesianOpt acquisition_samples must be >= 1", @@ -99,25 +105,24 @@ where let direction = objectives.objectives[0].direction; let dim = self.bounds.bounds.len(); if let Some(ls) = &self.config.length_scales { - assert_eq!(ls.len(), dim, "BayesianOpt length_scales.len() must equal dim"); + assert_eq!( + ls.len(), + dim, + "BayesianOpt length_scales.len() must equal dim" + ); } - let length_scales: Vec = self - .config - .length_scales - .clone() - .unwrap_or_else(|| { - self.bounds - .bounds - .iter() - .map(|&(lo, hi)| 0.2 * (hi - lo).max(1e-9)) - .collect() - }); + let length_scales: Vec = self.config.length_scales.clone().unwrap_or_else(|| { + self.bounds + .bounds + .iter() + .map(|&(lo, hi)| 0.2 * (hi - lo).max(1e-9)) + .collect() + }); let mut rng = rng_from_seed(self.config.seed); // ---------------- Initial random design ---------------- - let mut decisions: Vec> = Vec::with_capacity( - self.config.initial_samples + self.config.iterations, - ); + let mut decisions: Vec> = + Vec::with_capacity(self.config.initial_samples + self.config.iterations); let mut targets: Vec = Vec::with_capacity(decisions.capacity()); let mut evaluations = Vec::with_capacity(decisions.capacity()); for _ in 0..self.config.initial_samples { @@ -153,8 +158,7 @@ where } }; - let best_target = - targets.iter().cloned().fold(f64::INFINITY, f64::min); + let best_target = targets.iter().cloned().fold(f64::INFINITY, f64::min); // Maximize EI by best-of-N random sampling. let mut best_x = sample_uniform_in_bounds(&self.bounds, &mut rng); @@ -183,7 +187,11 @@ where .collect(); let mut best_idx = 0; for i in 1..final_pop.len() { - if better(&final_pop[i].evaluation, &final_pop[best_idx].evaluation, direction) { + if better( + &final_pop[i].evaluation, + &final_pop[best_idx].evaluation, + direction, + ) { best_idx = i; } } @@ -232,7 +240,13 @@ fn sample_uniform_in_bounds(bounds: &RealBounds, rng: &mut Rng) -> Vec { bounds .bounds .iter() - .map(|&(lo, hi)| if lo == hi { lo } else { lo + (hi - lo) * rng.random::() }) + .map(|&(lo, hi)| { + if lo == hi { + lo + } else { + lo + (hi - lo) * rng.random::() + } + }) .collect() } @@ -289,10 +303,19 @@ impl GpPosterior { let n = self.decisions.len(); let mut k_star = vec![0.0_f64; n]; for (i, k_star_i) in k_star.iter_mut().enumerate() { - *k_star_i = rbf_kernel(x, &self.decisions[i], &self.length_scales, self.signal_variance); + *k_star_i = rbf_kernel( + x, + &self.decisions[i], + &self.length_scales, + self.signal_variance, + ); } let _ = n; - let mu: f64 = k_star.iter().zip(self.alpha.iter()).map(|(a, b)| a * b).sum(); + let mu: f64 = k_star + .iter() + .zip(self.alpha.iter()) + .map(|(a, b)| a * b) + .sum(); // Var = k(x,x) - k_star^T · K^{-1} · k_star // Compute K^{-1}·k_star = solve_upper_transpose(L, solve_lower(L, k_star)) let v_temp = crate::internal::cholesky::solve_lower(&self.chol_l, &k_star); @@ -335,8 +358,7 @@ fn erf(x: f64) -> f64 { let sign = if x < 0.0 { -1.0 } else { 1.0 }; let x = x.abs(); let t = 1.0 / (1.0 + p * x); - let y = 1.0 - - (((((a5 * t + a4) * t) + a3) * t + a2) * t + a1) * t * (-x * x).exp(); + let y = 1.0 - (((((a5 * t + a4) * t) + a3) * t + a2) * t + a1) * t * (-x * x).exp(); sign * y } diff --git a/src/algorithms/cma_es.rs b/src/algorithms/cma_es.rs index 77485e1..1fee069 100644 --- a/src/algorithms/cma_es.rs +++ b/src/algorithms/cma_es.rs @@ -122,9 +122,7 @@ where // Standard CMA-ES strategy parameters (Hansen tutorial §7.1). // --------------------------------------------------------------- let c_sigma = (mu_eff + 2.0) / (n_f + mu_eff + 5.0); - let d_sigma = 1.0 - + 2.0 * ((mu_eff - 1.0) / (n_f + 1.0)).sqrt().max(0.0) - + c_sigma; + let d_sigma = 1.0 + 2.0 * ((mu_eff - 1.0) / (n_f + 1.0)).sqrt().max(0.0) + c_sigma; let c_c = (4.0 + mu_eff / n_f) / (n_f + 4.0 + 2.0 * mu_eff / n_f); let c_1 = 2.0 / ((n_f + 1.3).powi(2) + mu_eff); let c_mu = ((1.0 - c_1) * 2.0 * (mu_eff - 2.0 + 1.0 / mu_eff) @@ -150,7 +148,11 @@ where .map(|(v, &(lo, hi))| v.clamp(lo, hi)) .collect() } else { - self.bounds.bounds.iter().map(|&(lo, hi)| 0.5 * (lo + hi)).collect() + self.bounds + .bounds + .iter() + .map(|&(lo, hi)| 0.5 * (lo + hi)) + .collect() }; let mut sigma = self.config.initial_sigma; // Covariance C, eigenvectors B, eigenvalues d (square roots of eigenvalues of C). @@ -181,10 +183,7 @@ where let (eigenvalues, eigenvectors) = symmetric_eigen(&c_matrix, 1e-14, 100); // eigenvectors is sorted descending; we don't depend on order // for sampling correctness, but we do need positive eigenvalues. - d = eigenvalues - .iter() - .map(|&v| v.max(1e-20).sqrt()) - .collect(); + d = eigenvalues.iter().map(|&v| v.max(1e-20).sqrt()).collect(); // B is the matrix whose columns are the eigenvectors. The // helper returns `eigenvectors[i]` as the i-th *eigenvector*, // so b[r][c] should equal eigenvectors[c][r]. @@ -232,7 +231,11 @@ where // Sort offspring by fitness ascending (best first). let mut order: Vec = (0..lambda).collect(); order.sort_by(|&a, &b_| { - compare_so(&evaluated[a].evaluation, &evaluated[b_].evaluation, direction) + compare_so( + &evaluated[a].evaluation, + &evaluated[b_].evaluation, + direction, + ) }); // ----- Recompute mean from the μ best (weighted average of x) ----- @@ -284,13 +287,13 @@ where // ----- Evolution path for C: p_c = (1 - c_c) p_c + h_σ · sqrt(c_c (2 - c_c) μ_eff) · (m_new - m_old)/σ ----- let factor_p_c = h_sigma * (c_c * (2.0 - c_c) * mu_eff).sqrt(); for i in 0..n { - p_c[i] = (1.0 - c_c) * p_c[i] - + factor_p_c * (mean[i] - old_mean[i]) / sigma; + p_c[i] = (1.0 - c_c) * p_c[i] + factor_p_c * (mean[i] - old_mean[i]) / sigma; } // ----- Covariance matrix update (rank-1 + rank-μ) ----- let delta_h = (1.0 - h_sigma) * c_c * (2.0 - c_c); - #[allow(clippy::needless_range_loop)] // body uses both i and j to index c_matrix and offspring. + #[allow(clippy::needless_range_loop)] + // body uses both i and j to index c_matrix and offspring. for i in 0..n { for j in 0..n { let mut update = (1.0 - c_1 - c_mu) * c_matrix[i][j] @@ -443,7 +446,7 @@ mod tests { generations: 30, initial_sigma: 0.5, eigen_decomposition_period: 1, - initial_mean: None, + initial_mean: None, seed: 99, }; let mut a = CmaEs::new(cfg.clone(), RealBounds::new(vec![(-5.0, 5.0)])); @@ -459,10 +462,7 @@ mod tests { #[test] #[should_panic(expected = "single-objective")] fn multi_objective_panics() { - let mut opt = CmaEs::new( - CmaEsConfig::default(), - RealBounds::new(vec![(-5.0, 5.0)]), - ); + let mut opt = CmaEs::new(CmaEsConfig::default(), RealBounds::new(vec![(-5.0, 5.0)])); let _ = opt.run(&SchafferN1); } diff --git a/src/algorithms/differential_evolution.rs b/src/algorithms/differential_evolution.rs index 1c93ef7..c2780a9 100644 --- a/src/algorithms/differential_evolution.rs +++ b/src/algorithms/differential_evolution.rs @@ -91,8 +91,10 @@ where }; let initial_pop = evaluate_batch(problem, decisions.clone()); let mut evaluations = initial_pop.len(); - let mut evals: Vec = - initial_pop.iter().map(|c| c.evaluation.objectives[0]).collect(); + let mut evals: Vec = initial_pop + .iter() + .map(|c| c.evaluation.objectives[0]) + .collect(); for _gen in 0..self.config.generations { // Phase 1 (serial): construct one trial per target. RNG state is @@ -151,7 +153,11 @@ where } } -fn pick_three_distinct(n: usize, exclude: usize, rng: &mut crate::core::rng::Rng) -> (usize, usize, usize) { +fn pick_three_distinct( + n: usize, + exclude: usize, + rng: &mut crate::core::rng::Rng, +) -> (usize, usize, usize) { let pick = |rng: &mut crate::core::rng::Rng, taken: &[usize]| -> usize { loop { let v = rng.random_range(0..n); @@ -185,7 +191,10 @@ mod tests { ); let r = opt.run(&Sphere1D); let best = r.best.unwrap(); - assert!(best.evaluation.objectives[0] < 1e-3, "DE should converge near 0"); + assert!( + best.evaluation.objectives[0] < 1e-3, + "DE should converge near 0" + ); } #[test] @@ -197,8 +206,7 @@ mod tests { crossover_probability: 0.7, seed: 99, }; - let mut a = - DifferentialEvolution::new(cfg.clone(), RealBounds::new(vec![(-5.0, 5.0)])); + let mut a = DifferentialEvolution::new(cfg.clone(), RealBounds::new(vec![(-5.0, 5.0)])); let mut b = DifferentialEvolution::new(cfg, RealBounds::new(vec![(-5.0, 5.0)])); let ra = a.run(&Sphere1D); let rb = b.run(&Sphere1D); diff --git a/src/algorithms/epsilon_moea.rs b/src/algorithms/epsilon_moea.rs index 6123d47..a042f55 100644 --- a/src/algorithms/epsilon_moea.rs +++ b/src/algorithms/epsilon_moea.rs @@ -52,7 +52,11 @@ pub struct EpsilonMoea { impl EpsilonMoea { /// Construct an `EpsilonMoea`. pub fn new(config: EpsilonMoeaConfig, initializer: I, variation: V) -> Self { - Self { config, initializer, variation } + Self { + config, + initializer, + variation, + } } } @@ -64,7 +68,10 @@ where V: Variation, { fn run(&mut self, problem: &P) -> OptimizationResult { - assert!(self.config.population_size > 0, "EpsilonMoea population_size must be > 0"); + assert!( + self.config.population_size > 0, + "EpsilonMoea population_size must be > 0" + ); let n = self.config.population_size; let objectives = problem.objectives(); assert_eq!( @@ -110,7 +117,10 @@ where }; let parents = vec![parent_a, parent_b]; let children = self.variation.vary(&parents, &mut rng); - assert!(!children.is_empty(), "EpsilonMoea variation returned no children"); + assert!( + !children.is_empty(), + "EpsilonMoea variation returned no children" + ); let child_decision = children.into_iter().next().unwrap(); let child_eval = problem.evaluate(&child_decision); evaluations += 1; @@ -205,12 +215,8 @@ fn insert_into_epsilon_archive( } if let Some(idx) = child_box_index { // Same box: keep whichever is closer to box's ideal corner. - let member_corner_dist = corner_distance( - &archive[idx].evaluation, - objectives, - epsilon, - &child_box, - ); + let member_corner_dist = + corner_distance(&archive[idx].evaluation, objectives, epsilon, &child_box); if child_corner_dist < member_corner_dist { archive[idx] = child; } @@ -302,10 +308,16 @@ mod tests { let mut b = make_optimizer(99); let ra = a.run(&SchafferN1); let rb = b.run(&SchafferN1); - let oa: Vec> = - ra.pareto_front.iter().map(|c| c.evaluation.objectives.clone()).collect(); - let ob: Vec> = - rb.pareto_front.iter().map(|c| c.evaluation.objectives.clone()).collect(); + let oa: Vec> = ra + .pareto_front + .iter() + .map(|c| c.evaluation.objectives.clone()) + .collect(); + let ob: Vec> = rb + .pareto_front + .iter() + .map(|c| c.evaluation.objectives.clone()) + .collect(); assert_eq!(oa, ob); } diff --git a/src/algorithms/genetic_algorithm.rs b/src/algorithms/genetic_algorithm.rs index ed649bb..74882e3 100644 --- a/src/algorithms/genetic_algorithm.rs +++ b/src/algorithms/genetic_algorithm.rs @@ -59,7 +59,11 @@ pub struct GeneticAlgorithm { impl GeneticAlgorithm { /// Construct a `GeneticAlgorithm`. pub fn new(config: GeneticAlgorithmConfig, initializer: I, variation: V) -> Self { - Self { config, initializer, variation } + Self { + config, + initializer, + variation, + } } } @@ -109,7 +113,10 @@ where &mut rng, ); let children = self.variation.vary(&parents_decisions, &mut rng); - assert!(!children.is_empty(), "GeneticAlgorithm variation returned no children"); + assert!( + !children.is_empty(), + "GeneticAlgorithm variation returned no children" + ); for child in children { if offspring_decisions.len() >= n { break; @@ -123,13 +130,8 @@ where evaluations += offspring.len(); // --- Phase 3: survival = elites + best offspring --- - population = survival_selection( - &population, - offspring, - direction, - n, - self.config.elitism, - ); + population = + survival_selection(&population, offspring, direction, n, self.config.elitism); } let best = best_candidate(&population, &objectives); @@ -202,8 +204,10 @@ mod tests { fn make_optimizer( seed: u64, - ) -> GeneticAlgorithm> - { + ) -> GeneticAlgorithm< + RealBounds, + CompositeVariation, + > { let bounds = vec![(-5.0, 5.0)]; let initializer = RealBounds::new(bounds.clone()); let variation = CompositeVariation { diff --git a/src/algorithms/grea.rs b/src/algorithms/grea.rs index ff5e9ab..8de5c92 100644 --- a/src/algorithms/grea.rs +++ b/src/algorithms/grea.rs @@ -51,7 +51,11 @@ pub struct Grea { impl Grea { /// Construct a `Grea`. pub fn new(config: GreaConfig, initializer: I, variation: V) -> Self { - Self { config, initializer, variation } + Self { + config, + initializer, + variation, + } } } @@ -63,8 +67,14 @@ where V: Variation, { fn run(&mut self, problem: &P) -> OptimizationResult { - assert!(self.config.population_size > 0, "Grea population_size must be > 0"); - assert!(self.config.grid_divisions >= 1, "Grea grid_divisions must be >= 1"); + assert!( + self.config.population_size > 0, + "Grea population_size must be > 0" + ); + assert!( + self.config.grid_divisions >= 1, + "Grea grid_divisions must be >= 1" + ); let n = self.config.population_size; let objectives = problem.objectives(); let mut rng = rng_from_seed(self.config.seed); @@ -80,8 +90,10 @@ where while offspring_decisions.len() < n { let p1 = rng.random_range(0..population.len()); let p2 = rng.random_range(0..population.len()); - let parents = - vec![population[p1].decision.clone(), population[p2].decision.clone()]; + let parents = vec![ + population[p1].decision.clone(), + population[p2].decision.clone(), + ]; let children = self.variation.vary(&parents, &mut rng); assert!(!children.is_empty(), "Grea variation returned no children"); for child in children { @@ -98,7 +110,8 @@ where let mut combined: Vec> = Vec::with_capacity(2 * n); combined.extend(population); combined.extend(offspring); - population = environmental_selection(combined, &objectives, n, self.config.grid_divisions); + population = + environmental_selection(combined, &objectives, n, self.config.grid_divisions); } let front = pareto_front(&population, &objectives); @@ -253,10 +266,16 @@ mod tests { let mut b = make_optimizer(99); let ra = a.run(&SchafferN1); let rb = b.run(&SchafferN1); - let oa: Vec> = - ra.pareto_front.iter().map(|c| c.evaluation.objectives.clone()).collect(); - let ob: Vec> = - rb.pareto_front.iter().map(|c| c.evaluation.objectives.clone()).collect(); + let oa: Vec> = ra + .pareto_front + .iter() + .map(|c| c.evaluation.objectives.clone()) + .collect(); + let ob: Vec> = rb + .pareto_front + .iter() + .map(|c| c.evaluation.objectives.clone()) + .collect(); assert_eq!(oa, ob); } } diff --git a/src/algorithms/hill_climber.rs b/src/algorithms/hill_climber.rs index f25abdc..db3378f 100644 --- a/src/algorithms/hill_climber.rs +++ b/src/algorithms/hill_climber.rs @@ -19,7 +19,10 @@ pub struct HillClimberConfig { impl Default for HillClimberConfig { fn default() -> Self { - Self { iterations: 1000, seed: 42 } + Self { + iterations: 1000, + seed: 42, + } } } @@ -44,7 +47,11 @@ pub struct HillClimber { impl HillClimber { /// Construct a `HillClimber`. pub fn new(config: HillClimberConfig, initializer: I, variation: V) -> Self { - Self { config, initializer, variation } + Self { + config, + initializer, + variation, + } } } @@ -65,7 +72,10 @@ where let mut rng = rng_from_seed(self.config.seed); let mut initial = self.initializer.initialize(1, &mut rng); - assert!(!initial.is_empty(), "HillClimber initializer returned no decisions"); + assert!( + !initial.is_empty(), + "HillClimber initializer returned no decisions" + ); let mut current_decision = initial.remove(0); let mut current_eval = problem.evaluate(¤t_decision); let mut evaluations = 1usize; @@ -73,7 +83,10 @@ where for _ in 0..self.config.iterations { let parents = vec![current_decision.clone()]; let children = self.variation.vary(&parents, &mut rng); - assert!(!children.is_empty(), "HillClimber variation returned no children"); + assert!( + !children.is_empty(), + "HillClimber variation returned no children" + ); let child_decision = children.into_iter().next().unwrap(); let child_eval = problem.evaluate(&child_decision); evaluations += 1; @@ -116,7 +129,10 @@ mod tests { fn make_optimizer(seed: u64) -> HillClimber { HillClimber::new( - HillClimberConfig { iterations: 500, seed }, + HillClimberConfig { + iterations: 500, + seed, + }, RealBounds::new(vec![(-5.0, 5.0)]), GaussianMutation { sigma: 0.3 }, ) @@ -127,7 +143,11 @@ mod tests { let mut opt = make_optimizer(1); let r = opt.run(&Sphere1D); let best = r.best.unwrap(); - assert!(best.evaluation.objectives[0] < 1e-2, "got f = {}", best.evaluation.objectives[0]); + assert!( + best.evaluation.objectives[0] < 1e-2, + "got f = {}", + best.evaluation.objectives[0] + ); } #[test] diff --git a/src/algorithms/hype.rs b/src/algorithms/hype.rs index fc56090..d6cf141 100644 --- a/src/algorithms/hype.rs +++ b/src/algorithms/hype.rs @@ -61,7 +61,11 @@ pub struct Hype { impl Hype { /// Construct a `Hype`. pub fn new(config: HypeConfig, initializer: I, variation: V) -> Self { - Self { config, initializer, variation } + Self { + config, + initializer, + variation, + } } } @@ -73,7 +77,10 @@ where V: Variation, { fn run(&mut self, problem: &P) -> OptimizationResult { - assert!(self.config.population_size > 0, "Hype population_size must be > 0"); + assert!( + self.config.population_size > 0, + "Hype population_size must be > 0" + ); assert!(self.config.mc_samples > 0, "Hype mc_samples must be > 0"); let n = self.config.population_size; let objectives = problem.objectives(); @@ -93,14 +100,21 @@ where for _ in 0..self.config.generations { // Phase 1: parent selection + variation (random tournament on // a fitness-by-HV-estimate proxy). - let fitness = - hype_fitness(&population, &objectives, &reference, self.config.mc_samples, &mut rng); + let fitness = hype_fitness( + &population, + &objectives, + &reference, + self.config.mc_samples, + &mut rng, + ); let mut offspring_decisions: Vec = Vec::with_capacity(n); while offspring_decisions.len() < n { let p1 = binary_tournament(&fitness, &mut rng); let p2 = binary_tournament(&fitness, &mut rng); - let parents = - vec![population[p1].decision.clone(), population[p2].decision.clone()]; + let parents = vec![ + population[p1].decision.clone(), + population[p2].decision.clone(), + ]; let children = self.variation.vary(&parents, &mut rng); assert!(!children.is_empty(), "Hype variation returned no children"); for child in children { @@ -140,8 +154,13 @@ where // by largest HV contribution. let pool: Vec<&Candidate> = splitting.iter().map(|&i| &combined[i]).collect(); - let contributions = - estimate_contributions(&pool, &objectives, &reference, self.config.mc_samples, &mut rng); + let contributions = estimate_contributions( + &pool, + &objectives, + &reference, + self.config.mc_samples, + &mut rng, + ); let mut order: Vec = (0..splitting.len()).collect(); order.sort_by(|&a, &b| { contributions[b] @@ -154,7 +173,10 @@ where } // Materialize the next generation. - population = keep_indices.into_iter().map(|i| combined[i].clone()).collect(); + population = keep_indices + .into_iter() + .map(|i| combined[i].clone()) + .collect(); } let front = pareto_front(&population, &objectives); @@ -321,10 +343,16 @@ mod tests { let mut b = make_optimizer(99); let ra = a.run(&SchafferN1); let rb = b.run(&SchafferN1); - let oa: Vec> = - ra.pareto_front.iter().map(|c| c.evaluation.objectives.clone()).collect(); - let ob: Vec> = - rb.pareto_front.iter().map(|c| c.evaluation.objectives.clone()).collect(); + let oa: Vec> = ra + .pareto_front + .iter() + .map(|c| c.evaluation.objectives.clone()) + .collect(); + let ob: Vec> = rb + .pareto_front + .iter() + .map(|c| c.evaluation.objectives.clone()) + .collect(); assert_eq!(oa, ob); } diff --git a/src/algorithms/hyperband.rs b/src/algorithms/hyperband.rs index e4e85c2..310ae2d 100644 --- a/src/algorithms/hyperband.rs +++ b/src/algorithms/hyperband.rs @@ -31,7 +31,12 @@ pub struct HyperbandConfig { impl Default for HyperbandConfig { fn default() -> Self { - Self { max_budget: 81.0, eta: 3.0, max_brackets: 5, seed: 42 } + Self { + max_budget: 81.0, + eta: 3.0, + max_brackets: 5, + seed: 42, + } } } @@ -66,7 +71,11 @@ where { /// Construct a `Hyperband`. pub fn new(config: HyperbandConfig, initializer: I) -> Self { - Self { config, initializer, _marker: std::marker::PhantomData } + Self { + config, + initializer, + _marker: std::marker::PhantomData, + } } /// Run Hyperband on a multi-fidelity problem, returning the standard @@ -75,9 +84,15 @@ where where P: PartialProblem, { - assert!(self.config.max_budget > 0.0, "Hyperband max_budget must be > 0"); + assert!( + self.config.max_budget > 0.0, + "Hyperband max_budget must be > 0" + ); assert!(self.config.eta > 1.0, "Hyperband eta must be > 1"); - assert!(self.config.max_brackets >= 1, "Hyperband max_brackets must be >= 1"); + assert!( + self.config.max_brackets >= 1, + "Hyperband max_brackets must be >= 1" + ); let objectives = problem.objectives(); assert!( objectives.is_single_objective(), @@ -97,9 +112,8 @@ where // Brackets are indexed s = s_max, s_max - 1, ..., 0. for s in (0..=s_max).rev() { let s_f = s as f64; - let n = ((s_max as f64 + 1.0) / (s_f + 1.0) - * self.config.eta.powf(s_f)) - .ceil() as usize; + let n = + ((s_max as f64 + 1.0) / (s_f + 1.0) * self.config.eta.powf(s_f)).ceil() as usize; let r = self.config.max_budget / self.config.eta.powf(s_f); // Sample n configurations. @@ -268,10 +282,7 @@ mod tests { impl PartialProblem for MultiObj { type Decision = Vec; fn objectives(&self) -> ObjectiveSpace { - ObjectiveSpace::new(vec![ - Objective::minimize("a"), - Objective::minimize("b"), - ]) + ObjectiveSpace::new(vec![Objective::minimize("a"), Objective::minimize("b")]) } fn evaluate_at_budget(&self, _: &Vec, _: f64) -> Evaluation { Evaluation::new(vec![0.0, 0.0]) diff --git a/src/algorithms/ibea.rs b/src/algorithms/ibea.rs index 98f5ded..1c94f60 100644 --- a/src/algorithms/ibea.rs +++ b/src/algorithms/ibea.rs @@ -27,7 +27,12 @@ pub struct IbeaConfig { impl Default for IbeaConfig { fn default() -> Self { - Self { population_size: 100, generations: 250, kappa: 0.05, seed: 42 } + Self { + population_size: 100, + generations: 250, + kappa: 0.05, + seed: 42, + } } } @@ -45,7 +50,11 @@ pub struct Ibea { impl Ibea { /// Construct an `Ibea` optimizer. pub fn new(config: IbeaConfig, initializer: I, variation: V) -> Self { - Self { config, initializer, variation } + Self { + config, + initializer, + variation, + } } } @@ -57,7 +66,10 @@ where V: Variation, { fn run(&mut self, problem: &P) -> OptimizationResult { - assert!(self.config.population_size > 0, "Ibea population_size must be > 0"); + assert!( + self.config.population_size > 0, + "Ibea population_size must be > 0" + ); assert!(self.config.kappa > 0.0, "Ibea kappa must be > 0"); let n = self.config.population_size; let objectives = problem.objectives(); @@ -76,7 +88,10 @@ where while offspring_decisions.len() < n { let p1 = binary_tournament(&fitness, &mut rng); let p2 = binary_tournament(&fitness, &mut rng); - let parents = vec![population[p1].decision.clone(), population[p2].decision.clone()]; + let parents = vec![ + population[p1].decision.clone(), + population[p2].decision.clone(), + ]; let children = self.variation.vary(&parents, &mut rng); assert!(!children.is_empty(), "Ibea variation returned no children"); for child in children { @@ -204,11 +219,7 @@ fn environmental_selection( } /// Compute IBEA fitness without mutating, for use in tournament selection. -fn compute_fitness( - pool: &[Candidate], - objectives: &ObjectiveSpace, - kappa: f64, -) -> Vec { +fn compute_fitness(pool: &[Candidate], objectives: &ObjectiveSpace, kappa: f64) -> Vec { if pool.is_empty() { return Vec::new(); } @@ -284,7 +295,12 @@ mod tests { mutation: PolynomialMutation::new(bounds, 20.0, 1.0), }; Ibea::new( - IbeaConfig { population_size: 20, generations: 15, kappa: 0.05, seed }, + IbeaConfig { + population_size: 20, + generations: 15, + kappa: 0.05, + seed, + }, initializer, variation, ) @@ -304,10 +320,16 @@ mod tests { let mut b = make_optimizer(99); let ra = a.run(&SchafferN1); let rb = b.run(&SchafferN1); - let oa: Vec> = - ra.pareto_front.iter().map(|c| c.evaluation.objectives.clone()).collect(); - let ob: Vec> = - rb.pareto_front.iter().map(|c| c.evaluation.objectives.clone()).collect(); + let oa: Vec> = ra + .pareto_front + .iter() + .map(|c| c.evaluation.objectives.clone()) + .collect(); + let ob: Vec> = rb + .pareto_front + .iter() + .map(|c| c.evaluation.objectives.clone()) + .collect(); assert_eq!(oa, ob); } @@ -321,7 +343,12 @@ mod tests { mutation: PolynomialMutation::new(bounds, 20.0, 1.0), }; let mut opt = Ibea::new( - IbeaConfig { population_size: 0, generations: 1, kappa: 0.05, seed: 0 }, + IbeaConfig { + population_size: 0, + generations: 1, + kappa: 0.05, + seed: 0, + }, initializer, variation, ); diff --git a/src/algorithms/knea.rs b/src/algorithms/knea.rs index dce9f6c..b086d1f 100644 --- a/src/algorithms/knea.rs +++ b/src/algorithms/knea.rs @@ -26,7 +26,11 @@ pub struct KneaConfig { impl Default for KneaConfig { fn default() -> Self { - Self { population_size: 100, generations: 250, seed: 42 } + Self { + population_size: 100, + generations: 250, + seed: 42, + } } } @@ -48,7 +52,11 @@ pub struct Knea { impl Knea { /// Construct a `Knea`. pub fn new(config: KneaConfig, initializer: I, variation: V) -> Self { - Self { config, initializer, variation } + Self { + config, + initializer, + variation, + } } } @@ -60,7 +68,10 @@ where V: Variation, { fn run(&mut self, problem: &P) -> OptimizationResult { - assert!(self.config.population_size > 0, "Knea population_size must be > 0"); + assert!( + self.config.population_size > 0, + "Knea population_size must be > 0" + ); let n = self.config.population_size; let objectives = problem.objectives(); let mut rng = rng_from_seed(self.config.seed); @@ -75,8 +86,10 @@ where while offspring_decisions.len() < n { let p1 = rng.random_range(0..population.len()); let p2 = rng.random_range(0..population.len()); - let parents = - vec![population[p1].decision.clone(), population[p2].decision.clone()]; + let parents = vec![ + population[p1].decision.clone(), + population[p2].decision.clone(), + ]; let children = self.variation.vary(&parents, &mut rng); assert!(!children.is_empty(), "Knea variation returned no children"); for child in children { @@ -191,11 +204,7 @@ fn environmental_selection( /// Perpendicular distance from `point` to the hyperplane through the M /// extreme points (indices into `oriented`). -fn perpendicular_distance( - point: &[f64], - extremes: &[usize], - oriented: &[Vec], -) -> f64 { +fn perpendicular_distance(point: &[f64], extremes: &[usize], oriented: &[Vec]) -> f64 { let m = point.len(); if extremes.len() < m { // Degenerate: just return the L2 norm relative to first extreme. @@ -237,7 +246,11 @@ mod tests { mutation: PolynomialMutation::new(bounds, 20.0, 1.0), }; Knea::new( - KneaConfig { population_size: 20, generations: 15, seed }, + KneaConfig { + population_size: 20, + generations: 15, + seed, + }, initializer, variation, ) @@ -256,10 +269,16 @@ mod tests { let mut b = make_optimizer(99); let ra = a.run(&SchafferN1); let rb = b.run(&SchafferN1); - let oa: Vec> = - ra.pareto_front.iter().map(|c| c.evaluation.objectives.clone()).collect(); - let ob: Vec> = - rb.pareto_front.iter().map(|c| c.evaluation.objectives.clone()).collect(); + let oa: Vec> = ra + .pareto_front + .iter() + .map(|c| c.evaluation.objectives.clone()) + .collect(); + let ob: Vec> = rb + .pareto_front + .iter() + .map(|c| c.evaluation.objectives.clone()) + .collect(); assert_eq!(oa, ob); } } diff --git a/src/algorithms/mod.rs b/src/algorithms/mod.rs index 98dc015..7fc2abb 100644 --- a/src/algorithms/mod.rs +++ b/src/algorithms/mod.rs @@ -22,8 +22,8 @@ pub mod nsga3; pub mod one_plus_one_es; pub mod paes; pub(crate) mod parallel_eval; -pub mod pesa2; pub mod particle_swarm; +pub mod pesa2; pub mod random_search; pub mod rvea; pub mod simulated_annealing; diff --git a/src/algorithms/moead.rs b/src/algorithms/moead.rs index aab585a..c44da80 100644 --- a/src/algorithms/moead.rs +++ b/src/algorithms/moead.rs @@ -52,7 +52,11 @@ pub struct Moead { impl Moead { /// Construct a `Moead` optimizer. pub fn new(config: MoeadConfig, initializer: I, variation: V) -> Self { - Self { config, initializer, variation } + Self { + config, + initializer, + variation, + } } } @@ -119,7 +123,8 @@ where .collect(); for _ in 0..self.config.generations { - #[allow(clippy::needless_range_loop)] // Body indexes both `neighborhoods[i]` and `population[j]` via `nbh`. + #[allow(clippy::needless_range_loop)] + // Body indexes both `neighborhoods[i]` and `population[j]` via `nbh`. for i in 0..n { // Pick two distinct parents from the neighborhood. let nbh = &neighborhoods[i]; @@ -128,10 +133,15 @@ where while p2 == p1 && nbh.len() > 1 { p2 = *nbh.choose(&mut rng).unwrap(); } - let parents = - vec![population[p1].decision.clone(), population[p2].decision.clone()]; + let parents = vec![ + population[p1].decision.clone(), + population[p2].decision.clone(), + ]; let children = self.variation.vary(&parents, &mut rng); - assert!(!children.is_empty(), "MOEA/D variation returned no children"); + assert!( + !children.is_empty(), + "MOEA/D variation returned no children" + ); let child_decision = children.into_iter().next().unwrap(); let child_eval = problem.evaluate(&child_decision); evaluations += 1; @@ -152,8 +162,7 @@ where let g_cur = tchebycheff(&cur_oriented, &weights[j], &ideal); let g_new = tchebycheff(&oriented_child, &weights[j], &ideal); if g_new <= g_cur { - population[j] = - Candidate::new(child_decision.clone(), child_eval.clone()); + population[j] = Candidate::new(child_decision.clone(), child_eval.clone()); } } } @@ -188,7 +197,11 @@ fn tchebycheff(oriented_objectives: &[f64], weight: &[f64], ideal: &[f64]) -> f6 } fn weight_distance(a: &[f64], b: &[f64]) -> f64 { - a.iter().zip(b.iter()).map(|(x, y)| (x - y).powi(2)).sum::().sqrt() + a.iter() + .zip(b.iter()) + .map(|(x, y)| (x - y).powi(2)) + .sum::() + .sqrt() } #[cfg(test)] @@ -201,10 +214,7 @@ mod tests { fn make_optimizer( seed: u64, - ) -> Moead< - RealBounds, - CompositeVariation, - > { + ) -> Moead> { let bounds = vec![(-5.0, 5.0)]; let initializer = RealBounds::new(bounds.clone()); let variation = CompositeVariation { diff --git a/src/algorithms/mopso.rs b/src/algorithms/mopso.rs index d7e105f..36e24b9 100644 --- a/src/algorithms/mopso.rs +++ b/src/algorithms/mopso.rs @@ -73,7 +73,10 @@ where { fn run(&mut self, problem: &P) -> OptimizationResult { assert!(self.config.swarm_size >= 1, "Mopso swarm_size must be >= 1"); - assert!(self.config.archive_size >= 1, "Mopso archive_size must be >= 1"); + assert!( + self.config.archive_size >= 1, + "Mopso archive_size must be >= 1" + ); let objectives = problem.objectives(); assert!( objectives.is_multi_objective(), @@ -128,9 +131,8 @@ where let cognitive_term = self.config.cognitive * r1 * (pbest_decisions[i][j] - positions[i][j]); let social_term = self.config.social * r2 * (leader[j] - positions[i][j]); - let mut v = self.config.inertia * velocities[i][j] - + cognitive_term - + social_term; + let mut v = + self.config.inertia * velocities[i][j] + cognitive_term + social_term; if v > v_max[j] { v = v_max[j]; } else if v < -v_max[j] { @@ -148,8 +150,7 @@ where // --- Phase 3: serial pbest + archive updates --- for (i, cand) in evaluated.iter().enumerate() { - let dominance = - pareto_compare(&cand.evaluation, &pbest_evals[i], &objectives); + let dominance = pareto_compare(&cand.evaluation, &pbest_evals[i], &objectives); let replace = match dominance { Dominance::Dominates => true, Dominance::DominatedBy => false, @@ -212,10 +213,16 @@ mod tests { let mut b = make_optimizer(99); let ra = a.run(&SchafferN1); let rb = b.run(&SchafferN1); - let oa: Vec> = - ra.pareto_front.iter().map(|c| c.evaluation.objectives.clone()).collect(); - let ob: Vec> = - rb.pareto_front.iter().map(|c| c.evaluation.objectives.clone()).collect(); + let oa: Vec> = ra + .pareto_front + .iter() + .map(|c| c.evaluation.objectives.clone()) + .collect(); + let ob: Vec> = rb + .pareto_front + .iter() + .map(|c| c.evaluation.objectives.clone()) + .collect(); assert_eq!(oa, ob); } diff --git a/src/algorithms/nelder_mead.rs b/src/algorithms/nelder_mead.rs index 2816a55..ea5d86e 100644 --- a/src/algorithms/nelder_mead.rs +++ b/src/algorithms/nelder_mead.rs @@ -69,7 +69,10 @@ where P: Problem> + Sync, { fn run(&mut self, problem: &P) -> OptimizationResult { - assert!(self.config.reflection > 0.0, "NelderMead reflection must be > 0"); + assert!( + self.config.reflection > 0.0, + "NelderMead reflection must be > 0" + ); assert!( self.config.expansion > 1.0, "NelderMead expansion must be > 1", @@ -111,8 +114,7 @@ where v[j] = (v[j] + step).clamp(lo, hi); vertices.push(v); } - let mut evals: Vec = - vertices.iter().map(|v| problem.evaluate(v)).collect(); + let mut evals: Vec = vertices.iter().map(|v| problem.evaluate(v)).collect(); let mut evaluations = evals.len(); for _ in 0..self.config.iterations { @@ -141,8 +143,7 @@ where if better(&r_eval, &evals[best_idx], direction) { // Reflection beat the best — try expansion. - let expanded = - self.reflect(¢roid, &vertices[worst_idx], self.config.expansion); + let expanded = self.reflect(¢roid, &vertices[worst_idx], self.config.expansion); let e_eval = problem.evaluate(&expanded); evaluations += 1; if better(&e_eval, &r_eval, direction) { @@ -177,11 +178,11 @@ where if idx == best_idx { continue; } - #[allow(clippy::needless_range_loop)] // body indexes both vertices and best_pt. + #[allow(clippy::needless_range_loop)] + // body indexes both vertices and best_pt. for j in 0..n { vertices[idx][j] = best_pt[j] - + self.config.shrinkage - * (vertices[idx][j] - best_pt[j]); + + self.config.shrinkage * (vertices[idx][j] - best_pt[j]); } // Clamp to bounds. for (j, x) in vertices[idx].iter_mut().enumerate() { diff --git a/src/algorithms/nsga2.rs b/src/algorithms/nsga2.rs index 291ae16..9e03815 100644 --- a/src/algorithms/nsga2.rs +++ b/src/algorithms/nsga2.rs @@ -26,7 +26,11 @@ pub struct Nsga2Config { impl Default for Nsga2Config { fn default() -> Self { - Self { population_size: 100, generations: 250, seed: 42 } + Self { + population_size: 100, + generations: 250, + seed: 42, + } } } @@ -44,7 +48,11 @@ pub struct Nsga2 { impl Nsga2 { /// Construct an `Nsga2` optimizer. pub fn new(config: Nsga2Config, initializer: I, variation: V) -> Self { - Self { config, initializer, variation } + Self { + config, + initializer, + variation, + } } } @@ -78,8 +86,7 @@ where n, "NSGA-II initializer must return exactly population_size decisions", ); - let population: Vec> = - evaluate_batch(problem, initial_decisions); + let population: Vec> = evaluate_batch(problem, initial_decisions); let mut evaluations = population.len(); // Annotate the starting population with rank and crowding so the first @@ -130,7 +137,9 @@ where let dist = crowding_distance(&combined, front, &objectives); let mut order: Vec = (0..front.len()).collect(); order.sort_by(|&a, &b| { - dist[b].partial_cmp(&dist[a]).unwrap_or(std::cmp::Ordering::Equal) + dist[b] + .partial_cmp(&dist[a]) + .unwrap_or(std::cmp::Ordering::Equal) }); let needed = n - next.len(); for &k in order.iter().take(needed) { @@ -178,7 +187,11 @@ fn annotate( population .into_iter() .enumerate() - .map(|(i, c)| Nsga2Entry { candidate: c, rank: rank[i], crowding_distance: dist[i] }) + .map(|(i, c)| Nsga2Entry { + candidate: c, + rank: rank[i], + crowding_distance: dist[i], + }) .collect() } @@ -212,7 +225,11 @@ mod tests { #[test] fn final_population_has_expected_size() { let mut opt = Nsga2::new( - Nsga2Config { population_size: 20, generations: 5, seed: 1 }, + Nsga2Config { + population_size: 20, + generations: 5, + seed: 1, + }, RealBounds::new(vec![(-5.0, 5.0)]), GaussianMutation { sigma: 0.3 }, ); @@ -224,7 +241,11 @@ mod tests { #[test] fn evaluation_count_at_least_initial_population() { let mut opt = Nsga2::new( - Nsga2Config { population_size: 16, generations: 3, seed: 2 }, + Nsga2Config { + population_size: 16, + generations: 3, + seed: 2, + }, RealBounds::new(vec![(-5.0, 5.0)]), GaussianMutation { sigma: 0.3 }, ); @@ -236,21 +257,35 @@ mod tests { #[test] fn deterministic_with_same_seed() { let mut a = Nsga2::new( - Nsga2Config { population_size: 16, generations: 5, seed: 99 }, + Nsga2Config { + population_size: 16, + generations: 5, + seed: 99, + }, RealBounds::new(vec![(-5.0, 5.0)]), GaussianMutation { sigma: 0.2 }, ); let mut b = Nsga2::new( - Nsga2Config { population_size: 16, generations: 5, seed: 99 }, + Nsga2Config { + population_size: 16, + generations: 5, + seed: 99, + }, RealBounds::new(vec![(-5.0, 5.0)]), GaussianMutation { sigma: 0.2 }, ); let ra = a.run(&SchafferN1); let rb = b.run(&SchafferN1); - let oa: Vec> = - ra.pareto_front.iter().map(|c| c.evaluation.objectives.clone()).collect(); - let ob: Vec> = - rb.pareto_front.iter().map(|c| c.evaluation.objectives.clone()).collect(); + let oa: Vec> = ra + .pareto_front + .iter() + .map(|c| c.evaluation.objectives.clone()) + .collect(); + let ob: Vec> = rb + .pareto_front + .iter() + .map(|c| c.evaluation.objectives.clone()) + .collect(); assert_eq!(oa, ob); } @@ -258,7 +293,11 @@ mod tests { #[should_panic(expected = "population_size must be greater than 0")] fn zero_population_size_panics() { let mut opt = Nsga2::new( - Nsga2Config { population_size: 0, generations: 1, seed: 0 }, + Nsga2Config { + population_size: 0, + generations: 1, + seed: 0, + }, RealBounds::new(vec![(-1.0, 1.0)]), GaussianMutation { sigma: 0.1 }, ); diff --git a/src/algorithms/nsga3.rs b/src/algorithms/nsga3.rs index 04f7695..adcadf7 100644 --- a/src/algorithms/nsga3.rs +++ b/src/algorithms/nsga3.rs @@ -56,7 +56,11 @@ pub struct Nsga3 { impl Nsga3 { /// Construct an `Nsga3` optimizer. pub fn new(config: Nsga3Config, initializer: I, variation: V) -> Self { - Self { config, initializer, variation } + Self { + config, + initializer, + variation, + } } } @@ -99,8 +103,10 @@ where while offspring_decisions.len() < n { let p1 = rng.random_range(0..population.len()); let p2 = rng.random_range(0..population.len()); - let parents = - vec![population[p1].decision.clone(), population[p2].decision.clone()]; + let parents = vec![ + population[p1].decision.clone(), + population[p2].decision.clone(), + ]; let children = self.variation.vary(&parents, &mut rng); assert!( !children.is_empty(), @@ -117,11 +123,11 @@ where evaluations += offspring.len(); // --- Combine + survival selection --- - let mut combined: Vec> = - Vec::with_capacity(2 * n); + let mut combined: Vec> = Vec::with_capacity(2 * n); combined.extend(population); combined.extend(offspring); - population = environmental_selection(&combined, &objectives, &reference_points, n, &mut rng); + population = + environmental_selection(&combined, &objectives, &reference_points, n, &mut rng); } let front = pareto_front(&population, &objectives); @@ -205,7 +211,9 @@ fn environmental_selection( let candidate_refs: Vec = (0..reference_points.len()) .filter(|&j| !available_in_fl[j].is_empty() && niche_count[j] == min_count) .collect(); - let &chosen_ref = candidate_refs.choose(rng).expect("non-empty by construction"); + let &chosen_ref = candidate_refs + .choose(rng) + .expect("non-empty by construction"); let pool = &available_in_fl[chosen_ref]; let pick_local = if niche_count[chosen_ref] == 0 { @@ -420,10 +428,7 @@ mod tests { fn make_optimizer( seed: u64, - ) -> Nsga3< - RealBounds, - CompositeVariation, - > { + ) -> Nsga3> { let bounds = vec![(-5.0, 5.0)]; let initializer = RealBounds::new(bounds.clone()); let variation = CompositeVariation { @@ -457,10 +462,16 @@ mod tests { let mut b = make_optimizer(99); let ra = a.run(&SchafferN1); let rb = b.run(&SchafferN1); - let oa: Vec> = - ra.pareto_front.iter().map(|c| c.evaluation.objectives.clone()).collect(); - let ob: Vec> = - rb.pareto_front.iter().map(|c| c.evaluation.objectives.clone()).collect(); + let oa: Vec> = ra + .pareto_front + .iter() + .map(|c| c.evaluation.objectives.clone()) + .collect(); + let ob: Vec> = rb + .pareto_front + .iter() + .map(|c| c.evaluation.objectives.clone()) + .collect(); assert_eq!(oa, ob); } diff --git a/src/algorithms/one_plus_one_es.rs b/src/algorithms/one_plus_one_es.rs index d64ebd0..9354a01 100644 --- a/src/algorithms/one_plus_one_es.rs +++ b/src/algorithms/one_plus_one_es.rs @@ -68,7 +68,10 @@ where P: Problem> + Sync, { fn run(&mut self, problem: &P) -> OptimizationResult { - assert!(self.config.initial_sigma > 0.0, "OnePlusOneEs initial_sigma must be > 0"); + assert!( + self.config.initial_sigma > 0.0, + "OnePlusOneEs initial_sigma must be > 0" + ); assert!( self.config.step_increase > 1.0, "OnePlusOneEs step_increase must be > 1", @@ -123,8 +126,7 @@ where } // Apply one-fifth rule once we have a full window. if window.len() == self.config.adaptation_period { - let success_count: usize = - window.iter().map(|&b| b as usize).sum(); + let success_count: usize = window.iter().map(|&b| b as usize).sum(); let rate = success_count as f64 / window.len() as f64; if rate > 0.2 { sigma *= self.config.step_increase; diff --git a/src/algorithms/paes.rs b/src/algorithms/paes.rs index 110c7f3..7af6e8c 100644 --- a/src/algorithms/paes.rs +++ b/src/algorithms/paes.rs @@ -23,7 +23,11 @@ pub struct PaesConfig { impl Default for PaesConfig { fn default() -> Self { - Self { iterations: 1000, archive_size: 100, seed: 42 } + Self { + iterations: 1000, + archive_size: 100, + seed: 42, + } } } @@ -45,7 +49,11 @@ pub struct Paes { impl Paes { /// Construct a `Paes` optimizer. pub fn new(config: PaesConfig, initializer: I, variation: V) -> Self { - Self { config, initializer, variation } + Self { + config, + initializer, + variation, + } } } @@ -74,15 +82,15 @@ where let mut evaluations = 1usize; let mut archive = ParetoArchive::new(objectives.clone()); - archive.insert(Candidate::new(current_decision.clone(), current_eval.clone())); + archive.insert(Candidate::new( + current_decision.clone(), + current_eval.clone(), + )); for _ in 0..self.config.iterations { let parents = vec![current_decision.clone()]; let children = self.variation.vary(&parents, &mut rng); - assert!( - !children.is_empty(), - "PAES variation returned no children", - ); + assert!(!children.is_empty(), "PAES variation returned no children",); let child_decision = children.into_iter().next().unwrap(); let child_eval = problem.evaluate(&child_decision); evaluations += 1; @@ -103,7 +111,10 @@ where } archive.insert(Candidate::new(child_decision, child_eval)); - archive.insert(Candidate::new(current_decision.clone(), current_eval.clone())); + archive.insert(Candidate::new( + current_decision.clone(), + current_eval.clone(), + )); archive.truncate(self.config.archive_size); } @@ -129,7 +140,11 @@ mod tests { #[test] fn produces_at_least_one_candidate() { let mut opt = Paes::new( - PaesConfig { iterations: 50, archive_size: 16, seed: 1 }, + PaesConfig { + iterations: 50, + archive_size: 16, + seed: 1, + }, RealBounds::new(vec![(-5.0, 5.0)]), GaussianMutation { sigma: 0.3 }, ); @@ -141,7 +156,11 @@ mod tests { #[test] fn archive_size_respected() { let mut opt = Paes::new( - PaesConfig { iterations: 200, archive_size: 8, seed: 2 }, + PaesConfig { + iterations: 200, + archive_size: 8, + seed: 2, + }, RealBounds::new(vec![(-5.0, 5.0)]), GaussianMutation { sigma: 0.2 }, ); @@ -152,7 +171,11 @@ mod tests { #[test] fn single_objective_returns_best() { let mut opt = Paes::new( - PaesConfig { iterations: 200, archive_size: 8, seed: 3 }, + PaesConfig { + iterations: 200, + archive_size: 8, + seed: 3, + }, RealBounds::new(vec![(-2.0, 2.0)]), GaussianMutation { sigma: 0.1 }, ); diff --git a/src/algorithms/particle_swarm.rs b/src/algorithms/particle_swarm.rs index 858fb70..ff23af8 100644 --- a/src/algorithms/particle_swarm.rs +++ b/src/algorithms/particle_swarm.rs @@ -133,9 +133,8 @@ where self.config.cognitive * r1 * (pbest_decisions[i][j] - positions[i][j]); let social_term = self.config.social * r2 * (gbest_decision[j] - positions[i][j]); - let mut v = self.config.inertia * velocities[i][j] - + cognitive_term - + social_term; + let mut v = + self.config.inertia * velocities[i][j] + cognitive_term + social_term; if v > v_max[j] { v = v_max[j]; } else if v < -v_max[j] { diff --git a/src/algorithms/pesa2.rs b/src/algorithms/pesa2.rs index be870a7..4990431 100644 --- a/src/algorithms/pesa2.rs +++ b/src/algorithms/pesa2.rs @@ -60,7 +60,11 @@ pub struct PesaII { impl PesaII { /// Construct a `PesaII`. pub fn new(config: PesaIIConfig, initializer: I, variation: V) -> Self { - Self { config, initializer, variation } + Self { + config, + initializer, + variation, + } } } @@ -72,9 +76,18 @@ where V: Variation, { fn run(&mut self, problem: &P) -> OptimizationResult { - assert!(self.config.population_size > 0, "PesaII population_size must be > 0"); - assert!(self.config.archive_size > 0, "PesaII archive_size must be > 0"); - assert!(self.config.grid_divisions >= 1, "PesaII grid_divisions must be >= 1"); + assert!( + self.config.population_size > 0, + "PesaII population_size must be > 0" + ); + assert!( + self.config.archive_size > 0, + "PesaII archive_size must be > 0" + ); + assert!( + self.config.grid_divisions >= 1, + "PesaII grid_divisions must be >= 1" + ); let n = self.config.population_size; let objectives = problem.objectives(); let mut rng = rng_from_seed(self.config.seed); @@ -95,7 +108,11 @@ where for c in &internal { archive.insert(c.clone()); } - truncate_by_grid(&mut archive, self.config.archive_size, self.config.grid_divisions); + truncate_by_grid( + &mut archive, + self.config.archive_size, + self.config.grid_divisions, + ); for _ in 0..self.config.generations { // Build grid + box counts on the archive. @@ -106,9 +123,15 @@ where while offspring.len() < n { let p1 = region_tournament(&archive, &boxes, &counts, &mut rng); let p2 = region_tournament(&archive, &boxes, &counts, &mut rng); - let parents = vec![archive.members()[p1].decision.clone(), archive.members()[p2].decision.clone()]; + let parents = vec![ + archive.members()[p1].decision.clone(), + archive.members()[p2].decision.clone(), + ]; let children = self.variation.vary(&parents, &mut rng); - assert!(!children.is_empty(), "PesaII variation returned no children"); + assert!( + !children.is_empty(), + "PesaII variation returned no children" + ); for child in children { if offspring.len() >= n { break; @@ -124,7 +147,11 @@ where for c in &offspring { archive.insert(c.clone()); } - truncate_by_grid(&mut archive, self.config.archive_size, self.config.grid_divisions); + truncate_by_grid( + &mut archive, + self.config.archive_size, + self.config.grid_divisions, + ); internal = offspring; } @@ -213,11 +240,7 @@ fn region_tournament( /// Truncate the archive to `max_size` by repeatedly evicting a uniform-random /// member of the most-occupied grid box (PESA-II's standard approach). -fn truncate_by_grid( - archive: &mut ParetoArchive, - max_size: usize, - divisions: usize, -) { +fn truncate_by_grid(archive: &mut ParetoArchive, max_size: usize, divisions: usize) { while archive.members().len() > max_size { let objectives = archive.objectives.clone(); let (boxes, counts) = build_grid(archive, &objectives, divisions); @@ -291,10 +314,16 @@ mod tests { let mut b = make_optimizer(99); let ra = a.run(&SchafferN1); let rb = b.run(&SchafferN1); - let oa: Vec> = - ra.pareto_front.iter().map(|c| c.evaluation.objectives.clone()).collect(); - let ob: Vec> = - rb.pareto_front.iter().map(|c| c.evaluation.objectives.clone()).collect(); + let oa: Vec> = ra + .pareto_front + .iter() + .map(|c| c.evaluation.objectives.clone()) + .collect(); + let ob: Vec> = rb + .pareto_front + .iter() + .map(|c| c.evaluation.objectives.clone()) + .collect(); assert_eq!(oa, ob); } @@ -320,5 +349,4 @@ mod tests { ); let _ = opt.run(&SchafferN1); } - } diff --git a/src/algorithms/random_search.rs b/src/algorithms/random_search.rs index a260863..6da4a22 100644 --- a/src/algorithms/random_search.rs +++ b/src/algorithms/random_search.rs @@ -25,7 +25,11 @@ pub struct RandomSearchConfig { impl Default for RandomSearchConfig { fn default() -> Self { - Self { iterations: 100, batch_size: 1, seed: 42 } + Self { + iterations: 100, + batch_size: 1, + seed: 42, + } } } @@ -45,7 +49,10 @@ pub struct RandomSearch { impl RandomSearch { /// Construct a `RandomSearch` from its config and initializer. pub fn new(config: RandomSearchConfig, initializer: I) -> Self { - Self { config, initializer } + Self { + config, + initializer, + } } } @@ -62,7 +69,9 @@ where let mut evaluations = 0usize; for _ in 0..self.config.iterations { - let decisions = self.initializer.initialize(self.config.batch_size, &mut rng); + let decisions = self + .initializer + .initialize(self.config.batch_size, &mut rng); evaluations += decisions.len(); all.extend(evaluate_batch(problem, decisions)); } @@ -88,7 +97,11 @@ mod tests { #[test] fn evaluation_count_matches_iterations_times_batch() { let mut opt = RandomSearch::new( - RandomSearchConfig { iterations: 30, batch_size: 4, seed: 1 }, + RandomSearchConfig { + iterations: 30, + batch_size: 4, + seed: 1, + }, RealBounds::new(vec![(-2.0, 2.0)]), ); let r = opt.run(&Sphere1D); @@ -100,7 +113,11 @@ mod tests { #[test] fn pareto_front_non_empty_for_multi_objective() { let mut opt = RandomSearch::new( - RandomSearchConfig { iterations: 50, batch_size: 1, seed: 42 }, + RandomSearchConfig { + iterations: 50, + batch_size: 1, + seed: 42, + }, RealBounds::new(vec![(-5.0, 5.0)]), ); let r = opt.run(&SchafferN1); @@ -112,7 +129,11 @@ mod tests { #[test] fn single_objective_returns_best() { let mut opt = RandomSearch::new( - RandomSearchConfig { iterations: 100, batch_size: 1, seed: 7 }, + RandomSearchConfig { + iterations: 100, + batch_size: 1, + seed: 7, + }, RealBounds::new(vec![(-1.0, 1.0)]), ); let r = opt.run(&Sphere1D); diff --git a/src/algorithms/rvea.rs b/src/algorithms/rvea.rs index 06a29a1..61c93e9 100644 --- a/src/algorithms/rvea.rs +++ b/src/algorithms/rvea.rs @@ -54,7 +54,11 @@ pub struct Rvea { impl Rvea { /// Construct an `Rvea`. pub fn new(config: RveaConfig, initializer: I, variation: V) -> Self { - Self { config, initializer, variation } + Self { + config, + initializer, + variation, + } } } @@ -66,14 +70,20 @@ where V: Variation, { fn run(&mut self, problem: &P) -> OptimizationResult { - assert!(self.config.population_size > 0, "Rvea population_size must be > 0"); + assert!( + self.config.population_size > 0, + "Rvea population_size must be > 0" + ); let n = self.config.population_size; let objectives = problem.objectives(); let m = objectives.len(); // Reference vectors normalized to unit norm. let raw_refs = das_dennis(m, self.config.reference_divisions); let references: Vec> = raw_refs.into_iter().map(unit_normalize).collect(); - assert!(!references.is_empty(), "Rvea: no reference vectors generated"); + assert!( + !references.is_empty(), + "Rvea: no reference vectors generated" + ); // Smallest angle between any two reference vectors — used to scale // the APD penalty term. @@ -91,8 +101,10 @@ where while offspring_decisions.len() < n { let p1 = rng.random_range(0..population.len()); let p2 = rng.random_range(0..population.len()); - let parents = - vec![population[p1].decision.clone(), population[p2].decision.clone()]; + let parents = vec![ + population[p1].decision.clone(), + population[p2].decision.clone(), + ]; let children = self.variation.vary(&parents, &mut rng); assert!(!children.is_empty(), "Rvea variation returned no children"); for child in children { @@ -126,7 +138,11 @@ where .iter() .map(|c| { let oriented = objectives.as_minimization(&c.evaluation.objectives); - oriented.iter().enumerate().map(|(k, v)| v - ideal[k]).collect() + oriented + .iter() + .enumerate() + .map(|(k, v)| v - ideal[k]) + .collect() }) .collect(); @@ -157,22 +173,23 @@ where } } - let mut next: Vec> = - keep.into_iter().flatten().map(|(i, _)| combined[i].clone()).collect(); + let mut next: Vec> = keep + .into_iter() + .flatten() + .map(|(i, _)| combined[i].clone()) + .collect(); // If we ended up with fewer than n (some references unfilled), // backfill with the lowest-APD remaining candidates. if next.len() < n { let mut all_apds: Vec<(usize, f64)> = (0..combined.len()) .map(|i| { - let length: f64 = - translated[i].iter().map(|v| v * v).sum::().sqrt(); + let length: f64 = translated[i].iter().map(|v| v * v).sum::().sqrt(); let theta_max_safe = theta_max.max(1e-12); let penalty = 1.0 + (m_dim as f64) * alpha_t * (angles[i] / theta_max_safe); (i, penalty * length) }) .collect(); - all_apds - .sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal)); + all_apds.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal)); for (i, _) in all_apds { if next.len() >= n { break; @@ -246,7 +263,11 @@ fn smallest_neighbor_angle(references: &[Vec]) -> f64 { } } } - if !min_angle.is_finite() { std::f64::consts::FRAC_PI_4 } else { min_angle } + if !min_angle.is_finite() { + std::f64::consts::FRAC_PI_4 + } else { + min_angle + } } #[cfg(test)] @@ -292,10 +313,16 @@ mod tests { let mut b = make_optimizer(99); let ra = a.run(&SchafferN1); let rb = b.run(&SchafferN1); - let oa: Vec> = - ra.pareto_front.iter().map(|c| c.evaluation.objectives.clone()).collect(); - let ob: Vec> = - rb.pareto_front.iter().map(|c| c.evaluation.objectives.clone()).collect(); + let oa: Vec> = ra + .pareto_front + .iter() + .map(|c| c.evaluation.objectives.clone()) + .collect(); + let ob: Vec> = rb + .pareto_front + .iter() + .map(|c| c.evaluation.objectives.clone()) + .collect(); assert_eq!(oa, ob); } diff --git a/src/algorithms/simulated_annealing.rs b/src/algorithms/simulated_annealing.rs index d016913..dafd852 100644 --- a/src/algorithms/simulated_annealing.rs +++ b/src/algorithms/simulated_annealing.rs @@ -54,7 +54,11 @@ pub struct SimulatedAnnealing { impl SimulatedAnnealing { /// Construct a `SimulatedAnnealing`. pub fn new(config: SimulatedAnnealingConfig, initializer: I, variation: V) -> Self { - Self { config, initializer, variation } + Self { + config, + initializer, + variation, + } } } @@ -109,7 +113,10 @@ where for _ in 0..self.config.iterations { let parents = vec![current_decision.clone()]; let children = self.variation.vary(&parents, &mut rng); - assert!(!children.is_empty(), "SimulatedAnnealing variation returned no children"); + assert!( + !children.is_empty(), + "SimulatedAnnealing variation returned no children" + ); let child_decision = children.into_iter().next().unwrap(); let child_eval = problem.evaluate(&child_decision); evaluations += 1; diff --git a/src/algorithms/sms_emoa.rs b/src/algorithms/sms_emoa.rs index eaec59f..c98a8df 100644 --- a/src/algorithms/sms_emoa.rs +++ b/src/algorithms/sms_emoa.rs @@ -62,7 +62,11 @@ pub struct SmsEmoa { impl SmsEmoa { /// Construct a `SmsEmoa`. pub fn new(config: SmsEmoaConfig, initializer: I, variation: V) -> Self { - Self { config, initializer, variation } + Self { + config, + initializer, + variation, + } } } @@ -74,7 +78,10 @@ where V: Variation, { fn run(&mut self, problem: &P) -> OptimizationResult { - assert!(self.config.population_size > 0, "SmsEmoa population_size must be > 0"); + assert!( + self.config.population_size > 0, + "SmsEmoa population_size must be > 0" + ); let n = self.config.population_size; let objectives = problem.objectives(); assert_eq!( @@ -100,10 +107,15 @@ where // --- One offspring (steady-state) --- let p1 = rng.random_range(0..population.len()); let p2 = rng.random_range(0..population.len()); - let parents = - vec![population[p1].decision.clone(), population[p2].decision.clone()]; + let parents = vec![ + population[p1].decision.clone(), + population[p2].decision.clone(), + ]; let children = self.variation.vary(&parents, &mut rng); - assert!(!children.is_empty(), "SmsEmoa variation returned no children"); + assert!( + !children.is_empty(), + "SmsEmoa variation returned no children" + ); let child_decision = children.into_iter().next().unwrap(); let child_eval = problem.evaluate(&child_decision); evaluations += 1; @@ -212,10 +224,16 @@ mod tests { let mut b = make_optimizer(99); let ra = a.run(&SchafferN1); let rb = b.run(&SchafferN1); - let oa: Vec> = - ra.pareto_front.iter().map(|c| c.evaluation.objectives.clone()).collect(); - let ob: Vec> = - rb.pareto_front.iter().map(|c| c.evaluation.objectives.clone()).collect(); + let oa: Vec> = ra + .pareto_front + .iter() + .map(|c| c.evaluation.objectives.clone()) + .collect(); + let ob: Vec> = rb + .pareto_front + .iter() + .map(|c| c.evaluation.objectives.clone()) + .collect(); assert_eq!(oa, ob); } @@ -263,4 +281,3 @@ mod tests { let _ = opt.run(&SchafferN1); } } - diff --git a/src/algorithms/snes.rs b/src/algorithms/snes.rs index d017c9d..c591d69 100644 --- a/src/algorithms/snes.rs +++ b/src/algorithms/snes.rs @@ -76,7 +76,10 @@ where self.config.population_size >= 2, "SeparableNes population_size must be >= 2", ); - assert!(self.config.initial_sigma > 0.0, "SeparableNes initial_sigma must be > 0"); + assert!( + self.config.initial_sigma > 0.0, + "SeparableNes initial_sigma must be > 0" + ); let objectives = problem.objectives(); assert!( objectives.is_single_objective(), @@ -97,9 +100,10 @@ where let mut sigma = vec![self.config.initial_sigma; n]; // Default sigma learning rate (Wierstra et al. 2014, Eq. 11). - let eta_sigma = self.config.sigma_learning_rate.unwrap_or_else(|| { - (3.0 + (n as f64).ln()) / (5.0 * (n as f64).sqrt()) - }); + let eta_sigma = self + .config + .sigma_learning_rate + .unwrap_or_else(|| (3.0 + (n as f64).ln()) / (5.0 * (n as f64).sqrt())); let eta_mean = self.config.mean_learning_rate; // Rank utilities — the standard NES weighting: diff --git a/src/algorithms/spea2.rs b/src/algorithms/spea2.rs index 88c451e..7f4e820 100644 --- a/src/algorithms/spea2.rs +++ b/src/algorithms/spea2.rs @@ -28,7 +28,12 @@ pub struct Spea2Config { impl Default for Spea2Config { fn default() -> Self { - Self { population_size: 100, archive_size: 100, generations: 250, seed: 42 } + Self { + population_size: 100, + archive_size: 100, + generations: 250, + seed: 42, + } } } @@ -46,7 +51,11 @@ pub struct Spea2 { impl Spea2 { /// Construct a `Spea2` optimizer. pub fn new(config: Spea2Config, initializer: I, variation: V) -> Self { - Self { config, initializer, variation } + Self { + config, + initializer, + variation, + } } } @@ -190,7 +199,11 @@ fn compute_fitness(pool: &[Candidate], objectives: &ObjectiveSpace) -> Vec } fn euclidean(a: &[f64], b: &[f64]) -> f64 { - a.iter().zip(b.iter()).map(|(x, y)| (x - y).powi(2)).sum::().sqrt() + a.iter() + .zip(b.iter()) + .map(|(x, y)| (x - y).powi(2)) + .sum::() + .sqrt() } /// Build the next archive of exactly `target_size` members. @@ -213,10 +226,11 @@ fn build_archive( if nondom.len() < target_size { // Fill from dominated members ordered by ascending fitness. - let mut dominated: Vec = - (0..pool.len()).filter(|&i| fitness[i] >= 1.0).collect(); + let mut dominated: Vec = (0..pool.len()).filter(|&i| fitness[i] >= 1.0).collect(); dominated.sort_by(|&a, &b| { - fitness[a].partial_cmp(&fitness[b]).unwrap_or(std::cmp::Ordering::Equal) + fitness[a] + .partial_cmp(&fitness[b]) + .unwrap_or(std::cmp::Ordering::Equal) }); let needed = target_size - nondom.len(); nondom.extend(dominated.into_iter().take(needed)); @@ -244,8 +258,7 @@ fn build_archive( } neighbor_dists[i].push(euclidean(&oriented[i], &oriented[j])); } - neighbor_dists[i] - .sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)); + neighbor_dists[i].sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)); } // Find the alive member whose neighbor-distance vector is lex-smallest. let mut victim = usize::MAX; @@ -263,7 +276,11 @@ fn build_archive( .zip(neighbor_dists[victim].iter()) .find_map(|(a, b)| { let c = a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal); - if c != std::cmp::Ordering::Equal { Some(c) } else { None } + if c != std::cmp::Ordering::Equal { + Some(c) + } else { + None + } }) .unwrap_or(std::cmp::Ordering::Equal); if cmp == std::cmp::Ordering::Less { @@ -277,7 +294,13 @@ fn build_archive( nondom .into_iter() .enumerate() - .filter_map(|(local, idx)| if alive[local] { Some(pool[idx].clone()) } else { None }) + .filter_map(|(local, idx)| { + if alive[local] { + Some(pool[idx].clone()) + } else { + None + } + }) .collect() } @@ -354,10 +377,16 @@ mod tests { let mut b = make(); let ra = a.run(&SchafferN1); let rb = b.run(&SchafferN1); - let oa: Vec> = - ra.pareto_front.iter().map(|c| c.evaluation.objectives.clone()).collect(); - let ob: Vec> = - rb.pareto_front.iter().map(|c| c.evaluation.objectives.clone()).collect(); + let oa: Vec> = ra + .pareto_front + .iter() + .map(|c| c.evaluation.objectives.clone()) + .collect(); + let ob: Vec> = rb + .pareto_front + .iter() + .map(|c| c.evaluation.objectives.clone()) + .collect(); assert_eq!(oa, ob); } diff --git a/src/algorithms/tabu_search.rs b/src/algorithms/tabu_search.rs index 35332b9..4b2b968 100644 --- a/src/algorithms/tabu_search.rs +++ b/src/algorithms/tabu_search.rs @@ -25,7 +25,11 @@ pub struct TabuSearchConfig { impl Default for TabuSearchConfig { fn default() -> Self { - Self { iterations: 500, tabu_tenure: 16, seed: 42 } + Self { + iterations: 500, + tabu_tenure: 16, + seed: 42, + } } } @@ -62,7 +66,12 @@ where { /// Construct a `TabuSearch`. pub fn new(config: TabuSearchConfig, initializer: I, neighbors: N) -> Self { - Self { config, initializer, neighbors, _marker: std::marker::PhantomData } + Self { + config, + initializer, + neighbors, + _marker: std::marker::PhantomData, + } } } @@ -87,14 +96,18 @@ where let mut rng = rng_from_seed(self.config.seed); let mut initial = self.initializer.initialize(1, &mut rng); - assert!(!initial.is_empty(), "TabuSearch initializer returned no decisions"); + assert!( + !initial.is_empty(), + "TabuSearch initializer returned no decisions" + ); let mut current_decision = initial.remove(0); let mut current_eval = problem.evaluate(¤t_decision); let mut best_decision = current_decision.clone(); let mut best_eval = current_eval.clone(); let mut evaluations = 1usize; - let mut tabu_queue: VecDeque = VecDeque::with_capacity(self.config.tabu_tenure); + let mut tabu_queue: VecDeque = + VecDeque::with_capacity(self.config.tabu_tenure); let mut tabu_set: HashSet = HashSet::new(); for _ in 0..self.config.iterations { @@ -118,8 +131,7 @@ where for (i, c) in candidates.iter().enumerate() { let is_tabu = tabu_set.contains(c); - let aspires = is_tabu - && better_than(&cand_evals[i], &best_eval, direction); + let aspires = is_tabu && better_than(&cand_evals[i], &best_eval, direction); if is_tabu && !aspires { continue; } @@ -227,15 +239,16 @@ mod tests { } } - fn make_optimizer( - seed: u64, - neighbors: F, - ) -> TabuSearch, StartAtZero, F> + fn make_optimizer(seed: u64, neighbors: F) -> TabuSearch, StartAtZero, F> where F: FnMut(&Vec, &mut Rng) -> Vec>, { TabuSearch::new( - TabuSearchConfig { iterations: 50, tabu_tenure: 4, seed }, + TabuSearchConfig { + iterations: 50, + tabu_tenure: 4, + seed, + }, StartAtZero, neighbors, ) @@ -244,9 +257,7 @@ mod tests { #[test] fn finds_optimum_on_grid() { // Neighbors: ±1 of current value. - let neighbors = |x: &Vec, _rng: &mut Rng| { - vec![vec![x[0] - 1], vec![x[0] + 1]] - }; + let neighbors = |x: &Vec, _rng: &mut Rng| vec![vec![x[0] - 1], vec![x[0] + 1]]; let mut opt = make_optimizer(1, neighbors); let r = opt.run(&GridProblem); let best = r.best.unwrap(); diff --git a/src/algorithms/tlbo.rs b/src/algorithms/tlbo.rs index 2f5fb0a..51703aa 100644 --- a/src/algorithms/tlbo.rs +++ b/src/algorithms/tlbo.rs @@ -27,7 +27,11 @@ pub struct TlboConfig { impl Default for TlboConfig { fn default() -> Self { - Self { population_size: 30, generations: 200, seed: 42 } + Self { + population_size: 30, + generations: 200, + seed: 42, + } } } @@ -58,7 +62,10 @@ where P: Problem> + Sync, { fn run(&mut self, problem: &P) -> OptimizationResult { - assert!(self.config.population_size >= 2, "Tlbo population_size must be >= 2"); + assert!( + self.config.population_size >= 2, + "Tlbo population_size must be >= 2" + ); let objectives = problem.objectives(); assert!( objectives.is_single_objective(), @@ -73,8 +80,7 @@ where use crate::traits::Initializer as _; self.bounds.initialize(n, &mut rng) }; - let mut evals: Vec = - decisions.iter().map(|d| problem.evaluate(d)).collect(); + let mut evals: Vec = decisions.iter().map(|d| problem.evaluate(d)).collect(); let mut evaluations = decisions.len(); for _ in 0..self.config.generations { diff --git a/src/algorithms/tpe.rs b/src/algorithms/tpe.rs index 9d38fb4..c53d543 100644 --- a/src/algorithms/tpe.rs +++ b/src/algorithms/tpe.rs @@ -72,7 +72,10 @@ where P: Problem> + Sync, { fn run(&mut self, problem: &P) -> OptimizationResult { - assert!(self.config.initial_samples >= 2, "Tpe initial_samples must be >= 2"); + assert!( + self.config.initial_samples >= 2, + "Tpe initial_samples must be >= 2" + ); assert!( self.config.good_fraction > 0.0 && self.config.good_fraction < 1.0, "Tpe good_fraction must be in (0, 1)", @@ -81,7 +84,10 @@ where self.config.candidate_samples >= 1, "Tpe candidate_samples must be >= 1", ); - assert!(self.config.bandwidth_factor > 0.0, "Tpe bandwidth_factor must be > 0"); + assert!( + self.config.bandwidth_factor > 0.0, + "Tpe bandwidth_factor must be > 0" + ); let objectives = problem.objectives(); assert!( objectives.is_single_objective(), @@ -110,9 +116,27 @@ where let mut best_x: Option> = None; let mut best_ratio = f64::NEG_INFINITY; for _ in 0..self.config.candidate_samples { - let cand = sample_from_kde(&decisions, &good_idx, &self.bounds, self.config.bandwidth_factor, &mut rng); - let l = log_kde_density(&cand, &decisions, &good_idx, &self.bounds, self.config.bandwidth_factor); - let g = log_kde_density(&cand, &decisions, &bad_idx, &self.bounds, self.config.bandwidth_factor); + let cand = sample_from_kde( + &decisions, + &good_idx, + &self.bounds, + self.config.bandwidth_factor, + &mut rng, + ); + let l = log_kde_density( + &cand, + &decisions, + &good_idx, + &self.bounds, + self.config.bandwidth_factor, + ); + let g = log_kde_density( + &cand, + &decisions, + &bad_idx, + &self.bounds, + self.config.bandwidth_factor, + ); let ratio = l - g; if ratio > best_ratio { best_ratio = ratio; @@ -180,7 +204,13 @@ fn sample_uniform_in_bounds(bounds: &RealBounds, rng: &mut Rng) -> Vec { bounds .bounds .iter() - .map(|&(lo, hi)| if lo == hi { lo } else { lo + (hi - lo) * rng.random::() }) + .map(|&(lo, hi)| { + if lo == hi { + lo + } else { + lo + (hi - lo) * rng.random::() + } + }) .collect() } @@ -191,7 +221,9 @@ fn split_good_bad(targets: &[f64], good_fraction: f64) -> (Vec, Vec = (0..n).collect(); order.sort_by(|&a, &b| { - targets[a].partial_cmp(&targets[b]).unwrap_or(std::cmp::Ordering::Equal) + targets[a] + .partial_cmp(&targets[b]) + .unwrap_or(std::cmp::Ordering::Equal) }); let n_good = ((n as f64) * good_fraction).round() as usize; let n_good = n_good.clamp(1, n.saturating_sub(1)); @@ -288,7 +320,9 @@ fn scott_bandwidths(decisions: &[Vec], support: &[usize], factor: f64) -> V *v /= denom; } let scott_n = (support.len() as f64).powf(-0.2); - vars.into_iter().map(|v| factor * v.sqrt().max(1e-6) * scott_n).collect() + vars.into_iter() + .map(|v| factor * v.sqrt().max(1e-6) * scott_n) + .collect() } #[cfg(test)] diff --git a/src/algorithms/umda.rs b/src/algorithms/umda.rs index cb49c73..5e45689 100644 --- a/src/algorithms/umda.rs +++ b/src/algorithms/umda.rs @@ -67,7 +67,10 @@ where P: Problem> + Sync, { fn run(&mut self, problem: &P) -> OptimizationResult { - assert!(self.config.population_size >= 2, "Umda population_size must be >= 2"); + assert!( + self.config.population_size >= 2, + "Umda population_size must be >= 2" + ); assert!( self.config.selected_size >= 1, "Umda selected_size must be >= 1", @@ -114,7 +117,11 @@ where // --- Phase 1: select top μ members --- let mut order: Vec = (0..population.len()).collect(); order.sort_by(|&a, &b| { - compare_so(&population[a].evaluation, &population[b].evaluation, direction) + compare_so( + &population[a].evaluation, + &population[b].evaluation, + direction, + ) }); let selected: Vec<&Candidate>> = order.iter().take(mu).map(|&i| &population[i]).collect(); @@ -228,10 +235,7 @@ mod tests { type Decision = Vec; fn objectives(&self) -> ObjectiveSpace { - ObjectiveSpace::new(vec![ - Objective::minimize("a"), - Objective::minimize("b"), - ]) + ObjectiveSpace::new(vec![Objective::minimize("a"), Objective::minimize("b")]) } fn evaluate(&self, _x: &Vec) -> Evaluation { diff --git a/src/core/candidate.rs b/src/core/candidate.rs index 9839b4f..e07018d 100644 --- a/src/core/candidate.rs +++ b/src/core/candidate.rs @@ -18,7 +18,10 @@ pub struct Candidate { impl Candidate { /// Pair a decision with its evaluation. pub fn new(decision: D, evaluation: Evaluation) -> Self { - Self { decision, evaluation } + Self { + decision, + evaluation, + } } } diff --git a/src/core/evaluation.rs b/src/core/evaluation.rs index 8839807..0558fc4 100644 --- a/src/core/evaluation.rs +++ b/src/core/evaluation.rs @@ -18,12 +18,18 @@ pub struct Evaluation { impl Evaluation { /// Build a feasible evaluation from objective values. pub fn new(objectives: Vec) -> Self { - Self { objectives, constraint_violation: 0.0 } + Self { + objectives, + constraint_violation: 0.0, + } } /// Build an evaluation with a known total constraint violation. pub fn constrained(objectives: Vec, constraint_violation: f64) -> Self { - Self { objectives, constraint_violation } + Self { + objectives, + constraint_violation, + } } /// Returns `true` when `constraint_violation <= 0.0`. diff --git a/src/core/objective.rs b/src/core/objective.rs index 79838f2..631e4b2 100644 --- a/src/core/objective.rs +++ b/src/core/objective.rs @@ -26,12 +26,18 @@ pub struct Objective { impl Objective { /// Create a minimize objective with the given name. pub fn minimize(name: impl Into) -> Self { - Self { name: name.into(), direction: Direction::Minimize } + Self { + name: name.into(), + direction: Direction::Minimize, + } } /// Create a maximize objective with the given name. pub fn maximize(name: impl Into) -> Self { - Self { name: name.into(), direction: Direction::Maximize } + Self { + name: name.into(), + direction: Direction::Maximize, + } } } @@ -125,10 +131,7 @@ mod tests { assert!(!single.is_empty()); assert_eq!(single.len(), 1); - let multi = ObjectiveSpace::new(vec![ - Objective::minimize("f1"), - Objective::minimize("f2"), - ]); + let multi = ObjectiveSpace::new(vec![Objective::minimize("f1"), Objective::minimize("f2")]); assert!(multi.is_multi_objective()); assert!(!multi.is_single_objective()); diff --git a/src/core/result.rs b/src/core/result.rs index 425877f..c5b5f6a 100644 --- a/src/core/result.rs +++ b/src/core/result.rs @@ -31,7 +31,13 @@ impl OptimizationResult { evaluations: usize, generations: usize, ) -> Self { - Self { population, pareto_front, best, evaluations, generations } + Self { + population, + pareto_front, + best, + evaluations, + generations, + } } /// The final population. diff --git a/src/internal/eigen.rs b/src/internal/eigen.rs index 59b73a7..b69b1c0 100644 --- a/src/internal/eigen.rs +++ b/src/internal/eigen.rs @@ -21,7 +21,10 @@ pub(crate) fn symmetric_eigen( max_sweeps: usize, ) -> (Vec, Vec>) { let n = matrix.len(); - debug_assert!(matrix.iter().all(|row| row.len() == n), "matrix must be square"); + debug_assert!( + matrix.iter().all(|row| row.len() == n), + "matrix must be square" + ); // Working copy of the matrix; converges to a diagonal of eigenvalues. let mut a: Vec> = matrix.to_vec(); diff --git a/src/metrics/hypervolume.rs b/src/metrics/hypervolume.rs index 5f35d75..130502c 100644 --- a/src/metrics/hypervolume.rs +++ b/src/metrics/hypervolume.rs @@ -71,10 +71,7 @@ mod tests { } fn space_min2() -> ObjectiveSpace { - ObjectiveSpace::new(vec![ - Objective::minimize("f1"), - Objective::minimize("f2"), - ]) + ObjectiveSpace::new(vec![Objective::minimize("f1"), Objective::minimize("f2")]) } #[test] @@ -83,7 +80,11 @@ mod tests { // Dominated region area = 4*4 - sum of "outside" rectangles // stripes: x∈[1,2] y∈[3,4]→1, x∈[2,3] y∈[2,4]→2, x∈[3,4] y∈[1,4]→3 → total dominated = 1+2+3 = 6. let s = space_min2(); - let front = [cand(vec![1.0, 3.0]), cand(vec![2.0, 2.0]), cand(vec![3.0, 1.0])]; + let front = [ + cand(vec![1.0, 3.0]), + cand(vec![2.0, 2.0]), + cand(vec![3.0, 1.0]), + ]; let hv = hypervolume_2d(&front, &s, [4.0, 4.0]); assert!((hv - 6.0).abs() < 1e-12, "expected 6.0, got {hv}"); } @@ -156,7 +157,10 @@ pub fn hypervolume_nd( reference_point.len(), "hypervolume_nd: ObjectiveSpace and reference_point must agree on dimension", ); - assert!(!reference_point.is_empty(), "hypervolume_nd: dimension must be >= 1"); + assert!( + !reference_point.is_empty(), + "hypervolume_nd: dimension must be >= 1" + ); if front.is_empty() { return 0.0; @@ -193,9 +197,7 @@ fn hso_recursive(points: &[Vec], reference: &[f64]) -> f64 { // 2-D HV via the same sweep used by hypervolume_2d. Inlined here // because we already have the points in oriented form. let mut sorted: Vec<&Vec> = points.iter().collect(); - sorted.sort_by(|a, b| { - a[0].partial_cmp(&b[0]).unwrap_or(std::cmp::Ordering::Equal) - }); + sorted.sort_by(|a, b| a[0].partial_cmp(&b[0]).unwrap_or(std::cmp::Ordering::Equal)); let mut area = 0.0; let mut last_y = reference[1]; for p in sorted { @@ -236,10 +238,7 @@ fn hso_recursive(points: &[Vec], reference: &[f64]) -> f64 { for p in sorted.into_iter().rev() { let depth = prev - p[last]; if depth > 0.0 && !active.is_empty() { - let projected: Vec> = active - .iter() - .map(|q| q[..last].to_vec()) - .collect(); + let projected: Vec> = active.iter().map(|q| q[..last].to_vec()).collect(); let nd = non_dominated_projection(&projected); total += depth * hso_recursive(&nd, &sub_reference); } @@ -259,7 +258,11 @@ fn hso_recursive(points: &[Vec], reference: &[f64]) -> f64 { /// Drop dominated members of a projected point set. fn non_dominated_projection(points: &[Vec]) -> Vec> { - let m = if let Some(first) = points.first() { first.len() } else { return Vec::new(); }; + let m = if let Some(first) = points.first() { + first.len() + } else { + return Vec::new(); + }; let mut out: Vec> = Vec::new(); 'outer: for p in points { // Skip if dominated by any kept point. @@ -327,11 +330,12 @@ mod nd_tests { #[test] fn nd_matches_2d_on_known_case() { - let s = ObjectiveSpace::new(vec![ - Objective::minimize("f1"), - Objective::minimize("f2"), - ]); - let front = [cand_n(vec![1.0, 3.0]), cand_n(vec![2.0, 2.0]), cand_n(vec![3.0, 1.0])]; + let s = ObjectiveSpace::new(vec![Objective::minimize("f1"), Objective::minimize("f2")]); + let front = [ + cand_n(vec![1.0, 3.0]), + cand_n(vec![2.0, 2.0]), + cand_n(vec![3.0, 1.0]), + ]; let hv2 = hypervolume_2d(&front, &s, [4.0, 4.0]); let hvn = hypervolume_nd(&front, &s, &[4.0, 4.0]); assert!((hv2 - hvn).abs() < 1e-12, "{hv2} vs {hvn}"); @@ -409,10 +413,7 @@ mod nd_tests { #[test] #[should_panic(expected = "must agree on dimension")] fn nd_panics_on_dim_mismatch() { - let s = ObjectiveSpace::new(vec![ - Objective::minimize("f1"), - Objective::minimize("f2"), - ]); + let s = ObjectiveSpace::new(vec![Objective::minimize("f1"), Objective::minimize("f2")]); let front = [cand_n(vec![1.0, 1.0])]; let _ = hypervolume_nd(&front, &s, &[1.0, 1.0, 1.0]); } diff --git a/src/metrics/spacing.rs b/src/metrics/spacing.rs index 8f25a5e..84713e1 100644 --- a/src/metrics/spacing.rs +++ b/src/metrics/spacing.rs @@ -39,8 +39,7 @@ pub fn spacing(front: &[Candidate], objectives: &ObjectiveSpace) -> f64 { } let mean = nearest.iter().sum::() / n as f64; - let variance = - nearest.iter().map(|d| (d - mean).powi(2)).sum::() / n as f64; + let variance = nearest.iter().map(|d| (d - mean).powi(2)).sum::() / n as f64; variance.sqrt() } @@ -55,10 +54,7 @@ mod tests { } fn space_min2() -> ObjectiveSpace { - ObjectiveSpace::new(vec![ - Objective::minimize("f1"), - Objective::minimize("f2"), - ]) + ObjectiveSpace::new(vec![Objective::minimize("f1"), Objective::minimize("f2")]) } #[test] diff --git a/src/operators/real.rs b/src/operators/real.rs index f354cfe..b18bf3b 100644 --- a/src/operators/real.rs +++ b/src/operators/real.rs @@ -38,7 +38,11 @@ impl Initializer> for RealBounds { for _ in 0..size { let mut decision = Vec::with_capacity(self.bounds.len()); for &(lo, hi) in &self.bounds { - let v = if lo == hi { lo } else { rng.random_range(lo..=hi) }; + let v = if lo == hi { + lo + } else { + rng.random_range(lo..=hi) + }; decision.push(v); } out.push(decision); @@ -63,8 +67,7 @@ impl Variation> for GaussianMutation { !parents.is_empty(), "GaussianMutation requires at least one parent", ); - let normal = - Normal::new(0.0, self.sigma).expect("Normal distribution rejected sigma"); + let normal = Normal::new(0.0, self.sigma).expect("Normal distribution rejected sigma"); let mut child = parents[0].clone(); for x in child.iter_mut() { *x += normal.sample(rng); @@ -113,7 +116,11 @@ impl SimulatedBinaryCrossover { (0.0..=1.0).contains(&per_variable_probability), "SimulatedBinaryCrossover per_variable_probability must be in [0.0, 1.0]", ); - Self { bounds, eta, per_variable_probability } + Self { + bounds, + eta, + per_variable_probability, + } } } @@ -201,7 +208,11 @@ impl PolynomialMutation { (0.0..=1.0).contains(&per_variable_probability), "PolynomialMutation per_variable_probability must be in [0.0, 1.0]", ); - Self { bounds, eta, per_variable_probability } + Self { + bounds, + eta, + per_variable_probability, + } } } @@ -257,7 +268,10 @@ impl BoundedGaussianMutation { /// # Panics /// If `sigma <= 0.0` or any bound has `lo > hi`. pub fn new(sigma: f64, bounds: Vec<(f64, f64)>) -> Self { - assert!(sigma > 0.0, "BoundedGaussianMutation sigma must be positive"); + assert!( + sigma > 0.0, + "BoundedGaussianMutation sigma must be positive" + ); for (i, &(lo, hi)) in bounds.iter().enumerate() { assert!( lo <= hi, @@ -279,8 +293,7 @@ impl Variation> for BoundedGaussianMutation { self.bounds.len(), "BoundedGaussianMutation parent length must match bounds length", ); - let normal = - Normal::new(0.0, self.sigma).expect("Normal distribution rejected sigma"); + let normal = Normal::new(0.0, self.sigma).expect("Normal distribution rejected sigma"); let mut child = parents[0].clone(); for (x, &(lo, hi)) in child.iter_mut().zip(self.bounds.iter()) { *x = (*x + normal.sample(rng)).clamp(lo, hi); @@ -330,13 +343,20 @@ impl LevyMutation { "LevyMutation bound at index {i} has lo > hi: ({lo}, {hi})", ); } - Self { alpha, scale, bounds } + Self { + alpha, + scale, + bounds, + } } } impl Variation> for LevyMutation { fn vary(&mut self, parents: &[Vec], rng: &mut Rng) -> Vec> { - assert!(!parents.is_empty(), "LevyMutation requires at least one parent"); + assert!( + !parents.is_empty(), + "LevyMutation requires at least one parent" + ); let alpha = self.alpha; // Mantegna's algorithm σ for the numerator Normal: // sigma_u = (Γ(1+α)·sin(π·α/2) / (Γ((1+α)/2)·α·2^((α-1)/2)))^(1/α) diff --git a/src/pareto/archive.rs b/src/pareto/archive.rs index f07c90f..41b3f65 100644 --- a/src/pareto/archive.rs +++ b/src/pareto/archive.rs @@ -21,7 +21,10 @@ pub struct ParetoArchive { impl ParetoArchive { /// Build an empty archive against the given objective space. pub fn new(objectives: ObjectiveSpace) -> Self { - Self { members: Vec::new(), objectives } + Self { + members: Vec::new(), + objectives, + } } /// Insert a candidate, preserving the non-domination property. @@ -85,10 +88,7 @@ mod tests { use crate::core::objective::Objective; fn space_min2() -> ObjectiveSpace { - ObjectiveSpace::new(vec![ - Objective::minimize("f1"), - Objective::minimize("f2"), - ]) + ObjectiveSpace::new(vec![Objective::minimize("f1"), Objective::minimize("f2")]) } fn cand(decision: u32, obj: Vec) -> Candidate { diff --git a/src/pareto/crowding.rs b/src/pareto/crowding.rs index bdefac1..795b789 100644 --- a/src/pareto/crowding.rs +++ b/src/pareto/crowding.rs @@ -77,10 +77,7 @@ mod tests { } fn space_min2() -> ObjectiveSpace { - ObjectiveSpace::new(vec![ - Objective::minimize("f1"), - Objective::minimize("f2"), - ]) + ObjectiveSpace::new(vec![Objective::minimize("f1"), Objective::minimize("f2")]) } #[test] diff --git a/src/pareto/dominance.rs b/src/pareto/dominance.rs index 03a4489..aea7db0 100644 --- a/src/pareto/dominance.rs +++ b/src/pareto/dominance.rs @@ -29,11 +29,7 @@ pub enum Dominance { /// `constraint_violation` dominates. /// 3. Otherwise compare objective values after converting both to /// minimization orientation via [`ObjectiveSpace::as_minimization`]. -pub fn pareto_compare( - a: &Evaluation, - b: &Evaluation, - objectives: &ObjectiveSpace, -) -> Dominance { +pub fn pareto_compare(a: &Evaluation, b: &Evaluation, objectives: &ObjectiveSpace) -> Dominance { let a_feasible = a.is_feasible(); let b_feasible = b.is_feasible(); match (a_feasible, b_feasible) { @@ -78,10 +74,7 @@ mod tests { use crate::core::objective::Objective; fn space_min2() -> ObjectiveSpace { - ObjectiveSpace::new(vec![ - Objective::minimize("f1"), - Objective::minimize("f2"), - ]) + ObjectiveSpace::new(vec![Objective::minimize("f1"), Objective::minimize("f2")]) } #[test] diff --git a/src/pareto/front.rs b/src/pareto/front.rs index 2dba9f7..bd8b973 100644 --- a/src/pareto/front.rs +++ b/src/pareto/front.rs @@ -69,10 +69,7 @@ mod tests { } fn space_min2() -> ObjectiveSpace { - ObjectiveSpace::new(vec![ - Objective::minimize("f1"), - Objective::minimize("f2"), - ]) + ObjectiveSpace::new(vec![Objective::minimize("f1"), Objective::minimize("f2")]) } #[test] diff --git a/src/pareto/reference_points.rs b/src/pareto/reference_points.rs index dc74189..9da10eb 100644 --- a/src/pareto/reference_points.rs +++ b/src/pareto/reference_points.rs @@ -11,7 +11,10 @@ /// # Panics /// If `num_objectives == 0`. pub fn das_dennis(num_objectives: usize, divisions: usize) -> Vec> { - assert!(num_objectives > 0, "das_dennis requires num_objectives >= 1"); + assert!( + num_objectives > 0, + "das_dennis requires num_objectives >= 1" + ); let mut out = Vec::new(); let mut current = Vec::with_capacity(num_objectives); recurse(num_objectives, divisions, divisions, &mut current, &mut out); @@ -34,7 +37,13 @@ fn recurse( } for take in 0..=remaining_units { current.push(take); - recurse(remaining_axes - 1, remaining_units - take, total, current, out); + recurse( + remaining_axes - 1, + remaining_units - take, + total, + current, + out, + ); current.pop(); } } diff --git a/src/prelude.rs b/src/prelude.rs index 7bf05f3..02f1b68 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -12,28 +12,25 @@ pub use crate::core::{ pub use crate::traits::{Initializer, Optimizer, Repair, Variation}; pub use crate::pareto::{ - Dominance, ParetoArchive, best_candidate, crowding_distance, das_dennis, - non_dominated_sort, pareto_compare, pareto_front, + Dominance, ParetoArchive, best_candidate, crowding_distance, das_dennis, non_dominated_sort, + pareto_compare, pareto_front, }; pub use crate::operators::{ - BitFlipMutation, BoundedGaussianMutation, ClampToBounds, CompositeVariation, - GaussianMutation, LevyMutation, PolynomialMutation, ProjectToSimplex, RealBounds, - SimulatedBinaryCrossover, SwapMutation, + BitFlipMutation, BoundedGaussianMutation, ClampToBounds, CompositeVariation, GaussianMutation, + LevyMutation, PolynomialMutation, ProjectToSimplex, RealBounds, SimulatedBinaryCrossover, + SwapMutation, }; pub use crate::algorithms::{ - AgeMoea, AgeMoeaConfig, AntColonyTsp, AntColonyTspConfig, BayesianOpt, - BayesianOptConfig, CmaEs, CmaEsConfig, DifferentialEvolution, - DifferentialEvolutionConfig, EpsilonMoea, EpsilonMoeaConfig, - GeneticAlgorithm, GeneticAlgorithmConfig, Grea, GreaConfig, HillClimber, HillClimberConfig, Hype, - HypeConfig, Hyperband, HyperbandConfig, Ibea, IbeaConfig, IpopCmaEs, IpopCmaEsConfig, - Knea, KneaConfig, Moead, MoeadConfig, Mopso, MopsoConfig, - NelderMead, NelderMeadConfig, Nsga2, - Nsga2Config, Nsga3, Nsga3Config, OnePlusOneEs, OnePlusOneEsConfig, Paes, PaesConfig, ParticleSwarm, PesaII, PesaIIConfig, - ParticleSwarmConfig, RandomSearch, RandomSearchConfig, Rvea, RveaConfig, - SeparableNes, SeparableNesConfig, SimulatedAnnealing, + AgeMoea, AgeMoeaConfig, AntColonyTsp, AntColonyTspConfig, BayesianOpt, BayesianOptConfig, + CmaEs, CmaEsConfig, DifferentialEvolution, DifferentialEvolutionConfig, EpsilonMoea, + EpsilonMoeaConfig, GeneticAlgorithm, GeneticAlgorithmConfig, Grea, GreaConfig, HillClimber, + HillClimberConfig, Hype, HypeConfig, Hyperband, HyperbandConfig, Ibea, IbeaConfig, IpopCmaEs, + IpopCmaEsConfig, Knea, KneaConfig, Moead, MoeadConfig, Mopso, MopsoConfig, NelderMead, + NelderMeadConfig, Nsga2, Nsga2Config, Nsga3, Nsga3Config, OnePlusOneEs, OnePlusOneEsConfig, + Paes, PaesConfig, ParticleSwarm, ParticleSwarmConfig, PesaII, PesaIIConfig, RandomSearch, + RandomSearchConfig, Rvea, RveaConfig, SeparableNes, SeparableNesConfig, SimulatedAnnealing, SimulatedAnnealingConfig, SmsEmoa, SmsEmoaConfig, Spea2, Spea2Config, TabuSearch, - TabuSearchConfig, Tlbo, TlboConfig, Tpe, TpeConfig, Umda, - UmdaConfig, + TabuSearchConfig, Tlbo, TlboConfig, Tpe, TpeConfig, Umda, UmdaConfig, }; diff --git a/src/selection/random.rs b/src/selection/random.rs index 9d8613a..1a64988 100644 --- a/src/selection/random.rs +++ b/src/selection/random.rs @@ -9,11 +9,7 @@ use crate::core::rng::Rng; /// /// Returns cloned decisions. Panics if `population` is empty and `count > 0` /// (spec §10.1). -pub fn select_random( - population: &[Candidate], - count: usize, - rng: &mut Rng, -) -> Vec { +pub fn select_random(population: &[Candidate], count: usize, rng: &mut Rng) -> Vec { if count == 0 { return Vec::new(); } diff --git a/src/selection/tournament.rs b/src/selection/tournament.rs index 3ba7e03..53975c7 100644 --- a/src/selection/tournament.rs +++ b/src/selection/tournament.rs @@ -63,8 +63,18 @@ fn challenger_wins(c: &Candidate, b: &Candidate, dir: Direction) -> boo (false, true) => false, (false, false) => c.evaluation.constraint_violation < b.evaluation.constraint_violation, (true, true) => { - let cv = c.evaluation.objectives.first().copied().unwrap_or(f64::INFINITY); - let bv = b.evaluation.objectives.first().copied().unwrap_or(f64::INFINITY); + let cv = c + .evaluation + .objectives + .first() + .copied() + .unwrap_or(f64::INFINITY); + let bv = b + .evaluation + .objectives + .first() + .copied() + .unwrap_or(f64::INFINITY); match dir { Direction::Minimize => cv < bv, Direction::Maximize => cv > bv, @@ -218,10 +228,7 @@ mod tests { #[test] #[should_panic(expected = "exactly one objective")] fn multi_objective_panics() { - let s = ObjectiveSpace::new(vec![ - Objective::minimize("f1"), - Objective::minimize("f2"), - ]); + let s = ObjectiveSpace::new(vec![Objective::minimize("f1"), Objective::minimize("f2")]); let pop = [cand_min(1, 1.0)]; let mut rng = rng_from_seed(0); let _ = tournament_select_single_objective(&pop, &s, 2, 1, &mut rng); @@ -246,8 +253,8 @@ mod tests { let s = ObjectiveSpace::new(vec![Objective::minimize("f")]); let pop = [ Candidate::new(1u32, Evaluation::constrained(vec![0.0], 5.0)), // infeasible - Candidate::new(2u32, Evaluation::new(vec![10.0])), // feasible, big f - Candidate::new(3u32, Evaluation::new(vec![3.0])), // feasible, small f + Candidate::new(2u32, Evaluation::new(vec![10.0])), // feasible, big f + Candidate::new(3u32, Evaluation::new(vec![3.0])), // feasible, small f ]; let mut rng = rng_from_seed(0); let picks = stochastic_ranking_select(&pop, &s, 0.0, 3, &mut rng); diff --git a/tests/algorithm_properties.rs b/tests/algorithm_properties.rs index fdaf29c..7f1f034 100644 --- a/tests/algorithm_properties.rs +++ b/tests/algorithm_properties.rs @@ -43,6 +43,7 @@ impl Problem for SchafferN1 { } struct OneMax { + #[allow(dead_code)] bits: usize, } impl Problem for OneMax { @@ -69,8 +70,7 @@ fn mo_bounds() -> Vec<(f64, f64)> { vec![(-3.0, 3.0)] } -fn mo_variation() --> CompositeVariation { +fn mo_variation() -> CompositeVariation { let bounds = mo_bounds(); CompositeVariation { crossover: SimulatedBinaryCrossover::new(bounds.clone(), 15.0, 0.5), diff --git a/tests/operator_properties.rs b/tests/operator_properties.rs index 624105f..d70f9e6 100644 --- a/tests/operator_properties.rs +++ b/tests/operator_properties.rs @@ -9,8 +9,12 @@ use heuropt::prelude::*; /// Generate per-axis bounds whose width is at least 0.001 (avoid the /// degenerate `lo == hi` case for properties that need a proper interval). fn bounds(dim: usize) -> impl Strategy> { - prop::collection::vec((-50.0_f64..50.0, 0.001_f64..50.0), dim..=dim) - .prop_map(|pairs| pairs.into_iter().map(|(lo, span)| (lo, lo + span)).collect()) + prop::collection::vec((-50.0_f64..50.0, 0.001_f64..50.0), dim..=dim).prop_map(|pairs| { + pairs + .into_iter() + .map(|(lo, span)| (lo, lo + span)) + .collect() + }) } /// Generate a parent vector inside the given bounds. diff --git a/tests/properties.rs b/tests/properties.rs index e322b65..280366e 100644 --- a/tests/properties.rs +++ b/tests/properties.rs @@ -20,17 +20,12 @@ use heuropt::prelude::*; /// Generate a 2-objective minimize ObjectiveSpace. fn space_2d() -> ObjectiveSpace { - ObjectiveSpace::new(vec![ - Objective::minimize("f1"), - Objective::minimize("f2"), - ]) + ObjectiveSpace::new(vec![Objective::minimize("f1"), Objective::minimize("f2")]) } /// Generate a candidate with a 2-D objective vector in `[lo, hi]`. fn candidate_2d(lo: f64, hi: f64) -> impl Strategy> { - (lo..hi, lo..hi).prop_map(|(a, b)| { - Candidate::new((), Evaluation::new(vec![a, b])) - }) + (lo..hi, lo..hi).prop_map(|(a, b)| Candidate::new((), Evaluation::new(vec![a, b]))) } /// Generate a small 2-D population. @@ -40,8 +35,12 @@ fn population_2d() -> impl Strategy>> { /// Generate per-axis bounds. fn bounds(dim: usize) -> impl Strategy> { - prop::collection::vec((-50.0_f64..50.0, 0.001_f64..50.0), dim..=dim) - .prop_map(|pairs| pairs.into_iter().map(|(lo, span)| (lo, lo + span)).collect()) + prop::collection::vec((-50.0_f64..50.0, 0.001_f64..50.0), dim..=dim).prop_map(|pairs| { + pairs + .into_iter() + .map(|(lo, span)| (lo, lo + span)) + .collect() + }) } // -----------------------------------------------------------------------------