A Python genetic algorithm that evolves 3-word phrases toward a target using natural selection, mutation, crossover, and species grouping.
This project explores how evolutionary mechanisms like selection, mutation, and speciation interact to solve a constrained optimization problem.
A genetic algorithm - an evolutionary-inspired optimization technique. The algorithm:
- Creates a population of diverse random 3-word phrases
- Measures fitness (how many words match the target)
- Selects the fittest individuals
- Creates offspring through crossover (combining parent genomes)
- Applies mutation to introduce variation
- Repeats until solution is found
This is not a true evolution simulation. It's guided optimization:
- You provide the target (the "goal")
- Selection is directed toward YOUR goal
- It's search, not emergent behavior
python evolution.pyEnter a 3-word target when prompted:
Enter 3-word target: CAT EATS FISH
By default, each run produces different results (natural variation).
python evolution.py # Different each run
python evolution.py --seed # Reproducible (seed 42)Use --seed to get identical results for the same target, useful for debugging or demos.
Input: 3-word target phrase
↓ Generate vocabulary from WORD_POOL (50 words)
↓ Create population (60 diverse random individuals)
↓ Evolution loop:
1. Evaluate fitness (count matching words)
2. Group into species (Hamming distance ≤ 1)
3. Select survivors (top 50% per species)
4. Preserve elite (best genome always survives)
5. Crossover (70% chance to combine parents)
6. Mutate (1% chance per genome)
↓ Output: Generation where solution was found
Edit the constants at the top of evolution.py:
POPULATION_SIZE = 60 # Individuals per generation
GENOME_MUTATION_RATE = 0.01 # 1% chance per genome (not per gene) per generation
CROSSOVER_RATE = 0.7 # 70% chance to recombine parents
SPECIES_THRESHOLD = 1 # Hamming distance for species grouping (1=tight niches)
VERBOSE = True # Print every 10 generations
MIN_VOCAB_SIZE = 50 # Minimum vocabulary size
MAX_GENERATION_CAP = 20000 # Hard cap on max generationsThis implementation includes several design choices that go beyond a minimal genetic algorithm. These are included to demonstrate more realistic evolutionary behavior and to prevent common pitfalls like premature convergence.
The population is divided into "species" based on similarity between genomes.
- Two genomes belong to the same species if they differ by at most 1 word.
- This creates tight niches and prevents one dominant solution from taking over too early.
Why it matters:
- Maintains diversity in the population
- Allows multiple solution paths to evolve in parallel
Tradeoff:
- Too strict (like 0–1) → many tiny species, slower convergence
- Too loose (like 2–3) → species lose meaning, population converges to single solution
- This implementation uses 1 to enforce strict niches
The best-performing genome is always preserved into the next generation unchanged.
Why it matters:
- Guarantees that progress is never lost
- Ensures fitness improves monotonically over time
Tradeoff:
- Slightly reduces exploration (can bias toward current best)
Each genome has a 1% chance of mutation per generation. When selected, exactly 1 gene mutates.
This is intentionally per-genome (not per-gene) to produce slower, more gradual evolution.
Why it matters:
- Introduces new genetic variation
- Prevents the population from getting stuck in local optima
Why so low:
- Keeps evolution gradual rather than random
- Encourages refinement of good solutions instead of constant disruption
Tradeoff:
- Too low → slower exploration
- Too high → turns search into randomness
The algorithm ensures a minimum vocabulary size, even if the target uses fewer words.
Why it matters:
- Expands the search space significantly
- Makes the problem non-trivial
- Prevents trivial convergence
The maximum number of generations is calculated as:
- Search space = vocab_size ^ genome_length
- Expected coverage ≈ search_space / population_size
- Allowed generations ≈ 10 × expected coverage + buffer
Why it matters:
- Prevents infinite loops
- Gives the algorithm a fair chance to explore the space
Tradeoff:
- Heuristic, not guaranteed optimal
- May still terminate before finding a solution in rare cases
If evolution cannot find a solution within the computed max generations (capped at 20K), the program will stop and report:
No solution found within max generations
This prevents the algorithm from running indefinitely if stuck in a local optimum.
Enter 3-word target: CAT EATS FISH
==================================================
EVOLUTIONARY ALGORITHM SIMULATION
==================================================
Target: CAT EATS FISH
Vocabulary size: 50
Search space: 125000
Max generations: 20000
==================================================
Gen 0 | Best: 1/3 | Avg: 0.07 | CAT BIRD TREE
Gen 10 | Best: 1/3 | Avg: 0.05 | TREE EATS HORSE
Gen 20 | Best: 1/3 | Avg: 0.58 | TREE EATS DEER
...
Gen 1100 | Best: 2/3 | Avg: 1.93 | WHALE EATS FISH
==================================================
SOLUTION FOUND at generation 1105
Genome: CAT EATS FISH
==================================================
With --seed, convergence generation will be identical across runs.
- Python 3.x
- No external packages (stdlib only)
Built as a learning exercise in genetic algorithms.
We might choose to step beyond Science and briefly enter Metaphysics. This is optional, and not the focus of the project, consider this a side reflection.
Science explains how evolution operates. It does not answer why it exists, nor what, if anything, set it into motion. This program models evolution as an algorithm, a process where variation and selection gradually produce order.
When we reflect on Reality, we notice something striking: we are not even a dot in the observable Universe, yet everything, from the largest structures to the smallest interactions, follows precise patterns. That precision invites a deeper question: is it purely incidental, or does it point to something beyond itself?
One possible interpretation is the idea of a Creator.
If such a Creator exists, evolution could be the mechanism through which reality unfolds. The algorithm does not need to appear directed to produce meaningful outcomes. What we call “natural selection” might also be seen, philosophically, as a form of optimization, whether toward survival, complexity, or something we do not fully perceive.
If humans, and life in general, had appeared instantly, without process, existence might feel artificial or unearned. Evolution, as a gradual unfolding, gives the appearance (or experience) of development, continuity, and discovery. It allows reality to become, rather than simply be.
This is not a scientific claim, but a metaphysical reflection. Science describes the machinery. Philosophy and theology ask whether there is meaning, intention, or design behind it.