neat/selection

Controller-facing selection helpers for the NEAT lifecycle.

Selection is the point where a scored population becomes something the rest of the controller can reason about. The helpers in this root chapter answer the questions that show up most often during evolution work: which genome is currently best, what is the current average score, should the population be re-ordered before inspection, and which parent should breed next under the active selection strategy.

Keep the mental model in four steps:

  1. summary helpers such as {@link getFittest} and {@link getAverage} make sure evaluation has happened before they report on the population,
  2. ordering helpers such as {@link sort} keep descending-score reads deterministic,
  3. {@link getParent} delegates the actual parent choice to core/, where POWER, FITNESS_PROPORTIONATE, and TOURNAMENT strategy mechanics live,
  4. facade/ mirrors the stable Neat class entrypoints so callers can use these same behaviors without importing the lower-level module directly.

That split matters because selection is where a NEAT controller turns raw evaluation into search pressure. Once genomes have scores, the controller has to answer two different practical questions without mixing them together:

The public helpers stay readable by keeping those questions adjacent but not collapsed into one overloaded routine.

The re-exported constants in this file are the small tuning and traversal anchors that make those behaviors predictable: fallback scores for unevaluated genomes, default parameters for the built-in parent-selection strategies, and explicit index sentinels for threshold scans and tournament walks.

It helps to read those constants as three compact families instead of as one long shelf of numbers:

Read this root chapter when you want the controller story first. Drop into core/ when you need to understand the exact selection math, overflow rules, or threshold scans. Read facade/ when you are tracing how the public Neat class exposes the same inspection helpers.

flowchart TD
  Population[Population with scores or pending evaluation]
  Summaries[Summary helpers<br/>getFittest / getAverage]
  Ordering[Ordering helper<br/>sort]
  ParentChoice[Parent helper<br/>getParent]
  Core[core/<br/>strategy mechanics]
  Facade[facade/<br/>stable Neat wrappers]

  Population --> Summaries
  Population --> Ordering
  Population --> ParentChoice
  Summaries --> Ordering
  ParentChoice --> Core
  Ordering --> Facade
  Summaries --> Facade
flowchart LR
  classDef base fill:#08131f,stroke:#1ea7ff,color:#dff6ff,stroke-width:1px;
  classDef accent fill:#0f2233,stroke:#ffd166,color:#fff4cc,stroke-width:1.5px;

  Selection[Selection chapter]:::accent --> Inspection[Inspection reads]:::base
  Selection --> Breeding[Breeding read]:::base
  Selection --> Constants[Shared constant families]:::base
  Inspection --> Champion[getFittest / getAverage / sort]:::base
  Breeding --> Parent[getParent]:::base
  Constants --> Pressure[Strategy defaults]:::base
  Constants --> Fallbacks[Score fallback semantics]:::base
  Constants --> Traversal[Index and accumulator sentinels]:::base

Example:

neat.sort();
const champion = neat.getFittest();
const meanScore = neat.getAverage();
const parent = neat.getParent();

neat/selection/selection.ts

DEFAULT_POWER

Default power exponent for POWER selection when none is configured.

A value of 1 keeps POWER selection as a direct index-bias curve without adding extra front-loading beyond the strategy's normal rank preference. That makes this the mildest built-in pressure setting: strong enough to prefer the front of the sorted population, but not so aggressive that the champion becomes nearly inevitable on every draw.

DEFAULT_SCORE

Default score when a genome has no explicit score.

Selection uses one shared fallback so summaries, sorting, and threshold scans all interpret unevaluated or missing scores consistently. That matters for chapter coherence as much as runtime behavior: every inspection helper and parent-selection guard speaks the same "missing score" language instead of inventing its own local default.

DEFAULT_TOURNAMENT_PROBABILITY

Default tournament win probability when none is configured.

This keeps the top sampled participant favored while still allowing weaker entrants to remain reachable later in the tournament walk. Read it as the tournament counterpart to selection pressure: a balanced default that keeps the bracket competitive instead of turning every mini-tournament into a guaranteed top-seed march.

DEFAULT_TOURNAMENT_SIZE

Default tournament size when none is configured.

The built-in bracket stays intentionally small so tournament selection keeps some competitive pressure without collapsing into near-deterministic champion picks. In practice this means the default strategy samples just enough local competition to reward strong genomes while still letting non-champion genomes remain reachable.

FIRST_INDEX

First element index used by guards, fallbacks, and best-first reads.

Selection logic names this index explicitly because the front of the population has semantic meaning: it is where champion reads and sorted-bias strategies begin.

getAverage

getAverage(): number

Compute the average fitness across the population.

Use this when you want a coarse health signal for the whole generation rather than the single best genome. The helper ensures evaluation has happened, folds the total score across the full population, and returns the arithmetic mean that telemetry, progress logging, and quick sanity checks usually need.

Unlike parent selection, this helper does not care about order. It reports on the population as a group, which makes it a convenient companion to {@link getFittest} when you want both "best genome" and "overall generation" signals side by side.

Returns: Mean fitness across the current population.

Example:

const meanScore = neat.getAverage();
console.log(`Average score: ${meanScore}`);

getFittest

getFittest(): GenomeWithScore

Return the fittest genome in the population.

This is the safest "show me the current champion" helper for controller code, telemetry probes, and tests. If the population has not been evaluated yet, the existing evaluation path is triggered first. If scores exist but the population is out of descending order, the helper restores that order before returning the leading genome.

That behavior keeps call sites simple: callers do not need to remember whether evaluation or sorting has already happened earlier in the generation.

Returns: Genome with the highest current score.

Example:

const champion = neat.getFittest();
console.log(champion.score);

getParent

getParent(): GenomeWithScore

Select a parent genome according to the configured selection strategy.

This is the controller-facing gateway into the three built-in strategies:

If the selection mode is unrecognized, the helper falls back to the first genome in the current population so the controller still has a deterministic parent candidate instead of failing deep inside crossover logic.

Returns: Genome chosen according to the active selection strategy.

Example:

const parent = neat.getParent();
const strategyName = neat.options.selection?.name;

INITIAL_CUMULATIVE_FITNESS

Initial cumulative fitness value for roulette threshold scans.

Roulette-style selection accumulates shifted fitness as it walks the population. This zero point keeps that running threshold explicit and aligned with the rest of the selection fallback semantics.

INITIAL_MOST_NEGATIVE_SCORE

Initial most-negative score sentinel for shifted-fitness scans.

FITNESS_PROPORTIONATE selection may need to lift negative scores into a usable roulette space, and this sentinel marks the baseline from which that most-negative search starts.

INITIAL_TOTAL_FITNESS

Initial accumulator value for generation-wide score folds.

Summary helpers begin from this neutral total so whole-population averages and other folds remain explicit about their starting score semantics.

LAST_ELEMENT_INDEX

Index used with at() when checking the tail of the population.

The evaluation guard only needs the final genome to answer one practical question: has this generation already been scored all the way through?

LAST_INDEX_OFFSET

Offset for retrieving the last element via length arithmetic.

This keeps tail access readable in places where explicit length math is more portable than at() for the surrounding helper shape.

LOOP_INDEX_INCREMENT

Loop step used by explicit tournament and threshold walks.

Naming the increment makes the small index-based scans read like deliberate traversal code instead of scattered magic numbers.

SECOND_INDEX

Second element index used by the cheap leading-edge ordering guard.

Comparing the first two genomes is enough for the root helpers' fast "probably already sorted" check, so this constant marks the smallest useful comparison boundary.

sort

sort(): void

Sort the internal population in place by descending fitness.

Use this when later controller steps should read the population in explicit best-first order. The helper applies the same fallback-score semantics used elsewhere in selection, so genomes without a score are treated as if they had {@link DEFAULT_SCORE} until evaluation supplies a real value.

This helper is intentionally narrow: it only reorders the current population. It does not evaluate genomes, mutate them, or change the active parent selection strategy.

Returns: Nothing. The population array is reordered in place.

Example:

neat.sort();
const bestScore = neat.population[0]?.score;
Generated from source JSDoc • GitHub