From 6816722e8ff81d28da6d506256eb820b2a346211 Mon Sep 17 00:00:00 2001 From: "Walter.Kolczynski" Date: Mon, 29 Jul 2024 18:11:00 -0500 Subject: [PATCH] Change fcst segments to breakpoints, move to config.fcst Changes the way forecast segments are defined. Restores the original `FHMIN_GFS` and `FHMAX_GFS` and then adds a local `breakpnts` variable that contains the intermediate stopping points (if any). The original list of segment endpoints is then constructed from that. The determination of the `FHMIN` and `FHMAX` based on the segment is moved from `config.base` to `config.fcst`. This required adding some additional checks in `config.fcst` to clip other `FHMAX` variables to `FHMAX`. --- parm/config/gefs/config.base | 19 ++++++------------ parm/config/gefs/config.fcst | 14 +++++++++----- parm/config/gefs/yaml/defaults.yaml | 3 ++- parm/config/gfs/config.base | 18 +++++------------ parm/config/gfs/config.fcst | 12 ++++++++---- parm/config/gfs/yaml/defaults.yaml | 3 ++- workflow/applications/applications.py | 28 ++++++++++++++++++++++++++- 7 files changed, 59 insertions(+), 38 deletions(-) diff --git a/parm/config/gefs/config.base b/parm/config/gefs/config.base index 70688597d3c..b22291d295e 100644 --- a/parm/config/gefs/config.base +++ b/parm/config/gefs/config.base @@ -233,19 +233,12 @@ export FHOUT_ICE=3 export gfs_cyc=@gfs_cyc@ # 0: no GFS cycle, 1: 00Z only, 2: 00Z and 12Z only, 4: all 4 cycles. # GFS output and frequency -# Forecast hour intervals to run the forecast over -# For a single-segment forecast, this is simply "$FHMIN_GFS,$FHMAX_GFS" -export FCST_SEGMENTS_STR_GFS="@FCST_SEGMENTS_GFS@" -IFS=', ' read -ra FCST_SEGMENTS_GFS <<< "${FCST_SEGMENTS_STR_GFS}" -if (( ${FCST_SEGMENT:- -1} < 0 )); then - # Jobs other than the forecast don't care about segments, only the - # absolute start and end - declare -x FHMIN_GFS=${FCST_SEGMENTS_GFS[0]} - declare -x FHMAX_GFS=${FCST_SEGMENTS_GFS[-1]} -else - declare -x FHMIN_GFS=${FCST_SEGMENTS_GFS[${FCST_SEGMENT}]} - declare -x FHMAX_GFS=${FCST_SEGMENTS_GFS[${FCST_SEGMENT}+1]} -fi +export FHMIN_GFS=0 +export FHMAX_GFS="@FHMAX_GFS@" +# Intermediate times to stop forecast when running in segments +breakpnts="@FCST_BREAKPOINTS@" +export FCST_SEGMENTS="${FHMIN_GFS},${breakpnts:+${breakpnts},}${FHMAX_GFS}" + export FHOUT_GFS=6 export FHMAX_HF_GFS=@FHMAX_HF_GFS@ export FHOUT_HF_GFS=1 diff --git a/parm/config/gefs/config.fcst b/parm/config/gefs/config.fcst index f630d14388e..673a2af9b53 100644 --- a/parm/config/gefs/config.fcst +++ b/parm/config/gefs/config.fcst @@ -30,15 +30,19 @@ string="--fv3 ${CASE}" # shellcheck disable=SC2086 source "${EXPDIR}/config.ufs" ${string} -export FHMIN=${FHMIN_GFS} -# shellcheck disable=SC2153 -export FHMAX=${FHMAX_GFS} +# Convert comma-separated string into bash array +IFS=', ' read -ra segments <<< "${FCST_SEGMENTS}" +# Determine MIN and MAX based on the forecast segment +export FHMIN=${segments[${FCST_SEGMENT}]} +export FHMAX=${segments[${FCST_SEGMENT}+1]} +# Cap other FHMAX variables at FHMAX for the segment +export FHMAX_HF=$(( FHMAX_HF_GFS > FHMAX ? FHMAX : FHMAX_HF_GFS )) +export FHMAX_WAV=$(( FHMAX_WAV > FHMAX ? FHMAX : FHMAX_WAV )) # shellcheck disable=SC2153 export FHOUT=${FHOUT_GFS} -export FHMAX_HF=${FHMAX_HF_GFS} export FHOUT_HF=${FHOUT_HF_GFS} export FHOUT_OCN=${FHOUT_OCN_GFS} -export FHOUT_ICE=${FHOUT_ICE_GFS} +export FHOUT_ICE=${FHOUT_ICE_GFS} # Get task specific resources source "${EXPDIR}/config.resources" fcst diff --git a/parm/config/gefs/yaml/defaults.yaml b/parm/config/gefs/yaml/defaults.yaml index 17a78ea53de..e4666d1aba3 100644 --- a/parm/config/gefs/yaml/defaults.yaml +++ b/parm/config/gefs/yaml/defaults.yaml @@ -9,7 +9,8 @@ base: DO_AWIPS: "NO" KEEPDATA: "NO" DO_EXTRACTVARS: "NO" - FCST_SEGMENTS_GFS: "0,48,120" + FHMAX_GFS: 120 FHMAX_HF_GFS: 0 + FCST_BREAKPOINTS: "48" REPLAY_ICS: "NO" USE_OCN_PERTURB_FILES: "false" diff --git a/parm/config/gfs/config.base b/parm/config/gfs/config.base index 0b13a405a4c..77d4024048e 100644 --- a/parm/config/gfs/config.base +++ b/parm/config/gfs/config.base @@ -289,19 +289,11 @@ export EUPD_CYC="@EUPD_CYC@" export gfs_cyc=@gfs_cyc@ # 0: no GFS cycle, 1: 00Z only, 2: 00Z and 12Z only, 4: all 4 cycles. # GFS output and frequency -# Comma-separated forecast hour intervals to run the forecast over -# For a single-segment forecast, this is simply "$FHMIN_GFS,$FHMAX_GFS" -export FCST_SEGMENTS_STR_GFS="@FCST_SEGMENTS_GFS@" -IFS=', ' read -ra FCST_SEGMENTS_GFS <<< "${FCST_SEGMENTS_STR_GFS}" -if (( ${FCST_SEGMENT:- -1} < 0 )); then - # Jobs other than the forecast don't care about segments, only the - # absolute start and end - declare -x FHMIN_GFS=${FCST_SEGMENTS_GFS[0]} - declare -x FHMAX_GFS=${FCST_SEGMENTS_GFS[-1]} -else - declare -x FHMIN_GFS=${FCST_SEGMENTS_GFS[${FCST_SEGMENT}]} - declare -x FHMAX_GFS=${FCST_SEGMENTS_GFS[${FCST_SEGMENT}+1]} -fi +export FHMIN_GFS=0 +export FHMAX_GFS="@FHMAX_GFS@" +# Intermediate times to stop forecast when running in segments +breakpnts="@FCST_BREAKPOINTS@" +export FCST_SEGMENTS="${FHMIN_GFS},${breakpnts:+${breakpnts},}${FHMAX_GFS}" export FHOUT_GFS=3 # 3 for ops export FHMAX_HF_GFS=@FHMAX_HF_GFS@ export FHOUT_HF_GFS=1 diff --git a/parm/config/gfs/config.fcst b/parm/config/gfs/config.fcst index 7008347645b..333730982a4 100644 --- a/parm/config/gfs/config.fcst +++ b/parm/config/gfs/config.fcst @@ -33,12 +33,16 @@ source "${EXPDIR}/config.ufs" ${string} # Forecast length for GFS forecast case ${RUN} in *gfs) - export FHMIN=${FHMIN_GFS} - # shellcheck disable=SC2153 - export FHMAX=${FHMAX_GFS} + # Convert comma-separated string into bash array + IFS=', ' read -ra segments <<< "${FCST_SEGMENTS}" + # Determine MIN and MAX based on the forecast segment + export FHMIN=${segments[${FCST_SEGMENT}]} + export FHMAX=${segments[${FCST_SEGMENT}+1]} + # Cap other FHMAX variables at FHMAX for the segment + export FHMAX_HF=$(( FHMAX_HF_GFS > FHMAX ? FHMAX : FHMAX_HF_GFS )) + export FHMAX_WAV=$(( FHMAX_WAV > FHMAX ? FHMAX : FHMAX_WAV )) # shellcheck disable=SC2153 export FHOUT=${FHOUT_GFS} - export FHMAX_HF=${FHMAX_HF_GFS} export FHOUT_HF=${FHOUT_HF_GFS} export FHOUT_OCN=${FHOUT_OCN_GFS} export FHOUT_ICE=${FHOUT_ICE_GFS} diff --git a/parm/config/gfs/yaml/defaults.yaml b/parm/config/gfs/yaml/defaults.yaml index 3a4cb0f457f..24729ac43ec 100644 --- a/parm/config/gfs/yaml/defaults.yaml +++ b/parm/config/gfs/yaml/defaults.yaml @@ -14,8 +14,9 @@ base: DO_GENESIS: "YES" DO_GENESIS_FSU: "NO" DO_METP: "YES" - FCST_SEGMENTS_GFS: "0,120" + FHMAX_GFS: 120 FHMAX_HF_GFS: 0 + FCST_BREAKPOINTS: "" DO_VRFY_OCEANDA: "NO" GSI_SOILANAL: "NO" EUPD_CYC: "gdas" diff --git a/workflow/applications/applications.py b/workflow/applications/applications.py index 0d33ea2f74d..128b184861e 100644 --- a/workflow/applications/applications.py +++ b/workflow/applications/applications.py @@ -70,7 +70,10 @@ def __init__(self, conf: Configuration) -> None: self.do_hpssarch = _base.get('HPSSARCH', False) self.nens = _base.get('NMEM_ENS', 0) - self.fcst_segments = _base.get('FCST_SEGMENTS_STR_GFS', None) + self.fcst_segments = _base.get('FCST_SEGMENTS', None) + + if not AppConfig.is_monotonic(self.fcst_segments): + raise ValueError(f'Forecast segments do not increase monotonically: {",".join(self.fcst_segments)}') self.wave_cdumps = None if self.do_wave: @@ -204,3 +207,26 @@ def get_gfs_interval(gfs_cyc: int) -> timedelta: 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: + """ + Determine if an array is monotonically increasing or decreasing + + TODO: Move this into wxflow somewhere + + Inputs + test_list: List + A list of comparable values to check + check_decreasing: bool [default: False] + Check whether list is monotonically decreasing + + Returns + bool: Whether the list is monotonically increasing (if check_decreasing + if False) or decreasing (if check_decreasing is True) + + """ + if check_decreasing: + return all(x > y for x, y in zip(test_list, test_list[1:])) + else: + return all(x < y for x, y in zip(test_list, test_list[1:]))