worldgen

git clone https://git.godosa.eu/worldgen

master

raw ยท 5900 bytes

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)