Function crowding_distance
pub fn crowding_distance(
points: &[Point<'_>],
front: &[usize],
n_objectives: usize,
) -> Vec<f64>Expand description
The crowding distance of every member of one front (Deb et al. 2002).
The diversity half of NSGA-II’s crowded-comparison operator: within a front nothing dominates anything, so the tie is broken by isolation — a solution in a sparsely populated region of objective space is preferred, which is what stops the population collapsing onto one part of the front.
For each objective the front is sorted by that objective; the two extreme solutions are given an infinite distance so the front’s boundaries are never truncated away, and every interior solution accumulates the gap between its two neighbours, normalised by that objective’s range across the front:
d(i) += (f_m(i+1) − f_m(i−1)) / (f_m(max) − f_m(min))The normalisation is not cosmetic: without it an objective measured in millions (an env-step count) would drown one measured in units (a normalised return), and the “diversity” term would only ever see the large one.
§Arguments
front holds indices into points; the returned distances are aligned with
front, not with points. n_objectives is passed explicitly rather than
inferred, so a caller stays in control when a point is malformed.
§Degenerate cases, all deliberate
| Case | Answer |
|---|---|
| an empty front | an empty vector |
| a front of 1 or 2 | every member is a boundary in every objective → all ∞ |
| an objective with zero range across the front | contributes 0 to everyone (there is no spread to measure, and dividing by zero would poison every distance with NaN/∞) |
a non-finite range (an ±∞ objective) | the same: that objective is skipped |
| a missing objective column on some point | treated as NaN, which makes the range non-finite, so that objective is skipped |
Note that a front of two returns [∞, ∞] rather than something ordered:
with only two points there is no interior, and NSGA-II’s truncation step
falls back to the deterministic index tie-break. Crowding distance is
direction-agnostic — it measures spread, not quality — so no
Direction is needed.
use atune_core::pareto::{Point, crowding_distance};
// Three points on a line in one objective: the middle one is interior.
let a = [0.0];
let b = [1.0];
let c = [4.0];
let points = [
Point::unconstrained(&a),
Point::unconstrained(&b),
Point::unconstrained(&c),
];
let d = crowding_distance(&points, &[0, 1, 2], 1);
assert_eq!(d[0], f64::INFINITY);
assert_eq!(d[2], f64::INFINITY);
assert_eq!(d[1], (4.0 - 0.0) / (4.0 - 0.0)); // neighbours 0 and 4, range 4