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::candidate::Candidate;
|
||||||
use heuropt::core::evaluation::Evaluation;
|
use heuropt::core::evaluation::Evaluation;
|
||||||
use heuropt::core::objective::{Objective, ObjectiveSpace};
|
use heuropt::core::objective::{Objective, ObjectiveSpace};
|
||||||
|
use heuropt::core::problem::Problem;
|
||||||
use heuropt::metrics::hypervolume::{hypervolume_2d, hypervolume_nd};
|
use heuropt::metrics::hypervolume::{hypervolume_2d, hypervolume_nd};
|
||||||
use heuropt::pareto::crowding::crowding_distance;
|
use heuropt::pareto::crowding::crowding_distance;
|
||||||
use heuropt::pareto::sort::non_dominated_sort;
|
use heuropt::pareto::sort::non_dominated_sort;
|
||||||
use heuropt::core::problem::Problem;
|
|
||||||
use heuropt::prelude::*;
|
use heuropt::prelude::*;
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
@@ -53,7 +53,11 @@ fn crowding_distance_2d(n: usize) -> Vec<f64> {
|
|||||||
let pop = make_2d_population(n);
|
let pop = make_2d_population(n);
|
||||||
let s = space_2d();
|
let s = space_2d();
|
||||||
let front: Vec<usize> = (0..pop.len()).collect();
|
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]
|
#[library_benchmark]
|
||||||
@@ -62,7 +66,11 @@ fn crowding_distance_2d(n: usize) -> Vec<f64> {
|
|||||||
fn hypervolume_2d_bench(n: usize) -> f64 {
|
fn hypervolume_2d_bench(n: usize) -> f64 {
|
||||||
let pop = make_2d_population(n);
|
let pop = make_2d_population(n);
|
||||||
let s = space_2d();
|
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) {
|
fn make_3d_population(n: usize) -> (Vec<Candidate<()>>, ObjectiveSpace) {
|
||||||
@@ -75,10 +83,7 @@ fn make_3d_population(n: usize) -> (Vec<Candidate<()>>, ObjectiveSpace) {
|
|||||||
.map(|i| {
|
.map(|i| {
|
||||||
let t = i as f64 / n as f64;
|
let t = i as f64 / n as f64;
|
||||||
let theta = 0.5 * std::f64::consts::PI * t;
|
let theta = 0.5 * std::f64::consts::PI * t;
|
||||||
Candidate::new(
|
Candidate::new((), Evaluation::new(vec![theta.cos(), theta.sin(), 1.0 - t]))
|
||||||
(),
|
|
||||||
Evaluation::new(vec![theta.cos(), theta.sin(), 1.0 - t]),
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
(pop, s)
|
(pop, s)
|
||||||
@@ -89,7 +94,11 @@ fn make_3d_population(n: usize) -> (Vec<Candidate<()>>, ObjectiveSpace) {
|
|||||||
#[bench::n_100(100)]
|
#[bench::n_100(100)]
|
||||||
fn hypervolume_nd_bench_3d(n: usize) -> f64 {
|
fn hypervolume_nd_bench_3d(n: usize) -> f64 {
|
||||||
let (pop, s) = make_3d_population(n);
|
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!(
|
library_benchmark_group!(
|
||||||
@@ -128,7 +137,11 @@ fn nsga2_one_generation() -> usize {
|
|||||||
mutation: PolynomialMutation::new(bounds, 20.0, 1.0),
|
mutation: PolynomialMutation::new(bounds, 20.0, 1.0),
|
||||||
};
|
};
|
||||||
let mut opt = Nsga2::new(
|
let mut opt = Nsga2::new(
|
||||||
Nsga2Config { population_size: 50, generations: 1, seed: 0 },
|
Nsga2Config {
|
||||||
|
population_size: 50,
|
||||||
|
generations: 1,
|
||||||
|
seed: 0,
|
||||||
|
},
|
||||||
initializer,
|
initializer,
|
||||||
variation,
|
variation,
|
||||||
);
|
);
|
||||||
@@ -191,7 +204,11 @@ fn so_bounds() -> RealBounds {
|
|||||||
#[library_benchmark]
|
#[library_benchmark]
|
||||||
fn random_search_short() -> usize {
|
fn random_search_short() -> usize {
|
||||||
let mut o = RandomSearch::new(
|
let mut o = RandomSearch::new(
|
||||||
RandomSearchConfig { iterations: 50, batch_size: 1, seed: 0 },
|
RandomSearchConfig {
|
||||||
|
iterations: 50,
|
||||||
|
batch_size: 1,
|
||||||
|
seed: 0,
|
||||||
|
},
|
||||||
so_bounds(),
|
so_bounds(),
|
||||||
);
|
);
|
||||||
black_box(o.run(black_box(&Sphere1D)).evaluations)
|
black_box(o.run(black_box(&Sphere1D)).evaluations)
|
||||||
@@ -200,7 +217,10 @@ fn random_search_short() -> usize {
|
|||||||
#[library_benchmark]
|
#[library_benchmark]
|
||||||
fn hill_climber_short() -> usize {
|
fn hill_climber_short() -> usize {
|
||||||
let mut o = HillClimber::new(
|
let mut o = HillClimber::new(
|
||||||
HillClimberConfig { iterations: 50, seed: 0 },
|
HillClimberConfig {
|
||||||
|
iterations: 50,
|
||||||
|
seed: 0,
|
||||||
|
},
|
||||||
so_bounds(),
|
so_bounds(),
|
||||||
GaussianMutation { sigma: 0.1 },
|
GaussianMutation { sigma: 0.1 },
|
||||||
);
|
);
|
||||||
@@ -291,7 +311,11 @@ fn differential_evolution_short() -> usize {
|
|||||||
#[library_benchmark]
|
#[library_benchmark]
|
||||||
fn tlbo_short() -> usize {
|
fn tlbo_short() -> usize {
|
||||||
let mut o = Tlbo::new(
|
let mut o = Tlbo::new(
|
||||||
TlboConfig { population_size: 10, generations: 5, seed: 0 },
|
TlboConfig {
|
||||||
|
population_size: 10,
|
||||||
|
generations: 5,
|
||||||
|
seed: 0,
|
||||||
|
},
|
||||||
so_bounds(),
|
so_bounds(),
|
||||||
);
|
);
|
||||||
black_box(o.run(black_box(&Sphere1D)).evaluations)
|
black_box(o.run(black_box(&Sphere1D)).evaluations)
|
||||||
@@ -316,7 +340,10 @@ fn separable_nes_short() -> usize {
|
|||||||
#[library_benchmark]
|
#[library_benchmark]
|
||||||
fn nelder_mead_short() -> usize {
|
fn nelder_mead_short() -> usize {
|
||||||
let mut o = NelderMead::new(
|
let mut o = NelderMead::new(
|
||||||
NelderMeadConfig { iterations: 50, ..NelderMeadConfig::default() },
|
NelderMeadConfig {
|
||||||
|
iterations: 50,
|
||||||
|
..NelderMeadConfig::default()
|
||||||
|
},
|
||||||
so_bounds(),
|
so_bounds(),
|
||||||
);
|
);
|
||||||
black_box(o.run(black_box(&Sphere1D)).evaluations)
|
black_box(o.run(black_box(&Sphere1D)).evaluations)
|
||||||
@@ -388,8 +415,7 @@ library_benchmark_group!(
|
|||||||
fn schaffer_bounds() -> Vec<(f64, f64)> {
|
fn schaffer_bounds() -> Vec<(f64, f64)> {
|
||||||
vec![(-3.0, 3.0)]
|
vec![(-3.0, 3.0)]
|
||||||
}
|
}
|
||||||
fn mo_variation()
|
fn mo_variation() -> CompositeVariation<SimulatedBinaryCrossover, PolynomialMutation> {
|
||||||
-> CompositeVariation<SimulatedBinaryCrossover, PolynomialMutation> {
|
|
||||||
let bounds = schaffer_bounds();
|
let bounds = schaffer_bounds();
|
||||||
CompositeVariation {
|
CompositeVariation {
|
||||||
crossover: SimulatedBinaryCrossover::new(bounds.clone(), 15.0, 0.5),
|
crossover: SimulatedBinaryCrossover::new(bounds.clone(), 15.0, 0.5),
|
||||||
@@ -400,7 +426,12 @@ fn mo_variation()
|
|||||||
#[library_benchmark]
|
#[library_benchmark]
|
||||||
fn nsga3_short() -> usize {
|
fn nsga3_short() -> usize {
|
||||||
let mut o = Nsga3::new(
|
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()),
|
RealBounds::new(schaffer_bounds()),
|
||||||
mo_variation(),
|
mo_variation(),
|
||||||
);
|
);
|
||||||
@@ -410,7 +441,12 @@ fn nsga3_short() -> usize {
|
|||||||
#[library_benchmark]
|
#[library_benchmark]
|
||||||
fn spea2_short() -> usize {
|
fn spea2_short() -> usize {
|
||||||
let mut o = Spea2::new(
|
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()),
|
RealBounds::new(schaffer_bounds()),
|
||||||
mo_variation(),
|
mo_variation(),
|
||||||
);
|
);
|
||||||
@@ -420,7 +456,12 @@ fn spea2_short() -> usize {
|
|||||||
#[library_benchmark]
|
#[library_benchmark]
|
||||||
fn moead_short() -> usize {
|
fn moead_short() -> usize {
|
||||||
let mut o = Moead::new(
|
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()),
|
RealBounds::new(schaffer_bounds()),
|
||||||
mo_variation(),
|
mo_variation(),
|
||||||
);
|
);
|
||||||
@@ -431,8 +472,13 @@ fn moead_short() -> usize {
|
|||||||
fn mopso_short() -> usize {
|
fn mopso_short() -> usize {
|
||||||
let mut o = Mopso::new(
|
let mut o = Mopso::new(
|
||||||
MopsoConfig {
|
MopsoConfig {
|
||||||
swarm_size: 10, generations: 1, archive_size: 10,
|
swarm_size: 10,
|
||||||
inertia: 0.7, cognitive: 1.5, social: 1.5, seed: 0,
|
generations: 1,
|
||||||
|
archive_size: 10,
|
||||||
|
inertia: 0.7,
|
||||||
|
cognitive: 1.5,
|
||||||
|
social: 1.5,
|
||||||
|
seed: 0,
|
||||||
},
|
},
|
||||||
RealBounds::new(schaffer_bounds()),
|
RealBounds::new(schaffer_bounds()),
|
||||||
);
|
);
|
||||||
@@ -442,7 +488,12 @@ fn mopso_short() -> usize {
|
|||||||
#[library_benchmark]
|
#[library_benchmark]
|
||||||
fn ibea_short() -> usize {
|
fn ibea_short() -> usize {
|
||||||
let mut o = Ibea::new(
|
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()),
|
RealBounds::new(schaffer_bounds()),
|
||||||
mo_variation(),
|
mo_variation(),
|
||||||
);
|
);
|
||||||
@@ -453,8 +504,10 @@ fn ibea_short() -> usize {
|
|||||||
fn sms_emoa_short() -> usize {
|
fn sms_emoa_short() -> usize {
|
||||||
let mut o = SmsEmoa::new(
|
let mut o = SmsEmoa::new(
|
||||||
SmsEmoaConfig {
|
SmsEmoaConfig {
|
||||||
population_size: 8, generations: 5,
|
population_size: 8,
|
||||||
reference_point: vec![10.0, 10.0], seed: 0,
|
generations: 5,
|
||||||
|
reference_point: vec![10.0, 10.0],
|
||||||
|
seed: 0,
|
||||||
},
|
},
|
||||||
RealBounds::new(schaffer_bounds()),
|
RealBounds::new(schaffer_bounds()),
|
||||||
mo_variation(),
|
mo_variation(),
|
||||||
@@ -466,8 +519,11 @@ fn sms_emoa_short() -> usize {
|
|||||||
fn hype_short() -> usize {
|
fn hype_short() -> usize {
|
||||||
let mut o = Hype::new(
|
let mut o = Hype::new(
|
||||||
HypeConfig {
|
HypeConfig {
|
||||||
population_size: 10, generations: 1,
|
population_size: 10,
|
||||||
reference_point: vec![10.0, 10.0], mc_samples: 100, seed: 0,
|
generations: 1,
|
||||||
|
reference_point: vec![10.0, 10.0],
|
||||||
|
mc_samples: 100,
|
||||||
|
seed: 0,
|
||||||
},
|
},
|
||||||
RealBounds::new(schaffer_bounds()),
|
RealBounds::new(schaffer_bounds()),
|
||||||
mo_variation(),
|
mo_variation(),
|
||||||
@@ -479,8 +535,11 @@ fn hype_short() -> usize {
|
|||||||
fn pesa2_short() -> usize {
|
fn pesa2_short() -> usize {
|
||||||
let mut o = PesaII::new(
|
let mut o = PesaII::new(
|
||||||
PesaIIConfig {
|
PesaIIConfig {
|
||||||
population_size: 10, archive_size: 10, generations: 1,
|
population_size: 10,
|
||||||
grid_divisions: 4, seed: 0,
|
archive_size: 10,
|
||||||
|
generations: 1,
|
||||||
|
grid_divisions: 4,
|
||||||
|
seed: 0,
|
||||||
},
|
},
|
||||||
RealBounds::new(schaffer_bounds()),
|
RealBounds::new(schaffer_bounds()),
|
||||||
mo_variation(),
|
mo_variation(),
|
||||||
@@ -492,8 +551,10 @@ fn pesa2_short() -> usize {
|
|||||||
fn epsilon_moea_short() -> usize {
|
fn epsilon_moea_short() -> usize {
|
||||||
let mut o = EpsilonMoea::new(
|
let mut o = EpsilonMoea::new(
|
||||||
EpsilonMoeaConfig {
|
EpsilonMoeaConfig {
|
||||||
population_size: 10, evaluations: 30,
|
population_size: 10,
|
||||||
epsilon: vec![0.05, 0.05], seed: 0,
|
evaluations: 30,
|
||||||
|
epsilon: vec![0.05, 0.05],
|
||||||
|
seed: 0,
|
||||||
},
|
},
|
||||||
RealBounds::new(schaffer_bounds()),
|
RealBounds::new(schaffer_bounds()),
|
||||||
mo_variation(),
|
mo_variation(),
|
||||||
@@ -504,7 +565,11 @@ fn epsilon_moea_short() -> usize {
|
|||||||
#[library_benchmark]
|
#[library_benchmark]
|
||||||
fn age_moea_short() -> usize {
|
fn age_moea_short() -> usize {
|
||||||
let mut o = AgeMoea::new(
|
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()),
|
RealBounds::new(schaffer_bounds()),
|
||||||
mo_variation(),
|
mo_variation(),
|
||||||
);
|
);
|
||||||
@@ -514,7 +579,12 @@ fn age_moea_short() -> usize {
|
|||||||
#[library_benchmark]
|
#[library_benchmark]
|
||||||
fn grea_short() -> usize {
|
fn grea_short() -> usize {
|
||||||
let mut o = Grea::new(
|
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()),
|
RealBounds::new(schaffer_bounds()),
|
||||||
mo_variation(),
|
mo_variation(),
|
||||||
);
|
);
|
||||||
@@ -524,7 +594,11 @@ fn grea_short() -> usize {
|
|||||||
#[library_benchmark]
|
#[library_benchmark]
|
||||||
fn knea_short() -> usize {
|
fn knea_short() -> usize {
|
||||||
let mut o = Knea::new(
|
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()),
|
RealBounds::new(schaffer_bounds()),
|
||||||
mo_variation(),
|
mo_variation(),
|
||||||
);
|
);
|
||||||
@@ -535,8 +609,11 @@ fn knea_short() -> usize {
|
|||||||
fn rvea_short() -> usize {
|
fn rvea_short() -> usize {
|
||||||
let mut o = Rvea::new(
|
let mut o = Rvea::new(
|
||||||
RveaConfig {
|
RveaConfig {
|
||||||
population_size: 10, generations: 1,
|
population_size: 10,
|
||||||
reference_divisions: 9, alpha: 2.0, seed: 0,
|
generations: 1,
|
||||||
|
reference_divisions: 9,
|
||||||
|
alpha: 2.0,
|
||||||
|
seed: 0,
|
||||||
},
|
},
|
||||||
RealBounds::new(schaffer_bounds()),
|
RealBounds::new(schaffer_bounds()),
|
||||||
mo_variation(),
|
mo_variation(),
|
||||||
@@ -547,7 +624,11 @@ fn rvea_short() -> usize {
|
|||||||
#[library_benchmark]
|
#[library_benchmark]
|
||||||
fn paes_short() -> usize {
|
fn paes_short() -> usize {
|
||||||
let mut o = Paes::new(
|
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()),
|
RealBounds::new(schaffer_bounds()),
|
||||||
GaussianMutation { sigma: 0.1 },
|
GaussianMutation { sigma: 0.1 },
|
||||||
);
|
);
|
||||||
@@ -562,5 +643,9 @@ library_benchmark_group!(
|
|||||||
age_moea_short, grea_short, knea_short, rvea_short, paes_short
|
age_moea_short, grea_short, knea_short, rvea_short, paes_short
|
||||||
);
|
);
|
||||||
|
|
||||||
main!(library_benchmark_groups =
|
main!(
|
||||||
pareto_group, algorithm_group, single_objective_group, multi_objective_group);
|
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 {
|
fn evaluate(&self, x: &Vec<f64>) -> Evaluation {
|
||||||
let n = self.dim as f64;
|
let n = self.dim as f64;
|
||||||
let value = 10.0 * n
|
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])
|
Evaluation::new(vec![value])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -104,7 +106,11 @@ fn run_zdt1() {
|
|||||||
crossover: SimulatedBinaryCrossover::new(bounds.clone(), 15.0, 0.5),
|
crossover: SimulatedBinaryCrossover::new(bounds.clone(), 15.0, 0.5),
|
||||||
mutation: PolynomialMutation::new(bounds, 20.0, 1.0 / dim as f64),
|
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 mut optimizer = Nsga2::new(config, initializer, variation);
|
||||||
|
|
||||||
let result = optimizer.run(&problem);
|
let result = optimizer.run(&problem);
|
||||||
|
|||||||
+319
-80
@@ -145,8 +145,7 @@ impl Problem for Ackley {
|
|||||||
let n = self.dim as f64;
|
let n = self.dim as f64;
|
||||||
let sum_sq: f64 = x.iter().map(|v| v * v).sum();
|
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 sum_cos: f64 = x.iter().map(|v| (2.0 * PI * v).cos()).sum();
|
||||||
let f = -20.0 * (-0.2 * (sum_sq / n).sqrt()).exp()
|
let f = -20.0 * (-0.2 * (sum_sq / n).sqrt()).exp() - (sum_cos / n).exp()
|
||||||
- (sum_cos / n).exp()
|
|
||||||
+ 20.0
|
+ 20.0
|
||||||
+ std::f64::consts::E;
|
+ std::f64::consts::E;
|
||||||
Evaluation::new(vec![f])
|
Evaluation::new(vec![f])
|
||||||
@@ -228,7 +227,9 @@ impl Problem for Rastrigin {
|
|||||||
fn evaluate(&self, x: &Vec<f64>) -> Evaluation {
|
fn evaluate(&self, x: &Vec<f64>) -> Evaluation {
|
||||||
let n = self.dim as f64;
|
let n = self.dim as f64;
|
||||||
let value = 10.0 * n
|
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])
|
Evaluation::new(vec![value])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -297,7 +298,10 @@ fn zdt1_random(seed: u64) -> MoRun {
|
|||||||
let mut opt = RandomSearch::new(config, initializer);
|
let mut opt = RandomSearch::new(config, initializer);
|
||||||
let t0 = Instant::now();
|
let t0 = Instant::now();
|
||||||
let result = opt.run(&problem);
|
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 {
|
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 mut opt = Paes::new(config, initializer, variation);
|
||||||
let t0 = Instant::now();
|
let t0 = Instant::now();
|
||||||
let result = opt.run(&problem);
|
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 {
|
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 mut opt = Spea2::new(config, initializer, variation);
|
||||||
let t0 = Instant::now();
|
let t0 = Instant::now();
|
||||||
let result = opt.run(&problem);
|
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 {
|
fn zdt1_nsga2(seed: u64) -> MoRun {
|
||||||
@@ -349,11 +359,18 @@ fn zdt1_nsga2(seed: u64) -> MoRun {
|
|||||||
};
|
};
|
||||||
let pop = 100;
|
let pop = 100;
|
||||||
let gens = ZDT1_BUDGET / pop;
|
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 mut opt = Nsga2::new(config, initializer, variation);
|
||||||
let t0 = Instant::now();
|
let t0 = Instant::now();
|
||||||
let result = opt.run(&problem);
|
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 {
|
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 mut opt = SmsEmoa::new(config, initializer, variation);
|
||||||
let t0 = Instant::now();
|
let t0 = Instant::now();
|
||||||
let result = opt.run(&problem);
|
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 {
|
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 mut opt = Hype::new(config, initializer, variation);
|
||||||
let t0 = Instant::now();
|
let t0 = Instant::now();
|
||||||
let result = opt.run(&problem);
|
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 {
|
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 mut opt = Rvea::new(config, initializer, variation);
|
||||||
let t0 = Instant::now();
|
let t0 = Instant::now();
|
||||||
let result = opt.run(&problem);
|
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 {
|
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 mut opt = PesaII::new(config, initializer, variation);
|
||||||
let t0 = Instant::now();
|
let t0 = Instant::now();
|
||||||
let result = opt.run(&problem);
|
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 {
|
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 mut opt = EpsilonMoea::new(config, initializer, variation);
|
||||||
let t0 = Instant::now();
|
let t0 = Instant::now();
|
||||||
let result = opt.run(&problem);
|
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 {
|
fn zdt1_mopso(seed: u64) -> MoRun {
|
||||||
@@ -488,7 +520,10 @@ fn zdt1_mopso(seed: u64) -> MoRun {
|
|||||||
let mut opt = Mopso::new(config, bounds);
|
let mut opt = Mopso::new(config, bounds);
|
||||||
let t0 = Instant::now();
|
let t0 = Instant::now();
|
||||||
let result = opt.run(&problem);
|
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 {
|
fn zdt1_ibea(seed: u64) -> MoRun {
|
||||||
@@ -501,11 +536,19 @@ fn zdt1_ibea(seed: u64) -> MoRun {
|
|||||||
};
|
};
|
||||||
let pop = 100;
|
let pop = 100;
|
||||||
let gens = ZDT1_BUDGET / pop;
|
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 mut opt = Ibea::new(config, initializer, variation);
|
||||||
let t0 = Instant::now();
|
let t0 = Instant::now();
|
||||||
let result = opt.run(&problem);
|
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 {
|
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 mut opt = Moead::new(config, initializer, variation);
|
||||||
let t0 = Instant::now();
|
let t0 = Instant::now();
|
||||||
let result = opt.run(&problem);
|
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 {
|
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 mut opt = Nsga3::new(config, initializer, variation);
|
||||||
let t0 = Instant::now();
|
let t0 = Instant::now();
|
||||||
let result = opt.run(&problem);
|
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 {
|
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 {
|
fn dtlz2_random(seed: u64) -> MoRun {
|
||||||
@@ -574,7 +626,10 @@ fn dtlz2_random(seed: u64) -> MoRun {
|
|||||||
let mut opt = RandomSearch::new(config, initializer);
|
let mut opt = RandomSearch::new(config, initializer);
|
||||||
let t0 = Instant::now();
|
let t0 = Instant::now();
|
||||||
let result = opt.run(&problem);
|
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 {
|
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 pop = 92; // close to the 91-ref-point NSGA-III pop, for fairness
|
||||||
let gens = DTLZ2_BUDGET / pop;
|
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 mut opt = Nsga2::new(config, initializer, variation);
|
||||||
let t0 = Instant::now();
|
let t0 = Instant::now();
|
||||||
let result = opt.run(&problem);
|
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 {
|
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 mut opt = Spea2::new(config, initializer, variation);
|
||||||
let t0 = Instant::now();
|
let t0 = Instant::now();
|
||||||
let result = opt.run(&problem);
|
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 {
|
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 mut opt = SmsEmoa::new(config, initializer, variation);
|
||||||
let t0 = Instant::now();
|
let t0 = Instant::now();
|
||||||
let result = opt.run(&problem);
|
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 {
|
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 mut opt = Hype::new(config, initializer, variation);
|
||||||
let t0 = Instant::now();
|
let t0 = Instant::now();
|
||||||
let result = opt.run(&problem);
|
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 {
|
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 mut opt = Rvea::new(config, initializer, variation);
|
||||||
let t0 = Instant::now();
|
let t0 = Instant::now();
|
||||||
let result = opt.run(&problem);
|
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 {
|
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 mut opt = PesaII::new(config, initializer, variation);
|
||||||
let t0 = Instant::now();
|
let t0 = Instant::now();
|
||||||
let result = opt.run(&problem);
|
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 {
|
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 mut opt = EpsilonMoea::new(config, initializer, variation);
|
||||||
let t0 = Instant::now();
|
let t0 = Instant::now();
|
||||||
let result = opt.run(&problem);
|
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 {
|
fn dtlz2_mopso(seed: u64) -> MoRun {
|
||||||
@@ -747,7 +827,10 @@ fn dtlz2_mopso(seed: u64) -> MoRun {
|
|||||||
let mut opt = Mopso::new(config, bounds);
|
let mut opt = Mopso::new(config, bounds);
|
||||||
let t0 = Instant::now();
|
let t0 = Instant::now();
|
||||||
let result = opt.run(&problem);
|
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 {
|
fn dtlz2_ibea(seed: u64) -> MoRun {
|
||||||
@@ -760,11 +843,19 @@ fn dtlz2_ibea(seed: u64) -> MoRun {
|
|||||||
};
|
};
|
||||||
let pop = 92;
|
let pop = 92;
|
||||||
let gens = DTLZ2_BUDGET / pop;
|
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 mut opt = Ibea::new(config, initializer, variation);
|
||||||
let t0 = Instant::now();
|
let t0 = Instant::now();
|
||||||
let result = opt.run(&problem);
|
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 {
|
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 mut opt = Moead::new(config, initializer, variation);
|
||||||
let t0 = Instant::now();
|
let t0 = Instant::now();
|
||||||
let result = opt.run(&problem);
|
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 {
|
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 mut opt = Nsga3::new(config, initializer, variation);
|
||||||
let t0 = Instant::now();
|
let t0 = Instant::now();
|
||||||
let result = opt.run(&problem);
|
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
|
/// 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
|
let total: f64 = front
|
||||||
.iter()
|
.iter()
|
||||||
.map(|c| {
|
.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()
|
(norm - 1.0).abs()
|
||||||
})
|
})
|
||||||
.sum();
|
.sum();
|
||||||
@@ -880,7 +983,11 @@ fn rastrigin_nsga2(seed: u64) -> SoRun {
|
|||||||
};
|
};
|
||||||
let pop = 50;
|
let pop = 50;
|
||||||
let gens = RASTRIGIN_BUDGET / pop;
|
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 mut opt = Nsga2::new(config, initializer, variation);
|
||||||
let t0 = Instant::now();
|
let t0 = Instant::now();
|
||||||
let result = opt.run(&problem);
|
let result = opt.run(&problem);
|
||||||
@@ -915,7 +1022,10 @@ fn rastrigin_hill_climber(seed: u64) -> SoRun {
|
|||||||
let problem = Rastrigin { dim: RASTRIGIN_DIM };
|
let problem = Rastrigin { dim: RASTRIGIN_DIM };
|
||||||
let initializer = RealBounds::new(vec![(-5.12, 5.12); 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 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 mut opt = HillClimber::new(config, initializer, variation);
|
||||||
let t0 = Instant::now();
|
let t0 = Instant::now();
|
||||||
let result = opt.run(&problem);
|
let result = opt.run(&problem);
|
||||||
@@ -1137,7 +1247,9 @@ fn ackley_bo(seed: u64) -> SoRun {
|
|||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
fn rosenbrock_problem() -> Rosenbrock {
|
fn rosenbrock_problem() -> Rosenbrock {
|
||||||
Rosenbrock { dim: ROSENBROCK_DIM }
|
Rosenbrock {
|
||||||
|
dim: ROSENBROCK_DIM,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
fn ackley_problem() -> Ackley {
|
fn ackley_problem() -> Ackley {
|
||||||
Ackley { dim: ACKLEY_DIM }
|
Ackley { dim: ACKLEY_DIM }
|
||||||
@@ -1176,7 +1288,7 @@ macro_rules! so_run_cma {
|
|||||||
generations: $budget / pop,
|
generations: $budget / pop,
|
||||||
initial_sigma: 1.0,
|
initial_sigma: 1.0,
|
||||||
eigen_decomposition_period: 1,
|
eigen_decomposition_period: 1,
|
||||||
initial_mean: None,
|
initial_mean: None,
|
||||||
seed: $seed,
|
seed: $seed,
|
||||||
};
|
};
|
||||||
let mut opt = CmaEs::new(config, bounds);
|
let mut opt = CmaEs::new(config, bounds);
|
||||||
@@ -1219,7 +1331,11 @@ macro_rules! so_run_tlbo {
|
|||||||
let pop = 30;
|
let pop = 30;
|
||||||
// TLBO does ~2N evaluations per generation.
|
// TLBO does ~2N evaluations per generation.
|
||||||
let gens = ($budget - pop) / (2 * pop);
|
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 mut opt = Tlbo::new(config, bounds);
|
||||||
let t0 = Instant::now();
|
let t0 = Instant::now();
|
||||||
let result = opt.run(&problem);
|
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_de(seed: u64) -> SoRun {
|
||||||
fn rosenbrock_cma(seed: u64) -> SoRun { so_run_cma!(rosenbrock_problem(), ROSENBROCK_DIM, -5.0, 10.0, ROSENBROCK_BUDGET, seed) }
|
so_run_de!(
|
||||||
fn rosenbrock_pso(seed: u64) -> SoRun { so_run_pso!(rosenbrock_problem(), ROSENBROCK_DIM, -5.0, 10.0, ROSENBROCK_BUDGET, seed) }
|
rosenbrock_problem(),
|
||||||
fn rosenbrock_tlbo(seed: u64) -> SoRun { so_run_tlbo!(rosenbrock_problem(), ROSENBROCK_DIM, -5.0, 10.0, ROSENBROCK_BUDGET, seed) }
|
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_de(seed: u64) -> SoRun {
|
||||||
fn ackley_cma(seed: u64) -> SoRun { so_run_cma!(ackley_problem(), ACKLEY_DIM, -32.768, 32.768, ACKLEY_BUDGET, seed) }
|
so_run_de!(
|
||||||
fn ackley_pso(seed: u64) -> SoRun { so_run_pso!(ackley_problem(), ACKLEY_DIM, -32.768, 32.768, ACKLEY_BUDGET, seed) }
|
ackley_problem(),
|
||||||
fn ackley_tlbo(seed: u64) -> SoRun { so_run_tlbo!(ackley_problem(), ACKLEY_DIM, -32.768, 32.768, ACKLEY_BUDGET, seed) }
|
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)
|
// 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 {
|
fn zdt3_nsga2(seed: u64) -> MoRun {
|
||||||
let problem = zdt3_problem();
|
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),
|
mutation: PolynomialMutation::new(bounds, 20.0, 1.0 / ZDT3_DIM as f64),
|
||||||
};
|
};
|
||||||
let pop = 100;
|
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 mut opt = Nsga2::new(config, initializer, variation);
|
||||||
let t0 = Instant::now();
|
let t0 = Instant::now();
|
||||||
let result = opt.run(&problem);
|
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 {
|
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 mut opt = Moead::new(config, initializer, variation);
|
||||||
let t0 = Instant::now();
|
let t0 = Instant::now();
|
||||||
let result = opt.run(&problem);
|
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 {
|
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),
|
mutation: PolynomialMutation::new(bounds, 20.0, 1.0 / ZDT3_DIM as f64),
|
||||||
};
|
};
|
||||||
let pop = 100;
|
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 mut opt = Ibea::new(config, initializer, variation);
|
||||||
let t0 = Instant::now();
|
let t0 = Instant::now();
|
||||||
let result = opt.run(&problem);
|
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 {
|
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),
|
mutation: PolynomialMutation::new(bounds, 20.0, 1.0 / ZDT3_DIM as f64),
|
||||||
};
|
};
|
||||||
let pop = 100;
|
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 mut opt = AgeMoea::new(config, initializer, variation);
|
||||||
let t0 = Instant::now();
|
let t0 = Instant::now();
|
||||||
let result = opt.run(&problem);
|
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 {
|
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 {
|
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 mut opt = Nsga3::new(config, initializer, variation);
|
||||||
let t0 = Instant::now();
|
let t0 = Instant::now();
|
||||||
let result = opt.run(&problem);
|
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 {
|
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 mut opt = Moead::new(config, initializer, variation);
|
||||||
let t0 = Instant::now();
|
let t0 = Instant::now();
|
||||||
let result = opt.run(&problem);
|
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 {
|
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),
|
mutation: PolynomialMutation::new(bounds, 20.0, 1.0 / DTLZ1_DIM as f64),
|
||||||
};
|
};
|
||||||
let pop = 92;
|
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 mut opt = AgeMoea::new(config, initializer, variation);
|
||||||
let t0 = Instant::now();
|
let t0 = Instant::now();
|
||||||
let result = opt.run(&problem);
|
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 {
|
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 mut opt = Grea::new(config, initializer, variation);
|
||||||
let t0 = Instant::now();
|
let t0 = Instant::now();
|
||||||
let result = opt.run(&problem);
|
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
|
/// 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() {
|
fn run_zdt1_comparison() {
|
||||||
println!(
|
println!("== ZDT1 (dim={ZDT1_DIM}, {ZDT1_BUDGET} evals/run × {SEEDS} seeds) ==");
|
||||||
"== ZDT1 (dim={ZDT1_DIM}, {ZDT1_BUDGET} evals/run × {SEEDS} seeds) =="
|
|
||||||
);
|
|
||||||
println!("metric arrows: hypervolume↑ (higher better), others↓ (lower better)");
|
println!("metric arrows: hypervolume↑ (higher better), others↓ (lower better)");
|
||||||
println!();
|
println!();
|
||||||
println!(
|
println!(
|
||||||
@@ -1461,10 +1693,11 @@ fn run_zdt1_comparison() {
|
|||||||
.iter()
|
.iter()
|
||||||
.map(|r| hypervolume_2d(&r.front, &zdt1_objs, ZDT1_REFERENCE))
|
.map(|r| hypervolume_2d(&r.front, &zdt1_objs, ZDT1_REFERENCE))
|
||||||
.collect();
|
.collect();
|
||||||
let sp: Vec<f64> =
|
let sp: Vec<f64> = runs.iter().map(|r| spacing(&r.front, &zdt1_objs)).collect();
|
||||||
runs.iter().map(|r| spacing(&r.front, &zdt1_objs)).collect();
|
let l2: Vec<f64> = runs
|
||||||
let l2: Vec<f64> =
|
.iter()
|
||||||
runs.iter().map(|r| mean_l2_to_zdt1_front(&r.front)).collect();
|
.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 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();
|
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() {
|
fn run_dtlz2_comparison() {
|
||||||
println!();
|
println!();
|
||||||
println!(
|
println!("== DTLZ2 (3-obj, dim={DTLZ2_DIM}, {DTLZ2_BUDGET} evals/run × {SEEDS} seeds) ==");
|
||||||
"== 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!("Pareto front: unit sphere octant (Σf²=1, all f≥0); 'mean dist' is |‖f‖−1|");
|
||||||
println!();
|
println!();
|
||||||
println!(
|
println!(
|
||||||
@@ -1520,10 +1751,14 @@ fn run_dtlz2_comparison() {
|
|||||||
|
|
||||||
for (name, runner) in runners {
|
for (name, runner) in runners {
|
||||||
let runs: Vec<MoRun> = (0..SEEDS).map(runner).collect();
|
let runs: Vec<MoRun> = (0..SEEDS).map(runner).collect();
|
||||||
let dist: Vec<f64> =
|
let dist: Vec<f64> = runs
|
||||||
runs.iter().map(|r| mean_distance_to_dtlz2_front(&r.front)).collect();
|
.iter()
|
||||||
let sp: Vec<f64> =
|
.map(|r| mean_distance_to_dtlz2_front(&r.front))
|
||||||
runs.iter().map(|r| spacing(&r.front, &dtlz2_objs)).collect();
|
.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 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();
|
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() {
|
fn run_rastrigin_comparison() {
|
||||||
println!();
|
println!();
|
||||||
println!(
|
println!("== Rastrigin (dim={RASTRIGIN_DIM}, {RASTRIGIN_BUDGET} evals/run × {SEEDS} seeds) ==");
|
||||||
"== Rastrigin (dim={RASTRIGIN_DIM}, {RASTRIGIN_BUDGET} evals/run × {SEEDS} seeds) =="
|
|
||||||
);
|
|
||||||
println!("global minimum: f = 0 (lower is better)");
|
println!("global minimum: f = 0 (lower is better)");
|
||||||
println!();
|
println!();
|
||||||
println!("{:<14} {:>20} {:>10}", "algorithm", "best f", "ms");
|
println!("{:<14} {:>20} {:>10}", "algorithm", "best f", "ms");
|
||||||
@@ -1668,7 +1901,10 @@ fn run_zdt3_comparison() {
|
|||||||
];
|
];
|
||||||
for (name, runner) in runners {
|
for (name, runner) in runners {
|
||||||
let runs: Vec<MoRun> = (0..SEEDS).map(runner).collect();
|
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 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 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();
|
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 {
|
for (name, runner) in runners {
|
||||||
let runs: Vec<MoRun> = (0..SEEDS).map(runner).collect();
|
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 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 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();
|
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> {
|
fn run(&mut self, problem: &P) -> OptimizationResult<P::Decision> {
|
||||||
let objectives = problem.objectives();
|
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 rng = rng_from_seed(self.seed);
|
||||||
let mut variation = GaussianMutation { sigma: self.sigma };
|
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 rng = StdRng::seed_from_u64(day_seed);
|
||||||
let mut expire = s + rt;
|
let mut expire = s + rt;
|
||||||
// Boot press at workday start: user presses to begin cycle 1.
|
// 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
|
// Allow the loop to extend past the larger of (workday end, last
|
||||||
// possible cycle end given any in-loop expire bumps). Cap at one
|
// 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
|
// 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) {
|
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!(
|
println!(
|
||||||
"{}{:<6} {:>3} {:>3} {:>3} {:>9} {:>9} {:>7.2}/d {:>8} {:>6.1}%",
|
"{}{:<6} {:>3} {:>3} {:>3} {:>9} {:>9} {:>7.2}/d {:>8} {:>6.1}%",
|
||||||
prefix,
|
prefix,
|
||||||
@@ -440,7 +447,11 @@ fn main() {
|
|||||||
|
|
||||||
println!("=== Pareto front (sorted by lunch sleep, descending) ===");
|
println!("=== Pareto front (sorted by lunch sleep, descending) ===");
|
||||||
print_header();
|
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) {
|
for r in rows.iter().take(15) {
|
||||||
print_row("", r);
|
print_row("", r);
|
||||||
}
|
}
|
||||||
@@ -507,8 +518,12 @@ fn main() {
|
|||||||
let shipping_candidate_idx = candidates.len();
|
let shipping_candidate_idx = candidates.len();
|
||||||
candidates.push(("shipping default".to_string(), shipping_row.clone()));
|
candidates.push(("shipping default".to_string(), shipping_row.clone()));
|
||||||
|
|
||||||
let scores =
|
let scores = compute_weighted_scores(
|
||||||
compute_weighted_scores(&candidates.iter().map(|(_, r)| r.clone()).collect::<Vec<_>>());
|
&candidates
|
||||||
|
.iter()
|
||||||
|
.map(|(_, r)| r.clone())
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
);
|
||||||
let mut ranked: Vec<(usize, f64)> = scores.iter().copied().enumerate().collect();
|
let mut ranked: Vec<(usize, f64)> = scores.iter().copied().enumerate().collect();
|
||||||
ranked.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
|
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 bonus: min(yellow_width, red_width), saturates at {:.0} min",
|
||||||
BALANCE_SATURATION_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!();
|
||||||
println!("{:>4} {:>5} source", "rank", "score");
|
println!("{:>4} {:>5} source", "rank", "score");
|
||||||
print_header();
|
print_header();
|
||||||
@@ -548,7 +566,10 @@ fn main() {
|
|||||||
.map(|p| p + 1)
|
.map(|p| p + 1)
|
||||||
.unwrap_or(0);
|
.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!("=== RECOMMENDED PICK ({top_label}) ===");
|
||||||
println!(
|
println!(
|
||||||
@@ -591,9 +612,7 @@ fn main() {
|
|||||||
" • {:.2} button presses/day total — {}",
|
" • {:.2} button presses/day total — {}",
|
||||||
top.presses, press_note,
|
top.presses, press_note,
|
||||||
);
|
);
|
||||||
println!(
|
println!(" (counts: boot + 13:00 retap + warning-phase reactions + death-restarts)");
|
||||||
" (counts: boot + 13:00 retap + warning-phase reactions + death-restarts)"
|
|
||||||
);
|
|
||||||
println!(
|
println!(
|
||||||
" • warning phases: yellow {} min, red {} min, fast-red {} min (balance score {:.2})",
|
" • warning phases: yellow {} min, red {} min, fast-red {} min (balance score {:.2})",
|
||||||
yellow_w,
|
yellow_w,
|
||||||
@@ -629,12 +648,24 @@ fn main() {
|
|||||||
/// phases, computed as `min(YA - RA, RA - FRA)` saturated at
|
/// phases, computed as `min(YA - RA, RA - FRA)` saturated at
|
||||||
/// `BALANCE_SATURATION_MIN`.
|
/// `BALANCE_SATURATION_MIN`.
|
||||||
fn compute_weighted_scores(rows: &[Row]) -> Vec<f64> {
|
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_min = rows
|
||||||
let work_max = rows.iter().map(|r| r.work_fail).fold(f64::NEG_INFINITY, f64::max);
|
.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_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_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()
|
rows.iter()
|
||||||
.map(|r| {
|
.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).
|
/// Normalize a minimize-direction value to `[0, 1]` (best→1, worst→0).
|
||||||
fn norm_min(v: f64, lo: f64, hi: f64) -> f64 {
|
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).
|
/// Normalize a maximize-direction value to `[0, 1]` (best→1, worst→0).
|
||||||
fn norm_max(v: f64, lo: f64, hi: f64) -> f64 {
|
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.
|
/// Render `worst / best` as e.g. "7.5×" for the recommendation rationale.
|
||||||
|
|||||||
@@ -24,7 +24,11 @@ impl Problem for Sphere2D {
|
|||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let initializer = RealBounds::new(vec![(-5.0, 5.0), (-5.0, 5.0)]);
|
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 mut optimizer = RandomSearch::new(config, initializer);
|
||||||
|
|
||||||
let result = optimizer.run(&Sphere2D);
|
let result = optimizer.run(&Sphere2D);
|
||||||
|
|||||||
@@ -26,7 +26,11 @@ impl Problem for SchafferN1 {
|
|||||||
fn main() {
|
fn main() {
|
||||||
let initializer = RealBounds::new(vec![(-5.0, 5.0)]);
|
let initializer = RealBounds::new(vec![(-5.0, 5.0)]);
|
||||||
let variation = GaussianMutation { sigma: 0.2 };
|
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 mut optimizer = Nsga2::new(config, initializer, variation);
|
||||||
|
|
||||||
let result = optimizer.run(&SchafferN1);
|
let result = optimizer.run(&SchafferN1);
|
||||||
|
|||||||
+53
-20
@@ -26,7 +26,11 @@ pub struct AgeMoeaConfig {
|
|||||||
|
|
||||||
impl Default for AgeMoeaConfig {
|
impl Default for AgeMoeaConfig {
|
||||||
fn default() -> Self {
|
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> {
|
impl<I, V> AgeMoea<I, V> {
|
||||||
/// Construct an `AgeMoea`.
|
/// Construct an `AgeMoea`.
|
||||||
pub fn new(config: AgeMoeaConfig, initializer: I, variation: V) -> Self {
|
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>,
|
V: Variation<P::Decision>,
|
||||||
{
|
{
|
||||||
fn run(&mut self, problem: &P) -> OptimizationResult<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 n = self.config.population_size;
|
||||||
let objectives = problem.objectives();
|
let objectives = problem.objectives();
|
||||||
let mut rng = rng_from_seed(self.config.seed);
|
let mut rng = rng_from_seed(self.config.seed);
|
||||||
@@ -77,10 +88,15 @@ where
|
|||||||
while offspring_decisions.len() < n {
|
while offspring_decisions.len() < n {
|
||||||
let p1 = rng.random_range(0..population.len());
|
let p1 = rng.random_range(0..population.len());
|
||||||
let p2 = rng.random_range(0..population.len());
|
let p2 = rng.random_range(0..population.len());
|
||||||
let parents =
|
let parents = vec![
|
||||||
vec![population[p1].decision.clone(), population[p2].decision.clone()];
|
population[p1].decision.clone(),
|
||||||
|
population[p2].decision.clone(),
|
||||||
|
];
|
||||||
let children = self.variation.vary(&parents, &mut rng);
|
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 {
|
for child in children {
|
||||||
if offspring_decisions.len() >= n {
|
if offspring_decisions.len() >= n {
|
||||||
break;
|
break;
|
||||||
@@ -153,7 +169,11 @@ fn environmental_selection<D: Clone>(
|
|||||||
.iter()
|
.iter()
|
||||||
.map(|c| {
|
.map(|c| {
|
||||||
let oriented = objectives.as_minimization(&c.evaluation.objectives);
|
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();
|
.collect();
|
||||||
|
|
||||||
@@ -199,15 +219,14 @@ fn lp_norm(v: &[f64], p: f64) -> f64 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn lp_distance(a: &[f64], b: &[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(
|
fn nearest_neighbor_distance(i: usize, translated: &[Vec<f64>], selected: &[usize], p: f64) -> f64 {
|
||||||
i: usize,
|
|
||||||
translated: &[Vec<f64>],
|
|
||||||
selected: &[usize],
|
|
||||||
p: f64,
|
|
||||||
) -> f64 {
|
|
||||||
if selected.is_empty() {
|
if selected.is_empty() {
|
||||||
return f64::INFINITY;
|
return f64::INFINITY;
|
||||||
}
|
}
|
||||||
@@ -295,7 +314,11 @@ mod tests {
|
|||||||
mutation: PolynomialMutation::new(bounds, 20.0, 1.0),
|
mutation: PolynomialMutation::new(bounds, 20.0, 1.0),
|
||||||
};
|
};
|
||||||
AgeMoea::new(
|
AgeMoea::new(
|
||||||
AgeMoeaConfig { population_size: 20, generations: 15, seed },
|
AgeMoeaConfig {
|
||||||
|
population_size: 20,
|
||||||
|
generations: 15,
|
||||||
|
seed,
|
||||||
|
},
|
||||||
initializer,
|
initializer,
|
||||||
variation,
|
variation,
|
||||||
)
|
)
|
||||||
@@ -315,10 +338,16 @@ mod tests {
|
|||||||
let mut b = make_optimizer(99);
|
let mut b = make_optimizer(99);
|
||||||
let ra = a.run(&SchafferN1);
|
let ra = a.run(&SchafferN1);
|
||||||
let rb = b.run(&SchafferN1);
|
let rb = b.run(&SchafferN1);
|
||||||
let oa: Vec<Vec<f64>> =
|
let oa: Vec<Vec<f64>> = ra
|
||||||
ra.pareto_front.iter().map(|c| c.evaluation.objectives.clone()).collect();
|
.pareto_front
|
||||||
let ob: Vec<Vec<f64>> =
|
.iter()
|
||||||
rb.pareto_front.iter().map(|c| c.evaluation.objectives.clone()).collect();
|
.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);
|
assert_eq!(oa, ob);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -332,7 +361,11 @@ mod tests {
|
|||||||
mutation: PolynomialMutation::new(bounds, 20.0, 1.0),
|
mutation: PolynomialMutation::new(bounds, 20.0, 1.0),
|
||||||
};
|
};
|
||||||
let mut opt = AgeMoea::new(
|
let mut opt = AgeMoea::new(
|
||||||
AgeMoeaConfig { population_size: 0, generations: 1, seed: 0 },
|
AgeMoeaConfig {
|
||||||
|
population_size: 0,
|
||||||
|
generations: 1,
|
||||||
|
seed: 0,
|
||||||
|
},
|
||||||
initializer,
|
initializer,
|
||||||
variation,
|
variation,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -70,10 +70,16 @@ impl AntColonyTsp {
|
|||||||
/// and has a zero diagonal.
|
/// and has a zero diagonal.
|
||||||
pub fn new(config: AntColonyTspConfig, distances: Vec<Vec<f64>>) -> Self {
|
pub fn new(config: AntColonyTspConfig, distances: Vec<Vec<f64>>) -> Self {
|
||||||
let n = distances.len();
|
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() {
|
for (i, row) in distances.iter().enumerate() {
|
||||||
assert_eq!(row.len(), n, "AntColonyTsp distances matrix must be square");
|
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 }
|
Self { config, distances }
|
||||||
}
|
}
|
||||||
@@ -99,12 +105,15 @@ where
|
|||||||
let eta: Vec<Vec<f64>> = self
|
let eta: Vec<Vec<f64>> = self
|
||||||
.distances
|
.distances
|
||||||
.iter()
|
.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();
|
.collect();
|
||||||
|
|
||||||
// Pheromone matrix.
|
// Pheromone matrix.
|
||||||
let mut pheromone: Vec<Vec<f64>> =
|
let mut pheromone: Vec<Vec<f64>> = vec![vec![self.config.initial_pheromone; n]; n];
|
||||||
vec![vec![self.config.initial_pheromone; n]; n];
|
|
||||||
|
|
||||||
let mut best_decision: Option<Vec<usize>> = None;
|
let mut best_decision: Option<Vec<usize>> = None;
|
||||||
let mut best_eval: Option<crate::core::evaluation::Evaluation> = None;
|
let mut best_eval: Option<crate::core::evaluation::Evaluation> = None;
|
||||||
@@ -153,7 +162,12 @@ where
|
|||||||
|
|
||||||
// Pheromone deposit on each ant's tour.
|
// Pheromone deposit on each ant's tour.
|
||||||
for (tour, eval) in tours.iter().zip(tour_evals.iter()) {
|
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;
|
let deposit = self.config.deposit / length;
|
||||||
for w in tour.windows(2) {
|
for w in tour.windows(2) {
|
||||||
let (i, j) = (w[0], w[1]);
|
let (i, j) = (w[0], w[1]);
|
||||||
@@ -313,10 +327,7 @@ mod tests {
|
|||||||
type Decision = Vec<usize>;
|
type Decision = Vec<usize>;
|
||||||
|
|
||||||
fn objectives(&self) -> ObjectiveSpace {
|
fn objectives(&self) -> ObjectiveSpace {
|
||||||
ObjectiveSpace::new(vec![
|
ObjectiveSpace::new(vec![Objective::minimize("a"), Objective::minimize("b")])
|
||||||
Objective::minimize("a"),
|
|
||||||
Objective::minimize("b"),
|
|
||||||
])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn evaluate(&self, _tour: &Vec<usize>) -> Evaluation {
|
fn evaluate(&self, _tour: &Vec<usize>) -> Evaluation {
|
||||||
|
|||||||
@@ -85,8 +85,14 @@ where
|
|||||||
self.config.initial_samples >= 2,
|
self.config.initial_samples >= 2,
|
||||||
"BayesianOpt initial_samples must be >= 2",
|
"BayesianOpt initial_samples must be >= 2",
|
||||||
);
|
);
|
||||||
assert!(self.config.signal_variance > 0.0, "BayesianOpt signal_variance must be > 0");
|
assert!(
|
||||||
assert!(self.config.noise_variance > 0.0, "BayesianOpt noise_variance must be > 0");
|
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!(
|
assert!(
|
||||||
self.config.acquisition_samples >= 1,
|
self.config.acquisition_samples >= 1,
|
||||||
"BayesianOpt acquisition_samples must be >= 1",
|
"BayesianOpt acquisition_samples must be >= 1",
|
||||||
@@ -99,25 +105,24 @@ where
|
|||||||
let direction = objectives.objectives[0].direction;
|
let direction = objectives.objectives[0].direction;
|
||||||
let dim = self.bounds.bounds.len();
|
let dim = self.bounds.bounds.len();
|
||||||
if let Some(ls) = &self.config.length_scales {
|
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
|
let length_scales: Vec<f64> = self.config.length_scales.clone().unwrap_or_else(|| {
|
||||||
.config
|
self.bounds
|
||||||
.length_scales
|
.bounds
|
||||||
.clone()
|
.iter()
|
||||||
.unwrap_or_else(|| {
|
.map(|&(lo, hi)| 0.2 * (hi - lo).max(1e-9))
|
||||||
self.bounds
|
.collect()
|
||||||
.bounds
|
});
|
||||||
.iter()
|
|
||||||
.map(|&(lo, hi)| 0.2 * (hi - lo).max(1e-9))
|
|
||||||
.collect()
|
|
||||||
});
|
|
||||||
let mut rng = rng_from_seed(self.config.seed);
|
let mut rng = rng_from_seed(self.config.seed);
|
||||||
|
|
||||||
// ---------------- Initial random design ----------------
|
// ---------------- Initial random design ----------------
|
||||||
let mut decisions: Vec<Vec<f64>> = Vec::with_capacity(
|
let mut decisions: Vec<Vec<f64>> =
|
||||||
self.config.initial_samples + self.config.iterations,
|
Vec::with_capacity(self.config.initial_samples + self.config.iterations);
|
||||||
);
|
|
||||||
let mut targets: Vec<f64> = Vec::with_capacity(decisions.capacity());
|
let mut targets: Vec<f64> = Vec::with_capacity(decisions.capacity());
|
||||||
let mut evaluations = Vec::with_capacity(decisions.capacity());
|
let mut evaluations = Vec::with_capacity(decisions.capacity());
|
||||||
for _ in 0..self.config.initial_samples {
|
for _ in 0..self.config.initial_samples {
|
||||||
@@ -153,8 +158,7 @@ where
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let best_target =
|
let best_target = targets.iter().cloned().fold(f64::INFINITY, f64::min);
|
||||||
targets.iter().cloned().fold(f64::INFINITY, f64::min);
|
|
||||||
|
|
||||||
// Maximize EI by best-of-N random sampling.
|
// Maximize EI by best-of-N random sampling.
|
||||||
let mut best_x = sample_uniform_in_bounds(&self.bounds, &mut rng);
|
let mut best_x = sample_uniform_in_bounds(&self.bounds, &mut rng);
|
||||||
@@ -183,7 +187,11 @@ where
|
|||||||
.collect();
|
.collect();
|
||||||
let mut best_idx = 0;
|
let mut best_idx = 0;
|
||||||
for i in 1..final_pop.len() {
|
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;
|
best_idx = i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -232,7 +240,13 @@ fn sample_uniform_in_bounds(bounds: &RealBounds, rng: &mut Rng) -> Vec<f64> {
|
|||||||
bounds
|
bounds
|
||||||
.bounds
|
.bounds
|
||||||
.iter()
|
.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()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -289,10 +303,19 @@ impl GpPosterior {
|
|||||||
let n = self.decisions.len();
|
let n = self.decisions.len();
|
||||||
let mut k_star = vec![0.0_f64; n];
|
let mut k_star = vec![0.0_f64; n];
|
||||||
for (i, k_star_i) in k_star.iter_mut().enumerate() {
|
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 _ = 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
|
// 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))
|
// 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);
|
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 sign = if x < 0.0 { -1.0 } else { 1.0 };
|
||||||
let x = x.abs();
|
let x = x.abs();
|
||||||
let t = 1.0 / (1.0 + p * x);
|
let t = 1.0 / (1.0 + p * x);
|
||||||
let y = 1.0
|
let y = 1.0 - (((((a5 * t + a4) * t) + a3) * t + a2) * t + a1) * t * (-x * x).exp();
|
||||||
- (((((a5 * t + a4) * t) + a3) * t + a2) * t + a1) * t * (-x * x).exp();
|
|
||||||
sign * y
|
sign * y
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+17
-17
@@ -122,9 +122,7 @@ where
|
|||||||
// Standard CMA-ES strategy parameters (Hansen tutorial §7.1).
|
// Standard CMA-ES strategy parameters (Hansen tutorial §7.1).
|
||||||
// ---------------------------------------------------------------
|
// ---------------------------------------------------------------
|
||||||
let c_sigma = (mu_eff + 2.0) / (n_f + mu_eff + 5.0);
|
let c_sigma = (mu_eff + 2.0) / (n_f + mu_eff + 5.0);
|
||||||
let d_sigma = 1.0
|
let d_sigma = 1.0 + 2.0 * ((mu_eff - 1.0) / (n_f + 1.0)).sqrt().max(0.0) + c_sigma;
|
||||||
+ 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_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_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)
|
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))
|
.map(|(v, &(lo, hi))| v.clamp(lo, hi))
|
||||||
.collect()
|
.collect()
|
||||||
} else {
|
} 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;
|
let mut sigma = self.config.initial_sigma;
|
||||||
// Covariance C, eigenvectors B, eigenvalues d (square roots of eigenvalues of C).
|
// 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);
|
let (eigenvalues, eigenvectors) = symmetric_eigen(&c_matrix, 1e-14, 100);
|
||||||
// eigenvectors is sorted descending; we don't depend on order
|
// eigenvectors is sorted descending; we don't depend on order
|
||||||
// for sampling correctness, but we do need positive eigenvalues.
|
// for sampling correctness, but we do need positive eigenvalues.
|
||||||
d = eigenvalues
|
d = eigenvalues.iter().map(|&v| v.max(1e-20).sqrt()).collect();
|
||||||
.iter()
|
|
||||||
.map(|&v| v.max(1e-20).sqrt())
|
|
||||||
.collect();
|
|
||||||
// B is the matrix whose columns are the eigenvectors. The
|
// B is the matrix whose columns are the eigenvectors. The
|
||||||
// helper returns `eigenvectors[i]` as the i-th *eigenvector*,
|
// helper returns `eigenvectors[i]` as the i-th *eigenvector*,
|
||||||
// so b[r][c] should equal eigenvectors[c][r].
|
// so b[r][c] should equal eigenvectors[c][r].
|
||||||
@@ -232,7 +231,11 @@ where
|
|||||||
// Sort offspring by fitness ascending (best first).
|
// Sort offspring by fitness ascending (best first).
|
||||||
let mut order: Vec<usize> = (0..lambda).collect();
|
let mut order: Vec<usize> = (0..lambda).collect();
|
||||||
order.sort_by(|&a, &b_| {
|
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) -----
|
// ----- 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)/σ -----
|
// ----- 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();
|
let factor_p_c = h_sigma * (c_c * (2.0 - c_c) * mu_eff).sqrt();
|
||||||
for i in 0..n {
|
for i in 0..n {
|
||||||
p_c[i] = (1.0 - c_c) * p_c[i]
|
p_c[i] = (1.0 - c_c) * p_c[i] + factor_p_c * (mean[i] - old_mean[i]) / sigma;
|
||||||
+ factor_p_c * (mean[i] - old_mean[i]) / sigma;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----- Covariance matrix update (rank-1 + rank-μ) -----
|
// ----- Covariance matrix update (rank-1 + rank-μ) -----
|
||||||
let delta_h = (1.0 - h_sigma) * c_c * (2.0 - c_c);
|
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 i in 0..n {
|
||||||
for j in 0..n {
|
for j in 0..n {
|
||||||
let mut update = (1.0 - c_1 - c_mu) * c_matrix[i][j]
|
let mut update = (1.0 - c_1 - c_mu) * c_matrix[i][j]
|
||||||
@@ -443,7 +446,7 @@ mod tests {
|
|||||||
generations: 30,
|
generations: 30,
|
||||||
initial_sigma: 0.5,
|
initial_sigma: 0.5,
|
||||||
eigen_decomposition_period: 1,
|
eigen_decomposition_period: 1,
|
||||||
initial_mean: None,
|
initial_mean: None,
|
||||||
seed: 99,
|
seed: 99,
|
||||||
};
|
};
|
||||||
let mut a = CmaEs::new(cfg.clone(), RealBounds::new(vec![(-5.0, 5.0)]));
|
let mut a = CmaEs::new(cfg.clone(), RealBounds::new(vec![(-5.0, 5.0)]));
|
||||||
@@ -459,10 +462,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
#[should_panic(expected = "single-objective")]
|
#[should_panic(expected = "single-objective")]
|
||||||
fn multi_objective_panics() {
|
fn multi_objective_panics() {
|
||||||
let mut opt = CmaEs::new(
|
let mut opt = CmaEs::new(CmaEsConfig::default(), RealBounds::new(vec![(-5.0, 5.0)]));
|
||||||
CmaEsConfig::default(),
|
|
||||||
RealBounds::new(vec![(-5.0, 5.0)]),
|
|
||||||
);
|
|
||||||
let _ = opt.run(&SchafferN1);
|
let _ = opt.run(&SchafferN1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -91,8 +91,10 @@ where
|
|||||||
};
|
};
|
||||||
let initial_pop = evaluate_batch(problem, decisions.clone());
|
let initial_pop = evaluate_batch(problem, decisions.clone());
|
||||||
let mut evaluations = initial_pop.len();
|
let mut evaluations = initial_pop.len();
|
||||||
let mut evals: Vec<f64> =
|
let mut evals: Vec<f64> = initial_pop
|
||||||
initial_pop.iter().map(|c| c.evaluation.objectives[0]).collect();
|
.iter()
|
||||||
|
.map(|c| c.evaluation.objectives[0])
|
||||||
|
.collect();
|
||||||
|
|
||||||
for _gen in 0..self.config.generations {
|
for _gen in 0..self.config.generations {
|
||||||
// Phase 1 (serial): construct one trial per target. RNG state is
|
// 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 {
|
let pick = |rng: &mut crate::core::rng::Rng, taken: &[usize]| -> usize {
|
||||||
loop {
|
loop {
|
||||||
let v = rng.random_range(0..n);
|
let v = rng.random_range(0..n);
|
||||||
@@ -185,7 +191,10 @@ mod tests {
|
|||||||
);
|
);
|
||||||
let r = opt.run(&Sphere1D);
|
let r = opt.run(&Sphere1D);
|
||||||
let best = r.best.unwrap();
|
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]
|
#[test]
|
||||||
@@ -197,8 +206,7 @@ mod tests {
|
|||||||
crossover_probability: 0.7,
|
crossover_probability: 0.7,
|
||||||
seed: 99,
|
seed: 99,
|
||||||
};
|
};
|
||||||
let mut a =
|
let mut a = DifferentialEvolution::new(cfg.clone(), RealBounds::new(vec![(-5.0, 5.0)]));
|
||||||
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 mut b = DifferentialEvolution::new(cfg, RealBounds::new(vec![(-5.0, 5.0)]));
|
||||||
let ra = a.run(&Sphere1D);
|
let ra = a.run(&Sphere1D);
|
||||||
let rb = b.run(&Sphere1D);
|
let rb = b.run(&Sphere1D);
|
||||||
|
|||||||
@@ -52,7 +52,11 @@ pub struct EpsilonMoea<I, V> {
|
|||||||
impl<I, V> EpsilonMoea<I, V> {
|
impl<I, V> EpsilonMoea<I, V> {
|
||||||
/// Construct an `EpsilonMoea`.
|
/// Construct an `EpsilonMoea`.
|
||||||
pub fn new(config: EpsilonMoeaConfig, initializer: I, variation: V) -> Self {
|
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>,
|
V: Variation<P::Decision>,
|
||||||
{
|
{
|
||||||
fn run(&mut self, problem: &P) -> OptimizationResult<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 n = self.config.population_size;
|
||||||
let objectives = problem.objectives();
|
let objectives = problem.objectives();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -110,7 +117,10 @@ where
|
|||||||
};
|
};
|
||||||
let parents = vec![parent_a, parent_b];
|
let parents = vec![parent_a, parent_b];
|
||||||
let children = self.variation.vary(&parents, &mut rng);
|
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_decision = children.into_iter().next().unwrap();
|
||||||
let child_eval = problem.evaluate(&child_decision);
|
let child_eval = problem.evaluate(&child_decision);
|
||||||
evaluations += 1;
|
evaluations += 1;
|
||||||
@@ -205,12 +215,8 @@ fn insert_into_epsilon_archive<D: Clone>(
|
|||||||
}
|
}
|
||||||
if let Some(idx) = child_box_index {
|
if let Some(idx) = child_box_index {
|
||||||
// Same box: keep whichever is closer to box's ideal corner.
|
// Same box: keep whichever is closer to box's ideal corner.
|
||||||
let member_corner_dist = corner_distance(
|
let member_corner_dist =
|
||||||
&archive[idx].evaluation,
|
corner_distance(&archive[idx].evaluation, objectives, epsilon, &child_box);
|
||||||
objectives,
|
|
||||||
epsilon,
|
|
||||||
&child_box,
|
|
||||||
);
|
|
||||||
if child_corner_dist < member_corner_dist {
|
if child_corner_dist < member_corner_dist {
|
||||||
archive[idx] = child;
|
archive[idx] = child;
|
||||||
}
|
}
|
||||||
@@ -302,10 +308,16 @@ mod tests {
|
|||||||
let mut b = make_optimizer(99);
|
let mut b = make_optimizer(99);
|
||||||
let ra = a.run(&SchafferN1);
|
let ra = a.run(&SchafferN1);
|
||||||
let rb = b.run(&SchafferN1);
|
let rb = b.run(&SchafferN1);
|
||||||
let oa: Vec<Vec<f64>> =
|
let oa: Vec<Vec<f64>> = ra
|
||||||
ra.pareto_front.iter().map(|c| c.evaluation.objectives.clone()).collect();
|
.pareto_front
|
||||||
let ob: Vec<Vec<f64>> =
|
.iter()
|
||||||
rb.pareto_front.iter().map(|c| c.evaluation.objectives.clone()).collect();
|
.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);
|
assert_eq!(oa, ob);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -59,7 +59,11 @@ pub struct GeneticAlgorithm<I, V> {
|
|||||||
impl<I, V> GeneticAlgorithm<I, V> {
|
impl<I, V> GeneticAlgorithm<I, V> {
|
||||||
/// Construct a `GeneticAlgorithm`.
|
/// Construct a `GeneticAlgorithm`.
|
||||||
pub fn new(config: GeneticAlgorithmConfig, initializer: I, variation: V) -> Self {
|
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,
|
&mut rng,
|
||||||
);
|
);
|
||||||
let children = self.variation.vary(&parents_decisions, &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 {
|
for child in children {
|
||||||
if offspring_decisions.len() >= n {
|
if offspring_decisions.len() >= n {
|
||||||
break;
|
break;
|
||||||
@@ -123,13 +130,8 @@ where
|
|||||||
evaluations += offspring.len();
|
evaluations += offspring.len();
|
||||||
|
|
||||||
// --- Phase 3: survival = elites + best offspring ---
|
// --- Phase 3: survival = elites + best offspring ---
|
||||||
population = survival_selection(
|
population =
|
||||||
&population,
|
survival_selection(&population, offspring, direction, n, self.config.elitism);
|
||||||
offspring,
|
|
||||||
direction,
|
|
||||||
n,
|
|
||||||
self.config.elitism,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let best = best_candidate(&population, &objectives);
|
let best = best_candidate(&population, &objectives);
|
||||||
@@ -202,8 +204,10 @@ mod tests {
|
|||||||
|
|
||||||
fn make_optimizer(
|
fn make_optimizer(
|
||||||
seed: u64,
|
seed: u64,
|
||||||
) -> GeneticAlgorithm<RealBounds, CompositeVariation<SimulatedBinaryCrossover, PolynomialMutation>>
|
) -> GeneticAlgorithm<
|
||||||
{
|
RealBounds,
|
||||||
|
CompositeVariation<SimulatedBinaryCrossover, PolynomialMutation>,
|
||||||
|
> {
|
||||||
let bounds = vec![(-5.0, 5.0)];
|
let bounds = vec![(-5.0, 5.0)];
|
||||||
let initializer = RealBounds::new(bounds.clone());
|
let initializer = RealBounds::new(bounds.clone());
|
||||||
let variation = CompositeVariation {
|
let variation = CompositeVariation {
|
||||||
|
|||||||
+29
-10
@@ -51,7 +51,11 @@ pub struct Grea<I, V> {
|
|||||||
impl<I, V> Grea<I, V> {
|
impl<I, V> Grea<I, V> {
|
||||||
/// Construct a `Grea`.
|
/// Construct a `Grea`.
|
||||||
pub fn new(config: GreaConfig, initializer: I, variation: V) -> Self {
|
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>,
|
V: Variation<P::Decision>,
|
||||||
{
|
{
|
||||||
fn run(&mut self, problem: &P) -> OptimizationResult<P::Decision> {
|
fn run(&mut self, problem: &P) -> OptimizationResult<P::Decision> {
|
||||||
assert!(self.config.population_size > 0, "Grea population_size must be > 0");
|
assert!(
|
||||||
assert!(self.config.grid_divisions >= 1, "Grea grid_divisions must be >= 1");
|
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 n = self.config.population_size;
|
||||||
let objectives = problem.objectives();
|
let objectives = problem.objectives();
|
||||||
let mut rng = rng_from_seed(self.config.seed);
|
let mut rng = rng_from_seed(self.config.seed);
|
||||||
@@ -80,8 +90,10 @@ where
|
|||||||
while offspring_decisions.len() < n {
|
while offspring_decisions.len() < n {
|
||||||
let p1 = rng.random_range(0..population.len());
|
let p1 = rng.random_range(0..population.len());
|
||||||
let p2 = rng.random_range(0..population.len());
|
let p2 = rng.random_range(0..population.len());
|
||||||
let parents =
|
let parents = vec![
|
||||||
vec![population[p1].decision.clone(), population[p2].decision.clone()];
|
population[p1].decision.clone(),
|
||||||
|
population[p2].decision.clone(),
|
||||||
|
];
|
||||||
let children = self.variation.vary(&parents, &mut rng);
|
let children = self.variation.vary(&parents, &mut rng);
|
||||||
assert!(!children.is_empty(), "Grea variation returned no children");
|
assert!(!children.is_empty(), "Grea variation returned no children");
|
||||||
for child in children {
|
for child in children {
|
||||||
@@ -98,7 +110,8 @@ where
|
|||||||
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(population);
|
||||||
combined.extend(offspring);
|
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);
|
let front = pareto_front(&population, &objectives);
|
||||||
@@ -253,10 +266,16 @@ mod tests {
|
|||||||
let mut b = make_optimizer(99);
|
let mut b = make_optimizer(99);
|
||||||
let ra = a.run(&SchafferN1);
|
let ra = a.run(&SchafferN1);
|
||||||
let rb = b.run(&SchafferN1);
|
let rb = b.run(&SchafferN1);
|
||||||
let oa: Vec<Vec<f64>> =
|
let oa: Vec<Vec<f64>> = ra
|
||||||
ra.pareto_front.iter().map(|c| c.evaluation.objectives.clone()).collect();
|
.pareto_front
|
||||||
let ob: Vec<Vec<f64>> =
|
.iter()
|
||||||
rb.pareto_front.iter().map(|c| c.evaluation.objectives.clone()).collect();
|
.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);
|
assert_eq!(oa, ob);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,10 @@ pub struct HillClimberConfig {
|
|||||||
|
|
||||||
impl Default for HillClimberConfig {
|
impl Default for HillClimberConfig {
|
||||||
fn default() -> Self {
|
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> {
|
impl<I, V> HillClimber<I, V> {
|
||||||
/// Construct a `HillClimber`.
|
/// Construct a `HillClimber`.
|
||||||
pub fn new(config: HillClimberConfig, initializer: I, variation: V) -> Self {
|
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 rng = rng_from_seed(self.config.seed);
|
||||||
|
|
||||||
let mut initial = self.initializer.initialize(1, &mut rng);
|
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_decision = initial.remove(0);
|
||||||
let mut current_eval = problem.evaluate(¤t_decision);
|
let mut current_eval = problem.evaluate(¤t_decision);
|
||||||
let mut evaluations = 1usize;
|
let mut evaluations = 1usize;
|
||||||
@@ -73,7 +83,10 @@ where
|
|||||||
for _ in 0..self.config.iterations {
|
for _ in 0..self.config.iterations {
|
||||||
let parents = vec![current_decision.clone()];
|
let parents = vec![current_decision.clone()];
|
||||||
let children = self.variation.vary(&parents, &mut rng);
|
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_decision = children.into_iter().next().unwrap();
|
||||||
let child_eval = problem.evaluate(&child_decision);
|
let child_eval = problem.evaluate(&child_decision);
|
||||||
evaluations += 1;
|
evaluations += 1;
|
||||||
@@ -116,7 +129,10 @@ mod tests {
|
|||||||
|
|
||||||
fn make_optimizer(seed: u64) -> HillClimber<RealBounds, GaussianMutation> {
|
fn make_optimizer(seed: u64) -> HillClimber<RealBounds, GaussianMutation> {
|
||||||
HillClimber::new(
|
HillClimber::new(
|
||||||
HillClimberConfig { iterations: 500, seed },
|
HillClimberConfig {
|
||||||
|
iterations: 500,
|
||||||
|
seed,
|
||||||
|
},
|
||||||
RealBounds::new(vec![(-5.0, 5.0)]),
|
RealBounds::new(vec![(-5.0, 5.0)]),
|
||||||
GaussianMutation { sigma: 0.3 },
|
GaussianMutation { sigma: 0.3 },
|
||||||
)
|
)
|
||||||
@@ -127,7 +143,11 @@ mod tests {
|
|||||||
let mut opt = make_optimizer(1);
|
let mut opt = make_optimizer(1);
|
||||||
let r = opt.run(&Sphere1D);
|
let r = opt.run(&Sphere1D);
|
||||||
let best = r.best.unwrap();
|
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]
|
#[test]
|
||||||
|
|||||||
+41
-13
@@ -61,7 +61,11 @@ pub struct Hype<I, V> {
|
|||||||
impl<I, V> Hype<I, V> {
|
impl<I, V> Hype<I, V> {
|
||||||
/// Construct a `Hype`.
|
/// Construct a `Hype`.
|
||||||
pub fn new(config: HypeConfig, initializer: I, variation: V) -> Self {
|
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>,
|
V: Variation<P::Decision>,
|
||||||
{
|
{
|
||||||
fn run(&mut self, problem: &P) -> OptimizationResult<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");
|
assert!(self.config.mc_samples > 0, "Hype mc_samples must be > 0");
|
||||||
let n = self.config.population_size;
|
let n = self.config.population_size;
|
||||||
let objectives = problem.objectives();
|
let objectives = problem.objectives();
|
||||||
@@ -93,14 +100,21 @@ where
|
|||||||
for _ in 0..self.config.generations {
|
for _ in 0..self.config.generations {
|
||||||
// Phase 1: parent selection + variation (random tournament on
|
// Phase 1: parent selection + variation (random tournament on
|
||||||
// a fitness-by-HV-estimate proxy).
|
// a fitness-by-HV-estimate proxy).
|
||||||
let fitness =
|
let fitness = hype_fitness(
|
||||||
hype_fitness(&population, &objectives, &reference, self.config.mc_samples, &mut rng);
|
&population,
|
||||||
|
&objectives,
|
||||||
|
&reference,
|
||||||
|
self.config.mc_samples,
|
||||||
|
&mut rng,
|
||||||
|
);
|
||||||
let mut offspring_decisions: Vec<P::Decision> = Vec::with_capacity(n);
|
let mut offspring_decisions: Vec<P::Decision> = Vec::with_capacity(n);
|
||||||
while offspring_decisions.len() < n {
|
while offspring_decisions.len() < n {
|
||||||
let p1 = binary_tournament(&fitness, &mut rng);
|
let p1 = binary_tournament(&fitness, &mut rng);
|
||||||
let p2 = binary_tournament(&fitness, &mut rng);
|
let p2 = binary_tournament(&fitness, &mut rng);
|
||||||
let parents =
|
let parents = vec![
|
||||||
vec![population[p1].decision.clone(), population[p2].decision.clone()];
|
population[p1].decision.clone(),
|
||||||
|
population[p2].decision.clone(),
|
||||||
|
];
|
||||||
let children = self.variation.vary(&parents, &mut rng);
|
let children = self.variation.vary(&parents, &mut rng);
|
||||||
assert!(!children.is_empty(), "Hype variation returned no children");
|
assert!(!children.is_empty(), "Hype variation returned no children");
|
||||||
for child in children {
|
for child in children {
|
||||||
@@ -140,8 +154,13 @@ where
|
|||||||
// by largest HV contribution.
|
// by largest HV contribution.
|
||||||
let pool: Vec<&Candidate<P::Decision>> =
|
let pool: Vec<&Candidate<P::Decision>> =
|
||||||
splitting.iter().map(|&i| &combined[i]).collect();
|
splitting.iter().map(|&i| &combined[i]).collect();
|
||||||
let contributions =
|
let contributions = estimate_contributions(
|
||||||
estimate_contributions(&pool, &objectives, &reference, self.config.mc_samples, &mut rng);
|
&pool,
|
||||||
|
&objectives,
|
||||||
|
&reference,
|
||||||
|
self.config.mc_samples,
|
||||||
|
&mut rng,
|
||||||
|
);
|
||||||
let mut order: Vec<usize> = (0..splitting.len()).collect();
|
let mut order: Vec<usize> = (0..splitting.len()).collect();
|
||||||
order.sort_by(|&a, &b| {
|
order.sort_by(|&a, &b| {
|
||||||
contributions[b]
|
contributions[b]
|
||||||
@@ -154,7 +173,10 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Materialize the next generation.
|
// 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);
|
let front = pareto_front(&population, &objectives);
|
||||||
@@ -321,10 +343,16 @@ mod tests {
|
|||||||
let mut b = make_optimizer(99);
|
let mut b = make_optimizer(99);
|
||||||
let ra = a.run(&SchafferN1);
|
let ra = a.run(&SchafferN1);
|
||||||
let rb = b.run(&SchafferN1);
|
let rb = b.run(&SchafferN1);
|
||||||
let oa: Vec<Vec<f64>> =
|
let oa: Vec<Vec<f64>> = ra
|
||||||
ra.pareto_front.iter().map(|c| c.evaluation.objectives.clone()).collect();
|
.pareto_front
|
||||||
let ob: Vec<Vec<f64>> =
|
.iter()
|
||||||
rb.pareto_front.iter().map(|c| c.evaluation.objectives.clone()).collect();
|
.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);
|
assert_eq!(oa, ob);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+22
-11
@@ -31,7 +31,12 @@ pub struct HyperbandConfig {
|
|||||||
|
|
||||||
impl Default for HyperbandConfig {
|
impl Default for HyperbandConfig {
|
||||||
fn default() -> Self {
|
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`.
|
/// Construct a `Hyperband`.
|
||||||
pub fn new(config: HyperbandConfig, initializer: I) -> Self {
|
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
|
/// Run Hyperband on a multi-fidelity problem, returning the standard
|
||||||
@@ -75,9 +84,15 @@ where
|
|||||||
where
|
where
|
||||||
P: PartialProblem<Decision = D>,
|
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.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();
|
let objectives = problem.objectives();
|
||||||
assert!(
|
assert!(
|
||||||
objectives.is_single_objective(),
|
objectives.is_single_objective(),
|
||||||
@@ -97,9 +112,8 @@ where
|
|||||||
// Brackets are indexed s = s_max, s_max - 1, ..., 0.
|
// Brackets are indexed s = s_max, s_max - 1, ..., 0.
|
||||||
for s in (0..=s_max).rev() {
|
for s in (0..=s_max).rev() {
|
||||||
let s_f = s as f64;
|
let s_f = s as f64;
|
||||||
let n = ((s_max as f64 + 1.0) / (s_f + 1.0)
|
let n =
|
||||||
* self.config.eta.powf(s_f))
|
((s_max as f64 + 1.0) / (s_f + 1.0) * self.config.eta.powf(s_f)).ceil() as usize;
|
||||||
.ceil() as usize;
|
|
||||||
let r = self.config.max_budget / self.config.eta.powf(s_f);
|
let r = self.config.max_budget / self.config.eta.powf(s_f);
|
||||||
|
|
||||||
// Sample n configurations.
|
// Sample n configurations.
|
||||||
@@ -268,10 +282,7 @@ mod tests {
|
|||||||
impl PartialProblem for MultiObj {
|
impl PartialProblem for MultiObj {
|
||||||
type Decision = Vec<f64>;
|
type Decision = Vec<f64>;
|
||||||
fn objectives(&self) -> ObjectiveSpace {
|
fn objectives(&self) -> ObjectiveSpace {
|
||||||
ObjectiveSpace::new(vec![
|
ObjectiveSpace::new(vec![Objective::minimize("a"), Objective::minimize("b")])
|
||||||
Objective::minimize("a"),
|
|
||||||
Objective::minimize("b"),
|
|
||||||
])
|
|
||||||
}
|
}
|
||||||
fn evaluate_at_budget(&self, _: &Vec<f64>, _: f64) -> Evaluation {
|
fn evaluate_at_budget(&self, _: &Vec<f64>, _: f64) -> Evaluation {
|
||||||
Evaluation::new(vec![0.0, 0.0])
|
Evaluation::new(vec![0.0, 0.0])
|
||||||
|
|||||||
+42
-15
@@ -27,7 +27,12 @@ pub struct IbeaConfig {
|
|||||||
|
|
||||||
impl Default for IbeaConfig {
|
impl Default for IbeaConfig {
|
||||||
fn default() -> Self {
|
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> {
|
impl<I, V> Ibea<I, V> {
|
||||||
/// Construct an `Ibea` optimizer.
|
/// Construct an `Ibea` optimizer.
|
||||||
pub fn new(config: IbeaConfig, initializer: I, variation: V) -> Self {
|
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>,
|
V: Variation<P::Decision>,
|
||||||
{
|
{
|
||||||
fn run(&mut self, problem: &P) -> OptimizationResult<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");
|
assert!(self.config.kappa > 0.0, "Ibea kappa must be > 0");
|
||||||
let n = self.config.population_size;
|
let n = self.config.population_size;
|
||||||
let objectives = problem.objectives();
|
let objectives = problem.objectives();
|
||||||
@@ -76,7 +88,10 @@ where
|
|||||||
while offspring_decisions.len() < n {
|
while offspring_decisions.len() < n {
|
||||||
let p1 = binary_tournament(&fitness, &mut rng);
|
let p1 = binary_tournament(&fitness, &mut rng);
|
||||||
let p2 = 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);
|
let children = self.variation.vary(&parents, &mut rng);
|
||||||
assert!(!children.is_empty(), "Ibea variation returned no children");
|
assert!(!children.is_empty(), "Ibea variation returned no children");
|
||||||
for child in children {
|
for child in children {
|
||||||
@@ -204,11 +219,7 @@ fn environmental_selection<D: Clone>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Compute IBEA fitness without mutating, for use in tournament selection.
|
/// Compute IBEA fitness without mutating, for use in tournament selection.
|
||||||
fn compute_fitness<D>(
|
fn compute_fitness<D>(pool: &[Candidate<D>], objectives: &ObjectiveSpace, kappa: f64) -> Vec<f64> {
|
||||||
pool: &[Candidate<D>],
|
|
||||||
objectives: &ObjectiveSpace,
|
|
||||||
kappa: f64,
|
|
||||||
) -> Vec<f64> {
|
|
||||||
if pool.is_empty() {
|
if pool.is_empty() {
|
||||||
return Vec::new();
|
return Vec::new();
|
||||||
}
|
}
|
||||||
@@ -284,7 +295,12 @@ mod tests {
|
|||||||
mutation: PolynomialMutation::new(bounds, 20.0, 1.0),
|
mutation: PolynomialMutation::new(bounds, 20.0, 1.0),
|
||||||
};
|
};
|
||||||
Ibea::new(
|
Ibea::new(
|
||||||
IbeaConfig { population_size: 20, generations: 15, kappa: 0.05, seed },
|
IbeaConfig {
|
||||||
|
population_size: 20,
|
||||||
|
generations: 15,
|
||||||
|
kappa: 0.05,
|
||||||
|
seed,
|
||||||
|
},
|
||||||
initializer,
|
initializer,
|
||||||
variation,
|
variation,
|
||||||
)
|
)
|
||||||
@@ -304,10 +320,16 @@ mod tests {
|
|||||||
let mut b = make_optimizer(99);
|
let mut b = make_optimizer(99);
|
||||||
let ra = a.run(&SchafferN1);
|
let ra = a.run(&SchafferN1);
|
||||||
let rb = b.run(&SchafferN1);
|
let rb = b.run(&SchafferN1);
|
||||||
let oa: Vec<Vec<f64>> =
|
let oa: Vec<Vec<f64>> = ra
|
||||||
ra.pareto_front.iter().map(|c| c.evaluation.objectives.clone()).collect();
|
.pareto_front
|
||||||
let ob: Vec<Vec<f64>> =
|
.iter()
|
||||||
rb.pareto_front.iter().map(|c| c.evaluation.objectives.clone()).collect();
|
.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);
|
assert_eq!(oa, ob);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -321,7 +343,12 @@ mod tests {
|
|||||||
mutation: PolynomialMutation::new(bounds, 20.0, 1.0),
|
mutation: PolynomialMutation::new(bounds, 20.0, 1.0),
|
||||||
};
|
};
|
||||||
let mut opt = Ibea::new(
|
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,
|
initializer,
|
||||||
variation,
|
variation,
|
||||||
);
|
);
|
||||||
|
|||||||
+34
-15
@@ -26,7 +26,11 @@ pub struct KneaConfig {
|
|||||||
|
|
||||||
impl Default for KneaConfig {
|
impl Default for KneaConfig {
|
||||||
fn default() -> Self {
|
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> {
|
impl<I, V> Knea<I, V> {
|
||||||
/// Construct a `Knea`.
|
/// Construct a `Knea`.
|
||||||
pub fn new(config: KneaConfig, initializer: I, variation: V) -> Self {
|
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>,
|
V: Variation<P::Decision>,
|
||||||
{
|
{
|
||||||
fn run(&mut self, problem: &P) -> OptimizationResult<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 n = self.config.population_size;
|
||||||
let objectives = problem.objectives();
|
let objectives = problem.objectives();
|
||||||
let mut rng = rng_from_seed(self.config.seed);
|
let mut rng = rng_from_seed(self.config.seed);
|
||||||
@@ -75,8 +86,10 @@ where
|
|||||||
while offspring_decisions.len() < n {
|
while offspring_decisions.len() < n {
|
||||||
let p1 = rng.random_range(0..population.len());
|
let p1 = rng.random_range(0..population.len());
|
||||||
let p2 = rng.random_range(0..population.len());
|
let p2 = rng.random_range(0..population.len());
|
||||||
let parents =
|
let parents = vec![
|
||||||
vec![population[p1].decision.clone(), population[p2].decision.clone()];
|
population[p1].decision.clone(),
|
||||||
|
population[p2].decision.clone(),
|
||||||
|
];
|
||||||
let children = self.variation.vary(&parents, &mut rng);
|
let children = self.variation.vary(&parents, &mut rng);
|
||||||
assert!(!children.is_empty(), "Knea variation returned no children");
|
assert!(!children.is_empty(), "Knea variation returned no children");
|
||||||
for child in children {
|
for child in children {
|
||||||
@@ -191,11 +204,7 @@ fn environmental_selection<D: Clone>(
|
|||||||
|
|
||||||
/// Perpendicular distance from `point` to the hyperplane through the M
|
/// Perpendicular distance from `point` to the hyperplane through the M
|
||||||
/// extreme points (indices into `oriented`).
|
/// extreme points (indices into `oriented`).
|
||||||
fn perpendicular_distance(
|
fn perpendicular_distance(point: &[f64], extremes: &[usize], oriented: &[Vec<f64>]) -> f64 {
|
||||||
point: &[f64],
|
|
||||||
extremes: &[usize],
|
|
||||||
oriented: &[Vec<f64>],
|
|
||||||
) -> f64 {
|
|
||||||
let m = point.len();
|
let m = point.len();
|
||||||
if extremes.len() < m {
|
if extremes.len() < m {
|
||||||
// Degenerate: just return the L2 norm relative to first extreme.
|
// Degenerate: just return the L2 norm relative to first extreme.
|
||||||
@@ -237,7 +246,11 @@ mod tests {
|
|||||||
mutation: PolynomialMutation::new(bounds, 20.0, 1.0),
|
mutation: PolynomialMutation::new(bounds, 20.0, 1.0),
|
||||||
};
|
};
|
||||||
Knea::new(
|
Knea::new(
|
||||||
KneaConfig { population_size: 20, generations: 15, seed },
|
KneaConfig {
|
||||||
|
population_size: 20,
|
||||||
|
generations: 15,
|
||||||
|
seed,
|
||||||
|
},
|
||||||
initializer,
|
initializer,
|
||||||
variation,
|
variation,
|
||||||
)
|
)
|
||||||
@@ -256,10 +269,16 @@ mod tests {
|
|||||||
let mut b = make_optimizer(99);
|
let mut b = make_optimizer(99);
|
||||||
let ra = a.run(&SchafferN1);
|
let ra = a.run(&SchafferN1);
|
||||||
let rb = b.run(&SchafferN1);
|
let rb = b.run(&SchafferN1);
|
||||||
let oa: Vec<Vec<f64>> =
|
let oa: Vec<Vec<f64>> = ra
|
||||||
ra.pareto_front.iter().map(|c| c.evaluation.objectives.clone()).collect();
|
.pareto_front
|
||||||
let ob: Vec<Vec<f64>> =
|
.iter()
|
||||||
rb.pareto_front.iter().map(|c| c.evaluation.objectives.clone()).collect();
|
.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);
|
assert_eq!(oa, ob);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,8 +22,8 @@ pub mod nsga3;
|
|||||||
pub mod one_plus_one_es;
|
pub mod one_plus_one_es;
|
||||||
pub mod paes;
|
pub mod paes;
|
||||||
pub(crate) mod parallel_eval;
|
pub(crate) mod parallel_eval;
|
||||||
pub mod pesa2;
|
|
||||||
pub mod particle_swarm;
|
pub mod particle_swarm;
|
||||||
|
pub mod pesa2;
|
||||||
pub mod random_search;
|
pub mod random_search;
|
||||||
pub mod rvea;
|
pub mod rvea;
|
||||||
pub mod simulated_annealing;
|
pub mod simulated_annealing;
|
||||||
|
|||||||
+22
-12
@@ -52,7 +52,11 @@ pub struct Moead<I, V> {
|
|||||||
impl<I, V> Moead<I, V> {
|
impl<I, V> Moead<I, V> {
|
||||||
/// Construct a `Moead` optimizer.
|
/// Construct a `Moead` optimizer.
|
||||||
pub fn new(config: MoeadConfig, initializer: I, variation: V) -> Self {
|
pub fn new(config: MoeadConfig, initializer: I, variation: V) -> Self {
|
||||||
Self { config, initializer, variation }
|
Self {
|
||||||
|
config,
|
||||||
|
initializer,
|
||||||
|
variation,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,7 +123,8 @@ where
|
|||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
for _ in 0..self.config.generations {
|
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 {
|
for i in 0..n {
|
||||||
// Pick two distinct parents from the neighborhood.
|
// Pick two distinct parents from the neighborhood.
|
||||||
let nbh = &neighborhoods[i];
|
let nbh = &neighborhoods[i];
|
||||||
@@ -128,10 +133,15 @@ where
|
|||||||
while p2 == p1 && nbh.len() > 1 {
|
while p2 == p1 && nbh.len() > 1 {
|
||||||
p2 = *nbh.choose(&mut rng).unwrap();
|
p2 = *nbh.choose(&mut rng).unwrap();
|
||||||
}
|
}
|
||||||
let parents =
|
let parents = vec![
|
||||||
vec![population[p1].decision.clone(), population[p2].decision.clone()];
|
population[p1].decision.clone(),
|
||||||
|
population[p2].decision.clone(),
|
||||||
|
];
|
||||||
let children = self.variation.vary(&parents, &mut rng);
|
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_decision = children.into_iter().next().unwrap();
|
||||||
let child_eval = problem.evaluate(&child_decision);
|
let child_eval = problem.evaluate(&child_decision);
|
||||||
evaluations += 1;
|
evaluations += 1;
|
||||||
@@ -152,8 +162,7 @@ where
|
|||||||
let g_cur = tchebycheff(&cur_oriented, &weights[j], &ideal);
|
let g_cur = tchebycheff(&cur_oriented, &weights[j], &ideal);
|
||||||
let g_new = tchebycheff(&oriented_child, &weights[j], &ideal);
|
let g_new = tchebycheff(&oriented_child, &weights[j], &ideal);
|
||||||
if g_new <= g_cur {
|
if g_new <= g_cur {
|
||||||
population[j] =
|
population[j] = Candidate::new(child_decision.clone(), child_eval.clone());
|
||||||
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 {
|
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)]
|
#[cfg(test)]
|
||||||
@@ -201,10 +214,7 @@ mod tests {
|
|||||||
|
|
||||||
fn make_optimizer(
|
fn make_optimizer(
|
||||||
seed: u64,
|
seed: u64,
|
||||||
) -> Moead<
|
) -> Moead<RealBounds, CompositeVariation<SimulatedBinaryCrossover, PolynomialMutation>> {
|
||||||
RealBounds,
|
|
||||||
CompositeVariation<SimulatedBinaryCrossover, PolynomialMutation>,
|
|
||||||
> {
|
|
||||||
let bounds = vec![(-5.0, 5.0)];
|
let bounds = vec![(-5.0, 5.0)];
|
||||||
let initializer = RealBounds::new(bounds.clone());
|
let initializer = RealBounds::new(bounds.clone());
|
||||||
let variation = CompositeVariation {
|
let variation = CompositeVariation {
|
||||||
|
|||||||
+17
-10
@@ -73,7 +73,10 @@ where
|
|||||||
{
|
{
|
||||||
fn run(&mut self, problem: &P) -> OptimizationResult<P::Decision> {
|
fn run(&mut self, problem: &P) -> OptimizationResult<P::Decision> {
|
||||||
assert!(self.config.swarm_size >= 1, "Mopso swarm_size must be >= 1");
|
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();
|
let objectives = problem.objectives();
|
||||||
assert!(
|
assert!(
|
||||||
objectives.is_multi_objective(),
|
objectives.is_multi_objective(),
|
||||||
@@ -128,9 +131,8 @@ where
|
|||||||
let cognitive_term =
|
let cognitive_term =
|
||||||
self.config.cognitive * r1 * (pbest_decisions[i][j] - positions[i][j]);
|
self.config.cognitive * r1 * (pbest_decisions[i][j] - positions[i][j]);
|
||||||
let social_term = self.config.social * r2 * (leader[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]
|
let mut v =
|
||||||
+ cognitive_term
|
self.config.inertia * velocities[i][j] + cognitive_term + social_term;
|
||||||
+ social_term;
|
|
||||||
if v > v_max[j] {
|
if v > v_max[j] {
|
||||||
v = v_max[j];
|
v = v_max[j];
|
||||||
} else if v < -v_max[j] {
|
} else if v < -v_max[j] {
|
||||||
@@ -148,8 +150,7 @@ where
|
|||||||
|
|
||||||
// --- Phase 3: serial pbest + archive updates ---
|
// --- Phase 3: serial pbest + archive updates ---
|
||||||
for (i, cand) in evaluated.iter().enumerate() {
|
for (i, cand) in evaluated.iter().enumerate() {
|
||||||
let dominance =
|
let dominance = pareto_compare(&cand.evaluation, &pbest_evals[i], &objectives);
|
||||||
pareto_compare(&cand.evaluation, &pbest_evals[i], &objectives);
|
|
||||||
let replace = match dominance {
|
let replace = match dominance {
|
||||||
Dominance::Dominates => true,
|
Dominance::Dominates => true,
|
||||||
Dominance::DominatedBy => false,
|
Dominance::DominatedBy => false,
|
||||||
@@ -212,10 +213,16 @@ mod tests {
|
|||||||
let mut b = make_optimizer(99);
|
let mut b = make_optimizer(99);
|
||||||
let ra = a.run(&SchafferN1);
|
let ra = a.run(&SchafferN1);
|
||||||
let rb = b.run(&SchafferN1);
|
let rb = b.run(&SchafferN1);
|
||||||
let oa: Vec<Vec<f64>> =
|
let oa: Vec<Vec<f64>> = ra
|
||||||
ra.pareto_front.iter().map(|c| c.evaluation.objectives.clone()).collect();
|
.pareto_front
|
||||||
let ob: Vec<Vec<f64>> =
|
.iter()
|
||||||
rb.pareto_front.iter().map(|c| c.evaluation.objectives.clone()).collect();
|
.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);
|
assert_eq!(oa, ob);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -69,7 +69,10 @@ where
|
|||||||
P: Problem<Decision = Vec<f64>> + Sync,
|
P: Problem<Decision = Vec<f64>> + Sync,
|
||||||
{
|
{
|
||||||
fn run(&mut self, problem: &P) -> OptimizationResult<P::Decision> {
|
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!(
|
assert!(
|
||||||
self.config.expansion > 1.0,
|
self.config.expansion > 1.0,
|
||||||
"NelderMead expansion must be > 1",
|
"NelderMead expansion must be > 1",
|
||||||
@@ -111,8 +114,7 @@ where
|
|||||||
v[j] = (v[j] + step).clamp(lo, hi);
|
v[j] = (v[j] + step).clamp(lo, hi);
|
||||||
vertices.push(v);
|
vertices.push(v);
|
||||||
}
|
}
|
||||||
let mut evals: Vec<Evaluation> =
|
let mut evals: Vec<Evaluation> = vertices.iter().map(|v| problem.evaluate(v)).collect();
|
||||||
vertices.iter().map(|v| problem.evaluate(v)).collect();
|
|
||||||
let mut evaluations = evals.len();
|
let mut evaluations = evals.len();
|
||||||
|
|
||||||
for _ in 0..self.config.iterations {
|
for _ in 0..self.config.iterations {
|
||||||
@@ -141,8 +143,7 @@ where
|
|||||||
|
|
||||||
if better(&r_eval, &evals[best_idx], direction) {
|
if better(&r_eval, &evals[best_idx], direction) {
|
||||||
// Reflection beat the best — try expansion.
|
// Reflection beat the best — try expansion.
|
||||||
let expanded =
|
let expanded = self.reflect(¢roid, &vertices[worst_idx], self.config.expansion);
|
||||||
self.reflect(¢roid, &vertices[worst_idx], self.config.expansion);
|
|
||||||
let e_eval = problem.evaluate(&expanded);
|
let e_eval = problem.evaluate(&expanded);
|
||||||
evaluations += 1;
|
evaluations += 1;
|
||||||
if better(&e_eval, &r_eval, direction) {
|
if better(&e_eval, &r_eval, direction) {
|
||||||
@@ -177,11 +178,11 @@ where
|
|||||||
if idx == best_idx {
|
if idx == best_idx {
|
||||||
continue;
|
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 {
|
for j in 0..n {
|
||||||
vertices[idx][j] = best_pt[j]
|
vertices[idx][j] = best_pt[j]
|
||||||
+ self.config.shrinkage
|
+ self.config.shrinkage * (vertices[idx][j] - best_pt[j]);
|
||||||
* (vertices[idx][j] - best_pt[j]);
|
|
||||||
}
|
}
|
||||||
// Clamp to bounds.
|
// Clamp to bounds.
|
||||||
for (j, x) in vertices[idx].iter_mut().enumerate() {
|
for (j, x) in vertices[idx].iter_mut().enumerate() {
|
||||||
|
|||||||
+54
-15
@@ -26,7 +26,11 @@ pub struct Nsga2Config {
|
|||||||
|
|
||||||
impl Default for Nsga2Config {
|
impl Default for Nsga2Config {
|
||||||
fn default() -> Self {
|
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> {
|
impl<I, V> Nsga2<I, V> {
|
||||||
/// Construct an `Nsga2` optimizer.
|
/// Construct an `Nsga2` optimizer.
|
||||||
pub fn new(config: Nsga2Config, initializer: I, variation: V) -> Self {
|
pub fn new(config: Nsga2Config, initializer: I, variation: V) -> Self {
|
||||||
Self { config, initializer, variation }
|
Self {
|
||||||
|
config,
|
||||||
|
initializer,
|
||||||
|
variation,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,8 +86,7 @@ where
|
|||||||
n,
|
n,
|
||||||
"NSGA-II initializer must return exactly population_size decisions",
|
"NSGA-II initializer must return exactly population_size decisions",
|
||||||
);
|
);
|
||||||
let population: Vec<Candidate<P::Decision>> =
|
let population: Vec<Candidate<P::Decision>> = evaluate_batch(problem, initial_decisions);
|
||||||
evaluate_batch(problem, initial_decisions);
|
|
||||||
let mut evaluations = population.len();
|
let mut evaluations = population.len();
|
||||||
|
|
||||||
// Annotate the starting population with rank and crowding so the first
|
// Annotate the starting population with rank and crowding so the first
|
||||||
@@ -130,7 +137,9 @@ where
|
|||||||
let dist = crowding_distance(&combined, front, &objectives);
|
let dist = crowding_distance(&combined, front, &objectives);
|
||||||
let mut order: Vec<usize> = (0..front.len()).collect();
|
let mut order: Vec<usize> = (0..front.len()).collect();
|
||||||
order.sort_by(|&a, &b| {
|
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();
|
let needed = n - next.len();
|
||||||
for &k in order.iter().take(needed) {
|
for &k in order.iter().take(needed) {
|
||||||
@@ -178,7 +187,11 @@ fn annotate<D: Clone>(
|
|||||||
population
|
population
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.enumerate()
|
.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()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -212,7 +225,11 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn final_population_has_expected_size() {
|
fn final_population_has_expected_size() {
|
||||||
let mut opt = Nsga2::new(
|
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)]),
|
RealBounds::new(vec![(-5.0, 5.0)]),
|
||||||
GaussianMutation { sigma: 0.3 },
|
GaussianMutation { sigma: 0.3 },
|
||||||
);
|
);
|
||||||
@@ -224,7 +241,11 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn evaluation_count_at_least_initial_population() {
|
fn evaluation_count_at_least_initial_population() {
|
||||||
let mut opt = Nsga2::new(
|
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)]),
|
RealBounds::new(vec![(-5.0, 5.0)]),
|
||||||
GaussianMutation { sigma: 0.3 },
|
GaussianMutation { sigma: 0.3 },
|
||||||
);
|
);
|
||||||
@@ -236,21 +257,35 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn deterministic_with_same_seed() {
|
fn deterministic_with_same_seed() {
|
||||||
let mut a = Nsga2::new(
|
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)]),
|
RealBounds::new(vec![(-5.0, 5.0)]),
|
||||||
GaussianMutation { sigma: 0.2 },
|
GaussianMutation { sigma: 0.2 },
|
||||||
);
|
);
|
||||||
let mut b = Nsga2::new(
|
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)]),
|
RealBounds::new(vec![(-5.0, 5.0)]),
|
||||||
GaussianMutation { sigma: 0.2 },
|
GaussianMutation { sigma: 0.2 },
|
||||||
);
|
);
|
||||||
let ra = a.run(&SchafferN1);
|
let ra = a.run(&SchafferN1);
|
||||||
let rb = b.run(&SchafferN1);
|
let rb = b.run(&SchafferN1);
|
||||||
let oa: Vec<Vec<f64>> =
|
let oa: Vec<Vec<f64>> = ra
|
||||||
ra.pareto_front.iter().map(|c| c.evaluation.objectives.clone()).collect();
|
.pareto_front
|
||||||
let ob: Vec<Vec<f64>> =
|
.iter()
|
||||||
rb.pareto_front.iter().map(|c| c.evaluation.objectives.clone()).collect();
|
.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);
|
assert_eq!(oa, ob);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -258,7 +293,11 @@ mod tests {
|
|||||||
#[should_panic(expected = "population_size must be greater than 0")]
|
#[should_panic(expected = "population_size must be greater than 0")]
|
||||||
fn zero_population_size_panics() {
|
fn zero_population_size_panics() {
|
||||||
let mut opt = Nsga2::new(
|
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)]),
|
RealBounds::new(vec![(-1.0, 1.0)]),
|
||||||
GaussianMutation { sigma: 0.1 },
|
GaussianMutation { sigma: 0.1 },
|
||||||
);
|
);
|
||||||
|
|||||||
+26
-15
@@ -56,7 +56,11 @@ pub struct Nsga3<I, V> {
|
|||||||
impl<I, V> Nsga3<I, V> {
|
impl<I, V> Nsga3<I, V> {
|
||||||
/// Construct an `Nsga3` optimizer.
|
/// Construct an `Nsga3` optimizer.
|
||||||
pub fn new(config: Nsga3Config, initializer: I, variation: V) -> Self {
|
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 {
|
while offspring_decisions.len() < n {
|
||||||
let p1 = rng.random_range(0..population.len());
|
let p1 = rng.random_range(0..population.len());
|
||||||
let p2 = rng.random_range(0..population.len());
|
let p2 = rng.random_range(0..population.len());
|
||||||
let parents =
|
let parents = vec![
|
||||||
vec![population[p1].decision.clone(), population[p2].decision.clone()];
|
population[p1].decision.clone(),
|
||||||
|
population[p2].decision.clone(),
|
||||||
|
];
|
||||||
let children = self.variation.vary(&parents, &mut rng);
|
let children = self.variation.vary(&parents, &mut rng);
|
||||||
assert!(
|
assert!(
|
||||||
!children.is_empty(),
|
!children.is_empty(),
|
||||||
@@ -117,11 +123,11 @@ where
|
|||||||
evaluations += offspring.len();
|
evaluations += offspring.len();
|
||||||
|
|
||||||
// --- Combine + survival selection ---
|
// --- Combine + survival selection ---
|
||||||
let mut combined: Vec<Candidate<P::Decision>> =
|
let mut combined: Vec<Candidate<P::Decision>> = Vec::with_capacity(2 * n);
|
||||||
Vec::with_capacity(2 * n);
|
|
||||||
combined.extend(population);
|
combined.extend(population);
|
||||||
combined.extend(offspring);
|
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);
|
let front = pareto_front(&population, &objectives);
|
||||||
@@ -205,7 +211,9 @@ fn environmental_selection<D: Clone>(
|
|||||||
let candidate_refs: Vec<usize> = (0..reference_points.len())
|
let candidate_refs: Vec<usize> = (0..reference_points.len())
|
||||||
.filter(|&j| !available_in_fl[j].is_empty() && niche_count[j] == min_count)
|
.filter(|&j| !available_in_fl[j].is_empty() && niche_count[j] == min_count)
|
||||||
.collect();
|
.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 pool = &available_in_fl[chosen_ref];
|
||||||
let pick_local = if niche_count[chosen_ref] == 0 {
|
let pick_local = if niche_count[chosen_ref] == 0 {
|
||||||
@@ -420,10 +428,7 @@ mod tests {
|
|||||||
|
|
||||||
fn make_optimizer(
|
fn make_optimizer(
|
||||||
seed: u64,
|
seed: u64,
|
||||||
) -> Nsga3<
|
) -> Nsga3<RealBounds, CompositeVariation<SimulatedBinaryCrossover, PolynomialMutation>> {
|
||||||
RealBounds,
|
|
||||||
CompositeVariation<SimulatedBinaryCrossover, PolynomialMutation>,
|
|
||||||
> {
|
|
||||||
let bounds = vec![(-5.0, 5.0)];
|
let bounds = vec![(-5.0, 5.0)];
|
||||||
let initializer = RealBounds::new(bounds.clone());
|
let initializer = RealBounds::new(bounds.clone());
|
||||||
let variation = CompositeVariation {
|
let variation = CompositeVariation {
|
||||||
@@ -457,10 +462,16 @@ mod tests {
|
|||||||
let mut b = make_optimizer(99);
|
let mut b = make_optimizer(99);
|
||||||
let ra = a.run(&SchafferN1);
|
let ra = a.run(&SchafferN1);
|
||||||
let rb = b.run(&SchafferN1);
|
let rb = b.run(&SchafferN1);
|
||||||
let oa: Vec<Vec<f64>> =
|
let oa: Vec<Vec<f64>> = ra
|
||||||
ra.pareto_front.iter().map(|c| c.evaluation.objectives.clone()).collect();
|
.pareto_front
|
||||||
let ob: Vec<Vec<f64>> =
|
.iter()
|
||||||
rb.pareto_front.iter().map(|c| c.evaluation.objectives.clone()).collect();
|
.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);
|
assert_eq!(oa, ob);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -68,7 +68,10 @@ where
|
|||||||
P: Problem<Decision = Vec<f64>> + Sync,
|
P: Problem<Decision = Vec<f64>> + Sync,
|
||||||
{
|
{
|
||||||
fn run(&mut self, problem: &P) -> OptimizationResult<P::Decision> {
|
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!(
|
assert!(
|
||||||
self.config.step_increase > 1.0,
|
self.config.step_increase > 1.0,
|
||||||
"OnePlusOneEs step_increase must be > 1",
|
"OnePlusOneEs step_increase must be > 1",
|
||||||
@@ -123,8 +126,7 @@ where
|
|||||||
}
|
}
|
||||||
// Apply one-fifth rule once we have a full window.
|
// Apply one-fifth rule once we have a full window.
|
||||||
if window.len() == self.config.adaptation_period {
|
if window.len() == self.config.adaptation_period {
|
||||||
let success_count: usize =
|
let success_count: usize = window.iter().map(|&b| b as usize).sum();
|
||||||
window.iter().map(|&b| b as usize).sum();
|
|
||||||
let rate = success_count as f64 / window.len() as f64;
|
let rate = success_count as f64 / window.len() as f64;
|
||||||
if rate > 0.2 {
|
if rate > 0.2 {
|
||||||
sigma *= self.config.step_increase;
|
sigma *= self.config.step_increase;
|
||||||
|
|||||||
+34
-11
@@ -23,7 +23,11 @@ pub struct PaesConfig {
|
|||||||
|
|
||||||
impl Default for PaesConfig {
|
impl Default for PaesConfig {
|
||||||
fn default() -> Self {
|
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> {
|
impl<I, V> Paes<I, V> {
|
||||||
/// Construct a `Paes` optimizer.
|
/// Construct a `Paes` optimizer.
|
||||||
pub fn new(config: PaesConfig, initializer: I, variation: V) -> Self {
|
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 evaluations = 1usize;
|
||||||
|
|
||||||
let mut archive = ParetoArchive::new(objectives.clone());
|
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 {
|
for _ in 0..self.config.iterations {
|
||||||
let parents = vec![current_decision.clone()];
|
let parents = vec![current_decision.clone()];
|
||||||
let children = self.variation.vary(&parents, &mut rng);
|
let children = self.variation.vary(&parents, &mut rng);
|
||||||
assert!(
|
assert!(!children.is_empty(), "PAES variation returned no children",);
|
||||||
!children.is_empty(),
|
|
||||||
"PAES variation returned no children",
|
|
||||||
);
|
|
||||||
let child_decision = children.into_iter().next().unwrap();
|
let child_decision = children.into_iter().next().unwrap();
|
||||||
let child_eval = problem.evaluate(&child_decision);
|
let child_eval = problem.evaluate(&child_decision);
|
||||||
evaluations += 1;
|
evaluations += 1;
|
||||||
@@ -103,7 +111,10 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
archive.insert(Candidate::new(child_decision, child_eval));
|
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);
|
archive.truncate(self.config.archive_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,7 +140,11 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn produces_at_least_one_candidate() {
|
fn produces_at_least_one_candidate() {
|
||||||
let mut opt = Paes::new(
|
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)]),
|
RealBounds::new(vec![(-5.0, 5.0)]),
|
||||||
GaussianMutation { sigma: 0.3 },
|
GaussianMutation { sigma: 0.3 },
|
||||||
);
|
);
|
||||||
@@ -141,7 +156,11 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn archive_size_respected() {
|
fn archive_size_respected() {
|
||||||
let mut opt = Paes::new(
|
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)]),
|
RealBounds::new(vec![(-5.0, 5.0)]),
|
||||||
GaussianMutation { sigma: 0.2 },
|
GaussianMutation { sigma: 0.2 },
|
||||||
);
|
);
|
||||||
@@ -152,7 +171,11 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn single_objective_returns_best() {
|
fn single_objective_returns_best() {
|
||||||
let mut opt = Paes::new(
|
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)]),
|
RealBounds::new(vec![(-2.0, 2.0)]),
|
||||||
GaussianMutation { sigma: 0.1 },
|
GaussianMutation { sigma: 0.1 },
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -133,9 +133,8 @@ where
|
|||||||
self.config.cognitive * r1 * (pbest_decisions[i][j] - positions[i][j]);
|
self.config.cognitive * r1 * (pbest_decisions[i][j] - positions[i][j]);
|
||||||
let social_term =
|
let social_term =
|
||||||
self.config.social * r2 * (gbest_decision[j] - positions[i][j]);
|
self.config.social * r2 * (gbest_decision[j] - positions[i][j]);
|
||||||
let mut v = self.config.inertia * velocities[i][j]
|
let mut v =
|
||||||
+ cognitive_term
|
self.config.inertia * velocities[i][j] + cognitive_term + social_term;
|
||||||
+ social_term;
|
|
||||||
if v > v_max[j] {
|
if v > v_max[j] {
|
||||||
v = v_max[j];
|
v = v_max[j];
|
||||||
} else if 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> {
|
impl<I, V> PesaII<I, V> {
|
||||||
/// Construct a `PesaII`.
|
/// Construct a `PesaII`.
|
||||||
pub fn new(config: PesaIIConfig, initializer: I, variation: V) -> Self {
|
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>,
|
V: Variation<P::Decision>,
|
||||||
{
|
{
|
||||||
fn run(&mut self, problem: &P) -> OptimizationResult<P::Decision> {
|
fn run(&mut self, problem: &P) -> OptimizationResult<P::Decision> {
|
||||||
assert!(self.config.population_size > 0, "PesaII population_size must be > 0");
|
assert!(
|
||||||
assert!(self.config.archive_size > 0, "PesaII archive_size must be > 0");
|
self.config.population_size > 0,
|
||||||
assert!(self.config.grid_divisions >= 1, "PesaII grid_divisions must be >= 1");
|
"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 n = self.config.population_size;
|
||||||
let objectives = problem.objectives();
|
let objectives = problem.objectives();
|
||||||
let mut rng = rng_from_seed(self.config.seed);
|
let mut rng = rng_from_seed(self.config.seed);
|
||||||
@@ -95,7 +108,11 @@ where
|
|||||||
for c in &internal {
|
for c in &internal {
|
||||||
archive.insert(c.clone());
|
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 {
|
for _ in 0..self.config.generations {
|
||||||
// Build grid + box counts on the archive.
|
// Build grid + box counts on the archive.
|
||||||
@@ -106,9 +123,15 @@ where
|
|||||||
while offspring.len() < n {
|
while offspring.len() < n {
|
||||||
let p1 = region_tournament(&archive, &boxes, &counts, &mut rng);
|
let p1 = region_tournament(&archive, &boxes, &counts, &mut rng);
|
||||||
let p2 = 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);
|
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 {
|
for child in children {
|
||||||
if offspring.len() >= n {
|
if offspring.len() >= n {
|
||||||
break;
|
break;
|
||||||
@@ -124,7 +147,11 @@ where
|
|||||||
for c in &offspring {
|
for c in &offspring {
|
||||||
archive.insert(c.clone());
|
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;
|
internal = offspring;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -213,11 +240,7 @@ fn region_tournament<D: Clone>(
|
|||||||
|
|
||||||
/// Truncate the archive to `max_size` by repeatedly evicting a uniform-random
|
/// Truncate the archive to `max_size` by repeatedly evicting a uniform-random
|
||||||
/// member of the most-occupied grid box (PESA-II's standard approach).
|
/// member of the most-occupied grid box (PESA-II's standard approach).
|
||||||
fn truncate_by_grid<D: Clone>(
|
fn truncate_by_grid<D: Clone>(archive: &mut ParetoArchive<D>, max_size: usize, divisions: usize) {
|
||||||
archive: &mut ParetoArchive<D>,
|
|
||||||
max_size: usize,
|
|
||||||
divisions: usize,
|
|
||||||
) {
|
|
||||||
while archive.members().len() > max_size {
|
while archive.members().len() > max_size {
|
||||||
let objectives = archive.objectives.clone();
|
let objectives = archive.objectives.clone();
|
||||||
let (boxes, counts) = build_grid(archive, &objectives, divisions);
|
let (boxes, counts) = build_grid(archive, &objectives, divisions);
|
||||||
@@ -291,10 +314,16 @@ mod tests {
|
|||||||
let mut b = make_optimizer(99);
|
let mut b = make_optimizer(99);
|
||||||
let ra = a.run(&SchafferN1);
|
let ra = a.run(&SchafferN1);
|
||||||
let rb = b.run(&SchafferN1);
|
let rb = b.run(&SchafferN1);
|
||||||
let oa: Vec<Vec<f64>> =
|
let oa: Vec<Vec<f64>> = ra
|
||||||
ra.pareto_front.iter().map(|c| c.evaluation.objectives.clone()).collect();
|
.pareto_front
|
||||||
let ob: Vec<Vec<f64>> =
|
.iter()
|
||||||
rb.pareto_front.iter().map(|c| c.evaluation.objectives.clone()).collect();
|
.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);
|
assert_eq!(oa, ob);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -320,5 +349,4 @@ mod tests {
|
|||||||
);
|
);
|
||||||
let _ = opt.run(&SchafferN1);
|
let _ = opt.run(&SchafferN1);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,11 @@ pub struct RandomSearchConfig {
|
|||||||
|
|
||||||
impl Default for RandomSearchConfig {
|
impl Default for RandomSearchConfig {
|
||||||
fn default() -> Self {
|
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> {
|
impl<I> RandomSearch<I> {
|
||||||
/// Construct a `RandomSearch` from its config and initializer.
|
/// Construct a `RandomSearch` from its config and initializer.
|
||||||
pub fn new(config: RandomSearchConfig, initializer: I) -> Self {
|
pub fn new(config: RandomSearchConfig, initializer: I) -> Self {
|
||||||
Self { config, initializer }
|
Self {
|
||||||
|
config,
|
||||||
|
initializer,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,7 +69,9 @@ where
|
|||||||
let mut evaluations = 0usize;
|
let mut evaluations = 0usize;
|
||||||
|
|
||||||
for _ in 0..self.config.iterations {
|
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();
|
evaluations += decisions.len();
|
||||||
all.extend(evaluate_batch(problem, decisions));
|
all.extend(evaluate_batch(problem, decisions));
|
||||||
}
|
}
|
||||||
@@ -88,7 +97,11 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn evaluation_count_matches_iterations_times_batch() {
|
fn evaluation_count_matches_iterations_times_batch() {
|
||||||
let mut opt = RandomSearch::new(
|
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)]),
|
RealBounds::new(vec![(-2.0, 2.0)]),
|
||||||
);
|
);
|
||||||
let r = opt.run(&Sphere1D);
|
let r = opt.run(&Sphere1D);
|
||||||
@@ -100,7 +113,11 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn pareto_front_non_empty_for_multi_objective() {
|
fn pareto_front_non_empty_for_multi_objective() {
|
||||||
let mut opt = RandomSearch::new(
|
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)]),
|
RealBounds::new(vec![(-5.0, 5.0)]),
|
||||||
);
|
);
|
||||||
let r = opt.run(&SchafferN1);
|
let r = opt.run(&SchafferN1);
|
||||||
@@ -112,7 +129,11 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn single_objective_returns_best() {
|
fn single_objective_returns_best() {
|
||||||
let mut opt = RandomSearch::new(
|
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)]),
|
RealBounds::new(vec![(-1.0, 1.0)]),
|
||||||
);
|
);
|
||||||
let r = opt.run(&Sphere1D);
|
let r = opt.run(&Sphere1D);
|
||||||
|
|||||||
+44
-17
@@ -54,7 +54,11 @@ pub struct Rvea<I, V> {
|
|||||||
impl<I, V> Rvea<I, V> {
|
impl<I, V> Rvea<I, V> {
|
||||||
/// Construct an `Rvea`.
|
/// Construct an `Rvea`.
|
||||||
pub fn new(config: RveaConfig, initializer: I, variation: V) -> Self {
|
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>,
|
V: Variation<P::Decision>,
|
||||||
{
|
{
|
||||||
fn run(&mut self, problem: &P) -> OptimizationResult<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 n = self.config.population_size;
|
||||||
let objectives = problem.objectives();
|
let objectives = problem.objectives();
|
||||||
let m = objectives.len();
|
let m = objectives.len();
|
||||||
// Reference vectors normalized to unit norm.
|
// Reference vectors normalized to unit norm.
|
||||||
let raw_refs = das_dennis(m, self.config.reference_divisions);
|
let raw_refs = das_dennis(m, self.config.reference_divisions);
|
||||||
let references: Vec<Vec<f64>> = raw_refs.into_iter().map(unit_normalize).collect();
|
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
|
// Smallest angle between any two reference vectors — used to scale
|
||||||
// the APD penalty term.
|
// the APD penalty term.
|
||||||
@@ -91,8 +101,10 @@ where
|
|||||||
while offspring_decisions.len() < n {
|
while offspring_decisions.len() < n {
|
||||||
let p1 = rng.random_range(0..population.len());
|
let p1 = rng.random_range(0..population.len());
|
||||||
let p2 = rng.random_range(0..population.len());
|
let p2 = rng.random_range(0..population.len());
|
||||||
let parents =
|
let parents = vec![
|
||||||
vec![population[p1].decision.clone(), population[p2].decision.clone()];
|
population[p1].decision.clone(),
|
||||||
|
population[p2].decision.clone(),
|
||||||
|
];
|
||||||
let children = self.variation.vary(&parents, &mut rng);
|
let children = self.variation.vary(&parents, &mut rng);
|
||||||
assert!(!children.is_empty(), "Rvea variation returned no children");
|
assert!(!children.is_empty(), "Rvea variation returned no children");
|
||||||
for child in children {
|
for child in children {
|
||||||
@@ -126,7 +138,11 @@ where
|
|||||||
.iter()
|
.iter()
|
||||||
.map(|c| {
|
.map(|c| {
|
||||||
let oriented = objectives.as_minimization(&c.evaluation.objectives);
|
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();
|
.collect();
|
||||||
|
|
||||||
@@ -157,22 +173,23 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut next: Vec<Candidate<P::Decision>> =
|
let mut next: Vec<Candidate<P::Decision>> = keep
|
||||||
keep.into_iter().flatten().map(|(i, _)| combined[i].clone()).collect();
|
.into_iter()
|
||||||
|
.flatten()
|
||||||
|
.map(|(i, _)| combined[i].clone())
|
||||||
|
.collect();
|
||||||
// If we ended up with fewer than n (some references unfilled),
|
// If we ended up with fewer than n (some references unfilled),
|
||||||
// backfill with the lowest-APD remaining candidates.
|
// backfill with the lowest-APD remaining candidates.
|
||||||
if next.len() < n {
|
if next.len() < n {
|
||||||
let mut all_apds: Vec<(usize, f64)> = (0..combined.len())
|
let mut all_apds: Vec<(usize, f64)> = (0..combined.len())
|
||||||
.map(|i| {
|
.map(|i| {
|
||||||
let length: f64 =
|
let length: f64 = translated[i].iter().map(|v| v * v).sum::<f64>().sqrt();
|
||||||
translated[i].iter().map(|v| v * v).sum::<f64>().sqrt();
|
|
||||||
let theta_max_safe = theta_max.max(1e-12);
|
let theta_max_safe = theta_max.max(1e-12);
|
||||||
let penalty = 1.0 + (m_dim as f64) * alpha_t * (angles[i] / theta_max_safe);
|
let penalty = 1.0 + (m_dim as f64) * alpha_t * (angles[i] / theta_max_safe);
|
||||||
(i, penalty * length)
|
(i, penalty * length)
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
all_apds
|
all_apds.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal));
|
||||||
.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal));
|
|
||||||
for (i, _) in all_apds {
|
for (i, _) in all_apds {
|
||||||
if next.len() >= n {
|
if next.len() >= n {
|
||||||
break;
|
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)]
|
#[cfg(test)]
|
||||||
@@ -292,10 +313,16 @@ mod tests {
|
|||||||
let mut b = make_optimizer(99);
|
let mut b = make_optimizer(99);
|
||||||
let ra = a.run(&SchafferN1);
|
let ra = a.run(&SchafferN1);
|
||||||
let rb = b.run(&SchafferN1);
|
let rb = b.run(&SchafferN1);
|
||||||
let oa: Vec<Vec<f64>> =
|
let oa: Vec<Vec<f64>> = ra
|
||||||
ra.pareto_front.iter().map(|c| c.evaluation.objectives.clone()).collect();
|
.pareto_front
|
||||||
let ob: Vec<Vec<f64>> =
|
.iter()
|
||||||
rb.pareto_front.iter().map(|c| c.evaluation.objectives.clone()).collect();
|
.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);
|
assert_eq!(oa, ob);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -54,7 +54,11 @@ pub struct SimulatedAnnealing<I, V> {
|
|||||||
impl<I, V> SimulatedAnnealing<I, V> {
|
impl<I, V> SimulatedAnnealing<I, V> {
|
||||||
/// Construct a `SimulatedAnnealing`.
|
/// Construct a `SimulatedAnnealing`.
|
||||||
pub fn new(config: SimulatedAnnealingConfig, initializer: I, variation: V) -> Self {
|
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 {
|
for _ in 0..self.config.iterations {
|
||||||
let parents = vec![current_decision.clone()];
|
let parents = vec![current_decision.clone()];
|
||||||
let children = self.variation.vary(&parents, &mut rng);
|
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_decision = children.into_iter().next().unwrap();
|
||||||
let child_eval = problem.evaluate(&child_decision);
|
let child_eval = problem.evaluate(&child_decision);
|
||||||
evaluations += 1;
|
evaluations += 1;
|
||||||
|
|||||||
+27
-10
@@ -62,7 +62,11 @@ pub struct SmsEmoa<I, V> {
|
|||||||
impl<I, V> SmsEmoa<I, V> {
|
impl<I, V> SmsEmoa<I, V> {
|
||||||
/// Construct a `SmsEmoa`.
|
/// Construct a `SmsEmoa`.
|
||||||
pub fn new(config: SmsEmoaConfig, initializer: I, variation: V) -> Self {
|
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>,
|
V: Variation<P::Decision>,
|
||||||
{
|
{
|
||||||
fn run(&mut self, problem: &P) -> OptimizationResult<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 n = self.config.population_size;
|
||||||
let objectives = problem.objectives();
|
let objectives = problem.objectives();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -100,10 +107,15 @@ where
|
|||||||
// --- One offspring (steady-state) ---
|
// --- One offspring (steady-state) ---
|
||||||
let p1 = rng.random_range(0..population.len());
|
let p1 = rng.random_range(0..population.len());
|
||||||
let p2 = rng.random_range(0..population.len());
|
let p2 = rng.random_range(0..population.len());
|
||||||
let parents =
|
let parents = vec![
|
||||||
vec![population[p1].decision.clone(), population[p2].decision.clone()];
|
population[p1].decision.clone(),
|
||||||
|
population[p2].decision.clone(),
|
||||||
|
];
|
||||||
let children = self.variation.vary(&parents, &mut rng);
|
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_decision = children.into_iter().next().unwrap();
|
||||||
let child_eval = problem.evaluate(&child_decision);
|
let child_eval = problem.evaluate(&child_decision);
|
||||||
evaluations += 1;
|
evaluations += 1;
|
||||||
@@ -212,10 +224,16 @@ mod tests {
|
|||||||
let mut b = make_optimizer(99);
|
let mut b = make_optimizer(99);
|
||||||
let ra = a.run(&SchafferN1);
|
let ra = a.run(&SchafferN1);
|
||||||
let rb = b.run(&SchafferN1);
|
let rb = b.run(&SchafferN1);
|
||||||
let oa: Vec<Vec<f64>> =
|
let oa: Vec<Vec<f64>> = ra
|
||||||
ra.pareto_front.iter().map(|c| c.evaluation.objectives.clone()).collect();
|
.pareto_front
|
||||||
let ob: Vec<Vec<f64>> =
|
.iter()
|
||||||
rb.pareto_front.iter().map(|c| c.evaluation.objectives.clone()).collect();
|
.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);
|
assert_eq!(oa, ob);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -263,4 +281,3 @@ mod tests {
|
|||||||
let _ = opt.run(&SchafferN1);
|
let _ = opt.run(&SchafferN1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -76,7 +76,10 @@ where
|
|||||||
self.config.population_size >= 2,
|
self.config.population_size >= 2,
|
||||||
"SeparableNes population_size must be >= 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();
|
let objectives = problem.objectives();
|
||||||
assert!(
|
assert!(
|
||||||
objectives.is_single_objective(),
|
objectives.is_single_objective(),
|
||||||
@@ -97,9 +100,10 @@ where
|
|||||||
let mut sigma = vec![self.config.initial_sigma; n];
|
let mut sigma = vec![self.config.initial_sigma; n];
|
||||||
|
|
||||||
// Default sigma learning rate (Wierstra et al. 2014, Eq. 11).
|
// Default sigma learning rate (Wierstra et al. 2014, Eq. 11).
|
||||||
let eta_sigma = self.config.sigma_learning_rate.unwrap_or_else(|| {
|
let eta_sigma = self
|
||||||
(3.0 + (n as f64).ln()) / (5.0 * (n as f64).sqrt())
|
.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;
|
let eta_mean = self.config.mean_learning_rate;
|
||||||
|
|
||||||
// Rank utilities — the standard NES weighting:
|
// Rank utilities — the standard NES weighting:
|
||||||
|
|||||||
+43
-14
@@ -28,7 +28,12 @@ pub struct Spea2Config {
|
|||||||
|
|
||||||
impl Default for Spea2Config {
|
impl Default for Spea2Config {
|
||||||
fn default() -> Self {
|
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> {
|
impl<I, V> Spea2<I, V> {
|
||||||
/// Construct a `Spea2` optimizer.
|
/// Construct a `Spea2` optimizer.
|
||||||
pub fn new(config: Spea2Config, initializer: I, variation: V) -> Self {
|
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 {
|
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.
|
/// Build the next archive of exactly `target_size` members.
|
||||||
@@ -213,10 +226,11 @@ fn build_archive<D: Clone>(
|
|||||||
|
|
||||||
if nondom.len() < target_size {
|
if nondom.len() < target_size {
|
||||||
// Fill from dominated members ordered by ascending fitness.
|
// Fill from dominated members ordered by ascending fitness.
|
||||||
let mut dominated: Vec<usize> =
|
let mut dominated: Vec<usize> = (0..pool.len()).filter(|&i| fitness[i] >= 1.0).collect();
|
||||||
(0..pool.len()).filter(|&i| fitness[i] >= 1.0).collect();
|
|
||||||
dominated.sort_by(|&a, &b| {
|
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();
|
let needed = target_size - nondom.len();
|
||||||
nondom.extend(dominated.into_iter().take(needed));
|
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].push(euclidean(&oriented[i], &oriented[j]));
|
||||||
}
|
}
|
||||||
neighbor_dists[i]
|
neighbor_dists[i].sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
|
||||||
.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.
|
// Find the alive member whose neighbor-distance vector is lex-smallest.
|
||||||
let mut victim = usize::MAX;
|
let mut victim = usize::MAX;
|
||||||
@@ -263,7 +276,11 @@ fn build_archive<D: Clone>(
|
|||||||
.zip(neighbor_dists[victim].iter())
|
.zip(neighbor_dists[victim].iter())
|
||||||
.find_map(|(a, b)| {
|
.find_map(|(a, b)| {
|
||||||
let c = a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal);
|
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);
|
.unwrap_or(std::cmp::Ordering::Equal);
|
||||||
if cmp == std::cmp::Ordering::Less {
|
if cmp == std::cmp::Ordering::Less {
|
||||||
@@ -277,7 +294,13 @@ fn build_archive<D: Clone>(
|
|||||||
nondom
|
nondom
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.enumerate()
|
.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()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -354,10 +377,16 @@ mod tests {
|
|||||||
let mut b = make();
|
let mut b = make();
|
||||||
let ra = a.run(&SchafferN1);
|
let ra = a.run(&SchafferN1);
|
||||||
let rb = b.run(&SchafferN1);
|
let rb = b.run(&SchafferN1);
|
||||||
let oa: Vec<Vec<f64>> =
|
let oa: Vec<Vec<f64>> = ra
|
||||||
ra.pareto_front.iter().map(|c| c.evaluation.objectives.clone()).collect();
|
.pareto_front
|
||||||
let ob: Vec<Vec<f64>> =
|
.iter()
|
||||||
rb.pareto_front.iter().map(|c| c.evaluation.objectives.clone()).collect();
|
.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);
|
assert_eq!(oa, ob);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,11 @@ pub struct TabuSearchConfig {
|
|||||||
|
|
||||||
impl Default for TabuSearchConfig {
|
impl Default for TabuSearchConfig {
|
||||||
fn default() -> Self {
|
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`.
|
/// Construct a `TabuSearch`.
|
||||||
pub fn new(config: TabuSearchConfig, initializer: I, neighbors: N) -> Self {
|
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 rng = rng_from_seed(self.config.seed);
|
||||||
|
|
||||||
let mut initial = self.initializer.initialize(1, &mut rng);
|
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_decision = initial.remove(0);
|
||||||
let mut current_eval = problem.evaluate(¤t_decision);
|
let mut current_eval = problem.evaluate(¤t_decision);
|
||||||
let mut best_decision = current_decision.clone();
|
let mut best_decision = current_decision.clone();
|
||||||
let mut best_eval = current_eval.clone();
|
let mut best_eval = current_eval.clone();
|
||||||
let mut evaluations = 1usize;
|
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();
|
let mut tabu_set: HashSet<P::Decision> = HashSet::new();
|
||||||
|
|
||||||
for _ in 0..self.config.iterations {
|
for _ in 0..self.config.iterations {
|
||||||
@@ -118,8 +131,7 @@ where
|
|||||||
|
|
||||||
for (i, c) in candidates.iter().enumerate() {
|
for (i, c) in candidates.iter().enumerate() {
|
||||||
let is_tabu = tabu_set.contains(c);
|
let is_tabu = tabu_set.contains(c);
|
||||||
let aspires = is_tabu
|
let aspires = is_tabu && better_than(&cand_evals[i], &best_eval, direction);
|
||||||
&& better_than(&cand_evals[i], &best_eval, direction);
|
|
||||||
if is_tabu && !aspires {
|
if is_tabu && !aspires {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -227,15 +239,16 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_optimizer<F>(
|
fn make_optimizer<F>(seed: u64, neighbors: F) -> TabuSearch<Vec<i32>, StartAtZero, F>
|
||||||
seed: u64,
|
|
||||||
neighbors: F,
|
|
||||||
) -> TabuSearch<Vec<i32>, StartAtZero, F>
|
|
||||||
where
|
where
|
||||||
F: FnMut(&Vec<i32>, &mut Rng) -> Vec<Vec<i32>>,
|
F: FnMut(&Vec<i32>, &mut Rng) -> Vec<Vec<i32>>,
|
||||||
{
|
{
|
||||||
TabuSearch::new(
|
TabuSearch::new(
|
||||||
TabuSearchConfig { iterations: 50, tabu_tenure: 4, seed },
|
TabuSearchConfig {
|
||||||
|
iterations: 50,
|
||||||
|
tabu_tenure: 4,
|
||||||
|
seed,
|
||||||
|
},
|
||||||
StartAtZero,
|
StartAtZero,
|
||||||
neighbors,
|
neighbors,
|
||||||
)
|
)
|
||||||
@@ -244,9 +257,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn finds_optimum_on_grid() {
|
fn finds_optimum_on_grid() {
|
||||||
// Neighbors: ±1 of current value.
|
// Neighbors: ±1 of current value.
|
||||||
let neighbors = |x: &Vec<i32>, _rng: &mut Rng| {
|
let neighbors = |x: &Vec<i32>, _rng: &mut Rng| vec![vec![x[0] - 1], vec![x[0] + 1]];
|
||||||
vec![vec![x[0] - 1], vec![x[0] + 1]]
|
|
||||||
};
|
|
||||||
let mut opt = make_optimizer(1, neighbors);
|
let mut opt = make_optimizer(1, neighbors);
|
||||||
let r = opt.run(&GridProblem);
|
let r = opt.run(&GridProblem);
|
||||||
let best = r.best.unwrap();
|
let best = r.best.unwrap();
|
||||||
|
|||||||
+10
-4
@@ -27,7 +27,11 @@ pub struct TlboConfig {
|
|||||||
|
|
||||||
impl Default for TlboConfig {
|
impl Default for TlboConfig {
|
||||||
fn default() -> Self {
|
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,
|
P: Problem<Decision = Vec<f64>> + Sync,
|
||||||
{
|
{
|
||||||
fn run(&mut self, problem: &P) -> OptimizationResult<P::Decision> {
|
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();
|
let objectives = problem.objectives();
|
||||||
assert!(
|
assert!(
|
||||||
objectives.is_single_objective(),
|
objectives.is_single_objective(),
|
||||||
@@ -73,8 +80,7 @@ where
|
|||||||
use crate::traits::Initializer as _;
|
use crate::traits::Initializer as _;
|
||||||
self.bounds.initialize(n, &mut rng)
|
self.bounds.initialize(n, &mut rng)
|
||||||
};
|
};
|
||||||
let mut evals: Vec<Evaluation> =
|
let mut evals: Vec<Evaluation> = decisions.iter().map(|d| problem.evaluate(d)).collect();
|
||||||
decisions.iter().map(|d| problem.evaluate(d)).collect();
|
|
||||||
let mut evaluations = decisions.len();
|
let mut evaluations = decisions.len();
|
||||||
|
|
||||||
for _ in 0..self.config.generations {
|
for _ in 0..self.config.generations {
|
||||||
|
|||||||
+42
-8
@@ -72,7 +72,10 @@ where
|
|||||||
P: Problem<Decision = Vec<f64>> + Sync,
|
P: Problem<Decision = Vec<f64>> + Sync,
|
||||||
{
|
{
|
||||||
fn run(&mut self, problem: &P) -> OptimizationResult<P::Decision> {
|
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!(
|
assert!(
|
||||||
self.config.good_fraction > 0.0 && self.config.good_fraction < 1.0,
|
self.config.good_fraction > 0.0 && self.config.good_fraction < 1.0,
|
||||||
"Tpe good_fraction must be in (0, 1)",
|
"Tpe good_fraction must be in (0, 1)",
|
||||||
@@ -81,7 +84,10 @@ where
|
|||||||
self.config.candidate_samples >= 1,
|
self.config.candidate_samples >= 1,
|
||||||
"Tpe candidate_samples must be >= 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();
|
let objectives = problem.objectives();
|
||||||
assert!(
|
assert!(
|
||||||
objectives.is_single_objective(),
|
objectives.is_single_objective(),
|
||||||
@@ -110,9 +116,27 @@ where
|
|||||||
let mut best_x: Option<Vec<f64>> = None;
|
let mut best_x: Option<Vec<f64>> = None;
|
||||||
let mut best_ratio = f64::NEG_INFINITY;
|
let mut best_ratio = f64::NEG_INFINITY;
|
||||||
for _ in 0..self.config.candidate_samples {
|
for _ in 0..self.config.candidate_samples {
|
||||||
let cand = sample_from_kde(&decisions, &good_idx, &self.bounds, self.config.bandwidth_factor, &mut rng);
|
let cand = sample_from_kde(
|
||||||
let l = log_kde_density(&cand, &decisions, &good_idx, &self.bounds, self.config.bandwidth_factor);
|
&decisions,
|
||||||
let g = log_kde_density(&cand, &decisions, &bad_idx, &self.bounds, self.config.bandwidth_factor);
|
&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;
|
let ratio = l - g;
|
||||||
if ratio > best_ratio {
|
if ratio > best_ratio {
|
||||||
best_ratio = ratio;
|
best_ratio = ratio;
|
||||||
@@ -180,7 +204,13 @@ fn sample_uniform_in_bounds(bounds: &RealBounds, rng: &mut Rng) -> Vec<f64> {
|
|||||||
bounds
|
bounds
|
||||||
.bounds
|
.bounds
|
||||||
.iter()
|
.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()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,7 +221,9 @@ fn split_good_bad(targets: &[f64], good_fraction: f64) -> (Vec<usize>, Vec<usize
|
|||||||
let n = targets.len();
|
let n = targets.len();
|
||||||
let mut order: Vec<usize> = (0..n).collect();
|
let mut order: Vec<usize> = (0..n).collect();
|
||||||
order.sort_by(|&a, &b| {
|
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 as f64) * good_fraction).round() as usize;
|
||||||
let n_good = n_good.clamp(1, n.saturating_sub(1));
|
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;
|
*v /= denom;
|
||||||
}
|
}
|
||||||
let scott_n = (support.len() as f64).powf(-0.2);
|
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)]
|
#[cfg(test)]
|
||||||
|
|||||||
+10
-6
@@ -67,7 +67,10 @@ where
|
|||||||
P: Problem<Decision = Vec<bool>> + Sync,
|
P: Problem<Decision = Vec<bool>> + Sync,
|
||||||
{
|
{
|
||||||
fn run(&mut self, problem: &P) -> OptimizationResult<P::Decision> {
|
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!(
|
assert!(
|
||||||
self.config.selected_size >= 1,
|
self.config.selected_size >= 1,
|
||||||
"Umda selected_size must be >= 1",
|
"Umda selected_size must be >= 1",
|
||||||
@@ -114,7 +117,11 @@ where
|
|||||||
// --- Phase 1: select top μ members ---
|
// --- Phase 1: select top μ members ---
|
||||||
let mut order: Vec<usize> = (0..population.len()).collect();
|
let mut order: Vec<usize> = (0..population.len()).collect();
|
||||||
order.sort_by(|&a, &b| {
|
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>>> =
|
let selected: Vec<&Candidate<Vec<bool>>> =
|
||||||
order.iter().take(mu).map(|&i| &population[i]).collect();
|
order.iter().take(mu).map(|&i| &population[i]).collect();
|
||||||
@@ -228,10 +235,7 @@ mod tests {
|
|||||||
type Decision = Vec<bool>;
|
type Decision = Vec<bool>;
|
||||||
|
|
||||||
fn objectives(&self) -> ObjectiveSpace {
|
fn objectives(&self) -> ObjectiveSpace {
|
||||||
ObjectiveSpace::new(vec![
|
ObjectiveSpace::new(vec![Objective::minimize("a"), Objective::minimize("b")])
|
||||||
Objective::minimize("a"),
|
|
||||||
Objective::minimize("b"),
|
|
||||||
])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn evaluate(&self, _x: &Vec<bool>) -> Evaluation {
|
fn evaluate(&self, _x: &Vec<bool>) -> Evaluation {
|
||||||
|
|||||||
@@ -18,7 +18,10 @@ pub struct Candidate<D> {
|
|||||||
impl<D> Candidate<D> {
|
impl<D> Candidate<D> {
|
||||||
/// Pair a decision with its evaluation.
|
/// Pair a decision with its evaluation.
|
||||||
pub fn new(decision: D, evaluation: Evaluation) -> Self {
|
pub fn new(decision: D, evaluation: Evaluation) -> Self {
|
||||||
Self { decision, evaluation }
|
Self {
|
||||||
|
decision,
|
||||||
|
evaluation,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,12 +18,18 @@ pub struct Evaluation {
|
|||||||
impl Evaluation {
|
impl Evaluation {
|
||||||
/// Build a feasible evaluation from objective values.
|
/// Build a feasible evaluation from objective values.
|
||||||
pub fn new(objectives: Vec<f64>) -> Self {
|
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.
|
/// Build an evaluation with a known total constraint violation.
|
||||||
pub fn constrained(objectives: Vec<f64>, constraint_violation: f64) -> Self {
|
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`.
|
/// Returns `true` when `constraint_violation <= 0.0`.
|
||||||
|
|||||||
@@ -26,12 +26,18 @@ pub struct Objective {
|
|||||||
impl Objective {
|
impl Objective {
|
||||||
/// Create a minimize objective with the given name.
|
/// Create a minimize objective with the given name.
|
||||||
pub fn minimize(name: impl Into<String>) -> Self {
|
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.
|
/// Create a maximize objective with the given name.
|
||||||
pub fn maximize(name: impl Into<String>) -> Self {
|
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!(!single.is_empty());
|
||||||
assert_eq!(single.len(), 1);
|
assert_eq!(single.len(), 1);
|
||||||
|
|
||||||
let multi = ObjectiveSpace::new(vec![
|
let multi = ObjectiveSpace::new(vec![Objective::minimize("f1"), Objective::minimize("f2")]);
|
||||||
Objective::minimize("f1"),
|
|
||||||
Objective::minimize("f2"),
|
|
||||||
]);
|
|
||||||
assert!(multi.is_multi_objective());
|
assert!(multi.is_multi_objective());
|
||||||
assert!(!multi.is_single_objective());
|
assert!(!multi.is_single_objective());
|
||||||
|
|
||||||
|
|||||||
+7
-1
@@ -31,7 +31,13 @@ impl<D> OptimizationResult<D> {
|
|||||||
evaluations: usize,
|
evaluations: usize,
|
||||||
generations: usize,
|
generations: usize,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self { population, pareto_front, best, evaluations, generations }
|
Self {
|
||||||
|
population,
|
||||||
|
pareto_front,
|
||||||
|
best,
|
||||||
|
evaluations,
|
||||||
|
generations,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The final population.
|
/// The final population.
|
||||||
|
|||||||
@@ -21,7 +21,10 @@ pub(crate) fn symmetric_eigen(
|
|||||||
max_sweeps: usize,
|
max_sweeps: usize,
|
||||||
) -> (Vec<f64>, Vec<Vec<f64>>) {
|
) -> (Vec<f64>, Vec<Vec<f64>>) {
|
||||||
let n = matrix.len();
|
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.
|
// Working copy of the matrix; converges to a diagonal of eigenvalues.
|
||||||
let mut a: Vec<Vec<f64>> = matrix.to_vec();
|
let mut a: Vec<Vec<f64>> = matrix.to_vec();
|
||||||
|
|||||||
+24
-23
@@ -71,10 +71,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn space_min2() -> ObjectiveSpace {
|
fn space_min2() -> ObjectiveSpace {
|
||||||
ObjectiveSpace::new(vec![
|
ObjectiveSpace::new(vec![Objective::minimize("f1"), Objective::minimize("f2")])
|
||||||
Objective::minimize("f1"),
|
|
||||||
Objective::minimize("f2"),
|
|
||||||
])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -83,7 +80,11 @@ mod tests {
|
|||||||
// Dominated region area = 4*4 - sum of "outside" rectangles
|
// 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.
|
// 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 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]);
|
let hv = hypervolume_2d(&front, &s, [4.0, 4.0]);
|
||||||
assert!((hv - 6.0).abs() < 1e-12, "expected 6.0, got {hv}");
|
assert!((hv - 6.0).abs() < 1e-12, "expected 6.0, got {hv}");
|
||||||
}
|
}
|
||||||
@@ -156,7 +157,10 @@ pub fn hypervolume_nd<D>(
|
|||||||
reference_point.len(),
|
reference_point.len(),
|
||||||
"hypervolume_nd: ObjectiveSpace and reference_point must agree on dimension",
|
"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() {
|
if front.is_empty() {
|
||||||
return 0.0;
|
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
|
// 2-D HV via the same sweep used by hypervolume_2d. Inlined here
|
||||||
// because we already have the points in oriented form.
|
// because we already have the points in oriented form.
|
||||||
let mut sorted: Vec<&Vec<f64>> = points.iter().collect();
|
let mut sorted: Vec<&Vec<f64>> = points.iter().collect();
|
||||||
sorted.sort_by(|a, b| {
|
sorted.sort_by(|a, b| a[0].partial_cmp(&b[0]).unwrap_or(std::cmp::Ordering::Equal));
|
||||||
a[0].partial_cmp(&b[0]).unwrap_or(std::cmp::Ordering::Equal)
|
|
||||||
});
|
|
||||||
let mut area = 0.0;
|
let mut area = 0.0;
|
||||||
let mut last_y = reference[1];
|
let mut last_y = reference[1];
|
||||||
for p in sorted {
|
for p in sorted {
|
||||||
@@ -236,10 +238,7 @@ fn hso_recursive(points: &[Vec<f64>], reference: &[f64]) -> f64 {
|
|||||||
for p in sorted.into_iter().rev() {
|
for p in sorted.into_iter().rev() {
|
||||||
let depth = prev - p[last];
|
let depth = prev - p[last];
|
||||||
if depth > 0.0 && !active.is_empty() {
|
if depth > 0.0 && !active.is_empty() {
|
||||||
let projected: Vec<Vec<f64>> = active
|
let projected: Vec<Vec<f64>> = active.iter().map(|q| q[..last].to_vec()).collect();
|
||||||
.iter()
|
|
||||||
.map(|q| q[..last].to_vec())
|
|
||||||
.collect();
|
|
||||||
let nd = non_dominated_projection(&projected);
|
let nd = non_dominated_projection(&projected);
|
||||||
total += depth * hso_recursive(&nd, &sub_reference);
|
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.
|
/// Drop dominated members of a projected point set.
|
||||||
fn non_dominated_projection(points: &[Vec<f64>]) -> Vec<Vec<f64>> {
|
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();
|
let mut out: Vec<Vec<f64>> = Vec::new();
|
||||||
'outer: for p in points {
|
'outer: for p in points {
|
||||||
// Skip if dominated by any kept point.
|
// Skip if dominated by any kept point.
|
||||||
@@ -327,11 +330,12 @@ mod nd_tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn nd_matches_2d_on_known_case() {
|
fn nd_matches_2d_on_known_case() {
|
||||||
let s = ObjectiveSpace::new(vec![
|
let s = ObjectiveSpace::new(vec![Objective::minimize("f1"), Objective::minimize("f2")]);
|
||||||
Objective::minimize("f1"),
|
let front = [
|
||||||
Objective::minimize("f2"),
|
cand_n(vec![1.0, 3.0]),
|
||||||
]);
|
cand_n(vec![2.0, 2.0]),
|
||||||
let front = [cand_n(vec![1.0, 3.0]), cand_n(vec![2.0, 2.0]), cand_n(vec![3.0, 1.0])];
|
cand_n(vec![3.0, 1.0]),
|
||||||
|
];
|
||||||
let hv2 = hypervolume_2d(&front, &s, [4.0, 4.0]);
|
let hv2 = hypervolume_2d(&front, &s, [4.0, 4.0]);
|
||||||
let hvn = hypervolume_nd(&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}");
|
assert!((hv2 - hvn).abs() < 1e-12, "{hv2} vs {hvn}");
|
||||||
@@ -409,10 +413,7 @@ mod nd_tests {
|
|||||||
#[test]
|
#[test]
|
||||||
#[should_panic(expected = "must agree on dimension")]
|
#[should_panic(expected = "must agree on dimension")]
|
||||||
fn nd_panics_on_dim_mismatch() {
|
fn nd_panics_on_dim_mismatch() {
|
||||||
let s = ObjectiveSpace::new(vec![
|
let s = ObjectiveSpace::new(vec![Objective::minimize("f1"), Objective::minimize("f2")]);
|
||||||
Objective::minimize("f1"),
|
|
||||||
Objective::minimize("f2"),
|
|
||||||
]);
|
|
||||||
let front = [cand_n(vec![1.0, 1.0])];
|
let front = [cand_n(vec![1.0, 1.0])];
|
||||||
let _ = hypervolume_nd(&front, &s, &[1.0, 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 mean = nearest.iter().sum::<f64>() / n as f64;
|
||||||
let variance =
|
let variance = nearest.iter().map(|d| (d - mean).powi(2)).sum::<f64>() / n as f64;
|
||||||
nearest.iter().map(|d| (d - mean).powi(2)).sum::<f64>() / n as f64;
|
|
||||||
variance.sqrt()
|
variance.sqrt()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,10 +54,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn space_min2() -> ObjectiveSpace {
|
fn space_min2() -> ObjectiveSpace {
|
||||||
ObjectiveSpace::new(vec![
|
ObjectiveSpace::new(vec![Objective::minimize("f1"), Objective::minimize("f2")])
|
||||||
Objective::minimize("f1"),
|
|
||||||
Objective::minimize("f2"),
|
|
||||||
])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
+30
-10
@@ -38,7 +38,11 @@ impl Initializer<Vec<f64>> for RealBounds {
|
|||||||
for _ in 0..size {
|
for _ in 0..size {
|
||||||
let mut decision = Vec::with_capacity(self.bounds.len());
|
let mut decision = Vec::with_capacity(self.bounds.len());
|
||||||
for &(lo, hi) in &self.bounds {
|
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);
|
decision.push(v);
|
||||||
}
|
}
|
||||||
out.push(decision);
|
out.push(decision);
|
||||||
@@ -63,8 +67,7 @@ impl Variation<Vec<f64>> for GaussianMutation {
|
|||||||
!parents.is_empty(),
|
!parents.is_empty(),
|
||||||
"GaussianMutation requires at least one parent",
|
"GaussianMutation requires at least one parent",
|
||||||
);
|
);
|
||||||
let normal =
|
let normal = Normal::new(0.0, self.sigma).expect("Normal distribution rejected sigma");
|
||||||
Normal::new(0.0, self.sigma).expect("Normal distribution rejected sigma");
|
|
||||||
let mut child = parents[0].clone();
|
let mut child = parents[0].clone();
|
||||||
for x in child.iter_mut() {
|
for x in child.iter_mut() {
|
||||||
*x += normal.sample(rng);
|
*x += normal.sample(rng);
|
||||||
@@ -113,7 +116,11 @@ impl SimulatedBinaryCrossover {
|
|||||||
(0.0..=1.0).contains(&per_variable_probability),
|
(0.0..=1.0).contains(&per_variable_probability),
|
||||||
"SimulatedBinaryCrossover per_variable_probability must be in [0.0, 1.0]",
|
"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),
|
(0.0..=1.0).contains(&per_variable_probability),
|
||||||
"PolynomialMutation per_variable_probability must be in [0.0, 1.0]",
|
"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
|
/// # Panics
|
||||||
/// If `sigma <= 0.0` or any bound has `lo > hi`.
|
/// If `sigma <= 0.0` or any bound has `lo > hi`.
|
||||||
pub fn new(sigma: f64, bounds: Vec<(f64, f64)>) -> Self {
|
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() {
|
for (i, &(lo, hi)) in bounds.iter().enumerate() {
|
||||||
assert!(
|
assert!(
|
||||||
lo <= hi,
|
lo <= hi,
|
||||||
@@ -279,8 +293,7 @@ impl Variation<Vec<f64>> for BoundedGaussianMutation {
|
|||||||
self.bounds.len(),
|
self.bounds.len(),
|
||||||
"BoundedGaussianMutation parent length must match bounds length",
|
"BoundedGaussianMutation parent length must match bounds length",
|
||||||
);
|
);
|
||||||
let normal =
|
let normal = Normal::new(0.0, self.sigma).expect("Normal distribution rejected sigma");
|
||||||
Normal::new(0.0, self.sigma).expect("Normal distribution rejected sigma");
|
|
||||||
let mut child = parents[0].clone();
|
let mut child = parents[0].clone();
|
||||||
for (x, &(lo, hi)) in child.iter_mut().zip(self.bounds.iter()) {
|
for (x, &(lo, hi)) in child.iter_mut().zip(self.bounds.iter()) {
|
||||||
*x = (*x + normal.sample(rng)).clamp(lo, hi);
|
*x = (*x + normal.sample(rng)).clamp(lo, hi);
|
||||||
@@ -330,13 +343,20 @@ impl LevyMutation {
|
|||||||
"LevyMutation bound at index {i} has lo > hi: ({lo}, {hi})",
|
"LevyMutation bound at index {i} has lo > hi: ({lo}, {hi})",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Self { alpha, scale, bounds }
|
Self {
|
||||||
|
alpha,
|
||||||
|
scale,
|
||||||
|
bounds,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Variation<Vec<f64>> for LevyMutation {
|
impl Variation<Vec<f64>> for LevyMutation {
|
||||||
fn vary(&mut self, parents: &[Vec<f64>], rng: &mut Rng) -> Vec<Vec<f64>> {
|
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;
|
let alpha = self.alpha;
|
||||||
// Mantegna's algorithm σ for the numerator Normal:
|
// Mantegna's algorithm σ for the numerator Normal:
|
||||||
// sigma_u = (Γ(1+α)·sin(π·α/2) / (Γ((1+α)/2)·α·2^((α-1)/2)))^(1/α)
|
// sigma_u = (Γ(1+α)·sin(π·α/2) / (Γ((1+α)/2)·α·2^((α-1)/2)))^(1/α)
|
||||||
|
|||||||
@@ -21,7 +21,10 @@ pub struct ParetoArchive<D> {
|
|||||||
impl<D: Clone> ParetoArchive<D> {
|
impl<D: Clone> ParetoArchive<D> {
|
||||||
/// Build an empty archive against the given objective space.
|
/// Build an empty archive against the given objective space.
|
||||||
pub fn new(objectives: ObjectiveSpace) -> Self {
|
pub fn new(objectives: ObjectiveSpace) -> Self {
|
||||||
Self { members: Vec::new(), objectives }
|
Self {
|
||||||
|
members: Vec::new(),
|
||||||
|
objectives,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Insert a candidate, preserving the non-domination property.
|
/// Insert a candidate, preserving the non-domination property.
|
||||||
@@ -85,10 +88,7 @@ mod tests {
|
|||||||
use crate::core::objective::Objective;
|
use crate::core::objective::Objective;
|
||||||
|
|
||||||
fn space_min2() -> ObjectiveSpace {
|
fn space_min2() -> ObjectiveSpace {
|
||||||
ObjectiveSpace::new(vec![
|
ObjectiveSpace::new(vec![Objective::minimize("f1"), Objective::minimize("f2")])
|
||||||
Objective::minimize("f1"),
|
|
||||||
Objective::minimize("f2"),
|
|
||||||
])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cand(decision: u32, obj: Vec<f64>) -> Candidate<u32> {
|
fn cand(decision: u32, obj: Vec<f64>) -> Candidate<u32> {
|
||||||
|
|||||||
@@ -77,10 +77,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn space_min2() -> ObjectiveSpace {
|
fn space_min2() -> ObjectiveSpace {
|
||||||
ObjectiveSpace::new(vec![
|
ObjectiveSpace::new(vec![Objective::minimize("f1"), Objective::minimize("f2")])
|
||||||
Objective::minimize("f1"),
|
|
||||||
Objective::minimize("f2"),
|
|
||||||
])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -29,11 +29,7 @@ pub enum Dominance {
|
|||||||
/// `constraint_violation` dominates.
|
/// `constraint_violation` dominates.
|
||||||
/// 3. Otherwise compare objective values after converting both to
|
/// 3. Otherwise compare objective values after converting both to
|
||||||
/// minimization orientation via [`ObjectiveSpace::as_minimization`].
|
/// minimization orientation via [`ObjectiveSpace::as_minimization`].
|
||||||
pub fn pareto_compare(
|
pub fn pareto_compare(a: &Evaluation, b: &Evaluation, objectives: &ObjectiveSpace) -> Dominance {
|
||||||
a: &Evaluation,
|
|
||||||
b: &Evaluation,
|
|
||||||
objectives: &ObjectiveSpace,
|
|
||||||
) -> Dominance {
|
|
||||||
let a_feasible = a.is_feasible();
|
let a_feasible = a.is_feasible();
|
||||||
let b_feasible = b.is_feasible();
|
let b_feasible = b.is_feasible();
|
||||||
match (a_feasible, b_feasible) {
|
match (a_feasible, b_feasible) {
|
||||||
@@ -78,10 +74,7 @@ mod tests {
|
|||||||
use crate::core::objective::Objective;
|
use crate::core::objective::Objective;
|
||||||
|
|
||||||
fn space_min2() -> ObjectiveSpace {
|
fn space_min2() -> ObjectiveSpace {
|
||||||
ObjectiveSpace::new(vec![
|
ObjectiveSpace::new(vec![Objective::minimize("f1"), Objective::minimize("f2")])
|
||||||
Objective::minimize("f1"),
|
|
||||||
Objective::minimize("f2"),
|
|
||||||
])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
+1
-4
@@ -69,10 +69,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn space_min2() -> ObjectiveSpace {
|
fn space_min2() -> ObjectiveSpace {
|
||||||
ObjectiveSpace::new(vec![
|
ObjectiveSpace::new(vec![Objective::minimize("f1"), Objective::minimize("f2")])
|
||||||
Objective::minimize("f1"),
|
|
||||||
Objective::minimize("f2"),
|
|
||||||
])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -11,7 +11,10 @@
|
|||||||
/// # Panics
|
/// # Panics
|
||||||
/// If `num_objectives == 0`.
|
/// If `num_objectives == 0`.
|
||||||
pub fn das_dennis(num_objectives: usize, divisions: usize) -> Vec<Vec<f64>> {
|
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 out = Vec::new();
|
||||||
let mut current = Vec::with_capacity(num_objectives);
|
let mut current = Vec::with_capacity(num_objectives);
|
||||||
recurse(num_objectives, divisions, divisions, &mut current, &mut out);
|
recurse(num_objectives, divisions, divisions, &mut current, &mut out);
|
||||||
@@ -34,7 +37,13 @@ fn recurse(
|
|||||||
}
|
}
|
||||||
for take in 0..=remaining_units {
|
for take in 0..=remaining_units {
|
||||||
current.push(take);
|
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();
|
current.pop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+14
-17
@@ -12,28 +12,25 @@ pub use crate::core::{
|
|||||||
pub use crate::traits::{Initializer, Optimizer, Repair, Variation};
|
pub use crate::traits::{Initializer, Optimizer, Repair, Variation};
|
||||||
|
|
||||||
pub use crate::pareto::{
|
pub use crate::pareto::{
|
||||||
Dominance, ParetoArchive, best_candidate, crowding_distance, das_dennis,
|
Dominance, ParetoArchive, best_candidate, crowding_distance, das_dennis, non_dominated_sort,
|
||||||
non_dominated_sort, pareto_compare, pareto_front,
|
pareto_compare, pareto_front,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub use crate::operators::{
|
pub use crate::operators::{
|
||||||
BitFlipMutation, BoundedGaussianMutation, ClampToBounds, CompositeVariation,
|
BitFlipMutation, BoundedGaussianMutation, ClampToBounds, CompositeVariation, GaussianMutation,
|
||||||
GaussianMutation, LevyMutation, PolynomialMutation, ProjectToSimplex, RealBounds,
|
LevyMutation, PolynomialMutation, ProjectToSimplex, RealBounds, SimulatedBinaryCrossover,
|
||||||
SimulatedBinaryCrossover, SwapMutation,
|
SwapMutation,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub use crate::algorithms::{
|
pub use crate::algorithms::{
|
||||||
AgeMoea, AgeMoeaConfig, AntColonyTsp, AntColonyTspConfig, BayesianOpt,
|
AgeMoea, AgeMoeaConfig, AntColonyTsp, AntColonyTspConfig, BayesianOpt, BayesianOptConfig,
|
||||||
BayesianOptConfig, CmaEs, CmaEsConfig, DifferentialEvolution,
|
CmaEs, CmaEsConfig, DifferentialEvolution, DifferentialEvolutionConfig, EpsilonMoea,
|
||||||
DifferentialEvolutionConfig, EpsilonMoea, EpsilonMoeaConfig,
|
EpsilonMoeaConfig, GeneticAlgorithm, GeneticAlgorithmConfig, Grea, GreaConfig, HillClimber,
|
||||||
GeneticAlgorithm, GeneticAlgorithmConfig, Grea, GreaConfig, HillClimber, HillClimberConfig, Hype,
|
HillClimberConfig, Hype, HypeConfig, Hyperband, HyperbandConfig, Ibea, IbeaConfig, IpopCmaEs,
|
||||||
HypeConfig, Hyperband, HyperbandConfig, Ibea, IbeaConfig, IpopCmaEs, IpopCmaEsConfig,
|
IpopCmaEsConfig, Knea, KneaConfig, Moead, MoeadConfig, Mopso, MopsoConfig, NelderMead,
|
||||||
Knea, KneaConfig, Moead, MoeadConfig, Mopso, MopsoConfig,
|
NelderMeadConfig, Nsga2, Nsga2Config, Nsga3, Nsga3Config, OnePlusOneEs, OnePlusOneEsConfig,
|
||||||
NelderMead, NelderMeadConfig, Nsga2,
|
Paes, PaesConfig, ParticleSwarm, ParticleSwarmConfig, PesaII, PesaIIConfig, RandomSearch,
|
||||||
Nsga2Config, Nsga3, Nsga3Config, OnePlusOneEs, OnePlusOneEsConfig, Paes, PaesConfig, ParticleSwarm, PesaII, PesaIIConfig,
|
RandomSearchConfig, Rvea, RveaConfig, SeparableNes, SeparableNesConfig, SimulatedAnnealing,
|
||||||
ParticleSwarmConfig, RandomSearch, RandomSearchConfig, Rvea, RveaConfig,
|
|
||||||
SeparableNes, SeparableNesConfig, SimulatedAnnealing,
|
|
||||||
SimulatedAnnealingConfig, SmsEmoa, SmsEmoaConfig, Spea2, Spea2Config, TabuSearch,
|
SimulatedAnnealingConfig, SmsEmoa, SmsEmoaConfig, Spea2, Spea2Config, TabuSearch,
|
||||||
TabuSearchConfig, Tlbo, TlboConfig, Tpe, TpeConfig, Umda,
|
TabuSearchConfig, Tlbo, TlboConfig, Tpe, TpeConfig, Umda, UmdaConfig,
|
||||||
UmdaConfig,
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -9,11 +9,7 @@ use crate::core::rng::Rng;
|
|||||||
///
|
///
|
||||||
/// Returns cloned decisions. Panics if `population` is empty and `count > 0`
|
/// Returns cloned decisions. Panics if `population` is empty and `count > 0`
|
||||||
/// (spec §10.1).
|
/// (spec §10.1).
|
||||||
pub fn select_random<D: Clone>(
|
pub fn select_random<D: Clone>(population: &[Candidate<D>], count: usize, rng: &mut Rng) -> Vec<D> {
|
||||||
population: &[Candidate<D>],
|
|
||||||
count: usize,
|
|
||||||
rng: &mut Rng,
|
|
||||||
) -> Vec<D> {
|
|
||||||
if count == 0 {
|
if count == 0 {
|
||||||
return Vec::new();
|
return Vec::new();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,8 +63,18 @@ fn challenger_wins<D>(c: &Candidate<D>, b: &Candidate<D>, dir: Direction) -> boo
|
|||||||
(false, true) => false,
|
(false, true) => false,
|
||||||
(false, false) => c.evaluation.constraint_violation < b.evaluation.constraint_violation,
|
(false, false) => c.evaluation.constraint_violation < b.evaluation.constraint_violation,
|
||||||
(true, true) => {
|
(true, true) => {
|
||||||
let cv = c.evaluation.objectives.first().copied().unwrap_or(f64::INFINITY);
|
let cv = c
|
||||||
let bv = b.evaluation.objectives.first().copied().unwrap_or(f64::INFINITY);
|
.evaluation
|
||||||
|
.objectives
|
||||||
|
.first()
|
||||||
|
.copied()
|
||||||
|
.unwrap_or(f64::INFINITY);
|
||||||
|
let bv = b
|
||||||
|
.evaluation
|
||||||
|
.objectives
|
||||||
|
.first()
|
||||||
|
.copied()
|
||||||
|
.unwrap_or(f64::INFINITY);
|
||||||
match dir {
|
match dir {
|
||||||
Direction::Minimize => cv < bv,
|
Direction::Minimize => cv < bv,
|
||||||
Direction::Maximize => cv > bv,
|
Direction::Maximize => cv > bv,
|
||||||
@@ -218,10 +228,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
#[should_panic(expected = "exactly one objective")]
|
#[should_panic(expected = "exactly one objective")]
|
||||||
fn multi_objective_panics() {
|
fn multi_objective_panics() {
|
||||||
let s = ObjectiveSpace::new(vec![
|
let s = ObjectiveSpace::new(vec![Objective::minimize("f1"), Objective::minimize("f2")]);
|
||||||
Objective::minimize("f1"),
|
|
||||||
Objective::minimize("f2"),
|
|
||||||
]);
|
|
||||||
let pop = [cand_min(1, 1.0)];
|
let pop = [cand_min(1, 1.0)];
|
||||||
let mut rng = rng_from_seed(0);
|
let mut rng = rng_from_seed(0);
|
||||||
let _ = tournament_select_single_objective(&pop, &s, 2, 1, &mut rng);
|
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 s = ObjectiveSpace::new(vec![Objective::minimize("f")]);
|
||||||
let pop = [
|
let pop = [
|
||||||
Candidate::new(1u32, Evaluation::constrained(vec![0.0], 5.0)), // infeasible
|
Candidate::new(1u32, Evaluation::constrained(vec![0.0], 5.0)), // infeasible
|
||||||
Candidate::new(2u32, Evaluation::new(vec![10.0])), // feasible, big f
|
Candidate::new(2u32, Evaluation::new(vec![10.0])), // feasible, big f
|
||||||
Candidate::new(3u32, Evaluation::new(vec![3.0])), // feasible, small f
|
Candidate::new(3u32, Evaluation::new(vec![3.0])), // feasible, small f
|
||||||
];
|
];
|
||||||
let mut rng = rng_from_seed(0);
|
let mut rng = rng_from_seed(0);
|
||||||
let picks = stochastic_ranking_select(&pop, &s, 0.0, 3, &mut rng);
|
let picks = stochastic_ranking_select(&pop, &s, 0.0, 3, &mut rng);
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ impl Problem for SchafferN1 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct OneMax {
|
struct OneMax {
|
||||||
|
#[allow(dead_code)]
|
||||||
bits: usize,
|
bits: usize,
|
||||||
}
|
}
|
||||||
impl Problem for OneMax {
|
impl Problem for OneMax {
|
||||||
@@ -69,8 +70,7 @@ fn mo_bounds() -> Vec<(f64, f64)> {
|
|||||||
vec![(-3.0, 3.0)]
|
vec![(-3.0, 3.0)]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mo_variation()
|
fn mo_variation() -> CompositeVariation<SimulatedBinaryCrossover, PolynomialMutation> {
|
||||||
-> CompositeVariation<SimulatedBinaryCrossover, PolynomialMutation> {
|
|
||||||
let bounds = mo_bounds();
|
let bounds = mo_bounds();
|
||||||
CompositeVariation {
|
CompositeVariation {
|
||||||
crossover: SimulatedBinaryCrossover::new(bounds.clone(), 15.0, 0.5),
|
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
|
/// Generate per-axis bounds whose width is at least 0.001 (avoid the
|
||||||
/// degenerate `lo == hi` case for properties that need a proper interval).
|
/// degenerate `lo == hi` case for properties that need a proper interval).
|
||||||
fn bounds(dim: usize) -> impl Strategy<Value = Vec<(f64, f64)>> {
|
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::collection::vec((-50.0_f64..50.0, 0.001_f64..50.0), dim..=dim).prop_map(|pairs| {
|
||||||
.prop_map(|pairs| pairs.into_iter().map(|(lo, span)| (lo, lo + span)).collect())
|
pairs
|
||||||
|
.into_iter()
|
||||||
|
.map(|(lo, span)| (lo, lo + span))
|
||||||
|
.collect()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generate a parent vector inside the given bounds.
|
/// Generate a parent vector inside the given bounds.
|
||||||
|
|||||||
+8
-9
@@ -20,17 +20,12 @@ use heuropt::prelude::*;
|
|||||||
|
|
||||||
/// Generate a 2-objective minimize ObjectiveSpace.
|
/// Generate a 2-objective minimize ObjectiveSpace.
|
||||||
fn space_2d() -> ObjectiveSpace {
|
fn space_2d() -> ObjectiveSpace {
|
||||||
ObjectiveSpace::new(vec![
|
ObjectiveSpace::new(vec![Objective::minimize("f1"), Objective::minimize("f2")])
|
||||||
Objective::minimize("f1"),
|
|
||||||
Objective::minimize("f2"),
|
|
||||||
])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generate a candidate with a 2-D objective vector in `[lo, hi]`.
|
/// Generate a candidate with a 2-D objective vector in `[lo, hi]`.
|
||||||
fn candidate_2d(lo: f64, hi: f64) -> impl Strategy<Value = Candidate<()>> {
|
fn candidate_2d(lo: f64, hi: f64) -> impl Strategy<Value = Candidate<()>> {
|
||||||
(lo..hi, lo..hi).prop_map(|(a, b)| {
|
(lo..hi, lo..hi).prop_map(|(a, b)| Candidate::new((), Evaluation::new(vec![a, b])))
|
||||||
Candidate::new((), Evaluation::new(vec![a, b]))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generate a small 2-D population.
|
/// Generate a small 2-D population.
|
||||||
@@ -40,8 +35,12 @@ fn population_2d() -> impl Strategy<Value = Vec<Candidate<()>>> {
|
|||||||
|
|
||||||
/// Generate per-axis bounds.
|
/// Generate per-axis bounds.
|
||||||
fn bounds(dim: usize) -> impl Strategy<Value = Vec<(f64, f64)>> {
|
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::collection::vec((-50.0_f64..50.0, 0.001_f64..50.0), dim..=dim).prop_map(|pairs| {
|
||||||
.prop_map(|pairs| pairs.into_iter().map(|(lo, span)| (lo, lo + span)).collect())
|
pairs
|
||||||
|
.into_iter()
|
||||||
|
.map(|(lo, span)| (lo, lo + span))
|
||||||
|
.collect()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|||||||
Reference in New Issue
Block a user