Skip to content

Commit 8c8ee96

Browse files
authored
Fix exponent clipping in Holland 2010 implementation (#793)
Remove restriction of exponent to [0.0, 0.5]. Only enforce that the exponent is non-negative. Update tests accordingly.
1 parent 240dafa commit 8c8ee96

File tree

2 files changed

+57
-12
lines changed

2 files changed

+57
-12
lines changed

climada/hazard/test/test_trop_cyclone.py

Lines changed: 50 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -304,22 +304,61 @@ def test_v_max_s_holland_2008_pass(self):
304304

305305
def test_holland_2010_pass(self):
306306
"""Test Holland et al. 2010 wind field model."""
307-
# test at centroids within and outside of radius of max wind
307+
# The parameter "x" is designed to be exactly 0.5 inside the radius of max wind (RMW) and
308+
# to increase or decrease linearly outside of it in radial direction.
309+
#
310+
# An increase (decrease) of "x" outside of the RMW is for cases where the max wind is very
311+
# high (low), but the RMW is still comparably large (small). This means, wind speeds need
312+
# to decay very sharply (only moderately) outside of the RMW to reach the low prescribed
313+
# peripheral wind speeds.
314+
#
315+
# The "hol_b" parameter tunes the meaning of a "comparably" large or small RMW.
308316
si_track = xr.Dataset({
309-
"rad": ("time", KM_TO_M * np.array([75, 40])),
310-
"vmax": ("time", [35.0, 40.0]),
311-
"hol_b": ("time", [1.80, 2.5]),
317+
# four test cases:
318+
# - low vmax, moderate RMW: x decreases moderately
319+
# - large hol_b: x decreases sharply
320+
# - very low vmax: x decreases so much, it needs to be clipped at 0
321+
# - large vmax, large RMW: x increases
322+
"rad": ("time", KM_TO_M * np.array([75, 75, 75, 90])),
323+
"vmax": ("time", [35.0, 35.0, 16.0, 90.0]),
324+
"hol_b": ("time", [1.75, 2.5, 1.9, 1.6]),
312325
})
313-
d_centr = KM_TO_M * np.array([[35, 75, 220], [30, 1000, 300]], dtype=float)
314-
close_centr = np.array([[True, True, True], [True, False, True]], dtype=bool)
326+
d_centr = KM_TO_M * np.array([
327+
# first column is for locations within the storm eye
328+
# second column is for locations at or close to the radius of max wind
329+
# third column is for locations outside the storm eye
330+
# fourth column is for locations exactly at the peripheral radius
331+
# fifth column is for locations outside the peripheral radius
332+
[0., 75, 220, 300, 490],
333+
[30, 74, 170, 300, 501],
334+
[21, 76, 230, 300, 431],
335+
[32, 91, 270, 300, 452],
336+
], dtype=float)
337+
close_centr = np.array([
338+
# note that we set one of these to "False" for testing
339+
[True, True, True, True, True],
340+
[True, True, True, True, False],
341+
[True, True, True, True, True],
342+
[True, True, True, True, True],
343+
], dtype=bool)
315344
hol_x = _x_holland_2010(si_track, d_centr, close_centr)
316-
np.testing.assert_array_almost_equal(
317-
hol_x, [[0.5, 0.5, 0.47273], [0.5, 0, 0.211602]])
345+
np.testing.assert_array_almost_equal(hol_x, [
346+
[0.5, 0.500000, 0.485077, 0.476844, 0.457291],
347+
[0.5, 0.500000, 0.410997, 0.289203, 0.000000],
348+
[0.5, 0.497620, 0.131072, 0.000000, 0.000000],
349+
[0.5, 0.505022, 1.403952, 1.554611, 2.317948],
350+
])
318351

319-
# test exactly at radius of maximum wind (35 m/s) and at peripheral radius (17 m/s)
320352
v_ang_norm = _stat_holland_2010(si_track, d_centr, close_centr, hol_x)
321-
np.testing.assert_array_almost_equal(v_ang_norm,
322-
[[15.957853, 35.0, 20.99411], [33.854826, 0, 17.0]])
353+
np.testing.assert_array_almost_equal(v_ang_norm, [
354+
# first column: converge to 0 when approaching storm eye
355+
# second column: vmax at RMW
356+
# fourth column: peripheral speed (17 m/s) at peripheral radius (unless x is clipped!)
357+
[0.0000000, 35.000000, 21.181497, 17.00000, 12.103461],
358+
[1.2964800, 34.990037, 21.593755, 17.00000, 0.0000000],
359+
[0.3219518, 15.997500, 13.585498, 16.00000, 16.000000],
360+
[24.823469, 89.992938, 24.381965, 17.00000, 1.9292020],
361+
])
323362

324363
def test_stat_holland_1980(self):
325364
"""Test _stat_holland_1980 function. Compare to MATLAB reference."""

climada/hazard/trop_cyclone.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1424,7 +1424,13 @@ def _x_holland_2010(
14241424
# linearly interpolate between max exponent and peripheral exponent
14251425
x_max = 0.5
14261426
hol_x[close_centr] = x_max + np.fmax(0, d_centr - r_max) * (x_n - x_max) / (r_n - r_max)
1427-
hol_x[close_centr] = np.clip(hol_x[close_centr], 0.0, 0.5)
1427+
1428+
# Negative hol_x values appear when v_max_s is very close to or even lower than v_n (which
1429+
# should never happen in theory). In those cases, wind speeds might decrease outside of the eye
1430+
# wall and increase again towards the peripheral radius (which is actually unphysical).
1431+
# We clip hol_x to 0, otherwise wind speeds keep increasing indefinitely away from the eye:
1432+
hol_x[close_centr] = np.fmax(hol_x[close_centr], 0.0)
1433+
14281434
return hol_x
14291435

14301436
def _stat_holland_2010(

0 commit comments

Comments
 (0)