aboutsummaryrefslogtreecommitdiffziptar.gz
path: root/tests/test_events.py
blob: 1b3d51ab0133ce11bc4301a9cf140ac988f12319 (plain)
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)