Skip to content

jfullstackdev/evolution-algo-simulation

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 

Repository files navigation

Evolution Algo Simulation

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.

What It Is

A genetic algorithm - an evolutionary-inspired optimization technique. The algorithm:

  1. Creates a population of diverse random 3-word phrases
  2. Measures fitness (how many words match the target)
  3. Selects the fittest individuals
  4. Creates offspring through crossover (combining parent genomes)
  5. Applies mutation to introduce variation
  6. Repeats until solution is found

What It Is NOT

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

Quick Start

python evolution.py

Enter a 3-word target when prompted:

Enter 3-word target: CAT EATS FISH

Reproducible Runs

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.

How It Works

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

Configuration

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 generations

Design Decisions

This 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.

Speciation (Hamming Distance ≤ 1)

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

Elitism (Best Genome Always Survives)

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)

Mutation Rate (1% per genome)

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

Vocabulary Size (Minimum 50 Words)

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

Max Generations Heuristic

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

Failure Handling

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.

Example Output

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.

Dependencies

  • Python 3.x
  • No external packages (stdlib only)

Credits

Built as a learning exercise in genetic algorithms.

Philosophical Notes

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.