style: apply rustfmt drift across the crate
This commit is contained in:
@@ -0,0 +1 @@
|
||||
{"sessionId":"ac44d107-52ca-4cd4-9586-ae2fe91bc9f7","pid":2366937,"procStart":"77336928","acquiredAt":1778002505967}
|
||||
+122
-37
@@ -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<f64> {
|
||||
let pop = make_2d_population(n);
|
||||
let s = space_2d();
|
||||
let front: Vec<usize> = (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<f64> {
|
||||
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<Candidate<()>>, ObjectiveSpace) {
|
||||
@@ -75,10 +83,7 @@ fn make_3d_population(n: usize) -> (Vec<Candidate<()>>, 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<Candidate<()>>, 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<SimulatedBinaryCrossover, PolynomialMutation> {
|
||||
fn mo_variation() -> CompositeVariation<SimulatedBinaryCrossover, PolynomialMutation> {
|
||||
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
|
||||
);
|
||||
|
||||
@@ -58,7 +58,9 @@ impl Problem for Rastrigin {
|
||||
fn evaluate(&self, x: &Vec<f64>) -> 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::<f64>();
|
||||
+ x.iter()
|
||||
.map(|v| v * v - 10.0 * (2.0 * PI * v).cos())
|
||||
.sum::<f64>();
|
||||
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);
|
||||
|
||||
+319
-80
@@ -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<f64>) -> 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::<f64>();
|
||||
+ x.iter()
|
||||
.map(|v| v * v - 10.0 * (2.0 * PI * v).cos())
|
||||
.sum::<f64>();
|
||||
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<Vec<f64>>]) -> f64 {
|
||||
let total: f64 = front
|
||||
.iter()
|
||||
.map(|c| {
|
||||
let norm: f64 = c.evaluation.objectives.iter().map(|v| v * v).sum::<f64>().sqrt();
|
||||
let norm: f64 = c
|
||||
.evaluation
|
||||
.objectives
|
||||
.iter()
|
||||
.map(|v| v * v)
|
||||
.sum::<f64>()
|
||||
.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<Vec<f64>>]) -> 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<f64> =
|
||||
runs.iter().map(|r| spacing(&r.front, &zdt1_objs)).collect();
|
||||
let l2: Vec<f64> =
|
||||
runs.iter().map(|r| mean_l2_to_zdt1_front(&r.front)).collect();
|
||||
let sp: Vec<f64> = runs.iter().map(|r| spacing(&r.front, &zdt1_objs)).collect();
|
||||
let l2: Vec<f64> = runs
|
||||
.iter()
|
||||
.map(|r| mean_l2_to_zdt1_front(&r.front))
|
||||
.collect();
|
||||
let fs: Vec<f64> = runs.iter().map(|r| r.front.len() as f64).collect();
|
||||
let ms: Vec<f64> = 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<MoRun> = (0..SEEDS).map(runner).collect();
|
||||
let dist: Vec<f64> =
|
||||
runs.iter().map(|r| mean_distance_to_dtlz2_front(&r.front)).collect();
|
||||
let sp: Vec<f64> =
|
||||
runs.iter().map(|r| spacing(&r.front, &dtlz2_objs)).collect();
|
||||
let dist: Vec<f64> = runs
|
||||
.iter()
|
||||
.map(|r| mean_distance_to_dtlz2_front(&r.front))
|
||||
.collect();
|
||||
let sp: Vec<f64> = runs
|
||||
.iter()
|
||||
.map(|r| spacing(&r.front, &dtlz2_objs))
|
||||
.collect();
|
||||
let fs: Vec<f64> = runs.iter().map(|r| r.front.len() as f64).collect();
|
||||
let ms: Vec<f64> = 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<MoRun> = (0..SEEDS).map(runner).collect();
|
||||
let hv: Vec<f64> = runs.iter().map(|r| hypervolume_2d(&r.front, &objs, ZDT3_REFERENCE)).collect();
|
||||
let hv: Vec<f64> = runs
|
||||
.iter()
|
||||
.map(|r| hypervolume_2d(&r.front, &objs, ZDT3_REFERENCE))
|
||||
.collect();
|
||||
let sp: Vec<f64> = runs.iter().map(|r| spacing(&r.front, &objs)).collect();
|
||||
let fs: Vec<f64> = runs.iter().map(|r| r.front.len() as f64).collect();
|
||||
let ms: Vec<f64> = 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<MoRun> = (0..SEEDS).map(runner).collect();
|
||||
let dist: Vec<f64> = runs.iter().map(|r| mean_distance_to_dtlz1_front(&r.front)).collect();
|
||||
let dist: Vec<f64> = runs
|
||||
.iter()
|
||||
.map(|r| mean_distance_to_dtlz1_front(&r.front))
|
||||
.collect();
|
||||
let sp: Vec<f64> = runs.iter().map(|r| spacing(&r.front, &objs)).collect();
|
||||
let fs: Vec<f64> = runs.iter().map(|r| r.front.len() as f64).collect();
|
||||
let ms: Vec<f64> = runs.iter().map(|r| r.wall_ms as f64).collect();
|
||||
|
||||
@@ -26,7 +26,10 @@ where
|
||||
{
|
||||
fn run(&mut self, problem: &P) -> OptimizationResult<P::Decision> {
|
||||
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 };
|
||||
|
||||
|
||||
+55
-16
@@ -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::<Vec<_>>());
|
||||
let scores = compute_weighted_scores(
|
||||
&candidates
|
||||
.iter()
|
||||
.map(|(_, r)| r.clone())
|
||||
.collect::<Vec<_>>(),
|
||||
);
|
||||
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<f64> {
|
||||
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.
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
+53
-20
@@ -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<I, V> {
|
||||
impl<I, V> AgeMoea<I, V> {
|
||||
/// 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<P::Decision>,
|
||||
{
|
||||
fn run(&mut self, problem: &P) -> OptimizationResult<P::Decision> {
|
||||
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<D: Clone>(
|
||||
.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::<f64>().powf(1.0 / p)
|
||||
a.iter()
|
||||
.zip(b.iter())
|
||||
.map(|(x, y)| (x - y).abs().powf(p))
|
||||
.sum::<f64>()
|
||||
.powf(1.0 / p)
|
||||
}
|
||||
|
||||
fn nearest_neighbor_distance(
|
||||
i: usize,
|
||||
translated: &[Vec<f64>],
|
||||
selected: &[usize],
|
||||
p: f64,
|
||||
) -> f64 {
|
||||
fn nearest_neighbor_distance(i: usize, translated: &[Vec<f64>], 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<Vec<f64>> =
|
||||
ra.pareto_front.iter().map(|c| c.evaluation.objectives.clone()).collect();
|
||||
let ob: Vec<Vec<f64>> =
|
||||
rb.pareto_front.iter().map(|c| c.evaluation.objectives.clone()).collect();
|
||||
let oa: Vec<Vec<f64>> = ra
|
||||
.pareto_front
|
||||
.iter()
|
||||
.map(|c| c.evaluation.objectives.clone())
|
||||
.collect();
|
||||
let ob: Vec<Vec<f64>> = 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,
|
||||
);
|
||||
|
||||
@@ -70,10 +70,16 @@ impl AntColonyTsp {
|
||||
/// and has a zero diagonal.
|
||||
pub fn new(config: AntColonyTspConfig, distances: Vec<Vec<f64>>) -> 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<Vec<f64>> = 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<f64>> =
|
||||
vec![vec![self.config.initial_pheromone; n]; n];
|
||||
let mut pheromone: Vec<Vec<f64>> = vec![vec![self.config.initial_pheromone; n]; n];
|
||||
|
||||
let mut best_decision: Option<Vec<usize>> = None;
|
||||
let mut best_eval: Option<crate::core::evaluation::Evaluation> = 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<usize>;
|
||||
|
||||
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<usize>) -> Evaluation {
|
||||
|
||||
@@ -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<f64> = 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<f64> = 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<f64>> = Vec::with_capacity(
|
||||
self.config.initial_samples + self.config.iterations,
|
||||
);
|
||||
let mut decisions: Vec<Vec<f64>> =
|
||||
Vec::with_capacity(self.config.initial_samples + self.config.iterations);
|
||||
let mut targets: Vec<f64> = 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<f64> {
|
||||
bounds
|
||||
.bounds
|
||||
.iter()
|
||||
.map(|&(lo, hi)| if lo == hi { lo } else { lo + (hi - lo) * rng.random::<f64>() })
|
||||
.map(|&(lo, hi)| {
|
||||
if lo == hi {
|
||||
lo
|
||||
} else {
|
||||
lo + (hi - lo) * rng.random::<f64>()
|
||||
}
|
||||
})
|
||||
.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
|
||||
}
|
||||
|
||||
|
||||
+17
-17
@@ -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<usize> = (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);
|
||||
}
|
||||
|
||||
|
||||
@@ -91,8 +91,10 @@ where
|
||||
};
|
||||
let initial_pop = evaluate_batch(problem, decisions.clone());
|
||||
let mut evaluations = initial_pop.len();
|
||||
let mut evals: Vec<f64> =
|
||||
initial_pop.iter().map(|c| c.evaluation.objectives[0]).collect();
|
||||
let mut evals: Vec<f64> = 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);
|
||||
|
||||
@@ -52,7 +52,11 @@ pub struct EpsilonMoea<I, V> {
|
||||
impl<I, V> EpsilonMoea<I, V> {
|
||||
/// 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<P::Decision>,
|
||||
{
|
||||
fn run(&mut self, problem: &P) -> OptimizationResult<P::Decision> {
|
||||
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<D: Clone>(
|
||||
}
|
||||
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<Vec<f64>> =
|
||||
ra.pareto_front.iter().map(|c| c.evaluation.objectives.clone()).collect();
|
||||
let ob: Vec<Vec<f64>> =
|
||||
rb.pareto_front.iter().map(|c| c.evaluation.objectives.clone()).collect();
|
||||
let oa: Vec<Vec<f64>> = ra
|
||||
.pareto_front
|
||||
.iter()
|
||||
.map(|c| c.evaluation.objectives.clone())
|
||||
.collect();
|
||||
let ob: Vec<Vec<f64>> = rb
|
||||
.pareto_front
|
||||
.iter()
|
||||
.map(|c| c.evaluation.objectives.clone())
|
||||
.collect();
|
||||
assert_eq!(oa, ob);
|
||||
}
|
||||
|
||||
|
||||
@@ -59,7 +59,11 @@ pub struct GeneticAlgorithm<I, V> {
|
||||
impl<I, V> GeneticAlgorithm<I, V> {
|
||||
/// 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<RealBounds, CompositeVariation<SimulatedBinaryCrossover, PolynomialMutation>>
|
||||
{
|
||||
) -> GeneticAlgorithm<
|
||||
RealBounds,
|
||||
CompositeVariation<SimulatedBinaryCrossover, PolynomialMutation>,
|
||||
> {
|
||||
let bounds = vec![(-5.0, 5.0)];
|
||||
let initializer = RealBounds::new(bounds.clone());
|
||||
let variation = CompositeVariation {
|
||||
|
||||
+29
-10
@@ -51,7 +51,11 @@ pub struct Grea<I, V> {
|
||||
impl<I, V> Grea<I, V> {
|
||||
/// 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<P::Decision>,
|
||||
{
|
||||
fn run(&mut self, problem: &P) -> OptimizationResult<P::Decision> {
|
||||
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<Candidate<P::Decision>> = 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<Vec<f64>> =
|
||||
ra.pareto_front.iter().map(|c| c.evaluation.objectives.clone()).collect();
|
||||
let ob: Vec<Vec<f64>> =
|
||||
rb.pareto_front.iter().map(|c| c.evaluation.objectives.clone()).collect();
|
||||
let oa: Vec<Vec<f64>> = ra
|
||||
.pareto_front
|
||||
.iter()
|
||||
.map(|c| c.evaluation.objectives.clone())
|
||||
.collect();
|
||||
let ob: Vec<Vec<f64>> = rb
|
||||
.pareto_front
|
||||
.iter()
|
||||
.map(|c| c.evaluation.objectives.clone())
|
||||
.collect();
|
||||
assert_eq!(oa, ob);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<I, V> {
|
||||
impl<I, V> HillClimber<I, V> {
|
||||
/// 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<RealBounds, GaussianMutation> {
|
||||
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]
|
||||
|
||||
+41
-13
@@ -61,7 +61,11 @@ pub struct Hype<I, V> {
|
||||
impl<I, V> Hype<I, V> {
|
||||
/// 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<P::Decision>,
|
||||
{
|
||||
fn run(&mut self, problem: &P) -> OptimizationResult<P::Decision> {
|
||||
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<P::Decision> = 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<P::Decision>> =
|
||||
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<usize> = (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<Vec<f64>> =
|
||||
ra.pareto_front.iter().map(|c| c.evaluation.objectives.clone()).collect();
|
||||
let ob: Vec<Vec<f64>> =
|
||||
rb.pareto_front.iter().map(|c| c.evaluation.objectives.clone()).collect();
|
||||
let oa: Vec<Vec<f64>> = ra
|
||||
.pareto_front
|
||||
.iter()
|
||||
.map(|c| c.evaluation.objectives.clone())
|
||||
.collect();
|
||||
let ob: Vec<Vec<f64>> = rb
|
||||
.pareto_front
|
||||
.iter()
|
||||
.map(|c| c.evaluation.objectives.clone())
|
||||
.collect();
|
||||
assert_eq!(oa, ob);
|
||||
}
|
||||
|
||||
|
||||
+22
-11
@@ -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<Decision = D>,
|
||||
{
|
||||
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<f64>;
|
||||
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>, _: f64) -> Evaluation {
|
||||
Evaluation::new(vec![0.0, 0.0])
|
||||
|
||||
+42
-15
@@ -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<I, V> {
|
||||
impl<I, V> Ibea<I, V> {
|
||||
/// 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<P::Decision>,
|
||||
{
|
||||
fn run(&mut self, problem: &P) -> OptimizationResult<P::Decision> {
|
||||
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<D: Clone>(
|
||||
}
|
||||
|
||||
/// Compute IBEA fitness without mutating, for use in tournament selection.
|
||||
fn compute_fitness<D>(
|
||||
pool: &[Candidate<D>],
|
||||
objectives: &ObjectiveSpace,
|
||||
kappa: f64,
|
||||
) -> Vec<f64> {
|
||||
fn compute_fitness<D>(pool: &[Candidate<D>], objectives: &ObjectiveSpace, kappa: f64) -> Vec<f64> {
|
||||
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<Vec<f64>> =
|
||||
ra.pareto_front.iter().map(|c| c.evaluation.objectives.clone()).collect();
|
||||
let ob: Vec<Vec<f64>> =
|
||||
rb.pareto_front.iter().map(|c| c.evaluation.objectives.clone()).collect();
|
||||
let oa: Vec<Vec<f64>> = ra
|
||||
.pareto_front
|
||||
.iter()
|
||||
.map(|c| c.evaluation.objectives.clone())
|
||||
.collect();
|
||||
let ob: Vec<Vec<f64>> = 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,
|
||||
);
|
||||
|
||||
+34
-15
@@ -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<I, V> {
|
||||
impl<I, V> Knea<I, V> {
|
||||
/// 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<P::Decision>,
|
||||
{
|
||||
fn run(&mut self, problem: &P) -> OptimizationResult<P::Decision> {
|
||||
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<D: Clone>(
|
||||
|
||||
/// 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>],
|
||||
) -> f64 {
|
||||
fn perpendicular_distance(point: &[f64], extremes: &[usize], oriented: &[Vec<f64>]) -> 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<Vec<f64>> =
|
||||
ra.pareto_front.iter().map(|c| c.evaluation.objectives.clone()).collect();
|
||||
let ob: Vec<Vec<f64>> =
|
||||
rb.pareto_front.iter().map(|c| c.evaluation.objectives.clone()).collect();
|
||||
let oa: Vec<Vec<f64>> = ra
|
||||
.pareto_front
|
||||
.iter()
|
||||
.map(|c| c.evaluation.objectives.clone())
|
||||
.collect();
|
||||
let ob: Vec<Vec<f64>> = rb
|
||||
.pareto_front
|
||||
.iter()
|
||||
.map(|c| c.evaluation.objectives.clone())
|
||||
.collect();
|
||||
assert_eq!(oa, ob);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
+22
-12
@@ -52,7 +52,11 @@ pub struct Moead<I, V> {
|
||||
impl<I, V> Moead<I, V> {
|
||||
/// 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::<f64>().sqrt()
|
||||
a.iter()
|
||||
.zip(b.iter())
|
||||
.map(|(x, y)| (x - y).powi(2))
|
||||
.sum::<f64>()
|
||||
.sqrt()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -201,10 +214,7 @@ mod tests {
|
||||
|
||||
fn make_optimizer(
|
||||
seed: u64,
|
||||
) -> Moead<
|
||||
RealBounds,
|
||||
CompositeVariation<SimulatedBinaryCrossover, PolynomialMutation>,
|
||||
> {
|
||||
) -> Moead<RealBounds, CompositeVariation<SimulatedBinaryCrossover, PolynomialMutation>> {
|
||||
let bounds = vec![(-5.0, 5.0)];
|
||||
let initializer = RealBounds::new(bounds.clone());
|
||||
let variation = CompositeVariation {
|
||||
|
||||
+17
-10
@@ -73,7 +73,10 @@ where
|
||||
{
|
||||
fn run(&mut self, problem: &P) -> OptimizationResult<P::Decision> {
|
||||
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<Vec<f64>> =
|
||||
ra.pareto_front.iter().map(|c| c.evaluation.objectives.clone()).collect();
|
||||
let ob: Vec<Vec<f64>> =
|
||||
rb.pareto_front.iter().map(|c| c.evaluation.objectives.clone()).collect();
|
||||
let oa: Vec<Vec<f64>> = ra
|
||||
.pareto_front
|
||||
.iter()
|
||||
.map(|c| c.evaluation.objectives.clone())
|
||||
.collect();
|
||||
let ob: Vec<Vec<f64>> = rb
|
||||
.pareto_front
|
||||
.iter()
|
||||
.map(|c| c.evaluation.objectives.clone())
|
||||
.collect();
|
||||
assert_eq!(oa, ob);
|
||||
}
|
||||
|
||||
|
||||
@@ -69,7 +69,10 @@ where
|
||||
P: Problem<Decision = Vec<f64>> + Sync,
|
||||
{
|
||||
fn run(&mut self, problem: &P) -> OptimizationResult<P::Decision> {
|
||||
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<Evaluation> =
|
||||
vertices.iter().map(|v| problem.evaluate(v)).collect();
|
||||
let mut evals: Vec<Evaluation> = 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() {
|
||||
|
||||
+54
-15
@@ -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<I, V> {
|
||||
impl<I, V> Nsga2<I, V> {
|
||||
/// 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<Candidate<P::Decision>> =
|
||||
evaluate_batch(problem, initial_decisions);
|
||||
let population: Vec<Candidate<P::Decision>> = 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<usize> = (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<D: Clone>(
|
||||
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<Vec<f64>> =
|
||||
ra.pareto_front.iter().map(|c| c.evaluation.objectives.clone()).collect();
|
||||
let ob: Vec<Vec<f64>> =
|
||||
rb.pareto_front.iter().map(|c| c.evaluation.objectives.clone()).collect();
|
||||
let oa: Vec<Vec<f64>> = ra
|
||||
.pareto_front
|
||||
.iter()
|
||||
.map(|c| c.evaluation.objectives.clone())
|
||||
.collect();
|
||||
let ob: Vec<Vec<f64>> = 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 },
|
||||
);
|
||||
|
||||
+26
-15
@@ -56,7 +56,11 @@ pub struct Nsga3<I, V> {
|
||||
impl<I, V> Nsga3<I, V> {
|
||||
/// 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<Candidate<P::Decision>> =
|
||||
Vec::with_capacity(2 * n);
|
||||
let mut combined: Vec<Candidate<P::Decision>> = 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<D: Clone>(
|
||||
let candidate_refs: Vec<usize> = (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<SimulatedBinaryCrossover, PolynomialMutation>,
|
||||
> {
|
||||
) -> Nsga3<RealBounds, CompositeVariation<SimulatedBinaryCrossover, PolynomialMutation>> {
|
||||
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<Vec<f64>> =
|
||||
ra.pareto_front.iter().map(|c| c.evaluation.objectives.clone()).collect();
|
||||
let ob: Vec<Vec<f64>> =
|
||||
rb.pareto_front.iter().map(|c| c.evaluation.objectives.clone()).collect();
|
||||
let oa: Vec<Vec<f64>> = ra
|
||||
.pareto_front
|
||||
.iter()
|
||||
.map(|c| c.evaluation.objectives.clone())
|
||||
.collect();
|
||||
let ob: Vec<Vec<f64>> = rb
|
||||
.pareto_front
|
||||
.iter()
|
||||
.map(|c| c.evaluation.objectives.clone())
|
||||
.collect();
|
||||
assert_eq!(oa, ob);
|
||||
}
|
||||
|
||||
|
||||
@@ -68,7 +68,10 @@ where
|
||||
P: Problem<Decision = Vec<f64>> + Sync,
|
||||
{
|
||||
fn run(&mut self, problem: &P) -> OptimizationResult<P::Decision> {
|
||||
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;
|
||||
|
||||
+34
-11
@@ -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<I, V> {
|
||||
impl<I, V> Paes<I, V> {
|
||||
/// 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 },
|
||||
);
|
||||
|
||||
@@ -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] {
|
||||
|
||||
+46
-18
@@ -60,7 +60,11 @@ pub struct PesaII<I, V> {
|
||||
impl<I, V> PesaII<I, V> {
|
||||
/// 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<P::Decision>,
|
||||
{
|
||||
fn run(&mut self, problem: &P) -> OptimizationResult<P::Decision> {
|
||||
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<D: Clone>(
|
||||
|
||||
/// 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<D: Clone>(
|
||||
archive: &mut ParetoArchive<D>,
|
||||
max_size: usize,
|
||||
divisions: usize,
|
||||
) {
|
||||
fn truncate_by_grid<D: Clone>(archive: &mut ParetoArchive<D>, 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<Vec<f64>> =
|
||||
ra.pareto_front.iter().map(|c| c.evaluation.objectives.clone()).collect();
|
||||
let ob: Vec<Vec<f64>> =
|
||||
rb.pareto_front.iter().map(|c| c.evaluation.objectives.clone()).collect();
|
||||
let oa: Vec<Vec<f64>> = ra
|
||||
.pareto_front
|
||||
.iter()
|
||||
.map(|c| c.evaluation.objectives.clone())
|
||||
.collect();
|
||||
let ob: Vec<Vec<f64>> = 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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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<I> {
|
||||
impl<I> RandomSearch<I> {
|
||||
/// 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);
|
||||
|
||||
+44
-17
@@ -54,7 +54,11 @@ pub struct Rvea<I, V> {
|
||||
impl<I, V> Rvea<I, V> {
|
||||
/// 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<P::Decision>,
|
||||
{
|
||||
fn run(&mut self, problem: &P) -> OptimizationResult<P::Decision> {
|
||||
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<Vec<f64>> = 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<Candidate<P::Decision>> =
|
||||
keep.into_iter().flatten().map(|(i, _)| combined[i].clone()).collect();
|
||||
let mut next: Vec<Candidate<P::Decision>> = 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::<f64>().sqrt();
|
||||
let length: f64 = translated[i].iter().map(|v| v * v).sum::<f64>().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>]) -> 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<Vec<f64>> =
|
||||
ra.pareto_front.iter().map(|c| c.evaluation.objectives.clone()).collect();
|
||||
let ob: Vec<Vec<f64>> =
|
||||
rb.pareto_front.iter().map(|c| c.evaluation.objectives.clone()).collect();
|
||||
let oa: Vec<Vec<f64>> = ra
|
||||
.pareto_front
|
||||
.iter()
|
||||
.map(|c| c.evaluation.objectives.clone())
|
||||
.collect();
|
||||
let ob: Vec<Vec<f64>> = rb
|
||||
.pareto_front
|
||||
.iter()
|
||||
.map(|c| c.evaluation.objectives.clone())
|
||||
.collect();
|
||||
assert_eq!(oa, ob);
|
||||
}
|
||||
|
||||
|
||||
@@ -54,7 +54,11 @@ pub struct SimulatedAnnealing<I, V> {
|
||||
impl<I, V> SimulatedAnnealing<I, V> {
|
||||
/// 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;
|
||||
|
||||
+27
-10
@@ -62,7 +62,11 @@ pub struct SmsEmoa<I, V> {
|
||||
impl<I, V> SmsEmoa<I, V> {
|
||||
/// 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<P::Decision>,
|
||||
{
|
||||
fn run(&mut self, problem: &P) -> OptimizationResult<P::Decision> {
|
||||
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<Vec<f64>> =
|
||||
ra.pareto_front.iter().map(|c| c.evaluation.objectives.clone()).collect();
|
||||
let ob: Vec<Vec<f64>> =
|
||||
rb.pareto_front.iter().map(|c| c.evaluation.objectives.clone()).collect();
|
||||
let oa: Vec<Vec<f64>> = ra
|
||||
.pareto_front
|
||||
.iter()
|
||||
.map(|c| c.evaluation.objectives.clone())
|
||||
.collect();
|
||||
let ob: Vec<Vec<f64>> = 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
+43
-14
@@ -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<I, V> {
|
||||
impl<I, V> Spea2<I, V> {
|
||||
/// 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<D>(pool: &[Candidate<D>], objectives: &ObjectiveSpace) -> Vec
|
||||
}
|
||||
|
||||
fn euclidean(a: &[f64], b: &[f64]) -> f64 {
|
||||
a.iter().zip(b.iter()).map(|(x, y)| (x - y).powi(2)).sum::<f64>().sqrt()
|
||||
a.iter()
|
||||
.zip(b.iter())
|
||||
.map(|(x, y)| (x - y).powi(2))
|
||||
.sum::<f64>()
|
||||
.sqrt()
|
||||
}
|
||||
|
||||
/// Build the next archive of exactly `target_size` members.
|
||||
@@ -213,10 +226,11 @@ fn build_archive<D: Clone>(
|
||||
|
||||
if nondom.len() < target_size {
|
||||
// Fill from dominated members ordered by ascending fitness.
|
||||
let mut dominated: Vec<usize> =
|
||||
(0..pool.len()).filter(|&i| fitness[i] >= 1.0).collect();
|
||||
let mut dominated: Vec<usize> = (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<D: Clone>(
|
||||
}
|
||||
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<D: Clone>(
|
||||
.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<D: Clone>(
|
||||
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<Vec<f64>> =
|
||||
ra.pareto_front.iter().map(|c| c.evaluation.objectives.clone()).collect();
|
||||
let ob: Vec<Vec<f64>> =
|
||||
rb.pareto_front.iter().map(|c| c.evaluation.objectives.clone()).collect();
|
||||
let oa: Vec<Vec<f64>> = ra
|
||||
.pareto_front
|
||||
.iter()
|
||||
.map(|c| c.evaluation.objectives.clone())
|
||||
.collect();
|
||||
let ob: Vec<Vec<f64>> = rb
|
||||
.pareto_front
|
||||
.iter()
|
||||
.map(|c| c.evaluation.objectives.clone())
|
||||
.collect();
|
||||
assert_eq!(oa, ob);
|
||||
}
|
||||
|
||||
|
||||
@@ -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<P::Decision> = VecDeque::with_capacity(self.config.tabu_tenure);
|
||||
let mut tabu_queue: VecDeque<P::Decision> =
|
||||
VecDeque::with_capacity(self.config.tabu_tenure);
|
||||
let mut tabu_set: HashSet<P::Decision> = 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<F>(
|
||||
seed: u64,
|
||||
neighbors: F,
|
||||
) -> TabuSearch<Vec<i32>, StartAtZero, F>
|
||||
fn make_optimizer<F>(seed: u64, neighbors: F) -> TabuSearch<Vec<i32>, StartAtZero, F>
|
||||
where
|
||||
F: FnMut(&Vec<i32>, &mut Rng) -> Vec<Vec<i32>>,
|
||||
{
|
||||
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<i32>, _rng: &mut Rng| {
|
||||
vec![vec![x[0] - 1], vec![x[0] + 1]]
|
||||
};
|
||||
let neighbors = |x: &Vec<i32>, _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();
|
||||
|
||||
+10
-4
@@ -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<Decision = Vec<f64>> + Sync,
|
||||
{
|
||||
fn run(&mut self, problem: &P) -> OptimizationResult<P::Decision> {
|
||||
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<Evaluation> =
|
||||
decisions.iter().map(|d| problem.evaluate(d)).collect();
|
||||
let mut evals: Vec<Evaluation> = decisions.iter().map(|d| problem.evaluate(d)).collect();
|
||||
let mut evaluations = decisions.len();
|
||||
|
||||
for _ in 0..self.config.generations {
|
||||
|
||||
+42
-8
@@ -72,7 +72,10 @@ where
|
||||
P: Problem<Decision = Vec<f64>> + Sync,
|
||||
{
|
||||
fn run(&mut self, problem: &P) -> OptimizationResult<P::Decision> {
|
||||
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<Vec<f64>> = 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<f64> {
|
||||
bounds
|
||||
.bounds
|
||||
.iter()
|
||||
.map(|&(lo, hi)| if lo == hi { lo } else { lo + (hi - lo) * rng.random::<f64>() })
|
||||
.map(|&(lo, hi)| {
|
||||
if lo == hi {
|
||||
lo
|
||||
} else {
|
||||
lo + (hi - lo) * rng.random::<f64>()
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
@@ -191,7 +221,9 @@ fn split_good_bad(targets: &[f64], good_fraction: f64) -> (Vec<usize>, Vec<usize
|
||||
let n = targets.len();
|
||||
let mut order: Vec<usize> = (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<f64>], 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)]
|
||||
|
||||
+10
-6
@@ -67,7 +67,10 @@ where
|
||||
P: Problem<Decision = Vec<bool>> + Sync,
|
||||
{
|
||||
fn run(&mut self, problem: &P) -> OptimizationResult<P::Decision> {
|
||||
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<usize> = (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<Vec<bool>>> =
|
||||
order.iter().take(mu).map(|&i| &population[i]).collect();
|
||||
@@ -228,10 +235,7 @@ mod tests {
|
||||
type Decision = Vec<bool>;
|
||||
|
||||
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<bool>) -> Evaluation {
|
||||
|
||||
@@ -18,7 +18,10 @@ pub struct Candidate<D> {
|
||||
impl<D> Candidate<D> {
|
||||
/// Pair a decision with its evaluation.
|
||||
pub fn new(decision: D, evaluation: Evaluation) -> Self {
|
||||
Self { decision, evaluation }
|
||||
Self {
|
||||
decision,
|
||||
evaluation,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,12 +18,18 @@ pub struct Evaluation {
|
||||
impl Evaluation {
|
||||
/// Build a feasible evaluation from objective values.
|
||||
pub fn new(objectives: Vec<f64>) -> 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<f64>, constraint_violation: f64) -> Self {
|
||||
Self { objectives, constraint_violation }
|
||||
Self {
|
||||
objectives,
|
||||
constraint_violation,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` when `constraint_violation <= 0.0`.
|
||||
|
||||
@@ -26,12 +26,18 @@ pub struct Objective {
|
||||
impl Objective {
|
||||
/// Create a minimize objective with the given name.
|
||||
pub fn minimize(name: impl Into<String>) -> 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<String>) -> 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());
|
||||
|
||||
|
||||
+7
-1
@@ -31,7 +31,13 @@ impl<D> OptimizationResult<D> {
|
||||
evaluations: usize,
|
||||
generations: usize,
|
||||
) -> Self {
|
||||
Self { population, pareto_front, best, evaluations, generations }
|
||||
Self {
|
||||
population,
|
||||
pareto_front,
|
||||
best,
|
||||
evaluations,
|
||||
generations,
|
||||
}
|
||||
}
|
||||
|
||||
/// The final population.
|
||||
|
||||
@@ -21,7 +21,10 @@ pub(crate) fn symmetric_eigen(
|
||||
max_sweeps: usize,
|
||||
) -> (Vec<f64>, Vec<Vec<f64>>) {
|
||||
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<Vec<f64>> = matrix.to_vec();
|
||||
|
||||
+24
-23
@@ -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<D>(
|
||||
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<f64>], 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<f64>> = 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<f64>], 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<Vec<f64>> = active
|
||||
.iter()
|
||||
.map(|q| q[..last].to_vec())
|
||||
.collect();
|
||||
let projected: Vec<Vec<f64>> = 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<f64>], reference: &[f64]) -> f64 {
|
||||
|
||||
/// Drop dominated members of a projected point set.
|
||||
fn non_dominated_projection(points: &[Vec<f64>]) -> Vec<Vec<f64>> {
|
||||
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<f64>> = 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]);
|
||||
}
|
||||
|
||||
@@ -39,8 +39,7 @@ pub fn spacing<D>(front: &[Candidate<D>], objectives: &ObjectiveSpace) -> f64 {
|
||||
}
|
||||
|
||||
let mean = nearest.iter().sum::<f64>() / n as f64;
|
||||
let variance =
|
||||
nearest.iter().map(|d| (d - mean).powi(2)).sum::<f64>() / n as f64;
|
||||
let variance = nearest.iter().map(|d| (d - mean).powi(2)).sum::<f64>() / 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]
|
||||
|
||||
+30
-10
@@ -38,7 +38,11 @@ impl Initializer<Vec<f64>> 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<Vec<f64>> 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<Vec<f64>> 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<Vec<f64>> for LevyMutation {
|
||||
fn vary(&mut self, parents: &[Vec<f64>], rng: &mut Rng) -> Vec<Vec<f64>> {
|
||||
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/α)
|
||||
|
||||
@@ -21,7 +21,10 @@ pub struct ParetoArchive<D> {
|
||||
impl<D: Clone> ParetoArchive<D> {
|
||||
/// 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<f64>) -> Candidate<u32> {
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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]
|
||||
|
||||
+1
-4
@@ -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]
|
||||
|
||||
@@ -11,7 +11,10 @@
|
||||
/// # Panics
|
||||
/// If `num_objectives == 0`.
|
||||
pub fn das_dennis(num_objectives: usize, divisions: usize) -> Vec<Vec<f64>> {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
+14
-17
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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<D: Clone>(
|
||||
population: &[Candidate<D>],
|
||||
count: usize,
|
||||
rng: &mut Rng,
|
||||
) -> Vec<D> {
|
||||
pub fn select_random<D: Clone>(population: &[Candidate<D>], count: usize, rng: &mut Rng) -> Vec<D> {
|
||||
if count == 0 {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
@@ -63,8 +63,18 @@ fn challenger_wins<D>(c: &Candidate<D>, b: &Candidate<D>, 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);
|
||||
|
||||
@@ -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<SimulatedBinaryCrossover, PolynomialMutation> {
|
||||
fn mo_variation() -> CompositeVariation<SimulatedBinaryCrossover, PolynomialMutation> {
|
||||
let bounds = mo_bounds();
|
||||
CompositeVariation {
|
||||
crossover: SimulatedBinaryCrossover::new(bounds.clone(), 15.0, 0.5),
|
||||
|
||||
@@ -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<Value = Vec<(f64, f64)>> {
|
||||
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.
|
||||
|
||||
+8
-9
@@ -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<Value = Candidate<()>> {
|
||||
(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<Value = Vec<Candidate<()>>> {
|
||||
|
||||
/// Generate per-axis bounds.
|
||||
fn bounds(dim: usize) -> impl Strategy<Value = Vec<(f64, f64)>> {
|
||||
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()
|
||||
})
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user