Genetic algorithm experiments: the weasel program, and host and parasite coevolution
Two experiments from evolutionary computing, written for the EMATM0044 coursework at the University of Bristol and since rebuilt as a library with a test suite.
The first is the thought experiment from Richard Dawkins' The Blind
Watchmaker: evolve a random string towards methinks it is like a weasel by
mutation and selection alone, and watch how quickly cumulative selection finds
a target that random search never would. The second follows Cartlidge and
Bullock (2004) and pits two populations against each other, hosts against
parasites, to study how virulence and mutation bias decide whether an arms race
drives both populations upward or leaves one behind.
- A genetic algorithm over strings, with tournament selection, elitism, and one-point, uniform and multi-point crossover.
- A coevolutionary simulator with a virulence function, mutation bias, and a measured criterion for whether the two populations have disengaged.
- A Welch t-test, checked against
scipy.stats.ttest_ind, used to decide whether the differences the experiments produce are real. - Both labs as experiments that run each condition repeatedly and report the spread, rather than once.
- A command line interface, and 462 tests.
FINDINGS.md records what the experiments measure, with the seed and repeat count beside each number, and says where the original coursework's recorded conclusions were right and where they were not.
The headline: uniform crossover reaches the target in roughly half the generations of one-point crossover (77.2 against 146.9, p = 6.3e-07). The original guessed this and could not have measured it, because the flag selecting the crossover method was never read and both arms of its comparison ran the same algorithm.
git clone https://github.com/ReverseZoom2151/the-blind-watchmaker.git
cd the-blind-watchmaker
python -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
pip install -e ".[dev]"Python 3.9 or later. matplotlib is the only runtime dependency; scipy is used by the tests alone, to check the t-test against a reference implementation rather than against its own arithmetic.
# Evolve the target phrase
python -m src weasel --seed 1
# With uniform crossover, which finds it sooner
python -m src weasel --crossover 1.0 --method uniform --seed 1
# Coevolve hosts and parasites at moderate virulence
python -m src coevolve --virulence 0.75 --mutation-bias 0.9 --seed 1
# List and run the lab experiments
python -m src experiments
python -m src experiment lab1.crossover_method_experiment --repeats 20 --seed 12345Every run takes a --seed, so any figure or number can be regenerated. By
default nothing is written to disk and no window opens, so the tool is safe to
call from a script. Pass --save-figures DIRECTORY to keep the plots.
src/
weasel.py the string GA: initialise, assess, tournament, breed, mutate
crossover.py one-point, uniform and multi-point, selected by name
coevolution.py hosts against parasites, virulence, mutation bias
stats.py Welch t-test with a p-value, pure standard library
plotting.py styling, safe filenames, shared figure helpers
cli.py the command line interface
experiments/
lab1_weasel.py the weasel sweeps, with repeats
lab2_coevolution.py the coevolution conditions, with repeats
tests/ 462 tests
FINDINGS.md what the experiments measure
python -m pytest # everything
python -m pytest -m "not slow" # skip the full-sweep testsThe suite is headless and cannot hang: matplotlib is forced to a
non-interactive backend before anything imports, and a call to input() fails
the test that made it, naming it. Both guards exist because the original
scripts ran an entire lab at import, alternating blocking prompts with plot
windows.
This started as two coursework scripts. Rebuilding them turned up defects worth knowing about, all recorded in the commit history and in FINDINGS.md:
- The crossover comparison ran one algorithm against itself.
- The t-test paired Welch's standard error with Student's degrees of freedom, which overstates significance.
- Every saved figure was lost on Windows: a colon in the title made the path an
NTFS alternate data stream, so the write succeeded and left a zero byte file.
Worth knowing if you ever test for this,
Path.is_file()returns true for such a path andstat()reports the stream's size, so the obvious check passes. You have to list the directory. - Every parameter sweep ran each condition once, which for a stochastic algorithm is noise rather than measurement.
Released under the MIT Licence.
- Dawkins, R. (1986). The Blind Watchmaker. Norton.
- Cartlidge, J. and Bullock, S. (2004). Combating coevolutionary disengagement by reducing parasite virulence. Evolutionary Computation, 12(2), 193-222.
With thanks to the University of Bristol EMATM0044 teaching team, whose lab skeletons this began from.