aboutsummaryrefslogtreecommitdiffziptar.gz
path: root/tests/test_erosion.py
blob: 2adefc04f68a6e0dba2f14c0c9e2f2b07a4b34db (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
import unittest

import numpy as np

from mapgen import erosion as ER
from mapgen.crust import CRATON
from tests.helpers import make_ctx


def mountain_ctx(k=None):
    cfg = {"build": {"land_fraction": 0.05}}
    if k is not None:
        cfg["erosion"] = {"k": k}
    ctx = make_ctx(3, cfg=cfg)
    g = ctx.grid
    d = g.radius_km * np.arccos(np.clip(g.xyz @ g.xyz[g.cell_index(0.0, 0.0)], -1, 1))
    z = np.where(d < 6000, 6000 * np.clip(1 - d / 6000, 0, None) ** 1.2 + 50, -4000.0)
    ctx.data.update({"elevation_m": z.astype(np.float32), "age_class": np.full(g.n, CRATON, np.int8)})
    return ctx, z


class ErosionTest(unittest.TestCase):
    def test_erode_lowers_land(self):
        ctx, z0 = mountain_ctx()
        kmult = np.full(ctx.grid.n, 1.5)
        z1 = ER.erode(ctx.grid, z0, kmult, ER.DEFAULTS)
        land = z0 > 0
        self.assertLess(z1[land].mean(), z0[land].mean())
        self.assertLessEqual(z1.max(), z0.max() + 1e-6)

    def test_stage_keeps_land_fraction(self):
        ctx, _ = mountain_ctx()
        z = ER.run(ctx)["elevation_eroded_m"]
        g = ctx.grid
        self.assertTrue(np.all(np.isfinite(z)))
        self.assertAlmostEqual(g.area_km2[z > 0].sum() / g.area_km2.sum(), 0.05, delta=0.01)

    def test_stable_with_huge_k(self):
        ctx, _ = mountain_ctx(k=1e3)
        z = ER.run(ctx)["elevation_eroded_m"]
        self.assertTrue(np.all(np.isfinite(z)))
        self.assertGreaterEqual(z.min(), -11000)

    def test_stage_outputs_connected_ocean(self):
        ctx, z0 = mountain_ctx()
        g = ctx.grid
        c = g.xyz[g.cell_index(0.0, 0.0)]
        d = g.radius_km * np.arccos(np.clip(g.xyz @ c, -1, 1))
        z0 = np.where(d < 700, -500.0, z0)                     # deep interior pit in the mountain block
        ctx.data["elevation_m"] = z0.astype(np.float32)
        out = ER.run(ctx)
        self.assertIn("ocean", out)
        self.assertFalse(out["ocean"][d < 300].any())
        self.assertTrue(out["ocean"][d > 7000].all())

    def test_stage_adds_the_elevation_stages_added_land(self):
        ctx, z0 = mountain_ctx()
        ctx.cfg["build"]["land_fraction"] = 0.02
        g = ctx.grid
        mtn = z0 > 0
        ctx.data.update({"continental": mtn, "continental_base": mtn})   # lifted shelf: no new crust at all
        added = mtn & (g.lon >= 0)
        ctx.data["land_added"] = added
        extra = g.area_km2[added].sum() / g.area_km2.sum()
        z = ER.run(ctx)["elevation_eroded_m"]
        self.assertAlmostEqual(g.area_km2[z > 0].sum() / g.area_km2.sum(), 0.02 + extra, delta=0.005)

class BaseLevelTest(unittest.TestCase):
    def test_erosion_keeps_coastline(self):
        ctx = make_ctx(3)
        g = ctx.grid
        block = (np.abs(g.lat) < 30) & (np.abs(g.lon) < 50)
        z0 = np.where(block, 800.0 + 2500.0 * np.exp(-((g.lon + 45) / 6.0) ** 2), -6000.0)   # coastal range by a deep sea
        z1 = ER.erode(g, z0, np.full(g.n, 1.5), ER.DEFAULTS)
        np.testing.assert_array_equal(z1 > 0, z0 > 0)
        self.assertTrue(np.all(z1[~block] == z0[~block]))


class HillslopeResolutionTest(unittest.TestCase):
    def test_hillslope_smoothing_is_resolution_independent(self):
        from tests.helpers import small_grid
        drops = []
        for res in (3, 4):
            g = small_grid(res)
            c = g.xyz[g.cell_index(20.0, 0.0)]
            d = g.radius_km * np.arccos(np.clip(g.xyz @ c, -1, 1))
            z0 = np.where(g.lat > -40, 500.0 + 3000.0 * np.exp(-(d / 600.0) ** 2), -4000.0)
            P = dict(ER.DEFAULTS, k=0.0)                       # isolate hillslope smoothing
            z1 = ER.erode(g, z0, np.ones(g.n), P)
            drops.append(z0.max() - z1[d < 150].max())
        self.assertAlmostEqual(drops[0] / max(drops[1], 1e-9), 1.0, delta=0.35)


class SedimentFillTest(unittest.TestCase):
    def test_shallow_basins_fill_deep_ones_remain(self):
        g = make_ctx(3).grid
        z0 = np.where(g.lat > -20, 200.0 + 2.0 * (g.lat + 20), -3000.0)
        depth = {}
        for name, (lat, dz) in {"shallow": (20.0, 60.0), "deep": (50.0, 2500.0)}.items():
            c = g.xyz[g.cell_index(lat, 0.0)]
            d = g.radius_km * np.arccos(np.clip(g.xyz @ c, -1, 1))
            z0 = np.where(d < 700, z0 - dz * (1 - d / 700), z0)
            depth[name] = d < 250
        from mapgen.graph import priority_flood
        def fill_after(frac):
            z1 = ER.erode(g, z0, np.full(g.n, 1.0), dict(ER.DEFAULTS, k=0.0, sediment_fill=frac))
            return priority_flood(g, z1, z1 <= -1000) - z1
        self.assertGreater(fill_after(0.0)[depth["shallow"]].max(), 20.0)     # without sediment it stays a basin
        fill = fill_after(ER.DEFAULTS["sediment_fill"])
        self.assertLess(fill[depth["shallow"]].max(), 0.2 * 60.0)             # noise-scale basins mostly filled
        self.assertGreater(fill[depth["deep"]].max(), 20.0)