Skip to content

Commit 6f125b6

Browse files
author
Gerrit Tombrink
committed
World Genesis v0.3.0 — PyTorch JEPA backend, dashboard knob, test/doc fixes
1 parent 935d39c commit 6f125b6

19 files changed

Lines changed: 1522 additions & 40 deletions

.github/workflows/test.yml

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
name: Tests
2+
3+
on:
4+
push:
5+
branches: [main, master]
6+
pull_request:
7+
branches: [main, master]
8+
workflow_dispatch:
9+
10+
jobs:
11+
test:
12+
runs-on: ubuntu-latest
13+
strategy:
14+
fail-fast: false
15+
matrix:
16+
python-version: ["3.11", "3.12", "3.13"]
17+
18+
steps:
19+
- uses: actions/checkout@v4
20+
21+
- name: Set up Python ${{ matrix.python-version }}
22+
uses: actions/setup-python@v5
23+
with:
24+
python-version: ${{ matrix.python-version }}
25+
cache: pip
26+
27+
- name: Install dependencies
28+
run: |
29+
python -m pip install --upgrade pip
30+
pip install -r requirements.txt
31+
pip install -e ".[dev]"
32+
33+
# The committed data/ files let the world build without network access;
34+
# the optional torch backend is not installed here, so its test module
35+
# skips via pytest.importorskip — exercising the graceful-degradation path.
36+
- name: Run test suite
37+
run: pytest -q

.gitignore

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# ---- Python ----
2+
__pycache__/
3+
*.py[cod]
4+
*$py.class
5+
*.egg-info/
6+
.eggs/
7+
build/
8+
dist/
9+
*.egg
10+
11+
# ---- Virtual environments ----
12+
.venv/
13+
venv/
14+
env/
15+
ENV/
16+
17+
# ---- Test / type / lint caches ----
18+
.pytest_cache/
19+
.mypy_cache/
20+
.ruff_cache/
21+
.coverage
22+
.coverage.*
23+
htmlcov/
24+
coverage.xml
25+
.tox/
26+
27+
# ---- Simulation run output ----
28+
# CSV/JSON written by sim_logger.py at runtime — not source.
29+
logs/
30+
31+
# ---- Editors / OS ----
32+
.idea/
33+
.vscode/
34+
*.swp
35+
.DS_Store
36+
Thumbs.db
37+
38+
# ---- Secrets ----
39+
.env
40+
.env.*
41+
*.pem
42+
43+
# NOTE: data/ (Natural Earth + precomputed .npy/.npz) IS committed on purpose
44+
# so the app runs after a clone. See docs/data_attributions.md. To regenerate
45+
# instead, delete data/ and run the generate_*.py scripts.

CHANGELOG.md

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,65 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
## [0.3.0] - 2026-05-25
11+
12+
Adds the PyTorch JEPA backend that earlier releases listed as a v0.3
13+
candidate. The backend is opt-in; the default NumPy path and all prior
14+
behaviour are unchanged. This release also closes documentation/test gaps
15+
found in an upload-readiness audit.
16+
17+
### Added — PyTorch JEPA backend
18+
19+
- **`world_model_torch.py`** — a PyTorch re-implementation of the JEPA
20+
world model (encoder, AdaLN predictor, SIGReg, CEM planner) using autograd
21+
instead of the hand-written NumPy backprop. At its default settings it
22+
reproduces `world_model.py` exactly: weights copied across backends via
23+
`load_numpy_params` / `export_numpy_params` produce encode/predict outputs
24+
matching to < 1e-4 (verified in `test_world_model_torch.py`). CPU by
25+
default with optional CUDA (`device="auto"`); `torch.set_num_threads(1)`
26+
by default to stay friendly to the eventlet server loop.
27+
- **Paper-aligned toggles (opt-in, off by default):** `sigreg_mode=
28+
"epps_pulley"` implements the LeWorldModel (Maes et al. 2026,
29+
arXiv:2603.19312) characteristic-function SIGReg with the paper's
30+
λ = 0.1 and a large projection count, plus `predictor_dropout`. The
31+
default `moments` SIGReg (M = 15, λ = 0.01) is unchanged.
32+
- **`SharedWorldModel(backend="numpy"|"torch", ...)`** dispatch; the torch
33+
import is lazy so PyTorch remains an optional dependency
34+
(`pip install -e ".[torch]"`).
35+
- **Dashboard JEPA tab** — select backend (NumPy / PyTorch), settings preset
36+
(Repo default / Paper), and device at runtime via `set_jepa_backend`
37+
(`app.py`) / `World.set_jepa_backend` (`world.py`). The swap preserves the
38+
experience buffer and repoints all agents; the sim loop is paused during
39+
the swap and resumed. Falls back gracefully with a clear message when
40+
PyTorch is not installed.
41+
42+
### Added — tests
43+
44+
- **`test_world_model_gradcheck.py`** — central finite-difference gradient
45+
checks for every analytic backward pass (linear, GELU, RMSNorm, AdaLN,
46+
SIGReg). Measured relative error < 1e-8. This is the test prior READMEs
47+
referred to but did not ship.
48+
- **`test_world_model.py`** — JEPA learning/inference behaviour: prediction
49+
loss reduction, learned action conditioning (zero-init AdaLN identity →
50+
action-sensitive after training), anti-collapse, linear probe R², CEM
51+
planner output validity.
52+
- **`test_world_model_torch.py`** — torch backend parity, weight cross-check,
53+
Epps–Pulley toggle, and device handling (skips when torch is absent).
54+
55+
### Fixed — documentation & packaging
56+
57+
- README test table referenced two test files that did not exist
58+
(`test_world_model.py`, `test_world_model_gradcheck.py`); both now ship.
59+
- `world_model.py` docstring pointed at a non-existent `test_layers_gradcheck.py`.
60+
- Project version was out of sync (`pyproject.toml` 0.1.0 vs CITATION/CHANGELOG
61+
0.2.1); all now read 0.3.0.
62+
- `docs/validation.md` described the PyTorch port as future work; it is now
63+
documented as implemented.
64+
- Added `.gitignore` and a GitHub Actions test workflow
65+
(`.github/workflows/test.yml`, Python 3.11/3.12/3.13).
66+
- `test_macro.py` unit tests now `assert` instead of `return`-ing a bool
67+
(removes pytest `PytestReturnNotNoneWarning`).
68+
1069
## [0.2.1] - 2026-05-06
1170

1271
A same-day follow-up review pass on v0.2.0 surfaced five additional bugs

CITATION.cff

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ authors:
1313
given-names: Gerrit
1414
name-suffix: Dr.
1515
affiliation: GeoLambda GmbH
16-
version: 0.2.1
17-
date-released: "2026-05-06"
16+
version: 0.3.0
17+
date-released: "2026-05-25"
1818
license: AGPL-3.0-or-later
1919
repository-code: "https://github.com/GeoLambdaAI/world-genesis"
2020
url: "https://www.geolambda.ai"

README.md

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ Each agent perceives the world through a **Joint Embedding Predictive Architectu
199199
| Predictor | MLP with **Adaptive Layer Normalization** (AdaLN) — action conditions each layer's scale and shift; zero-init scale/shift weights (DiT-style) | Maes et al. 2026, Section 3.2; Peebles & Xie 2022 |
200200
| SIGReg (v0.2) | Differentiable moments-matching variant: skewness² + kurtosis² + variance penalty along random unit-norm projections, in the spirit of Cramer-Wold gaussianity testing | Adapted from Maes et al. 2026, Section 4 |
201201
| CEM Planner | Cross-Entropy Method: sample action sequences, rollout in latent space, select elites, refine | LeCun 2022, Section 3.4 |
202-
| Training | L = L\_pred + λ · SIGReg(Z), **analytic backpropagation** (hand-implemented in NumPy, gradient-checked against finite differences to <1e-10), Adam optimizer with gradient clipping at 5.0 | LeCun 2022 |
202+
| Training | L = L\_pred + λ · SIGReg(Z), **analytic backpropagation** (hand-implemented in NumPy, gradient-checked against central finite differences to <1e-8 in `test_world_model_gradcheck.py`), Adam optimizer with gradient clipping at 5.0. An optional PyTorch backend (`world_model_torch.py`) uses autograd. | LeCun 2022 |
203203

204204
**Loss function:**
205205

@@ -209,6 +209,20 @@ L = ||z_hat_{t+1} - z_{t+1}||^2 + lambda * SIGReg(Z)
209209

210210
where `z_hat_{t+1} = Predictor(Encoder(x_t), a_t)` and `z_{t+1} = Encoder(x_{t+1})`.
211211

212+
**Backends (NumPy default, PyTorch optional).** The reference implementation in
213+
`world_model.py` is pure NumPy with hand-written, gradient-checked backprop. An
214+
opt-in PyTorch backend (`world_model_torch.py`) implements the identical
215+
architecture with autograd and optional CUDA. At its default settings it
216+
reproduces the NumPy model — weights copied across backends match encode/predict
217+
outputs to < 1e-4 — so it is a true drop-in, not a different model. It also adds
218+
*opt-in* paper-aligned toggles: an Epps–Pulley characteristic-function SIGReg
219+
(Maes et al. 2026) with λ = 0.1, and predictor dropout. Install with
220+
`pip install -e ".[torch]"`; select at runtime in code
221+
(`SharedWorldModel(backend="torch")`) or from the dashboard's **JEPA** tab
222+
(NumPy / PyTorch × Repo-default / Paper × device). Switching preserves the
223+
shared experience buffer; if PyTorch is not installed the option degrades
224+
gracefully and the NumPy backend keeps running.
225+
212226
**Agent decision loop** (Kahneman's Dual Process Theory):
213227
- **System 1, symbolic** (every tick): Maslow-style needs hierarchy weights
214228
eleven goal candidates by trait-modulated priorities (eat, heal, work, trade,
@@ -407,9 +421,10 @@ altitude, and disease environment have higher survival and reproduction rates.
407421
| Module | Lines | Purpose |
408422
|--------|-------|---------|
409423
| `agents.py` | 1,208 | Autonomous agents: JEPA cognition, physics, traits, skills, memory, social actions |
410-
| `world.py` | 1,079 | World engine: tick loop, resources, businesses, settlements, scenario dispatch, era-aware UI summaries |
411-
| `world_model.py` | 701 | JEPA implementation: encoder, predictor (AdaLN), SIGReg, CEM planner, deterministic batch sampling |
412-
| `shared_world_model.py` | 229 | Single shared JEPA for all agents with batch encode/plan |
424+
| `world.py` | 1,189 | World engine: tick loop, resources, businesses, settlements, scenario dispatch, era-aware UI summaries, runtime JEPA backend swap |
425+
| `world_model.py` | 701 | JEPA implementation (NumPy, hand-written backprop): encoder, predictor (AdaLN), SIGReg, CEM planner, deterministic batch sampling |
426+
| `world_model_torch.py` | 534 | Optional PyTorch JEPA backend (autograd): same architecture, CUDA-ready, Epps–Pulley SIGReg toggle, NumPy weight bridge |
427+
| `shared_world_model.py` | 263 | Single shared JEPA for all agents with batch encode/plan; selects NumPy or PyTorch backend |
413428
| `macro.py` | 512 | 14-state ODE: climate, resources, pollution, socioeconomics |
414429
| `geopolitics.py` | 705 | Emergent nations, alliances, trade (gravity model), conflict (IFs) |
415430
| `bridge.py` | 456 | Bidirectional coupling: agents <-> macro <-> geopolitics; per-cell regen baselines |
@@ -443,9 +458,10 @@ altitude, and disease environment have higher survival and reproduction rates.
443458
| Test | Validates | Count |
444459
|------|-----------|-------|
445460
| `test_macro.py` | BAU 2025–2100 vs. IPCC AR6 SSP2-4.5/SSP3-7.0 envelope; carbon-cycle vs. Mauna Loa decadal mean; ECS-consistency unit test | 9 + 2 |
446-
| `test_world_model.py` | JEPA training: prediction-loss reduction, action-conditioning, anti-collapse, linear probe R², CEM planner output validity | 5 |
447-
| `test_world_model_gradcheck.py` | Backward implementations (linear, GELU, RMSNorm, AdaLN, SIGReg) verified against finite-difference gradients to <1e-10 | 5 |
461+
| `test_world_model.py` | JEPA training: prediction-loss reduction, learned action-conditioning, anti-collapse, linear probe R², CEM planner output validity | 5 |
462+
| `test_world_model_gradcheck.py` | Backward implementations (linear, GELU, RMSNorm, AdaLN, SIGReg) verified against central finite differences (measured relative error <1e-8) | 5 |
448463
| `test_shared_world_model.py` | Single vs. batch equivalence (max diff 1e-15), per-agent vs. plan_batch identity, edge cases | 6 |
464+
| `test_world_model_torch.py` | PyTorch backend (opt-in): single/batch parity, NumPy↔Torch weight cross-check (<1e-4), Epps–Pulley toggle, device handling (skips if torch absent) | 9 (+1 CUDA-gated) |
449465
| `test_agents_lifecycle.py` | Era-aware lifecycle thresholds across 4 eras, modern drift bounds, paleolithic 1-tick floor | 7 |
450466
| `test_geopolitics.py` | Haversine correctness, conflict monotonicity, 5-nation BAU prevalence calibration, summit-cadence independence | 5 |
451467
| `test_world.py` | Haversine threshold semantics, snapshot iteration safety | 4 |
@@ -517,7 +533,8 @@ that affected scientific correctness without breaking the runtime:
517533
10⁻⁴, so the prediction loss decreased only on the bias terms and the
518534
AdaLN action-conditioning weights were never updated at all. v0.2
519535
replaces this with hand-written analytic backpropagation in pure NumPy,
520-
verified against finite-difference gradients to <1e-10 relative error.
536+
verified against central finite differences to <1e-8 relative error
537+
(`test_world_model_gradcheck.py`).
521538
On a synthetic toy problem with hidden physical parameters, prediction
522539
loss now decreases 103× and a linear probe recovers the hidden physics
523540
with R² = 0.98.

app.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,15 @@ def get_god_log():
205205
return jsonify(world.god_mode.get_intervention_log())
206206

207207

208+
@app.route("/api/jepa/status")
209+
def get_jepa_status():
210+
if world is None:
211+
from world import _torch_available
212+
return jsonify({"backend": "numpy", "preset": "default",
213+
"torch_available": _torch_available()})
214+
return jsonify(world.get_jepa_status())
215+
216+
208217
@app.route("/api/dialogues")
209218
def get_dialogues():
210219
if world is None:
@@ -295,6 +304,37 @@ def on_test_llm():
295304
socketio.emit("llm_test_result", result)
296305

297306

307+
# ---- JEPA World-Model Backend ----
308+
@socketio.on("set_jepa_backend")
309+
def on_set_jepa_backend(data):
310+
"""Swap the shared JEPA backend/preset at runtime.
311+
312+
Rebuilding the model and repointing every agent must not interleave with
313+
a tick, so the sim loop is paused for the swap (cooperative eventlet
314+
scheduling means this is the same safety pattern used by on_reset) and
315+
resumed afterwards only if it was running and the swap succeeded.
316+
"""
317+
global sim_running, sim_thread
318+
if not world:
319+
return
320+
backend = data.get("backend", "numpy")
321+
preset = data.get("preset", "default")
322+
device = data.get("device", "auto")
323+
324+
was_running = sim_running
325+
if was_running:
326+
sim_running = False
327+
time.sleep(0.12) # let the current tick finish before swapping
328+
329+
result = world.set_jepa_backend(backend=backend, preset=preset, device=device)
330+
331+
if was_running:
332+
sim_running = True
333+
sim_thread = eventlet.spawn(simulation_loop)
334+
335+
socketio.emit("jepa_status", result)
336+
337+
298338
# ---- God Mode ----
299339
@socketio.on("set_god_mode")
300340
def on_set_god_mode(data):

docs/validation.md

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -106,11 +106,15 @@ Full per-year trace is reproducible via `python test_macro.py | tee logs/validat
106106
(`latent_dim = 24` in `SharedWorldModel` defaults; `hidden_dim = 48`),
107107
versus millions of parameters in the published papers. v0.2.0 replaced
108108
v0.1's directional finite-difference gradient estimator with hand-written
109-
analytic back-propagation in pure NumPy, gradient-checked against finite
110-
differences to <1e-10; v0.2.1 made the training-batch sampling
111-
deterministic via a per-instance `RandomState`, removing global-RNG
112-
coupling. A PyTorch port for larger latent dimensions remains a v0.3
113-
candidate.
109+
analytic back-propagation in pure NumPy, gradient-checked against central
110+
finite differences to <1e-8 relative error
111+
([`test_world_model_gradcheck.py`](../test_world_model_gradcheck.py));
112+
v0.2.1 made the training-batch sampling deterministic via a per-instance
113+
`RandomState`, removing global-RNG coupling. v0.3.0 adds an opt-in PyTorch
114+
backend ([`world_model_torch.py`](../world_model_torch.py)) with autograd
115+
and optional CUDA for scaling `latent_dim` up; it reproduces the NumPy
116+
model at default settings (cross-backend encode/predict agree to <1e-4)
117+
and exposes the paper's Epps–Pulley SIGReg as an opt-in toggle.
114118
7. **Conceptual references vs. quantitative ones.** Diamond (1997),
115119
Dawkins (2009), Stringer (2012), and Marshak (2019) are cited at the
116120
*structural* level — the simulator implements the continental-axis

pyproject.toml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "world-genesis"
7-
version = "0.1.0"
7+
version = "0.3.0"
88
description = "A physics-based, AI-driven simulation of human civilization on Planet Earth — JEPA agents, macro ODE, emergent geopolitics, real Earth geography."
99
readme = "README.md"
1010
requires-python = ">=3.11"
@@ -62,6 +62,9 @@ viz = [
6262
"matplotlib>=3.8",
6363
"pandas>=2.2",
6464
]
65+
torch = [
66+
"torch>=2.2",
67+
]
6568

6669
[project.urls]
6770
Homepage = "https://www.geolambda.ai"
@@ -89,6 +92,7 @@ py-modules = [
8992
"sim_logger",
9093
"world",
9194
"world_model",
95+
"world_model_torch",
9296
]
9397

9498
[tool.pytest.ini_options]

requirements.txt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Python 3.11+ required — see pyproject.toml ([project] requires-python).
2-
# Optional extras: pip install -e ".[viz]" (matplotlib + pandas for scripts/figures.py)
3-
# pip install -e ".[dev]" (pytest + pytest-cov + ruff + mypy)
2+
# Optional extras: pip install -e ".[viz]" (matplotlib + pandas for scripts/figures.py)
3+
# pip install -e ".[dev]" (pytest + pytest-cov + ruff + mypy)
4+
# pip install -e ".[torch]" (PyTorch JEPA backend; CPU or CUDA)
45

56
numpy>=1.24.0
67
flask>=3.0.0
-17.2 KB
Binary file not shown.

0 commit comments

Comments
 (0)