Skip to content

Commit a1193de

Browse files
Refactor parallel evaluators: fix ParallelEvaluator, remove ThreadedEvaluator and DistributedEvaluator
- Add context manager support to ParallelEvaluator for proper resource cleanup - Fix multiprocessing.Pool lifecycle management to prevent zombie processes - Remove ThreadedEvaluator (minimal utility due to GIL, implementation issues) - Remove DistributedEvaluator (beta/unstable, overly complex) - Update all documentation, examples, and tests - Add MIGRATION.md guide for users - Add CHANGELOG.md documenting breaking changes - Bump version to 1.0 to indicate breaking changes BREAKING CHANGE: ThreadedEvaluator and DistributedEvaluator have been removed. See MIGRATION.md for migration guidance. Removed files: - neat/threaded.py - neat/distributed.py - examples/xor/evolve-feedforward-threaded.py - examples/xor/evolve-feedforward-distributed.py - tests/test_distributed.py - tests/test_xor_example_distributed.py Modified files: - neat/parallel.py: Added context manager support and fixed resource management - neat/__init__.py: Removed imports for deleted evaluators - setup.py: Bumped version to 1.0 - examples/xor/evolve-feedforward-parallel.py: Updated to demonstrate context manager pattern - tests/test_simple_run.py: Removed threaded evaluator tests - WARP.md: Updated evaluation modes documentation Added files: - MIGRATION.md: Comprehensive migration guide - CHANGELOG.md: Version history and breaking changes - REFACTORING_PLAN.md: Implementation plan documentation - tests/test_parallel_evaluator.py: New tests for context manager functionality
1 parent 7d83b3f commit a1193de

16 files changed

+773
-1705
lines changed

CHANGELOG.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# Changelog
2+
3+
All notable changes to neat-python will be documented in this file.
4+
5+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7+
8+
## [Unreleased]
9+
10+
### Changed
11+
- `ParallelEvaluator` now implements context manager protocol (`__enter__`/`__exit__`) for proper resource cleanup
12+
- Improved resource management in `ParallelEvaluator` to prevent multiprocessing pool leaks
13+
- Fixed `ParallelEvaluator.__del__()` to properly clean up resources without calling `terminate()` unnecessarily
14+
15+
### Removed
16+
- **BREAKING**: `ThreadedEvaluator` has been removed
17+
- Reason: Minimal utility due to Python's Global Interpreter Lock (GIL)
18+
- Had implementation issues including unreliable cleanup and potential deadlocks
19+
- Migration: Use `ParallelEvaluator` for CPU-bound tasks
20+
- **BREAKING**: `DistributedEvaluator` has been removed
21+
- Reason: Marked as beta/unstable with known reliability issues
22+
- Overly complex implementation (574 lines) with fragile error handling
23+
- Migration: Use established frameworks like Ray or Dask for distributed computing
24+
- Removed `neat/threaded.py` module
25+
- Removed `neat/distributed.py` module
26+
- Removed example files: `examples/xor/evolve-feedforward-threaded.py` and `examples/xor/evolve-feedforward-distributed.py`
27+
- Removed test files: `tests/test_distributed.py` and `tests/test_xor_example_distributed.py`
28+
29+
### Added
30+
- Context manager support for `ParallelEvaluator` - recommended usage pattern
31+
- `ParallelEvaluator.close()` method for explicit resource cleanup
32+
- New tests for `ParallelEvaluator` context manager functionality
33+
- `MIGRATION.md` guide for users migrating from removed evaluators
34+
35+
### Migration
36+
- See [MIGRATION.md](MIGRATION.md) for detailed guidance on updating existing code
37+
- `ParallelEvaluator` remains fully backward compatible but context manager usage is recommended
38+
39+
## [0.93] - Previous Release
40+
41+
*Note: Changelog started with version 1.0. For changes prior to 1.0, please see git history.*
42+
43+
---
44+
45+
**For the complete migration guide, see [MIGRATION.md](MIGRATION.md)**

MIGRATION.md

Lines changed: 245 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
1+
# Migration Guide for neat-python 1.0
2+
3+
This guide helps you migrate from neat-python 0.93 to 1.0, which includes breaking changes to the parallel evaluation APIs.
4+
5+
## Overview of Changes
6+
7+
### Removed Components
8+
- **`ThreadedEvaluator`** - Removed due to minimal utility (Python GIL) and implementation issues
9+
- **`DistributedEvaluator`** - Removed due to instability and complexity
10+
11+
### Improved Components
12+
- **`ParallelEvaluator`** - Now supports context manager protocol for proper resource cleanup
13+
14+
---
15+
16+
## ThreadedEvaluator (Removed)
17+
18+
### Why Was It Removed?
19+
20+
The `ThreadedEvaluator` provided minimal benefit for most use cases:
21+
- Python's Global Interpreter Lock (GIL) prevents true parallel execution of CPU-bound code
22+
- Only beneficial for I/O-bound fitness functions (rare in neural network evolution)
23+
- Had implementation issues including unreliable cleanup and potential deadlocks
24+
- No timeout on output queue operations could cause indefinite hangs
25+
26+
### Migration Path
27+
28+
**For CPU-bound fitness evaluation (most common):**
29+
30+
Use `ParallelEvaluator` instead, which uses process-based parallelism to bypass the GIL:
31+
32+
```python
33+
# Old code (ThreadedEvaluator)
34+
import neat
35+
36+
evaluator = neat.ThreadedEvaluator(4, eval_genome)
37+
winner = population.run(evaluator.evaluate, 300)
38+
evaluator.stop() # Manual cleanup
39+
40+
# New code (ParallelEvaluator with context manager)
41+
import neat
42+
import multiprocessing
43+
44+
with neat.ParallelEvaluator(multiprocessing.cpu_count(), eval_genome) as evaluator:
45+
winner = population.run(evaluator.evaluate, 300)
46+
# Automatic cleanup on context exit
47+
```
48+
49+
**For I/O-bound fitness evaluation (uncommon):**
50+
51+
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.
52+
53+
---
54+
55+
## DistributedEvaluator (Removed)
56+
57+
### Why Was It Removed?
58+
59+
The `DistributedEvaluator` had several fundamental problems:
60+
- Marked as **beta/unstable** in the documentation since its introduction
61+
- Used `multiprocessing.managers` which is notoriously unreliable across networks
62+
- Integration tests were skipped due to pickling and reliability issues
63+
- 574 lines of complex, fragile code with extensive error handling
64+
- Better alternatives exist for distributed computing
65+
66+
### Migration Path
67+
68+
**Option 1: Single-machine parallelism (simplest)**
69+
70+
If you were using `DistributedEvaluator` on a single machine, migrate to `ParallelEvaluator`:
71+
72+
```python
73+
# Old code (DistributedEvaluator - single machine)
74+
import neat
75+
76+
de = neat.DistributedEvaluator(
77+
('localhost', 8022),
78+
authkey=b'password',
79+
eval_function=eval_genome,
80+
mode=neat.distributed.MODE_PRIMARY
81+
)
82+
de.start()
83+
winner = population.run(de.evaluate, 300)
84+
de.stop()
85+
86+
# New code (ParallelEvaluator)
87+
import neat
88+
import multiprocessing
89+
90+
with neat.ParallelEvaluator(multiprocessing.cpu_count(), eval_genome) as evaluator:
91+
winner = population.run(evaluator.evaluate, 300)
92+
```
93+
94+
**Option 2: Multi-machine distributed computing (recommended for large-scale)**
95+
96+
Use established distributed computing frameworks like **Ray** or **Dask**:
97+
98+
**Using Ray (recommended):**
99+
100+
```python
101+
import neat
102+
import ray
103+
104+
# Initialize Ray
105+
ray.init(address='auto') # or ray.init() for local cluster
106+
107+
@ray.remote
108+
def eval_genome_remote(genome, config):
109+
"""Fitness evaluation function wrapped for Ray."""
110+
net = neat.nn.FeedForwardNetwork.create(genome, config)
111+
# Your fitness evaluation logic here
112+
return fitness_value
113+
114+
def eval_genomes_distributed(genomes, config):
115+
"""Fitness function that distributes work via Ray."""
116+
# Submit all evaluation tasks
117+
futures = [eval_genome_remote.remote(genome, config)
118+
for genome_id, genome in genomes]
119+
120+
# Gather results
121+
results = ray.get(futures)
122+
123+
# Assign fitness values
124+
for (genome_id, genome), fitness in zip(genomes, results):
125+
genome.fitness = fitness
126+
127+
# Use with NEAT
128+
population = neat.Population(config)
129+
winner = population.run(eval_genomes_distributed, 300)
130+
```
131+
132+
**Using Dask:**
133+
134+
```python
135+
import neat
136+
from dask.distributed import Client
137+
138+
# Connect to Dask cluster
139+
client = Client('scheduler-address:8786')
140+
141+
def eval_genome_dask(genome, config):
142+
"""Fitness evaluation function."""
143+
net = neat.nn.FeedForwardNetwork.create(genome, config)
144+
# Your fitness evaluation logic here
145+
return fitness_value
146+
147+
def eval_genomes_distributed(genomes, config):
148+
"""Fitness function that distributes work via Dask."""
149+
# Submit all evaluation tasks
150+
futures = [client.submit(eval_genome_dask, genome, config)
151+
for genome_id, genome in genomes]
152+
153+
# Gather results
154+
results = client.gather(futures)
155+
156+
# Assign fitness values
157+
for (genome_id, genome), fitness in zip(genomes, results):
158+
genome.fitness = fitness
159+
160+
# Use with NEAT
161+
population = neat.Population(config)
162+
winner = population.run(eval_genomes_distributed, 300)
163+
```
164+
165+
**Option 3: Custom solution**
166+
167+
You can implement your own distributed evaluation using:
168+
- Message queues (RabbitMQ, Redis, AWS SQS)
169+
- Task queues (Celery)
170+
- Cloud functions (AWS Lambda, Google Cloud Functions)
171+
172+
---
173+
174+
## ParallelEvaluator Improvements
175+
176+
The `ParallelEvaluator` has been improved with proper resource management and context manager support.
177+
178+
### Context Manager Pattern (Recommended)
179+
180+
**New recommended usage:**
181+
182+
```python
183+
import neat
184+
import multiprocessing
185+
186+
with neat.ParallelEvaluator(multiprocessing.cpu_count(), eval_genome) as evaluator:
187+
winner = population.run(evaluator.evaluate, 300)
188+
# Pool is automatically cleaned up when exiting the context
189+
```
190+
191+
**Benefits:**
192+
- Guaranteed cleanup of multiprocessing pool
193+
- No risk of zombie processes
194+
- Cleaner, more Pythonic code
195+
- Exception-safe resource management
196+
197+
### Backward Compatibility
198+
199+
**Old usage still works:**
200+
201+
```python
202+
import neat
203+
import multiprocessing
204+
205+
evaluator = neat.ParallelEvaluator(multiprocessing.cpu_count(), eval_genome)
206+
winner = population.run(evaluator.evaluate, 300)
207+
# Pool will be cleaned up by __del__, but context manager is preferred
208+
```
209+
210+
While the old pattern still functions, we **strongly recommend** migrating to the context manager pattern for better resource management.
211+
212+
### Explicit Cleanup
213+
214+
If you need explicit control over cleanup:
215+
216+
```python
217+
evaluator = neat.ParallelEvaluator(multiprocessing.cpu_count(), eval_genome)
218+
try:
219+
winner = population.run(evaluator.evaluate, 300)
220+
finally:
221+
evaluator.close() # Explicit cleanup
222+
```
223+
224+
---
225+
226+
## Additional Resources
227+
228+
- **Ray Documentation**: https://docs.ray.io/
229+
- **Dask Documentation**: https://docs.dask.org/
230+
- **neat-python Documentation**: http://neat-python.readthedocs.io/
231+
- **GitHub Repository**: https://github.com/CodeReclaimers/neat-python
232+
233+
## Getting Help
234+
235+
If you encounter issues during migration:
236+
237+
1. Check the [GitHub Issues](https://github.com/CodeReclaimers/neat-python/issues) for similar problems
238+
2. Review the updated [documentation](http://neat-python.readthedocs.io/)
239+
3. Open a new issue with details about your migration challenge
240+
241+
---
242+
243+
**Version Information:**
244+
- This guide applies to migration from neat-python 0.93 → 1.0
245+
- Last updated: 2025-11-09

0 commit comments

Comments
 (0)