1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
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)
|