neat/adaptive

Adaptive NEAT controllers for complexity, acceptance, lineage diversity, and mutation-rate tuning.

Adaptive control is the NEAT controller's "change the rules while the run is in flight" chapter. These helpers do not mutate one genome directly for the sake of a single operator. Instead they watch population behavior over time and adjust thresholds, budgets, or rates that later generations inherit.

The root chapter answers five practical controller questions:

The helper folders own the narrower heuristics:

Read this root chapter when you want the controller map of adaptive behavior: which adaptive loops exist, what long-lived state they keep, and which later stages of evolution they are meant to influence.

flowchart TD
  Signals["scores, telemetry, generation count, operator stats"] --> Budget["applyComplexityBudget()"]
  Signals --> Phase["applyPhasedComplexity()"]
  Signals --> Acceptance["applyMinimalCriterionAdaptive()"]
  Signals --> Lineage["applyAncestorUniqAdaptive()"]
  Signals --> Mutation["applyAdaptiveMutation()"]
  Signals --> Operators["applyOperatorAdaptation()"]
  Budget --> Options["update controller limits and options"]
  Phase --> Options
  Acceptance --> Population["rewrite acceptance state on current genomes"]
  Lineage --> Options
  Mutation --> Population["update per-genome mutation fields"]
  Operators --> Stats["decay operator success history"]
  Options --> Later["later selection, mutation, and evolution passes"]
  Population --> Later
  Stats --> Later

neat/adaptive/adaptive.ts

applyAdaptiveMutation

applyAdaptiveMutation(): void

Self-adaptive per-genome mutation tuning.

This function implements several strategies to adjust each genome's internal mutation rate (g._mutRate) and optionally its mutation amount (g._mutAmount) over time. Strategies include:

The method reads this.options.adaptiveMutation for configuration and mutates genomes in-place.

This is the adaptive controller that feeds most directly into the root mutation chapter. Rather than choosing one operator itself, it adjusts each genome's readiness for later mutation so the next structural-edit pass can be more exploratory or more conservative depending on recent success.

Returns: Updates per-genome mutation-rate state in place when the current generation satisfies the adaptation cadence.

Example:

// configuration example: // options.adaptiveMutation = { enabled: true, initialRate: 0.5, adaptEvery: 1, strategy: 'twoTier', minRate: 0.01, maxRate: 1 } engine.applyAdaptiveMutation();

applyAncestorUniqAdaptive

applyAncestorUniqAdaptive(): void

Adaptive adjustments based on ancestor uniqueness telemetry.

This helper inspects the most recent telemetry lineage block (if available) for an ancestorUniq metric indicating how unique ancestry is across the population. If ancestry uniqueness drifts outside configured thresholds, the method will adjust either the multi-objective dominance epsilon (if mode === 'epsilon') or the lineage pressure strength (if mode === 'lineagePressure').

This makes the lineage controller the feedback bridge between telemetry and future search policy. It does not rewrite the current population directly; instead it nudges the options that govern how later multi-objective or lineage-pressure decisions behave.

Typical usage: keep population lineage diversity within a healthy band. Low ancestor uniqueness means too many genomes share ancestors (risking premature convergence); high uniqueness might indicate excessive divergence.

Returns: May update lineage-related controller options and record the most recent adjustment generation.

Example:

// Adjusts options.multiObjective.dominanceEpsilon when configured engine.applyAncestorUniqAdaptive();

applyComplexityBudget

applyComplexityBudget(): void

Apply complexity budget scheduling to the evolving population.

Use this controller when topology growth should be managed as a moving budget instead of a fixed hard cap. The adaptive loop observes recent run progress and rewrites the controller's complexity limits so later mutation and evolve passes know how much structural growth they should allow.

This routine updates this.options.maxNodes (and optionally this.options.maxConns) according to a configured complexity budget strategy. Two modes are supported:

Internal state used/maintained on the this object:

The important distinction is that this helper does not mutate genomes directly. It mutates controller policy, so its effect is feed-forward into later structural decisions rather than an immediate topology rewrite.

Returns: Updates this.options.maxNodes and possibly this.options.maxConns in-place; no value is returned.

Example:

// inside a training loop where engine is your Neat instance: engine.applyComplexityBudget(); // engine.options.maxNodes now holds the adjusted complexity cap

applyMinimalCriterionAdaptive

applyMinimalCriterionAdaptive(): void

Apply adaptive minimal criterion (MC) acceptance.

This method maintains an MC threshold used to decide whether an individual genome is considered acceptable. It adapts the threshold based on the proportion of the population that meets the current threshold, trying to converge to a target acceptance rate.

Use this controller when you want the population to earn the right to stay in play. Unlike the complexity and lineage controllers, this one writes back to the current population immediately by zeroing scores below the accepted bar, so it directly changes the selection landscape for the same generation.

Behavior summary:

Returns: Updates _mcThreshold over time and may zero out scores for currently rejected genomes.

Example:

// Example config snippet used by the engine // options.minimalCriterionAdaptive = { enabled: true, initialThreshold: 0.1, targetAcceptance: 0.5, adjustRate: 0.1 } engine.applyMinimalCriterionAdaptive();

applyOperatorAdaptation

applyOperatorAdaptation(): void

Decay operator adaptation statistics (success/attempt counters).

Many adaptive operator-selection schemes keep running tallies of how successful each operator has been. This helper applies an exponential moving-average style decay to those counters so older outcomes progressively matter less.

Use this controller when operator adaptation is enabled and you want the mutation selector to favor recent evidence over stale wins from much earlier generations. It is a policy-maintenance helper, not an operator chooser by itself.

The _operatorStats map on this is expected to contain values of the shape { success: number, attempts: number } keyed by operator id/name.

Returns: Decays _operatorStats in place so later mutation-method selection reflects more recent operator performance.

Example:

engine.applyOperatorAdaptation();

applyPhasedComplexity

applyPhasedComplexity(): void

Toggle phased complexity mode between 'complexify' and 'simplify'.

Phased complexity supports alternating periods where the algorithm is encouraged to grow (complexify) or shrink (simplify) network structures. This can help escape local minima or reduce bloat.

Use this controller when one static mutation policy is not enough. Instead of adjusting caps continuously like applyComplexityBudget(), this helper flips the controller into a new structural mood and records when that phase began.

The current phase and its start generation are stored on this as _phase and _phaseStartGeneration so the state persists across generations.

Returns: Mutates this._phase and this._phaseStartGeneration so later mutation-selection code knows whether to favor growth or simplification.

Example:

// Called once per generation to update the phase state engine.applyPhasedComplexity();

NeatLikeWithAdaptive

Contract map for the adaptive helper boundary.

The adaptive subtree works because each policy chapter can stay focused on a single feedback loop while still sharing one precise agreement about what it may read, what it may rewrite, and which option family owns each tuning decision. This file is that agreement.

Read the contracts in three passes:

The matching defaults and mode labels live in adaptive.core.constants.ts. This file stays focused on contracts so the generated chapter reads as a bounded vocabulary map rather than a second controller implementation.

flowchart TD
  Host[NeatLikeWithAdaptive host] --> Options[Adaptive option families]
  Host --> Population[Population runtime state]
  Host --> Scratch[Adaptive scratch fields and telemetry]
  Options --> Complexity[Complexity and phased schedules]
  Options --> Acceptance[Acceptance and minimal criterion]
  Options --> Mutation[Mutation and operator adaptation]
  Options --> Lineage[Ancestor uniqueness and lineage pressure]
Generated from source JSDoc • GitHub