worldmap-viewer

git clone https://git.godosa.eu/worldmap-viewer

master

raw · 9742 bytes

import unittest

import numpy as np

import refine as RF
import seafloor as SF
from mapgen.graph import priority_flood
from mapgen.sphere import gc_dist_km, latlon_to_xyz
from tests.test_refine import square

R = 12742.0


def grid(lat=0.0, lon=0.0, d=4.0):
    (cells,) = RF.area_cells([square(lat, lon, d)], 5)
    return RF.subgrid(cells, R)


def shelf():
    """A res-5 square across a coast: land west of 3° W, a shelf, a slope with gullies, an abyssal plain with a deep
    hollow (≈ 300 m, at 0°, 2.5° E) and a shallow one (≈ 30 m, at 2.5° S, 1.5° E)."""
    g, halo = grid()
    x = g.lon
    z = np.where(x < -3.0, 200.0,
                 np.where(x < -1.5, -100.0 - 100.0 * (x + 3.0) / 1.5,
                          np.where(x < 0.0, -200.0 - 3800.0 * (x + 1.5) / 1.5, -4000.0 - 75.0 * x)))
    z = z + np.where((x >= -1.5) & (x < 0.0), 40.0 * np.sin(np.radians(g.lat) * 400.0), 0.0)   # gullies
    for la, lo, r_km, depth in ((0.0, 2.5, 60.0, 300.0), (-2.5, 1.5, 40.0, 30.0)):
        d = gc_dist_km(g.xyz, latlon_to_xyz(la, lo), R)
        z = z - depth * np.clip(1.0 - (d / r_km) ** 2, 0.0, 1.0)
    return g, halo, z, z <= 0


class CanyonTest(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        cls.g, cls.halo, cls.z, cls.sea = shelf()
        cls.zc, cls.cut, cls.fan = SF.canyons(cls.g, cls.z, cls.sea, cls.halo)

    def test_canyons_cut_the_slope(self):
        self.assertGreater(self.cut.max(), 50.0)
        self.assertLessEqual(self.cut.max(), SF.CANYON["cap_m"])
        slope = (self.g.lon > -1.5) & (self.g.lon < 0.0)
        self.assertGreater(self.cut[slope].max(), self.cut[~slope].max(initial=0.0))

    def test_only_lower_in_canyons_and_raise_at_most_100_m_on_fans(self):
        dz = self.zc - self.z
        self.assertGreaterEqual(dz.min(), -SF.CANYON["cap_m"] - 1e-9)
        self.assertLessEqual(dz.max(), SF.FAN["max_m"] + 1e-9)
        np.testing.assert_array_equal(dz < 0, self.cut > 0)
        self.assertTrue(np.all(self.fan[dz > 0] > 0))
        self.assertGreater(self.fan.max(), 0.0, "fans where the flows stop")

    def test_land_and_halo_stay(self):
        keep = ~self.sea | self.halo
        np.testing.assert_array_equal(self.zc[keep], self.z[keep])
        self.assertTrue(np.all(self.zc[self.sea] <= np.maximum(self.z[self.sea], SF.SEA_TOP_M)))

    def test_deterministic(self):
        again = SF.canyons(self.g, self.z, self.sea, self.halo)
        for a, b in zip(again, (self.zc, self.cut, self.fan)):
            np.testing.assert_array_equal(a, b)


class PondTest(unittest.TestCase):
    def setUp(self):
        self.g, self.halo, self.z, self.sea = shelf()
        self.sed = np.full(self.g.n, 500.0)

    def test_deep_hollows_fill_flat_below_their_spill(self):
        zp, fill = SF.ponds(self.g, self.z, self.sea, self.halo, self.sed)
        zf = priority_flood(self.g, np.where(self.sea, self.z, SF.WALL_M), self.sea & self.halo, eps=0.0)
        self.assertTrue(np.all(zp <= np.maximum(zf, self.z) + 1e-9), "never above the spill level")
        self.assertTrue(np.all(zp >= self.z))
        np.testing.assert_allclose(fill, zp - self.z)
        deep = gc_dist_km(self.g.xyz, latlon_to_xyz(0.0, 2.5), R) < 30.0
        self.assertTrue(np.all(fill[deep] > 0))
        self.assertLess(float(np.ptp(zp[deep])), 1e-6, "a flat floor")
        self.assertAlmostEqual(float(zp[deep][0]), float(self.z[deep].min()) + 0.3 * 500.0, delta=1.0)
        shallow = gc_dist_km(self.g.xyz, latlon_to_xyz(-2.5, 1.5), R) < 20.0
        self.assertTrue(np.all(fill[shallow] == 0), "hollows under 50 m stay as they are")

    def test_no_sea_is_a_no_op(self):
        z = np.full(self.g.n, 300.0)
        sea = np.zeros(self.g.n, bool)
        zc, cut, fan = SF.canyons(self.g, z, sea, self.halo)
        zp, fill = SF.ponds(self.g, z, sea, self.halo, self.sed)
        np.testing.assert_array_equal(zc, z)
        np.testing.assert_array_equal(zp, z)
        self.assertFalse(cut.any() or fan.any() or fill.any())

    def test_sea_without_halo_sea_still_ponds(self):
        d = gc_dist_km(self.g.xyz, latlon_to_xyz(0.0, 0.0), R)
        z = np.where(d < 200.0, -1000.0 - 300.0 * np.clip(1.0 - (d / 100.0) ** 2, 0.0, 1.0), 500.0)
        sea = z <= 0
        self.assertFalse((sea & self.halo).any())
        zp, fill = SF.ponds(self.g, z, sea, self.halo, self.sed)
        zc, _, _ = SF.canyons(self.g, z, sea, self.halo)
        self.assertTrue(np.all(np.isfinite(zp)) and np.all(np.isfinite(zc)))


from mapgen import plateaus as PL, seabed as SB

PLAT = {"name": "p", "center": [0.0, 0.0], "area_km2": 2.0e5, "top_m": [1500.0, 2500.0]}


def world_values(n=1):
    return {"vent_potential": np.full(n, 0.8), "seabed_type": np.full(n, SB.SB_CONTINENTAL, np.int8),
            "seabed_mineral": np.zeros(n, np.int8), "bottom_temp_c": np.full(n, 3.0),
            "sediment_m": np.full(n, 400.0), "d_div_km": np.full(n, 900.0), "T_mean": np.full(n, 10.0)}


class CellDrawTest(unittest.TestCase):
    def test_cell_draws_are_uniform_and_stable(self):
        ids = np.arange(10 ** 5, dtype=np.uint64) * np.uint64(7919) + np.uint64(612345678901)
        u = SF._u(ids, 3)
        self.assertTrue(0.0 <= u.min() and u.max() < 1.0)
        self.assertAlmostEqual(float(u.mean()), 0.5, delta=0.01)
        np.testing.assert_array_equal(SF._u(ids, 3), u)
        self.assertFalse(np.array_equal(SF._u(ids, 4), u))


class VolcanicTest(unittest.TestCase):
    def test_fine_features_on_the_plateau_only(self):
        g, halo = grid()
        pid = PL.cell_ids(g.xyz, [PLAT], 7, R)
        add, near = SF.volcanic(g, np.ones(g.n, bool), halo, pid, [PLAT], 7)
        self.assertGreater(add.max(), 150.0)
        self.assertTrue(np.all(add[pid < 0] == 0) and np.all(near[pid < 0] == 0))
        self.assertTrue(np.all(add[halo] == 0))

    def test_features_and_vents_do_not_depend_on_the_area(self):
        outs = []
        for g, h in (grid(0.0, 0.0, 4.0), grid(0.5, 0.5, 3.0)):
            pid = PL.cell_ids(g.xyz, [PLAT], 7, R)
            sea = np.ones(g.n, bool)
            add, near = SF.volcanic(g, sea, h, pid, [PLAT], 7)
            _, v = SF.vents(g, np.full(g.n, -2000.0) + add, sea, h, np.full(g.n, 0.8), near, np.full(g.n, 500.0),
                            np.full(g.n, 100.0), np.full(g.n, 2.0), 7)
            outs.append((g, h, add, v))
        (ga, ha, aa, va), (gb, hb, ab, vb) = outs
        ca, cb = np.flatnonzero(~ha), np.flatnonzero(~hb)
        common, ia, ib = np.intersect1d(ga.ids[ca], gb.ids[cb], return_indices=True)
        self.assertGreater(len(common), 100)
        np.testing.assert_allclose(aa[ca[ia]], ab[cb[ib]], atol=1e-9)
        np.testing.assert_array_equal(np.intersect1d(va["cell"], common), np.intersect1d(vb["cell"], common))

    def test_hidden_plateaus_stay_500_m_down_and_no_sea_rises_to_land(self):
        g, halo = grid()
        z = np.full(g.n, -600.0)
        sea = np.ones(g.n, bool)
        zn, f, v = SF.run(g, z, sea, halo, np.zeros(g.n, np.int64), world_values(), [PLAT], 7)
        on = (f["plateau_id"] >= 0) & ~halo
        self.assertTrue(on.any())
        self.assertLessEqual(float(zn[on].max()), -500.0)
        isl = {**PLAT, "islands": True}
        zi, fi, _ = SF.run(g, z, sea, halo, np.zeros(g.n, np.int64), world_values(), [isl], 7)
        self.assertLessEqual(float(zi.max()), SF.SEA_TOP_M)
        np.testing.assert_array_equal(zn[halo], z[halo])


class VentTest(unittest.TestCase):
    def test_vents_only_in_the_sea_and_smokers_on_the_ridge(self):
        g, halo = grid()
        z = np.where(g.lon < -3.0, 100.0, -2500.0)
        sea = z <= 0
        d_div = np.abs(g.lon - 2.0) * 111.0                    # a ridge axis along 2° E
        pot = np.where(sea, np.clip(1.0 - d_div / 300.0, 0.05, 1.0), 0.0)
        st, v = SF.vents(g, z, sea, halo, pot, np.zeros(g.n), d_div, np.full(g.n, 1500.0), np.full(g.n, 2.0), 7)
        cells = np.searchsorted(g.ids, v["cell"])
        self.assertGreater(len(cells), 0)
        self.assertTrue(np.all(sea[cells] & ~halo[cells] & (z[cells] < -SF.VENT["min_depth_m"])))
        self.assertTrue(np.all(v["type"][d_div[cells] < SF.VENT["ridge_km"]] == 0), "black smokers on the ridge")
        t, temp = v["type"], v["temp_c"]
        self.assertTrue(np.all((temp[t == 0] >= 300) & (temp[t == 0] <= 400)))
        self.assertTrue(np.all(st[~sea] == 0) and st.max() <= 1.0)
        np.testing.assert_array_equal(v["mineral"], v["type"])


class FieldsTest(unittest.TestCase):
    def test_fields_zero_on_land_and_features_mark_the_floor(self):
        pv = {"seabed_type": np.array([SB.SB_CLAY, SB.SB_NONE, SB.SB_CLAY, SB.SB_CLAY, SB.SB_NONE], np.int8),
              "seabed_mineral": np.zeros(5, np.int8), "bottom_temp_c": np.array([2.0, 0.0, 2.0, 2.0, 0.0]),
              "sediment_m": np.array([400.0, 0.0, 400.0, 400.0, 0.0]), "T_mean": np.array([10.0, 12.0, 10.0, 10.0, 12.0])}
        sea = np.array([True, True, True, True, False])
        z0 = np.zeros(5)
        f = SF.fields(pv, sea, np.zeros(5, bool), np.array([0.0, 0, 100, 0, 0]), np.array([0.0, 0, 0, 5, 0]), z0,
                      np.array([0.0, 0, 0, 0, 0]), np.array([0.9, 0, 0, 0, 0]))
        np.testing.assert_array_equal(f["seabed_type"], [SB.SB_VENTS, SB.SB_TERRIGENOUS, SB.SB_TERRIGENOUS,
                                                         SB.SB_TERRIGENOUS, SB.SB_NONE])
        self.assertEqual(int(f["seabed_mineral"][0]), SB.MI_SULFIDES)
        np.testing.assert_allclose(f["sediment_m"], [0.0, 300.0, 120.0, 405.0, 0.0])
        np.testing.assert_allclose(f["bottom_temp_c"], [20.0, 12.0, 2.0, 2.0, 0.0])
        np.testing.assert_allclose(f["vent"], [0.9, 0, 0, 0, 0], rtol=1e-6)
        np.testing.assert_allclose(f["canyon_m"], [0, 0, 100, 0, 0])