raw · 9742 bytes
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 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 | 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]) |