Skip to content

Commit e166d37

Browse files
Update documentation to match current implementation
- CHANGELOG: Add fitness_criterion fix, checkpoint/reporter timing changes, CTRNN double-buffer fix, aggregation validation fix. Remove dead GPU_DESIGN_NOTES.md reference. - installation.rst: Remove hardcoded version numbers, add optional extras section documenting [gpu], [examples], [docs], [all]. - module_summaries.rst: Update Checkpointer to reflect post_evaluate timing, save_checkpoint signature, restore_checkpoint behavior. - reproducibility.rst: Fix checkpoint numbering explanation (suffix N is the evaluated generation, not the next-to-evaluate generation). - config_file.rst: Document time_constant_* parameters (7 params). - migration.rst: Add v1.x to 2.0 migration section (CTRNN API change). - innovation_numbers.rst: Fix invalid pseudocode, update checkpoint data format, replace dead file reference. - customization.rst: Remove dead example links (circuits/, openai-lander/). - faq.rst: Correct GPU availability description. - genome-interface.rst: Add fitness_criterion parameter to crossover. - examples/README.md: Add lorenz-ctrnn, signal-tracking-gpu, spike-timing-gpu examples. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 05b8341 commit e166d37

File tree

11 files changed

+156
-61
lines changed

11 files changed

+156
-61
lines changed

CHANGELOG.md

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1616
- **Configurable node gene distance** via `compatibility_include_node_genes` in `[DefaultGenome]`. Default True (current behavior). Set to False for canonical NEAT distance formula. (NEAT paper compliance)
1717
- **Configurable enabled-state penalty** via `compatibility_enable_penalty` in `[DefaultGenome]`. Default 1.0 (current behavior). Set to 0.0 for canonical NEAT distance formula. (NEAT paper compliance)
1818
- **Canonical spawn allocation** via `spawn_method = proportional` in `[DefaultReproduction]`. Default `smoothed` (current behavior). (NEAT paper compliance)
19-
20-
### Changed
21-
- **Distance function now matches genes by innovation number**, consistent with crossover behavior. Previously used tuple keys (endpoint pairs). This affects speciation when the same connection endpoints receive different innovation numbers in different generations (uncommon but possible). (NEAT paper compliance)
22-
- **Dangling nodes are now pruned** after `mutate_delete_node` and `mutate_delete_connection`. Hidden nodes that become disconnected from all outputs are automatically removed along with their connections. This reduces structural bloat in long evolution runs.
23-
24-
### Fixed
25-
- **75% disable rule** now matches the NEAT paper specification (Stanley & Miikkulainen, 2002, p. 111). Previously, the rule was applied after random attribute inheritance, producing an effective ~87.5% disable rate. Now correctly produces 75%. This may affect evolution dynamics in existing configurations.
26-
2719
- **GPU-accelerated evaluation** for CTRNN and Izhikevich spiking networks via optional CuPy dependency
2820
- `GPUCTRNNEvaluator` and `GPUIZNNEvaluator` in `neat.gpu.evaluator`
2921
- Batch-evaluates entire populations on GPU using padded tensor operations
@@ -32,9 +24,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
3224
- Requires sum aggregation; other aggregation functions raise `ValueError`
3325
- `import neat` never loads CuPy; all GPU imports are lazy
3426
- Benchmark script in `benchmarks/gpu_benchmark.py`
35-
- See `GPU_DESIGN_NOTES.md` for design rationale
27+
- GPU comparison examples in `examples/signal-tracking-gpu/` and `examples/spike-timing-gpu/`
3628

3729
### Changed
30+
- **Distance function now matches genes by innovation number**, consistent with crossover behavior. Previously used tuple keys (endpoint pairs). This affects speciation when the same connection endpoints receive different innovation numbers in different generations (uncommon but possible). (NEAT paper compliance)
31+
- **Dangling nodes are now pruned** after `mutate_delete_node` and `mutate_delete_connection`. Hidden nodes that become disconnected from all outputs are automatically removed along with their connections. This reduces structural bloat in long evolution runs.
3832
- **CTRNN integration method** changed from forward Euler to exponential Euler (ETD1)
3933
- Integrates the linear decay term `-y/tau` exactly
4034
- Unconditionally stable regardless of `dt/tau` ratio (forward Euler required `dt < 2*tau`)
@@ -43,6 +37,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
4337
to the same continuous solution as `dt` decreases
4438
- The `time_constant_min_value` constraint is relaxed: values well below the integration
4539
timestep are now safe
40+
- **Checkpoint timing moved from `end_generation` to `post_evaluate`**. Checkpoints now save the evaluated population (with fitness values) rather than the unevaluated post-reproduction population. Restoring a checkpoint no longer re-runs the last generation's fitness evaluation. Checkpoint file `N` now means "generation N has been evaluated." Old 5-tuple checkpoint files are still loadable for backward compatibility.
41+
- **Reporter species output moved from `end_generation` to `post_evaluate`**. The `StdOutReporter` species detail table now appears alongside fitness statistics for the same generation, eliminating the previous mismatch where species sizes from the next generation were printed under the current generation's banner.
42+
43+
### Fixed
44+
- **`fitness_criterion = min` now works correctly**. Previously, only the termination check honored this setting. Best-genome tracking, stagnation detection, elite selection, crossover parent selection, spawn allocation, and statistics reporting all hardcoded "higher is better." All fitness comparisons now use direction-aware methods on the `Config` object (`is_better_fitness`, `meets_threshold`, `worst_fitness`).
45+
- **75% disable rule** now matches the NEAT paper specification (Stanley & Miikkulainen, 2002, p. 111). Previously, the rule was applied after random attribute inheritance, producing an effective ~87.5% disable rate. Now correctly produces 75%. This may affect evolution dynamics in existing configurations.
46+
- **Two double-buffer bugs in CTRNN advance method** fixed. Incorrect buffer swapping could cause state corruption during multi-step CTRNN evaluation.
47+
- **Aggregation validation** for builtins and callables fixed.
4648

4749

4850
## [1.1.0] - 2025-12-05
@@ -91,11 +93,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
9193

9294
### Fixed
9395
- **Add-node mutation bias behavior**: Newly inserted nodes created by `mutate_add_node` now start with zero bias so that splitting a connection is as neutral as possible with respect to the original signal flow. This makes the structural mutation less disruptive while preserving the existing weight-preserving semantics (incoming weight 1.0, outgoing weight equal to the original connection).
94-
- **Checkpoint Generation Semantics**: Clarified and corrected how checkpoint generation numbers are labeled and interpreted.
95-
- A checkpoint file named `neat-checkpoint-N` now always contains the population, species state, and RNG state needed to begin evaluating **generation `N`**.
96-
- Previously, checkpoints were labeled with the index of the generation that had just been evaluated, while storing the *next* generation's population; this could make restored runs appear to "repeat" the previous generation.
97-
- The NEAT evolution loop and genetic algorithm behavior are unchanged; this is a bookkeeping fix that aligns checkpoint behavior with user expectations and the original NEAT paper's generational model.
98-
- New and updated tests in `tests/test_checkpoint.py` and `tests/test_population.py` enforce the invariant that checkpoint `N` resumes at the start of generation `N`.
96+
- **Checkpoint Generation Semantics**: Clarified and corrected how checkpoint generation numbers are labeled and interpreted. (Note: checkpoint timing was further improved in v2.1 to save after evaluation rather than after reproduction, eliminating wasted work on restore.)
9997
- **Population Size Drift**: Fixed small mismatches between actual population size and configured `pop_size`
10098
- `DefaultReproduction.reproduce()` now strictly enforces `len(population) == config.pop_size` for every non-extinction generation
10199
- New `_adjust_spawn_exact` helper adjusts per-species spawn counts after `compute_spawn()` to correct rounding/clamping drift
@@ -131,7 +129,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
131129
- New `InnovationTracker` class in `neat/innovation.py`
132130
- Comprehensive unit tests in `tests/test_innovation.py` (19 tests)
133131
- Integration tests in `tests/test_innovation_integration.py` (6 tests)
134-
- Innovation tracking documentation in `INNOVATION_TRACKING_IMPLEMENTATION.md`
132+
- Innovation tracking documentation in `docs/innovation_numbers.rst`
135133

136134
### Changed
137135
- **BREAKING**: `DefaultConnectionGene.__init__()` now requires mandatory `innovation` parameter

docs/config_file.rst

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -624,6 +624,37 @@ required for your particular implementation.
624624
The probability that :term:`mutation` will replace the response multiplier of a node with a newly :py:meth:`chosen <attributes.FloatAttribute.init_value>`
625625
random value (as if it were a new node).
626626

627+
.. index:: time_constant
628+
629+
* *time_constant_init_mean*
630+
The mean of the normal/gaussian distribution used to select time constant values for new nodes.
631+
**Default: 1.0.** Only relevant for CTRNN networks; for feedforward and discrete-time recurrent
632+
networks this attribute is unused.
633+
634+
* *time_constant_init_stdev*
635+
The standard deviation of the distribution used to select time constant values for new nodes.
636+
**Default: 0.0** (all new nodes start with the mean value).
637+
638+
* *time_constant_max_value*
639+
The maximum allowed time constant value. **Default: 10.0.**
640+
641+
* *time_constant_min_value*
642+
The minimum allowed time constant value. **Default: 0.01.**
643+
644+
* *time_constant_mutate_power*
645+
The standard deviation of the zero-centered normal/gaussian distribution from which a time constant
646+
mutation value is drawn. **Default: 0.0** (no mutation).
647+
648+
* *time_constant_mutate_rate*
649+
The probability that mutation will change the time constant of a node by adding a random value.
650+
**Default: 0.0** (no mutation).
651+
652+
* *time_constant_replace_rate*
653+
The probability that mutation will replace the time constant of a node with a newly chosen random
654+
value. **Default: 0.0** (no replacement).
655+
656+
.. versionadded:: 2.0
657+
627658
.. index:: ! single_structural_mutation
628659
.. index:: ! structural_mutation_surer
629660
.. index:: mutation

docs/customization.rst

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -76,13 +76,8 @@ New genome types
7676
To use a different genome type, you can create a custom class whose interface matches that of
7777
`DefaultGenome` and pass this as the ``genome_type`` argument to the `Config` constructor. The minimum genome type interface is documented here: :ref:`genome-interface-label`.
7878

79-
This is demonstrated in the `circuit evolution
80-
<https://github.com/CodeReclaimers/neat-python/blob/master/examples/circuits/evolve.py>`_ example.
81-
8279
Alternatively, you can subclass `DefaultGenome` in cases where you need to just add some extra behavior.
83-
This is done in the `OpenAI lander
84-
<https://github.com/CodeReclaimers/neat-python/blob/master/examples/openai-lander/evolve.py>`_ example to
85-
add an evolvable per-genome reward discount value. It is also done in the :py:mod:`iznn` setup, with :py:class:`IZGenome <iznn.IZGenome>`.
80+
This is done in the :py:mod:`iznn` setup, with :py:class:`IZGenome <iznn.IZGenome>`.
8681

8782
.. index:: species
8883

docs/faq.rst

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -180,11 +180,13 @@ Smaller populations:
180180
Can I use GPUs?
181181
~~~~~~~~~~~~~~~
182182

183-
**NEAT-Python is CPU-only.** The evolutionary algorithm itself doesn't benefit from GPU acceleration.
183+
The evolutionary algorithm itself (selection, crossover, speciation) runs on CPU. However, neat-python
184+
includes optional **GPU-accelerated network evaluation** for CTRNN and Izhikevich spiking networks via
185+
the ``neat.gpu`` module. Install with ``pip install 'neat-python[gpu]'``. See :doc:`ctrnn` for details.
184186

185-
**However:**
187+
**Beyond the built-in GPU support:**
186188

187-
- You **can** use GPUs in your fitness function (e.g., if running neural network simulations with PyTorch)
189+
- You **can** also use GPUs in your own fitness function (e.g., running simulations with PyTorch or JAX)
188190
- The evolved networks themselves are small and fast on CPU
189191
- Use ``ParallelEvaluator`` to utilize multiple CPU cores
190192

docs/genome-interface.rst

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,11 @@ Initialization/Reproduction
4040
Crossover/Mutation
4141
---------------------------
4242

43-
:py:meth:`configure_crossover(self, genome1, genome2, config) <DefaultGenome.configure_crossover>`
43+
:py:meth:`configure_crossover(self, genome1, genome2, config, fitness_criterion=None) <DefaultGenome.configure_crossover>`
4444

45-
Configure the genome as a child of the given parent genomes.
45+
Configure the genome as a child of the given parent genomes. The optional ``fitness_criterion`` parameter
46+
(``'max'``, ``'min'``, or ``None``) determines which parent is considered fitter for the purpose of inheriting
47+
disjoint and excess genes. When ``'min'``, lower fitness is better. Defaults to ``'max'`` if not provided.
4648

4749
:py:meth:`mutate(self, config) <DefaultGenome.mutate>`
4850

docs/innovation_numbers.rst

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -221,9 +221,9 @@ When two genomes mate, genes are matched by innovation number:
221221
parent1_genes[innovation],
222222
parent2_genes[innovation]
223223
]).copy()
224-
elif fittest parent has it:
225-
# Disjoint/excess - inherit from fittest
226-
child_genes[innovation] = fittest_gene.copy()
224+
elif innovation in fitter_parent_genes:
225+
# Disjoint/excess from fitter parent - inherit
226+
child_genes[innovation] = fitter_parent_genes[innovation].copy()
227227
228228
Generation Tracking
229229
~~~~~~~~~~~~~~~~~~~
@@ -250,10 +250,11 @@ The innovation tracker is automatically saved with checkpoints:
250250

251251
.. code-block:: python
252252
253-
# Saving
254-
checkpoint_data = (generation, config, population, species_set, rndstate)
255-
# innovation_tracker saved as part of population.reproduction
256-
253+
# Saving (in post_evaluate callback)
254+
checkpoint_data = (generation, config, population, species_set,
255+
rndstate, best_genome)
256+
# innovation_tracker is saved as part of config.genome_config
257+
257258
# Loading
258259
population = neat.Checkpointer.restore_checkpoint('checkpoint-file')
259260
# innovation_tracker automatically reconnected to genome_config
@@ -263,7 +264,8 @@ The global counter state is preserved, so innovation numbers continue from where
263264
Implementation Details
264265
~~~~~~~~~~~~~~~~~~~~~~
265266

266-
For complete implementation details, see ``INNOVATION_TRACKING_IMPLEMENTATION.md`` in the `source repository <https://github.com/CodeReclaimers/neat-python/blob/master/INNOVATION_TRACKING_IMPLEMENTATION.md>`_.
267+
For complete implementation details, see the ``InnovationTracker`` class in ``neat/innovation.py``
268+
in the `source repository <https://github.com/CodeReclaimers/neat-python/blob/master/neat/innovation.py>`_.
267269

268270
References
269271
----------

docs/installation.rst

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,26 +11,22 @@ two methods outlined below:
1111

1212
Install neat-python from PyPI using pip
1313
---------------------------------------
14-
To install the most recent release (version 1.1.0) from PyPI, you should run the command (as root or using `sudo`
14+
To install the most recent release from PyPI, you should run the command (as root or using `sudo`
1515
as necessary)::
1616

1717
pip install neat-python
1818

1919
Note that the examples are not included with the package installed from PyPI, so you should download the `source archive
20-
for release 1.1.0
21-
<https://github.com/CodeReclaimers/neat-python/releases/tag/v1.0.0>`_ and use the example code contained in it.
22-
23-
You may also just get the 1.1.0 release source, and install it directly (as shown below)
24-
instead of `pip`.
20+
<https://github.com/CodeReclaimers/neat-python/releases>`_ and use the example code contained in it.
2521

2622
Install neat-python from source
2723
--------------------------------
2824
Obtain the source code by either cloning the source repository::
2925

3026
git clone https://github.com/CodeReclaimers/neat-python.git
3127

32-
or downloading the `source archive for release 1.1.0
33-
<https://github.com/CodeReclaimers/neat-python/releases/tag/v1.1.0>`_.
28+
or downloading the latest `source archive
29+
<https://github.com/CodeReclaimers/neat-python/releases>`_.
3430

3531
Note that the most current code in the repository may not always be in the most polished state, but I do make sure the
3632
tests pass and that most of the examples run. If you encounter any problems, please open an `issue on GitHub
@@ -47,3 +43,16 @@ For development (editable install with dev dependencies)::
4743
pip install -e ".[dev]"
4844

4945
This installs the package in editable mode with testing tools (pytest, coverage, etc.).
46+
47+
Optional extras
48+
---------------
49+
50+
neat-python supports optional dependency groups that can be installed via pip extras::
51+
52+
pip install 'neat-python[gpu]' # GPU acceleration (CuPy, requires NVIDIA GPU)
53+
pip install 'neat-python[examples]' # Dependencies for running examples
54+
pip install 'neat-python[docs]' # Documentation building tools
55+
pip install 'neat-python[all]' # Everything (includes GPU)
56+
57+
The ``[gpu]`` extra installs CuPy for GPU-accelerated CTRNN and Izhikevich network evaluation.
58+
See :doc:`ctrnn` for details.

docs/migration.rst

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,49 @@
1-
Migration Guide for neat-python 1.0
2-
====================================
1+
Migration Guide
2+
===============
3+
4+
Migration from 1.x to 2.0
5+
--------------------------
6+
7+
neat-python 2.0 includes one breaking API change:
8+
9+
CTRNN time constants
10+
~~~~~~~~~~~~~~~~~~~~
11+
12+
In v1.x, all CTRNN nodes shared a single fixed time constant passed at network creation time::
13+
14+
# v1.x (no longer works)
15+
net = neat.ctrnn.CTRNN.create(genome, config, time_constant=0.01)
16+
17+
In v2.0, each node carries its own time constant as an evolved gene attribute::
18+
19+
# v2.0
20+
net = neat.ctrnn.CTRNN.create(genome, config)
21+
22+
The ``time_constant`` parameter has been removed from ``CTRNN.create()``. Time constants are now
23+
configured via the ``[DefaultGenome]`` section of your config file using the ``time_constant_*``
24+
parameters (e.g., ``time_constant_init_mean``, ``time_constant_mutate_rate``). The defaults
25+
(mean 1.0, zero mutation rate) reproduce the old behavior of a fixed time constant of 1.0.
26+
27+
For details, see `CTRNN-CHANGES.md <https://github.com/CodeReclaimers/neat-python/blob/master/examples/lorenz-ctrnn/docs/CTRNN-CHANGES.md>`_.
28+
29+
Checkpoint format
30+
~~~~~~~~~~~~~~~~~
31+
32+
Checkpoints created with v1.x are not loadable in v2.0 due to internal class changes (per-node
33+
time constants change the gene structure). Re-run evolution from scratch or keep the old version
34+
installed for loading legacy checkpoints.
35+
36+
Other changes
37+
~~~~~~~~~~~~~
38+
39+
- Feedforward and discrete-time recurrent configurations require no changes.
40+
- The CTRNN integration method changed from forward Euler to exponential Euler (ETD1), which
41+
improves numerical stability but produces slightly different trajectories for the same ``dt``.
42+
43+
Migration from 0.93 to 1.0
44+
---------------------------
345

4-
This guide helps you migrate from neat-python 0.93 to 1.0, which includes breaking changes to the parallel evaluation APIs.
46+
This section helps you migrate from neat-python 0.93 to 1.0, which includes breaking changes to the parallel evaluation APIs.
547

648
Overview of Changes
749
-------------------

0 commit comments

Comments
 (0)