Skip to content

Commit 67e1b90

Browse files
authored
Holland 1980 and 2010 wind field models (#263)
* trop_cyclone: add Holland 1980/2010 wind field models * trop_cyclone: docstrings
1 parent 611a15b commit 67e1b90

File tree

3 files changed

+356
-100
lines changed

3 files changed

+356
-100
lines changed

climada/hazard/tc_tracks.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -547,7 +547,7 @@ def read_ibtracs_netcdf(self, provider=None, rescale_windspeeds=True, storm_id=N
547547
'have been found: %s%s', len(invalid_sids), ", ".join(invalid_sids[:5]),
548548
", ..." if len(invalid_sids) > 5 else ".")
549549
ibtracs_ds = ibtracs_ds.sel(storm=valid_storms_mask)
550-
550+
551551
if discard_single_points:
552552
valid_storms_mask = ibtracs_ds.valid_t.sum(dim="date_time") > 1
553553
invalid_storms_idx = np.nonzero(~valid_storms_mask.data)[0]
@@ -1549,7 +1549,7 @@ def _dist_since_lf(track):
15491549
sea_land_idx, land_sea_idx = _get_landfall_idx(track, True)
15501550
if not sea_land_idx.size:
15511551
return (dist_since_lf + 1) * np.nan
1552-
1552+
15531553
orig_lf = np.empty((sea_land_idx.size, 2))
15541554
for i_lf, lf_point in enumerate(sea_land_idx):
15551555
if lf_point > 0:
@@ -1595,7 +1595,7 @@ def _get_landfall_idx(track, include_starting_landfall=False):
15951595
include_starting_landfall : bool
15961596
If the track starts over land, whether to include the track segment before
15971597
reaching the ocean as a landfall. Default: False.
1598-
1598+
15991599
16001600
Returns
16011601
-------

climada/hazard/test/test_trop_cyclone.py

Lines changed: 102 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,9 @@
2727
from climada import CONFIG
2828
from climada.util import ureg
2929
from climada.hazard.tc_tracks import TCTracks
30-
from climada.hazard.trop_cyclone import TropCyclone,\
31-
_bs_hol08, _close_centroids, _stat_holland, _vtrans
30+
from climada.hazard.trop_cyclone import (
31+
TropCyclone, _close_centroids, _vtrans, _B_holland_1980, _bs_holland_2008,
32+
_v_max_s_holland_2008, _x_holland_2010, _stat_holland_1980, _stat_holland_2010)
3233
from climada.hazard.centroids.centr import Centroids
3334

3435
DATA_DIR = CONFIG.hazard.test_data.dir()
@@ -91,11 +92,11 @@ def test_set_one_pass(self):
9192
self.assertEqual(tc_haz.intensity.shape, (1, 296))
9293
self.assertEqual(np.nonzero(tc_haz.intensity)[0].size, 280)
9394

95+
np.testing.assert_array_almost_equal(
96+
tc_haz.intensity[0, intensity_idx].toarray()[0], intensity_values[metric])
9497
for idx, val in zip(intensity_idx, intensity_values[metric]):
9598
if val == 0:
9699
self.assertEqual(tc_haz.intensity[0, idx], 0)
97-
else:
98-
self.assertAlmostEqual(tc_haz.intensity[0, idx], val)
99100

100101
windfields = tc_haz.windfields[0].toarray()
101102
windfields = windfields.reshape(windfields.shape[0], -1, 2)
@@ -104,6 +105,34 @@ def test_set_one_pass(self):
104105
msk = (intensity > 0)
105106
np.testing.assert_array_equal(windfield_norms[msk], intensity[msk])
106107

108+
def test_windfield_models(self):
109+
"""Test _tc_from_track function with different wind field models."""
110+
intensity_idx = [0, 1, 2, 3, 80, 100, 120, 200, 220, 250, 260, 295]
111+
intensity_values = {
112+
"H08": [25.60778909, 26.90887264, 28.26624642, 25.54092386, 31.21941738, 36.16596567,
113+
21.11399856, 28.01452136, 32.65076804, 31.33884098, 0, 40.27002104],
114+
"H10": [27.477252, 28.626236, 29.829914, 27.393616, 32.495186, 37.113324,
115+
23.573216, 29.552127, 33.767067, 32.530964, 19.656737, 41.014578],
116+
# Holland 1980 is the only model that uses recorded wind speeds, while the above use
117+
# pressure values only. That's why the results for Holland 1980 are so different:
118+
"H1980": [20.291397, 22.678914, 25.428598, 20.44718 , 31.868592, 41.920317,
119+
0, 25.715983, 38.351686, 35.591153, 0, 46.873912],
120+
}
121+
122+
tc_track = TCTracks()
123+
tc_track.read_processed_ibtracs_csv(TEST_TRACK)
124+
tc_track.equal_timestep()
125+
tc_track.data = tc_track.data[:1]
126+
127+
for model in ["H08", "H10", "H1980"]:
128+
tc_haz = TropCyclone()
129+
tc_haz.set_from_tracks(tc_track, centroids=CENTR_TEST_BRB, model=model)
130+
np.testing.assert_array_almost_equal(
131+
tc_haz.intensity[0, intensity_idx].toarray()[0], intensity_values[model])
132+
for idx, val in zip(intensity_idx, intensity_values[model]):
133+
if val == 0:
134+
self.assertEqual(tc_haz.intensity[0, idx], 0)
135+
107136
def test_set_one_file_pass(self):
108137
"""Test set function set_from_tracks with one input."""
109138
tc_track = TCTracks()
@@ -181,59 +210,78 @@ def test_close_centroids_pass(self):
181210
mask = _close_centroids(t_lat, t_lon, centroids, buffer=5)
182211
np.testing.assert_equal(mask, test_mask)
183212

184-
def test_bs_hol08_pass(self):
185-
"""Test _bs_hol08 function. Compare to MATLAB reference."""
186-
v_trans = 5.241999541820597
187-
penv = 1010
188-
pcen = 1005.263333333329
189-
prepcen = 1005.258500000000
190-
lat = 12.299999504631343
191-
tint = 1
192-
_bs_res = _bs_hol08(v_trans, penv, pcen, prepcen, lat, tint)
193-
self.assertAlmostEqual(_bs_res, 1.270856908796045)
194-
195-
v_trans = 5.123882725120426
196-
penv = 1010
197-
pcen = 1005.268166666671
198-
prepcen = 1005.263333333329
199-
lat = 12.299999279463769
200-
tint = 1
201-
_bs_res = _bs_hol08(v_trans, penv, pcen, prepcen, lat, tint)
202-
self.assertAlmostEqual(_bs_res, 1.265551666104679)
203-
204-
def test_stat_holland(self):
205-
"""Test _stat_holland function. Compare to MATLAB reference."""
206-
d_centr = np.array([[293.6067129546862, 298.2652319413182]])
207-
r_max = np.array([75.547902916671745])
208-
hol_b = np.array([1.265551666104679])
209-
penv = np.array([1010.0])
210-
pcen = np.array([1005.268166666671])
211-
lat = np.array([12.299999279463769])
212-
mask = np.ones_like(d_centr, dtype=bool)
213-
214-
_v_arr = _stat_holland(d_centr, r_max, hol_b, penv, pcen, lat, mask)[0]
215-
self.assertAlmostEqual(_v_arr[0], 5.384115724400597)
216-
self.assertAlmostEqual(_v_arr[1], 5.281356766052531)
217-
218-
d_centr = np.array([[]])
219-
mask = np.ones_like(d_centr, dtype=bool)
220-
_v_arr = _stat_holland(d_centr, r_max, hol_b, penv, pcen, lat, mask)[0]
221-
self.assertTrue(np.array_equal(_v_arr, np.array([])))
222-
213+
def test_B_holland_1980_pass(self):
214+
"""Test _B_holland_1980 function."""
215+
gradient_winds = np.array([35, 40])
216+
penv = np.array([1010, 1010])
217+
pcen = np.array([995, 980])
218+
_B_res = _B_holland_1980(gradient_winds, penv, pcen)
219+
np.testing.assert_array_almost_equal(_B_res, [2.5, 1.667213])
220+
221+
def test_bs_holland_2008_pass(self):
222+
"""Test _bs_holland_2008 function. Compare to MATLAB reference."""
223+
v_trans = np.array([5.241999541820597, 5.123882725120426])
224+
penv = np.array([1010, 1010])
225+
pcen = np.array([1005.263333333329, 1005.268166666671])
226+
prepcen = np.array([1005.258500000000, 1005.263333333329])
227+
lat = np.array([12.299999504631343, 12.299999279463769])
228+
tint = np.array([1.0, 1.0])
229+
_bs_res = _bs_holland_2008(v_trans, penv, pcen, prepcen, lat, tint)
230+
np.testing.assert_array_almost_equal(_bs_res, [1.270856908796045, 1.265551666104679])
231+
232+
def test_v_max_s_holland_2008_pass(self):
233+
"""Test _v_max_s_holland_2008 function."""
234+
# Numbers analogous to test_B_holland_1980_pass
235+
penv = np.array([1010, 1010])
236+
pcen = np.array([995, 980])
237+
b_s = np.array([2.5, 1.67])
238+
v_max_s = _v_max_s_holland_2008(penv, pcen, b_s)
239+
np.testing.assert_array_almost_equal(v_max_s, [34.635341, 40.033421])
240+
241+
def test_holland_2010_pass(self):
242+
"""Test Holland et al. 2010 wind field model."""
243+
# test at centroids within and outside of radius of max wind
244+
d_centr = np.array([[35e3, 75e3, 220e3], [30e3, 1000e3, 300e3]])
245+
r_max = np.array([75e3, 40e3])
246+
v_max_s = np.array([35.0, 40.0])
247+
hol_b = np.array([1.80, 2.5])
248+
mask = np.array([[True, True, True], [True, False, True]], dtype=bool)
249+
hol_x = _x_holland_2010(d_centr, r_max, v_max_s, hol_b, mask)
250+
np.testing.assert_array_almost_equal(hol_x, [[0.5, 0.5, 0.47273], [0.5, 0, 0.211602]])
251+
252+
# test exactly at radius of maximum wind (35 m/s) and at peripheral radius (17 m/s)
253+
v_ang_norm = _stat_holland_2010(d_centr, v_max_s, r_max, hol_b, mask, hol_x)
254+
np.testing.assert_array_almost_equal(v_ang_norm,
255+
[[15.957853, 35.0, 20.99411], [33.854826, 0, 17.0]])
256+
257+
def test_stat_holland_1980(self):
258+
"""Test _stat_holland_1980 function. Compare to MATLAB reference."""
223259
d_centr = np.array([
224-
[299.4501244109841, 291.0737897183741, 292.5441003235722]
260+
[299.4501244109841, 291.0737897183741, 292.5441003235722, 40.665454622610511],
261+
[293.6067129546862, 1000.0, 298.2652319413182, 70.0],
225262
])
226-
r_max = np.array([40.665454622610511])
227-
hol_b = np.array([1.486076257880692])
228-
penv = np.array([1010.0])
229-
pcen = np.array([970.8727666672957])
230-
lat = np.array([14.089110370469488])
263+
r_max = np.array([40.665454622610511, 75.547902916671745])
264+
hol_b = np.array([1.486076257880692, 1.265551666104679])
265+
penv = np.array([1010.0, 1010.0])
266+
pcen = np.array([970.8727666672957, 1005.268166666671])
267+
lat = np.array([-14.089110370469488, 12.299999279463769])
268+
mask = np.array([[True, True, True, True], [True, False, True, True]], dtype=bool)
269+
v_ang_norm = _stat_holland_1980(d_centr, r_max, hol_b, penv, pcen, lat, mask)
270+
np.testing.assert_array_almost_equal(v_ang_norm,
271+
[[11.279764005440288, 11.682978583939310, 11.610940769149384, 42.412845],
272+
[5.384115724400597, 0, 5.281356766052531, 12.763087]])
273+
274+
# without Coriolis force, values are higher, esp. far away from the center:
275+
v_ang_norm = _stat_holland_1980(d_centr, r_max, hol_b, penv, pcen, lat, mask,
276+
cyclostrophic=True)
277+
np.testing.assert_array_almost_equal(v_ang_norm,
278+
[[15.719924, 16.037052, 15.980323, 43.128461],
279+
[8.836768, 0, 8.764678, 13.807452]])
280+
281+
d_centr = np.array([[], []])
231282
mask = np.ones_like(d_centr, dtype=bool)
232-
233-
_v_arr = _stat_holland(d_centr, r_max, hol_b, penv, pcen, lat, mask)[0]
234-
self.assertAlmostEqual(_v_arr[0], 11.279764005440288)
235-
self.assertAlmostEqual(_v_arr[1], 11.682978583939310)
236-
self.assertAlmostEqual(_v_arr[2], 11.610940769149384)
283+
v_ang_norm = _stat_holland_1980(d_centr, r_max, hol_b, penv, pcen, lat, mask)
284+
np.testing.assert_array_equal(v_ang_norm, np.array([[], []]))
237285

238286
def test_vtrans_pass(self):
239287
"""Test _vtrans function. Compare to MATLAB reference."""

0 commit comments

Comments
 (0)