Dominator#
Comparators in the ParetoComparator family (NSGA2Comparator/HypervolumeComparator/NSGA3Comparator/RNSGA2Comparator/EpsilonDominanceComparator) delegate the definition of the dominance relation itself to Dominator, a swappable component.
When you want to use a preference relation other than Pareto dominance, you only need to swap out the dominator argument, not the Comparator itself.
Dominator’s role#
Dominator requires only one method, dominance_matrix(f, direction=None), to be implemented.
f is a matrix of objective values with shape (n, n_obj); it returns a boolean matrix of shape (n, n) where D[i, j] indicates “does row i dominate row j.”
f is assumed to contain no NaN; the caller is responsible for guaranteeing this.
There’s also dominates(fa, fb, direction=None) -> bool, which compares just two points, but this is a default implementation derived from dominance_matrix — when implementing a custom Dominator, implementing dominance_matrix alone automatically guarantees consistency with dominates.
Internally, it stacks fa/fb into two rows and calls dominance_matrix.
If fa contains NaN, it always returns False (does not dominate), while NaN on the fb side is replaced with +inf and treated as an individual that “is always dominated” — an asymmetric treatment.
Built-in Dominators#
Class |
Parameters |
Characteristics |
|---|---|---|
|
None |
Standard Pareto dominance. Considered dominance when at or below on every objective, and strictly less on at least one objective (default) |
|
|
Quantizes into ε-boxes and then delegates internally to |
EpsilonDominator implements the ε-dominance of [6].
There are two quantization modes.
additive (default): Computes each objective’s box index as
floor(f_i / eps_i)multiplicative: Computes it as
floor(log(f_i) / log(1 + eps_i)). Assumes all objective values are positive; raisesValueErrorotherwise
eps is specified as a scalar or an array of shape (n_obj,), and every element must be positive (otherwise ValueError at construction time).
EpsilonDominanceComparator is a thin wrapper that simply passes EpsilonDominator(eps, mode) to ParetoComparator’s dominator argument.
Note
For new code, use ParetoDominator().dominates(...)/ParetoDominator().dominance_matrix(...) directly.
Implementing a custom Dominator#
If you need a custom dominance relation, subclass Dominator and implement only dominance_matrix().
The following example applies a scaling factor to each objective before applying Pareto dominance.
import numpy as np
from saealib import Dominator, ParetoDominator
class WeightedDominator(Dominator):
"""Applies a scaling factor to each objective, then applies standard Pareto dominance."""
def __init__(self, scale):
self.scale = np.asarray(scale, dtype=float)
self._pareto = ParetoDominator()
def dominance_matrix(self, f, direction=None):
return self._pareto.dominance_matrix(f * self.scale, direction)
As with EpsilonDominator, delegating to the existing ParetoDominator on the converted values means you don’t have to write the objective-value comparison logic yourself.