Skip to content

Commit 104dfdb

Browse files
ThomasRoosliThomas Rooslitovogtpeanutfun
authored
Feature/tc holland vtrans (#833)
* Change how vtrans is considered in the implementation of Holland 2008 and Holland 2010 model * trop_cyclone: broadcast_arrays -> broadcast_to * Update CHANGELOG.md --------- Co-authored-by: Thomas Roosli <[email protected]> Co-authored-by: Thomas Vogt <[email protected]> Co-authored-by: Lukas Riedel <[email protected]>
1 parent e81249f commit 104dfdb

File tree

3 files changed

+71
-39
lines changed

3 files changed

+71
-39
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ Code freeze date: YYYY-MM-DD
2626
- `Hazard.from_xarray_raster` now stores strings as default values for `Hazard.event_name` [#795](https://github.com/CLIMADA-project/climada_python/pull/795)
2727
- Fix the dist_approx util function when used with method="geosphere" and log=True and points that are very close. [#792](https://github.com/CLIMADA-project/climada_python/pull/792)
2828
- `climada.util.yearsets.sample_from_poisson`: fix a bug ([#819](https://github.com/CLIMADA-project/climada_python/issues/819)) and inconsistency that occurs when lambda events per year (`lam`) are set to 1. [[#823](https://github.com/CLIMADA-project/climada_python/pull/823)]
29+
- In the TropCyclone class in the Holland model 2008 and 2010 implementation, a doublecounting of translational velocity is removed [#833](https://github.com/CLIMADA-project/climada_python/pull/833)
2930

3031
### Deprecated
3132

climada/hazard/test/test_trop_cyclone.py

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,10 @@ def test_memory_limit(self):
6060
tc_haz = TropCyclone.from_tracks(tc_track, centroids=CENTR_TEST_BRB, max_memory_gb=0.001)
6161
intensity_idx = [0, 1, 2, 3, 80, 100, 120, 200, 220, 250, 260, 295]
6262
intensity_values = [
63-
25.60778909, 26.90887264, 28.26624642, 25.54092386, 31.21941738, 36.16596567,
64-
21.11399856, 28.01452136, 32.65076804, 31.33884098, 0, 40.27002104,
63+
22.74903, 23.784691, 24.82255, 22.67403, 27.218706, 30.593959,
64+
18.980878, 24.540069, 27.826407, 26.846293, 0., 34.568898,
6565
]
66+
6667
np.testing.assert_array_almost_equal(
6768
tc_haz.intensity[0, intensity_idx].toarray()[0],
6869
intensity_values,
@@ -72,12 +73,14 @@ def test_set_one_pass(self):
7273
"""Test _tc_from_track function."""
7374
intensity_idx = [0, 1, 2, 3, 80, 100, 120, 200, 220, 250, 260, 295]
7475
intensity_values = {
75-
"geosphere": [25.60794285, 26.90906280, 28.26649026, 25.54076797, 31.21986961,
76-
36.17171808, 21.11408573, 28.01457948, 32.65349378, 31.34027741, 0,
77-
40.27362679],
78-
"equirect": [25.60778909, 26.90887264, 28.26624642, 25.54092386, 31.21941738,
79-
36.16596567, 21.11399856, 28.01452136, 32.65076804, 31.33884098, 0,
80-
40.27002104]
76+
"geosphere": [
77+
22.74927, 23.78498, 24.822908, 22.674202, 27.220042, 30.602122,
78+
18.981022, 24.540138, 27.830925, 26.8489, 0., 34.572391,
79+
],
80+
"equirect": [
81+
22.74903, 23.784691, 24.82255, 22.67403, 27.218706, 30.593959,
82+
18.980878, 24.540069, 27.826407, 26.846293, 0., 34.568898,
83+
]
8184
}
8285
# the values for the two metrics should agree up to first digit at least
8386
for i, val in enumerate(intensity_values["geosphere"]):
@@ -108,7 +111,7 @@ def test_set_one_pass(self):
108111

109112
self.assertTrue(isinstance(tc_haz.intensity, sparse.csr_matrix))
110113
self.assertEqual(tc_haz.intensity.shape, (1, 296))
111-
self.assertEqual(np.nonzero(tc_haz.intensity)[0].size, 280)
114+
self.assertEqual(np.nonzero(tc_haz.intensity)[0].size, 255)
112115

113116
np.testing.assert_array_almost_equal(
114117
tc_haz.intensity[0, intensity_idx].toarray()[0], intensity_values[metric])
@@ -127,10 +130,14 @@ def test_windfield_models(self):
127130
"""Test _tc_from_track function with different wind field models."""
128131
intensity_idx = [0, 1, 2, 3, 80, 100, 120, 200, 220, 250, 260, 295]
129132
intensity_values = {
130-
"H08": [25.60778909, 26.90887264, 28.26624642, 25.54092386, 31.21941738, 36.16596567,
131-
21.11399856, 28.01452136, 32.65076804, 31.33884098, 0, 40.27002104],
132-
"H10": [27.604317, 28.720708, 29.894993, 27.52234 , 32.512395, 37.114355,
133-
23.848917, 29.614752, 33.775593, 32.545347, 19.957627, 41.014578],
133+
"H08": [
134+
22.74903, 23.784691, 24.82255, 22.67403, 27.218706, 30.593959,
135+
18.980878, 24.540069, 27.826407, 26.846293, 0., 34.568898,
136+
],
137+
"H10": [
138+
24.745521, 25.596484, 26.475329, 24.690914, 28.650107, 31.584395,
139+
21.723546, 26.140293, 28.94964, 28.051915, 18.49378, 35.312152,
140+
],
134141
# Holland 1980 and Emanuel & Rotunno 2011 use recorded wind speeds, while the above use
135142
# pressure values only. That's why the results are so different:
136143
"H1980": [21.376807, 21.957217, 22.569568, 21.284351, 24.254226, 26.971303,

climada/hazard/trop_cyclone.py

Lines changed: 50 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -955,11 +955,6 @@ def _compute_windfields(
955955
si_track, d_centr, close_centr_msk, model, cyclostrophic=False,
956956
)
957957

958-
# vectorial angular velocity
959-
windfields = (
960-
si_track.attrs["latsign"] * np.array([1.0, -1.0])[..., :] * v_centr_normed[:, :, ::-1]
961-
)
962-
windfields[close_centr_msk] *= v_ang_norm[close_centr_msk, None]
963958

964959
# Influence of translational speed decreases with distance from eye.
965960
# The "absorbing factor" is according to the following paper (see Fig. 7):
@@ -969,11 +964,26 @@ def _compute_windfields(
969964
# wind speed profiles in a GIS. UNED/GRID-Geneva.
970965
# https://unepgrid.ch/en/resource/19B7D302
971966
#
972-
t_rad_bc = np.broadcast_arrays(si_track["rad"].values[:, None], d_centr)[0]
967+
t_rad_bc = np.broadcast_to(si_track["rad"].values[:, None], d_centr.shape)
973968
v_trans_corr = np.zeros_like(d_centr)
974969
v_trans_corr[close_centr_msk] = np.fmin(
975970
1, t_rad_bc[close_centr_msk] / d_centr[close_centr_msk])
976971

972+
if model in [MODEL_VANG['H08'], MODEL_VANG['H10']]:
973+
# In these models, v_ang_norm already contains vtrans_norm, so subtract it first, before
974+
# converting to vectors and then adding (vectorial) vtrans again. Make sure to apply the
975+
# "absorbing factor" in both steps:
976+
vtrans_norm_bc = np.broadcast_to(si_track["vtrans_norm"].values[:, None], d_centr.shape)
977+
v_ang_norm[close_centr_msk] -= (
978+
vtrans_norm_bc[close_centr_msk] * v_trans_corr[close_centr_msk]
979+
)
980+
981+
# vectorial angular velocity
982+
windfields = (
983+
si_track.attrs["latsign"] * np.array([1.0, -1.0])[..., :] * v_centr_normed[:, :, ::-1]
984+
)
985+
windfields[close_centr_msk] *= v_ang_norm[close_centr_msk, None]
986+
977987
# add angular and corrected translational velocity vectors
978988
windfields[1:] += si_track["vtrans"].values[1:, None, :] * v_trans_corr[1:, :, None]
979989
windfields[np.isnan(windfields)] = 0
@@ -1407,11 +1417,15 @@ def _x_holland_2010(
14071417
"""
14081418
hol_x = np.zeros_like(d_centr)
14091419
r_max, v_max_s, hol_b, d_centr, v_n, r_n = [
1410-
ar[close_centr] for ar in np.broadcast_arrays(
1411-
si_track["rad"].values[:, None], si_track["vmax"].values[:, None],
1412-
si_track["hol_b"].values[:, None], d_centr,
1413-
np.atleast_1d(v_n)[:, None], np.atleast_1d(r_n_km)[:, None],
1414-
)
1420+
np.broadcast_to(ar, d_centr.shape)[close_centr]
1421+
for ar in [
1422+
si_track["rad"].values[:, None],
1423+
si_track["vmax"].values[:, None],
1424+
si_track["hol_b"].values[:, None],
1425+
d_centr,
1426+
np.atleast_1d(v_n)[:, None],
1427+
np.atleast_1d(r_n_km)[:, None],
1428+
]
14151429
]
14161430

14171431
# convert to SI units
@@ -1470,11 +1484,15 @@ def _stat_holland_2010(
14701484
Absolute values of wind speeds (in m/s) in angular direction.
14711485
"""
14721486
v_ang = np.zeros_like(d_centr)
1473-
d_centr, v_max_s, r_max, hol_b, hol_x = [
1474-
ar[close_centr] for ar in np.broadcast_arrays(
1475-
d_centr, si_track["vmax"].values[:, None], si_track["rad"].values[:, None],
1476-
si_track["hol_b"].values[:, None], hol_x,
1477-
)
1487+
v_max_s, r_max, hol_b, d_centr, hol_x = [
1488+
np.broadcast_to(ar, d_centr.shape)[close_centr]
1489+
for ar in [
1490+
si_track["vmax"].values[:, None],
1491+
si_track["rad"].values[:, None],
1492+
si_track["hol_b"].values[:, None],
1493+
d_centr,
1494+
hol_x,
1495+
]
14781496
]
14791497

14801498
r_max_norm = (r_max / np.fmax(1, d_centr))**hol_b
@@ -1526,12 +1544,16 @@ def _stat_holland_1980(
15261544
Absolute values of wind speeds (m/s) in angular direction.
15271545
"""
15281546
v_ang = np.zeros_like(d_centr)
1529-
d_centr, r_max, hol_b, penv, pcen, coriolis_p = [
1530-
ar[close_centr] for ar in np.broadcast_arrays(
1531-
d_centr, si_track["rad"].values[:, None], si_track["hol_b"].values[:, None],
1532-
si_track["env"].values[:, None], si_track["cen"].values[:, None],
1533-
si_track["cp"].values[:, None]
1534-
)
1547+
r_max, hol_b, penv, pcen, coriolis_p, d_centr = [
1548+
np.broadcast_to(ar, d_centr.shape)[close_centr]
1549+
for ar in [
1550+
si_track["rad"].values[:, None],
1551+
si_track["hol_b"].values[:, None],
1552+
si_track["env"].values[:, None],
1553+
si_track["cen"].values[:, None],
1554+
si_track["cp"].values[:, None],
1555+
d_centr,
1556+
]
15351557
]
15361558

15371559
r_coriolis = 0
@@ -1587,12 +1609,14 @@ def _stat_er_2011(
15871609
Absolute values of wind speeds (m/s) in angular direction.
15881610
"""
15891611
v_ang = np.zeros_like(d_centr)
1590-
d_centr, r_max, v_max, coriolis_p = [
1591-
ar[close_centr] for ar in np.broadcast_arrays(
1592-
d_centr, si_track["rad"].values[:, None],
1612+
r_max, v_max, coriolis_p, d_centr = [
1613+
np.broadcast_to(ar, d_centr.shape)[close_centr]
1614+
for ar in [
1615+
si_track["rad"].values[:, None],
15931616
si_track["vmax"].values[:, None],
15941617
si_track["cp"].values[:, None],
1595-
)
1618+
d_centr,
1619+
]
15961620
]
15971621

15981622
# compute the momentum at the maximum

0 commit comments

Comments
 (0)