Expand benches/hot_paths.rs so the instruction-count harness exercises the whole library. Adds four groups — permutation_ops_group (all 10 permutation operators at n=30/100), variation_ops_group (BitFlip, Levy, BoundedGaussian, ClampToBounds, ProjectToSimplex), combinatorial_group (TSP/JSS/knapsack end-to-end plus AntColonyTsp), and multi_fidelity_group (Hyperband) — and folds tabu_search_short and umda_short into single_objective_group. All 33 algorithms and 20 operators are now on the benchmarking surface (69 benches). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1313 lines
37 KiB
Rust
1313 lines
37 KiB
Rust
//! Instruction-count benchmarks for heuropt's algorithmic hot paths.
|
|
//!
|
|
//! Run with `cargo bench`. Requires `valgrind` installed.
|
|
//!
|
|
//! The benchmarks here are *not* end-to-end optimizer runs; those are
|
|
//! covered by `examples/compare`. These are the inner-loop primitives
|
|
//! that every algorithm depends on, so a regression here lights up
|
|
//! across the whole crate.
|
|
|
|
use std::hint::black_box;
|
|
|
|
use gungraun::prelude::*;
|
|
use rand::Rng as _;
|
|
|
|
use heuropt::core::candidate::Candidate;
|
|
use heuropt::core::evaluation::Evaluation;
|
|
use heuropt::core::objective::{Objective, ObjectiveSpace};
|
|
use heuropt::core::partial_problem::PartialProblem;
|
|
use heuropt::core::problem::Problem;
|
|
use heuropt::core::rng::{Rng, rng_from_seed};
|
|
use heuropt::metrics::hypervolume::{hypervolume_2d, hypervolume_nd};
|
|
use heuropt::pareto::crowding::crowding_distance;
|
|
use heuropt::pareto::sort::non_dominated_sort;
|
|
use heuropt::prelude::*;
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// Pareto utilities
|
|
// -----------------------------------------------------------------------------
|
|
|
|
fn make_2d_population(n: usize) -> Vec<Candidate<()>> {
|
|
(0..n)
|
|
.map(|i| {
|
|
let t = i as f64 / n as f64;
|
|
Candidate::new((), Evaluation::new(vec![t, 1.0 - t.sqrt()]))
|
|
})
|
|
.collect()
|
|
}
|
|
|
|
fn space_2d() -> ObjectiveSpace {
|
|
ObjectiveSpace::new(vec![Objective::minimize("f1"), Objective::minimize("f2")])
|
|
}
|
|
|
|
#[library_benchmark]
|
|
#[bench::n_50(50)]
|
|
#[bench::n_200(200)]
|
|
fn non_dominated_sort_2d(n: usize) -> Vec<Vec<usize>> {
|
|
let pop = make_2d_population(n);
|
|
let s = space_2d();
|
|
black_box(non_dominated_sort(black_box(&pop), black_box(&s)))
|
|
}
|
|
|
|
#[library_benchmark]
|
|
#[bench::n_50(50)]
|
|
#[bench::n_200(200)]
|
|
fn crowding_distance_2d(n: usize) -> Vec<f64> {
|
|
let pop = make_2d_population(n);
|
|
let s = space_2d();
|
|
let front: Vec<usize> = (0..pop.len()).collect();
|
|
black_box(crowding_distance(
|
|
black_box(&pop),
|
|
black_box(&front),
|
|
black_box(&s),
|
|
))
|
|
}
|
|
|
|
#[library_benchmark]
|
|
#[bench::n_30(30)]
|
|
#[bench::n_100(100)]
|
|
fn hypervolume_2d_bench(n: usize) -> f64 {
|
|
let pop = make_2d_population(n);
|
|
let s = space_2d();
|
|
black_box(hypervolume_2d(
|
|
black_box(&pop),
|
|
black_box(&s),
|
|
black_box([1.1, 1.1]),
|
|
))
|
|
}
|
|
|
|
fn make_3d_population(n: usize) -> (Vec<Candidate<()>>, ObjectiveSpace) {
|
|
let s = ObjectiveSpace::new(vec![
|
|
Objective::minimize("f1"),
|
|
Objective::minimize("f2"),
|
|
Objective::minimize("f3"),
|
|
]);
|
|
let pop = (0..n)
|
|
.map(|i| {
|
|
let t = i as f64 / n as f64;
|
|
let theta = 0.5 * std::f64::consts::PI * t;
|
|
Candidate::new((), Evaluation::new(vec![theta.cos(), theta.sin(), 1.0 - t]))
|
|
})
|
|
.collect();
|
|
(pop, s)
|
|
}
|
|
|
|
#[library_benchmark]
|
|
#[bench::n_30(30)]
|
|
#[bench::n_100(100)]
|
|
fn hypervolume_nd_bench_3d(n: usize) -> f64 {
|
|
let (pop, s) = make_3d_population(n);
|
|
black_box(hypervolume_nd(
|
|
black_box(&pop),
|
|
black_box(&s),
|
|
black_box(&[2.0, 2.0, 2.0]),
|
|
))
|
|
}
|
|
|
|
library_benchmark_group!(
|
|
name = pareto_group;
|
|
benchmarks =
|
|
non_dominated_sort_2d,
|
|
crowding_distance_2d,
|
|
hypervolume_2d_bench,
|
|
hypervolume_nd_bench_3d
|
|
);
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// End-to-end algorithm smoke benches (single-generation cost)
|
|
// -----------------------------------------------------------------------------
|
|
|
|
/// Schaffer N.1 (2-objective). Inlined here so the bench doesn't need
|
|
/// to reach into the crate's `cfg(test)` test support.
|
|
struct SchafferN1;
|
|
impl Problem for SchafferN1 {
|
|
type Decision = Vec<f64>;
|
|
fn objectives(&self) -> ObjectiveSpace {
|
|
ObjectiveSpace::new(vec![Objective::minimize("f1"), Objective::minimize("f2")])
|
|
}
|
|
fn evaluate(&self, x: &Vec<f64>) -> Evaluation {
|
|
let v = x[0];
|
|
Evaluation::new(vec![v * v, (v - 2.0).powi(2)])
|
|
}
|
|
}
|
|
|
|
#[library_benchmark]
|
|
fn nsga2_one_generation() -> usize {
|
|
let bounds = vec![(-5.0, 5.0)];
|
|
let initializer = RealBounds::new(bounds.clone());
|
|
let variation = CompositeVariation {
|
|
crossover: SimulatedBinaryCrossover::new(bounds.clone(), 15.0, 0.5),
|
|
mutation: PolynomialMutation::new(bounds, 20.0, 1.0),
|
|
};
|
|
let mut opt = Nsga2::new(
|
|
Nsga2Config {
|
|
population_size: 50,
|
|
generations: 1,
|
|
seed: 0,
|
|
},
|
|
initializer,
|
|
variation,
|
|
);
|
|
let result = opt.run(black_box(&SchafferN1));
|
|
black_box(result.evaluations)
|
|
}
|
|
|
|
#[library_benchmark]
|
|
fn cma_es_one_generation() -> usize {
|
|
let bounds = RealBounds::new(vec![(-5.0, 5.0); 5]);
|
|
let mut opt = CmaEs::new(
|
|
CmaEsConfig {
|
|
population_size: 16,
|
|
generations: 1,
|
|
initial_sigma: 0.5,
|
|
eigen_decomposition_period: 1,
|
|
initial_mean: None,
|
|
seed: 0,
|
|
},
|
|
bounds,
|
|
);
|
|
struct Sphere5D;
|
|
impl Problem for Sphere5D {
|
|
type Decision = Vec<f64>;
|
|
fn objectives(&self) -> ObjectiveSpace {
|
|
ObjectiveSpace::new(vec![Objective::minimize("f")])
|
|
}
|
|
fn evaluate(&self, x: &Vec<f64>) -> Evaluation {
|
|
Evaluation::new(vec![x.iter().map(|v| v * v).sum()])
|
|
}
|
|
}
|
|
let result = opt.run(black_box(&Sphere5D));
|
|
black_box(result.evaluations)
|
|
}
|
|
|
|
library_benchmark_group!(
|
|
name = algorithm_group;
|
|
benchmarks = nsga2_one_generation, cma_es_one_generation
|
|
);
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// Wider single-objective sweep
|
|
// -----------------------------------------------------------------------------
|
|
|
|
struct Sphere1D;
|
|
impl Problem for Sphere1D {
|
|
type Decision = Vec<f64>;
|
|
fn objectives(&self) -> ObjectiveSpace {
|
|
ObjectiveSpace::new(vec![Objective::minimize("f")])
|
|
}
|
|
fn evaluate(&self, x: &Vec<f64>) -> Evaluation {
|
|
Evaluation::new(vec![x[0] * x[0]])
|
|
}
|
|
}
|
|
|
|
fn so_bounds() -> RealBounds {
|
|
RealBounds::new(vec![(-3.0, 3.0)])
|
|
}
|
|
|
|
#[library_benchmark]
|
|
fn random_search_short() -> usize {
|
|
let mut o = RandomSearch::new(
|
|
RandomSearchConfig {
|
|
iterations: 50,
|
|
batch_size: 1,
|
|
seed: 0,
|
|
},
|
|
so_bounds(),
|
|
);
|
|
black_box(o.run(black_box(&Sphere1D)).evaluations)
|
|
}
|
|
|
|
#[library_benchmark]
|
|
fn hill_climber_short() -> usize {
|
|
let mut o = HillClimber::new(
|
|
HillClimberConfig {
|
|
iterations: 50,
|
|
seed: 0,
|
|
},
|
|
so_bounds(),
|
|
GaussianMutation { sigma: 0.1 },
|
|
);
|
|
black_box(o.run(black_box(&Sphere1D)).evaluations)
|
|
}
|
|
|
|
#[library_benchmark]
|
|
fn one_plus_one_es_short() -> usize {
|
|
let mut o = OnePlusOneEs::new(
|
|
OnePlusOneEsConfig {
|
|
iterations: 50,
|
|
initial_sigma: 0.5,
|
|
adaptation_period: 10,
|
|
step_increase: 1.22,
|
|
seed: 0,
|
|
},
|
|
so_bounds(),
|
|
);
|
|
black_box(o.run(black_box(&Sphere1D)).evaluations)
|
|
}
|
|
|
|
#[library_benchmark]
|
|
fn simulated_annealing_short() -> usize {
|
|
let mut o = SimulatedAnnealing::new(
|
|
SimulatedAnnealingConfig {
|
|
iterations: 50,
|
|
initial_temperature: 1.0,
|
|
final_temperature: 1e-3,
|
|
seed: 0,
|
|
},
|
|
so_bounds(),
|
|
GaussianMutation { sigma: 0.1 },
|
|
);
|
|
black_box(o.run(black_box(&Sphere1D)).evaluations)
|
|
}
|
|
|
|
#[library_benchmark]
|
|
fn genetic_algorithm_short() -> usize {
|
|
let bounds = vec![(-3.0, 3.0)];
|
|
let mut o = GeneticAlgorithm::new(
|
|
GeneticAlgorithmConfig {
|
|
population_size: 10,
|
|
generations: 5,
|
|
tournament_size: 2,
|
|
elitism: 1,
|
|
seed: 0,
|
|
},
|
|
RealBounds::new(bounds.clone()),
|
|
CompositeVariation {
|
|
crossover: SimulatedBinaryCrossover::new(bounds.clone(), 15.0, 0.5),
|
|
mutation: PolynomialMutation::new(bounds, 20.0, 1.0),
|
|
},
|
|
);
|
|
black_box(o.run(black_box(&Sphere1D)).evaluations)
|
|
}
|
|
|
|
#[library_benchmark]
|
|
fn particle_swarm_short() -> usize {
|
|
let mut o = ParticleSwarm::new(
|
|
ParticleSwarmConfig {
|
|
swarm_size: 10,
|
|
generations: 5,
|
|
inertia: 0.7,
|
|
cognitive: 1.5,
|
|
social: 1.5,
|
|
seed: 0,
|
|
},
|
|
so_bounds(),
|
|
);
|
|
black_box(o.run(black_box(&Sphere1D)).evaluations)
|
|
}
|
|
|
|
#[library_benchmark]
|
|
fn differential_evolution_short() -> usize {
|
|
let mut o = DifferentialEvolution::new(
|
|
DifferentialEvolutionConfig {
|
|
population_size: 10,
|
|
generations: 5,
|
|
differential_weight: 0.5,
|
|
crossover_probability: 0.9,
|
|
seed: 0,
|
|
},
|
|
so_bounds(),
|
|
);
|
|
black_box(o.run(black_box(&Sphere1D)).evaluations)
|
|
}
|
|
|
|
#[library_benchmark]
|
|
fn tlbo_short() -> usize {
|
|
let mut o = Tlbo::new(
|
|
TlboConfig {
|
|
population_size: 10,
|
|
generations: 5,
|
|
seed: 0,
|
|
},
|
|
so_bounds(),
|
|
);
|
|
black_box(o.run(black_box(&Sphere1D)).evaluations)
|
|
}
|
|
|
|
#[library_benchmark]
|
|
fn separable_nes_short() -> usize {
|
|
let mut o = SeparableNes::new(
|
|
SeparableNesConfig {
|
|
population_size: 8,
|
|
generations: 5,
|
|
initial_sigma: 0.5,
|
|
mean_learning_rate: 1.0,
|
|
sigma_learning_rate: None,
|
|
seed: 0,
|
|
},
|
|
so_bounds(),
|
|
);
|
|
black_box(o.run(black_box(&Sphere1D)).evaluations)
|
|
}
|
|
|
|
#[library_benchmark]
|
|
fn nelder_mead_short() -> usize {
|
|
let mut o = NelderMead::new(
|
|
NelderMeadConfig {
|
|
iterations: 50,
|
|
..NelderMeadConfig::default()
|
|
},
|
|
so_bounds(),
|
|
);
|
|
black_box(o.run(black_box(&Sphere1D)).evaluations)
|
|
}
|
|
|
|
#[library_benchmark]
|
|
fn bayesian_opt_short() -> usize {
|
|
let mut o = BayesianOpt::new(
|
|
BayesianOptConfig {
|
|
initial_samples: 5,
|
|
iterations: 10,
|
|
length_scales: None,
|
|
signal_variance: 1.0,
|
|
noise_variance: 1e-6,
|
|
acquisition_samples: 100,
|
|
seed: 0,
|
|
},
|
|
so_bounds(),
|
|
);
|
|
black_box(o.run(black_box(&Sphere1D)).evaluations)
|
|
}
|
|
|
|
#[library_benchmark]
|
|
fn tpe_short() -> usize {
|
|
let mut o = Tpe::new(
|
|
TpeConfig {
|
|
initial_samples: 5,
|
|
iterations: 10,
|
|
good_fraction: 0.25,
|
|
candidate_samples: 12,
|
|
bandwidth_factor: 1.0,
|
|
seed: 0,
|
|
},
|
|
so_bounds(),
|
|
);
|
|
black_box(o.run(black_box(&Sphere1D)).evaluations)
|
|
}
|
|
|
|
#[library_benchmark]
|
|
fn ipop_cma_es_short() -> usize {
|
|
let mut o = IpopCmaEs::new(
|
|
IpopCmaEsConfig {
|
|
initial_population_size: 8,
|
|
total_generations: 30,
|
|
initial_sigma: 0.5,
|
|
eigen_decomposition_period: 1,
|
|
stall_generations: None,
|
|
seed: 0,
|
|
},
|
|
so_bounds(),
|
|
);
|
|
black_box(o.run(black_box(&Sphere1D)).evaluations)
|
|
}
|
|
|
|
/// 1-D integer parabola: minimize `(x - 5)^2`. `Vec<i32>` decision so it
|
|
/// satisfies `TabuSearch`'s `Hash + Eq` decision bound (`f64` is neither).
|
|
struct IntParabola;
|
|
impl Problem for IntParabola {
|
|
type Decision = Vec<i32>;
|
|
fn objectives(&self) -> ObjectiveSpace {
|
|
ObjectiveSpace::new(vec![Objective::minimize("f")])
|
|
}
|
|
fn evaluate(&self, x: &Vec<i32>) -> Evaluation {
|
|
let v = (x[0] - 5) as f64;
|
|
Evaluation::new(vec![v * v])
|
|
}
|
|
}
|
|
|
|
/// Start every 1-D integer decision at 0.
|
|
struct IntStartAtZero;
|
|
impl Initializer<Vec<i32>> for IntStartAtZero {
|
|
fn initialize(&mut self, size: usize, _rng: &mut Rng) -> Vec<Vec<i32>> {
|
|
(0..size).map(|_| vec![0]).collect()
|
|
}
|
|
}
|
|
|
|
#[library_benchmark]
|
|
fn tabu_search_short() -> usize {
|
|
let neighbors = |x: &Vec<i32>, _rng: &mut Rng| {
|
|
vec![
|
|
vec![x[0] - 2],
|
|
vec![x[0] - 1],
|
|
vec![x[0] + 1],
|
|
vec![x[0] + 2],
|
|
]
|
|
};
|
|
let mut o = TabuSearch::new(
|
|
TabuSearchConfig {
|
|
iterations: 50,
|
|
tabu_tenure: 8,
|
|
seed: 0,
|
|
},
|
|
IntStartAtZero,
|
|
neighbors,
|
|
);
|
|
black_box(o.run(black_box(&IntParabola)).evaluations)
|
|
}
|
|
|
|
/// OneMax over 16 bits: maximize the count of `true` bits.
|
|
struct OneMax16;
|
|
impl Problem for OneMax16 {
|
|
type Decision = Vec<bool>;
|
|
fn objectives(&self) -> ObjectiveSpace {
|
|
ObjectiveSpace::new(vec![Objective::maximize("ones")])
|
|
}
|
|
fn evaluate(&self, x: &Vec<bool>) -> Evaluation {
|
|
Evaluation::new(vec![x.iter().filter(|b| **b).count() as f64])
|
|
}
|
|
}
|
|
|
|
#[library_benchmark]
|
|
fn umda_short() -> usize {
|
|
let mut o = Umda::new(UmdaConfig {
|
|
population_size: 20,
|
|
selected_size: 8,
|
|
generations: 5,
|
|
bits: 16,
|
|
seed: 0,
|
|
});
|
|
black_box(o.run(black_box(&OneMax16)).evaluations)
|
|
}
|
|
|
|
library_benchmark_group!(
|
|
name = single_objective_group;
|
|
benchmarks =
|
|
random_search_short, hill_climber_short, one_plus_one_es_short,
|
|
simulated_annealing_short, genetic_algorithm_short,
|
|
particle_swarm_short, differential_evolution_short, tlbo_short,
|
|
separable_nes_short, nelder_mead_short,
|
|
bayesian_opt_short, tpe_short, ipop_cma_es_short,
|
|
tabu_search_short, umda_short
|
|
);
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// Multi-objective sweep
|
|
// -----------------------------------------------------------------------------
|
|
|
|
fn schaffer_bounds() -> Vec<(f64, f64)> {
|
|
vec![(-3.0, 3.0)]
|
|
}
|
|
fn mo_variation() -> CompositeVariation<SimulatedBinaryCrossover, PolynomialMutation> {
|
|
let bounds = schaffer_bounds();
|
|
CompositeVariation {
|
|
crossover: SimulatedBinaryCrossover::new(bounds.clone(), 15.0, 0.5),
|
|
mutation: PolynomialMutation::new(bounds, 20.0, 1.0),
|
|
}
|
|
}
|
|
|
|
#[library_benchmark]
|
|
fn nsga3_short() -> usize {
|
|
let mut o = Nsga3::new(
|
|
Nsga3Config {
|
|
population_size: 12,
|
|
generations: 1,
|
|
reference_divisions: 11,
|
|
seed: 0,
|
|
},
|
|
RealBounds::new(schaffer_bounds()),
|
|
mo_variation(),
|
|
);
|
|
black_box(o.run(black_box(&SchafferN1)).evaluations)
|
|
}
|
|
|
|
#[library_benchmark]
|
|
fn spea2_short() -> usize {
|
|
let mut o = Spea2::new(
|
|
Spea2Config {
|
|
population_size: 10,
|
|
archive_size: 10,
|
|
generations: 1,
|
|
seed: 0,
|
|
},
|
|
RealBounds::new(schaffer_bounds()),
|
|
mo_variation(),
|
|
);
|
|
black_box(o.run(black_box(&SchafferN1)).evaluations)
|
|
}
|
|
|
|
#[library_benchmark]
|
|
fn moead_short() -> usize {
|
|
let mut o = Moead::new(
|
|
MoeadConfig {
|
|
generations: 1,
|
|
reference_divisions: 9,
|
|
neighborhood_size: 4,
|
|
seed: 0,
|
|
},
|
|
RealBounds::new(schaffer_bounds()),
|
|
mo_variation(),
|
|
);
|
|
black_box(o.run(black_box(&SchafferN1)).evaluations)
|
|
}
|
|
|
|
#[library_benchmark]
|
|
fn mopso_short() -> usize {
|
|
let mut o = Mopso::new(
|
|
MopsoConfig {
|
|
swarm_size: 10,
|
|
generations: 1,
|
|
archive_size: 10,
|
|
inertia: 0.7,
|
|
cognitive: 1.5,
|
|
social: 1.5,
|
|
seed: 0,
|
|
},
|
|
RealBounds::new(schaffer_bounds()),
|
|
);
|
|
black_box(o.run(black_box(&SchafferN1)).evaluations)
|
|
}
|
|
|
|
#[library_benchmark]
|
|
fn ibea_short() -> usize {
|
|
let mut o = Ibea::new(
|
|
IbeaConfig {
|
|
population_size: 10,
|
|
generations: 1,
|
|
kappa: 0.05,
|
|
seed: 0,
|
|
},
|
|
RealBounds::new(schaffer_bounds()),
|
|
mo_variation(),
|
|
);
|
|
black_box(o.run(black_box(&SchafferN1)).evaluations)
|
|
}
|
|
|
|
#[library_benchmark]
|
|
fn sms_emoa_short() -> usize {
|
|
let mut o = SmsEmoa::new(
|
|
SmsEmoaConfig {
|
|
population_size: 8,
|
|
generations: 5,
|
|
reference_point: vec![10.0, 10.0],
|
|
seed: 0,
|
|
},
|
|
RealBounds::new(schaffer_bounds()),
|
|
mo_variation(),
|
|
);
|
|
black_box(o.run(black_box(&SchafferN1)).evaluations)
|
|
}
|
|
|
|
#[library_benchmark]
|
|
fn hype_short() -> usize {
|
|
let mut o = Hype::new(
|
|
HypeConfig {
|
|
population_size: 10,
|
|
generations: 1,
|
|
reference_point: vec![10.0, 10.0],
|
|
mc_samples: 100,
|
|
seed: 0,
|
|
},
|
|
RealBounds::new(schaffer_bounds()),
|
|
mo_variation(),
|
|
);
|
|
black_box(o.run(black_box(&SchafferN1)).evaluations)
|
|
}
|
|
|
|
#[library_benchmark]
|
|
fn pesa2_short() -> usize {
|
|
let mut o = PesaII::new(
|
|
PesaIIConfig {
|
|
population_size: 10,
|
|
archive_size: 10,
|
|
generations: 1,
|
|
grid_divisions: 4,
|
|
seed: 0,
|
|
},
|
|
RealBounds::new(schaffer_bounds()),
|
|
mo_variation(),
|
|
);
|
|
black_box(o.run(black_box(&SchafferN1)).evaluations)
|
|
}
|
|
|
|
#[library_benchmark]
|
|
fn epsilon_moea_short() -> usize {
|
|
let mut o = EpsilonMoea::new(
|
|
EpsilonMoeaConfig {
|
|
population_size: 10,
|
|
evaluations: 30,
|
|
epsilon: vec![0.05, 0.05],
|
|
seed: 0,
|
|
},
|
|
RealBounds::new(schaffer_bounds()),
|
|
mo_variation(),
|
|
);
|
|
black_box(o.run(black_box(&SchafferN1)).evaluations)
|
|
}
|
|
|
|
#[library_benchmark]
|
|
fn age_moea_short() -> usize {
|
|
let mut o = AgeMoea::new(
|
|
AgeMoeaConfig {
|
|
population_size: 10,
|
|
generations: 1,
|
|
seed: 0,
|
|
},
|
|
RealBounds::new(schaffer_bounds()),
|
|
mo_variation(),
|
|
);
|
|
black_box(o.run(black_box(&SchafferN1)).evaluations)
|
|
}
|
|
|
|
#[library_benchmark]
|
|
fn grea_short() -> usize {
|
|
let mut o = Grea::new(
|
|
GreaConfig {
|
|
population_size: 10,
|
|
generations: 1,
|
|
grid_divisions: 4,
|
|
seed: 0,
|
|
},
|
|
RealBounds::new(schaffer_bounds()),
|
|
mo_variation(),
|
|
);
|
|
black_box(o.run(black_box(&SchafferN1)).evaluations)
|
|
}
|
|
|
|
#[library_benchmark]
|
|
fn knea_short() -> usize {
|
|
let mut o = Knea::new(
|
|
KneaConfig {
|
|
population_size: 10,
|
|
generations: 1,
|
|
seed: 0,
|
|
},
|
|
RealBounds::new(schaffer_bounds()),
|
|
mo_variation(),
|
|
);
|
|
black_box(o.run(black_box(&SchafferN1)).evaluations)
|
|
}
|
|
|
|
#[library_benchmark]
|
|
fn rvea_short() -> usize {
|
|
let mut o = Rvea::new(
|
|
RveaConfig {
|
|
population_size: 10,
|
|
generations: 1,
|
|
reference_divisions: 9,
|
|
alpha: 2.0,
|
|
seed: 0,
|
|
},
|
|
RealBounds::new(schaffer_bounds()),
|
|
mo_variation(),
|
|
);
|
|
black_box(o.run(black_box(&SchafferN1)).evaluations)
|
|
}
|
|
|
|
#[library_benchmark]
|
|
fn paes_short() -> usize {
|
|
let mut o = Paes::new(
|
|
PaesConfig {
|
|
iterations: 30,
|
|
archive_size: 10,
|
|
seed: 0,
|
|
},
|
|
RealBounds::new(schaffer_bounds()),
|
|
GaussianMutation { sigma: 0.1 },
|
|
);
|
|
black_box(o.run(black_box(&SchafferN1)).evaluations)
|
|
}
|
|
|
|
library_benchmark_group!(
|
|
name = multi_objective_group;
|
|
benchmarks =
|
|
nsga3_short, spea2_short, moead_short, mopso_short, ibea_short,
|
|
sms_emoa_short, hype_short, pesa2_short, epsilon_moea_short,
|
|
age_moea_short, grea_short, knea_short, rvea_short, paes_short
|
|
);
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// Permutation operator micro-benchmarks
|
|
// -----------------------------------------------------------------------------
|
|
|
|
fn perm_parent(n: usize) -> Vec<usize> {
|
|
(0..n).collect()
|
|
}
|
|
|
|
/// Reversed `[0..n)`: same value multiset as `perm_parent`, shares no oriented
|
|
/// edges with it — a stress input for the edge-based crossovers.
|
|
fn perm_parent_rev(n: usize) -> Vec<usize> {
|
|
(0..n).rev().collect()
|
|
}
|
|
|
|
#[library_benchmark]
|
|
#[bench::n_30(30)]
|
|
#[bench::n_100(100)]
|
|
fn shuffled_permutation_init(n: usize) -> Vec<Vec<usize>> {
|
|
let mut rng = rng_from_seed(0);
|
|
let mut init = ShuffledPermutation { n };
|
|
black_box(init.initialize(black_box(16), black_box(&mut rng)))
|
|
}
|
|
|
|
#[library_benchmark]
|
|
#[bench::n_30(30)]
|
|
#[bench::n_100(100)]
|
|
fn shuffled_multiset_permutation_init(n: usize) -> Vec<Vec<usize>> {
|
|
let mut rng = rng_from_seed(0);
|
|
let mut init = ShuffledMultisetPermutation::new(vec![5; n]);
|
|
black_box(init.initialize(black_box(16), black_box(&mut rng)))
|
|
}
|
|
|
|
#[library_benchmark]
|
|
#[bench::n_30(30)]
|
|
#[bench::n_100(100)]
|
|
fn swap_mutation_vary(n: usize) -> Vec<Vec<usize>> {
|
|
let parent = perm_parent(n);
|
|
let mut rng = rng_from_seed(1);
|
|
let mut op = SwapMutation;
|
|
black_box(op.vary(
|
|
black_box(std::slice::from_ref(&parent)),
|
|
black_box(&mut rng),
|
|
))
|
|
}
|
|
|
|
#[library_benchmark]
|
|
#[bench::n_30(30)]
|
|
#[bench::n_100(100)]
|
|
fn inversion_mutation_vary(n: usize) -> Vec<Vec<usize>> {
|
|
let parent = perm_parent(n);
|
|
let mut rng = rng_from_seed(1);
|
|
let mut op = InversionMutation;
|
|
black_box(op.vary(
|
|
black_box(std::slice::from_ref(&parent)),
|
|
black_box(&mut rng),
|
|
))
|
|
}
|
|
|
|
#[library_benchmark]
|
|
#[bench::n_30(30)]
|
|
#[bench::n_100(100)]
|
|
fn insertion_mutation_vary(n: usize) -> Vec<Vec<usize>> {
|
|
let parent = perm_parent(n);
|
|
let mut rng = rng_from_seed(1);
|
|
let mut op = InsertionMutation;
|
|
black_box(op.vary(
|
|
black_box(std::slice::from_ref(&parent)),
|
|
black_box(&mut rng),
|
|
))
|
|
}
|
|
|
|
#[library_benchmark]
|
|
#[bench::n_30(30)]
|
|
#[bench::n_100(100)]
|
|
fn scramble_mutation_vary(n: usize) -> Vec<Vec<usize>> {
|
|
let parent = perm_parent(n);
|
|
let mut rng = rng_from_seed(1);
|
|
let mut op = ScrambleMutation;
|
|
black_box(op.vary(
|
|
black_box(std::slice::from_ref(&parent)),
|
|
black_box(&mut rng),
|
|
))
|
|
}
|
|
|
|
#[library_benchmark]
|
|
#[bench::n_30(30)]
|
|
#[bench::n_100(100)]
|
|
fn order_crossover_vary(n: usize) -> Vec<Vec<usize>> {
|
|
let parents = [perm_parent(n), perm_parent_rev(n)];
|
|
let mut rng = rng_from_seed(2);
|
|
let mut op = OrderCrossover;
|
|
black_box(op.vary(black_box(&parents), black_box(&mut rng)))
|
|
}
|
|
|
|
#[library_benchmark]
|
|
#[bench::n_30(30)]
|
|
#[bench::n_100(100)]
|
|
fn pmx_crossover_vary(n: usize) -> Vec<Vec<usize>> {
|
|
let parents = [perm_parent(n), perm_parent_rev(n)];
|
|
let mut rng = rng_from_seed(2);
|
|
let mut op = PartiallyMappedCrossover;
|
|
black_box(op.vary(black_box(&parents), black_box(&mut rng)))
|
|
}
|
|
|
|
#[library_benchmark]
|
|
#[bench::n_30(30)]
|
|
#[bench::n_100(100)]
|
|
fn cycle_crossover_vary(n: usize) -> Vec<Vec<usize>> {
|
|
let parents = [perm_parent(n), perm_parent_rev(n)];
|
|
let mut rng = rng_from_seed(2);
|
|
let mut op = CycleCrossover;
|
|
black_box(op.vary(black_box(&parents), black_box(&mut rng)))
|
|
}
|
|
|
|
#[library_benchmark]
|
|
#[bench::n_30(30)]
|
|
#[bench::n_100(100)]
|
|
fn edge_recombination_crossover_vary(n: usize) -> Vec<Vec<usize>> {
|
|
let parents = [perm_parent(n), perm_parent_rev(n)];
|
|
let mut rng = rng_from_seed(2);
|
|
let mut op = EdgeRecombinationCrossover;
|
|
black_box(op.vary(black_box(&parents), black_box(&mut rng)))
|
|
}
|
|
|
|
library_benchmark_group!(
|
|
name = permutation_ops_group;
|
|
benchmarks =
|
|
shuffled_permutation_init, shuffled_multiset_permutation_init,
|
|
swap_mutation_vary, inversion_mutation_vary, insertion_mutation_vary,
|
|
scramble_mutation_vary, order_crossover_vary, pmx_crossover_vary,
|
|
cycle_crossover_vary, edge_recombination_crossover_vary
|
|
);
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// Un-benchmarked operators from the binary / real / repair families
|
|
// -----------------------------------------------------------------------------
|
|
|
|
#[library_benchmark]
|
|
fn bit_flip_mutation_vary() -> Vec<Vec<bool>> {
|
|
let parent: Vec<bool> = (0..64).map(|i| i % 2 == 0).collect();
|
|
let mut rng = rng_from_seed(3);
|
|
let mut op = BitFlipMutation {
|
|
probability: 1.0 / 64.0,
|
|
};
|
|
black_box(op.vary(
|
|
black_box(std::slice::from_ref(&parent)),
|
|
black_box(&mut rng),
|
|
))
|
|
}
|
|
|
|
#[library_benchmark]
|
|
fn levy_mutation_vary() -> Vec<Vec<f64>> {
|
|
let parent = vec![0.0_f64; 16];
|
|
let mut rng = rng_from_seed(3);
|
|
let mut op = LevyMutation::new(1.5, 0.1, vec![(-5.0, 5.0); 16]);
|
|
black_box(op.vary(
|
|
black_box(std::slice::from_ref(&parent)),
|
|
black_box(&mut rng),
|
|
))
|
|
}
|
|
|
|
#[library_benchmark]
|
|
fn bounded_gaussian_mutation_vary() -> Vec<Vec<f64>> {
|
|
let parent = vec![0.0_f64; 16];
|
|
let mut rng = rng_from_seed(3);
|
|
let mut op = BoundedGaussianMutation::new(0.3, vec![(-1.0, 1.0); 16]);
|
|
black_box(op.vary(
|
|
black_box(std::slice::from_ref(&parent)),
|
|
black_box(&mut rng),
|
|
))
|
|
}
|
|
|
|
#[library_benchmark]
|
|
fn clamp_to_bounds_repair() -> Vec<f64> {
|
|
let mut x: Vec<f64> = (0..32).map(|i| (i as f64) - 16.0).collect();
|
|
let mut op = ClampToBounds::new(vec![(-1.0, 1.0); 32]);
|
|
op.repair(black_box(&mut x));
|
|
black_box(x)
|
|
}
|
|
|
|
#[library_benchmark]
|
|
fn project_to_simplex_repair() -> Vec<f64> {
|
|
// 32-dim mixed-sign vector; exercises the sort-based projection path.
|
|
let mut x: Vec<f64> = (0..32).map(|i| ((i * 7 % 13) as f64) - 6.0).collect();
|
|
let mut op = ProjectToSimplex::new(1.0);
|
|
op.repair(black_box(&mut x));
|
|
black_box(x)
|
|
}
|
|
|
|
library_benchmark_group!(
|
|
name = variation_ops_group;
|
|
benchmarks =
|
|
bit_flip_mutation_vary, levy_mutation_vary, bounded_gaussian_mutation_vary,
|
|
clamp_to_bounds_repair, project_to_simplex_repair
|
|
);
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// Combinatorial / sequencing end-to-end benches
|
|
// -----------------------------------------------------------------------------
|
|
|
|
const TSP_N: usize = 15;
|
|
|
|
/// Deterministic pseudo-scattered city coordinates. The bench only needs a
|
|
/// stable distance matrix, not a known optimum.
|
|
fn tsp_coords() -> Vec<(f64, f64)> {
|
|
(0..TSP_N)
|
|
.map(|i| {
|
|
let x = ((i * 37) % 100) as f64;
|
|
let y = ((i * 53 + 11) % 100) as f64;
|
|
(x, y)
|
|
})
|
|
.collect()
|
|
}
|
|
|
|
fn tsp_distance_matrix() -> Vec<Vec<f64>> {
|
|
let c = tsp_coords();
|
|
let n = c.len();
|
|
let mut d = vec![vec![0.0_f64; n]; n];
|
|
for i in 0..n {
|
|
for j in 0..n {
|
|
if i != j {
|
|
let dx = c[i].0 - c[j].0;
|
|
let dy = c[i].1 - c[j].1;
|
|
d[i][j] = (dx * dx + dy * dy).sqrt();
|
|
}
|
|
}
|
|
}
|
|
d
|
|
}
|
|
|
|
/// Single-objective TSP over a precomputed distance matrix.
|
|
struct TspProblem {
|
|
distances: Vec<Vec<f64>>,
|
|
}
|
|
impl Problem for TspProblem {
|
|
type Decision = Vec<usize>;
|
|
fn objectives(&self) -> ObjectiveSpace {
|
|
ObjectiveSpace::new(vec![Objective::minimize("length")])
|
|
}
|
|
fn evaluate(&self, tour: &Vec<usize>) -> Evaluation {
|
|
let n = tour.len();
|
|
let mut len = 0.0;
|
|
for i in 0..n {
|
|
len += self.distances[tour[i]][tour[(i + 1) % n]];
|
|
}
|
|
Evaluation::new(vec![len])
|
|
}
|
|
}
|
|
|
|
/// Bi-objective TSP: two distance matrices over the same city set.
|
|
struct BiTspProblem {
|
|
dist_a: Vec<Vec<f64>>,
|
|
dist_b: Vec<Vec<f64>>,
|
|
}
|
|
impl Problem for BiTspProblem {
|
|
type Decision = Vec<usize>;
|
|
fn objectives(&self) -> ObjectiveSpace {
|
|
ObjectiveSpace::new(vec![
|
|
Objective::minimize("length_a"),
|
|
Objective::minimize("length_b"),
|
|
])
|
|
}
|
|
fn evaluate(&self, tour: &Vec<usize>) -> Evaluation {
|
|
let n = tour.len();
|
|
let (mut la, mut lb) = (0.0, 0.0);
|
|
for i in 0..n {
|
|
let (u, v) = (tour[i], tour[(i + 1) % n]);
|
|
la += self.dist_a[u][v];
|
|
lb += self.dist_b[u][v];
|
|
}
|
|
Evaluation::new(vec![la, lb])
|
|
}
|
|
}
|
|
|
|
#[library_benchmark]
|
|
fn tsp_nsga2_short() -> usize {
|
|
let dist_a = tsp_distance_matrix();
|
|
// Second objective: a distinct symmetric matrix with a zero diagonal.
|
|
let dist_b: Vec<Vec<f64>> = dist_a
|
|
.iter()
|
|
.enumerate()
|
|
.map(|(i, row)| {
|
|
row.iter()
|
|
.enumerate()
|
|
.map(|(j, &d)| if i == j { 0.0 } else { d * 0.5 + 3.0 })
|
|
.collect()
|
|
})
|
|
.collect();
|
|
let problem = BiTspProblem { dist_a, dist_b };
|
|
let mut o = Nsga2::new(
|
|
Nsga2Config {
|
|
population_size: 20,
|
|
generations: 3,
|
|
seed: 0,
|
|
},
|
|
ShuffledPermutation { n: TSP_N },
|
|
CompositeVariation {
|
|
crossover: OrderCrossover,
|
|
mutation: InversionMutation,
|
|
},
|
|
);
|
|
black_box(o.run(black_box(&problem)).evaluations)
|
|
}
|
|
|
|
#[library_benchmark]
|
|
fn ant_colony_tsp_short() -> usize {
|
|
let distances = tsp_distance_matrix();
|
|
let problem = TspProblem {
|
|
distances: distances.clone(),
|
|
};
|
|
let mut o = AntColonyTsp::new(
|
|
AntColonyTspConfig {
|
|
ants: 8,
|
|
generations: 3,
|
|
alpha: 1.0,
|
|
beta: 2.0,
|
|
evaporation: 0.5,
|
|
deposit: 1.0,
|
|
initial_pheromone: 1.0,
|
|
seed: 0,
|
|
},
|
|
distances,
|
|
);
|
|
black_box(o.run(black_box(&problem)).evaluations)
|
|
}
|
|
|
|
const JSS_JOBS: usize = 6;
|
|
const JSS_MACHINES: usize = 6;
|
|
|
|
/// FT06 (Fisher & Thompson 1963) routing — machine id of the k-th operation
|
|
/// of job j.
|
|
const FT06_MACHINE: [[usize; JSS_MACHINES]; JSS_JOBS] = [
|
|
[2, 0, 1, 3, 5, 4],
|
|
[1, 2, 4, 5, 0, 3],
|
|
[2, 3, 5, 0, 1, 4],
|
|
[1, 0, 2, 3, 4, 5],
|
|
[2, 1, 4, 5, 0, 3],
|
|
[1, 3, 5, 0, 4, 2],
|
|
];
|
|
|
|
/// FT06 processing times — duration of the k-th operation of job j.
|
|
const FT06_TIME: [[f64; JSS_MACHINES]; JSS_JOBS] = [
|
|
[1.0, 3.0, 6.0, 7.0, 3.0, 6.0],
|
|
[8.0, 5.0, 10.0, 10.0, 10.0, 4.0],
|
|
[5.0, 4.0, 8.0, 9.0, 1.0, 7.0],
|
|
[5.0, 5.0, 5.0, 3.0, 8.0, 9.0],
|
|
[9.0, 3.0, 5.0, 4.0, 3.0, 1.0],
|
|
[3.0, 3.0, 9.0, 10.0, 4.0, 1.0],
|
|
];
|
|
|
|
/// Bi-objective FT06 job-shop scheduling: f1 = makespan, f2 = total flow time.
|
|
struct Ft06Problem;
|
|
impl Problem for Ft06Problem {
|
|
type Decision = Vec<usize>;
|
|
fn objectives(&self) -> ObjectiveSpace {
|
|
ObjectiveSpace::new(vec![
|
|
Objective::minimize("makespan"),
|
|
Objective::minimize("total_flow_time"),
|
|
])
|
|
}
|
|
fn evaluate(&self, schedule: &Vec<usize>) -> Evaluation {
|
|
let mut job_next = [0_usize; JSS_JOBS];
|
|
let mut job_clock = [0.0_f64; JSS_JOBS];
|
|
let mut machine_clock = [0.0_f64; JSS_MACHINES];
|
|
for &job in schedule {
|
|
let k = job_next[job];
|
|
let m = FT06_MACHINE[job][k];
|
|
let t = FT06_TIME[job][k];
|
|
let start = job_clock[job].max(machine_clock[m]);
|
|
let end = start + t;
|
|
job_clock[job] = end;
|
|
machine_clock[m] = end;
|
|
job_next[job] = k + 1;
|
|
}
|
|
let makespan = machine_clock.iter().cloned().fold(0.0_f64, f64::max);
|
|
let flow_time: f64 = job_clock.iter().sum();
|
|
Evaluation::new(vec![makespan, flow_time])
|
|
}
|
|
}
|
|
|
|
/// Precedence-Order Crossover — multiset-preserving crossover for the
|
|
/// operation-string JSS encoding. Trimmed from `examples/mo_jss_la01.rs`;
|
|
/// the strict-permutation crossovers cannot be used on multiset encodings.
|
|
#[derive(Debug, Clone, Copy, Default)]
|
|
struct PrecedenceOrderCrossover;
|
|
impl Variation<Vec<usize>> for PrecedenceOrderCrossover {
|
|
fn vary(&mut self, parents: &[Vec<usize>], rng: &mut Rng) -> Vec<Vec<usize>> {
|
|
assert!(parents.len() >= 2, "POX requires 2 parents");
|
|
let (p1, p2) = (&parents[0], &parents[1]);
|
|
let mut in_j1 = [false; JSS_JOBS];
|
|
loop {
|
|
for slot in &mut in_j1 {
|
|
*slot = rng.random_bool(0.5);
|
|
}
|
|
let c = in_j1.iter().filter(|&&b| b).count();
|
|
if c > 0 && c < JSS_JOBS {
|
|
break;
|
|
}
|
|
}
|
|
vec![pox_child(p1, p2, &in_j1), pox_child(p2, p1, &in_j1)]
|
|
}
|
|
}
|
|
fn pox_child(donor: &[usize], filler: &[usize], in_donor_set: &[bool]) -> Vec<usize> {
|
|
let n = donor.len();
|
|
let mut child = vec![usize::MAX; n];
|
|
for k in 0..n {
|
|
if in_donor_set[donor[k]] {
|
|
child[k] = donor[k];
|
|
}
|
|
}
|
|
let mut fill_idx = 0;
|
|
for &v in filler {
|
|
if !in_donor_set[v] {
|
|
while fill_idx < n && child[fill_idx] != usize::MAX {
|
|
fill_idx += 1;
|
|
}
|
|
child[fill_idx] = v;
|
|
fill_idx += 1;
|
|
}
|
|
}
|
|
child
|
|
}
|
|
|
|
#[library_benchmark]
|
|
fn jss_nsga2_short() -> usize {
|
|
let mut o = Nsga2::new(
|
|
Nsga2Config {
|
|
population_size: 20,
|
|
generations: 3,
|
|
seed: 0,
|
|
},
|
|
ShuffledMultisetPermutation::new(vec![JSS_MACHINES; JSS_JOBS]),
|
|
CompositeVariation {
|
|
crossover: PrecedenceOrderCrossover,
|
|
mutation: InsertionMutation,
|
|
},
|
|
);
|
|
black_box(o.run(black_box(&Ft06Problem)).evaluations)
|
|
}
|
|
|
|
const KNAPSACK_N: usize = 20;
|
|
|
|
const KP_PROFIT_A: [f64; KNAPSACK_N] = [
|
|
61.0, 17.0, 92.0, 49.0, 73.0, 28.0, 84.0, 36.0, 55.0, 78.0, 23.0, 91.0, 12.0, 67.0, 45.0, 58.0,
|
|
33.0, 71.0, 14.0, 26.0,
|
|
];
|
|
const KP_PROFIT_B: [f64; KNAPSACK_N] = [
|
|
24.0, 81.0, 16.0, 67.0, 29.0, 73.0, 41.0, 60.0, 52.0, 19.0, 77.0, 34.0, 95.0, 22.0, 71.0, 88.0,
|
|
56.0, 27.0, 64.0, 90.0,
|
|
];
|
|
const KP_WEIGHT: [f64; KNAPSACK_N] = [
|
|
35.0, 58.0, 22.0, 71.0, 14.0, 86.0, 31.0, 53.0, 78.0, 19.0, 44.0, 16.0, 67.0, 88.0, 25.0, 51.0,
|
|
33.0, 74.0, 12.0, 47.0,
|
|
];
|
|
|
|
/// Bi-objective 0/1 knapsack with a penalty-based capacity constraint.
|
|
struct KnapsackProblem {
|
|
capacity: f64,
|
|
}
|
|
impl Problem for KnapsackProblem {
|
|
type Decision = Vec<bool>;
|
|
fn objectives(&self) -> ObjectiveSpace {
|
|
ObjectiveSpace::new(vec![
|
|
Objective::maximize("profit_a"),
|
|
Objective::maximize("profit_b"),
|
|
])
|
|
}
|
|
fn evaluate(&self, take: &Vec<bool>) -> Evaluation {
|
|
let (mut pa, mut pb, mut w) = (0.0, 0.0, 0.0);
|
|
for (i, &t) in take.iter().enumerate() {
|
|
if t {
|
|
pa += KP_PROFIT_A[i];
|
|
pb += KP_PROFIT_B[i];
|
|
w += KP_WEIGHT[i];
|
|
}
|
|
}
|
|
let penalty = 1000.0 * (w - self.capacity).max(0.0);
|
|
Evaluation::new(vec![pa - penalty, pb - penalty])
|
|
}
|
|
}
|
|
|
|
/// Random binary initializer — each bit 50/50 independently.
|
|
#[derive(Debug, Clone, Copy)]
|
|
struct RandomBinary {
|
|
n: usize,
|
|
}
|
|
impl Initializer<Vec<bool>> for RandomBinary {
|
|
fn initialize(&mut self, size: usize, rng: &mut Rng) -> Vec<Vec<bool>> {
|
|
(0..size)
|
|
.map(|_| (0..self.n).map(|_| rng.random_bool(0.5)).collect())
|
|
.collect()
|
|
}
|
|
}
|
|
|
|
/// One-point crossover for binary chromosomes. Trimmed from
|
|
/// `examples/mo_knapsack.rs`.
|
|
#[derive(Debug, Clone, Copy, Default)]
|
|
struct OnePointCrossoverBool;
|
|
impl Variation<Vec<bool>> for OnePointCrossoverBool {
|
|
fn vary(&mut self, parents: &[Vec<bool>], rng: &mut Rng) -> Vec<Vec<bool>> {
|
|
assert!(
|
|
parents.len() >= 2,
|
|
"OnePointCrossoverBool requires 2 parents"
|
|
);
|
|
let (p1, p2) = (&parents[0], &parents[1]);
|
|
let n = p1.len();
|
|
if n < 2 {
|
|
return vec![p1.clone(), p2.clone()];
|
|
}
|
|
let cut = rng.random_range(1..n);
|
|
let mut c1 = Vec::with_capacity(n);
|
|
let mut c2 = Vec::with_capacity(n);
|
|
c1.extend_from_slice(&p1[..cut]);
|
|
c1.extend_from_slice(&p2[cut..]);
|
|
c2.extend_from_slice(&p2[..cut]);
|
|
c2.extend_from_slice(&p1[cut..]);
|
|
vec![c1, c2]
|
|
}
|
|
}
|
|
|
|
#[library_benchmark]
|
|
fn knapsack_nsga2_short() -> usize {
|
|
let capacity = 0.5 * KP_WEIGHT.iter().sum::<f64>();
|
|
let problem = KnapsackProblem { capacity };
|
|
let mut o = Nsga2::new(
|
|
Nsga2Config {
|
|
population_size: 20,
|
|
generations: 3,
|
|
seed: 0,
|
|
},
|
|
RandomBinary { n: KNAPSACK_N },
|
|
CompositeVariation {
|
|
crossover: OnePointCrossoverBool,
|
|
mutation: BitFlipMutation {
|
|
probability: 1.0 / KNAPSACK_N as f64,
|
|
},
|
|
},
|
|
);
|
|
black_box(o.run(black_box(&problem)).evaluations)
|
|
}
|
|
|
|
library_benchmark_group!(
|
|
name = combinatorial_group;
|
|
benchmarks =
|
|
tsp_nsga2_short, ant_colony_tsp_short, jss_nsga2_short, knapsack_nsga2_short
|
|
);
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// Multi-fidelity (Hyperband)
|
|
// -----------------------------------------------------------------------------
|
|
|
|
/// Multi-fidelity 2-D sphere: higher budget shrinks an additive residual, so
|
|
/// the loss is budget-monotone the way Hyperband expects. Deterministic.
|
|
struct MultiFidelitySphere;
|
|
impl PartialProblem for MultiFidelitySphere {
|
|
type Decision = Vec<f64>;
|
|
fn objectives(&self) -> ObjectiveSpace {
|
|
ObjectiveSpace::new(vec![Objective::minimize("loss")])
|
|
}
|
|
fn evaluate_at_budget(&self, x: &Vec<f64>, budget: f64) -> Evaluation {
|
|
let true_f: f64 = x.iter().map(|v| v * v).sum();
|
|
let residual = 1.0 / (budget + 1.0);
|
|
Evaluation::new(vec![true_f + residual])
|
|
}
|
|
}
|
|
|
|
#[library_benchmark]
|
|
fn hyperband_short() -> usize {
|
|
let mut o = Hyperband::new(
|
|
HyperbandConfig {
|
|
max_budget: 27.0,
|
|
eta: 3.0,
|
|
max_brackets: 3,
|
|
seed: 0,
|
|
},
|
|
RealBounds::new(vec![(-5.0, 5.0); 2]),
|
|
);
|
|
black_box(o.run(black_box(&MultiFidelitySphere)).evaluations)
|
|
}
|
|
|
|
library_benchmark_group!(
|
|
name = multi_fidelity_group;
|
|
benchmarks = hyperband_short
|
|
);
|
|
|
|
main!(
|
|
library_benchmark_groups = pareto_group,
|
|
algorithm_group,
|
|
single_objective_group,
|
|
multi_objective_group,
|
|
permutation_ops_group,
|
|
variation_ops_group,
|
|
combinatorial_group,
|
|
multi_fidelity_group
|
|
);
|