use forge_core::problem::multi_func;
use forge_core::testfn::Zdt1;
use forge_core::{NsgaII, Termination};
fn mean_distance_to_zdt1_front(front: &forge_core::ParetoFront) -> f64 {
let mut total = 0.0;
for o in front.objective_vectors() {
let (f1, f2) = (o[0], o[1]);
total += (f2 - Zdt1::front_f2(f1)).abs();
}
total / front.len() as f64
}
#[test]
fn nsga2_converges_to_zdt1_front() {
let problem = Zdt1::new(10);
let nsga = NsgaII {
pop_size: 100,
..NsgaII::default()
};
let front = nsga.optimize(&problem, &Termination::budget(20_000));
assert!(front.len() >= 50, "front too small: {}", front.len());
let gd = mean_distance_to_zdt1_front(&front);
assert!(gd < 0.02, "mean distance to ZDT1 front too high: {gd}");
let f1s: Vec<f64> = front.objective_vectors().map(|o| o[0]).collect();
let (min, max) = f1s
.iter()
.fold((f64::INFINITY, f64::NEG_INFINITY), |(lo, hi), &v| {
(lo.min(v), hi.max(v))
});
assert!(min < 0.1 && max > 0.9, "poor spread: f1 in [{min}, {max}]");
}
#[test]
fn nsga2_front_is_internally_non_dominated() {
let problem = Zdt1::new(8);
let front = NsgaII::default().optimize(&problem, &Termination::budget(8000));
let objs: Vec<&[f64]> = front.objective_vectors().collect();
for (i, a) in objs.iter().enumerate() {
for (j, b) in objs.iter().enumerate() {
if i == j {
continue;
}
let dominates = a[0] <= b[0] && a[1] <= b[1] && (a[0] < b[0] || a[1] < b[1]);
assert!(!dominates, "solution {i} dominates {j} on the front");
}
}
}
#[test]
fn nsga2_is_deterministic() {
let problem = Zdt1::new(6);
let t = Termination::budget(5000);
let a = NsgaII::default().optimize(&problem, &t);
let b = NsgaII::default().optimize(&problem, &t);
assert_eq!(a.solutions, b.solutions);
assert_eq!(a.evaluations, b.evaluations);
}
#[test]
fn nsga2_solves_schaffer_n1() {
let sch = multi_func(vec![(-5.0, 5.0)], 2, |x| {
vec![x[0] * x[0], (x[0] - 2.0).powi(2)]
});
let front = NsgaII {
pop_size: 60,
..NsgaII::default()
}
.optimize(&sch, &Termination::budget(6000));
for s in &front.solutions {
assert!(
(-0.05..=2.05).contains(&s.x[0]),
"x outside Pareto set: {}",
s.x[0]
);
}
}
#[test]
fn nsga2_respects_budget_roughly() {
let front = NsgaII::default().optimize(&Zdt1::new(5), &Termination::budget(3000));
assert!(
front.evaluations < 3000 + 100,
"spent {}",
front.evaluations
);
}