Migration Guide
Migration from 1.x to 2.0
neat-python 2.0 includes one breaking API change:
CTRNN time constants
In v1.x, all CTRNN nodes shared a single fixed time constant passed at network creation time:
# v1.x (no longer works)
net = neat.ctrnn.CTRNN.create(genome, config, time_constant=0.01)
In v2.0, each node carries its own time constant as an evolved gene attribute:
# v2.0
net = neat.ctrnn.CTRNN.create(genome, config)
The time_constant parameter has been removed from CTRNN.create(). Time constants are now
configured via the [DefaultGenome] section of your config file using the time_constant_*
parameters (e.g., time_constant_init_mean, time_constant_mutate_rate). The defaults
(mean 1.0, zero mutation rate) reproduce the old behavior of a fixed time constant of 1.0.
For details, see CTRNN-CHANGES.md.
Checkpoint format
Checkpoints created with v1.x are not loadable in v2.0 due to internal class changes (per-node time constants change the gene structure). Re-run evolution from scratch or keep the old version installed for loading legacy checkpoints.
Other changes
Feedforward and discrete-time recurrent configurations require no changes.
The CTRNN integration method changed from forward Euler to exponential Euler (ETD1), which improves numerical stability but produces slightly different trajectories for the same
dt.
Migration from 0.93 to 1.0
This section helps you migrate from neat-python 0.93 to 1.0, which includes breaking changes to the parallel evaluation APIs.
Overview of Changes
Removed Components
ThreadedEvaluator - Removed due to minimal utility (Python GIL) and implementation issues
DistributedEvaluator - Removed due to instability and complexity
Improved Components
ParallelEvaluator - Now supports context manager protocol for proper resource cleanup
ThreadedEvaluator (Removed)
Why Was It Removed?
The ThreadedEvaluator provided minimal benefit for most use cases:
Python’s Global Interpreter Lock (GIL) prevents true parallel execution of CPU-bound code
Only beneficial for I/O-bound fitness functions (rare in neural network evolution)
Had implementation issues including unreliable cleanup and potential deadlocks
No timeout on output queue operations could cause indefinite hangs
Migration Path
For CPU-bound fitness evaluation (most common):
Use ParallelEvaluator instead, which uses process-based parallelism to bypass the GIL:
# Old code (ThreadedEvaluator)
import neat
evaluator = neat.ThreadedEvaluator(4, eval_genome)
winner = population.run(evaluator.evaluate, 300)
evaluator.stop() # Manual cleanup
# New code (ParallelEvaluator with context manager)
import neat
import multiprocessing
with neat.ParallelEvaluator(multiprocessing.cpu_count(), eval_genome) as evaluator:
winner = population.run(evaluator.evaluate, 300)
# Automatic cleanup on context exit
For I/O-bound fitness evaluation (uncommon):
Consider using Python’s asyncio for truly I/O-bound operations, or still use ParallelEvaluator which works well for both CPU and I/O-bound tasks.
DistributedEvaluator (Removed)
Why Was It Removed?
The DistributedEvaluator had several fundamental problems:
Marked as beta/unstable in the documentation since its introduction
Used
multiprocessing.managerswhich is notoriously unreliable across networksIntegration tests were skipped due to pickling and reliability issues
574 lines of complex, fragile code with extensive error handling
Better alternatives exist for distributed computing
Migration Path
Option 1: Single-machine parallelism (simplest)
If you were using DistributedEvaluator on a single machine, migrate to ParallelEvaluator:
# Old code (DistributedEvaluator - single machine)
import neat
de = neat.DistributedEvaluator(
('localhost', 8022),
authkey=b'password',
eval_function=eval_genome,
mode=neat.distributed.MODE_PRIMARY
)
de.start()
winner = population.run(de.evaluate, 300)
de.stop()
# New code (ParallelEvaluator)
import neat
import multiprocessing
with neat.ParallelEvaluator(multiprocessing.cpu_count(), eval_genome) as evaluator:
winner = population.run(evaluator.evaluate, 300)
Option 2: Multi-machine distributed computing (recommended for large-scale)
Use established distributed computing frameworks like Ray or Dask.
Using Ray (recommended)
import neat
import ray
# Initialize Ray
ray.init(address='auto') # or ray.init() for local cluster
@ray.remote
def eval_genome_remote(genome, config):
"""Fitness evaluation function wrapped for Ray."""
net = neat.nn.FeedForwardNetwork.create(genome, config)
# Your fitness evaluation logic here
return fitness_value
def eval_genomes_distributed(genomes, config):
"""Fitness function that distributes work via Ray."""
# Submit all evaluation tasks
futures = [eval_genome_remote.remote(genome, config)
for genome_id, genome in genomes]
# Gather results
results = ray.get(futures)
# Assign fitness values
for (genome_id, genome), fitness in zip(genomes, results):
genome.fitness = fitness
# Use with NEAT
population = neat.Population(config)
winner = population.run(eval_genomes_distributed, 300)
Using Dask
import neat
from dask.distributed import Client
# Connect to Dask cluster
client = Client('scheduler-address:8786')
def eval_genome_dask(genome, config):
"""Fitness evaluation function."""
net = neat.nn.FeedForwardNetwork.create(genome, config)
# Your fitness evaluation logic here
return fitness_value
def eval_genomes_distributed(genomes, config):
"""Fitness function that distributes work via Dask."""
# Submit all evaluation tasks
futures = [client.submit(eval_genome_dask, genome, config)
for genome_id, genome in genomes]
# Gather results
results = client.gather(futures)
# Assign fitness values
for (genome_id, genome), fitness in zip(genomes, results):
genome.fitness = fitness
# Use with NEAT
population = neat.Population(config)
winner = population.run(eval_genomes_distributed, 300)
Option 3: Custom solution
You can implement your own distributed evaluation using:
Message queues (RabbitMQ, Redis, AWS SQS)
Task queues (Celery)
Cloud functions (AWS Lambda, Google Cloud Functions)
ParallelEvaluator Improvements
The ParallelEvaluator has been improved with proper resource management and context manager support.
Context Manager Pattern (Recommended)
New recommended usage:
import neat
import multiprocessing
with neat.ParallelEvaluator(multiprocessing.cpu_count(), eval_genome) as evaluator:
winner = population.run(evaluator.evaluate, 300)
# Pool is automatically cleaned up when exiting the context
Benefits:
Guaranteed cleanup of multiprocessing pool
No risk of zombie processes
Cleaner, more Pythonic code
Exception-safe resource management
Backward Compatibility
Old usage still works:
import neat
import multiprocessing
evaluator = neat.ParallelEvaluator(multiprocessing.cpu_count(), eval_genome)
winner = population.run(evaluator.evaluate, 300)
# Pool will be cleaned up by __del__, but context manager is preferred
While the old pattern still functions, we strongly recommend migrating to the context manager pattern for better resource management.
Explicit Cleanup
If you need explicit control over cleanup:
evaluator = neat.ParallelEvaluator(multiprocessing.cpu_count(), eval_genome)
try:
winner = population.run(evaluator.evaluate, 300)
finally:
evaluator.close() # Explicit cleanup
Additional Resources
Ray Documentation: https://docs.ray.io/
Dask Documentation: https://docs.dask.org/
neat-python Documentation: http://neat-python.readthedocs.io/
GitHub Repository: https://github.com/CodeReclaimers/neat-python
Getting Help
If you encounter issues during migration:
Check the GitHub Issues for similar problems
Review the updated documentation
Open a new issue with details about your migration challenge
Version Information
This guide covers migrations: 1.x → 2.0 and 0.93 → 1.0
Last updated: 2026-03-02
Configuration File Migration (v1.0)
Starting with v1.0, neat-python requires all configuration parameters to be explicitly specified in your configuration file. This section guides you through updating your configuration files for compatibility with v1.0.
What Changed
Previously, if you omitted certain parameters, neat-python would use default values and issue deprecation warnings (which were easy to miss). Now, missing required parameters cause immediate errors with helpful suggestions on what to add.
This change improves reliability and reproducibility:
No silent defaults: You always know exactly what configuration you’re using
Explicit is better than implicit: Your config file is now self-documenting
Prevents mistakes: Can’t accidentally run experiments with unintended default values
Better for v1.0: Breaking change is appropriate for a major version
What You’ll See
If you’re missing a required parameter, you’ll get a clear error message like this:
RuntimeError: Missing required configuration item: 'bias_init_type'
This parameter must be explicitly specified in your configuration file.
Suggested value: bias_init_type = gaussian
The error tells you:
Exactly which parameter is missing
That it must be added to your config file
A suggested value to use
Required Parameters by Section
[NEAT] Section
Add this parameter if missing:
no_fitness_termination = False
Set to True if you want evolution to run for a fixed number of generations regardless of fitness. Set to False to use fitness-based termination.
[DefaultGenome] Section
Add these parameters if missing:
# Structural mutation parameters
single_structural_mutation = false
structural_mutation_surer = default
# Initialization type parameters
bias_init_type = gaussian
response_init_type = gaussian
weight_init_type = gaussian
# Connection enable mutation parameters
enabled_rate_to_true_add = 0.0
enabled_rate_to_false_add = 0.0
Parameter meanings:
single_structural_mutation: Set totrueto allow only one structural mutation (add/remove node/connection) per genome per generationstructural_mutation_surer: Controls fallback behavior when structural mutations fail (defaultuses same value assingle_structural_mutation)*_init_type: Distribution type for initializing values (gaussianfor normal distribution oruniformfor uniform distribution)enabled_rate_to_*_add: Additional probability to enable/disable connections during mutation
[DefaultStagnation] Section
Add this parameter if missing:
species_elitism = 2
This sets the number of species protected from stagnation removal. For example, species_elitism = 2 protects the 2 best-performing species from being removed due to stagnation.
[DefaultReproduction] Section
Add this parameter if missing:
min_species_size = 2
This sets the minimum size for a species to be maintained after reproduction.
[IZGenome] Section (Spiking Networks Only)
If you’re using IZNN genomes (Izhikevich spiking neuron model), also add:
a_init_type = gaussian
b_init_type = gaussian
c_init_type = gaussian
d_init_type = gaussian
Complete Example Configuration
Here’s a minimal complete configuration file for v1.0 (feedforward network solving XOR):
[NEAT]
fitness_criterion = max
fitness_threshold = 0.9
pop_size = 150
reset_on_extinction = False
no_fitness_termination = False
[DefaultGenome]
# Node activation options
activation_default = sigmoid
activation_mutate_rate = 0.0
activation_options = sigmoid
# Node aggregation options
aggregation_default = sum
aggregation_mutate_rate = 0.0
aggregation_options = sum
# Node bias options
bias_init_mean = 0.0
bias_init_stdev = 1.0
bias_init_type = gaussian
bias_max_value = 30.0
bias_min_value = -30.0
bias_mutate_power = 0.5
bias_mutate_rate = 0.7
bias_replace_rate = 0.1
# Genome compatibility options
compatibility_disjoint_coefficient = 1.0
compatibility_weight_coefficient = 0.5
# Connection add/remove rates
conn_add_prob = 0.5
conn_delete_prob = 0.5
# Connection enable options
enabled_default = True
enabled_mutate_rate = 0.01
enabled_rate_to_true_add = 0.0
enabled_rate_to_false_add = 0.0
# Network topology
feed_forward = True
initial_connection = full
# Node add/remove rates
node_add_prob = 0.2
node_delete_prob = 0.2
# Network parameters
num_hidden = 0
num_inputs = 2
num_outputs = 1
# Node response options
response_init_mean = 1.0
response_init_stdev = 0.0
response_init_type = gaussian
response_max_value = 30.0
response_min_value = -30.0
response_mutate_power = 0.0
response_mutate_rate = 0.0
response_replace_rate = 0.0
# Structural mutation
single_structural_mutation = false
structural_mutation_surer = default
# Connection weight options
weight_init_mean = 0.0
weight_init_stdev = 1.0
weight_init_type = gaussian
weight_max_value = 30
weight_min_value = -30
weight_mutate_power = 0.5
weight_mutate_rate = 0.8
weight_replace_rate = 0.1
[DefaultSpeciesSet]
compatibility_threshold = 3.0
[DefaultStagnation]
species_fitness_func = max
max_stagnation = 20
species_elitism = 2
[DefaultReproduction]
elitism = 2
survival_threshold = 0.2
min_species_size = 2
Step-by-Step Migration Instructions
Run your existing code - If you have missing parameters, you’ll get a clear error message
Add the suggested parameter - Copy the suggested line from the error message into your config file under the appropriate section
Repeat - Continue running and adding parameters until no errors remain
Verify - Run your experiments to ensure everything works as expected
Note
The error messages are designed to be helpful and actionable. They tell you exactly what to add and where.
Where to Find Example Configs
All example configurations in the examples/ directory have been updated for v1.0. You can use these as references:
examples/xor/config-feedforward- Simple feedforward networkexamples/xor/config-feedforward-partial- Partial connectivityexamples/xor/config-spiking- Spiking neural network (IZNN)
Quick Reference: New Required Parameters
Here’s a quick summary of all parameters that became required in v1.0:
[NEAT] section:
no_fitness_termination- Controls whether fitness-based termination is used
[DefaultGenome] section:
single_structural_mutation- Limit to one structural mutation per generationstructural_mutation_surer- Fallback behavior for failed mutationsbias_init_type- Distribution for bias initializationresponse_init_type- Distribution for response initializationweight_init_type- Distribution for weight initializationenabled_rate_to_true_add- Extra probability to enable connectionsenabled_rate_to_false_add- Extra probability to disable connections
[DefaultStagnation] section:
species_elitism- Number of species protected from stagnation removal
[DefaultReproduction] section:
min_species_size- Minimum size for species maintenance
[IZGenome] section (if using spiking networks):
a_init_type,b_init_type,c_init_type,d_init_type- Distributions for Izhikevich parameters
For detailed documentation on what each parameter does, see Configuration file description.
Configuration Migration Resources
Configuration File Reference: Configuration file description
Example Configurations:
examples/directory in the repositoryGitHub Issues: https://github.com/CodeReclaimers/neat-python/issues
Documentation: http://neat-python.readthedocs.io/