Skip to content

Commit 7045f62

Browse files
Merge pull request #74 from oceanmodeling/feature/rmw_option
Feature/rmw option
2 parents 9edaa49 + 32b9bfb commit 7045f62

File tree

10 files changed

+177
-51
lines changed

10 files changed

+177
-51
lines changed

pyproject.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ dependencies = [
5454
"pytz",
5555
"pyyaml",
5656
"shapely>=2",
57-
"stormevents>=2.3.2", # rmax forecast
57+
"stormevents>=2.3.4", # rmax option
5858
"rasterio",
5959
"requests",
6060
"rtree",

stormworkflow/main.py

+24-5
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818

1919
_logger = logging.getLogger(__file__)
2020

21-
CUR_INPUT_VER = Version('0.0.2')
21+
CUR_INPUT_VER = Version('0.0.3')
2222

2323

2424
def _handle_input_v0_0_1_to_v0_0_2(inout_conf):
@@ -43,6 +43,23 @@ def _handle_input_v0_0_1_to_v0_0_2(inout_conf):
4343
return Version('0.0.2')
4444

4545

46+
def _handle_input_v0_0_2_to_v0_0_3(inout_conf):
47+
48+
ver = Version(inout_conf['input_version'])
49+
50+
# Only update config if specified version matches the assumed one
51+
if ver != Version('0.0.2'):
52+
return ver
53+
54+
55+
_logger.info(
56+
"Adding RMW fill method default to persistent"
57+
)
58+
inout_conf['rmw_fill_method'] = 'persistent'
59+
60+
return Version('0.0.3')
61+
62+
4663
def handle_input_version(inout_conf):
4764

4865
if 'input_version' not in inout_conf:
@@ -60,16 +77,18 @@ def handle_input_version(inout_conf):
6077
f"Input version not supported! Max version supported is {CUR_INPUT_VER}"
6178
)
6279

63-
ver = _handle_input_v0_0_1_to_v0_0_2(inout_conf)
80+
for fn in [
81+
_handle_input_v0_0_1_to_v0_0_2,
82+
_handle_input_v0_0_2_to_v0_0_3,
83+
]:
84+
ver = fn(inout_conf)
85+
inout_conf['input_version'] = str(ver)
6486

6587
if ver != CUR_INPUT_VER:
6688
raise ValueError(
6789
f"Could NOT update input to the latest version! Updated to {ver}"
6890
)
6991

70-
inout_conf['input_version'] = str(ver)
71-
72-
7392
def main():
7493

7594
parser = ArgumentParser()

stormworkflow/prep/hurricane_data.py

+39-6
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
from shapely.geometry import box, base
2525
from stormevents import StormEvent
2626
from stormevents.nhc import VortexTrack
27+
from stormevents.nhc.const import RMWFillMethod
2728
from stormevents.nhc.track import (
2829
combine_tracks,
2930
correct_ofcl_based_on_carq_n_hollandb,
@@ -144,6 +145,7 @@ def main(args):
144145
hr_before_landfall = args.hours_before_landfall
145146
lead_times = args.lead_times
146147
track_dir = args.preprocessed_tracks_dir
148+
rmw_fill = RMWFillMethod[args.rmw_fill.lower()]
147149

148150
if hr_before_landfall < 0:
149151
hr_before_landfall = 48
@@ -183,7 +185,8 @@ def main(args):
183185

184186
advisory = 'OFCL'
185187
if not local_track_file.is_file():
186-
# Find and pick a single advisory based on priority
188+
# Find and pick a single advisory based on priority, the
189+
# track is only used to get the available advisories
187190
temp_track = event.track(file_deck='a')
188191
adv_avail = temp_track.unfiltered_data.advisory.unique()
189192
adv_order = ['OFCL', 'HWRF', 'HMON', 'CARQ']
@@ -193,41 +196,65 @@ def main(args):
193196
advisory = adv
194197
break
195198

196-
# TODO: THIS IS NO LONGER RELEVANT IF WE FAKE RMWP AS OFCL!
197199
if advisory == "OFCL" and "CARQ" not in adv_avail:
198200
raise ValueError(
199201
"OFCL advisory needs CARQ for fixing missing variables!"
200202
)
201203

202-
track = VortexTrack(nhc_code, file_deck='a', advisories=[advisory])
204+
track = VortexTrack(
205+
nhc_code,
206+
file_deck='a',
207+
advisories=[advisory],
208+
rmw_fill=rmw_fill,
209+
)
203210

204211
else: # read from preprocessed file
212+
205213
advisory = 'OFCL'
206214

207215
# If a file exists, use the local file
208216
track_raw = pd.read_csv(local_track_file, header=None, dtype=str)
209-
assert len(track_raw[4].unique()) == 1
217+
# The provided tracks should have a single advisory type,
218+
# e.g. in NHC adjusted track files the value is RMWP
219+
if len(track_raw[4].unique()) != 1:
220+
raise RuntimeError(
221+
"Only single advisory-name track files are supported!"
222+
)
223+
# Treat the existing advisory as if it's OFCL so that
224+
# stormevents supports reading it
210225
track_raw[4] = advisory
211226

212227
with tempfile.NamedTemporaryFile() as tmp:
213228
track_raw.to_csv(tmp.name, header=False, index=False)
214229

230+
# Track read from file is NOT corrected because it
231+
# does NOT have CARQ advisory
215232
unfixed_track = VortexTrack(
216233
tmp.name, file_deck='a', advisories=[advisory]
217234
)
235+
# Since we're only getting CARQ, there's no need to
236+
# pass rmw fill method
218237
carq_track = event.track(file_deck='a', advisories=['CARQ'])
219238
unfix_dict = {
220239
**separate_tracks(unfixed_track.data),
221240
**separate_tracks(carq_track.data),
222241
}
223242

224-
fix_dict = correct_ofcl_based_on_carq_n_hollandb(unfix_dict)
243+
# Fix the file track with the fetched CARQ; if RMW
244+
# is already filled, it fills out other missing values
245+
fix_dict = correct_ofcl_based_on_carq_n_hollandb(
246+
unfix_dict, rmw_fill=rmw_fill
247+
)
225248
fix_track = combine_tracks(fix_dict)
226249

250+
# Create a new VortexTrack object from the datasets.
251+
# Since the values are already filled in, there's
252+
# no need to fill the rmw!
227253
track = VortexTrack(
228254
fix_track[fix_track.advisory == advisory],
229255
file_deck='a',
230-
advisories=[advisory]
256+
advisories=[advisory],
257+
rmw_fill=RMWFillMethod.none,
231258
)
232259

233260

@@ -418,6 +445,12 @@ def cli():
418445
help="Existing adjusted track directory",
419446
)
420447

448+
parser.add_argument(
449+
'--rmw-fill',
450+
type=str,
451+
help="Method to use to fill missing RMW data for OFCL track",
452+
)
453+
421454
args = parser.parse_args()
422455

423456
main(args)

stormworkflow/prep/setup_ensemble.py

+3
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,9 @@ def main(args):
176176
# get has unique forecast time for only the segment we want to
177177
# perturb, the preceeding entries are 0-hour forecasts from
178178
# previous forecast_times
179+
#
180+
# Here we're working with NA-filled track files, so there's
181+
# no need for rmw fill argument
179182
track_to_perturb = VortexTrack.from_file(
180183
track_path,
181184
start_date=perturb_start,

stormworkflow/refs/input.yaml

+9-7
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
input_version: 0.0.2
2+
input_version: 0.0.3
33

44
storm: "florence"
55
year: 2018
@@ -12,14 +12,16 @@ use_wwm: 0
1212
pahm_model: "gahm"
1313
num_perturb: 2
1414
sample_rule: "korobov"
15+
perturb_vars:
16+
- "cross_track"
17+
- "along_track"
18+
# - "radius_of_maximum_winds"
19+
- "radius_of_maximum_winds_persistent"
20+
- "max_sustained_wind_speed"
21+
rmw_fill_method: "persistent"
22+
1523
spinup_exec: "pschism_PAHM_TVD-VL"
1624
hotstart_exec: "pschism_PAHM_TVD-VL"
17-
perturb_vars:
18-
- 'cross_track'
19-
- 'along_track'
20-
# - 'radius_of_maximum_winds'
21-
- 'radius_of_maximum_winds_persistent'
22-
- 'max_sustained_wind_speed'
2325

2426
hpc_solver_nnodes: 3
2527
hpc_solver_ntasks: 108

stormworkflow/scripts/workflow.sh

+1
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ hurricane_data \
7777
--hours-before-landfall "$hr_prelandfall" \
7878
--lead-times "$L_LEADTIMES_DATASET" \
7979
--preprocessed-tracks-dir "$L_TRACK_DIR" \
80+
--rmw-fill "$rmw_fill_method" \
8081
$storm $year 2>&1 | tee "${run_dir}/output/head_hurricane_data.out"
8182

8283

stormworkflow/slurm/post.sbatch

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#SBATCH --parsable
33
#SBATCH --time=05:00:00
44
#SBATCH --nodes=1
5+
#SBATCH --exclusive
56

67
set -ex
78

tests/conftest.py

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
from importlib.resources import files
2+
3+
import pytest
4+
import yaml
5+
from yaml import Loader
6+
7+
8+
refs = files('stormworkflow.refs')
9+
test_refs = files('tests.data.refs')
10+
input_v0_0_1 = test_refs.joinpath('input_v0.0.1.yaml')
11+
input_v0_0_2 = test_refs.joinpath('input_v0.0.2.yaml')
12+
input_v0_0_3 = test_refs.joinpath('input_v0.0.3.yaml')
13+
input_latest = refs.joinpath('input.yaml')
14+
15+
16+
def read_conf(infile):
17+
with open(infile, 'r') as yfile:
18+
conf = yaml.load(yfile, Loader=Loader)
19+
return conf
20+
21+
22+
@pytest.fixture
23+
def conf_v0_0_1():
24+
return read_conf(input_v0_0_1)
25+
26+
27+
@pytest.fixture
28+
def conf_v0_0_2():
29+
return read_conf(input_v0_0_2)
30+
31+
32+
@pytest.fixture
33+
def conf_v0_0_3():
34+
return read_conf(input_v0_0_3)
35+
36+
37+
@pytest.fixture
38+
def conf_latest():
39+
return read_conf(input_latest)

tests/data/refs/input_v0.0.3.yaml

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
---
2+
input_version: 0.0.3
3+
4+
storm: "florence"
5+
year: 2018
6+
suffix: ""
7+
subset_mesh: 1
8+
hr_prelandfall: -1
9+
past_forecast: 1
10+
hydrology: 0
11+
use_wwm: 0
12+
pahm_model: "gahm"
13+
num_perturb: 2
14+
sample_rule: "korobov"
15+
perturb_vars:
16+
- "cross_track"
17+
- "along_track"
18+
# - "radius_of_maximum_winds"
19+
- "radius_of_maximum_winds_persistent"
20+
- "max_sustained_wind_speed"
21+
rmw_fill_method: "persistent"
22+
23+
spinup_exec: "pschism_PAHM_TVD-VL"
24+
hotstart_exec: "pschism_PAHM_TVD-VL"
25+
26+
hpc_solver_nnodes: 3
27+
hpc_solver_ntasks: 108
28+
hpc_account: ""
29+
hpc_partition: ""
30+
31+
RUN_OUT: ""
32+
L_NWM_DATASET: ""
33+
L_TPXO_DATASET: ""
34+
L_LEADTIMES_DATASET: ""
35+
L_TRACK_DIR: ""
36+
L_DEM_HI: ""
37+
L_DEM_LO: ""
38+
L_MESH_HI: ""
39+
L_MESH_LO: ""
40+
L_SHP_DIR: ""
41+
42+
TMPDIR: "/tmp"
43+
PATH_APPEND: ""
44+
45+
L_SOLVE_MODULES:
46+
- "intel/2022.1.2"
47+
- "impi/2022.1.2"
48+
- "netcdf"

tests/test_input_version.py

+12-32
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,10 @@
11
from copy import deepcopy
2-
from importlib.resources import files
32

43
import pytest
5-
import yaml
6-
from packaging.version import Version
7-
from yaml import Loader, Dumper
84

95
from stormworkflow.main import handle_input_version, CUR_INPUT_VER
106

117

12-
refs = files('tests.data.refs')
13-
input_v0_0_1 = refs.joinpath('input_v0.0.1.yaml')
14-
input_v0_0_2 = refs.joinpath('input_v0.0.2.yaml')
15-
16-
17-
def read_conf(infile):
18-
with open(infile, 'r') as yfile:
19-
conf = yaml.load(yfile, Loader=Loader)
20-
return conf
21-
22-
23-
@pytest.fixture
24-
def conf_v0_0_1():
25-
return read_conf(input_v0_0_1)
26-
27-
28-
@pytest.fixture
29-
def conf_v0_0_2():
30-
return read_conf(input_v0_0_2)
31-
32-
33-
@pytest.fixture
34-
def conf_latest(conf_v0_0_2):
35-
return conf_v0_0_2
36-
37-
388
def test_no_version_specified(conf_latest):
399
conf_latest.pop('input_version')
4010
with pytest.warns(UserWarning):
@@ -62,6 +32,16 @@ def test_invalid_version_specified(conf_latest):
6232
assert "invalid version" in str(e.value).lower()
6333

6434

65-
def test_v0_0_1_to_v0_0_2(conf_v0_0_1, conf_v0_0_2):
35+
def test_v0_0_1_to_latest(conf_v0_0_1, conf_latest):
6636
handle_input_version(conf_v0_0_1)
67-
assert conf_v0_0_2 == conf_v0_0_1
37+
assert conf_latest == conf_v0_0_1
38+
39+
40+
def test_v0_0_2_to_latest(conf_v0_0_2, conf_latest):
41+
handle_input_version(conf_v0_0_2)
42+
assert conf_latest == conf_v0_0_2
43+
44+
45+
def test_v0_0_3_to_latest(conf_v0_0_3, conf_latest):
46+
handle_input_version(conf_v0_0_3)
47+
assert conf_latest == conf_v0_0_3

0 commit comments

Comments
 (0)