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