Skip to main content

crowding_distance

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

CaseAnswer
an empty frontan empty vector
a front of 1 or 2every member is a boundary in every objective → all ∞
an objective with zero range across the frontcontributes 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 pointtreated 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