Skip to content

Commit fcdb159

Browse files
authored
Merge branch 'main' into cleanup/carq_hollandb
2 parents 9769866 + e4a3aab commit fcdb159

File tree

4 files changed

+81
-52
lines changed

4 files changed

+81
-52
lines changed

.github/workflows/test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ jobs:
4040
runs-on: ${{ matrix.os }}
4141
strategy:
4242
matrix:
43-
os: [ ubuntu-latest, macos-latest ]
43+
os: [ ubuntu-latest ]
4444
python: [ '3.9', '3.10', '3.11' ]
4545
steps:
4646
- uses: actions/checkout@main

stormevents/nhc/const.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77
class RMWFillMethod(Enum):
88
none = None
99
persistent = auto()
10-
regression_penny_2023 = auto()
10+
regression_penny_2023_with_smoothing = auto()
11+
regression_penny_2023 = regression_penny_2023_with_smoothing
12+
regression_penny_2023_no_smoothing = auto()
1113

1214

1315
# Bias correction values for the Rmax forecast

stormevents/nhc/track.py

Lines changed: 55 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1287,6 +1287,55 @@ def correct_ofcl_based_on_carq_n_hollandb(
12871287
def clamp(n, minn, maxn):
12881288
return max(min(maxn, n), minn)
12891289

1290+
def movingmean(dff):
1291+
fcsthr_index = dff["forecast_hours"].drop_duplicates().index
1292+
df_temp = dff.loc[fcsthr_index].copy()
1293+
# make sure 60, 84, and 108 are added
1294+
fcsthrs_12hr = numpy.unique(
1295+
numpy.append(df_temp["forecast_hours"].values, [60, 84, 108])
1296+
)
1297+
rmw_12hr = numpy.interp(
1298+
fcsthrs_12hr,
1299+
df_temp["forecast_hours"],
1300+
df_temp["radius_of_maximum_winds"],
1301+
)
1302+
dt_12hr = pandas.to_datetime(
1303+
fcsthrs_12hr, unit="h", origin=df_temp["datetime"].iloc[0]
1304+
)
1305+
df_temp = DataFrame(
1306+
data={
1307+
"forecast_hours": fcsthrs_12hr,
1308+
"radius_of_maximum_winds": rmw_12hr,
1309+
},
1310+
index=dt_12hr,
1311+
)
1312+
rmw_rolling = df_temp.rolling(window="24h1min", center=True, min_periods=1)[
1313+
"radius_of_maximum_winds"
1314+
].mean()
1315+
for valid_time, rmw in rmw_rolling.items():
1316+
valid_index = dff["datetime"] == valid_time
1317+
if (
1318+
valid_index.sum() == 0
1319+
or dff.loc[valid_index, "forecast_hours"].iloc[0] == 0
1320+
):
1321+
continue
1322+
# make sure rolling rmw is not larger than the maximum radii of the strongest isotach
1323+
# this problem usually comes from the rolling average
1324+
max_isotach_radii = isotach_radii.loc[valid_index].iloc[-1].max()
1325+
if rmw < max_isotach_radii or numpy.isnan(max_isotach_radii):
1326+
dff.loc[valid_index, "radius_of_maximum_winds"] = rmw
1327+
# in case it does not come from rolling average just set to be Vr/Vmax ratio of max_isotach_radii
1328+
if (
1329+
dff.loc[valid_index, "radius_of_maximum_winds"].iloc[-1]
1330+
> max_isotach_radii
1331+
):
1332+
dff.loc[valid_index, "radius_of_maximum_winds"] = (
1333+
max_isotach_radii
1334+
* dff.loc[valid_index, "isotach_radius"].iloc[-1]
1335+
/ dff.loc[valid_index, "max_sustained_wind_speed"].iloc[-1]
1336+
)
1337+
return dff
1338+
12901339
ofcl_tracks = tracks["OFCL"]
12911340
carq_tracks = tracks["CARQ"]
12921341

@@ -1327,7 +1376,10 @@ def clamp(n, minn, maxn):
13271376
"radius_of_maximum_winds"
13281377
]
13291378

1330-
elif rmw_fill == RMWFillMethod.regression_penny_2023:
1379+
elif (
1380+
rmw_fill == RMWFillMethod.regression_penny_2023_with_smoothing
1381+
or rmw_fill == RMWFillMethod.regression_penny_2023_no_smoothing
1382+
):
13311383
# fill OFCL maximum wind radius based on regression method from
13321384
# Penny et al. (2023). https://doi.org/10.1175/WAF-D-22-0209.1
13331385
isotach_radii = forecast[
@@ -1383,52 +1435,8 @@ def clamp(n, minn, maxn):
13831435
rmw_, 5.0, max(120.0, rmw0)
13841436
)
13851437
# apply 24-HR moving mean to unique datetimes
1386-
fcsthr_index = forecast["forecast_hours"].drop_duplicates().index
1387-
df_temp = forecast.loc[fcsthr_index].copy()
1388-
# make sure 60, 84, and 108 are added
1389-
fcsthrs_12hr = numpy.unique(
1390-
numpy.append(df_temp["forecast_hours"].values, [60, 84, 108])
1391-
)
1392-
rmw_12hr = numpy.interp(
1393-
fcsthrs_12hr,
1394-
df_temp["forecast_hours"],
1395-
df_temp["radius_of_maximum_winds"],
1396-
)
1397-
dt_12hr = pandas.to_datetime(
1398-
fcsthrs_12hr, unit="h", origin=df_temp["datetime"].iloc[0]
1399-
)
1400-
df_temp = DataFrame(
1401-
data={
1402-
"forecast_hours": fcsthrs_12hr,
1403-
"radius_of_maximum_winds": rmw_12hr,
1404-
},
1405-
index=dt_12hr,
1406-
)
1407-
rmw_rolling = df_temp.rolling(window="24.01 h", center=True, min_periods=1)[
1408-
"radius_of_maximum_winds"
1409-
].mean()
1410-
for valid_time, rmw in rmw_rolling.items():
1411-
valid_index = forecast["datetime"] == valid_time
1412-
if (
1413-
valid_index.sum() == 0
1414-
or forecast.loc[valid_index, "forecast_hours"].iloc[0] == 0
1415-
):
1416-
continue
1417-
# make sure rolling rmw is not larger than the maximum radii of the strongest isotach
1418-
# this problem usually comes from the rolling average
1419-
max_isotach_radii = isotach_radii.loc[valid_index].iloc[-1].max()
1420-
if rmw < max_isotach_radii or numpy.isnan(max_isotach_radii):
1421-
forecast.loc[valid_index, "radius_of_maximum_winds"] = rmw
1422-
# in case it does not come from rolling average just set to be Vr/Vmax ratio of max_isotach_radii
1423-
if (
1424-
forecast.loc[valid_index, "radius_of_maximum_winds"].iloc[-1]
1425-
> max_isotach_radii
1426-
):
1427-
forecast.loc[valid_index, "radius_of_maximum_winds"] = (
1428-
max_isotach_radii
1429-
* forecast.loc[valid_index, "isotach_radius"].iloc[-1]
1430-
/ forecast.loc[valid_index, "max_sustained_wind_speed"].iloc[-1]
1431-
)
1438+
if rmw_fill == RMWFillMethod.regression_penny_2023_with_smoothing:
1439+
forecast = movingmean(forecast)
14321440

14331441
# fill OFCL background pressure with the first entry from 0-hr CARQ background pressure (at sea level)
14341442
forecast.loc[radp_missing, "background_pressure"] = carq_ref[

tests/test_nhc.py

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -468,21 +468,40 @@ def test_rmw_fill_method_persistent():
468468
assert rmw.unique() == 10
469469

470470

471-
def test_rmw_fill_method_regression_penny_2023():
471+
def test_rmw_fill_method_regression_penny_2023_with_smoothing():
472472
tr_florence2018 = VortexTrack.from_storm_name(
473473
"Florence",
474474
2018,
475475
file_deck="a",
476476
advisories=["OFCL"],
477477
rmw_fill=RMWFillMethod.regression_penny_2023,
478478
)
479-
assert tr_florence2018.rmw_fill == RMWFillMethod.regression_penny_2023
479+
assert (
480+
tr_florence2018.rmw_fill == RMWFillMethod.regression_penny_2023_with_smoothing
481+
)
482+
data = tr_florence2018.data
483+
i_uq_row = 40
484+
rmw = data.loc[data.track_start_time == data.track_start_time.unique()[i_uq_row]][
485+
"radius_of_maximum_winds"
486+
]
487+
assert len(rmw.unique()) == 11
488+
489+
490+
def test_rmw_fill_method_regression_penny_2023_no_smoothing():
491+
tr_florence2018 = VortexTrack.from_storm_name(
492+
"Florence",
493+
2018,
494+
file_deck="a",
495+
advisories=["OFCL"],
496+
rmw_fill=RMWFillMethod.regression_penny_2023_no_smoothing,
497+
)
498+
assert tr_florence2018.rmw_fill == RMWFillMethod.regression_penny_2023_no_smoothing
480499
data = tr_florence2018.data
481500
i_uq_row = 40
482501
rmw = data.loc[data.track_start_time == data.track_start_time.unique()[i_uq_row]][
483502
"radius_of_maximum_winds"
484503
]
485-
assert len(rmw.unique()) > 1
504+
assert len(rmw.unique()) == 10
486505

487506

488507
def test_rmw_fill_method_setvalue_invalid():

0 commit comments

Comments
 (0)