use forge_core::problem::func;
use forge_core::testfn::Sphere;
use forge_core::{
Algorithm, CmaEs, Dds, De, LShade, LSrtde, Moead, NsgaII, NsgaIII, Optimizer, Padds, Pso,
RestartCmaEs, Sce, SmsEmoa, Termination,
};
fn all_single_objective() -> Vec<(&'static str, Algorithm)> {
vec![
("dds", Algorithm::Dds(Dds::default())),
("sce", Algorithm::Sce(Sce::default())),
("de", Algorithm::De(De::default())),
("lshade", Algorithm::LShade(LShade::default())),
("lsrtde", Algorithm::LSrtde(LSrtde::default())),
("pso", Algorithm::Pso(Pso::default())),
("cmaes", Algorithm::CmaEs(CmaEs::default())),
("bipop", Algorithm::RestartCmaEs(RestartCmaEs::bipop())),
]
}
#[test]
fn budget_is_exact_for_every_optimizer() {
let problem = Sphere::new(5);
for (name, algo) in all_single_objective() {
for budget in [1usize, 5, 7, 37, 100, 1013] {
let r = algo.optimize(&problem, &Termination::budget(budget));
assert_eq!(
r.evaluations, budget,
"{name} spent {} evaluations on a budget of {budget}",
r.evaluations
);
}
}
}
#[test]
fn budget_is_exact_for_multiobjective() {
use forge_core::testfn::Zdt1;
let problem = Zdt1::new(6);
for budget in [1usize, 57, 250, 1003] {
let a = NsgaII::default().optimize(&problem, &Termination::budget(budget));
assert_eq!(a.evaluations, budget, "NsgaII spent {}", a.evaluations);
let b = NsgaIII::default().optimize(&problem, &Termination::budget(budget));
assert_eq!(b.evaluations, budget, "NsgaIII spent {}", b.evaluations);
let c = SmsEmoa::default().optimize(&problem, &Termination::budget(budget));
assert_eq!(c.evaluations, budget, "SmsEmoa spent {}", c.evaluations);
let d = Moead::default().optimize(&problem, &Termination::budget(budget));
assert_eq!(d.evaluations, budget, "Moead spent {}", d.evaluations);
let e = Padds::default().optimize(&problem, &Termination::budget(budget));
assert_eq!(e.evaluations, budget, "Padds spent {}", e.evaluations);
}
}
#[test]
fn all_nan_objective_terminates_cleanly() {
let problem = func(vec![(-1.0, 1.0); 3], |_| f64::NAN);
for (name, algo) in all_single_objective() {
let r = algo.optimize(&problem, &Termination::budget(500));
assert_eq!(r.evaluations, 500, "{name}");
assert!(
r.best_value().is_infinite(),
"{name}: all-NaN objective must report +inf, got {}",
r.best_value()
);
}
}
#[test]
fn mostly_nan_objective_does_not_hang_adaptive_de() {
let problem = func(vec![(-5.0, 5.0); 3], |x| {
let s: f64 = x.iter().map(|v| v * v).sum();
if s < 1.0 {
s
} else {
f64::NAN
}
});
for algo in [
Algorithm::LShade(LShade::default()),
Algorithm::LSrtde(LSrtde::default()),
] {
let r = algo.optimize(&problem, &Termination::budget(3000));
assert_eq!(r.evaluations, 3000);
assert!(r.best_value().is_finite(), "got {}", r.best_value());
}
}
#[test]
#[should_panic(expected = "invalid problem")]
fn inverted_bounds_panic() {
let bad = func(vec![(1.0, -1.0)], |x| x[0]);
Dds::default().optimize(&bad, &Termination::budget(10));
}
#[test]
#[should_panic(expected = "invalid problem")]
fn zero_dimensional_problem_panics() {
let bad = func(vec![], |_| 0.0);
De::default().optimize(&bad, &Termination::budget(10));
}
#[test]
#[should_panic(expected = "init has length")]
fn dds_rejects_wrong_length_start() {
let p = Sphere::new(3);
Dds::default().optimize_from(&p, &Termination::budget(10), Some(&[0.0, 0.0]));
}
#[test]
#[should_panic(expected = "invalid problem")]
fn short_objective_vector_panics() {
let bad = forge_core::problem::multi_func(vec![(0.0, 1.0)], 3, |x| vec![x[0], 1.0 - x[0]]);
NsgaII::default().optimize(&bad, &Termination::budget(100));
}