raw · 4493 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 | import unittest import numpy as np from tests.helpers import line_world, make_history, make_race from worldhistory.habitat import environment, suitability from worldhistory.migration import drift_and_bud, long_jumps from worldhistory.predicates import evaluate from worldhistory.state import new_state MP = make_history().migration def east(w, speed=1.0): """Uniform current along the line (the line runs along the equator, eastward = +lon).""" lon = np.radians(w.lon) return speed * np.stack([-np.sin(lon), np.cos(lon), np.zeros(w.n)], 1) def sea_world(n, spacing_km=100.0, current=True, **over): w = line_world(n, spacing_km=spacing_km, ocean=True, elevation_m=-3000.0, **over) if current: w.base["current"] = east(w) return w def swimmer(bias): return make_race(demography={"founder": 2.0, "allee": 1.0, "jump_path": "sea", "current_bias": bias, "mobility": 0.2}, habitat={"realm": "sea", "terms": [{"p": "sea", "w": 1.0}]}) class SailCostTest(unittest.TestCase): def test_downstream_cheaper_than_still_cheaper_than_upstream(self): w = sea_world(3) c = w.sail_cost(5.0) k_east = list(w.nb[1]).index(2) k_west = list(w.nb[1]).index(0) edge_h = 100.0 * 1000.0 / 3600.0 # 100 km in hours per (m/s) self.assertAlmostEqual(c[1, k_east], edge_h / 6.0, delta=0.01) # 5 + 1 m/s self.assertAlmostEqual(c[1, k_west], edge_h / 4.0, delta=0.01) # 5 − 1 m/s still = sea_world(3, current=False).sail_cost(5.0) self.assertAlmostEqual(still[1, k_east], edge_h / 5.0, delta=0.01) self.assertTrue(np.isinf(c[0, list(w.nb[0]).index(-1)])) # no neighbour: no edge def test_land_edges_are_impassable(self): w = line_world(3, ocean=[True, True, False]) c = w.sail_cost(5.0) self.assertTrue(np.isinf(c[1, list(w.nb[1]).index(2)])) class FieldsTest(unittest.TestCase): def test_missing_current_is_still_water(self): w = sea_world(3, current=False) self.assertEqual(w.current.shape, (3, 3)) self.assertTrue(np.all(w.current == 0)) def test_productivity_predicate(self): w = sea_world(3, productivity=np.array([0.0, 0.5, 1.0])) np.testing.assert_allclose(evaluate("productivity", w), [0.0, 0.5, 1.0]) np.testing.assert_allclose(evaluate("productivity", sea_world(3)), [0.0, 0.0, 0.0]) # absent → 0 class JumpBiasTest(unittest.TestCase): def landings(self, bias, current=True): w = sea_world(400, spacing_km=10.0, current=current) r = swimmer(bias) env, suit = environment(w), suitability(w, r)[None] east_n = west_n = 0 for seed in range(150): st = new_state(1, w.n, np.zeros((1, 6)), seed=0) st.P[0, 200] = 1e6 long_jumps(st, w, [r], np.full((1, w.n), 1e6), np.ones((1, w.n), bool), np.random.default_rng(seed), {**MP, "jump_rate": 1.0}, np.ones((1, w.n)), env=env, suit=suit, f_min=0.0) hit = np.flatnonzero(st.P[0] > 0) east_n += int(np.sum(hit > 200)) west_n += int(np.sum(hit < 200)) return east_n, west_n def test_downstream_jumps_dominate_with_bias(self): e, w = self.landings(3.0) self.assertGreater(e, 3 * max(w, 1)) def test_no_bias_is_symmetric(self): e, w = self.landings(0.0) self.assertLess(abs(e - w), 0.35 * (e + w) + 5) def test_no_current_field_ignores_bias(self): e, w = self.landings(3.0, current=False) self.assertLess(abs(e - w), 0.35 * (e + w) + 5) class DriftBiasTest(unittest.TestCase): def drift(self, bias): w = sea_world(3) r = swimmer(bias) st = new_state(1, 3, np.zeros((1, 6)), seed=0) st.P[0] = [100.0, 1000.0, 100.0] # neighbours settled: drift, not founding drift_and_bud(st, w, [r], np.full((1, 3), 1e5), np.ones((1, 3), bool), np.zeros((1, 3)), np.zeros((1, 3)), np.ones((1, 3)), np.random.default_rng(0), MP) return st.P[0] def test_drift_follows_the_current(self): P = self.drift(2.0) self.assertGreater(P[2] - 100.0, 3.0 * max(P[0] - 100.0, 1e-9)) # the downstream neighbour gains most def test_no_bias_drifts_evenly(self): P = self.drift(0.0) self.assertAlmostEqual(P[0], P[2], delta=1e-6 * P.sum()) |