Source code for openswmm_gymnasium.scoring.igd

"""
Inverted Generational Distance (IGD) and IGD+.

@author: Caleb Buahin
@copyright: Copyright (c) 2026 Caleb Buahin
@license: MIT
"""

from __future__ import annotations

import numpy as np
from numpy.typing import ArrayLike


[docs] def igd(approximation: ArrayLike, reference_front: ArrayLike) -> float: """Inverted Generational Distance. For each point in C{reference_front}, compute the Euclidean distance to the closest point in C{approximation}; IGD is the mean of those distances. Lower is better. @param approximation: 2-D array, shape C{(n, d)}. @type approximation: array_like @param reference_front: 2-D array, shape C{(m, d)}. @type reference_front: array_like @return: Mean nearest-neighbour distance, or C{inf} if either input is empty. @rtype: float """ a = np.asarray(approximation, dtype=float) r = np.asarray(reference_front, dtype=float) if a.size == 0 or r.size == 0: return float("inf") diff = r[:, None, :] - a[None, :, :] dists = np.sqrt(np.sum(diff * diff, axis=2)) return float(dists.min(axis=1).mean())
[docs] def igd_plus(approximation: ArrayLike, reference_front: ArrayLike) -> float: """IGD+ — distance is computed only along dominated dimensions. For minimisation, the IGD+ distance from reference point C{r} to approximation point C{a} replaces C{(a - r)} with C{max(0, a - r)} before computing the Euclidean norm. The result is zero whenever C{a} weakly dominates C{r}, which makes IGD+ Pareto-compliant in a way standard IGD is not. @rtype: float """ a = np.asarray(approximation, dtype=float) r = np.asarray(reference_front, dtype=float) if a.size == 0 or r.size == 0: return float("inf") diff = a[None, :, :] - r[:, None, :] diff_clamped = np.maximum(0.0, diff) dists = np.sqrt(np.sum(diff_clamped * diff_clamped, axis=2)) return float(dists.min(axis=1).mean())