Skip to content

cuevase/origins

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

6 Commits
 
 
 
 
 
 
 
 

Repository files navigation

Origins — Joint Distribution Evolution Simulation

A minimal evolutionary simulation where each organism maintains a full joint probability distribution over local neighborhood (X), internal energy (H), and action (O). Behavior is sampled from that joint; offspring inherit a mutated, renormalized joint. The environment is a one-dimensional circular world with food spawning, metabolic decay, and collision death.

Useful for experimenting with how selection pressure reshapes high-dimensional categorical policies without hand-specifying neural nets or explicit fitness functions beyond survival and reproduction.

What gets simulated

  • State space: 27 neighborhood encodings ( spots: empty / food / occupied), 11 discrete energy levels (0–10), 3 actions (move left, stay, move right).
  • Organism: Position, energy H, and a 27 × 11 × 3 joint array (normalized). Actions are drawn as P(O | X, H) from the appropriate slice.
  • World: World wraps toroidally; each tick organisms choose moves; collisions (multiple movers to one cell, or moving into someone who stays) remove organisms; eating increases H; periodic decay lowers H; reproduction requires energy above a threshold and places offspring in an empty neighbor slot with a mutated joint.
  • Outputs: Time series in logs.csv (population, energy stats, births/deaths intervals, KL divergence vs. uniform joint), periodic .npy joint snapshots, and matplotlib plots under outputs/plots/ after each run.

Requirements

Python 3.10+ recommended (uses int | None style hints). Dependencies are listed in joint_sim/requirements.txt:

Quick start

Install and run from the joint_sim directory (imports assume that working directory):

cd joint_sim
python3 -m venv .venv && source .venv/bin/activate   # optional
pip install -r requirements.txt
python3 run.py

By default this uses values from joint_sim/config.py (100_000 ticks, world size 1000, etc.). Override with CLI flags or edit Config dataclasses in code.

Analysis (population curve, KL vs. uniform, marginals and other plots) runs automatically after the simulation. To regenerate plots from existing logs without rerunning:

python3 run.py --analyze

Optional ASCII world printout during runs:

python3 run.py --visualize --progress-interval 2000

Optional Pygame window:

python3 visualizer.py

CLI reference

Flag Role
--ticks Simulation length
--seed RNG seed
--world-size Cells on the ring
--initial-food, --food-rate Initial food count and spawn rate per tick
--population Starting organism count
--repro-threshold Minimum H to reproduce
--decay-interval Ticks between -1 energy metabolic steps
--mutation-rate, --mutation-sigma Per-entry mutation probability and Gaussian noise scale
--output-dir Base output directory (default outputs)
--log-interval Tick spacing for CSV / snapshot writes
--quiet, --progress-interval, --visualize Console noise and ASCII preview

Example long run matching a typical exploratory profile:

python3 run.py --ticks 100000 --population 50 --food-rate 10 --initial-food 400 \
  --decay-interval 15 --progress-interval 5000 \
  --mutation-rate 0.01 --mutation-sigma 0.02

For a tabular rundown of hyperparameters and intuition, see hyperparameter_reference.txt at the repo root.

Latest example run (long exploratory trajectory)

Re-analyzed from joint_sim/outputs/ on 2026-05-31 (python3 run.py --analyze).

Run configuration

python3 run.py --ticks 500000 --decay-interval 15 --progress-interval 10000 \
  --mutation-rate 0.02 --mutation-sigma 0.03

Other settings from joint_sim/config.py defaults: world 1000, initial population 50, food spawn 10/tick, initial food 400, repro threshold H ≥ 8, log interval 100 ticks.

Runtime

Target 500 000 ticks
Actual (simulator counter) 369 482 ticks (Ctrl+C interrupt)
Logged max tick 369 400 (CSV rows every 100 ticks)
Completion ~74% of planned length
Wall clock ~2.9 hours (first → last snapshot file timestamps: 17:21 → 20:14 on 2026-04-30)
Throughput ~35 ticks/sec effective over the full run

Post-hoc analysis ran on interrupt; all artifacts remain under joint_sim/outputs/.

Summary metrics

Metric Value
Ticks simulated (logged max) 369 400
Final / peak / min population 49 / 99 / 8
Mean population (whole run) 57.7
Final KL (pop‑mean joint ∥ uniform) 0.420
Max KL (over run) 1.103 (at tick 148 100, during bottleneck)
Mean KL (whole run) 0.416
% of run with KL > 0.3 97.2%
Total births 639 137
Total starvation deaths 289 (0.05%)
Total collision deaths 638 849 (99.95%)
Final mean / median H 5.67 / 6.0
Final organisms saved 54 (.npy in final_joints/)
Joint snapshots 3 695
Lineage traces 30

Hypothesis: selection shapes the joint away from uniform

Verdict: supported.

Evidence Detail
Fast departure from uniform KL rises from 0.003 (tick 0) to > 0.1 by tick 100, > 0.5 by tick 500.
Sustained structure KL stays elevated for 97% of the run (mean 0.42); not a transient early artifact.
Peak under selection pressure Max KL 1.10 coincides with population bottleneck (pop = 8 at tick 148 100), when survivors carry the most peaked joints.
Individual joints are highly structured Per-organism KL at end: mean 2.340.05, CV 2.3%) — far above uniform, and survivors converged to similar policies.
Lower entropy than uniform Final joint entropy 4.45 vs uniform 6.79 (891-dimensional simplex).
Population persisted Recovered from bottleneck 8 → 49 by run end; no extinction.

Caveat: high KL proves concentrated mass, not necessarily “optimal foraging.” Pair with conditional slices below.

KL & population phases

Phase Tick range Mean pop Mean KL Notes
Early 0 – 10 k 58.2 0.40 Structure appears within first ~500 ticks
Mid 100 – 200 k 55.0 0.44 Peak KL 1.10 near tick 148 k
Late 300 k+ 59.7 0.40 Post-bottleneck recovery; KL dips then stabilizes

Behavioral slices (start → end, population-mean joint)

Qualitative shifts in P(O | X, H) — see outputs/plots/conditional_slices.png:

Context Hypothesis Result
Food to left, H=5 ↑ move left Supported — LEFT 0.31 → 0.49 (+0.17)
Food center, H=3 ↑ stay Not supported — STAY 0.36 → 0.06; RIGHT rose to 0.69
Surrounded, H=8 ↑ stay Not supported — STAY 0.34 → 0.19; RIGHT 0.65
Food right, H=2 ↑ move right Not supported — RIGHT 0.33 → 0.10; LEFT 0.80

Read: the joint is clearly non-uniform and context-dependent, but simple “move toward food / stay on food” stories only partially hold. Collision-dominated selection may favor movement patterns that reduce crowding over naive foraging heuristics.

Other dynamics

  • Mortality: 99.95% collision, 0.05% starvation — crowded ring, movement is the main selective filter.
  • Energy: mean H 5.0 → 5.7 (modest rise); median stays 6 — metabolic state stable, not the primary evolution signal.
  • Food: world food count 400 → 909 (mean 870); environment is food-rich relative to population.

Reproduce this analysis

From joint_sim/:

python3 run.py --analyze

Plots written to outputs/plots/: population_over_time, kl_divergence_over_time, conditional_slices, h_marginal_comparison, diversity_histogram.

Generated outputs / git

Artifacts under joint_sim/outputs/ (logs.csv, joint_snapshots/, plots/, final_joints/, lineages/) are regenerated every run. They are listed in .gitignore so bulky or machine-specific traces are not committed. To reproduce plots from an existing CSV: python3 run.py --analyze (run from joint_sim). If you committed outputs/ before adding the ignore file, stop tracking it with git rm -r --cached joint_sim/outputs and commit (local files stay on disk).

Repository layout

Path Purpose
joint_sim/run.py CLI entrypoint: simulate → log → analyze
joint_sim/config.py Dataclass configs + StateSpace constants
joint_sim/organism.py Joint representation, sampling, reproduction / mutation
joint_sim/world.py Circular grid, food, sensing, collisions helpers
joint_sim/simulation.py Tick loop
joint_sim/logger.py CSV + snapshot + KL metrics
joint_sim/analysis.py Matplotlib plots from logs and snapshots
joint_sim/visualizer.py Live pygame view
joint_sim/tests/ Unit tests (pytest style)

Tests

From joint_sim:

pip install pytest
pytest tests/

AI use

AI-assisted coding tools were used to implement the simulation code and tooling that runs these experiments. The experiments themselves — including the hypotheses, design choices, parameter sweeps, and interpretation of results — were ideated, iterated, and designed by me.

License

No license file is included in this repository; add one if you plan to publish or accept contributions.

About

exploring the origins of intelligence

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages