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.managers which is notoriously unreliable across networks

  • Integration 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 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.

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

Getting Help

If you encounter issues during migration:

  1. Check the GitHub Issues for similar problems

  2. Review the updated documentation

  3. 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:

  1. Exactly which parameter is missing

  2. That it must be added to your config file

  3. 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 to true to allow only one structural mutation (add/remove node/connection) per genome per generation

  • structural_mutation_surer: Controls fallback behavior when structural mutations fail (default uses same value as single_structural_mutation)

  • *_init_type: Distribution type for initializing values (gaussian for normal distribution or uniform for 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

  1. Run your existing code - If you have missing parameters, you’ll get a clear error message

  2. Add the suggested parameter - Copy the suggested line from the error message into your config file under the appropriate section

  3. Repeat - Continue running and adding parameters until no errors remain

  4. 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 network

  • examples/xor/config-feedforward-partial - Partial connectivity

  • examples/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 generation

  • structural_mutation_surer - Fallback behavior for failed mutations

  • bias_init_type - Distribution for bias initialization

  • response_init_type - Distribution for response initialization

  • weight_init_type - Distribution for weight initialization

  • enabled_rate_to_true_add - Extra probability to enable connections

  • enabled_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