use crate::core::candidate::Candidate;
use crate::core::objective::ObjectiveSpace;
pub fn hypervolume_2d<D>(
front: &[Candidate<D>],
objectives: &ObjectiveSpace,
reference_point: [f64; 2],
) -> f64 {
assert_eq!(
objectives.len(),
2,
"hypervolume_2d requires exactly 2 objectives",
);
if front.is_empty() {
return 0.0;
}
let mut points: Vec<[f64; 2]> = front
.iter()
.filter_map(|c| {
let m = objectives.as_minimization(&c.evaluation.objectives);
let p = [m[0], m[1]];
if p[0] < reference_point[0] && p[1] < reference_point[1] {
Some(p)
} else {
None
}
})
.collect();
if points.is_empty() {
return 0.0;
}
points.sort_by(|a, b| a[0].partial_cmp(&b[0]).unwrap_or(std::cmp::Ordering::Equal));
let mut area = 0.0;
let mut last_y = reference_point[1];
for p in &points {
if p[1] >= last_y {
continue;
}
let width = reference_point[0] - p[0];
let height = last_y - p[1];
area += width * height;
last_y = p[1];
}
area
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::evaluation::Evaluation;
use crate::core::objective::Objective;
fn cand(obj: Vec<f64>) -> Candidate<()> {
Candidate::new((), Evaluation::new(obj))
}
fn space_min2() -> ObjectiveSpace {
ObjectiveSpace::new(vec![
Objective::minimize("f1"),
Objective::minimize("f2"),
])
}
#[test]
fn known_three_point_front_area() {
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 hv = hypervolume_2d(&front, &s, [4.0, 4.0]);
assert!((hv - 6.0).abs() < 1e-12, "expected 6.0, got {hv}");
}
#[test]
fn empty_front_is_zero() {
let s = space_min2();
let front: [Candidate<()>; 0] = [];
assert_eq!(hypervolume_2d(&front, &s, [10.0, 10.0]), 0.0);
}
#[test]
fn point_not_dominating_reference_skipped() {
let s = space_min2();
let front = [cand(vec![2.0, 0.5])];
assert_eq!(hypervolume_2d(&front, &s, [1.0, 1.0]), 0.0);
}
#[test]
fn maximize_axis_handled_via_orientation() {
let s = ObjectiveSpace::new(vec![
Objective::minimize("cost"),
Objective::maximize("score"),
]);
let front = [cand(vec![1.0, 0.9])];
let hv = hypervolume_2d(&front, &s, [2.0, 0.0]);
assert!((hv - 0.9).abs() < 1e-12);
}
#[test]
#[should_panic(expected = "exactly 2 objectives")]
fn panics_on_non_2d() {
let s = ObjectiveSpace::new(vec![Objective::minimize("only")]);
let front = [cand(vec![1.0])];
let _ = hypervolume_2d(&front, &s, [10.0, 10.0]);
}
}