Skip to content

Commit

Permalink
Replace gfs_cyc with interval
Browse files Browse the repository at this point in the history
To facilitate longer and more flexible GFS cadences, the `gfs_cyc`
variable is replaced with a specified interval. Up front, this is
reflected in a change in the arguments for setup_exp to:

```
--interval <n_hours>
```

Where `n_hours` is the interval (in hours) between gfs forecasts.
`n_hours` must be a multiple of 6. If 0, no gfs will be run (only
gdas; only valid for cycled mode). The default value is 6 (every
cycle).

In cycled mode, there is an additional argument to control which
cycle will be the first gfs cycle:

```
---sdate_gfs <YYYYMMDDHH>
```

The default if not provided is `--idate` + 6h (first full cycle).

As part of this change, some of the validation of the dates has
been added. `--edate` has also been made optional and defaults to
`--idate` if not provided.

During `config.base` template-filling, `INTERVAL_GFS` (renamed from
`STEP_GFS`) is defined as `--interval` and `SDATE_GFS as
`--sdate_gfs`.

Some changes were necessary to the gfs verification (metp) job, as
`gfs_cyc` was being used downstream by verif-global. That has been
removed, and instead workflow will be responsible for only running
metp on the correct cycles. This also removes "do nothing" metp
tasks that exit immediately, because only the last GFS cycle in a
day would actually process verification.

Now, metp has its own cycledef and always runs at 18z, regardless
of whether gfs is running at 18z or not. This is simplier than
trying to determine the last gfs cycle of a day when it could
change from day to day. To facilitate this change, support for the
undocumented rocoto dependency tag `taskvalid` is added, as the
metp task needs to know whether the cycle has a gfsarch task or not.
metp will trigger on gfsarch completing (as before), or gdasarch
completing if there is no gfsarch.

metp tasks are no longer generated for forecast-only, as the pgbanl
files (copied of the 1p00 pgbanl files) are not generated for f-o
anyway. If metp is needed for f-o, additional work will be needed.

Additionally, a couple EE2 issues with the metp job are resolved
(even though it is not run in ops):
- verif-global update replaced `$CDUMP` with `$RUN`
- `$DATAROOT` is no longer redefined in the metp job

Depends on NOAA-EMC/EMC_verif-global#137
Resolves NOAA-EMC#260
Refs NOAA-EMC#1299
  • Loading branch information
WalterKolczynski-NOAA committed Sep 17, 2024
1 parent 2602eac commit b50b930
Show file tree
Hide file tree
Showing 19 changed files with 150 additions and 160 deletions.
2 changes: 1 addition & 1 deletion gempak/ush/gfs_meta_comp.sh
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ device="nc | ${metaname}"

export COMIN="gfs.multi"
mkdir "${COMIN}"
for cycle in $(seq -f "%02g" -s ' ' 0 "${STEP_GFS}" "${cyc}"); do
for cycle in $(seq -f "%02g" -s ' ' 0 "${INTERVAL_GFS}" "${cyc}"); do
YMD=${PDY} HH=${cycle} GRID="1p00" declare_from_tmpl gempak_dir:COM_ATMOS_GEMPAK_TMPL
for file_in in "${gempak_dir}/gfs_1p00_${PDY}${cycle}f"*; do
file_out="${COMIN}/$(basename "${file_in}")"
Expand Down
2 changes: 1 addition & 1 deletion gempak/ush/gfs_meta_mar_comp.sh
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ cp "${HOMEgfs}/gempak/fix/datatype.tbl" datatype.tbl

export COMIN="gfs.multi"
mkdir -p "${COMIN}"
for cycle in $(seq -f "%02g" -s ' ' 0 "${STEP_GFS}" "${cyc}"); do
for cycle in $(seq -f "%02g" -s ' ' 0 "${INTERVAL_GFS}" "${cyc}"); do
YMD=${PDY} HH=${cycle} GRID="1p00" declare_from_tmpl gempak_dir:COM_ATMOS_GEMPAK_TMPL
for file_in in "${gempak_dir}/gfs_1p00_${PDY}${cycle}f"*; do
file_out="${COMIN}/$(basename "${file_in}")"
Expand Down
6 changes: 0 additions & 6 deletions jobs/JGFS_ATMOS_VERIFICATION
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,6 @@ source "${HOMEgfs}/ush/jjob_header.sh" -e "metp" -c "base metp"
## METPCASE : METplus verification use case (g2g1 | g2o1 | pcp1)
###############################################################

# TODO: This should not be permitted as DATAROOT is set at the job-card level.
# TODO: DATAROOT is being used as DATA in metp jobs. This should be rectified in metp.
# TODO: The temporary directory is DATA and is created at the top of the J-Job.
# TODO: remove this line
export DATAROOT=${DATA}

VDATE=$(date --utc +%Y%m%d%H -d "${PDY} ${cyc} - ${VRFYBACK_HRS} hours")
export VDATE=${VDATE:0:8}

Expand Down
8 changes: 2 additions & 6 deletions parm/config/gefs/config.base
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,8 @@ export FHOUT_OCN=3
export FHOUT_ICE=3

# GFS cycle info
export gfs_cyc=@gfs_cyc@ # 0: no GFS cycle, 1: 00Z only, 2: 00Z and 12Z only, 4: all 4 cycles.
export INTERVAL_GFS=@INTERVAL_GFS@ # Frequency of GFS forecast
export SDATE_GFS=@SDATE_GFS@

# GFS output and frequency
export FHMIN_GFS=0
Expand All @@ -243,11 +244,6 @@ export FHOUT_WAV=3
export FHMAX_HF_WAV=120
export FHOUT_HF_WAV=1
export FHMAX_WAV=${FHMAX_GFS}
if (( gfs_cyc != 0 )); then
export STEP_GFS=$(( 24 / gfs_cyc ))
else
export STEP_GFS="0"
fi
export ILPOST=1 # gempak output frequency up to F120

export FHMIN_ENKF=${FHMIN_GFS}
Expand Down
9 changes: 3 additions & 6 deletions parm/config/gfs/config.base
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,8 @@ export FHOUT_ICE=3
export EUPD_CYC="@EUPD_CYC@"

# GFS cycle info
export gfs_cyc=@gfs_cyc@ # 0: no GFS cycle, 1: 00Z only, 2: 00Z and 12Z only, 4: all 4 cycles.
export INTERVAL_GFS=@INTERVAL_GFS@ # Frequency of GFS forecast
export SDATE_GFS=@SDATE_GFS@

# GFS output and frequency
export FHMIN_GFS=0
Expand All @@ -299,11 +300,7 @@ export FHMAX_HF_WAV=120
export FHOUT_HF_WAV=1
export FHMAX_WAV=${FHMAX:-9}
export FHMAX_WAV_GFS=${FHMAX_GFS}
if (( gfs_cyc != 0 )); then
export STEP_GFS=$(( 24 / gfs_cyc ))
else
export STEP_GFS="0"
fi

# TODO: Change gempak to use standard out variables (#2348)
export ILPOST=${FHOUT_HF_GFS} # gempak output frequency up to F120
if (( FHMAX_HF_GFS < 120 )); then
Expand Down
2 changes: 1 addition & 1 deletion parm/config/gfs/config.wave
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ if [[ "${RUN}" == "gdas" ]]; then
export WAVNCYC=4
export WAVHCYC=${assim_freq:-6}
export FHMAX_WAV_CUR=48 # RTOFS forecasts only out to 8 days
elif [[ ${gfs_cyc} -ne 0 ]]; then
elif (( INTERVAL_GFS > 0 )); then
export WAVHCYC=${assim_freq:-6}
export FHMAX_WAV_CUR=192 # RTOFS forecasts only out to 8 days
else
Expand Down
16 changes: 8 additions & 8 deletions scripts/exgfs_aero_init_aerosol.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,13 @@
---------
This script requires the following environment variables be set beforehand:
CDATE: Initial time in YYYYMMDDHH format
STEP_GFS: Forecast cadence (frequency) in hours
FHMAX_GFS: Forecast length in hours
RUN: Forecast phase (gfs or gdas). Currently always expected to be gfs.
ROTDIR: Rotating (COM) directory
USHgfs: Path to global-workflow `ush` directory
PARMgfs: Path to global-workflow `parm` directory
CDATE: Initial time in YYYYMMDDHH format
INTERVAL_GFS: Forecast cadence (frequency) in hours
FHMAX_GFS: Forecast length in hours
RUN: Forecast phase (gfs or gdas). Currently always expected to be gfs.
ROTDIR: Rotating (COM) directory
USHgfs: Path to global-workflow `ush` directory
PARMgfs: Path to global-workflow `parm` directory
Additionally, the following data files are used:
Expand Down Expand Up @@ -66,7 +66,7 @@
def main() -> None:
# Read in environment variables and make sure they exist
cdate = get_env_var("CDATE")
incr = int(get_env_var('STEP_GFS'))
incr = int(get_env_var('INTERVAL_GFS'))
fcst_length = int(get_env_var('FHMAX_GFS'))
run = get_env_var("RUN")
rot_dir = get_env_var("ROTDIR")
Expand Down
2 changes: 1 addition & 1 deletion sorc/verif-global.fd
17 changes: 1 addition & 16 deletions workflow/applications/applications.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ def __init__(self, conf: Configuration) -> None:

self.nens = base.get('NMEM_ENS', 0)
self.fcst_segments = base.get('FCST_SEGMENTS', None)
self.interval_gfs = to_timedelta(f"{base.get('INTERVAL_GFS')}H")

if not AppConfig.is_monotonic(self.fcst_segments):
raise ValueError(f'Forecast segments do not increase monotonically: {",".join(self.fcst_segments)}')
Expand Down Expand Up @@ -109,9 +110,6 @@ def _init_finalize(self, conf: Configuration):
# Save base in the internal state since it is often needed
base = self.configs['_no_run']['base']

# Get more configuration options into the class attributes
self.gfs_cyc = base.get('gfs_cyc')

# Get task names for the application
self.task_names = self.get_task_names()

Expand Down Expand Up @@ -199,19 +197,6 @@ def get_task_names(self, run="_no_run") -> Dict[str, List[str]]:
'''
pass

@staticmethod
def get_gfs_interval(gfs_cyc: int) -> timedelta:
"""
return interval in hours based on gfs_cyc
"""

gfs_internal_map = {'1': '24H', '2': '12H', '4': '6H'}

try:
return to_timedelta(gfs_internal_map[str(gfs_cyc)])
except KeyError:
raise KeyError(f'Invalid gfs_cyc = {gfs_cyc}')

@staticmethod
def is_monotonic(test_list: List, check_decreasing: bool = False) -> bool:
"""
Expand Down
1 change: 0 additions & 1 deletion workflow/applications/gefs.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ def _get_app_configs(self):
def _update_base(base_in):

base_out = base_in.copy()
base_out['INTERVAL_GFS'] = AppConfig.get_gfs_interval(base_in['gfs_cyc'])
base_out['RUN'] = 'gefs'

return base_out
Expand Down
50 changes: 2 additions & 48 deletions workflow/applications/gfs_cycled.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ def _get_app_configs(self):
@staticmethod
def _update_base(base_in):

return GFSCycledAppConfig.get_gfs_cyc_dates(base_in)
return base_in

def get_task_names(self):
"""
Expand Down Expand Up @@ -297,7 +297,7 @@ def get_task_names(self):
tasks['enkfgdas'] = enkfgdas_tasks

# Add RUN=gfs tasks if running early cycle
if self.gfs_cyc > 0:
if self.interval_gfs > to_timedelta("0H"):
tasks['gfs'] = gfs_tasks

if self.do_hybvar and 'gfs' in self.eupd_runs:
Expand All @@ -307,49 +307,3 @@ def get_task_names(self):
tasks['enkfgfs'] = enkfgfs_tasks

return tasks

@staticmethod
def get_gfs_cyc_dates(base: Dict[str, Any]) -> Dict[str, Any]:
"""
Generate GFS dates from experiment dates and gfs_cyc choice
"""

base_out = base.copy()

sdate = base['SDATE']
edate = base['EDATE']
base_out['INTERVAL'] = to_timedelta(f"{base['assim_freq']}H")

# Set GFS cycling dates
gfs_cyc = base['gfs_cyc']
if gfs_cyc != 0:
interval_gfs = AppConfig.get_gfs_interval(gfs_cyc)
hrinc = 0
hrdet = 0
if gfs_cyc == 1:
hrinc = 24 - sdate.hour
hrdet = edate.hour
elif gfs_cyc == 2:
if sdate.hour in [0, 12]:
hrinc = 12
elif sdate.hour in [6, 18]:
hrinc = 6
if edate.hour in [6, 18]:
hrdet = 6
elif gfs_cyc == 4:
hrinc = 6
sdate_gfs = sdate + timedelta(hours=hrinc)
edate_gfs = edate - timedelta(hours=hrdet)
if sdate_gfs > edate:
print('W A R N I N G!')
print('Starting date for GFS cycles is after Ending date of experiment')
print(f'SDATE = {sdate.strftime("%Y%m%d%H")}, EDATE = {edate.strftime("%Y%m%d%H")}')
print(f'SDATE_GFS = {sdate_gfs.strftime("%Y%m%d%H")}, EDATE_GFS = {edate_gfs.strftime("%Y%m%d%H")}')
gfs_cyc = 0

base_out['gfs_cyc'] = gfs_cyc
base_out['SDATE_GFS'] = sdate_gfs
base_out['EDATE_GFS'] = edate_gfs
base_out['INTERVAL_GFS'] = interval_gfs

return base_out
8 changes: 1 addition & 7 deletions workflow/applications/gfs_forecast_only.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,6 @@ def _get_app_configs(self):
if self.do_genesis_fsu:
configs += ['genesis_fsu']

if self.do_metp:
configs += ['metp']

if self.do_bufrsnd:
configs += ['postsnd']

Expand Down Expand Up @@ -78,7 +75,7 @@ def _get_app_configs(self):
def _update_base(base_in):

base_out = base_in.copy()
base_out['INTERVAL_GFS'] = AppConfig.get_gfs_interval(base_in['gfs_cyc'])
base_out['RUN'] = 'gfs'

return base_out

Expand Down Expand Up @@ -122,9 +119,6 @@ def get_task_names(self):
if self.do_genesis_fsu:
tasks += ['genesis_fsu']

if self.do_metp:
tasks += ['metp']

if self.do_bufrsnd:
tasks += ['postsnd']

Expand Down
12 changes: 6 additions & 6 deletions workflow/rocoto/gefs_xml.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,19 @@ def __init__(self, app_config: AppConfig, rocoto_config: Dict) -> None:
super().__init__(app_config, rocoto_config)

def get_cycledefs(self):
sdate = self._base['SDATE']
sdate = self._base['SDATE_GFS']
edate = self._base['EDATE']
interval = self._base.get('INTERVAL_GFS', to_timedelta('24H'))
interval = self.interval_gfs
sdate_str = sdate.strftime("%Y%m%d%H%M")
edate_str = edate.strftime("%Y%m%d%H%M")
interval_str = timedelta_to_HMS(interval)
strings = []
strings.append(f'\t<cycledef group="gefs">{sdate_str} {edate_str} {interval_str}</cycledef>')

sdate = sdate + interval
if sdate <= edate:
sdate_str = sdate.strftime("%Y%m%d%H%M")
strings.append(f'\t<cycledef group="gefs_seq">{sdate_str} {edate_str} {interval_str}</cycledef>')
date2 = sdate + interval
if date2 <= edate:
date2_str = date2.strftime("%Y%m%d%H%M")
strings.append(f'\t<cycledef group="gefs_seq">{date2_str} {edate_str} {interval_str}</cycledef>')

strings.append('')
strings.append('')
Expand Down
25 changes: 18 additions & 7 deletions workflow/rocoto/gfs_cycled_xml.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,30 @@ def get_cycledefs(self):
sdate_str = sdate.strftime("%Y%m%d%H%M")
strings.append(f'\t<cycledef group="gdas">{sdate_str} {edate_str} {interval_str}</cycledef>')

if self._app_config.gfs_cyc != 0:
interval_gfs = self._app_config.interval_gfs

if interval_gfs > to_timedelta("0H"):
sdate_gfs = self._base['SDATE_GFS']
edate_gfs = self._base['EDATE_GFS']
interval_gfs = self._base['INTERVAL_GFS']
edate_gfs = edate
sdate_gfs_str = sdate_gfs.strftime("%Y%m%d%H%M")
edate_gfs_str = edate_gfs.strftime("%Y%m%d%H%M")
interval_gfs_str = timedelta_to_HMS(interval_gfs)
strings.append(f'\t<cycledef group="gfs">{sdate_gfs_str} {edate_gfs_str} {interval_gfs_str}</cycledef>')

sdate_gfs = sdate_gfs + interval_gfs
sdate_gfs_str = sdate_gfs.strftime("%Y%m%d%H%M")
if sdate_gfs <= edate_gfs:
strings.append(f'\t<cycledef group="gfs_seq">{sdate_gfs_str} {edate_gfs_str} {interval_gfs_str}</cycledef>')
date2_gfs = sdate_gfs + interval_gfs
date2_gfs_str = date2_gfs.strftime("%Y%m%d%H%M")
if date2_gfs <= edate_gfs:
strings.append(f'\t<cycledef group="gfs_seq">{date2_gfs_str} {edate_gfs_str} {interval_gfs_str}</cycledef>')

if self._base['DO_METP']:
# Run verification at 18z, no matter what
sdate_metp = sdate_gfs.replace(hour=18)
edate_metp = edate_gfs.replace(hour=18)
interval_metp = to_timedelta('24H')
sdate_metp_str = sdate_metp.strftime("%Y%m%d%H%M")
edate_metp_str = edate_metp.strftime("%Y%m%d%H%M")
interval_metp_str = timedelta_to_HMS(interval_metp)
strings.append(f'\t<cycledef group="metp">{sdate_metp_str} {edate_metp_str} {interval_metp_str}</cycledef>')

strings.append('')
strings.append('')
Expand Down
11 changes: 6 additions & 5 deletions workflow/rocoto/gfs_forecast_only_xml.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,16 @@ def __init__(self, app_config: AppConfig, rocoto_config: Dict) -> None:
super().__init__(app_config, rocoto_config)

def get_cycledefs(self):
sdate = self._base['SDATE']
sdate = self._base['SDATE_GFS']
edate = self._base['EDATE']
interval = self._base.get('INTERVAL_GFS', to_timedelta('24H'))
interval = self._app_config.interval_gfs
strings = []
strings.append(f'\t<cycledef group="gfs">{sdate.strftime("%Y%m%d%H%M")} {edate.strftime("%Y%m%d%H%M")} {timedelta_to_HMS(interval)}</cycledef>')

sdate = sdate + interval
if sdate <= edate:
strings.append(f'\t<cycledef group="gfs_seq">{sdate.strftime("%Y%m%d%H%M")} {edate.strftime("%Y%m%d%H%M")} {timedelta_to_HMS(interval)}</cycledef>')
date2 = sdate + interval
date2_str = date2.strftime("%Y%m%d%H%M")
if date2 <= edate:
strings.append(f'\t<cycledef group="gfs_seq">{date2.strftime("%Y%m%d%H%M")} {edate.strftime("%Y%m%d%H%M")} {timedelta_to_HMS(interval)}</cycledef>')

strings.append('')
strings.append('')
Expand Down
Loading

0 comments on commit b50b930

Please sign in to comment.