import unittest import numpy as np from mapgen import check as CK from tests.helpers import small_grid class CheckTest(unittest.TestCase): def setUp(self): self.g = small_grid(2) def test_land_fraction(self): a = np.ones(100) z = np.where(np.arange(100) < 22, 100.0, -100.0) self.assertIsNone(CK.check_land_fraction(z, a, 0.22)) self.assertIn("land fraction", CK.check_land_fraction(z, a, 0.40)) def test_hypsometry(self): rng = np.random.default_rng(0) z = np.concatenate([rng.normal(-4000, 600, 7000), rng.normal(500, 400, 3000)]) self.assertIsNone(CK.check_hypsometry(z, np.ones(len(z)))) self.assertIn("bimodal", CK.check_hypsometry(rng.normal(0, 3000, 10000), np.ones(10000))) def test_max_elevation(self): self.assertIsNone(CK.check_max_elevation(np.array([11000.0]), np.array([1.0]))) self.assertIsNone(CK.check_max_elevation(np.array([20000.0]), np.array([0.5]))) self.assertIn("12000", CK.check_max_elevation(np.array([13000.0]), np.array([1.0]))) def test_drainage_and_uphill(self): recv = np.array([0, 0, 1, 3]) z = np.array([-10.0, 5.0, 8.0, 20.0]) endo = np.array([False, False, False, False]) self.assertIn("sink", CK.check_drainage(recv, z, endo)) endo[3] = True self.assertIsNone(CK.check_drainage(recv, z, endo)) self.assertIsNone(CK.check_no_uphill(z, recv, z, endo)) self.assertIn("uphill", CK.check_no_uphill(np.array([-10.0, 9.0, 8.0, 20.0]), recv, z, endo)) def test_temperature_and_rain_bands(self): g = self.g self.assertIsNone(CK.check_temperature(g, 30 - 0.6 * np.abs(g.lat))) self.assertIn("poleward", CK.check_temperature(g, 0.3 * np.abs(g.lat))) wet = 2000 * np.exp(-(g.lat / 8) ** 2) + 900 * np.exp(-((np.abs(g.lat) - 40) / 8) ** 2) + 100 self.assertIsNone(CK.check_rain_bands(g, wet, 20.0)) self.assertIn("subtropic", CK.check_rain_bands(g, np.full(g.n, 800.0), 20.0)) def test_drainage_terminals_must_hold_water_or_salt(self): recv = np.array([0, 0, 1, 3]) z = np.array([-10.0, 5.0, 8.0, 20.0]) endo = np.array([False, False, False, True]) dry = np.zeros(4, bool) self.assertIn("lake", CK.check_drainage(recv, z, endo, terminal_ok=dry)) wet = dry.copy() wet[3] = True self.assertIsNone(CK.check_drainage(recv, z, endo, terminal_ok=wet)) def test_lake_surface_routing_is_exempt(self): recv = np.array([0, 0, 1, 2]) zf = np.array([-10.0, 5.0, 5.02, 5.01]) # cell 3 → 2 is ε-uphill across a flat lake z = np.array([-10.0, 5.0, 4.0, 4.5]) endo = np.zeros(4, bool) lake = np.array([False, False, True, True]) self.assertIn("uphill", CK.check_no_uphill(zf, recv, z, endo)) self.assertIsNone(CK.check_no_uphill(zf, recv, z, endo, lake=lake)) class RevisionCheckTest(unittest.TestCase): P1 = {"name": "east-flank", "center": [-39.9, -18.0], "area_km2": 3.0e6, "elongation": 1.9, "azimuth_deg": 30.0, "top_m": [1500.0, 3000.0]} P2 = {"name": "plateau-02", "center": [-0.4, 176.6], "area_km2": 1.0e6, "top_m": [1000.0, 1500.0], "islands": True} def world(self): from mapgen import plateaus as PL g = small_grid(4) ids = PL.cell_ids(g.xyz, [self.P1, self.P2], 7, g.radius_km) z = PL.apply(g, np.full(g.n, -6000.0), [self.P1, self.P2], ids, 7) return g, {"elevation_eroded_m": z, "ocean": z <= 0, "plateau_id": ids} def test_plateaus_pass_and_fail(self): g, d = self.world() self.assertEqual(CK.check_plateaus(g, d, [self.P1, self.P2], 7), []) z = d["elevation_eroded_m"].copy() z[np.flatnonzero(d["plateau_id"] == 0)[0]] = -100.0 z[z > 0] = -700.0 bad = CK.check_plateaus(g, {**d, "elevation_eroded_m": z, "ocean": z <= 0}, [self.P1, self.P2], 7) self.assertTrue(any("east-flank" in m and "−500" in m for m in bad), bad) self.assertTrue(any("plateau-02" in m and "island" in m for m in bad), bad) def test_zones_gradual_rule(self): from mapgen import zones as ZN g = small_grid(4) m = ZN.contribution(g.xyz, {"name": "R1", "field": "o2", "center": [0.0, 0.0], "radius_km": 3200.0, "v": 1.0}, 7, g.radius_km) smooth = {"o2_fraction": 0.21 * (1 + 0.5 * m), "gravity_g": np.full(g.n, 1.05)} self.assertEqual(CK.check_zones(g, smooth), []) step = {"o2_fraction": np.where(g.lon > 0, 0.35, 0.21), "gravity_g": np.full(g.n, 1.05)} self.assertTrue(any("o2_fraction" in m for m in CK.check_zones(g, step))) def test_compare_skips_o2_fields_inside_o2_zones(self): g = small_grid(3) tect = {"plateau": [], "land_patch": [], "zone": [{"name": "r", "field": "o2", "center": [0.0, 0.0], "radius_km": 2000.0, "v": 1.0}]} o2 = CK.o2_areas(g.xyz, tect, g.radius_km) self.assertTrue(o2[g.cell_index(0.0, 0.0)]) self.assertFalse(o2[g.cell_index(0.0, 90.0)]) ex = CK.edited_areas(g.xyz, tect, g.radius_km) self.assertFalse(ex.any(), "O₂ zones don't change relief") old = {"po2_bar": np.zeros(g.n), "T_mean": np.zeros(g.n)} new = {"po2_bar": np.where(o2, 0.1, 0.0), "T_mean": np.where(o2, 1.0, 0.0)} lines = CK.compare(old, new, ex, {k: o2 for k in CK.O2_FIELDS}) self.assertIn("po2_bar: 0.0% changed, max 0, p99 0", lines) self.assertTrue(any(s.startswith("T_mean:") and not s.startswith("T_mean: 0.0%") for s in lines), lines) def test_compare_command_needs_a_world_cells_file(self): import importlib.util import tempfile from pathlib import Path from mapgen.testing import built_world spec = importlib.util.spec_from_file_location("mapgen_cli", Path(CK.__file__).parents[1] / "mapgen.py") cli = importlib.util.module_from_spec(spec) spec.loader.exec_module(cli) old = Path(tempfile.mkdtemp()) / "other.npz" np.savez(old, a=np.zeros(3)) self.assertEqual(cli.main(["compare", "--res", "2", "--old", str(old)], root=built_world()), 2) def test_compare_ignores_edited_areas(self): g = small_grid(3) tect = {"plateau": [self.P1], "land_patch": [], "zone": []} ex = CK.edited_areas(g.xyz, tect, g.radius_km) self.assertTrue(ex[g.cell_index(-39.9, -18.0)]) self.assertFalse(ex[g.cell_index(40.0, 100.0)]) old = {"a": np.zeros(g.n), "b": np.zeros(g.n), "g_ids": g.ids} new = {"a": np.where(ex, 5.0, 0.0), "b": np.where(ex, 0.0, 1.0), "g_ids": g.ids} lines = CK.compare(old, new, ex) self.assertIn("a: 0.0% changed, max 0, p99 0", lines) self.assertTrue(any(line.startswith("b: 100.0% changed, max 1") for line in lines), lines) def test_site_report_warns_about_land_nearby(self): from mapgen import plateaus as PL g, d = self.world() land = np.abs(g.lat - (-39.9)) < 2.0 near = land & (np.abs(g.lon - (-18.0)) < 30.0) & (d["plateau_id"] < 0) data = {**d, "ocean": ~near, "bnd_type": np.zeros(g.n, np.int8)} msgs = PL.site_report(g, data, [self.P1, self.P2]) self.assertTrue(any("east-flank" in m and "from land" in m for m in msgs), msgs) def test_compare_command(self): import importlib.util import shutil import tempfile from pathlib import Path from mapgen.testing import built_world spec = importlib.util.spec_from_file_location("mapgen_cli", Path(CK.__file__).parents[1] / "mapgen.py") cli = importlib.util.module_from_spec(spec) spec.loader.exec_module(cli) old = Path(tempfile.mkdtemp()) / "cells.npz" shutil.copy(built_world() / "out" / "r2" / "cells.npz", old) self.assertEqual(cli.main(["compare", "--res", "2", "--old", str(old)], root=built_world()), 0) def test_compare_equal_infinities_are_unchanged(self): ex = np.zeros(3, bool) lines = CK.compare({"d": np.array([np.inf, 1.0, 2.0])}, {"d": np.array([np.inf, 1.0, 3.0])}, ex) self.assertIn("d: 33.3% changed, max 1, p99 0.98", lines)