diff options
| author | godosa <godosa@godosa.eu> | 2026-10-07 00:01:14 +0200 |
|---|---|---|
| committer | godosa <godosa@godosa.eu> | 2026-10-07 00:01:14 +0200 |
| commit | ed1dea2639b1191421de3986483aedcc14067a12 (patch) | |
| tree | 0118c6e119a84f1a9433ed0a304c8be1c9098463 /tests/test_engine.py | |
| download | worldhistory-ed1dea2639b1191421de3986483aedcc14067a12.tar.gz worldhistory-ed1dea2639b1191421de3986483aedcc14067a12.zip | |
worldhistory: initial public history
Diffstat (limited to 'tests/test_engine.py')
| -rw-r--r-- | tests/test_engine.py | 128 |
1 files changed, 128 insertions, 0 deletions
diff --git a/tests/test_engine.py b/tests/test_engine.py new file mode 100644 index 0000000..b0178e2 --- /dev/null +++ b/tests/test_engine.py @@ -0,0 +1,128 @@ +import os +import tempfile +import tomllib +import unittest +from pathlib import Path + +import numpy as np + +from tests.helpers import fields, globe_world, line_world, make_history, make_race +from worldhistory.config import CONDITIONS, link, load_config +from worldhistory.engine import Engine +from worldhistory.nudges import nudge_multipliers +from worldhistory.regions import Regions +from worldhistory.snapshot import load_snapshot + + +def small_setup(seed=1): + w = globe_world(1) + w.base["ocean"] = w.lat < -40 + w.eras["late"] = fields(w.n, ocean=w.lat < -30, gravity_g=np.where(w.lon > 90, 0.35, 1.0)) + h = make_history(run={"years": 60, "step": 10, "snapshot_every": 20, "seed": seed}, + world={"eras": ["late"]}, + regions={"zone": {"kind": "changed", "era": "late", "fields": ["gravity_g"], "land": "base"}}, + event=[{"year": 0, "kind": "seed"}, + {"year": 20, "kind": "cull", "region": "zone", "share": 0.6}, + {"year": 20, "kind": "era_switch", "era": "late"}], + hazard=[{"kind": "roaming", "start": 20, "home_region": "zone", "local": 3, "wanderers": 1}], + stats={"regions": ["zone"]}) + races = link(h, [make_race("a", seed={"clusters": 10, "heads": 500}, + demography={"density": 2.0, "growth": 0.1, "mobility": 0.1}), + make_race("b", seed={"clusters": 10, "heads": 500}, conflict={"border": 0.01}), + make_race("c", emerge={"parent": "a", "birth_min": 0.0, "birth_sure": 0.0, "birth_rate": 1.0, + "return_min": 1.0, "return_sure": 1.0})]) + return w, h, races + + +class EngineTest(unittest.TestCase): + def test_run_writes_outputs(self): + w, h, races = small_setup() + with tempfile.TemporaryDirectory() as d: + stats = Engine(w, h, races).run(d) + files = sorted(p.name for p in (Path(d) / "snap").iterdir()) + self.assertEqual(files, ["y0000.npz", "y0020.npz", "y0040.npz", "y0060.npz"]) + run = tomllib.loads((Path(d) / "run.toml").read_text()) + self.assertEqual(run["seed"], 1) + snap = load_snapshot(Path(d) / "snap" / "y0060.npz", w.n) + self.assertEqual(snap["races"], ["a", "b", "c"]) + self.assertTrue((Path(d) / "stats.json").exists()) + self.assertEqual([s["year"] for s in stats["steps"]], [0, 10, 20, 30, 40, 50, 60]) + self.assertEqual(stats["events"][0]["after"]["a"], 5000.0) # 10 clusters × 500 at the gifting + kinds = [e["kind"] for e in stats["events"]] + self.assertEqual(kinds, ["seed", "cull", "era_switch"]) + self.assertIn("zone", stats["steps"][-1]["regions"]) + self.assertEqual(len(stats["emergence"]), 1) # exposure 10 yrs → c emerges once + self.assertGreater(stats["steps"][-1]["pop"]["c"], 0) + self.assertEqual(stats["steps"][-1]["family"]["a"], + stats["steps"][-1]["pop"]["a"] + stats["steps"][-1]["pop"]["c"]) + + def test_no_birth_where_native_curves_do_not_fit(self): + w, h, _ = small_setup() + races = link(h, [make_race("a", seed={"clusters": 10, "heads": 500}, + demography={"density": 2.0, "growth": 0.1, "mobility": 0.1}), + make_race("c", emerge={"parent": "a", "birth_min": 0.0, "birth_sure": 0.0, + "birth_rate": 1.0}, + tolerance={"temperature": {"optimum": -80, "width": 1, "lo": -90, "hi": 60}})]) + with tempfile.TemporaryDirectory() as d: + stats = Engine(w, h, races).run(d) + self.assertEqual(stats["emergence"], []) + + def test_progress_and_returns_in_stats(self): + w, h, races = small_setup() + with tempfile.TemporaryDirectory() as d: + stats = Engine(w, h, races).run(d) + self.assertIn("a>c", stats["steps"][-1]["progress"]) + self.assertIsInstance(stats["returns"], list) + self.assertIsInstance(stats["raids"], list) + self.assertIn("a", stats["emergence"][0]) + + def test_deterministic(self): + a = Engine(*small_setup(1)) + b = Engine(*small_setup(1)) + c = Engine(*small_setup(2)) + for e in (a, b, c): + for _ in range(5): + e.step() + np.testing.assert_array_equal(a.state.P, b.state.P) + self.assertFalse(np.array_equal(a.state.P, c.state.P)) + + def test_depth_creep_rate(self): + n = 16 + w = line_world(n, ocean=[False] + [True] * (n - 1), elevation_m=[10] + [-100.0 * i for i in range(1, n)]) + tol = {"depth": {"optimum": 0, "width": 30, "lo": 0, "hi": 5000, "rate": 2.0}} + h = make_history(run={"years": 600, "step": 10, "seed": 1}, event=[]) + r = make_race("d", habitat={"realm": "both", "terms": [{"p": "land", "w": 1}, {"p": "sea", "w": 1}]}, + demography={"density": 5.0, "growth": 0.3, "founder": 2.0, "mobility": 0.2}, tolerance=tol) + e = Engine(w, h, link(h, [r])) + e.state.P[0, 0] = 1000.0 + front = [] + for _ in range(60): + e.step() + occ = np.flatnonzero(e.state.P[0] >= 1) + front.append(100.0 * occ.max()) + self.assertEqual(front, sorted(front)) # only deeper with time + self.assertGreater(front[-1], 0.4 * 2.0 * 600) # ≈ rate × years (1,200 m) + self.assertLess(front[-1], 1.3 * 2.0 * 600) + + def test_nudges(self): + w = line_world(3) + g, c, x, a = nudge_multipliers([{"race": "a", "region": "all", "years": [0, 100], "growth": 1.1, + "capacity": 1.2, "expansion": 1.3, "adapt": 2.0}], + [make_race("a")], Regions(w, {}), 50, 3) + np.testing.assert_allclose(g, 1.1) + np.testing.assert_allclose(c, 1.2) + np.testing.assert_allclose(a, 2.0) + g, c, x, a = nudge_multipliers([{"race": "a", "region": "all", "years": [0, 10], "growth": 1.1, "capacity": 1, + "expansion": 1, "adapt": 1}], [make_race("a")], Regions(w, {}), 50, 3) + np.testing.assert_allclose(g, 1.0) + + +@unittest.skipUnless(os.environ.get("WORLDHISTORY_SMOKE_WORLD"), "set WORLDHISTORY_SMOKE_WORLD and _CONFIG") +class SmokeTest(unittest.TestCase): + def test_100_years_on_a_real_world(self): + from worldhistory.world import load_world + h, races = load_config(os.environ["WORLDHISTORY_SMOKE_CONFIG"]) + w = load_world(os.environ["WORLDHISTORY_SMOKE_WORLD"], eras=h.eras, extra_fields=h.world_fields) + with tempfile.TemporaryDirectory() as d: + stats = Engine(w, h, races).run(d, years=100) + self.assertGreater(sum(stats["steps"][-1]["pop"].values()), 0) |
