neat/multiobjective/fronts
Frontier construction for NEAT multi-objective ranking.
This chapter picks up where dominance/ stops. Once the pairwise pass has
recorded how many times each genome is dominated and which genomes each row
blocks, this file peels the population into ordered Pareto fronts and writes
the _moRank annotations that later selection and telemetry reads expect.
The boundary is intentionally narrow:
objectives/owns safe value extraction and matrix assemblydominance/owns pairwise comparison and first-front discovery- this file owns frontier peeling and rank annotation
crowding/owns within-front spacing after ranks already exist
That split matters because front construction depends on one stable contract: matrix row order, population order, and dominance-state indices must all keep referring to the same genome while fronts are peeled layer by layer. This file therefore works only with index lists and one shared population array; it does not rebuild the matrix or recompute pairwise dominance.
Conceptually, the pass answers three questions:
- Which indices already belong to the first front?
- When a front is removed, which domination counts should drop to zero next?
- Which genomes should be annotated with the current Pareto rank before the next frontier is collected?
flowchart TD
A[Dominance state with firstFrontIndices] --> B[Take current front indices]
B --> C[Annotate genomes with current _moRank]
C --> D[Relax domination counts for dominated neighbors]
D --> E[Collect indices whose counts reach zero]
E --> F[Append current front to ordered results]
F --> G{Guard exceeded?}
G -->|No| B
G -->|Yes| H[Stop with bounded result]Read this chapter when the missing question is "how did the bookkeeping from
the dominance pass become ordered Pareto fronts?" Read dominance/ first if
the missing context is how firstFrontIndices or domination counts were
computed in the first place.
neat/multiobjective/fronts/multiobjective.fronts.ts
annotateGenomeRank
annotateGenomeRank(
population: default[],
genomeIndex: number,
frontRank: number,
): void
Annotates a genome with its Pareto front rank.
Ranking is stored directly on the genome so later selection, telemetry, and archive helpers can read one stable annotation instead of carrying a parallel rank table beside the population.
Parameters:
population- - Genome population.genomeIndex- - Index of the genome to annotate.frontRank- - Pareto front rank (0 = best front).
appendFront
appendFront(
paretoFronts: default[][],
population: default[],
currentFrontIndices: number[],
): void
Appends the current front (index list) as genome references to the
paretoFronts accumulator.
The accumulator stores genomes, not indices, because the returned fronts are meant to be consumed by later crowding and archive code. The conversion from stable indices to genome references happens only after the current layer has been fully identified.
Parameters:
paretoFronts- - Accumulator for Pareto fronts.population- - Genome population.currentFrontIndices- - Indices for the current front.
buildNextFrontIndices
buildNextFrontIndices(
population: default[],
dominanceState: DominanceState,
currentFrontIndices: number[],
currentFrontRank: number,
): number[]
Builds the next front by applying rank annotations and dominance updates.
This helper is the inner loop of frontier peeling: mark every genome in the current front with the same rank, then remove each genome's blocking influence so newly non-dominated neighbors can surface as the next front.
Parameters:
population- - Genome population.dominanceState- - Dominance bookkeeping.currentFrontIndices- - Indices for the current front.currentFrontRank- - Rank to assign to the current front.
Returns: Indices for the next front.
buildParetoFronts
buildParetoFronts(
population: default[],
dominanceState: DominanceState,
maxFrontRankGuard: number,
): default[][]
Builds Pareto fronts from a precomputed dominance state.
This performs the “peeling” phase of fast non-dominated sorting:
- Start with the first front (all non-dominated genomes).
- For each front, reduce domination counts of the genomes it dominates.
- Each genome whose domination count becomes zero moves to the next front.
The implementation is intentionally breadth-first. It never revisits pairwise comparisons; instead it trusts the dominance-state handoff and repeatedly relaxes domination counts until the next layer becomes visible.
Side effects:
- Annotates each genome in
populationwith_moRank(0 = best front).
Guard:
- Stops when
currentFrontRank > maxFrontRankGuardto avoid pathological infinite/degenerate runs. If the guard triggers, the returned fronts may be incomplete.
Parameters:
population- - Genome population (same ordering used by dominance bookkeeping).dominanceState- - Dominance bookkeeping.maxFrontRankGuard- - Safety guard for ranking iterations.
Returns: Ordered Pareto fronts (rank order).
collectNextFrontIndices
collectNextFrontIndices(
dominanceState: DominanceState,
genomeIndex: number,
nextFrontIndices: number[],
): void
Collects indices that become non-dominated after removing the current genome’s dominance influence.
Every dominated neighbor starts this pass with one known blocker: the genome that just joined the current front. Decrementing its domination count models removing that blocker. When the count reaches zero, the neighbor has no remaining dominating opponents and can join the next frontier.
Parameters:
dominanceState- - Dominance bookkeeping.genomeIndex- - Index of the current genome.nextFrontIndices- - Accumulator for the next front.
incrementFrontRank
incrementFrontRank(
currentFrontRank: number,
): number
Increments the front rank counter.
Keeping rank advancement in its own helper makes the frontier loop easier to read and keeps the guard check phrased in terms of the next rank value.
Parameters:
currentFrontRank- - Current front rank.
Returns: Incremented front rank.
MAX_PARETO_FRONT_RANK_GUARD
Maximum number of Pareto fronts to allow during ranking before aborting.
This is a defensive guard against pathological conditions (e.g., corrupted dominance bookkeeping) that could otherwise cause long/infinite loops. A healthy ranking pass should terminate well before this threshold, so the constant exists as a safety rail rather than a normal control knob.
shouldStopFrontRanking
shouldStopFrontRanking(
currentFrontRank: number,
maxFrontRankGuard: number,
): boolean
Determines whether ranking should stop due to a safety guard.
The guard is intentionally blunt: if frontier peeling advances beyond the configured maximum rank, the function stops and returns the fronts collected so far rather than risking a pathological or corrupted infinite loop.
Parameters:
currentFrontRank- - Current front rank after increment.maxFrontRankGuard- - Safety guard for ranking iterations.
Returns: true if ranking should stop.