From 346b1c5195bffc71ceaa9262453e3c189656400b Mon Sep 17 00:00:00 2001 From: godosa Date: Tue, 6 Oct 2026 23:52:03 +0200 Subject: worldgen: initial public history --- tests/test_environment.py | 173 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 173 insertions(+) create mode 100644 tests/test_environment.py (limited to 'tests/test_environment.py') diff --git a/tests/test_environment.py b/tests/test_environment.py new file mode 100644 index 0000000..0f4c5c6 --- /dev/null +++ b/tests/test_environment.py @@ -0,0 +1,173 @@ +import unittest + +import numpy as np + +from mapgen import environment as EN +from tests.helpers import make_ctx + + +def name(bio, p, tmin): + z, _ = EN.holdridge(np.array([bio]), np.array([p]), np.array([tmin])) + return EN.HOLDRIDGE_NAMES[z[0]] + + +class HoldridgeTest(unittest.TestCase): + def test_count(self): + self.assertEqual(len(EN.HOLDRIDGE_NAMES), 38) + + def test_table(self): + self.assertEqual(name(26, 3000, 20), "tropical moist forest") + self.assertEqual(name(26, 9000, 20), "tropical rain forest") + self.assertEqual(name(26, 100, 20), "tropical desert") + self.assertEqual(name(0.5, 100, -30), "polar desert") + self.assertEqual(name(8, 300, -10), "cool temperate steppe") + self.assertEqual(name(15, 1500, -5), "warm temperate moist forest") + self.assertEqual(name(15, 1500, 5), "subtropical moist forest") + self.assertEqual(name(4, 700, -20), "boreal wet forest") + self.assertEqual(name(2, 200, -25), "subpolar moist tundra") + + def test_polar_desert_with_summer_monsoon(self): + tag = EN.seasonality(np.array([180.0, 20.0]), np.array([20.0, 180.0]), np.array([80.0, 80.0]), + np.array([-30.0, -30.0]), np.array([100.0, 100.0]), EN.DEFAULTS) + self.assertEqual(tag[0], EN.SEAS_W) # NH: wet June (summer), dry December + self.assertEqual(tag[1], EN.SEAS_S) # NH: dry summer + sh = EN.seasonality(np.array([20.0]), np.array([180.0]), np.array([-80.0]), np.array([-30.0]), + np.array([100.0]), EN.DEFAULTS) + self.assertEqual(sh[0], EN.SEAS_W) # SH summer is December + + def test_tropical_monsoon(self): + tag = EN.seasonality(np.array([3000.0]), np.array([200.0]), np.array([15.0]), np.array([20.0]), + np.array([1800.0]), EN.DEFAULTS) + self.assertEqual(tag[0], EN.SEAS_M) + + +class StageTest(unittest.TestCase): + def test_run(self): + ctx = make_ctx(3) + g = ctx.grid + n = g.n + land = (np.abs(g.lat) < 40) & (np.abs(g.lon) < 60) + z = np.where(land, 300.0, -4000.0) + z = np.where(land & (np.abs(g.lon) < 4), 4000.0, z) + zeros = np.zeros(n) + ctx.data.update({ + "elevation_eroded_m": z, "continental": land, "age_class": np.where(land, 1, 0).astype(np.int8), + "d_over_km": np.full(n, np.inf), "T_mean": np.where(np.abs(g.lat) > 30, -5.0, 20.0), + "T_min": np.full(n, -10.0), "T_range": np.full(n, 20.0), "P_ann": np.full(n, 800.0), + "P_jun": np.full(n, 800.0), "P_dec": np.full(n, 800.0), "biotemp": np.full(n, 10.0), + "dist_ocean_km": np.where(land, 1000.0, 0.0), "river": np.zeros(n, bool), + "strahler": np.zeros(n, np.int8), "discharge_km3_yr": zeros, "lake": np.zeros(n, bool), + "salt_flat": np.zeros(n, bool)}) + out = EN.run(ctx) + self.assertEqual(out["landform"][g.cell_index(0.0, 0.0)], EN.LF_MOUNTAINS) + self.assertEqual(out["landform"][g.cell_index(0.0, 150.0)], EN.LF_OCEAN) + self.assertEqual(out["ground"][g.cell_index(35.0, 40.0)], EN.GR_PERMAFROST) + for k in ("holdridge", "hold_region", "seasonality", "landform", "relief_m", "lithology", "ground", + "coal_potential", "iron_potential"): + self.assertEqual(len(out[k]), n) + + +class OceanMaskEnvTest(unittest.TestCase): + def test_inland_basin_not_ocean_landform(self): + ctx = make_ctx(3) + g = ctx.grid + n = g.n + land = (np.abs(g.lat) < 40) & (np.abs(g.lon) < 60) + z = np.where(land, 300.0, -4000.0) + basin = (np.abs(g.lat) < 6) & (np.abs(g.lon) < 6) + z[basin] = -30.0 + zeros = np.zeros(n) + ctx.data.update({ + "elevation_eroded_m": z, "ocean": ~land, "continental": land, "age_class": np.where(land, 1, 0).astype(np.int8), + "d_over_km": np.full(n, np.inf), "T_mean": np.full(n, 20.0), "T_min": np.full(n, 10.0), + "T_range": np.full(n, 10.0), "P_ann": np.full(n, 800.0), "P_jun": np.full(n, 800.0), + "P_dec": np.full(n, 800.0), "biotemp": np.full(n, 20.0), "dist_ocean_km": np.where(land, 1000.0, 0.0), + "river": np.zeros(n, bool), "strahler": np.zeros(n, np.int8), "discharge_km3_yr": zeros, + "lake": np.zeros(n, bool), "salt_flat": np.zeros(n, bool)}) + out = EN.run(ctx) + self.assertFalse(np.any(out["landform"][basin] == EN.LF_OCEAN)) + + +class ReliefResolutionTest(unittest.TestCase): + def test_relief_window_is_in_km(self): + from tests.helpers import small_grid + med = [] + for res in (2, 3): + g = small_grid(res) + z = 1000.0 * np.sin(10.0 * g.xyz[:, 0]) + 800.0 * np.cos(9.0 * g.xyz[:, 1]) + _, relief = EN.landform(g, z, np.ones(g.n, np.int8), np.ones(g.n, np.int8), np.full(g.n, np.inf), + np.full(g.n, 800.0), np.zeros(g.n, bool), relief_km=700.0) + med.append(np.median(relief)) + self.assertAlmostEqual(med[0] / med[1], 1.0, delta=0.3) + + +class WetlandRuleTest(unittest.TestCase): + def test_wetlands_need_real_rivers_on_real_flats(self): + from types import SimpleNamespace + from mapgen import graph as G + g = small_grid_env() + n = g.n + z = np.where(g.lat > -30, 100.0 + 0.0001 * g.lat, -3000.0) # a very flat humid plain + common = dict(lit=np.full(n, EN.LI_GRANITE, np.int8), t_mean=np.full(n, 15.0), t_min=np.full(n, 5.0), + p_ann=np.full(n, 1200.0), dist_ocean=np.full(n, 2000.0), river=np.zeros(n, bool), + strahler=np.zeros(n, np.int8), lake=np.zeros(n, bool), salt_flat=np.zeros(n, bool)) + def wet(q): + gr = EN.ground(g, z, common["lit"], common["t_mean"], common["t_min"], common["p_ann"], common["dist_ocean"], + common["river"], common["strahler"], np.full(n, q), common["lake"], common["salt_flat"], + EN.DEFAULTS, g.lat <= -30) + return np.mean(gr[g.lat > 0] == EN.GR_WETLAND) + self.assertLess(wet(3.0), 0.05) # small streams don't make a wetland + self.assertGreater(wet(8.0), 0.9) # a big river across a flat does + + +def small_grid_env(): + from tests.helpers import small_grid + return small_grid(3) + + +class DepositsTest(unittest.TestCase): + def test_shares_rules_and_main(self): + from mapgen import minerals as MN + from mapgen import environment as EN + from mapgen.crust import CRATON, POST_OROGEN + g = make_ctx(4).grid + n = g.n + land = np.abs(g.lat) < 60 + mountain = land & (g.lon > 0) & (g.lon < 60) + f = {"land": land, "z": np.where(mountain, 3500.0, 200.0), "lit": np.where(mountain, EN.LI_METAMORPHIC, EN.LI_SANDSTONE).astype(np.int8), + "age": np.where(mountain, POST_OROGEN, CRATON), "lf": np.where(mountain, EN.LF_MOUNTAINS, EN.LF_PLAIN), + "relief": np.where(mountain, 2500.0, 50.0), "t_mean": np.full(n, 15.0), "p_ann": np.full(n, 900.0), + "d_coll": np.full(n, np.inf), "salt": np.zeros(n, bool), "endo": np.zeros(n, bool), + "dist_ocean": np.full(n, 500.0), "coal": land & ~mountain} + bits, main = MN.place(g, f, 7) + self.assertEqual(len(MN.DEPOSITS), 32) + self.assertFalse(bits[~land].any()) + for name in ("ironstone", "oil_gas", "iron_high", "tungsten"): + has = (bits & np.uint32(MN.BIT[name])) > 0 + share = dict((d[0], d[2]) for d in MN.DEPOSITS)[name] + self.assertLessEqual(has.sum(), round(share * land.sum()) + 1, name) + self.assertTrue(has.any(), name) + for name in MN.DEEP[:2]: # the good stuff: in the mountains only here + has = (bits & np.uint32(MN.BIT[name])) > 0 + self.assertTrue(np.all(mountain[has]), name) + some = bits > 0 + self.assertTrue(np.all(main[some] > 0) and np.all(main[~some] == 0)) + k = np.flatnonzero(some)[0] + self.assertTrue(bits[k] & np.uint32(1 << (int(main[k]) - 1))) + + def test_deposits_are_sprinkled_not_one_hotspot(self): + from mapgen import minerals as MN + g = make_ctx(4).grid + land = np.abs(g.lat) < 60 + hot = land & (g.lat > 0) & (g.lat < 30) & (g.lon > 0) & (g.lon < 40) # a world-class district + score = np.where(hot, 3.0, np.where(land, 1.0, 0.0)) + prov = MN.provinces(g, 7) + sel = MN._rank_select(score, 0.02, land, prov) + self.assertEqual(sel.sum(), round(0.02 * land.sum())) + self.assertFalse(sel[~land].any()) + self.assertGreater((sel & hot).sum(), 0.3 * sel.sum()) # the district keeps the most + self.assertGreater((sel & ~hot).sum(), 0.3 * sel.sum()) # the rest is sprinkled + self.assertGreater(len(np.unique(prov[sel & ~hot])), 0.3 * len(np.unique(prov[land & ~hot]))) + self.assertFalse(MN._rank_select(np.where(hot, 1.0, 0.0), 0.02, land, prov)[~hot].any()) # rules still rule + poor = MN._rank_select(np.where(hot, 10.0, np.where(land, 1.0, 0.0)), 0.02, land, prov) + self.assertFalse(poor[~hot].any()) # far poorer ground: no local mines -- cgit