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
|
import unittest
import numpy as np
from mapgen import climate as CL
from mapgen.sphere import east_north
from tests.helpers import make_ctx, small_grid
class WindTest(unittest.TestCase):
def test_bands(self):
g = small_grid(3)
w = CL.band_winds(g, 0.0, CL.DEFAULTS)
e, n = east_north(g.xyz)
u = np.sum(w * e, axis=1)
v = np.sum(w * n, axis=1)
self.assertLess(u[np.abs(g.lat - 10) < 2].mean(), -3) # trades from the east
self.assertGreater(u[np.abs(g.lat - 40) < 2].mean(), 3) # westerlies
self.assertLess(v[np.abs(g.lat - 10) < 2].mean(), 0) # NH trades flow equatorward
self.assertGreater(v[np.abs(g.lat + 10) < 2].mean(), 0) # SH trades flow equatorward
def test_monsoon_reverses_onshore_flow(self):
g = small_grid(3)
land = (g.lat > 10) & (g.lat < 40) & (np.abs(g.lon) < 50)
z = np.where(land, 200.0, -4000.0)
T, _ = CL.temperatures(g, z, land, CL.DEFAULTS, 20.0)
_, n = east_north(g.xyz)
south = (g.lat > 0) & (g.lat < 8) & (np.abs(g.lon) < 40)
vj = np.sum(CL.monsoon_winds(g, T["jun"], land, CL.DEFAULTS) * n, axis=1)[south].mean()
vd = np.sum(CL.monsoon_winds(g, T["dec"], land, CL.DEFAULTS) * n, axis=1)[south].mean()
self.assertGreater(vj, 0.5)
self.assertLess(vd, -0.5)
class RainTest(unittest.TestCase):
def test_itcz_wetter_than_subtropics_on_aquaplanet(self):
g = small_grid(3)
z = np.full(g.n, -4000.0)
T, _ = CL.temperatures(g, z, z > 0, CL.DEFAULTS, 20.0)
w = CL.band_winds(g, 0.0, CL.DEFAULTS)
p = CL.precipitation(g, w, T["eq"], z, z > 0, 0.0, CL.DEFAULTS)
self.assertGreater(p[np.abs(g.lat) < 5].mean(), 1.5 * p[np.abs(np.abs(g.lat) - 21) < 3].mean())
def test_windward_wetter_than_lee(self):
g = small_grid(3)
land = (g.lat > 30) & (g.lat < 50) & (np.abs(g.lon) < 60)
z = np.where(land, 200.0, -4000.0)
z = np.where(land & (np.abs(g.lon) < 5), 3000.0, z)
T, _ = CL.temperatures(g, z, land, CL.DEFAULTS, 20.0)
w = CL.band_winds(g, 0.0, CL.DEFAULTS)
p = CL.precipitation(g, w, T["eq"], z, land, 0.0, CL.DEFAULTS)
band = (g.lat > 35) & (g.lat < 45)
west = p[band & (g.lon > -12) & (g.lon < -3)].mean()
east = p[band & (g.lon > 3) & (g.lon < 12)].mean()
self.assertGreater(west, 1.3 * east)
def test_stage_outputs(self):
ctx = make_ctx(2)
g = ctx.grid
land = (np.abs(g.lat) < 40) & (np.abs(g.lon) < 50)
ctx.data["elevation_eroded_m"] = np.where(land, 300.0, -4000.0).astype(np.float32)
out = CL.run(ctx)
for k in ("T_jun", "T_dec", "T_eq", "T_mean", "T_range", "T_min", "P_jun", "P_dec", "P_eq", "P_ann",
"wind_jun", "biotemp", "PET", "dist_ocean_km"):
self.assertIn(k, out)
self.assertTrue(np.all(np.isfinite(out[k])), k)
mean = np.sum(out["P_ann"] * g.area_km2) / g.area_km2.sum()
self.assertAlmostEqual(mean, 1000.0, delta=1.0)
self.assertTrue(np.all(out["P_ann"] >= 0))
class StormTrackTest(unittest.TestCase):
def test_midlatitudes_wetter_than_subtropics(self):
g = small_grid(3)
z = np.full(g.n, -4000.0)
T, _ = CL.temperatures(g, z, z > 0, CL.DEFAULTS, 20.0)
p = CL.precipitation(g, CL.band_winds(g, 0.0, CL.DEFAULTS), T["eq"], z, z > 0, 0.0, CL.DEFAULTS)
a = np.abs(g.lat)
h = CL.DEFAULTS["hadley_edge_deg"] # storm track ≈ h+12..h+25, dry belt ≈ h−2..h+8
self.assertGreater(p[(a > h + 12) & (a < h + 25)].mean(), 1.2 * p[(a > h - 2) & (a < h + 8)].mean())
class LandMoistureTest(unittest.TestCase):
def test_equatorial_land_stays_wet(self):
g = small_grid(3)
land = (np.abs(g.lat) < 12) & (np.abs(g.lon) < 40)
z = np.where(land, 300.0, -4000.0)
T, _ = CL.temperatures(g, z, land, CL.DEFAULTS, 20.0)
p = CL.precipitation(g, CL.band_winds(g, 0.0, CL.DEFAULTS), T["eq"], z, land, 0.0, CL.DEFAULTS)
eq = np.abs(g.lat) < 8
self.assertGreater(p[land & eq].mean(), 0.5 * p[~land & eq].mean())
class OceanMaskUseTest(unittest.TestCase):
def test_inland_basin_is_land_for_climate(self):
ctx = make_ctx(3)
g = ctx.grid
land = (np.abs(g.lat) < 40) & (np.abs(g.lon) < 60)
z = np.where(land, 300.0, -4000.0)
basin = (np.abs(g.lat) < 8) & (np.abs(g.lon) < 8)
z[basin] = -30.0
ctx.data.update({"elevation_eroded_m": z.astype(np.float32), "ocean": ~land})
out = CL.run(ctx)
self.assertTrue(np.all(out["dist_ocean_km"][basin] > 1000))
class InteriorRainTest(unittest.TestCase):
def test_continental_interior_keeps_a_third_of_coastal_rain(self):
from mapgen.graph import distance_to
g = small_grid(3)
land = (g.lat > 10) & (g.lat < 50) & (np.abs(g.lon) < 70)
z = np.where(land, 300.0, -4000.0)
T, _ = CL.temperatures(g, z, land, CL.DEFAULTS, 20.0)
p = CL.precipitation(g, CL.band_winds(g, 0.0, CL.DEFAULTS), T["eq"], z, land, 0.0, CL.DEFAULTS)
d = distance_to(g, ~land)
coast, interior = p[land & (d < 500)].mean(), p[land & (d > 1500)].mean()
self.assertGreater(interior, 0.33 * coast) # was ≈0.25 before the desert retune
|