aboutsummaryrefslogtreecommitdiffziptar.gz
path: root/tests/test_events.py
diff options
context:
space:
mode:
Diffstat (limited to 'tests/test_events.py')
-rw-r--r--tests/test_events.py121
1 files changed, 121 insertions, 0 deletions
diff --git a/tests/test_events.py b/tests/test_events.py
new file mode 100644
index 0000000..1b3d51a
--- /dev/null
+++ b/tests/test_events.py
@@ -0,0 +1,121 @@
+import unittest
+
+import numpy as np
+
+from mapgen import events as EV
+from mapgen.pipeline import StageError
+from mapgen.sphere import east_north, gc_dist_km, great_circle_point, latlon_to_xyz
+from tests.helpers import small_grid
+
+R = 12742.0
+CUT = {"name": "cut", "kind": "disintegrate", "center": [0.0, 0.0], "radius_km": 3000.0, "depth_m": 3000.0}
+
+
+class DisintegrateTest(unittest.TestCase):
+ def test_bowl_only_lowers_and_reaches_its_depth(self):
+ g = small_grid(3)
+ d = gc_dist_km(g.xyz, latlon_to_xyz(0.0, 0.0), R)
+ z = np.where(d < 4000, 800.0, -4000.0)
+ z2, z_ref = EV.disintegrate(g.xyz, z, z > 0, CUT, R)
+ self.assertAlmostEqual(z_ref, 800.0)
+ self.assertTrue(np.all(z2 <= z))
+ np.testing.assert_array_equal(z2[d >= 3000], z[d >= 3000])
+ c = int(np.argmin(d))
+ self.assertAlmostEqual(z2[c], 800.0 - 3000.0 * np.sqrt(1 - (d[c] / 3000.0) ** 2), places=6)
+
+
+ def test_disintegration_over_open_sea_has_no_rim_height(self):
+ g = small_grid(3)
+ z2, z_ref = EV.disintegrate(g.xyz, np.full(g.n, -1000.0), np.zeros(g.n, bool), CUT, R)
+ self.assertIsNone(z_ref)
+ self.assertLess(float(z2[g.cell_index(0.0, 0.0)]), -2990.0, "the bowl hangs from 0 m")
+
+ def test_heights_warn_when_a_cut_has_no_rim_land(self):
+ from mapgen import eras as ER
+ g = small_grid(3)
+ logs = []
+ ER._heights(g, np.full(g.n, -1000.0), [dict(CUT)], 5.0e6, logs.append, "x")
+ self.assertTrue(any("no land in its rim" in m for m in logs), logs)
+
+
+class VolcanoTest(unittest.TestCase):
+ def test_shapes_and_modes(self):
+ g = small_grid(3)
+ d = gc_dist_km(g.xyz, latlon_to_xyz(0.0, 90.0), R)
+ c = int(np.argmin(d))
+ z = np.full(g.n, -3000.0)
+ above = EV.volcano(g.xyz, z, {"center": [0.0, 90.0], "radius_km": 900.0, "peak_m": 2000.0}, R)
+ self.assertTrue(np.all(above >= z))
+ self.assertAlmostEqual(above[c], -3000.0 + 2000.0 * (1 - d[c] / 900.0) ** 1.5)
+ absolute = EV.volcano(g.xyz, z, {"center": [0.0, 90.0], "radius_km": 900.0, "peak_m": 1500.0,
+ "peak_mode": "absolute"}, R)
+ self.assertGreater(absolute[c], 0.0, "a volcano above 0 m makes land")
+ np.testing.assert_array_equal(absolute[d >= 900.0], z[d >= 900.0])
+ cal = EV.volcano(g.xyz, np.zeros(g.n), {"center": [0.0, 90.0], "radius_km": 2000.0, "peak_m": 2000.0,
+ "shape": "caldera"}, R)
+ ring = (d > 300.0) & (d < 700.0)
+ self.assertLess(cal[c], cal[ring].max(), "a caldera's middle sits below its rim")
+
+
+class ZoneWeightTest(unittest.TestCase):
+ def test_sharp_circle_edge_ramps_over_edge_km(self):
+ ev = {"name": "z", "shape": "circle", "center": [10.0, 20.0], "radius_km": 800.0, "edge_km": 5.0}
+ p0 = latlon_to_xyz(10.0, 20.0)
+ e, n = east_north(p0[None])
+ s = np.arange(780.0, 810.0, 0.5)
+ pts = np.array([great_circle_point(p0, e[0], si, R) for si in s])
+ w = EV.zone_weight(pts, ev, R, 1296)
+ self.assertTrue(np.all(w[s <= 794.5] == 1.0))
+ self.assertTrue(np.all(w[s >= 800.5] == 0.0))
+ ramp = s[(w > 0) & (w < 1)]
+ self.assertLessEqual(ramp.max() - ramp.min(), 5.0)
+
+ def test_landmass_reach_varies_between_its_bounds(self):
+ g = small_grid(3)
+ land = (np.abs(g.lat) < 20) & (np.abs(g.lon) < 30)
+ ev = {"name": "aura", "shape": "landmass", "seed": [0.0, 0.0], "reach_km": [500.0, 1000.0], "edge_km": 5.0}
+ lm = EV.landmass(g, land, ev["seed"], ev["name"])
+ np.testing.assert_array_equal(lm, land)
+ w = EV.zone_weight(g.xyz, ev, R, 1296, g.xyz[lm])
+ self.assertTrue(np.all(w[land] == 1.0))
+ from scipy.spatial import cKDTree
+ chord, _ = cKDTree(g.xyz[lm]).query(g.xyz)
+ d = 2 * np.arcsin(chord / 2) * R
+ self.assertTrue(np.all(w[d > 1000.0] == 0.0))
+ self.assertTrue(np.all(w[(d > 0) & (d < 495.0)] == 1.0))
+
+ def test_landmass_seed_in_the_sea_names_the_event(self):
+ g = small_grid(3)
+ land = (np.abs(g.lat) < 20) & (np.abs(g.lon) < 30)
+ with self.assertRaises(StageError) as cm:
+ EV.landmass(g, land, [0.0, 150.0], "impact-aftermath")
+ self.assertIn("impact-aftermath", str(cm.exception))
+ self.assertIn("nearest land", str(cm.exception))
+
+
+class ApplyZoneTest(unittest.TestCase):
+ EV = {"name": "aura", "fields": {"gravity_g": 0.35, "pressure_bar": 2.0, "o2_fraction": 0.35,
+ "fire_reactivity": 0.5}}
+
+ def base(self, n=4):
+ return {"gravity_g": np.full(n, 1.05, np.float32), "o2_fraction": np.array([0.21, 0.3, 0.1, 0.21]),
+ "pressure_bar": np.array([1.0, 1.0, np.exp(-1), 1.0]), "fire_reactivity": np.ones(n),
+ "po2_bar": np.array([0.21, 0.3, 0.1 * np.exp(-1), 0.21])}
+
+ def test_zone_values_are_absolute_over_any_base(self):
+ f = self.base()
+ w = np.array([1.0, 1.0, 1.0, 0.0])
+ z = np.array([0.0, -50.0, 8000.0, 0.0])
+ out = EV.apply_zone(f, w, self.EV, z, 8000.0)
+ np.testing.assert_allclose(out["o2_fraction"][:3], 0.35)
+ np.testing.assert_allclose(out["gravity_g"][:3], 0.35, rtol=1e-6)
+ np.testing.assert_allclose(out["pressure_bar"][:3], [2.0, 2.0, 2.0 * np.exp(-1)])
+ np.testing.assert_allclose(out["po2_bar"], out["o2_fraction"] * out["pressure_bar"])
+ self.assertEqual(out["gravity_g"].dtype, np.float32)
+ for k in out:
+ self.assertEqual(out[k][3], f[k][3], f"{k}: untouched outside the zone")
+
+ def test_half_weight_blends(self):
+ out = EV.apply_zone(self.base(), np.full(4, 0.5), self.EV, np.zeros(4), 8000.0)
+ self.assertAlmostEqual(float(out["fire_reactivity"][0]), 0.75)
+ self.assertAlmostEqual(float(out["pressure_bar"][0]), 1.5)