Files
heuropt/src/pareto/reference_points.rs
T

109 lines
3.1 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! Structured reference-point generators on the unit simplex.
/// DasDennis reference points: all compositions of `divisions` into
/// `num_objectives` non-negative integer parts, divided by `divisions`.
///
/// Returns `binomial(divisions + num_objectives - 1, num_objectives - 1)`
/// points, each a `Vec<f64>` of length `num_objectives` summing to `1.0`.
///
/// This is the canonical NSGA-III / MOEA/D weight-vector generator.
///
/// # Panics
/// If `num_objectives == 0`.
pub fn das_dennis(num_objectives: usize, divisions: usize) -> Vec<Vec<f64>> {
assert!(
num_objectives > 0,
"das_dennis requires num_objectives >= 1"
);
let mut out = Vec::new();
let mut current = Vec::with_capacity(num_objectives);
recurse(num_objectives, divisions, divisions, &mut current, &mut out);
out
}
fn recurse(
remaining_axes: usize,
remaining_units: usize,
total: usize,
current: &mut Vec<usize>,
out: &mut Vec<Vec<f64>>,
) {
if remaining_axes == 1 {
current.push(remaining_units);
let scale = (total as f64).max(1.0);
out.push(current.iter().map(|&v| v as f64 / scale).collect());
current.pop();
return;
}
for take in 0..=remaining_units {
current.push(take);
recurse(
remaining_axes - 1,
remaining_units - take,
total,
current,
out,
);
current.pop();
}
}
#[cfg(test)]
mod tests {
use super::*;
fn approx_eq(a: f64, b: f64) -> bool {
(a - b).abs() < 1e-12
}
#[test]
fn two_objective_four_divisions() {
let pts = das_dennis(2, 4);
// Expected: (0,4),(1,3),(2,2),(3,1),(4,0) → /4 → 5 points.
assert_eq!(pts.len(), 5);
for p in &pts {
assert_eq!(p.len(), 2);
assert!(approx_eq(p[0] + p[1], 1.0));
}
let first = &pts[0];
let last = &pts[pts.len() - 1];
assert!(approx_eq(first[0], 0.0) && approx_eq(first[1], 1.0));
assert!(approx_eq(last[0], 1.0) && approx_eq(last[1], 0.0));
}
#[test]
fn three_objective_twelve_divisions_has_91_points() {
// C(12+3-1, 3-1) = C(14, 2) = 91 — the canonical NSGA-III 3-obj set.
let pts = das_dennis(3, 12);
assert_eq!(pts.len(), 91);
for p in &pts {
assert_eq!(p.len(), 3);
assert!(approx_eq(p.iter().sum::<f64>(), 1.0));
}
}
#[test]
fn five_objective_six_divisions_has_210_points() {
// C(6+5-1, 5-1) = C(10, 4) = 210.
let pts = das_dennis(5, 6);
assert_eq!(pts.len(), 210);
for p in &pts {
assert!(approx_eq(p.iter().sum::<f64>(), 1.0));
}
}
#[test]
fn zero_divisions_yields_one_zero_point() {
// With 0 divisions every axis must take 0 → a single all-zero point.
let pts = das_dennis(3, 0);
assert_eq!(pts.len(), 1);
assert_eq!(pts[0], vec![0.0, 0.0, 0.0]);
}
#[test]
#[should_panic(expected = "num_objectives >= 1")]
fn zero_objectives_panics() {
let _ = das_dennis(0, 4);
}
}