From 21aa9f5ce3ff17e592d19636a6d5c7b7adaee936 Mon Sep 17 00:00:00 2001 From: HenryWinterbottom-NOAA Date: Wed, 29 Mar 2023 18:49:46 -0600 Subject: [PATCH 01/31] Bringing branch UTD with WIP. --- ush/python/pygfs/exceptions.py | 84 ++++++++++++++ ush/python/pygfs/task/forecast.py | 42 +++++++ ush/python/pygfs/task/gfs.py | 33 ++++++ ush/python/pygfs/ufswm.py | 187 ++++++++++++++++++++++++++++++ 4 files changed, 346 insertions(+) create mode 100644 ush/python/pygfs/exceptions.py create mode 100644 ush/python/pygfs/task/forecast.py create mode 100644 ush/python/pygfs/task/gfs.py create mode 100644 ush/python/pygfs/ufswm.py diff --git a/ush/python/pygfs/exceptions.py b/ush/python/pygfs/exceptions.py new file mode 100644 index 00000000000..180356d3d7b --- /dev/null +++ b/ush/python/pygfs/exceptions.py @@ -0,0 +1,84 @@ +""" +Module +------ + + exceptions.py + +Description +----------- + + This module contains all pyufs package exceptions. + +Classes +------- + + ForecastError(msg) + + This is the base-class for exceptions encountered within the + ush/python/pygfs/task/forecast module; it is a sub-class of + WorkflowException. + + GFSError(msg) + + This is the base-class for exceptions encountered within the + ush/python/pygfs/task/gfs module; it is a sub-class of + WorkflowException. + + UFSWMError(msg) + + This is the base-class for exceptions encountered within the + ush/python/pygfs/ufswm module; it is a sub-class of + WorkflowException. + +""" + +# ---- + +from pygw.exceptions import WorkflowException + +# ---- + +# Define all available classes. +__all__ = ["ForecastError", "GFSError", "UFSWMError"] + +# ---- + + +class ForecastError(WorkflowException): + """ + Description + ----------- + + This is the base-class for exceptions encountered within the + ush/python/pygfs/task/forecast module; it is a sub-class of + WorkflowException. + + """ + +# ---- + + +class GFSError(WorkflowException): + """ + Description + ----------- + + This is the base-class for exceptions encountered within the + ush/python/pygfs/task/gfs module; it is a sub-class of + WorkflowException. + + """ + +# ---- + + +class UFSWMError(WorkflowException): + """ + Description + ----------- + + This is the base-class for exceptions encountered within the + ush/python/pygfs/ufswm module; it is a sub-class of + WorkflowException. + + """ diff --git a/ush/python/pygfs/task/forecast.py b/ush/python/pygfs/task/forecast.py new file mode 100644 index 00000000000..15cb57da65e --- /dev/null +++ b/ush/python/pygfs/task/forecast.py @@ -0,0 +1,42 @@ +from typing import Dict + +from pygw.task import Task +from pygw.logger import Logger + +from pygfs.ufswm import UFSWM +from pygfs.exceptions import ForecastError + +# ---- + + +class Forecast(Task): + """ + Description + ----------- + + This is the base-class object for the respective Unified Forecast + System (UFS) forecast task; it is a sub-class of Task. + + """ + + def __init__(self: Task, config: Dict, model: str, *args, **kwargs): + """ + Description + ----------- + + Creates a new Forecast object. + + """ + + # Define the base-class attributes. + super().__init__(config, *args, *kwargs) + self.logger = Logger(level=self.config.loglev, colored_log=True) + self.config.model = model + + # Update the configuration accordingly. + if self.config.model.lower() == "gfs": + self.config.ntiles = 6 + if self.config.model.lower() != "gfs": + raise ForecastError(msg=msg) + + self.ufswm = UFSWM(config=self.config) diff --git a/ush/python/pygfs/task/gfs.py b/ush/python/pygfs/task/gfs.py new file mode 100644 index 00000000000..5662fc28c2f --- /dev/null +++ b/ush/python/pygfs/task/gfs.py @@ -0,0 +1,33 @@ + +from pygfs.task.forecast import Forecast +from pygfs.exceptions import GFSError +from typing import Dict + +# ---- + + +class GFS(Forecast): + """ + + """ + + def __init__(self: Forecast, config: Dict): + """ + Description + ----------- + + Creates a new GFS object. + + """ + + # Define the base-class attributes. + super().__init__(config=config, model="GFS") + + def initialize(self: Forecast): + """ + + """ + + super().initialize() + + # NEED TO READ YAMLS SOMEHOW. diff --git a/ush/python/pygfs/ufswm.py b/ush/python/pygfs/ufswm.py new file mode 100644 index 00000000000..5b5ef25b3ae --- /dev/null +++ b/ush/python/pygfs/ufswm.py @@ -0,0 +1,187 @@ +""" +Module +------ + + pygfs.ufswm (pygfs/ufswm.py) + +Description +----------- + + This module contains the base-class module for all Unified + Forecast System (UFS) Weather Model (WM) applications. + +Classes +------- + + UFSWM(config) + + This is the base-class object for all Unified Forecast System + (UFS) Weather Model (WM) applications. + +""" + +# ---- + +__author__ = "Henry R. Winterbottom" +__maintainer__ = "Henry R. Winterbottom" +__email__ = "henry.winterbottom@noaa.gov" +__version__ = 0.0 + +# ---- + +from dataclasses import dataclass +from typing import Any, Dict + +from pygw.attrdict import AttrDict +from pygw.logger import Logger + +from pygfs.exceptions import UFSWMError + +# ---- + +# Define the supported options for the respective forecast model +# attributes; only GFS configurations are currently supported. +ATM_RES_LIST = ["c48", "c96", "c192", "c384"] + +ATM_LEVS_LIST = [64, 128] + +ATM_NTILES_LIST = [6] + +# ---- + + +@dataclass +class UFSWM: + """ + Description + ----------- + + This is the base-class object for all Unified Forecast System + (UFS) Weather Model (WM) applications. + + Parameters + ---------- + + config: Dict + + A Python dictionary containing the application configuration + attributes. + + """ + + def __init__(self, config: Dict): + """ + Description + ----------- + + Creates a new UFSWM object. + + """ + + # Define the base-class attributes. + self.config = config + self.logger = Logger(level=self.config.loglev, colored_log=True) + + # Configure the respective forecast models. + self.atmos_grid = self.__atmos_grid_setup( + atm_res=self.config.CASE, + atm_levs=self.config.LEVS, + atm_ntiles=self.config.ntiles, + logger=self.logger, + ) + + @staticmethod + def __atmos_grid_setup( + atm_res: str, atm_levs: int, atm_ntiles: int, logger: object + ) -> Dict[str, Any]: + """ + Description + ----------- + + This method defines the atmosphere model (e.g., FV3) grid + configuration attributes. + + Parameters + ---------- + + atm_res: str + + A Python string define the atmosphere model resolution; + this should be of the form `C##` where `##` is the + cubed-sphere resolution (e.g., 48, 96, 192, etc.,). + + atm_levs: int + + A Python integer defining the total number of levels for + the atmosphere model configuration. + + atm_ntiles: int + + A Python integer defining the total number of tiles for + the cubed-sphere (i.e., FV3 forecast model) application. + + logger: object + + A Python object containing the defined logger object. + + Returns + ------- + + atm_config: Dict + + A Python dictionary containing the atmosphere model + configuration established via the parameter attributes. + + """ + + # Check that the parameter attribute values are valid; proceed + # accordingly. + atm_config = AttrDict() + if atm_res.lower() not in ATM_RES_LIST: + msg = ( + f"The cubed sphere resolution {atm_res.upper()} is not " + "supported; valid values are: " + f"{', '.join([res.upper() for res in ATM_RES_LIST])}. " + "Aborting!!!" + ) + raise UFSWMError(msg=msg) + + if atm_levs not in ATM_LEVS_LIST: + msg = ( + f"The number of vertical levels {atm_levs} is not supported; " + f"valid values are {','.join(ATM_LEVS_LIST)}. " + "Aborting!!!" + ) + raise UFSWMError(msg=msg) + + if atm_ntiles not in ATM_NTILES_LIST: + msg = ( + f"The specified number of cubed-sphere tiles {atm_ntiles} is not " + f"supported; value values are {','.join(ATM_NTILES_LIST)}. " + "Aborting!!!" + ) + raise UFSWMError(msg=msg) + + msg = ( + "\nThe atmosphere model configuration is as follows:\n\n" + f"Cubed sphere resolution: {atm_res.upper()}\n" + f"Vertical levels: {atm_levs}\n" + f"Number of cubed sphere tiles: {atm_ntiles}.\n" + ) + logger.warn(msg=msg) + + # Define all atmosphere model configuration attributes with + # respect to the parameter attributes. + atm_config = AttrDict() + + atm_config.case_res = atm_res + atm_config.csg_res = int(atm_config.case_res[1:]) + + atm_config.jcap = int((atm_config.csg_res * 2) - 2) + atm_config.lonb = int(4 * atm_config.csg_res) + atm_config.lonb = int(2 * atm_config.csg_res) + atm_config.npx = int(atm_config.csg_res + 1) + atm_config.npy = int(atm_config.csg_res + 1) + atm_config.npz = int(atm_levs - 1) + + return atm_config From fa1c5ad55969cd2229e978c18491faf41810a10f Mon Sep 17 00:00:00 2001 From: HenryWinterbottom-NOAA Date: Wed, 29 Mar 2023 18:50:15 -0600 Subject: [PATCH 02/31] Task level script. --- scripts/exglobal_gfs_forecast_initialize.py | 108 ++++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 scripts/exglobal_gfs_forecast_initialize.py diff --git a/scripts/exglobal_gfs_forecast_initialize.py b/scripts/exglobal_gfs_forecast_initialize.py new file mode 100644 index 00000000000..9d367c0cc29 --- /dev/null +++ b/scripts/exglobal_gfs_forecast_initialize.py @@ -0,0 +1,108 @@ +#! /usr/env/bin python + +# ----------------------------------------------------------------------------- +# +# Program Name: exglobal_ufs_forecast_init.py +# +# Author(s)/Contacts(s): Henry R. Winterbottom (henry.winterbottom@noaa.gov) +# +# Abstract: A Python 3.5+ script to initialize a global UFS +# forecast application. +# +# History Log: +# +# - 2023-03-28: Henry R. Winterbottom -- Original version. +# +# Usage: user@host:$ python exglobal_ufs_forecast_init.py +# +# ----------------------------------------------------------------------------- + +""" +Script +------ + + exglobal_ufs_forecast_init.py + +Description +----------- + + This script contains a task level interface for the global UFS + forecast initialization application. + +Functions +--------- + + main() + + This is the driver-level function to invoke the tasks within + this script. + +Author(s) +--------- + + + Henry R. Winterbottom; 28 March 2023 + +History +------- + + 2023-03-28: Henry Winterbottom -- Initial implementation. + +""" + +# ---- + +import os +import time + +from pygw.configuration import cast_strdict_as_dtypedict +from pygw.logger import Logger +from pygfs.task.gfs import GFS + +# ---- + +logger = Logger() + +# ---- + +__author__ = "Henry R. Winterbottom" +__maintainer__ = "Henry R. Winterbottom" +__email__ = "henry.winterbottom@noaa.gov" + +# ---- + + +def main() -> None: + """ + Description + ----------- + + This is the driver-level function to invoke the tasks within this + script. + + """ + + # Take configuration from environment and cast it as Python + # dictionary. + script_name = os.path.basename(__file__) + msg = f"Completed application {script_name}." + logger.info(msg=msg) + start_time = time.time() + config = cast_strdict_as_dtypedict(os.environ) + + # Launch the task. + task = GFS(config=config) + task.initialize() + + stop_time = time.time() + msg = f"Completed application {script_name}." + logger.info(msg=msg) + total_time = stop_time - start_time + msg = f"Total Elapsed Time: {total_time} seconds." + logger.info(msg=msg) + +# ---- + + +if __name__ == "__main__": + main() From 1e2429a5d59f7b0c62a0f7a42409f8f90ae1ddc9 Mon Sep 17 00:00:00 2001 From: HenryWinterbottom-NOAA Date: Thu, 30 Mar 2023 10:48:22 -0600 Subject: [PATCH 03/31] Bringing branch UTD with WIP. --- ush/python/pygfs/task/forecast.b.py | 42 ++++++++ ush/python/pygfs/task/forecast.py | 26 ++++- ush/python/pygfs/task/gfs.py | 155 +++++++++++++++++++++++++++- 3 files changed, 220 insertions(+), 3 deletions(-) create mode 100644 ush/python/pygfs/task/forecast.b.py diff --git a/ush/python/pygfs/task/forecast.b.py b/ush/python/pygfs/task/forecast.b.py new file mode 100644 index 00000000000..15cb57da65e --- /dev/null +++ b/ush/python/pygfs/task/forecast.b.py @@ -0,0 +1,42 @@ +from typing import Dict + +from pygw.task import Task +from pygw.logger import Logger + +from pygfs.ufswm import UFSWM +from pygfs.exceptions import ForecastError + +# ---- + + +class Forecast(Task): + """ + Description + ----------- + + This is the base-class object for the respective Unified Forecast + System (UFS) forecast task; it is a sub-class of Task. + + """ + + def __init__(self: Task, config: Dict, model: str, *args, **kwargs): + """ + Description + ----------- + + Creates a new Forecast object. + + """ + + # Define the base-class attributes. + super().__init__(config, *args, *kwargs) + self.logger = Logger(level=self.config.loglev, colored_log=True) + self.config.model = model + + # Update the configuration accordingly. + if self.config.model.lower() == "gfs": + self.config.ntiles = 6 + if self.config.model.lower() != "gfs": + raise ForecastError(msg=msg) + + self.ufswm = UFSWM(config=self.config) diff --git a/ush/python/pygfs/task/forecast.py b/ush/python/pygfs/task/forecast.py index 15cb57da65e..d00ba30d330 100644 --- a/ush/python/pygfs/task/forecast.py +++ b/ush/python/pygfs/task/forecast.py @@ -1,4 +1,4 @@ -from typing import Dict +from typing import Dict, List from pygw.task import Task from pygw.logger import Logger @@ -30,13 +30,35 @@ def __init__(self: Task, config: Dict, model: str, *args, **kwargs): # Define the base-class attributes. super().__init__(config, *args, *kwargs) - self.logger = Logger(level=self.config.loglev, colored_log=True) self.config.model = model + # Define the appplication logger object. + if getattr(self.config, "loglev") is None: + self.config.loglev = "info" + self.logger = Logger(level=self.config.loglev, colored_log=True) + # Update the configuration accordingly. if self.config.model.lower() == "gfs": self.config.ntiles = 6 + if self.config.model.lower() != "gfs": raise ForecastError(msg=msg) self.ufswm = UFSWM(config=self.config) + + def get_fixedfiles_info(self: Task, app: str) -> List: + """ """ + + fixed_files_list = [ + item for item in self.config.keys() if f"FIX{app}" in item] + + if len(fixed_files_list) == 0: + fixed_files_info = None + + return fixed_files_list + + def sync_fixedfiles(self: Task, fixedfiles_dict: Dict) -> None: + """ + + + """ diff --git a/ush/python/pygfs/task/gfs.py b/ush/python/pygfs/task/gfs.py index 5662fc28c2f..f1f769c9464 100644 --- a/ush/python/pygfs/task/gfs.py +++ b/ush/python/pygfs/task/gfs.py @@ -1,7 +1,27 @@ +import os + from pygfs.task.forecast import Forecast from pygfs.exceptions import GFSError -from typing import Dict +from typing import Dict, List +from pygw.logger import logit + +from pygw.attrdict import AttrDict + +# ---- + +# The following attributes are mandatory for fixed-file syncing; this +# should not be changed unless absolutely necessary. +FIXED_MAND_ATTR_DICT = {"DATA": "DATA", + "FIXgfs": "FIXgfs", + "HOMEgfs": "HOMEgfs", + "atm_res": "CASE", + "ocn_res": "OCNRES" + } + +# ---- + +logger = Logger().build_logger() # ---- @@ -22,12 +42,145 @@ def __init__(self: Forecast, config: Dict): # Define the base-class attributes. super().__init__(config=config, model="GFS") + self.gfs_app = self.config.APP + self.fixed_path = self.config.FIXgfs + + @staticmethod + @logit(logger) + def __fixedfiles(config: Dict, logger: object, + fixed_list: List = None) -> Dict: + """ + Description + ----------- + + This method collects the fixed-file attributes from the + run-time environment (e.g., configuration Python dictionary + `config`), checks the validity of the respective files and/or + attributes, and returns a Python dictionary containing the + fixed-file attributes. + + Parameters + ---------- + + config: Dict + + A Python dictionary containing the configuration + attributes collected from the run-time environment. + + logger: object + + A Python object containing the defined logger object. + + Keywords + -------- + + fixed_list: List, optional + + A Python list of additional fixed-file paths to be synced + in addition to the default values specified within this + method. + + Returns + ------- + + fixedfiles_dict: Dict + + A Python dictionary containing the fixed-file path + attributes. + + Raises + ------ + + GFSError: + + - raised a mandatory configuration attribute (see + `FIXED_MAND_ATTR_DICT`) is not specified within the + run-time environment/configuration. + + - raised if a mandatory directory tree (beneath `FIXgfs`) + is either not a directory or does not exist. + + - raised if an application specific directory tree + (beneath `FIXgfs`) is either not a dictionary or does + not exist. + + """ + + # Define the fixed-file attributes. + fixedfiles_dict = AttrDict() + + for (fixed_attr_key, fixed_attr_value) in FIXED_MAND_ATTR_DICT.items(): + + # Define the respective configuration attribute (i.e., + # `fixed_attr_key`) and assign the corresponding value + # `fixed_attr_value`; proceed accordingly. + value = getattr(config, fixed_attr_key) + if value is None: + msg = (f"The configuration attribute {fixed_attr_key} could not " + "be determined from the run-time environment. Aborting!!!" + ) + raise GFSError(msg=msg) + + setattr(fixedfiles_dict, fixed_attr_key, value) + + # Define the top-level directory-tree path containing the + # respective fixed-files. + fix_dirpath = fixedfiles_dict.FIXgfs + if fix_dirpath is None: + msg = ("The attribute FIXgfs could not be determined from the " + "run-time environment. Aborting!!!" + ) + raise GFSError(msg=msg) + + if (not os.path.isdir(fix_dirpath)) or (not os.path.exists(fix_dirpath)): + msg = (f"The directory tree {fix_dirpath} is either not a directory " + "or does not exist. Aborting!!!" + ) + raise GFSError(msg=msg) + + # Define the fixed-file directory tree attributes. + fixed_attr_dict = {"FIX_aer": "aer", + "FIX_am": "am", + "FIX_lut": "lut", + "FIX_orog": "orog", + "FIX_lut": "lut" + } + + # Check whether the run-time environment contains additional + # fixed-files to be synced; proceed accordingly. + if fixed_list is not None: + for item in fixed_list: + dirtree_path = getattr(config, item) + if (not os.path.isdir(dirtree_path)) or (not os.path.exists(dirpath_path)): + msg = (f"The directory tree path {dirtree_path} is either not " + "a directory does not exist; this may cause unexpected results " + "or failures." + ) + logger.warning(msg=msg) + + for (fixed_attr_key, fixed_attr_value) in fixed_attr_dict.items(): + dirtree_path = os.path.join(fix_dirpath, fixed_attr_value) + if (not os.path.isdir(dirtree_path)) or (not os.path.exists(dirpath_path)): + msg = (f"The directory tree path {dirtree_path} is either not a directory " + "or does not exist. Aborting!!!" + ) + raise GFSError(msg=msg) + + setattr(fixedfiles_dict, fixed_attr_key, dirtree_path) + + return fixedfiles_dict def initialize(self: Forecast): """ """ + # Define the base-class method attributes. super().initialize() + # Sync the fixed files accordingly. + fixed_list = self.get_fixedfiles_info(app=self.gfs_app) + fixedfiles_dict = self.__fixedfiles(config=self.config, fixed_path=self.fixed_path, + logger=self.logger, fixed_list=fixed_list) + # NEED TO READ YAMLS SOMEHOW. From 080d1769866a6ea8cf823766f457d524cdabc17d Mon Sep 17 00:00:00 2001 From: HenryWinterbottom-NOAA Date: Thu, 30 Mar 2023 14:23:50 -0600 Subject: [PATCH 04/31] Bringing branch UTD. --- jobs/JGLOBAL_FORECAST_GFS_INITIALIZE | 62 ++++++++++++++++++++++++++++ jobs/rocoto/gfs_init.sh | 34 +++++++++++++++ ush/python/pygfs/task/gfs.py | 19 +++++---- 3 files changed, 107 insertions(+), 8 deletions(-) create mode 100755 jobs/JGLOBAL_FORECAST_GFS_INITIALIZE create mode 100755 jobs/rocoto/gfs_init.sh diff --git a/jobs/JGLOBAL_FORECAST_GFS_INITIALIZE b/jobs/JGLOBAL_FORECAST_GFS_INITIALIZE new file mode 100755 index 00000000000..9c528210e32 --- /dev/null +++ b/jobs/JGLOBAL_FORECAST_GFS_INITIALIZE @@ -0,0 +1,62 @@ +#! /usr/bin/env bash + +source "${HOMEgfs}/ush/preamble.sh" +source "${HOMEgfs}/ush/jjob_header.sh" -e "fcst" -c "base fcst" + +############################################## +# Set variables used in the script +############################################## +# Set wave variables +if [ ${DO_WAVE:-"NO"} = "YES" ]; then + # WAVE component directory + export CDUMPwave=${CDUMPwave:-${CDUMP}wave} + export COMINwave=${COMINwave:-$(compath.py ${envir}/${NET}/${gfs_ver})/${CDUMP}.${PDY}/${cyc}/wave} + export COMOUTwave=${COMOUTwave:-$(compath.py -o ${NET}/${gfs_ver})/${CDUMP}.${PDY}/${cyc}/wave} +fi + +############################################## +# Begin JOB SPECIFIC work +############################################## + +# Restart conditions for GFS cycle come from GDAS +rCDUMP=${CDUMP} +[[ ${CDUMP} = "gfs" ]] && export rCDUMP="gdas" + +# Forecast length for GFS forecast +if [ ${CDUMP} = "gfs" ]; then + export FHMAX=${FHMAX_GFS} + export FHOUT=${FHOUT_GFS} + export FHMAX_HF=${FHMAX_HF_GFS} + export FHOUT_HF=${FHOUT_HF_GFS} +else + export FHMAX_HF=0 + export FHOUT_HF=0 +fi + + +############################################################### +# Run relevant exglobal script +$(which python) ${HOMEgfs}/scripts/exglobal_gfs_forecast_initialize.py + +exit 9999 + + +############################################## +# End JOB SPECIFIC work +############################################## + +############################################## +# Final processing +############################################## +if [ -e "${pgmout}" ] ; then + cat ${pgmout} +fi + +########################################## +# Remove the Temporary working directory +########################################## +cd ${DATAROOT} +[[ ${KEEPDATA} = "NO" ]] && rm -rf ${DATA} + + +exit 0 diff --git a/jobs/rocoto/gfs_init.sh b/jobs/rocoto/gfs_init.sh new file mode 100755 index 00000000000..4c6d8e00d68 --- /dev/null +++ b/jobs/rocoto/gfs_init.sh @@ -0,0 +1,34 @@ +#! /usr/bin/env bash + +# HRW source "${HOMEgfs}/ush/preamble.sh" + +############################################################### +# Source FV3GFS workflow modules +#. ${HOMEgfs}/ush/load_fv3gfs_modules.sh +#status=$? +#[[ ${status} -ne 0 ]] && exit ${status} + +# TODO: clean this up +# HRW source "${HOMEgfs}/ush/detect_machine.sh" +# HRW set +x +# HRW source "${HOMEgfs}/ush/module-setup.sh" +# HRW module use "${HOMEgfs}/sorc/ufs_model.fd/tests" +# HRW module load modules.ufs_model.lua +# HRW module load prod_util +# HRW if [[ "${MACHINE_ID}" = "wcoss2" ]]; then +# HRW module load cray-pals +# HRW fi +# HRW module list +# HRW unset MACHINE_ID +#HRW set_trace + +export job="fcst" +export jobid="${job}.refactor" # HRW + +############################################################### +# Execute the JJOB +${HOMEgfs}/jobs/JGLOBAL_FORECAST_GFS_INITIALIZE +status=$? + + +exit ${status} diff --git a/ush/python/pygfs/task/gfs.py b/ush/python/pygfs/task/gfs.py index f1f769c9464..e428b5488fb 100644 --- a/ush/python/pygfs/task/gfs.py +++ b/ush/python/pygfs/task/gfs.py @@ -4,14 +4,18 @@ from pygfs.task.forecast import Forecast from pygfs.exceptions import GFSError from typing import Dict, List -from pygw.logger import logit +from pygw.logger import Logger, logit from pygw.attrdict import AttrDict # ---- -# The following attributes are mandatory for fixed-file syncing; this -# should not be changed unless absolutely necessary. +logger = Logger(name="GFS", colored_log=True) + +# ---- + +# The following attributes are mandatory for fixed-file syncing; +# this should not be changed unless absolutely necessary. FIXED_MAND_ATTR_DICT = {"DATA": "DATA", "FIXgfs": "FIXgfs", "HOMEgfs": "HOMEgfs", @@ -21,16 +25,14 @@ # ---- -logger = Logger().build_logger() - -# ---- - +@logit(logger) class GFS(Forecast): """ """ + @logit(logger) def __init__(self: Forecast, config: Dict): """ Description @@ -47,7 +49,7 @@ def __init__(self: Forecast, config: Dict): @staticmethod @logit(logger) - def __fixedfiles(config: Dict, logger: object, + def __fixedfiles(config: Dict, logger: object, fixed_path: str, fixed_list: List = None) -> Dict: """ Description @@ -170,6 +172,7 @@ def __fixedfiles(config: Dict, logger: object, return fixedfiles_dict + @logit(logger) def initialize(self: Forecast): """ From 410fa8f7b1323d17b09721fb606d9a09453d993e Mon Sep 17 00:00:00 2001 From: HenryWinterbottom-NOAA Date: Mon, 3 Apr 2023 18:18:40 -0600 Subject: [PATCH 05/31] Bringing branch UTD with WIP. --- ush/python/pygfs/task/forecast.py | 63 ++++++++++++++------ ush/python/pygfs/task/gfs.py | 99 +++++++++++-------------------- ush/python/pygfs/ufswm.py | 7 ++- 3 files changed, 81 insertions(+), 88 deletions(-) diff --git a/ush/python/pygfs/task/forecast.py b/ush/python/pygfs/task/forecast.py index d00ba30d330..293df0fe573 100644 --- a/ush/python/pygfs/task/forecast.py +++ b/ush/python/pygfs/task/forecast.py @@ -1,11 +1,24 @@ + +import os from typing import Dict, List from pygw.task import Task from pygw.logger import Logger +from pygw.attrdict import AttrDict + from pygfs.ufswm import UFSWM from pygfs.exceptions import ForecastError +from pygw.yaml_file import YAMLFile, parse_yamltmpl + +from pygw.file_utils import FileHandler + +# ---- + +# Define the valid forecast model list. +VALID_MODEL_LIST = ["gfs"] + # ---- @@ -29,36 +42,48 @@ def __init__(self: Task, config: Dict, model: str, *args, **kwargs): """ # Define the base-class attributes. - super().__init__(config, *args, *kwargs) - self.config.model = model + super().__init__(config=config, *args, *kwargs) + self.model = model + self.fcst_config = AttrDict() # Define the appplication logger object. if getattr(self.config, "loglev") is None: self.config.loglev = "info" - self.logger = Logger(level=self.config.loglev, colored_log=True) - # Update the configuration accordingly. - if self.config.model.lower() == "gfs": - self.config.ntiles = 6 + self.logger = Logger( + level=self.config.loglev, colored_log=True) - if self.config.model.lower() != "gfs": + # Check that the specified forecast model is supported; + # proceed accordingly. + if model.lower() not in VALID_MODEL_LIST: + msg = f"Forecast model {model} is not (yet) supported. Aborting!!!" raise ForecastError(msg=msg) - self.ufswm = UFSWM(config=self.config) - - def get_fixedfiles_info(self: Task, app: str) -> List: - """ """ + try: + self.fcst_config.config = YAMLFile( + path=self.config.FCSTYAML).as_dict()["forecast"] + except KeyError: + msg = ("The attribute (e.g., YAML-key) `forecast` could not be determined " + f"from YAML-formatted file {self.config.FCSTYAML}. Aborting!!!" + ) + raise ForecastError(msg=msg) - fixed_files_list = [ - item for item in self.config.keys() if f"FIX{app}" in item] + def build_dirtree(self: Task) -> None: + """ - if len(fixed_files_list) == 0: - fixed_files_info = None - return fixed_files_list + """ - def sync_fixedfiles(self: Task, fixedfiles_dict: Dict) -> None: - """ + fixed_yaml = self.fcst_config.config.fixed_file_yaml + fixed_data = parse_yamltmpl( + path=fixed_yaml, data=self.fcst_config) + # Build the directory tree and link the fixed files to the + # working directory. + FileHandler(fixed_data.dirtree_atmos).sync() + FileHandler(fixed_data.fix_atmos).sync() + FileHandler(fixed_data.fix_land).sync() - """ + if self.fcst_config.coupled: + FileHandler(fixed_data.dirtree_ocean).sync() + FileHandler(fixed_data.fix_ocean).sync() diff --git a/ush/python/pygfs/task/gfs.py b/ush/python/pygfs/task/gfs.py index e428b5488fb..85bf9d1ba9a 100644 --- a/ush/python/pygfs/task/gfs.py +++ b/ush/python/pygfs/task/gfs.py @@ -7,6 +7,7 @@ from pygw.logger import Logger, logit from pygw.attrdict import AttrDict +from pygw.yaml_file import YAMLFile # ---- @@ -20,19 +21,18 @@ "FIXgfs": "FIXgfs", "HOMEgfs": "HOMEgfs", "atm_res": "CASE", + "coupled": "cpl", "ocn_res": "OCNRES" } # ---- -@logit(logger) class GFS(Forecast): """ """ - @logit(logger) def __init__(self: Forecast, config: Dict): """ Description @@ -44,13 +44,12 @@ def __init__(self: Forecast, config: Dict): # Define the base-class attributes. super().__init__(config=config, model="GFS") - self.gfs_app = self.config.APP - self.fixed_path = self.config.FIXgfs - @staticmethod - @logit(logger) - def __fixedfiles(config: Dict, logger: object, fixed_path: str, - fixed_list: List = None) -> Dict: + self.fcst_config.model = "gfs" + self.fcst_config.ntiles = 6 + self.fcst_config.fix_path = self.config.FIXgfs + + def __fixedfiles(self: Forecast) -> Dict: """ Description ----------- @@ -61,27 +60,6 @@ def __fixedfiles(config: Dict, logger: object, fixed_path: str, attributes, and returns a Python dictionary containing the fixed-file attributes. - Parameters - ---------- - - config: Dict - - A Python dictionary containing the configuration - attributes collected from the run-time environment. - - logger: object - - A Python object containing the defined logger object. - - Keywords - -------- - - fixed_list: List, optional - - A Python list of additional fixed-file paths to be synced - in addition to the default values specified within this - method. - Returns ------- @@ -116,23 +94,23 @@ def __fixedfiles(config: Dict, logger: object, fixed_path: str, # Define the respective configuration attribute (i.e., # `fixed_attr_key`) and assign the corresponding value # `fixed_attr_value`; proceed accordingly. - value = getattr(config, fixed_attr_key) + value = self.config[fixed_attr_value] + + if isinstance(value, dict): + value = self.runtime_config[fixed_attr_value] + if value is None: msg = (f"The configuration attribute {fixed_attr_key} could not " "be determined from the run-time environment. Aborting!!!" ) raise GFSError(msg=msg) - setattr(fixedfiles_dict, fixed_attr_key, value) + setattr(self.fcst_config, fixed_attr_key, value) # Define the top-level directory-tree path containing the - # respective fixed-files. - fix_dirpath = fixedfiles_dict.FIXgfs - if fix_dirpath is None: - msg = ("The attribute FIXgfs could not be determined from the " - "run-time environment. Aborting!!!" - ) - raise GFSError(msg=msg) + # respective fixed-files and build the Python dictionary + # required to link the application-specific fixed files. + fix_dirpath = self.fcst_config.FIXgfs if (not os.path.isdir(fix_dirpath)) or (not os.path.exists(fix_dirpath)): msg = (f"The directory tree {fix_dirpath} is either not a directory " @@ -145,45 +123,34 @@ def __fixedfiles(config: Dict, logger: object, fixed_path: str, "FIX_am": "am", "FIX_lut": "lut", "FIX_orog": "orog", - "FIX_lut": "lut" + "FIX_lut": "lut", + "FIX_ugwd": "ugwd", } - # Check whether the run-time environment contains additional - # fixed-files to be synced; proceed accordingly. - if fixed_list is not None: - for item in fixed_list: - dirtree_path = getattr(config, item) - if (not os.path.isdir(dirtree_path)) or (not os.path.exists(dirpath_path)): - msg = (f"The directory tree path {dirtree_path} is either not " - "a directory does not exist; this may cause unexpected results " - "or failures." - ) - logger.warning(msg=msg) - for (fixed_attr_key, fixed_attr_value) in fixed_attr_dict.items(): - dirtree_path = os.path.join(fix_dirpath, fixed_attr_value) - if (not os.path.isdir(dirtree_path)) or (not os.path.exists(dirpath_path)): - msg = (f"The directory tree path {dirtree_path} is either not a directory " - "or does not exist. Aborting!!!" - ) - raise GFSError(msg=msg) - setattr(fixedfiles_dict, fixed_attr_key, dirtree_path) + fix_subdirpath = os.path.join(fix_dirpath, fixed_attr_value) + if (not os.path.isdir(fix_subdirpath)) and (not os.path.isfile(fix_subdirpath)): + msg = (f"The directory tree path {fix_subdirpath} is either not " + "a directory does not exist; this may cause unexpected results " + "or failures." + ) + self.logger.warning(msg=msg) - return fixedfiles_dict + setattr(self.fcst_config, fixed_attr_key, os.path.join( + self.config.FIXgfs, fixed_attr_value)) - @logit(logger) def initialize(self: Forecast): """ """ - # Define the base-class method attributes. + # Update the base-class method attributes. super().initialize() - # Sync the fixed files accordingly. - fixed_list = self.get_fixedfiles_info(app=self.gfs_app) - fixedfiles_dict = self.__fixedfiles(config=self.config, fixed_path=self.fixed_path, - logger=self.logger, fixed_list=fixed_list) + # Build the directory tree and copy the required fixed files + # accordingly. + self.__fixedfiles() + self.build_dirtree() - # NEED TO READ YAMLS SOMEHOW. + # Build the respective UFS configuration files. diff --git a/ush/python/pygfs/ufswm.py b/ush/python/pygfs/ufswm.py index 5b5ef25b3ae..a82b5323888 100644 --- a/ush/python/pygfs/ufswm.py +++ b/ush/python/pygfs/ufswm.py @@ -69,7 +69,7 @@ class UFSWM: """ - def __init__(self, config: Dict): + def __init__(self, config: Dict, forecast_config: Dict): """ Description ----------- @@ -80,13 +80,14 @@ def __init__(self, config: Dict): # Define the base-class attributes. self.config = config + self.forecast_config = forecast_config self.logger = Logger(level=self.config.loglev, colored_log=True) # Configure the respective forecast models. self.atmos_grid = self.__atmos_grid_setup( atm_res=self.config.CASE, atm_levs=self.config.LEVS, - atm_ntiles=self.config.ntiles, + atm_ntiles=self.forecast_config.ntiles, logger=self.logger, ) @@ -127,7 +128,7 @@ def __atmos_grid_setup( Returns ------- - atm_config: Dict + atm_config: Dict[str, Any] A Python dictionary containing the atmosphere model configuration established via the parameter attributes. From 164f42f423c898d9786b127f9e237e6ba2ab2e6c Mon Sep 17 00:00:00 2001 From: HenryWinterbottom-NOAA Date: Mon, 3 Apr 2023 18:20:09 -0600 Subject: [PATCH 06/31] Bringing branch UTD with WIP. --- jobs/JGLOBAL_FORECAST_GFS_INITIALIZE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jobs/JGLOBAL_FORECAST_GFS_INITIALIZE b/jobs/JGLOBAL_FORECAST_GFS_INITIALIZE index 9c528210e32..46cab5f5b31 100755 --- a/jobs/JGLOBAL_FORECAST_GFS_INITIALIZE +++ b/jobs/JGLOBAL_FORECAST_GFS_INITIALIZE @@ -1,6 +1,6 @@ #! /usr/bin/env bash -source "${HOMEgfs}/ush/preamble.sh" +source "${HOMEgfs}/ush/preamble.testing.sh" source "${HOMEgfs}/ush/jjob_header.sh" -e "fcst" -c "base fcst" ############################################## From 3424fa4e2ff1bcc085f2c4f8cb317caea6ac5227 Mon Sep 17 00:00:00 2001 From: HenryWinterbottom-NOAA Date: Mon, 3 Apr 2023 18:21:35 -0600 Subject: [PATCH 07/31] Bringing branch UTD with WIP. --- parm/config/config.fcst | 1 + parm/config/config.gfs_init | 7 +++++++ 2 files changed, 8 insertions(+) create mode 100644 parm/config/config.gfs_init diff --git a/parm/config/config.fcst b/parm/config/config.fcst index 357b68512c0..b4f34e51992 100644 --- a/parm/config/config.fcst +++ b/parm/config/config.fcst @@ -73,6 +73,7 @@ fi export FORECASTSH="$HOMEgfs/scripts/exglobal_forecast.sh" export FCSTEXECDIR="$HOMEgfs/exec" export FCSTEXEC="ufs_model.x" +export FCSTYAML="$HOMEgfs/parm/ufs/forecast/gfs.yaml" ####################################################################### # Model configuration diff --git a/parm/config/config.gfs_init b/parm/config/config.gfs_init new file mode 100644 index 00000000000..9d987bead5d --- /dev/null +++ b/parm/config/config.gfs_init @@ -0,0 +1,7 @@ +#!/bin/bash -x + +########## config.gfsinit ########## +# Configuration common to all GFS initialization tasks. + +echo "BEGIN: config.gfsinit" + From 2539672514c5193b31e0534b7ad7e2c42ddf6abc Mon Sep 17 00:00:00 2001 From: HenryWinterbottom-NOAA Date: Tue, 4 Apr 2023 17:12:50 -0600 Subject: [PATCH 08/31] Bringing branch UTD with WIP. --- parm/ufs/forecast/gfs.yaml | 21 ++ .../forecast/gfs/fix/atmos.fixed_files.yaml | 87 ++++++++ .../forecast/gfs/fix/land.fixed_files.yaml | 58 ++++++ .../forecast/gfs/fix/ocean.fixed_files.yaml | 10 + parm/ufs/forecast/gfs/model_configure.IN | 36 ++++ .../forecast/gfs/nems/nems.configure.atm.IN | 8 + .../gfs/nems/nems.configure.atm_aero.IN | 47 +++++ .../nems/nems.configure.blocked_atm_wav.IN | 38 ++++ .../forecast/gfs/nems/nems.configure.cpld.IN | 111 +++++++++++ .../nems.configure.cpld_aero_outerwave.IN | 141 +++++++++++++ .../gfs/nems/nems.configure.cpld_aero_wave.IN | 141 +++++++++++++ .../gfs/nems/nems.configure.cpld_outerwave.IN | 130 ++++++++++++ .../gfs/nems/nems.configure.cpld_wave.IN | 130 ++++++++++++ .../nems/nems.configure.leapfrog_atm_wav.IN | 38 ++++ parm/ufs/forecast/grids/fv3.yaml | 98 +++++++++ .../forecast/ufs.atm.fixed_files.working.yaml | 187 ++++++++++++++++++ parm/ufs/forecast/ufs.atm.fixed_files.yaml | 34 ++++ ush/python/pygfs/task/forecast.py | 107 +++++++--- ush/python/pygfs/task/gfs.py | 20 +- 19 files changed, 1412 insertions(+), 30 deletions(-) create mode 100644 parm/ufs/forecast/gfs.yaml create mode 100644 parm/ufs/forecast/gfs/fix/atmos.fixed_files.yaml create mode 100644 parm/ufs/forecast/gfs/fix/land.fixed_files.yaml create mode 100644 parm/ufs/forecast/gfs/fix/ocean.fixed_files.yaml create mode 100644 parm/ufs/forecast/gfs/model_configure.IN create mode 100644 parm/ufs/forecast/gfs/nems/nems.configure.atm.IN create mode 100644 parm/ufs/forecast/gfs/nems/nems.configure.atm_aero.IN create mode 100644 parm/ufs/forecast/gfs/nems/nems.configure.blocked_atm_wav.IN create mode 100644 parm/ufs/forecast/gfs/nems/nems.configure.cpld.IN create mode 100644 parm/ufs/forecast/gfs/nems/nems.configure.cpld_aero_outerwave.IN create mode 100644 parm/ufs/forecast/gfs/nems/nems.configure.cpld_aero_wave.IN create mode 100644 parm/ufs/forecast/gfs/nems/nems.configure.cpld_outerwave.IN create mode 100644 parm/ufs/forecast/gfs/nems/nems.configure.cpld_wave.IN create mode 100644 parm/ufs/forecast/gfs/nems/nems.configure.leapfrog_atm_wav.IN create mode 100644 parm/ufs/forecast/grids/fv3.yaml create mode 100644 parm/ufs/forecast/ufs.atm.fixed_files.working.yaml create mode 100644 parm/ufs/forecast/ufs.atm.fixed_files.yaml diff --git a/parm/ufs/forecast/gfs.yaml b/parm/ufs/forecast/gfs.yaml new file mode 100644 index 00000000000..bfff8caab80 --- /dev/null +++ b/parm/ufs/forecast/gfs.yaml @@ -0,0 +1,21 @@ +forecast: + + dirtree_atmos: + mkdir: + - $(DATA)/INPUT + - $(DATA)/RESTART + + dirtree_ocean: + mkdir: + - $(DATA)/MOM6_OUTPUT + - $(DATA)/history + - $(DATA)/INPUT + - $(DATA)/RESTART + + fixed_files: + atmos: ${PARMgfs}/ufs/forecast/gfs/fix/atmos.fixed_files.yaml + land: ${PARMgfs}/ufs/forecast/gfs/fix/land.fixed_files.yaml + ocean: ${PARMgfs}/ufs/forecast/gfs/fix/ocean.fixed_files.yaml + + model_configure: ${PARMufs}/ufs/forecast/gfs/model_configure.IN + nems_configure: ${PARMufs}/ufs/forecast/gfs/nems/nems.configure.atm.IN diff --git a/parm/ufs/forecast/gfs/fix/atmos.fixed_files.yaml b/parm/ufs/forecast/gfs/fix/atmos.fixed_files.yaml new file mode 100644 index 00000000000..5b41f42f6ac --- /dev/null +++ b/parm/ufs/forecast/gfs/fix/atmos.fixed_files.yaml @@ -0,0 +1,87 @@ +copy: +# Atmosphere mosaic file linked as the grid_spec file (atm only) +- [$(FIX_orog)/$(atm_res)/$(atm_res)_mosaic.nc, $(DATA)/INPUT/grid_spec.nc] + +# Atmosphere grid tile files +- [$(FIX_orog)/$(atm_res)/$(atm_res)_grid.tile1.nc, $(DATA)/INPUT/] +- [$(FIX_orog)/$(atm_res)/$(atm_res)_grid.tile2.nc, $(DATA)/INPUT/] +- [$(FIX_orog)/$(atm_res)/$(atm_res)_grid.tile3.nc, $(DATA)/INPUT/] +- [$(FIX_orog)/$(atm_res)/$(atm_res)_grid.tile4.nc, $(DATA)/INPUT/] +- [$(FIX_orog)/$(atm_res)/$(atm_res)_grid.tile5.nc, $(DATA)/INPUT/] +- [$(FIX_orog)/$(atm_res)/$(atm_res)_grid.tile6.nc, $(DATA)/INPUT/] + + + +# oro_data_ls and oro_data_ss files from FIX_ugwd +- [$(FIX_ugwd)/$(atm_res)/$(atm_res)_oro_data_ls.tile1.nc, $(DATA)/INPUT/oro_data_ls.tile1.nc] +- [$(FIX_ugwd)/$(atm_res)/$(atm_res)_oro_data_ls.tile2.nc, $(DATA)/INPUT/oro_data_ls.tile2.nc] +- [$(FIX_ugwd)/$(atm_res)/$(atm_res)_oro_data_ls.tile3.nc, $(DATA)/INPUT/oro_data_ls.tile3.nc] +- [$(FIX_ugwd)/$(atm_res)/$(atm_res)_oro_data_ls.tile4.nc, $(DATA)/INPUT/oro_data_ls.tile4.nc] +- [$(FIX_ugwd)/$(atm_res)/$(atm_res)_oro_data_ls.tile5.nc, $(DATA)/INPUT/oro_data_ls.tile5.nc] +- [$(FIX_ugwd)/$(atm_res)/$(atm_res)_oro_data_ls.tile6.nc, $(DATA)/INPUT/oro_data_ls.tile6.nc] +- [$(FIX_ugwd)/$(atm_res)/$(atm_res)_oro_data_ss.tile1.nc, $(DATA)/INPUT/oro_data_ss.tile1.nc] +- [$(FIX_ugwd)/$(atm_res)/$(atm_res)_oro_data_ss.tile2.nc, $(DATA)/INPUT/oro_data_ss.tile2.nc] +- [$(FIX_ugwd)/$(atm_res)/$(atm_res)_oro_data_ss.tile3.nc, $(DATA)/INPUT/oro_data_ss.tile3.nc] +- [$(FIX_ugwd)/$(atm_res)/$(atm_res)_oro_data_ss.tile4.nc, $(DATA)/INPUT/oro_data_ss.tile4.nc] +- [$(FIX_ugwd)/$(atm_res)/$(atm_res)_oro_data_ss.tile5.nc, $(DATA)/INPUT/oro_data_ss.tile5.nc] +- [$(FIX_ugwd)/$(atm_res)/$(atm_res)_oro_data_ss.tile6.nc, $(DATA)/INPUT/oro_data_ss.tile6.nc] + +# GWD?? +- [$(FIX_ugwd)/ugwp_limb_tau.nc, $(DATA)/ugwp_limb_tau.nc] + +# CO2 climatology +- [$(FIX_am)/co2monthlycyc.txt, $(DATA)/co2monthlycyc.txt] +- [$(FIX_am)/global_co2historicaldata_glob.txt, $(DATA)/co2historicaldata_glob.txt] +- [$(FIX_am)/fix_co2_proj/global_co2historicaldata_2009.txt, $(DATA)/co2historicaldata_2009.txt] +- [$(FIX_am)/fix_co2_proj/global_co2historicaldata_2010.txt, $(DATA)/co2historicaldata_2010.txt] +- [$(FIX_am)/fix_co2_proj/global_co2historicaldata_2011.txt, $(DATA)/co2historicaldata_2011.txt] +- [$(FIX_am)/fix_co2_proj/global_co2historicaldata_2012.txt, $(DATA)/co2historicaldata_2012.txt] +- [$(FIX_am)/fix_co2_proj/global_co2historicaldata_2013.txt, $(DATA)/co2historicaldata_2013.txt] +- [$(FIX_am)/fix_co2_proj/global_co2historicaldata_2014.txt, $(DATA)/co2historicaldata_2014.txt] +- [$(FIX_am)/fix_co2_proj/global_co2historicaldata_2015.txt, $(DATA)/co2historicaldata_2015.txt] +- [$(FIX_am)/fix_co2_proj/global_co2historicaldata_2016.txt, $(DATA)/co2historicaldata_2016.txt] +- [$(FIX_am)/fix_co2_proj/global_co2historicaldata_2017.txt, $(DATA)/co2historicaldata_2017.txt] +- [$(FIX_am)/fix_co2_proj/global_co2historicaldata_2018.txt, $(DATA)/co2historicaldata_2018.txt] +- [$(FIX_am)/fix_co2_proj/global_co2historicaldata_2019.txt, $(DATA)/co2historicaldata_2019.txt] +- [$(FIX_am)/fix_co2_proj/global_co2historicaldata_2020.txt, $(DATA)/co2historicaldata_2020.txt] +- [$(FIX_am)/fix_co2_proj/global_co2historicaldata_2021.txt, $(DATA)/co2historicaldata_2021.txt] +- [$(FIX_am)/fix_co2_proj/global_co2historicaldata_2022.txt, $(DATA)/co2historicaldata_2022.txt] +- [$(FIX_am)/fix_co2_proj/global_co2historicaldata_2023.txt, $(DATA)/co2historicaldata_2023.txt] + +# FIX_am files +- [$(FIX_am)/global_climaeropac_global.txt, $(DATA)/aerosol.dat] +- [$(FIX_am)/ozprdlos_2015_new_sbuvO3_tclm15_nuchem.f77, $(DATA)/global_o3prdlos.f77] +- [$(FIX_am)/global_h2o_pltc.f77, $(DATA)/global_h2oprdlos.f77] +- [$(FIX_am)/global_glacier.2x2.grb, $(DATA)/global_glacier.2x2.grb] +- [$(FIX_am)/global_maxice.2x2.grb, $(DATA)/global_maxice.2x2.grb] +- [$(FIX_am)/global_snoclim.1.875.grb, $(DATA)/global_snoclim.1.875.grb] +- [$(FIX_am)/global_slmask.t1534.3072.1536.grb, $(DATA)/global_slmask.t1534.3072.1536.grb] +- [$(FIX_am)/global_soilmgldas.statsgo.t1534.3072.1536.grb, $(DATA)/global_soilmgldas.statsgo.t1534.3072.1536.grb] +- [$(FIX_am)/global_solarconstant_noaa_an.txt, $(DATA)/solarconstant_noaa_an.txt] +- [$(FIX_am)/global_sfc_emissivity_idx.txt, $(DATA)/sfc_emissivity_idx.txt] +- [$(FIX_am)/RTGSST.1982.2012.monthly.clim.grb, $(DATA)/RTGSST.1982.2012.monthly.clim.grb] +- [$(FIX_am)/IMS-NIC.blended.ice.monthly.clim.grb, $(DATA)/IMS-NIC.blended.ice.monthly.clim.grb] + +# MERRA2 Aerosol Climatology +- [$(FIX_aer)/merra2.aerclim.2003-2014.m01.nc, $(DATA)/aeroclim.m01.nc] +- [$(FIX_aer)/merra2.aerclim.2003-2014.m02.nc, $(DATA)/aeroclim.m02.nc] +- [$(FIX_aer)/merra2.aerclim.2003-2014.m03.nc, $(DATA)/aeroclim.m03.nc] +- [$(FIX_aer)/merra2.aerclim.2003-2014.m04.nc, $(DATA)/aeroclim.m04.nc] +- [$(FIX_aer)/merra2.aerclim.2003-2014.m05.nc, $(DATA)/aeroclim.m05.nc] +- [$(FIX_aer)/merra2.aerclim.2003-2014.m06.nc, $(DATA)/aeroclim.m06.nc] +- [$(FIX_aer)/merra2.aerclim.2003-2014.m07.nc, $(DATA)/aeroclim.m07.nc] +- [$(FIX_aer)/merra2.aerclim.2003-2014.m08.nc, $(DATA)/aeroclim.m08.nc] +- [$(FIX_aer)/merra2.aerclim.2003-2014.m09.nc, $(DATA)/aeroclim.m09.nc] +- [$(FIX_aer)/merra2.aerclim.2003-2014.m10.nc, $(DATA)/aeroclim.m10.nc] +- [$(FIX_aer)/merra2.aerclim.2003-2014.m11.nc, $(DATA)/aeroclim.m11.nc] +- [$(FIX_aer)/merra2.aerclim.2003-2014.m12.nc, $(DATA)/aeroclim.m12.nc] + +# Optical depth +- [$(FIX_lut)/optics_BC.v1_3.dat, $(DATA)/optics_BC.dat] +- [$(FIX_lut)/optics_DU.v15_3.dat, $(DATA)/optics_DU.dat] +- [$(FIX_lut)/optics_OC.v1_3.dat, $(DATA)/optics_OC.dat] +- [$(FIX_lut)/optics_SS.v3_3.dat, $(DATA)/optics_SS.dat] +- [$(FIX_lut)/optics_SU.v1_3.dat, $(DATA)/optics_SU.dat] + +# fd_nems.yaml file +- [$(HOMEgfs)/sorc/ufs_model.fd/tests/parm/fd_nems.yaml, $(DATA)/] diff --git a/parm/ufs/forecast/gfs/fix/land.fixed_files.yaml b/parm/ufs/forecast/gfs/fix/land.fixed_files.yaml new file mode 100644 index 00000000000..ab93ff27a63 --- /dev/null +++ b/parm/ufs/forecast/gfs/fix/land.fixed_files.yaml @@ -0,0 +1,58 @@ +copy: + + # Files from FIX_orog/C??.mx??_frac/fix_sfc + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/fix_sfc/$(atm_res).facsf.tile1.nc, $(DATA)/] + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/fix_sfc/$(atm_res).facsf.tile2.nc, $(DATA)/] + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/fix_sfc/$(atm_res).facsf.tile3.nc, $(DATA)/] + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/fix_sfc/$(atm_res).facsf.tile4.nc, $(DATA)/] + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/fix_sfc/$(atm_res).facsf.tile5.nc, $(DATA)/] + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/fix_sfc/$(atm_res).facsf.tile6.nc, $(DATA)/] + + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/fix_sfc/$(atm_res).maximum_snow_albedo.tile1.nc, $(DATA)/] + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/fix_sfc/$(atm_res).maximum_snow_albedo.tile2.nc, $(DATA)/] + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/fix_sfc/$(atm_res).maximum_snow_albedo.tile3.nc, $(DATA)/] + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/fix_sfc/$(atm_res).maximum_snow_albedo.tile4.nc, $(DATA)/] + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/fix_sfc/$(atm_res).maximum_snow_albedo.tile5.nc, $(DATA)/] + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/fix_sfc/$(atm_res).maximum_snow_albedo.tile6.nc, $(DATA)/] + + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/fix_sfc/$(atm_res).slope_type.tile1.nc, $(DATA)/] + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/fix_sfc/$(atm_res).slope_type.tile2.nc, $(DATA)/] + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/fix_sfc/$(atm_res).slope_type.tile3.nc, $(DATA)/] + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/fix_sfc/$(atm_res).slope_type.tile4.nc, $(DATA)/] + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/fix_sfc/$(atm_res).slope_type.tile5.nc, $(DATA)/] + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/fix_sfc/$(atm_res).slope_type.tile6.nc, $(DATA)/] + + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/fix_sfc/$(atm_res).snowfree_albedo.tile1.nc, $(DATA)/] + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/fix_sfc/$(atm_res).snowfree_albedo.tile2.nc, $(DATA)/] + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/fix_sfc/$(atm_res).snowfree_albedo.tile3.nc, $(DATA)/] + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/fix_sfc/$(atm_res).snowfree_albedo.tile4.nc, $(DATA)/] + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/fix_sfc/$(atm_res).snowfree_albedo.tile5.nc, $(DATA)/] + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/fix_sfc/$(atm_res).snowfree_albedo.tile6.nc, $(DATA)/] + + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/fix_sfc/$(atm_res).soil_type.tile1.nc, $(DATA)/] + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/fix_sfc/$(atm_res).soil_type.tile2.nc, $(DATA)/] + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/fix_sfc/$(atm_res).soil_type.tile3.nc, $(DATA)/] + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/fix_sfc/$(atm_res).soil_type.tile4.nc, $(DATA)/] + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/fix_sfc/$(atm_res).soil_type.tile5.nc, $(DATA)/] + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/fix_sfc/$(atm_res).soil_type.tile6.nc, $(DATA)/] + + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/fix_sfc/$(atm_res).substrate_temperature.tile1.nc, $(DATA)/] + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/fix_sfc/$(atm_res).substrate_temperature.tile2.nc, $(DATA)/] + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/fix_sfc/$(atm_res).substrate_temperature.tile3.nc, $(DATA)/] + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/fix_sfc/$(atm_res).substrate_temperature.tile4.nc, $(DATA)/] + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/fix_sfc/$(atm_res).substrate_temperature.tile5.nc, $(DATA)/] + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/fix_sfc/$(atm_res).substrate_temperature.tile6.nc, $(DATA)/] + + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/fix_sfc/$(atm_res).vegetation_greenness.tile1.nc, $(DATA)/] + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/fix_sfc/$(atm_res).vegetation_greenness.tile2.nc, $(DATA)/] + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/fix_sfc/$(atm_res).vegetation_greenness.tile3.nc, $(DATA)/] + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/fix_sfc/$(atm_res).vegetation_greenness.tile4.nc, $(DATA)/] + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/fix_sfc/$(atm_res).vegetation_greenness.tile5.nc, $(DATA)/] + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/fix_sfc/$(atm_res).vegetation_greenness.tile6.nc, $(DATA)/] + + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/fix_sfc/$(atm_res).vegetation_type.tile1.nc, $(DATA)/] + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/fix_sfc/$(atm_res).vegetation_type.tile2.nc, $(DATA)/] + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/fix_sfc/$(atm_res).vegetation_type.tile3.nc, $(DATA)/] + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/fix_sfc/$(atm_res).vegetation_type.tile4.nc, $(DATA)/] + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/fix_sfc/$(atm_res).vegetation_type.tile5.nc, $(DATA)/] + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/fix_sfc/$(atm_res).vegetation_type.tile6.nc, $(DATA)/] diff --git a/parm/ufs/forecast/gfs/fix/ocean.fixed_files.yaml b/parm/ufs/forecast/gfs/fix/ocean.fixed_files.yaml new file mode 100644 index 00000000000..801f070c49a --- /dev/null +++ b/parm/ufs/forecast/gfs/fix/ocean.fixed_files.yaml @@ -0,0 +1,10 @@ +copy: + + # Orography data tile files + # The following are for "frac_grid = .true." + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/oro_$(atm_res).mx$(ocn_res).tile1.nc, $(DATA)/INPUT/oro_data.tile1.nc] + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/oro_$(atm_res).mx$(ocn_res).tile2.nc, $(DATA)/INPUT/oro_data.tile2.nc] + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/oro_$(atm_res).mx$(ocn_res).tile3.nc, $(DATA)/INPUT/oro_data.tile3.nc] + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/oro_$(atm_res).mx$(ocn_res).tile4.nc, $(DATA)/INPUT/oro_data.tile4.nc] + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/oro_$(atm_res).mx$(ocn_res).tile5.nc, $(DATA)/INPUT/oro_data.tile5.nc] + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/oro_$(atm_res).mx$(ocn_res).tile6.nc, $(DATA)/INPUT/oro_data.tile6.nc] diff --git a/parm/ufs/forecast/gfs/model_configure.IN b/parm/ufs/forecast/gfs/model_configure.IN new file mode 100644 index 00000000000..af850bb26fb --- /dev/null +++ b/parm/ufs/forecast/gfs/model_configure.IN @@ -0,0 +1,36 @@ +start_year: {{ start_year }} +start_month: {{ start_month }} +start_day: {{ start_day }} +start_hour: {{ start_hour }} +start_minute: {{ start_minute|0 }} +start_second: {{ start_second|0 }} +nhours_fcst: {{ FHMAX }} +fhrot: {{ IAU_FHROT|0 }} + +dt_atmos: {{ DELTIM }} +calendar: {{ calendar|"julian" }} +restart_interval: {{ restart_interval }} +output_1st_tstep_rst: .false. + +quilting: {{ QUILTING }} +write_groups: {{ WRITE_GROUP|1 }} +write_tasks_per_group: {{ WRTTASK_PER_GROUP|24 }} +itasks: 1 +output_history: {{ OUTPUT_HISTORY|.true. }} +write_dopost: {{ WRITE_DOPOST|.false. }} +write_nsflip: {{ WRITE_NSFLIP|.false. }} +num_files: {{ NUM_FILES|2 }} +filename_base: "atm" "sfc" +output_grid: {{ OUTPUT_GRID }} +output_file: "{{ OUTPUT_FILETYPE_ATM }}" "{{ OUTPUT_FILETYPE_SFC }}" +ichunk2d: {{ ichunk2d|0 }} +jchunk2d: {{ jchunk2d|0 }} +ichunk3d: {{ ichunk3d|0 }} +jchunk3d: {{ jchunk3d|0 }} +kchunk3d: {{ kchunk3d|0 }} +ideflate: {{ ideflate|1 }} +nbits: {{ nbits|14 }} +imo: {{ LONB_IMO }} +jmo: {{ LATB_JMO }} +output_fh: {{ OUTPUT_FH }} +iau_offset: {{ IAU_OFFSET|0 }} diff --git a/parm/ufs/forecast/gfs/nems/nems.configure.atm.IN b/parm/ufs/forecast/gfs/nems/nems.configure.atm.IN new file mode 100644 index 00000000000..3ca60ccef3a --- /dev/null +++ b/parm/ufs/forecast/gfs/nems/nems.configure.atm.IN @@ -0,0 +1,8 @@ +# ESMF # +logKindFlag: {{ esmf_logkind or "ESMF_LOGKIND_NONE"}} + +EARTH_component_list: ATM +ATM_model: fv3 +runSeq:: + ATM +:: diff --git a/parm/ufs/forecast/gfs/nems/nems.configure.atm_aero.IN b/parm/ufs/forecast/gfs/nems/nems.configure.atm_aero.IN new file mode 100644 index 00000000000..b3fc775034d --- /dev/null +++ b/parm/ufs/forecast/gfs/nems/nems.configure.atm_aero.IN @@ -0,0 +1,47 @@ +############################################# +#### NEMS Run-Time Configuration File ##### +############################################# + +# ESMF # + logKindFlag: @[esmf_logkind] + +# EARTH # +EARTH_component_list: ATM CHM +EARTH_attributes:: + Verbosity = max +:: + +# ATM # +ATM_model: @[atm_model] +ATM_petlist_bounds: @[atm_petlist_bounds] +ATM_attributes:: + Verbosity = max +:: + +# CHM # +CHM_model: @[chm_model] +CHM_petlist_bounds: @[chm_petlist_bounds] +CHM_attributes:: + Verbosity = max +:: + +# Run Sequence # +runSeq:: + @@[coupling_interval_fast_sec] + ATM phase1 + ATM -> CHM + CHM + CHM -> ATM + ATM phase2 + @ +:: + +# CMEPS variables + +DRIVER_attributes:: + mediator_read_restart = .false. +:: + +ALLCOMP_attributes:: + start_type = startup +:: diff --git a/parm/ufs/forecast/gfs/nems/nems.configure.blocked_atm_wav.IN b/parm/ufs/forecast/gfs/nems/nems.configure.blocked_atm_wav.IN new file mode 100644 index 00000000000..159cf6a4589 --- /dev/null +++ b/parm/ufs/forecast/gfs/nems/nems.configure.blocked_atm_wav.IN @@ -0,0 +1,38 @@ +############################################# +#### NEMS Run-Time Configuration File ##### +############################################# + +# ESMF # + logKindFlag: @[esmf_logkind] + +# EARTH # +EARTH_component_list: ATM WAV +EARTH_attributes:: + Verbosity = max +:: + +# ATM # +ATM_model: @[atm_model] +ATM_petlist_bounds: @[atm_petlist_bounds] +ATM_attributes:: + Verbosity = max + DumpFields = true +:: + +# WAV # +WAV_model: @[wav_model] +WAV_petlist_bounds: @[wav_petlist_bounds] +WAV_attributes:: + Verbosity = max +:: + + + +# Run Sequence # +runSeq:: + @@[coupling_interval_sec] + ATM -> WAV + ATM + WAV + @ +:: diff --git a/parm/ufs/forecast/gfs/nems/nems.configure.cpld.IN b/parm/ufs/forecast/gfs/nems/nems.configure.cpld.IN new file mode 100644 index 00000000000..dda18352051 --- /dev/null +++ b/parm/ufs/forecast/gfs/nems/nems.configure.cpld.IN @@ -0,0 +1,111 @@ +############################################# +#### NEMS Run-Time Configuration File ##### +############################################# + +# ESMF # +logKindFlag: @[esmf_logkind] + +# EARTH # +EARTH_component_list: MED ATM OCN ICE +EARTH_attributes:: + Verbosity = 0 +:: + +# MED # +MED_model: @[med_model] +MED_petlist_bounds: @[med_petlist_bounds] +:: + +# ATM # +ATM_model: @[atm_model] +ATM_petlist_bounds: @[atm_petlist_bounds] +ATM_attributes:: + Verbosity = 0 + DumpFields = @[DumpFields] + ProfileMemory = false + OverwriteSlice = true +:: + +# OCN # +OCN_model: @[ocn_model] +OCN_petlist_bounds: @[ocn_petlist_bounds] +OCN_attributes:: + Verbosity = 0 + DumpFields = @[DumpFields] + ProfileMemory = false + OverwriteSlice = true + mesh_ocn = @[MESH_OCN_ICE] +:: + +# ICE # +ICE_model: @[ice_model] +ICE_petlist_bounds: @[ice_petlist_bounds] +ICE_attributes:: + Verbosity = 0 + DumpFields = @[DumpFields] + ProfileMemory = false + OverwriteSlice = true + mesh_ice = @[MESH_OCN_ICE] + stop_n = @[RESTART_N] + stop_option = nhours + stop_ymd = -999 +:: + +# CMEPS warm run sequence +runSeq:: +@@[coupling_interval_slow_sec] + MED med_phases_prep_ocn_avg + MED -> OCN :remapMethod=redist + OCN + @@[coupling_interval_fast_sec] + MED med_phases_prep_atm + MED med_phases_prep_ice + MED -> ATM :remapMethod=redist + MED -> ICE :remapMethod=redist + ATM + ICE + ATM -> MED :remapMethod=redist + MED med_phases_post_atm + ICE -> MED :remapMethod=redist + MED med_phases_post_ice + MED med_phases_prep_ocn_accum + @ + OCN -> MED :remapMethod=redist + MED med_phases_post_ocn + MED med_phases_restart_write +@ +:: + +# CMEPS variables + +DRIVER_attributes:: +:: +MED_attributes:: + ATM_model = @[atm_model] + ICE_model = @[ice_model] + OCN_model = @[ocn_model] + history_n = 0 + history_option = nhours + history_ymd = -999 + coupling_mode = @[CPLMODE] + history_tile_atm = @[ATMTILESIZE] +:: +ALLCOMP_attributes:: + ScalarFieldCount = 2 + ScalarFieldIdxGridNX = 1 + ScalarFieldIdxGridNY = 2 + ScalarFieldName = cpl_scalars + start_type = @[RUNTYPE] + restart_dir = RESTART/ + case_name = ufs.cpld + restart_n = @[RESTART_N] + restart_option = nhours + restart_ymd = -999 + dbug_flag = @[cap_dbug_flag] + use_coldstart = @[use_coldstart] + use_mommesh = @[use_mommesh] + eps_imesh = @[eps_imesh] + stop_n = @[FHMAX] + stop_option = nhours + stop_ymd = -999 +:: diff --git a/parm/ufs/forecast/gfs/nems/nems.configure.cpld_aero_outerwave.IN b/parm/ufs/forecast/gfs/nems/nems.configure.cpld_aero_outerwave.IN new file mode 100644 index 00000000000..e7a13a02c12 --- /dev/null +++ b/parm/ufs/forecast/gfs/nems/nems.configure.cpld_aero_outerwave.IN @@ -0,0 +1,141 @@ +############################################# +#### NEMS Run-Time Configuration File ##### +############################################# + +# ESMF # + logKindFlag: @[esmf_logkind] + +# EARTH # +EARTH_component_list: MED ATM CHM OCN ICE WAV +EARTH_attributes:: + Verbosity = 0 +:: + +# MED # +MED_model: @[med_model] +MED_petlist_bounds: @[med_petlist_bounds] +:: + +# ATM # +ATM_model: @[atm_model] +ATM_petlist_bounds: @[atm_petlist_bounds] +ATM_attributes:: + Verbosity = 0 + DumpFields = @[DumpFields] + ProfileMemory = false + OverwriteSlice = true +:: + +# CHM # +CHM_model: @[chm_model] +CHM_petlist_bounds: @[chm_petlist_bounds] +CHM_attributes:: + Verbosity = 0 +:: + +# OCN # +OCN_model: @[ocn_model] +OCN_petlist_bounds: @[ocn_petlist_bounds] +OCN_attributes:: + Verbosity = 0 + DumpFields = @[DumpFields] + ProfileMemory = false + OverwriteSlice = true + mesh_ocn = @[MESH_OCN_ICE] +:: + +# ICE # +ICE_model: @[ice_model] +ICE_petlist_bounds: @[ice_petlist_bounds] +ICE_attributes:: + Verbosity = 0 + DumpFields = @[DumpFields] + ProfileMemory = false + OverwriteSlice = true + mesh_ice = @[MESH_OCN_ICE] + stop_n = @[RESTART_N] + stop_option = nhours + stop_ymd = -999 +:: + +# WAV # +WAV_model: @[wav_model] +WAV_petlist_bounds: @[wav_petlist_bounds] +WAV_attributes:: + Verbosity = 0 + OverwriteSlice = false + diro = "." + logfile = wav.log + mesh_wav = @[MESH_WAV] + multigrid = @[MULTIGRID] +:: + +# CMEPS warm run sequence +runSeq:: +@@[coupling_interval_slow_sec] + MED med_phases_prep_wav_avg + MED med_phases_prep_ocn_avg + MED -> WAV :remapMethod=redist + MED -> OCN :remapMethod=redist + WAV + OCN + @@[coupling_interval_fast_sec] + MED med_phases_prep_atm + MED med_phases_prep_ice + MED -> ATM :remapMethod=redist + MED -> ICE :remapMethod=redist + ATM phase1 + ATM -> CHM + CHM + CHM -> ATM + ATM phase2 + ICE + ATM -> MED :remapMethod=redist + MED med_phases_post_atm + ICE -> MED :remapMethod=redist + MED med_phases_post_ice + MED med_phases_prep_ocn_accum + MED med_phases_prep_wav_accum + @ + OCN -> MED :remapMethod=redist + WAV -> MED :remapMethod=redist + MED med_phases_post_ocn + MED med_phases_post_wav + MED med_phases_restart_write +@ +:: + +# CMEPS variables + +DRIVER_attributes:: +:: +MED_attributes:: + ATM_model = @[atm_model] + ICE_model = @[ice_model] + OCN_model = @[ocn_model] + WAV_model = @[wav_model] + history_n = 0 + history_option = nhours + history_ymd = -999 + coupling_mode = @[CPLMODE] + history_tile_atm = @[ATMTILESIZE] +:: +ALLCOMP_attributes:: + ScalarFieldCount = 2 + ScalarFieldIdxGridNX = 1 + ScalarFieldIdxGridNY = 2 + ScalarFieldName = cpl_scalars + start_type = @[RUNTYPE] + restart_dir = RESTART/ + case_name = ufs.cpld + restart_n = @[RESTART_N] + restart_option = nhours + restart_ymd = -999 + dbug_flag = @[cap_dbug_flag] + use_coldstart = @[use_coldstart] + use_mommesh = @[use_mommesh] + eps_imesh = @[eps_imesh] + stop_n = @[FHMAX] + stop_option = nhours + stop_ymd = -999 +:: diff --git a/parm/ufs/forecast/gfs/nems/nems.configure.cpld_aero_wave.IN b/parm/ufs/forecast/gfs/nems/nems.configure.cpld_aero_wave.IN new file mode 100644 index 00000000000..9e67af9ba45 --- /dev/null +++ b/parm/ufs/forecast/gfs/nems/nems.configure.cpld_aero_wave.IN @@ -0,0 +1,141 @@ +############################################# +#### NEMS Run-Time Configuration File ##### +############################################# + +# ESMF # + logKindFlag: @[esmf_logkind] + +# EARTH # +EARTH_component_list: MED ATM CHM OCN ICE WAV +EARTH_attributes:: + Verbosity = 0 +:: + +# MED # +MED_model: @[med_model] +MED_petlist_bounds: @[med_petlist_bounds] +:: + +# ATM # +ATM_model: @[atm_model] +ATM_petlist_bounds: @[atm_petlist_bounds] +ATM_attributes:: + Verbosity = 0 + DumpFields = @[DumpFields] + ProfileMemory = false + OverwriteSlice = true +:: + +# CHM # +CHM_model: @[chm_model] +CHM_petlist_bounds: @[chm_petlist_bounds] +CHM_attributes:: + Verbosity = 0 +:: + +# OCN # +OCN_model: @[ocn_model] +OCN_petlist_bounds: @[ocn_petlist_bounds] +OCN_attributes:: + Verbosity = 0 + DumpFields = @[DumpFields] + ProfileMemory = false + OverwriteSlice = true + mesh_ocn = @[MESH_OCN_ICE] +:: + +# ICE # +ICE_model: @[ice_model] +ICE_petlist_bounds: @[ice_petlist_bounds] +ICE_attributes:: + Verbosity = 0 + DumpFields = @[DumpFields] + ProfileMemory = false + OverwriteSlice = true + mesh_ice = @[MESH_OCN_ICE] + stop_n = @[RESTART_N] + stop_option = nhours + stop_ymd = -999 +:: + +# WAV # +WAV_model: @[wav_model] +WAV_petlist_bounds: @[wav_petlist_bounds] +WAV_attributes:: + Verbosity = 0 + OverwriteSlice = false + diro = "." + logfile = wav.log + mesh_wav = @[MESH_WAV] + multigrid = @[MULTIGRID] +:: + +# CMEPS warm run sequence +runSeq:: +@@[coupling_interval_slow_sec] + MED med_phases_prep_ocn_avg + MED -> OCN :remapMethod=redist + OCN + @@[coupling_interval_fast_sec] + MED med_phases_prep_atm + MED med_phases_prep_ice + MED med_phases_prep_wav_accum + MED med_phases_prep_wav_avg + MED -> ATM :remapMethod=redist + MED -> ICE :remapMethod=redist + MED -> WAV :remapMethod=redist + ATM phase1 + ATM -> CHM + CHM + CHM -> ATM + ATM phase2 + ICE + WAV + ATM -> MED :remapMethod=redist + MED med_phases_post_atm + ICE -> MED :remapMethod=redist + MED med_phases_post_ice + WAV -> MED :remapMethod=redist + MED med_phases_post_wav + MED med_phases_prep_ocn_accum + @ + OCN -> MED :remapMethod=redist + MED med_phases_post_ocn + MED med_phases_restart_write +@ +:: + +# CMEPS variables + +DRIVER_attributes:: +:: +MED_attributes:: + ATM_model = @[atm_model] + ICE_model = @[ice_model] + OCN_model = @[ocn_model] + WAV_model = @[wav_model] + history_n = 0 + history_option = nhours + history_ymd = -999 + coupling_mode = @[CPLMODE] + history_tile_atm = @[ATMTILESIZE] +:: +ALLCOMP_attributes:: + ScalarFieldCount = 2 + ScalarFieldIdxGridNX = 1 + ScalarFieldIdxGridNY = 2 + ScalarFieldName = cpl_scalars + start_type = @[RUNTYPE] + restart_dir = RESTART/ + case_name = ufs.cpld + restart_n = @[RESTART_N] + restart_option = nhours + restart_ymd = -999 + dbug_flag = @[cap_dbug_flag] + use_coldstart = @[use_coldstart] + use_mommesh = @[use_mommesh] + eps_imesh = @[eps_imesh] + stop_n = @[FHMAX] + stop_option = nhours + stop_ymd = -999 +:: diff --git a/parm/ufs/forecast/gfs/nems/nems.configure.cpld_outerwave.IN b/parm/ufs/forecast/gfs/nems/nems.configure.cpld_outerwave.IN new file mode 100644 index 00000000000..8f4552a5896 --- /dev/null +++ b/parm/ufs/forecast/gfs/nems/nems.configure.cpld_outerwave.IN @@ -0,0 +1,130 @@ +############################################# +#### NEMS Run-Time Configuration File ##### +############################################# + +# ESMF # +logKindFlag: @[esmf_logkind] + +# EARTH # +EARTH_component_list: MED ATM OCN ICE WAV +EARTH_attributes:: + Verbosity = 0 +:: + +# MED # +MED_model: @[med_model] +MED_petlist_bounds: @[med_petlist_bounds] +:: + +# ATM # +ATM_model: @[atm_model] +ATM_petlist_bounds: @[atm_petlist_bounds] +ATM_attributes:: + Verbosity = 0 + DumpFields = @[DumpFields] + ProfileMemory = false + OverwriteSlice = true +:: + +# OCN # +OCN_model: @[ocn_model] +OCN_petlist_bounds: @[ocn_petlist_bounds] +OCN_attributes:: + Verbosity = 0 + DumpFields = @[DumpFields] + ProfileMemory = false + OverwriteSlice = true + mesh_ocn = @[MESH_OCN_ICE] +:: + +# ICE # +ICE_model: @[ice_model] +ICE_petlist_bounds: @[ice_petlist_bounds] +ICE_attributes:: + Verbosity = 0 + DumpFields = @[DumpFields] + ProfileMemory = false + OverwriteSlice = true + mesh_ice = @[MESH_OCN_ICE] + stop_n = @[RESTART_N] + stop_option = nhours + stop_ymd = -999 +:: + +# WAV # +WAV_model: @[wav_model] +WAV_petlist_bounds: @[wav_petlist_bounds] +WAV_attributes:: + Verbosity = 0 + OverwriteSlice = false + diro = "." + logfile = wav.log + mesh_wav = @[MESH_WAV] + multigrid = @[MULTIGRID] +:: + +# CMEPS warm run sequence +runSeq:: +@@[coupling_interval_slow_sec] + MED med_phases_prep_wav_avg + MED med_phases_prep_ocn_avg + MED -> WAV :remapMethod=redist + MED -> OCN :remapMethod=redist + WAV + OCN + @@[coupling_interval_fast_sec] + MED med_phases_prep_atm + MED med_phases_prep_ice + MED -> ATM :remapMethod=redist + MED -> ICE :remapMethod=redist + ATM + ICE + ATM -> MED :remapMethod=redist + MED med_phases_post_atm + ICE -> MED :remapMethod=redist + MED med_phases_post_ice + MED med_phases_prep_ocn_accum + MED med_phases_prep_wav_accum + @ + OCN -> MED :remapMethod=redist + WAV -> MED :remapMethod=redist + MED med_phases_post_ocn + MED med_phases_post_wav + MED med_phases_restart_write +@ +:: + +# CMEPS variables + +DRIVER_attributes:: +:: +MED_attributes:: + ATM_model = @[atm_model] + ICE_model = @[ice_model] + OCN_model = @[ocn_model] + WAV_model = @[wav_model] + history_n = 0 + history_option = nhours + history_ymd = -999 + coupling_mode = @[CPLMODE] + history_tile_atm = @[ATMTILESIZE] +:: +ALLCOMP_attributes:: + ScalarFieldCount = 2 + ScalarFieldIdxGridNX = 1 + ScalarFieldIdxGridNY = 2 + ScalarFieldName = cpl_scalars + start_type = @[RUNTYPE] + restart_dir = RESTART/ + case_name = ufs.cpld + restart_n = @[RESTART_N] + restart_option = nhours + restart_ymd = -999 + dbug_flag = @[cap_dbug_flag] + use_coldstart = @[use_coldstart] + use_mommesh = @[use_mommesh] + eps_imesh = @[eps_imesh] + stop_n = @[FHMAX] + stop_option = nhours + stop_ymd = -999 +:: diff --git a/parm/ufs/forecast/gfs/nems/nems.configure.cpld_wave.IN b/parm/ufs/forecast/gfs/nems/nems.configure.cpld_wave.IN new file mode 100644 index 00000000000..89ef5101605 --- /dev/null +++ b/parm/ufs/forecast/gfs/nems/nems.configure.cpld_wave.IN @@ -0,0 +1,130 @@ +############################################# +#### NEMS Run-Time Configuration File ##### +############################################# + +# ESMF # +logKindFlag: @[esmf_logkind] + +# EARTH # +EARTH_component_list: MED ATM OCN ICE WAV +EARTH_attributes:: + Verbosity = 0 +:: + +# MED # +MED_model: @[med_model] +MED_petlist_bounds: @[med_petlist_bounds] +:: + +# ATM # +ATM_model: @[atm_model] +ATM_petlist_bounds: @[atm_petlist_bounds] +ATM_attributes:: + Verbosity = 0 + DumpFields = @[DumpFields] + ProfileMemory = false + OverwriteSlice = true +:: + +# OCN # +OCN_model: @[ocn_model] +OCN_petlist_bounds: @[ocn_petlist_bounds] +OCN_attributes:: + Verbosity = 0 + DumpFields = @[DumpFields] + ProfileMemory = false + OverwriteSlice = true + mesh_ocn = @[MESH_OCN_ICE] +:: + +# ICE # +ICE_model: @[ice_model] +ICE_petlist_bounds: @[ice_petlist_bounds] +ICE_attributes:: + Verbosity = 0 + DumpFields = @[DumpFields] + ProfileMemory = false + OverwriteSlice = true + mesh_ice = @[MESH_OCN_ICE] + stop_n = @[RESTART_N] + stop_option = nhours + stop_ymd = -999 +:: + +# WAV # +WAV_model: @[wav_model] +WAV_petlist_bounds: @[wav_petlist_bounds] +WAV_attributes:: + Verbosity = 0 + OverwriteSlice = false + diro = "." + logfile = wav.log + mesh_wav = @[MESH_WAV] + multigrid = @[MULTIGRID] +:: + +# CMEPS warm run sequence +runSeq:: +@@[coupling_interval_slow_sec] + MED med_phases_prep_ocn_avg + MED -> OCN :remapMethod=redist + OCN + @@[coupling_interval_fast_sec] + MED med_phases_prep_atm + MED med_phases_prep_ice + MED med_phases_prep_wav_accum + MED med_phases_prep_wav_avg + MED -> ATM :remapMethod=redist + MED -> ICE :remapMethod=redist + MED -> WAV :remapMethod=redist + ATM + ICE + WAV + ATM -> MED :remapMethod=redist + MED med_phases_post_atm + ICE -> MED :remapMethod=redist + MED med_phases_post_ice + WAV -> MED :remapMethod=redist + MED med_phases_post_wav + MED med_phases_prep_ocn_accum + @ + OCN -> MED :remapMethod=redist + MED med_phases_post_ocn + MED med_phases_restart_write +@ +:: + +# CMEPS variables + +DRIVER_attributes:: +:: +MED_attributes:: + ATM_model = @[atm_model] + ICE_model = @[ice_model] + OCN_model = @[ocn_model] + WAV_model = @[wav_model] + history_n = 0 + history_option = nhours + history_ymd = -999 + coupling_mode = @[CPLMODE] + history_tile_atm = @[ATMTILESIZE] +:: +ALLCOMP_attributes:: + ScalarFieldCount = 2 + ScalarFieldIdxGridNX = 1 + ScalarFieldIdxGridNY = 2 + ScalarFieldName = cpl_scalars + start_type = @[RUNTYPE] + restart_dir = RESTART/ + case_name = ufs.cpld + restart_n = @[RESTART_N] + restart_option = nhours + restart_ymd = -999 + dbug_flag = @[cap_dbug_flag] + use_coldstart = @[use_coldstart] + use_mommesh = @[use_mommesh] + eps_imesh = @[eps_imesh] + stop_n = @[FHMAX] + stop_option = nhours + stop_ymd = -999 +:: diff --git a/parm/ufs/forecast/gfs/nems/nems.configure.leapfrog_atm_wav.IN b/parm/ufs/forecast/gfs/nems/nems.configure.leapfrog_atm_wav.IN new file mode 100644 index 00000000000..a29951d001f --- /dev/null +++ b/parm/ufs/forecast/gfs/nems/nems.configure.leapfrog_atm_wav.IN @@ -0,0 +1,38 @@ +############################################# +#### NEMS Run-Time Configuration File ##### +############################################# + +# ESMF # + logKindFlag: @[esmf_logkind] + +# EARTH # +EARTH_component_list: ATM WAV +EARTH_attributes:: + Verbosity = max +:: + +# ATM # +ATM_model: @[atm_model] +ATM_petlist_bounds: @[atm_petlist_bounds] +ATM_attributes:: + Verbosity = max + DumpFields = true +:: + +# WAV # +WAV_model: @[wav_model] +WAV_petlist_bounds: @[wav_petlist_bounds] +WAV_attributes:: + Verbosity = max +:: + + + +# Run Sequence # +runSeq:: + @@[coupling_interval_slow_sec] + ATM + ATM -> WAV + WAV + @ +:: diff --git a/parm/ufs/forecast/grids/fv3.yaml b/parm/ufs/forecast/grids/fv3.yaml new file mode 100644 index 00000000000..5f3f4ed54f2 --- /dev/null +++ b/parm/ufs/forecast/grids/fv3.yaml @@ -0,0 +1,98 @@ +C48: + DELTIM: 1200 + layout_x: 1 + layout_y: 1 + layout_x_gfs: 1 + layout_y_gfs: 1 + nthreads_fv3: 1 + nthreads_fv3_gfs: 1 + cdmbgwd: 0.071,2.1,1.0,1.0 # mountain blocking, ogwd, cgwd, cgwd src scaling + WRITE_GROUP: 1 + WRTTASK_PER_GROUP: 2 + WRITE_GROUP_GFS: 1 + WRTTASK_PER_GROUP_GFS: 2 + +C96: + DELTIM: 600 + layout_x: 2 + layout_y: 2 + layout_x_gfs: 2 + layout_y_gfs: 2 + nthreads_fv3: 1 + nthreads_fv3_gfs: 1 + cdmbgwd: 0.14,1.8,1.0,1.0 # mountain blocking, ogwd, cgwd, cgwd src scaling + WRITE_GROUP: 1 + WRTTASK_PER_GROUP: 4 + WRITE_GROUP_GFS: 1 + WRTTASK_PER_GROUP_GFS: 4 + +C192: + DELTIM: 450 + layout_x: 4 + layout_y: 6 + layout_x_gfs: 4 + layout_y_gfs: 6 + nthreads_fv3: 1 + nthreads_fv3_gfs: 2 + cdmbgwd: 0.23,1.5,1.0,1.0 # mountain blocking, ogwd, cgwd, cgwd src scaling + WRITE_GROUP: 1 + WRTTASK_PER_GROUP: 64 + WRITE_GROUP_GFS: 2 + WRTTASK_PER_GROUP_GFS: 64 + +C384: + DELTIM: 200 + layout_x: 6 + layout_y: 8 + layout_x_gfs: 8 + layout_y_gfs: 12 + nthreads_fv3: 1 + nthreads_fv3_gfs: 2 + cdmbgwd: 1.1,0.72,1.0,1.0 # mountain blocking, ogwd, cgwd, cgwd src scaling + WRITE_GROUP: 2 + WRTTASK_PER_GROUP: 48 + WRITE_GROUP_GFS: 2 + WRTTASK_PER_GROUP_GFS: 64 + +C768: + DELTIM: 150 + layout_x: 8 + layout_y: 12 + layout_x_gfs: 12 + layout_y_gfs: 16 + nthreads_fv3: 4 + nthreads_fv3_gfs: 4 + cdmbgwd: 4.0,0.15,1.0,1.0 # mountain blocking, ogwd, cgwd, cgwd src scaling + WRITE_GROUP: 2 + WRTTASK_PER_GROUP: 64 + WRITE_GROUP_GFS: 4 + WRTTASK_PER_GROUP_GFS: 64 + +C1152: + DELTIM: 120 + layout_x: 8 + layout_y: 16 + layout_x_gfs: 8 + layout_y_gfs: 16 + nthreads_fv3: 4 + nthreads_fv3_gfs: 4 + cdmbgwd: 4.0,0.10,1.0,1.0 # mountain blocking, ogwd, cgwd, cgwd src scaling + WRITE_GROUP: 4 + WRTTASK_PER_GROUP: 64 # TODO: refine these numbers when a case is available + WRITE_GROUP_GFS: 4 + WRTTASK_PER_GROUP_GFS: 64 # TODO: refine these numbers when a case is available + +C3072: + DELTIM: 90 + layout_x: 16 + layout_y: 32 + layout_x_gfs: 16 + layout_y_gfs: 32 + nthreads_fv3: 4 + nthreads_fv3_gfs: 4 + cdmbgwd: 4.0,0.05,1.0,1.0 # mountain blocking, ogwd, cgwd, cgwd src scaling + WRITE_GROUP: 4 + WRTTASK_PER_GROUP: 64 # TODO: refine these numbers when a case is available + WRITE_GROUP_GFS: 4 + WRTTASK_PER_GROUP_GFS: 64 # TODO: refine these numbers when a case is available + diff --git a/parm/ufs/forecast/ufs.atm.fixed_files.working.yaml b/parm/ufs/forecast/ufs.atm.fixed_files.working.yaml new file mode 100644 index 00000000000..567fd04ce6f --- /dev/null +++ b/parm/ufs/forecast/ufs.atm.fixed_files.working.yaml @@ -0,0 +1,187 @@ +# The following inputs need to be resolved: +# HOMEgfs - path to HOMEgfs directory +# DATA - path to DATA directory where files will be staged and run (DATA must exist) +# atm_res - resolution of the atmospheric cubed-sphere grid e.g C48 +# ocn_res - resolution of the ocean tripolar grid e.g. 100 +# FIX_orog - path to orography fix files +# FIX_ugwd - path to xyz fix files +# FIX_am - path to am fix files +# FIX_aer - path to aer fix files +# FIX_lut - path to lut fix files + +stage: + mkdir: + - $(DATA)/INPUT + - $(DATA)/RESTART + +fix: + copy: + # Atmosphere mosaic file linked as the grid_spec file (atm only) + - [$(FIX_orog)/$(atm_res)/$(atm_res)_mosaic.nc, $(DATA)/INPUT/grid_spec.nc] + + # Atmosphere grid tile files + - [$(FIX_orog)/$(atm_res)/$(atm_res)_grid.tile1.nc, $(DATA)/INPUT/] + - [$(FIX_orog)/$(atm_res)/$(atm_res)_grid.tile2.nc, $(DATA)/INPUT/] + - [$(FIX_orog)/$(atm_res)/$(atm_res)_grid.tile3.nc, $(DATA)/INPUT/] + - [$(FIX_orog)/$(atm_res)/$(atm_res)_grid.tile4.nc, $(DATA)/INPUT/] + - [$(FIX_orog)/$(atm_res)/$(atm_res)_grid.tile5.nc, $(DATA)/INPUT/] + - [$(FIX_orog)/$(atm_res)/$(atm_res)_grid.tile6.nc, $(DATA)/INPUT/] + + # Orography data tile files + # The following are for "frac_grid = .true." + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/oro_$(atm_res).mx$(ocn_res).tile1.nc, $(DATA)/INPUT/oro_data.tile1.nc] + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/oro_$(atm_res).mx$(ocn_res).tile2.nc, $(DATA)/INPUT/oro_data.tile2.nc] + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/oro_$(atm_res).mx$(ocn_res).tile3.nc, $(DATA)/INPUT/oro_data.tile3.nc] + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/oro_$(atm_res).mx$(ocn_res).tile4.nc, $(DATA)/INPUT/oro_data.tile4.nc] + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/oro_$(atm_res).mx$(ocn_res).tile5.nc, $(DATA)/INPUT/oro_data.tile5.nc] + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/oro_$(atm_res).mx$(ocn_res).tile6.nc, $(DATA)/INPUT/oro_data.tile6.nc] + + # oro_data_ls and oro_data_ss files from FIX_ugwd + - [$(FIX_ugwd)/$(atm_res)/$(atm_res)_oro_data_ls.tile1.nc, $(DATA)/INPUT/oro_data_ls.tile1.nc] + - [$(FIX_ugwd)/$(atm_res)/$(atm_res)_oro_data_ls.tile2.nc, $(DATA)/INPUT/oro_data_ls.tile2.nc] + - [$(FIX_ugwd)/$(atm_res)/$(atm_res)_oro_data_ls.tile3.nc, $(DATA)/INPUT/oro_data_ls.tile3.nc] + - [$(FIX_ugwd)/$(atm_res)/$(atm_res)_oro_data_ls.tile4.nc, $(DATA)/INPUT/oro_data_ls.tile4.nc] + - [$(FIX_ugwd)/$(atm_res)/$(atm_res)_oro_data_ls.tile5.nc, $(DATA)/INPUT/oro_data_ls.tile5.nc] + - [$(FIX_ugwd)/$(atm_res)/$(atm_res)_oro_data_ls.tile6.nc, $(DATA)/INPUT/oro_data_ls.tile6.nc] + - [$(FIX_ugwd)/$(atm_res)/$(atm_res)_oro_data_ss.tile1.nc, $(DATA)/INPUT/oro_data_ss.tile1.nc] + - [$(FIX_ugwd)/$(atm_res)/$(atm_res)_oro_data_ss.tile2.nc, $(DATA)/INPUT/oro_data_ss.tile2.nc] + - [$(FIX_ugwd)/$(atm_res)/$(atm_res)_oro_data_ss.tile3.nc, $(DATA)/INPUT/oro_data_ss.tile3.nc] + - [$(FIX_ugwd)/$(atm_res)/$(atm_res)_oro_data_ss.tile4.nc, $(DATA)/INPUT/oro_data_ss.tile4.nc] + - [$(FIX_ugwd)/$(atm_res)/$(atm_res)_oro_data_ss.tile5.nc, $(DATA)/INPUT/oro_data_ss.tile5.nc] + - [$(FIX_ugwd)/$(atm_res)/$(atm_res)_oro_data_ss.tile6.nc, $(DATA)/INPUT/oro_data_ss.tile6.nc] + + # GWD?? + - [$(FIX_ugwd)/ugwp_limb_tau.nc, $(DATA)/ugwp_limb_tau.nc] + + # CO2 climatology + - [$(FIX_am)/co2monthlycyc.txt, $(DATA)/co2monthlycyc.txt] + - [$(FIX_am)/global_co2historicaldata_glob.txt, $(DATA)/co2historicaldata_glob.txt] + - [$(FIX_am)/fix_co2_proj/global_co2historicaldata_2009.txt, $(DATA)/co2historicaldata_2009.txt] + - [$(FIX_am)/fix_co2_proj/global_co2historicaldata_2010.txt, $(DATA)/co2historicaldata_2010.txt] + - [$(FIX_am)/fix_co2_proj/global_co2historicaldata_2011.txt, $(DATA)/co2historicaldata_2011.txt] + - [$(FIX_am)/fix_co2_proj/global_co2historicaldata_2012.txt, $(DATA)/co2historicaldata_2012.txt] + - [$(FIX_am)/fix_co2_proj/global_co2historicaldata_2013.txt, $(DATA)/co2historicaldata_2013.txt] + - [$(FIX_am)/fix_co2_proj/global_co2historicaldata_2014.txt, $(DATA)/co2historicaldata_2014.txt] + - [$(FIX_am)/fix_co2_proj/global_co2historicaldata_2015.txt, $(DATA)/co2historicaldata_2015.txt] + - [$(FIX_am)/fix_co2_proj/global_co2historicaldata_2016.txt, $(DATA)/co2historicaldata_2016.txt] + - [$(FIX_am)/fix_co2_proj/global_co2historicaldata_2017.txt, $(DATA)/co2historicaldata_2017.txt] + - [$(FIX_am)/fix_co2_proj/global_co2historicaldata_2018.txt, $(DATA)/co2historicaldata_2018.txt] + - [$(FIX_am)/fix_co2_proj/global_co2historicaldata_2019.txt, $(DATA)/co2historicaldata_2019.txt] + - [$(FIX_am)/fix_co2_proj/global_co2historicaldata_2020.txt, $(DATA)/co2historicaldata_2020.txt] + - [$(FIX_am)/fix_co2_proj/global_co2historicaldata_2021.txt, $(DATA)/co2historicaldata_2021.txt] + - [$(FIX_am)/fix_co2_proj/global_co2historicaldata_2022.txt, $(DATA)/co2historicaldata_2022.txt] + - [$(FIX_am)/fix_co2_proj/global_co2historicaldata_2023.txt, $(DATA)/co2historicaldata_2023.txt] + + # FIX_am files + - [$(FIX_am)/global_climaeropac_global.txt, $(DATA)/aerosol.dat] + - [$(FIX_am)/ozprdlos_2015_new_sbuvO3_tclm15_nuchem.f77, $(DATA)/global_o3prdlos.f77] + - [$(FIX_am)/global_h2o_pltc.f77, $(DATA)/global_h2oprdlos.f77] + - [$(FIX_am)/global_glacier.2x2.grb, $(DATA)/global_glacier.2x2.grb] + - [$(FIX_am)/global_maxice.2x2.grb, $(DATA)/global_maxice.2x2.grb] + - [$(FIX_am)/global_snoclim.1.875.grb, $(DATA)/global_snoclim.1.875.grb] + - [$(FIX_am)/global_slmask.t1534.3072.1536.grb, $(DATA)/global_slmask.t1534.3072.1536.grb] + - [$(FIX_am)/global_soilmgldas.statsgo.t1534.3072.1536.grb, $(DATA)/global_soilmgldas.statsgo.t1534.3072.1536.grb] + - [$(FIX_am)/global_solarconstant_noaa_an.txt, $(DATA)/solarconstant_noaa_an.txt] + - [$(FIX_am)/global_sfc_emissivity_idx.txt, $(DATA)/sfc_emissivity_idx.txt] + - [$(FIX_am)/RTGSST.1982.2012.monthly.clim.grb, $(DATA)/RTGSST.1982.2012.monthly.clim.grb] + - [$(FIX_am)/IMS-NIC.blended.ice.monthly.clim.grb, $(DATA)/IMS-NIC.blended.ice.monthly.clim.grb] + + # MERRA2 Aerosol Climatology + - [$(FIX_aer)/merra2.aerclim.2003-2014.m01.nc, $(DATA)/aeroclim.m01.nc] + - [$(FIX_aer)/merra2.aerclim.2003-2014.m02.nc, $(DATA)/aeroclim.m02.nc] + - [$(FIX_aer)/merra2.aerclim.2003-2014.m03.nc, $(DATA)/aeroclim.m03.nc] + - [$(FIX_aer)/merra2.aerclim.2003-2014.m04.nc, $(DATA)/aeroclim.m04.nc] + - [$(FIX_aer)/merra2.aerclim.2003-2014.m05.nc, $(DATA)/aeroclim.m05.nc] + - [$(FIX_aer)/merra2.aerclim.2003-2014.m06.nc, $(DATA)/aeroclim.m06.nc] + - [$(FIX_aer)/merra2.aerclim.2003-2014.m07.nc, $(DATA)/aeroclim.m07.nc] + - [$(FIX_aer)/merra2.aerclim.2003-2014.m08.nc, $(DATA)/aeroclim.m08.nc] + - [$(FIX_aer)/merra2.aerclim.2003-2014.m09.nc, $(DATA)/aeroclim.m09.nc] + - [$(FIX_aer)/merra2.aerclim.2003-2014.m10.nc, $(DATA)/aeroclim.m10.nc] + - [$(FIX_aer)/merra2.aerclim.2003-2014.m11.nc, $(DATA)/aeroclim.m11.nc] + - [$(FIX_aer)/merra2.aerclim.2003-2014.m12.nc, $(DATA)/aeroclim.m12.nc] + + # Optical depth + - [$(FIX_lut)/optics_BC.v1_3.dat, $(DATA)/optics_BC.dat] + - [$(FIX_lut)/optics_DU.v15_3.dat, $(DATA)/optics_DU.dat] + - [$(FIX_lut)/optics_OC.v1_3.dat, $(DATA)/optics_OC.dat] + - [$(FIX_lut)/optics_SS.v3_3.dat, $(DATA)/optics_SS.dat] + - [$(FIX_lut)/optics_SU.v1_3.dat, $(DATA)/optics_SU.dat] + + # Files from FIX_orog/C??.mx??_frac/fix_sfc + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/fix_sfc/$(atm_res).facsf.tile1.nc, $(DATA)/] + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/fix_sfc/$(atm_res).facsf.tile2.nc, $(DATA)/] + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/fix_sfc/$(atm_res).facsf.tile3.nc, $(DATA)/] + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/fix_sfc/$(atm_res).facsf.tile4.nc, $(DATA)/] + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/fix_sfc/$(atm_res).facsf.tile5.nc, $(DATA)/] + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/fix_sfc/$(atm_res).facsf.tile6.nc, $(DATA)/] + + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/fix_sfc/$(atm_res).maximum_snow_albedo.tile1.nc, $(DATA)/] + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/fix_sfc/$(atm_res).maximum_snow_albedo.tile2.nc, $(DATA)/] + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/fix_sfc/$(atm_res).maximum_snow_albedo.tile3.nc, $(DATA)/] + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/fix_sfc/$(atm_res).maximum_snow_albedo.tile4.nc, $(DATA)/] + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/fix_sfc/$(atm_res).maximum_snow_albedo.tile5.nc, $(DATA)/] + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/fix_sfc/$(atm_res).maximum_snow_albedo.tile6.nc, $(DATA)/] + + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/fix_sfc/$(atm_res).slope_type.tile1.nc, $(DATA)/] + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/fix_sfc/$(atm_res).slope_type.tile2.nc, $(DATA)/] + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/fix_sfc/$(atm_res).slope_type.tile3.nc, $(DATA)/] + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/fix_sfc/$(atm_res).slope_type.tile4.nc, $(DATA)/] + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/fix_sfc/$(atm_res).slope_type.tile5.nc, $(DATA)/] + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/fix_sfc/$(atm_res).slope_type.tile6.nc, $(DATA)/] + + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/fix_sfc/$(atm_res).snowfree_albedo.tile1.nc, $(DATA)/] + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/fix_sfc/$(atm_res).snowfree_albedo.tile2.nc, $(DATA)/] + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/fix_sfc/$(atm_res).snowfree_albedo.tile3.nc, $(DATA)/] + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/fix_sfc/$(atm_res).snowfree_albedo.tile4.nc, $(DATA)/] + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/fix_sfc/$(atm_res).snowfree_albedo.tile5.nc, $(DATA)/] + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/fix_sfc/$(atm_res).snowfree_albedo.tile6.nc, $(DATA)/] + + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/fix_sfc/$(atm_res).soil_type.tile1.nc, $(DATA)/] + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/fix_sfc/$(atm_res).soil_type.tile2.nc, $(DATA)/] + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/fix_sfc/$(atm_res).soil_type.tile3.nc, $(DATA)/] + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/fix_sfc/$(atm_res).soil_type.tile4.nc, $(DATA)/] + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/fix_sfc/$(atm_res).soil_type.tile5.nc, $(DATA)/] + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/fix_sfc/$(atm_res).soil_type.tile6.nc, $(DATA)/] + + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/fix_sfc/$(atm_res).substrate_temperature.tile1.nc, $(DATA)/] + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/fix_sfc/$(atm_res).substrate_temperature.tile2.nc, $(DATA)/] + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/fix_sfc/$(atm_res).substrate_temperature.tile3.nc, $(DATA)/] + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/fix_sfc/$(atm_res).substrate_temperature.tile4.nc, $(DATA)/] + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/fix_sfc/$(atm_res).substrate_temperature.tile5.nc, $(DATA)/] + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/fix_sfc/$(atm_res).substrate_temperature.tile6.nc, $(DATA)/] + + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/fix_sfc/$(atm_res).vegetation_greenness.tile1.nc, $(DATA)/] + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/fix_sfc/$(atm_res).vegetation_greenness.tile2.nc, $(DATA)/] + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/fix_sfc/$(atm_res).vegetation_greenness.tile3.nc, $(DATA)/] + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/fix_sfc/$(atm_res).vegetation_greenness.tile4.nc, $(DATA)/] + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/fix_sfc/$(atm_res).vegetation_greenness.tile5.nc, $(DATA)/] + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/fix_sfc/$(atm_res).vegetation_greenness.tile6.nc, $(DATA)/] + + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/fix_sfc/$(atm_res).vegetation_type.tile1.nc, $(DATA)/] + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/fix_sfc/$(atm_res).vegetation_type.tile2.nc, $(DATA)/] + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/fix_sfc/$(atm_res).vegetation_type.tile3.nc, $(DATA)/] + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/fix_sfc/$(atm_res).vegetation_type.tile4.nc, $(DATA)/] + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/fix_sfc/$(atm_res).vegetation_type.tile5.nc, $(DATA)/] + - [$(FIX_orog)/$(atm_res).mx$(ocn_res)_frac/fix_sfc/$(atm_res).vegetation_type.tile6.nc, $(DATA)/] + + # fd_nems.yaml file + - [$(HOMEgfs)/sorc/ufs_model.fd/tests/parm/fd_nems.yaml, $(DATA)/] + +diag_table: # This list will be concatenated into a "$(DATA)diag_table.tmpl" + - $(HOMEgfs)/parm/parm_fv3diag/diag_table_header # This MUST ALWAYS be the first! + - $(HOMEgfs)/parm/parm_fv3diag/diag_table_atm + +field_table: # This list will be concatenated into a "$(DATA)field_table" + - $(HOMEgfs)/parm/parm_fv3diag/field_table + +model_configure: # This will be copied as "$(DATA)model_configure.tmpl" + copy: + - [$(HOMEgfs)/sorc/ufs_model.fd/tests/parm/model_configure.IN, $(DATA)/model_configure.tmpl] + +nems_configure: # This will be copied as "$(DATA)/nems.configure.tmpl" + copy: + - [$(HOMEgfs)/sorc/ufs_model.fd/tests/parm/nems.configure.atm.IN, $(DATA)/nems.configure.tmpl] + +input_nml: # This will be copied as "$(DATA)/input.nml.tmpl" + copy: + - [$(HOMEgfs)/sorc/ufs_model.fd/tests/parm/cpld_control.nml.IN, $(DATA)/input.nml.tmpl] diff --git a/parm/ufs/forecast/ufs.atm.fixed_files.yaml b/parm/ufs/forecast/ufs.atm.fixed_files.yaml new file mode 100644 index 00000000000..f3bcf3fd7de --- /dev/null +++ b/parm/ufs/forecast/ufs.atm.fixed_files.yaml @@ -0,0 +1,34 @@ +# The following inputs need to be resolved: +# HOMEgfs - path to HOMEgfs directory +# DATA - path to DATA directory where files will be staged and run (DATA must exist) +# atm_res - resolution of the atmospheric cubed-sphere grid e.g C48 +# ocn_res - resolution of the ocean tripolar grid e.g. 100 +# FIX_orog - path to orography fix files +# FIX_ugwd - path to xyz fix files +# FIX_am - path to am fix files +# FIX_aer - path to aer fix files +# FIX_lut - path to lut fix files + +#dirtree_atmos: +# mkdir: +# - $(DATA)/INPUT +# - $(DATA)/RESTART + +#dirtree_ocean: +# mkdir: +# - $(DATA)/MOM6_OUTPUT +# - $(DATA)/history +# - $(DATA)/INPUT +# - $(DATA)/RESTART + +# Define the path to the respective YAML-formatted files containing +# the fixed-files for the respective UFS component models. +#fix_atmos: !INC ${HOMEgfs}/parm/ufs/forecast/atmos.fixed_files.yaml +#fix_land: !INC ${HOMEgfs}/parm/ufs/forecast/land.fixed_files.yaml +#fix_ocean: !INC ${HOMEgfs}/parm/ufs/forecast/ocean.fixed_files.yaml + + + + + + diff --git a/ush/python/pygfs/task/forecast.py b/ush/python/pygfs/task/forecast.py index 293df0fe573..e89ab6de308 100644 --- a/ush/python/pygfs/task/forecast.py +++ b/ush/python/pygfs/task/forecast.py @@ -3,13 +3,14 @@ from typing import Dict, List from pygw.task import Task -from pygw.logger import Logger +from pygw.logger import Logger, logit from pygw.attrdict import AttrDict from pygfs.ufswm import UFSWM from pygfs.exceptions import ForecastError +from pygw.jinja import Jinja from pygw.yaml_file import YAMLFile, parse_yamltmpl from pygw.file_utils import FileHandler @@ -19,6 +20,9 @@ # Define the valid forecast model list. VALID_MODEL_LIST = ["gfs"] +# The following are the supported GFS applications. +GFS_APP_LIST = ["atm", "atmw", "atma", "s2s", "s2sw", "s2swa"] + # ---- @@ -30,6 +34,12 @@ class Forecast(Task): This is the base-class object for the respective Unified Forecast System (UFS) forecast task; it is a sub-class of Task. + Parameters + ---------- + + Raises + ------ + """ def __init__(self: Task, config: Dict, model: str, *args, **kwargs): @@ -43,47 +53,90 @@ def __init__(self: Task, config: Dict, model: str, *args, **kwargs): # Define the base-class attributes. super().__init__(config=config, *args, *kwargs) - self.model = model - self.fcst_config = AttrDict() + self.config.model = model - # Define the appplication logger object. - if getattr(self.config, "loglev") is None: - self.config.loglev = "info" - - self.logger = Logger( - level=self.config.loglev, colored_log=True) - - # Check that the specified forecast model is supported; - # proceed accordingly. - if model.lower() not in VALID_MODEL_LIST: - msg = f"Forecast model {model} is not (yet) supported. Aborting!!!" - raise ForecastError(msg=msg) + # HRW: THIS ALLOWS ME TO BUILD ON THE DICTIONARY FOR THE + # RESPECTIVE APPLICATION (i.e., FORECAST). + self.fcst_config = AttrDict(self.config).deepcopy() try: self.fcst_config.config = YAMLFile( - path=self.config.FCSTYAML).as_dict()["forecast"] + path=self.fcst_config.FCSTYAML).as_dict()["forecast"] except KeyError: msg = ("The attribute (e.g., YAML-key) `forecast` could not be determined " - f"from YAML-formatted file {self.config.FCSTYAML}. Aborting!!!" + f"from YAML-formatted file {self.fcst_config.FCSTYAML}. Aborting!!!" ) raise ForecastError(msg=msg) - def build_dirtree(self: Task) -> None: + if self.fcst_config.model.lower() not in VALID_MODEL_LIST: + msg = f"Forecast model {self.fcst_config.model} is not (yet) supported. Aborting!!!" + raise ForecastError(msg=msg) + + if getattr(self.fcst_config, "loglev") is None: + self.fcst_config.loglev = "info" + self.logger = Logger( + level=self.config.loglev, colored_log=True) + + def build_model_configure(self: Task) -> None: + """ """ + + model_configure_tmpl = self.fcst_config.config.model_configure + model_configure_path = os.path.join( + self.runtime_config.DATA, "model_configure") + + def build_nems_configure(self: Task) -> None: """ + Description + ----------- + This method parses a Jinja2-formatted template and builds the + UFS forecast application nems.configure file within the + forecast task application working directory. """ - fixed_yaml = self.fcst_config.config.fixed_file_yaml - fixed_data = parse_yamltmpl( - path=fixed_yaml, data=self.fcst_config) + # Define then NEMS configuration template and write the file + # accordingly. + nems_configure_tmpl = self.fcst_config.config.nems_configure + nems_configure_path = os.path.join( + self.runtime_config.DATA, "nems.configure") + + # HRW: FORCING ALL TEMPLATE VARIABLES TO BE RENDERED IF + # THEY ARE WITHOUT DEFAULT VALUES (IN THE JINJA TEMPLATE); + # IF A VARIABLE IS NOT RENDERED CORRECTLY THE FORECAST + # MODEL WILL FAIL; THIS IS ALSO USEFUL IN THE CASES WHEN A + # USER HAS DEFINE THE INCORRECT nems.configure TEMPLATE. + Jinja(nems_configure_tmpl, data=self.fcst_config, + allow_missing=False).save(nems_configure_path) + + with open(nems_configure_path, "a", encoding="utf-8") as fout: + fout.write(f"\n\n# Template: {nems_configure_tmpl}.") + + def config_dirtree(self: Task) -> None: + """ + Description + ----------- + + This method builds the directory tree and collects the + fixed-files for the respective forecast application. + + """ # Build the directory tree and link the fixed files to the - # working directory. - FileHandler(fixed_data.dirtree_atmos).sync() - FileHandler(fixed_data.fix_atmos).sync() - FileHandler(fixed_data.fix_land).sync() + # working directory; proceed accordingly. + dirtree_config = parse_yamltmpl( + path=self.fcst_config.FCSTYAML, data=self.runtime_config)["forecast"] + + FileHandler(dirtree_config.dirtree_atmos).sync() + atmos_fcst_config = parse_yamltmpl( + path=dirtree_config.fixed_files.atmos, data=self.fcst_config) + FileHandler(atmos_fcst_config).sync() + land_fcst_config = parse_yamltmpl( + path=dirtree_config.fixed_files.land, data=self.fcst_config) + FileHandler(land_fcst_config).sync() if self.fcst_config.coupled: - FileHandler(fixed_data.dirtree_ocean).sync() - FileHandler(fixed_data.fix_ocean).sync() + FileHandler(dirtree_config.dirtree_ocean).sync() + ocean_fcst_config = parse_yamltmpl( + path=dirtree_config.fixed_files.ocean, data=self.fcst_config) + FileHandler(ocean_fcst_config).sync() diff --git a/ush/python/pygfs/task/gfs.py b/ush/python/pygfs/task/gfs.py index 85bf9d1ba9a..e0fb90021d0 100644 --- a/ush/python/pygfs/task/gfs.py +++ b/ush/python/pygfs/task/gfs.py @@ -1,7 +1,7 @@ import os -from pygfs.task.forecast import Forecast +from pygfs.task.forecast import Forecast, GFS_APP_LIST from pygfs.exceptions import GFSError from typing import Dict, List from pygw.logger import Logger, logit @@ -21,7 +21,6 @@ "FIXgfs": "FIXgfs", "HOMEgfs": "HOMEgfs", "atm_res": "CASE", - "coupled": "cpl", "ocn_res": "OCNRES" } @@ -45,10 +44,23 @@ def __init__(self: Forecast, config: Dict): # Define the base-class attributes. super().__init__(config=config, model="GFS") + if self.fcst_config.APP.lower() not in GFS_APP_LIST: + msg = (f"The GFS forecast application {self.config.APP} is not " + "supported. Aborting!!!" + ) + raise GFSError(msg=msg) + self.fcst_config.model = "gfs" self.fcst_config.ntiles = 6 self.fcst_config.fix_path = self.config.FIXgfs + # HRW: THIS IS ALREADY DEFINED IN THE RUN-TIME ENVIRONMENT; DO + # WE WANT TO KEEP IT HERE AS IT CURRENTLY HAS NOT BEARING BUT + # COULD BE AN ATTRIBUTE COLLECTED FROM A YAML-FORMATTED + # CONFIGURATION FILE IN THE FUTURE? + self.fcst_config.coupled = (self.fcst_config.app in [ + "s2s", "s2sw", "s2swa"]) + def __fixedfiles(self: Forecast) -> Dict: """ Description @@ -151,6 +163,8 @@ def initialize(self: Forecast): # Build the directory tree and copy the required fixed files # accordingly. self.__fixedfiles() - self.build_dirtree() + self.config_dirtree() # Build the respective UFS configuration files. + self.build_model_configure() + self.build_nems_configure() From c6d6bc366eedea7be26bb07bab80ba1237480e63 Mon Sep 17 00:00:00 2001 From: HenryWinterbottom-NOAA Date: Wed, 5 Apr 2023 14:44:31 -0600 Subject: [PATCH 09/31] Created base-class for time attributes. --- ush/python/pygfs/tools/timeinfo.py | 192 +++++++++++++++++++++++++++++ 1 file changed, 192 insertions(+) create mode 100644 ush/python/pygfs/tools/timeinfo.py diff --git a/ush/python/pygfs/tools/timeinfo.py b/ush/python/pygfs/tools/timeinfo.py new file mode 100644 index 00000000000..0f22403c3f4 --- /dev/null +++ b/ush/python/pygfs/tools/timeinfo.py @@ -0,0 +1,192 @@ +""" +Module +------ + + pygfs.tools.timeinfo (pygfs/tools/timeinfo.py) + +Description +----------- + + This module contains the base-class module for all time-stamp + attributes definitions and derivations relative to a specified + time-stamp and optional formatting. + +Classes +------- + + TimeInfo(datestr, fmt="%Y-%m-%d %H:%M:%S") + + This is the base-class object for all time-stamp attribute + definitions. + +""" + +# ---- + +__author__ = "Henry R. Winterbottom" +__maintainer__ = "Henry R. Winterbottom" +__email__ = "henry.winterbottom@noaa.gov" +__version__ = 0.0 + +# ---- + +import datetime +import sqlite3 +from dataclasses import dataclass + +from pygfs.exceptions import TimeInfoError +from pygw.attrdict import AttrDict +from pygw.timetools import strftime, strptime + +# ---- + + +@dataclass +class TimeInfo: + """ + Description + ----------- + + This is the base-class object for all time-stamp attribute + definitions. + + Parameters + ---------- + + datestr: str + + A Python string specifying the the time-stamp; if the + parameter `fmt` is not specified upon entry, the module + assumes that the time-stamp is formatted as "%Y-%m-%d + %H:%M:%S". + + Keywords + -------- + + fmt: str + + A Python string specifying the POSIX-format for the `datestr` + parameter upon entry. + + Raises + ------ + + TimeInfoError: + + - raised if an exception is encountered while discerning the + format of the attribute `datestr` upon entry. + + - raised if an exception is encountered while defining the + derived time-stamp attributes. + + """ + + def __init__(self: object, datestr: str, fmt: str = "%Y-%m-%d %H:%M:%S"): + """ + Description + ----------- + + Creates a new TimeInfo object. + + """ + + # Define the base-class attributes. + self.timeinfo = AttrDict() + + # Define the time-stamp attributes relative to the application + # initialization time; enforce input attribute `datestr` to be + # Python type string. + try: + time_str = strptime(dtstr=str(datestr), fmt=fmt) + + except Exception as errmsg: + msg = ( + f"Initializing the attributes for input timestamp {datestr} " + f"failed with error {errmsg}. Aborting!!!" + ) + raise TimeInfoError(msg=msg) from errmsg + + time_attr_dict = { + "year": "%Y", + "month": "%m", + "day": "%d", + "hour": "%H", + "minute": "%M", + "second": "%S", + "month_name_long": "%B", + "month_name_short": "%b", + "century_short": "%C", + "year_short": "%y", + "weekday_long": "%A", + "weekday_short": "%a", + "day_of_year": "%j", + "day_of_week": "%u", + "timezone": "%Z", + "week_of_year": "%W", + } + + for (time_attr, time_attr_value) in time_attr_dict.items(): + value = strftime(dt=time_str, fmt=time_attr_value) + try: + setattr(self.timeinfo, time_attr, int(value)) + except ValueError: + setattr(self.timeinfo, time_attr, value) + + # Define the derived time-stamp attributes; proceed + # accordingly. + try: + self.__julianday() + self.__epoch() + + except Exception as errmsg: + msg = ( + "Defining timestamp attributes failed with error " + f"{errmsg}. Aborting!!!" + ) + raise TimeInfoError(msg=msg) from errmsg + + def __epoch(self: object): + """ + Description + ----------- + + This method defines the epoch time relative to the respective + timestamp specified upon entry. + + """ + + # Define the epoch time (i.e., number of seconds since 0000 + # UTC 01 January 1970). + self.timeinfo.epoch = datetime.datetime( + int(self.timeinfo.year), + int(self.timeinfo.month), + int(self.timeinfo.day), + int(self.timeinfo.hour), + int(self.timeinfo.minute), + int(self.timeinfo.second), + ).timestamp() + + def __julianday(self: object): + """ + Description + ----------- + + This method define the Julian day relative to the respective + timestamp specified upon entry. + + """ + + # Define the Julian day. + connect = sqlite3.connect(":memory:") + datestr = ( + f"{self.timeinfo.year}-" + f"{self.timeinfo.month}-" + f"{self.timeinfo.day} " + f"{self.timeinfo.hour}:" + f"{self.timeinfo.minute}:" + f"{self.timeinfo.second}" + ) + + self.timeinfo.julian_day = list( + connect.execute(f"select julianday('{datestr}')") + )[0][0] From 80e76c73469bcf2476be7846b809bfbe9a98de9f Mon Sep 17 00:00:00 2001 From: HenryWinterbottom-NOAA Date: Wed, 5 Apr 2023 17:01:40 -0600 Subject: [PATCH 10/31] Updates. --- ush/python/pygfs/ufswm.py | 142 +++++++++++--------------------------- 1 file changed, 40 insertions(+), 102 deletions(-) diff --git a/ush/python/pygfs/ufswm.py b/ush/python/pygfs/ufswm.py index a82b5323888..0aaa12ec3d7 100644 --- a/ush/python/pygfs/ufswm.py +++ b/ush/python/pygfs/ufswm.py @@ -33,19 +33,12 @@ from typing import Any, Dict from pygw.attrdict import AttrDict -from pygw.logger import Logger +from pygw.timetools import strftime, strptime from pygfs.exceptions import UFSWMError - -# ---- - -# Define the supported options for the respective forecast model -# attributes; only GFS configurations are currently supported. -ATM_RES_LIST = ["c48", "c96", "c192", "c384"] - -ATM_LEVS_LIST = [64, 128] - -ATM_NTILES_LIST = [6] +from pygfs.utils.grids import FV3GFS +from pygfs.utils.logger import Logger +from pygfs.utils.datetime import DateTime # ---- @@ -69,7 +62,7 @@ class UFSWM: """ - def __init__(self, config: Dict, forecast_config: Dict): + def __init__(self: dataclass, config: Dict, model: str): """ Description ----------- @@ -80,109 +73,54 @@ def __init__(self, config: Dict, forecast_config: Dict): # Define the base-class attributes. self.config = config - self.forecast_config = forecast_config - self.logger = Logger(level=self.config.loglev, colored_log=True) - - # Configure the respective forecast models. - self.atmos_grid = self.__atmos_grid_setup( - atm_res=self.config.CASE, - atm_levs=self.config.LEVS, - atm_ntiles=self.forecast_config.ntiles, - logger=self.logger, - ) + self.model = model.lower() + self.logger = Logger(config=self.config).logger - @staticmethod - def __atmos_grid_setup( - atm_res: str, atm_levs: int, atm_ntiles: int, logger: object - ) -> Dict[str, Any]: + # Define the respective forecast model configuration + # attributes. + self.configure() + + def __fv3gfs(self: dataclass) -> None: """ Description ----------- - This method defines the atmosphere model (e.g., FV3) grid - configuration attributes. - - Parameters - ---------- - - atm_res: str - - A Python string define the atmosphere model resolution; - this should be of the form `C##` where `##` is the - cubed-sphere resolution (e.g., 48, 96, 192, etc.,). - - atm_levs: int - - A Python integer defining the total number of levels for - the atmosphere model configuration. - - atm_ntiles: int - - A Python integer defining the total number of tiles for - the cubed-sphere (i.e., FV3 forecast model) application. - - logger: object - - A Python object containing the defined logger object. - - Returns - ------- - - atm_config: Dict[str, Any] - - A Python dictionary containing the atmosphere model - configuration established via the parameter attributes. + This method defines the configuration attributes for the UFS + FV3 GFS forecast component model. """ - # Check that the parameter attribute values are valid; proceed - # accordingly. - atm_config = AttrDict() - if atm_res.lower() not in ATM_RES_LIST: - msg = ( - f"The cubed sphere resolution {atm_res.upper()} is not " - "supported; valid values are: " - f"{', '.join([res.upper() for res in ATM_RES_LIST])}. " - "Aborting!!!" - ) - raise UFSWMError(msg=msg) - - if atm_levs not in ATM_LEVS_LIST: - msg = ( - f"The number of vertical levels {atm_levs} is not supported; " - f"valid values are {','.join(ATM_LEVS_LIST)}. " - "Aborting!!!" - ) - raise UFSWMError(msg=msg) - - if atm_ntiles not in ATM_NTILES_LIST: - msg = ( - f"The specified number of cubed-sphere tiles {atm_ntiles} is not " - f"supported; value values are {','.join(ATM_NTILES_LIST)}. " - "Aborting!!!" - ) - raise UFSWMError(msg=msg) + # Define the configuration attributes for the FV3 GFS forecast + # model. + self.config.ufswm.atmos.grids = FV3GFS(config=self.config, + model="FV3GFS", + res=self.config.CASE, + nlevs=self.config.LEVS).grids + + # HRW: The following may eventually be moved out of this + # method into `configure`; this is dependent on the additional + # UFS component model needs; TBD. + self.config.ufswm.atmos.datetime = DateTime(datestr=self.config.CDATE, + fmt="%Y-%m-%d %H:%M:%S").datetime msg = ( "\nThe atmosphere model configuration is as follows:\n\n" - f"Cubed sphere resolution: {atm_res.upper()}\n" - f"Vertical levels: {atm_levs}\n" - f"Number of cubed sphere tiles: {atm_ntiles}.\n" + f"Cubed sphere resolution: {self.config.ufswm.atmos.grids.csres.upper()}\n" + f"Vertical levels: {self.config.ufswm.atmos.grids.nlevs}\n" + f"Number of cubed sphere tiles: {self.config.ufswm.atmos.grids.ntiles}.\n" ) - logger.warn(msg=msg) + self.logger.warn(msg=msg) - # Define all atmosphere model configuration attributes with - # respect to the parameter attributes. - atm_config = AttrDict() + def configure(self: dataclass) -> None: + """ + Description + ----------- - atm_config.case_res = atm_res - atm_config.csg_res = int(atm_config.case_res[1:]) + This method collects and defines the configuration attributes + for the respective UFS forecast component model. - atm_config.jcap = int((atm_config.csg_res * 2) - 2) - atm_config.lonb = int(4 * atm_config.csg_res) - atm_config.lonb = int(2 * atm_config.csg_res) - atm_config.npx = int(atm_config.csg_res + 1) - atm_config.npy = int(atm_config.csg_res + 1) - atm_config.npz = int(atm_levs - 1) + """ - return atm_config + # UFS WM atmosphere FV3 GFS forecast model. + if self.model == "fv3gfs": + self.__fv3gfs() From df7f06ccaa6675a8e1a4d7cd9b946f9b89fdf845 Mon Sep 17 00:00:00 2001 From: HenryWinterbottom-NOAA Date: Wed, 5 Apr 2023 17:02:50 -0600 Subject: [PATCH 11/31] Formatting updates. --- ush/python/pygfs/ufswm.py | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/ush/python/pygfs/ufswm.py b/ush/python/pygfs/ufswm.py index 0aaa12ec3d7..94b6496209a 100644 --- a/ush/python/pygfs/ufswm.py +++ b/ush/python/pygfs/ufswm.py @@ -30,15 +30,11 @@ # ---- from dataclasses import dataclass -from typing import Any, Dict +from typing import Dict -from pygw.attrdict import AttrDict -from pygw.timetools import strftime, strptime - -from pygfs.exceptions import UFSWMError +from pygfs.utils.datetime import DateTime from pygfs.utils.grids import FV3GFS from pygfs.utils.logger import Logger -from pygfs.utils.datetime import DateTime # ---- @@ -92,16 +88,19 @@ def __fv3gfs(self: dataclass) -> None: # Define the configuration attributes for the FV3 GFS forecast # model. - self.config.ufswm.atmos.grids = FV3GFS(config=self.config, - model="FV3GFS", - res=self.config.CASE, - nlevs=self.config.LEVS).grids + self.config.ufswm.atmos.grids = FV3GFS( + config=self.config, + model="FV3GFS", + res=self.config.CASE, + nlevs=self.config.LEVS, + ).grids # HRW: The following may eventually be moved out of this # method into `configure`; this is dependent on the additional # UFS component model needs; TBD. - self.config.ufswm.atmos.datetime = DateTime(datestr=self.config.CDATE, - fmt="%Y-%m-%d %H:%M:%S").datetime + self.config.ufswm.atmos.datetime = DateTime( + datestr=self.config.CDATE, fmt="%Y-%m-%d %H:%M:%S" + ).datetime msg = ( "\nThe atmosphere model configuration is as follows:\n\n" From 2b4e21f0da93063f2269267b7ad3f8fa5d741fb6 Mon Sep 17 00:00:00 2001 From: HenryWinterbottom-NOAA Date: Wed, 5 Apr 2023 17:04:42 -0600 Subject: [PATCH 12/31] Bringing branch UTD with WIP. --- scripts/exglobal_gfs_forecast_initialize.py | 20 ++- ush/python/pygfs/exceptions.py | 55 ++++++- ush/python/pygfs/task/forecast.b.py | 42 ------ ush/python/pygfs/task/forecast.py | 62 ++++---- ush/python/pygfs/task/{gfs.py => fv3gfs.py} | 32 +++-- .../{tools/timeinfo.py => utils/datetime.py} | 56 ++++---- ush/python/pygfs/utils/grids.py | 135 ++++++++++++++++++ ush/python/pygfs/utils/logger.py | 34 +++++ 8 files changed, 313 insertions(+), 123 deletions(-) delete mode 100644 ush/python/pygfs/task/forecast.b.py rename ush/python/pygfs/task/{gfs.py => fv3gfs.py} (85%) rename ush/python/pygfs/{tools/timeinfo.py => utils/datetime.py} (76%) create mode 100644 ush/python/pygfs/utils/grids.py create mode 100644 ush/python/pygfs/utils/logger.py diff --git a/scripts/exglobal_gfs_forecast_initialize.py b/scripts/exglobal_gfs_forecast_initialize.py index 9d367c0cc29..94ed3f2d82b 100644 --- a/scripts/exglobal_gfs_forecast_initialize.py +++ b/scripts/exglobal_gfs_forecast_initialize.py @@ -2,7 +2,7 @@ # ----------------------------------------------------------------------------- # -# Program Name: exglobal_ufs_forecast_init.py +# Program Name: exglobal_gfs_forecast_init.py # # Author(s)/Contacts(s): Henry R. Winterbottom (henry.winterbottom@noaa.gov) # @@ -52,12 +52,18 @@ # ---- +__author__ = "Henry R. Winterbottom" +__maintainer__ = "Henry R. Winterbottom" +__email__ = "henry.winterbottom@noaa.gov" + +# ---- + import os import time from pygw.configuration import cast_strdict_as_dtypedict from pygw.logger import Logger -from pygfs.task.gfs import GFS +from pygfs.task.fv3gfs import FV3GFS # ---- @@ -65,12 +71,6 @@ # ---- -__author__ = "Henry R. Winterbottom" -__maintainer__ = "Henry R. Winterbottom" -__email__ = "henry.winterbottom@noaa.gov" - -# ---- - def main() -> None: """ @@ -85,13 +85,11 @@ def main() -> None: # Take configuration from environment and cast it as Python # dictionary. script_name = os.path.basename(__file__) - msg = f"Completed application {script_name}." - logger.info(msg=msg) start_time = time.time() config = cast_strdict_as_dtypedict(os.environ) # Launch the task. - task = GFS(config=config) + task = FV3GFS(config=config) task.initialize() stop_time = time.time() diff --git a/ush/python/pygfs/exceptions.py b/ush/python/pygfs/exceptions.py index 180356d3d7b..8a6d004f97c 100644 --- a/ush/python/pygfs/exceptions.py +++ b/ush/python/pygfs/exceptions.py @@ -2,16 +2,22 @@ Module ------ - exceptions.py + pygfs.exceptions (pygfs/exceptions.py) Description ----------- - This module contains all pyufs package exceptions. + This module contains all pygfs package exceptions. Classes ------- + DateTimeError(msg) + + This is the base-class for exceptions encountered within the + ush/python/pygfs/utils/datetime module; it is a sub-class of + WorkflowException. + ForecastError(msg) This is the base-class for exceptions encountered within the @@ -24,6 +30,12 @@ ush/python/pygfs/task/gfs module; it is a sub-class of WorkflowException. + GridsError(msg) + + This is the base-class for exceptions encountered within the + ush/python/pygfs/utils/grids module; it is a sub-class of + WorkflowException. + UFSWMError(msg) This is the base-class for exceptions encountered within the @@ -34,12 +46,34 @@ # ---- +__author__ = "Henry R. Winterbottom" +__maintainer__ = "Henry R. Winterbottom" +__email__ = "henry.winterbottom@noaa.gov" +__version__ = 0.0 + +# ---- + from pygw.exceptions import WorkflowException # ---- # Define all available classes. -__all__ = ["ForecastError", "GFSError", "UFSWMError"] +__all__ = ["DateTimeError", "ForecastError", "GFSError", "GridsInfoError", + "UFSWMError"] + +# ---- + + +class DateTimeError(WorkflowException): + """ + Description + ----------- + + This is the base-class for exceptions encountered within the + ush/python/pygfs/utils/datetime module; it is a sub-class of + WorkflowException. + + """ # ---- @@ -72,6 +106,21 @@ class GFSError(WorkflowException): # ---- +class GridsError(WorkflowException): + """ + Description + ----------- + + This is the base-class for exceptions encountered within the + ush/python/pygfs/utils/grids module; it is a sub-class of + WorkflowException. + + """ + + +# ---- + + class UFSWMError(WorkflowException): """ Description diff --git a/ush/python/pygfs/task/forecast.b.py b/ush/python/pygfs/task/forecast.b.py deleted file mode 100644 index 15cb57da65e..00000000000 --- a/ush/python/pygfs/task/forecast.b.py +++ /dev/null @@ -1,42 +0,0 @@ -from typing import Dict - -from pygw.task import Task -from pygw.logger import Logger - -from pygfs.ufswm import UFSWM -from pygfs.exceptions import ForecastError - -# ---- - - -class Forecast(Task): - """ - Description - ----------- - - This is the base-class object for the respective Unified Forecast - System (UFS) forecast task; it is a sub-class of Task. - - """ - - def __init__(self: Task, config: Dict, model: str, *args, **kwargs): - """ - Description - ----------- - - Creates a new Forecast object. - - """ - - # Define the base-class attributes. - super().__init__(config, *args, *kwargs) - self.logger = Logger(level=self.config.loglev, colored_log=True) - self.config.model = model - - # Update the configuration accordingly. - if self.config.model.lower() == "gfs": - self.config.ntiles = 6 - if self.config.model.lower() != "gfs": - raise ForecastError(msg=msg) - - self.ufswm = UFSWM(config=self.config) diff --git a/ush/python/pygfs/task/forecast.py b/ush/python/pygfs/task/forecast.py index e89ab6de308..47ae4fcc3e7 100644 --- a/ush/python/pygfs/task/forecast.py +++ b/ush/python/pygfs/task/forecast.py @@ -3,12 +3,13 @@ from typing import Dict, List from pygw.task import Task -from pygw.logger import Logger, logit +# from pygw.logger import Logger, logit from pygw.attrdict import AttrDict from pygfs.ufswm import UFSWM from pygfs.exceptions import ForecastError +from pygfs.utils.logger import Logger from pygw.jinja import Jinja from pygw.yaml_file import YAMLFile, parse_yamltmpl @@ -18,7 +19,7 @@ # ---- # Define the valid forecast model list. -VALID_MODEL_LIST = ["gfs"] +VALID_MODEL_LIST = ["fv3gfs"] # The following are the supported GFS applications. GFS_APP_LIST = ["atm", "atmw", "atma", "s2s", "s2sw", "s2swa"] @@ -53,37 +54,50 @@ def __init__(self: Task, config: Dict, model: str, *args, **kwargs): # Define the base-class attributes. super().__init__(config=config, *args, *kwargs) - self.config.model = model + self.config = config + self.model = model.lower() + self.logger = Logger(config=self.config).logger - # HRW: THIS ALLOWS ME TO BUILD ON THE DICTIONARY FOR THE - # RESPECTIVE APPLICATION (i.e., FORECAST). - self.fcst_config = AttrDict(self.config).deepcopy() + UFSWM(config=self.config, model=self.model) try: - self.fcst_config.config = YAMLFile( - path=self.fcst_config.FCSTYAML).as_dict()["forecast"] + self.config.forecast = YAMLFile( + path=self.config.FCSTYAML).as_dict()["forecast"] + except KeyError: msg = ("The attribute (e.g., YAML-key) `forecast` could not be determined " f"from YAML-formatted file {self.fcst_config.FCSTYAML}. Aborting!!!" ) raise ForecastError(msg=msg) - if self.fcst_config.model.lower() not in VALID_MODEL_LIST: - msg = f"Forecast model {self.fcst_config.model} is not (yet) supported. Aborting!!!" - raise ForecastError(msg=msg) + # try: + # self.config.yaml_config = YAMLFile( + # path=self.config.FCSTYAML).as_dict()["forecast"] + # except KeyError: + # msg = ("The attribute (e.g., YAML-key) `forecast` could not be determined " + # f"from YAML-formatted file {self.fcst_config.FCSTYAML}. Aborting!!!" + # ) + # raise ForecastError(msg=msg) + + # if self.model not in VALID_MODEL_LIST: + # msg = f"Forecast model {self.model} is not (yet) supported. Aborting!!!" + # raise ForecastError(msg=msg) - if getattr(self.fcst_config, "loglev") is None: - self.fcst_config.loglev = "info" - self.logger = Logger( - level=self.config.loglev, colored_log=True) + # if getattr(self.config, "loglev") is None: + # self.config.loglev = "info" + # self.logger = Logger( + # level=self.config.loglev, colored_log=True) def build_model_configure(self: Task) -> None: """ """ - model_configure_tmpl = self.fcst_config.config.model_configure + model_configure_tmpl = self.config.forecast.model_configure model_configure_path = os.path.join( self.runtime_config.DATA, "model_configure") + Jinja(model_configure_tmpl, data=self.config, + allow_missing=True).save(model_configure_path) + def build_nems_configure(self: Task) -> None: """ Description @@ -97,7 +111,7 @@ def build_nems_configure(self: Task) -> None: # Define then NEMS configuration template and write the file # accordingly. - nems_configure_tmpl = self.fcst_config.config.nems_configure + nems_configure_tmpl = self.config.forecast.nems_configure nems_configure_path = os.path.join( self.runtime_config.DATA, "nems.configure") @@ -106,7 +120,7 @@ def build_nems_configure(self: Task) -> None: # IF A VARIABLE IS NOT RENDERED CORRECTLY THE FORECAST # MODEL WILL FAIL; THIS IS ALSO USEFUL IN THE CASES WHEN A # USER HAS DEFINE THE INCORRECT nems.configure TEMPLATE. - Jinja(nems_configure_tmpl, data=self.fcst_config, + Jinja(nems_configure_tmpl, data=self.config, allow_missing=False).save(nems_configure_path) with open(nems_configure_path, "a", encoding="utf-8") as fout: @@ -125,18 +139,18 @@ def config_dirtree(self: Task) -> None: # Build the directory tree and link the fixed files to the # working directory; proceed accordingly. dirtree_config = parse_yamltmpl( - path=self.fcst_config.FCSTYAML, data=self.runtime_config)["forecast"] - + path=self.config.FCSTYAML, data=self.runtime_config)["forecast"] FileHandler(dirtree_config.dirtree_atmos).sync() + atmos_fcst_config = parse_yamltmpl( - path=dirtree_config.fixed_files.atmos, data=self.fcst_config) + path=dirtree_config.fixed_files.atmos, data=self.config) FileHandler(atmos_fcst_config).sync() land_fcst_config = parse_yamltmpl( - path=dirtree_config.fixed_files.land, data=self.fcst_config) + path=dirtree_config.fixed_files.land, data=self.config) FileHandler(land_fcst_config).sync() - if self.fcst_config.coupled: + if self.config.coupled: # HRW: THIS NEEDS TO BE UPDATED. FileHandler(dirtree_config.dirtree_ocean).sync() ocean_fcst_config = parse_yamltmpl( - path=dirtree_config.fixed_files.ocean, data=self.fcst_config) + path=dirtree_config.fixed_files.ocean, data=self.config) FileHandler(ocean_fcst_config).sync() diff --git a/ush/python/pygfs/task/gfs.py b/ush/python/pygfs/task/fv3gfs.py similarity index 85% rename from ush/python/pygfs/task/gfs.py rename to ush/python/pygfs/task/fv3gfs.py index e0fb90021d0..36f407f4f4a 100644 --- a/ush/python/pygfs/task/gfs.py +++ b/ush/python/pygfs/task/fv3gfs.py @@ -27,7 +27,7 @@ # ---- -class GFS(Forecast): +class FV3GFS(Forecast): """ """ @@ -42,24 +42,26 @@ def __init__(self: Forecast, config: Dict): """ # Define the base-class attributes. - super().__init__(config=config, model="GFS") + super().__init__(config=config, model="FV3GFS") - if self.fcst_config.APP.lower() not in GFS_APP_LIST: - msg = (f"The GFS forecast application {self.config.APP} is not " - "supported. Aborting!!!" - ) - raise GFSError(msg=msg) + print(self.config.ufswm.atmos.grids) + +# if self.fcst_config.APP.lower() not in GFS_APP_LIST: +# msg = (f"The GFS forecast application {self.config.APP} is not " +# "supported. Aborting!!!" +# ) +# raise GFSError(msg=msg) - self.fcst_config.model = "gfs" - self.fcst_config.ntiles = 6 - self.fcst_config.fix_path = self.config.FIXgfs + # self.fcst_config.model = "gfs" + # self.fcst_config.ntiles = 6 + # self.fcst_config.fix_path = self.config.FIXgfs # HRW: THIS IS ALREADY DEFINED IN THE RUN-TIME ENVIRONMENT; DO # WE WANT TO KEEP IT HERE AS IT CURRENTLY HAS NOT BEARING BUT # COULD BE AN ATTRIBUTE COLLECTED FROM A YAML-FORMATTED # CONFIGURATION FILE IN THE FUTURE? - self.fcst_config.coupled = (self.fcst_config.app in [ - "s2s", "s2sw", "s2swa"]) + # self.fcst_config.coupled = (self.fcst_config.app in [ + # "s2s", "s2sw", "s2swa"]) def __fixedfiles(self: Forecast) -> Dict: """ @@ -117,12 +119,12 @@ def __fixedfiles(self: Forecast) -> Dict: ) raise GFSError(msg=msg) - setattr(self.fcst_config, fixed_attr_key, value) + setattr(self.config, fixed_attr_key, value) # Define the top-level directory-tree path containing the # respective fixed-files and build the Python dictionary # required to link the application-specific fixed files. - fix_dirpath = self.fcst_config.FIXgfs + fix_dirpath = self.config.FIXgfs if (not os.path.isdir(fix_dirpath)) or (not os.path.exists(fix_dirpath)): msg = (f"The directory tree {fix_dirpath} is either not a directory " @@ -149,7 +151,7 @@ def __fixedfiles(self: Forecast) -> Dict: ) self.logger.warning(msg=msg) - setattr(self.fcst_config, fixed_attr_key, os.path.join( + setattr(self.config, fixed_attr_key, os.path.join( self.config.FIXgfs, fixed_attr_value)) def initialize(self: Forecast): diff --git a/ush/python/pygfs/tools/timeinfo.py b/ush/python/pygfs/utils/datetime.py similarity index 76% rename from ush/python/pygfs/tools/timeinfo.py rename to ush/python/pygfs/utils/datetime.py index 0f22403c3f4..055fc7f14b4 100644 --- a/ush/python/pygfs/tools/timeinfo.py +++ b/ush/python/pygfs/utils/datetime.py @@ -2,7 +2,7 @@ Module ------ - pygfs.tools.timeinfo (pygfs/tools/timeinfo.py) + pygfs.utils.datetime (pygfs/utils/datetime.py) Description ----------- @@ -14,7 +14,7 @@ Classes ------- - TimeInfo(datestr, fmt="%Y-%m-%d %H:%M:%S") + DateTime(datestr, fmt="%Y-%m-%d %H:%M:%S") This is the base-class object for all time-stamp attribute definitions. @@ -34,7 +34,7 @@ import sqlite3 from dataclasses import dataclass -from pygfs.exceptions import TimeInfoError +from pygfs.exceptions import DateTimeError from pygw.attrdict import AttrDict from pygw.timetools import strftime, strptime @@ -42,7 +42,7 @@ @dataclass -class TimeInfo: +class DateTime: """ Description ----------- @@ -71,7 +71,7 @@ class TimeInfo: Raises ------ - TimeInfoError: + DateTimeError: - raised if an exception is encountered while discerning the format of the attribute `datestr` upon entry. @@ -81,17 +81,17 @@ class TimeInfo: """ - def __init__(self: object, datestr: str, fmt: str = "%Y-%m-%d %H:%M:%S"): + def __init__(self: dataclass, datestr: str, fmt: str = "%Y-%m-%d %H:%M:%S"): """ Description ----------- - Creates a new TimeInfo object. + Creates a new DateTime object. """ # Define the base-class attributes. - self.timeinfo = AttrDict() + self.datetime = AttrDict() # Define the time-stamp attributes relative to the application # initialization time; enforce input attribute `datestr` to be @@ -104,7 +104,7 @@ def __init__(self: object, datestr: str, fmt: str = "%Y-%m-%d %H:%M:%S"): f"Initializing the attributes for input timestamp {datestr} " f"failed with error {errmsg}. Aborting!!!" ) - raise TimeInfoError(msg=msg) from errmsg + raise DateTimeError(msg=msg) from errmsg time_attr_dict = { "year": "%Y", @@ -128,9 +128,9 @@ def __init__(self: object, datestr: str, fmt: str = "%Y-%m-%d %H:%M:%S"): for (time_attr, time_attr_value) in time_attr_dict.items(): value = strftime(dt=time_str, fmt=time_attr_value) try: - setattr(self.timeinfo, time_attr, int(value)) + setattr(self.datetime, time_attr, int(value)) except ValueError: - setattr(self.timeinfo, time_attr, value) + setattr(self.datetime, time_attr, value) # Define the derived time-stamp attributes; proceed # accordingly. @@ -143,9 +143,9 @@ def __init__(self: object, datestr: str, fmt: str = "%Y-%m-%d %H:%M:%S"): "Defining timestamp attributes failed with error " f"{errmsg}. Aborting!!!" ) - raise TimeInfoError(msg=msg) from errmsg + raise DateTimeError(msg=msg) from errmsg - def __epoch(self: object): + def __epoch(self: dataclass): """ Description ----------- @@ -157,16 +157,16 @@ def __epoch(self: object): # Define the epoch time (i.e., number of seconds since 0000 # UTC 01 January 1970). - self.timeinfo.epoch = datetime.datetime( - int(self.timeinfo.year), - int(self.timeinfo.month), - int(self.timeinfo.day), - int(self.timeinfo.hour), - int(self.timeinfo.minute), - int(self.timeinfo.second), + self.datetime.epoch = datetime.datetime( + int(self.datetime.year), + int(self.datetime.month), + int(self.datetime.day), + int(self.datetime.hour), + int(self.datetime.minute), + int(self.datetime.second), ).timestamp() - def __julianday(self: object): + def __julianday(self: dataclass): """ Description ----------- @@ -179,14 +179,14 @@ def __julianday(self: object): # Define the Julian day. connect = sqlite3.connect(":memory:") datestr = ( - f"{self.timeinfo.year}-" - f"{self.timeinfo.month}-" - f"{self.timeinfo.day} " - f"{self.timeinfo.hour}:" - f"{self.timeinfo.minute}:" - f"{self.timeinfo.second}" + f"{self.datetime.year}-" + f"{self.datetime.month}-" + f"{self.datetime.day} " + f"{self.datetime.hour}:" + f"{self.datetime.minute}:" + f"{self.datetime.second}" ) - self.timeinfo.julian_day = list( + self.datetime.julian_day = list( connect.execute(f"select julianday('{datestr}')") )[0][0] diff --git a/ush/python/pygfs/utils/grids.py b/ush/python/pygfs/utils/grids.py new file mode 100644 index 00000000000..d912b0dfe06 --- /dev/null +++ b/ush/python/pygfs/utils/grids.py @@ -0,0 +1,135 @@ +""" +Module +------ + + pygfs.utils.grids (pygfs/utils/grids.py) + +Description +----------- + + + +Classes +------- + + + +""" + +# ---- + +__author__ = "Henry R. Winterbottom" +__maintainer__ = "Henry R. Winterbottom" +__email__ = "henry.winterbottom@noaa.gov" +__version__ = 0.0 + +# ---- + +from typing import Dict, Union + +from dataclasses import dataclass + +from pygfs.exceptions import GridsError +from pygw.attrdict import AttrDict + +# ---- + +# Define the supported options for the respective forecast model +# attributes; only GFS configurations are currently supported. +FV3GFS_RES_LIST = ["c48", "c96", "c192", "c384"] +FV3GFS_LEVS_LIST = [64, 128] + +# ---- + + +@dataclass +class Grids: + """ + Description + ----------- + + """ + + def __init__(self: dataclass, model: str, res: Union[str, int]): + """ + Description + ----------- + + Creates a new Grids object. + + """ + + # Define the base-class attributes. + self.grids = AttrDict() + self.model = model.lower() + self.res = str(res) + +# ---- + + +class FV3GFS(Grids): + """ """ + + def __init__(self: Grids, config: Dict, model: str, res: str, + nlevs: int): + """ + Description + ----------- + + Creates a new GFSFV3 object. + + """ + + # Define the base-class attributes. + super().__init__(model=model, res=res) + self.grids.ntiles = 6 + self.grids.nlevs = nlevs + self.grids.csres = self.res + self.grids.res = int(self.res[1:]) + + # Validate and build the grids information attributes. + self.__check() + self.__config() + + def __check(self: Grids): + """ + + """ + # Check that the base-class object containing the FV3 GFS + # forecast model attributes is valid; proceed accordingly. + if self.grids.csres.lower() not in FV3GFS_RES_LIST: + msg = ( + f"The cubed sphere resolution {self.grids.csres.upper()} is not " + "supported; valid values are: " + f"{', '.join([res.upper() for res in FV3GFS_RES_LIST])}. Aborting!!!" + ) + raise GridsError(msg=msg) + + if self.grids.nlevs not in FV3GFS_LEVS_LIST: + msg = (f"The specified number of vertical levels {self.grids.levs}" + f"is not supported; valid values are {','.join(FV3GFS_LEVS_LIST)}. " + "Aborting!!!" + ) + raise GridsError(msg=msg) + + def __config(self: Grids): + """ + Description + ----------- + + This method defines the FV3 GFS atmosphere model grid + configuration attributes. + + """ + + grids_dict = { + "jcap": int(self.grids.res * 2) - 2, + "lonb": int(4*self.grids.res), + "latb": int(2*self.grids.res), + "npx": int(self.grids.res + 1), + "npy": int(self.grids.res + 1), + "npz": int(self.grids.nlevs - 1) + } + + [setattr(self.grids, grids_key, grids_value) + for (grids_key, grids_value) in grids_dict.items()] diff --git a/ush/python/pygfs/utils/logger.py b/ush/python/pygfs/utils/logger.py new file mode 100644 index 00000000000..0b7c94cd903 --- /dev/null +++ b/ush/python/pygfs/utils/logger.py @@ -0,0 +1,34 @@ +from pygw.logger import Logger as pygw_Logger +from pygw.logger import logit as pygw_logit + +from dataclasses import dataclass + +from typing import Dict + +# ---- + + +@dataclass +class Logger: + """ + + """ + + def __init__(self: dataclass, config: Dict, level: str = None): + """ + Description + ----------- + + Creates a new Logger object. + + """ + + # Define the base-class attributes. + if level is None: + if getattr(config, "loglev") is None: + level = "info" + else: + level = config.loglev + + self.logger = pygw_Logger(level=level, colored_log=True) + self.logit = pygw_logit(self.logger) From 6c9797965ae3f8d7cf97764b6ac6b7a3551995ee Mon Sep 17 00:00:00 2001 From: HenryWinterbottom-NOAA Date: Wed, 5 Apr 2023 17:25:43 -0600 Subject: [PATCH 13/31] Bringing branch UTD with WIP. --- parm/ufs/forecast/gfs/model_configure.IN | 25 +-- scripts/exglobal_gfs_forecast_initialize.py | 6 +- ush/python/pygfs/task/fv3gfs_forecast.py | 172 ++++++++++++++++++++ 3 files changed, 190 insertions(+), 13 deletions(-) create mode 100644 ush/python/pygfs/task/fv3gfs_forecast.py diff --git a/parm/ufs/forecast/gfs/model_configure.IN b/parm/ufs/forecast/gfs/model_configure.IN index af850bb26fb..bacb7d1fd2b 100644 --- a/parm/ufs/forecast/gfs/model_configure.IN +++ b/parm/ufs/forecast/gfs/model_configure.IN @@ -1,18 +1,22 @@ -start_year: {{ start_year }} -start_month: {{ start_month }} -start_day: {{ start_day }} -start_hour: {{ start_hour }} -start_minute: {{ start_minute|0 }} -start_second: {{ start_second|0 }} -nhours_fcst: {{ FHMAX }} -fhrot: {{ IAU_FHROT|0 }} +start_year: {{ ufswm.atmos.datetime.year }} +start_month: {{ ufswm.atmos.datetime.month }} +start_day: {{ ufswm.atmos.datetime.day }} +start_hour: {{ ufswm.atmos.datetime.hour }} +start_minute: {{ ufswm.atmos.datetime.minute }} +start_second: {{ ufswm.atmos.datetime.second }} +nhours_fcst {{ FHMAX }} +fhrot: {{ 0 }} dt_atmos: {{ DELTIM }} -calendar: {{ calendar|"julian" }} +calendar: "julian" restart_interval: {{ restart_interval }} output_1st_tstep_rst: .false. -quilting: {{ QUILTING }} +quilting: {{ QUILTING }} ### NEED TO CREATE A MODEL + # THAT TERMS I/O AND + # COMPUTING CONFIGURATIONS + # AS A FUNCTION OF UFS WM + # RESOLUTIONS. write_groups: {{ WRITE_GROUP|1 }} write_tasks_per_group: {{ WRTTASK_PER_GROUP|24 }} itasks: 1 @@ -34,3 +38,4 @@ imo: {{ LONB_IMO }} jmo: {{ LATB_JMO }} output_fh: {{ OUTPUT_FH }} iau_offset: {{ IAU_OFFSET|0 }} + diff --git a/scripts/exglobal_gfs_forecast_initialize.py b/scripts/exglobal_gfs_forecast_initialize.py index 94ed3f2d82b..ec21d6583d6 100644 --- a/scripts/exglobal_gfs_forecast_initialize.py +++ b/scripts/exglobal_gfs_forecast_initialize.py @@ -2,7 +2,7 @@ # ----------------------------------------------------------------------------- # -# Program Name: exglobal_gfs_forecast_init.py +# Program Name: exglobal_fv3gfs_forecast_init.py # # Author(s)/Contacts(s): Henry R. Winterbottom (henry.winterbottom@noaa.gov) # @@ -21,7 +21,7 @@ Script ------ - exglobal_ufs_forecast_init.py + exglobal_fv3gfs_forecast_init.py Description ----------- @@ -63,7 +63,7 @@ from pygw.configuration import cast_strdict_as_dtypedict from pygw.logger import Logger -from pygfs.task.fv3gfs import FV3GFS +from pygfs.task.fv3gfs_forecast import FV3GFS # ---- diff --git a/ush/python/pygfs/task/fv3gfs_forecast.py b/ush/python/pygfs/task/fv3gfs_forecast.py new file mode 100644 index 00000000000..36f407f4f4a --- /dev/null +++ b/ush/python/pygfs/task/fv3gfs_forecast.py @@ -0,0 +1,172 @@ + +import os + +from pygfs.task.forecast import Forecast, GFS_APP_LIST +from pygfs.exceptions import GFSError +from typing import Dict, List +from pygw.logger import Logger, logit + +from pygw.attrdict import AttrDict +from pygw.yaml_file import YAMLFile + +# ---- + +logger = Logger(name="GFS", colored_log=True) + +# ---- + +# The following attributes are mandatory for fixed-file syncing; +# this should not be changed unless absolutely necessary. +FIXED_MAND_ATTR_DICT = {"DATA": "DATA", + "FIXgfs": "FIXgfs", + "HOMEgfs": "HOMEgfs", + "atm_res": "CASE", + "ocn_res": "OCNRES" + } + +# ---- + + +class FV3GFS(Forecast): + """ + + """ + + def __init__(self: Forecast, config: Dict): + """ + Description + ----------- + + Creates a new GFS object. + + """ + + # Define the base-class attributes. + super().__init__(config=config, model="FV3GFS") + + print(self.config.ufswm.atmos.grids) + +# if self.fcst_config.APP.lower() not in GFS_APP_LIST: +# msg = (f"The GFS forecast application {self.config.APP} is not " +# "supported. Aborting!!!" +# ) +# raise GFSError(msg=msg) + + # self.fcst_config.model = "gfs" + # self.fcst_config.ntiles = 6 + # self.fcst_config.fix_path = self.config.FIXgfs + + # HRW: THIS IS ALREADY DEFINED IN THE RUN-TIME ENVIRONMENT; DO + # WE WANT TO KEEP IT HERE AS IT CURRENTLY HAS NOT BEARING BUT + # COULD BE AN ATTRIBUTE COLLECTED FROM A YAML-FORMATTED + # CONFIGURATION FILE IN THE FUTURE? + # self.fcst_config.coupled = (self.fcst_config.app in [ + # "s2s", "s2sw", "s2swa"]) + + def __fixedfiles(self: Forecast) -> Dict: + """ + Description + ----------- + + This method collects the fixed-file attributes from the + run-time environment (e.g., configuration Python dictionary + `config`), checks the validity of the respective files and/or + attributes, and returns a Python dictionary containing the + fixed-file attributes. + + Returns + ------- + + fixedfiles_dict: Dict + + A Python dictionary containing the fixed-file path + attributes. + + Raises + ------ + + GFSError: + + - raised a mandatory configuration attribute (see + `FIXED_MAND_ATTR_DICT`) is not specified within the + run-time environment/configuration. + + - raised if a mandatory directory tree (beneath `FIXgfs`) + is either not a directory or does not exist. + + - raised if an application specific directory tree + (beneath `FIXgfs`) is either not a dictionary or does + not exist. + + """ + + # Define the fixed-file attributes. + fixedfiles_dict = AttrDict() + + for (fixed_attr_key, fixed_attr_value) in FIXED_MAND_ATTR_DICT.items(): + + # Define the respective configuration attribute (i.e., + # `fixed_attr_key`) and assign the corresponding value + # `fixed_attr_value`; proceed accordingly. + value = self.config[fixed_attr_value] + + if isinstance(value, dict): + value = self.runtime_config[fixed_attr_value] + + if value is None: + msg = (f"The configuration attribute {fixed_attr_key} could not " + "be determined from the run-time environment. Aborting!!!" + ) + raise GFSError(msg=msg) + + setattr(self.config, fixed_attr_key, value) + + # Define the top-level directory-tree path containing the + # respective fixed-files and build the Python dictionary + # required to link the application-specific fixed files. + fix_dirpath = self.config.FIXgfs + + if (not os.path.isdir(fix_dirpath)) or (not os.path.exists(fix_dirpath)): + msg = (f"The directory tree {fix_dirpath} is either not a directory " + "or does not exist. Aborting!!!" + ) + raise GFSError(msg=msg) + + # Define the fixed-file directory tree attributes. + fixed_attr_dict = {"FIX_aer": "aer", + "FIX_am": "am", + "FIX_lut": "lut", + "FIX_orog": "orog", + "FIX_lut": "lut", + "FIX_ugwd": "ugwd", + } + + for (fixed_attr_key, fixed_attr_value) in fixed_attr_dict.items(): + + fix_subdirpath = os.path.join(fix_dirpath, fixed_attr_value) + if (not os.path.isdir(fix_subdirpath)) and (not os.path.isfile(fix_subdirpath)): + msg = (f"The directory tree path {fix_subdirpath} is either not " + "a directory does not exist; this may cause unexpected results " + "or failures." + ) + self.logger.warning(msg=msg) + + setattr(self.config, fixed_attr_key, os.path.join( + self.config.FIXgfs, fixed_attr_value)) + + def initialize(self: Forecast): + """ + + """ + + # Update the base-class method attributes. + super().initialize() + + # Build the directory tree and copy the required fixed files + # accordingly. + self.__fixedfiles() + self.config_dirtree() + + # Build the respective UFS configuration files. + self.build_model_configure() + self.build_nems_configure() From b1a8bc08652bc8a1f5a0070aaea8778bf61aa9a6 Mon Sep 17 00:00:00 2001 From: HenryWinterbottom-NOAA Date: Wed, 5 Apr 2023 17:26:02 -0600 Subject: [PATCH 14/31] Renamed file. --- ush/python/pygfs/task/fv3gfs.py | 172 -------------------------------- 1 file changed, 172 deletions(-) delete mode 100644 ush/python/pygfs/task/fv3gfs.py diff --git a/ush/python/pygfs/task/fv3gfs.py b/ush/python/pygfs/task/fv3gfs.py deleted file mode 100644 index 36f407f4f4a..00000000000 --- a/ush/python/pygfs/task/fv3gfs.py +++ /dev/null @@ -1,172 +0,0 @@ - -import os - -from pygfs.task.forecast import Forecast, GFS_APP_LIST -from pygfs.exceptions import GFSError -from typing import Dict, List -from pygw.logger import Logger, logit - -from pygw.attrdict import AttrDict -from pygw.yaml_file import YAMLFile - -# ---- - -logger = Logger(name="GFS", colored_log=True) - -# ---- - -# The following attributes are mandatory for fixed-file syncing; -# this should not be changed unless absolutely necessary. -FIXED_MAND_ATTR_DICT = {"DATA": "DATA", - "FIXgfs": "FIXgfs", - "HOMEgfs": "HOMEgfs", - "atm_res": "CASE", - "ocn_res": "OCNRES" - } - -# ---- - - -class FV3GFS(Forecast): - """ - - """ - - def __init__(self: Forecast, config: Dict): - """ - Description - ----------- - - Creates a new GFS object. - - """ - - # Define the base-class attributes. - super().__init__(config=config, model="FV3GFS") - - print(self.config.ufswm.atmos.grids) - -# if self.fcst_config.APP.lower() not in GFS_APP_LIST: -# msg = (f"The GFS forecast application {self.config.APP} is not " -# "supported. Aborting!!!" -# ) -# raise GFSError(msg=msg) - - # self.fcst_config.model = "gfs" - # self.fcst_config.ntiles = 6 - # self.fcst_config.fix_path = self.config.FIXgfs - - # HRW: THIS IS ALREADY DEFINED IN THE RUN-TIME ENVIRONMENT; DO - # WE WANT TO KEEP IT HERE AS IT CURRENTLY HAS NOT BEARING BUT - # COULD BE AN ATTRIBUTE COLLECTED FROM A YAML-FORMATTED - # CONFIGURATION FILE IN THE FUTURE? - # self.fcst_config.coupled = (self.fcst_config.app in [ - # "s2s", "s2sw", "s2swa"]) - - def __fixedfiles(self: Forecast) -> Dict: - """ - Description - ----------- - - This method collects the fixed-file attributes from the - run-time environment (e.g., configuration Python dictionary - `config`), checks the validity of the respective files and/or - attributes, and returns a Python dictionary containing the - fixed-file attributes. - - Returns - ------- - - fixedfiles_dict: Dict - - A Python dictionary containing the fixed-file path - attributes. - - Raises - ------ - - GFSError: - - - raised a mandatory configuration attribute (see - `FIXED_MAND_ATTR_DICT`) is not specified within the - run-time environment/configuration. - - - raised if a mandatory directory tree (beneath `FIXgfs`) - is either not a directory or does not exist. - - - raised if an application specific directory tree - (beneath `FIXgfs`) is either not a dictionary or does - not exist. - - """ - - # Define the fixed-file attributes. - fixedfiles_dict = AttrDict() - - for (fixed_attr_key, fixed_attr_value) in FIXED_MAND_ATTR_DICT.items(): - - # Define the respective configuration attribute (i.e., - # `fixed_attr_key`) and assign the corresponding value - # `fixed_attr_value`; proceed accordingly. - value = self.config[fixed_attr_value] - - if isinstance(value, dict): - value = self.runtime_config[fixed_attr_value] - - if value is None: - msg = (f"The configuration attribute {fixed_attr_key} could not " - "be determined from the run-time environment. Aborting!!!" - ) - raise GFSError(msg=msg) - - setattr(self.config, fixed_attr_key, value) - - # Define the top-level directory-tree path containing the - # respective fixed-files and build the Python dictionary - # required to link the application-specific fixed files. - fix_dirpath = self.config.FIXgfs - - if (not os.path.isdir(fix_dirpath)) or (not os.path.exists(fix_dirpath)): - msg = (f"The directory tree {fix_dirpath} is either not a directory " - "or does not exist. Aborting!!!" - ) - raise GFSError(msg=msg) - - # Define the fixed-file directory tree attributes. - fixed_attr_dict = {"FIX_aer": "aer", - "FIX_am": "am", - "FIX_lut": "lut", - "FIX_orog": "orog", - "FIX_lut": "lut", - "FIX_ugwd": "ugwd", - } - - for (fixed_attr_key, fixed_attr_value) in fixed_attr_dict.items(): - - fix_subdirpath = os.path.join(fix_dirpath, fixed_attr_value) - if (not os.path.isdir(fix_subdirpath)) and (not os.path.isfile(fix_subdirpath)): - msg = (f"The directory tree path {fix_subdirpath} is either not " - "a directory does not exist; this may cause unexpected results " - "or failures." - ) - self.logger.warning(msg=msg) - - setattr(self.config, fixed_attr_key, os.path.join( - self.config.FIXgfs, fixed_attr_value)) - - def initialize(self: Forecast): - """ - - """ - - # Update the base-class method attributes. - super().initialize() - - # Build the directory tree and copy the required fixed files - # accordingly. - self.__fixedfiles() - self.config_dirtree() - - # Build the respective UFS configuration files. - self.build_model_configure() - self.build_nems_configure() From 4bf809bece586e1a757f48a9cf0838747010db29 Mon Sep 17 00:00:00 2001 From: HenryWinterbottom-NOAA Date: Wed, 5 Apr 2023 17:26:48 -0600 Subject: [PATCH 15/31] Renamed file. --- .../exglobal_fv3gfs_forecast_initialize.py | 106 ++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 scripts/exglobal_fv3gfs_forecast_initialize.py diff --git a/scripts/exglobal_fv3gfs_forecast_initialize.py b/scripts/exglobal_fv3gfs_forecast_initialize.py new file mode 100644 index 00000000000..ec21d6583d6 --- /dev/null +++ b/scripts/exglobal_fv3gfs_forecast_initialize.py @@ -0,0 +1,106 @@ +#! /usr/env/bin python + +# ----------------------------------------------------------------------------- +# +# Program Name: exglobal_fv3gfs_forecast_init.py +# +# Author(s)/Contacts(s): Henry R. Winterbottom (henry.winterbottom@noaa.gov) +# +# Abstract: A Python 3.5+ script to initialize a global UFS +# forecast application. +# +# History Log: +# +# - 2023-03-28: Henry R. Winterbottom -- Original version. +# +# Usage: user@host:$ python exglobal_ufs_forecast_init.py +# +# ----------------------------------------------------------------------------- + +""" +Script +------ + + exglobal_fv3gfs_forecast_init.py + +Description +----------- + + This script contains a task level interface for the global UFS + forecast initialization application. + +Functions +--------- + + main() + + This is the driver-level function to invoke the tasks within + this script. + +Author(s) +--------- + + + Henry R. Winterbottom; 28 March 2023 + +History +------- + + 2023-03-28: Henry Winterbottom -- Initial implementation. + +""" + +# ---- + +__author__ = "Henry R. Winterbottom" +__maintainer__ = "Henry R. Winterbottom" +__email__ = "henry.winterbottom@noaa.gov" + +# ---- + +import os +import time + +from pygw.configuration import cast_strdict_as_dtypedict +from pygw.logger import Logger +from pygfs.task.fv3gfs_forecast import FV3GFS + +# ---- + +logger = Logger() + +# ---- + + +def main() -> None: + """ + Description + ----------- + + This is the driver-level function to invoke the tasks within this + script. + + """ + + # Take configuration from environment and cast it as Python + # dictionary. + script_name = os.path.basename(__file__) + start_time = time.time() + config = cast_strdict_as_dtypedict(os.environ) + + # Launch the task. + task = FV3GFS(config=config) + task.initialize() + + stop_time = time.time() + msg = f"Completed application {script_name}." + logger.info(msg=msg) + total_time = stop_time - start_time + msg = f"Total Elapsed Time: {total_time} seconds." + logger.info(msg=msg) + +# ---- + + +if __name__ == "__main__": + main() From 0e715aa020d3d24f7b1f5a858e687e43226fb238 Mon Sep 17 00:00:00 2001 From: HenryWinterbottom-NOAA Date: Wed, 5 Apr 2023 17:29:31 -0600 Subject: [PATCH 16/31] Renaming. --- jobs/JGLOBAL_FORECAST_GFS_INITIALIZE | 2 +- jobs/rocoto/{gfs_init.sh => fv3gfs_init.sh} | 0 scripts/exglobal_gfs_forecast_initialize.py | 106 -------------------- 3 files changed, 1 insertion(+), 107 deletions(-) rename jobs/rocoto/{gfs_init.sh => fv3gfs_init.sh} (100%) delete mode 100644 scripts/exglobal_gfs_forecast_initialize.py diff --git a/jobs/JGLOBAL_FORECAST_GFS_INITIALIZE b/jobs/JGLOBAL_FORECAST_GFS_INITIALIZE index 46cab5f5b31..e69f1c843cf 100755 --- a/jobs/JGLOBAL_FORECAST_GFS_INITIALIZE +++ b/jobs/JGLOBAL_FORECAST_GFS_INITIALIZE @@ -36,7 +36,7 @@ fi ############################################################### # Run relevant exglobal script -$(which python) ${HOMEgfs}/scripts/exglobal_gfs_forecast_initialize.py +$(which python) ${HOMEgfs}/scripts/exglobal_fv3gfs_forecast_initialize.py exit 9999 diff --git a/jobs/rocoto/gfs_init.sh b/jobs/rocoto/fv3gfs_init.sh similarity index 100% rename from jobs/rocoto/gfs_init.sh rename to jobs/rocoto/fv3gfs_init.sh diff --git a/scripts/exglobal_gfs_forecast_initialize.py b/scripts/exglobal_gfs_forecast_initialize.py deleted file mode 100644 index ec21d6583d6..00000000000 --- a/scripts/exglobal_gfs_forecast_initialize.py +++ /dev/null @@ -1,106 +0,0 @@ -#! /usr/env/bin python - -# ----------------------------------------------------------------------------- -# -# Program Name: exglobal_fv3gfs_forecast_init.py -# -# Author(s)/Contacts(s): Henry R. Winterbottom (henry.winterbottom@noaa.gov) -# -# Abstract: A Python 3.5+ script to initialize a global UFS -# forecast application. -# -# History Log: -# -# - 2023-03-28: Henry R. Winterbottom -- Original version. -# -# Usage: user@host:$ python exglobal_ufs_forecast_init.py -# -# ----------------------------------------------------------------------------- - -""" -Script ------- - - exglobal_fv3gfs_forecast_init.py - -Description ------------ - - This script contains a task level interface for the global UFS - forecast initialization application. - -Functions ---------- - - main() - - This is the driver-level function to invoke the tasks within - this script. - -Author(s) ---------- - - - Henry R. Winterbottom; 28 March 2023 - -History -------- - - 2023-03-28: Henry Winterbottom -- Initial implementation. - -""" - -# ---- - -__author__ = "Henry R. Winterbottom" -__maintainer__ = "Henry R. Winterbottom" -__email__ = "henry.winterbottom@noaa.gov" - -# ---- - -import os -import time - -from pygw.configuration import cast_strdict_as_dtypedict -from pygw.logger import Logger -from pygfs.task.fv3gfs_forecast import FV3GFS - -# ---- - -logger = Logger() - -# ---- - - -def main() -> None: - """ - Description - ----------- - - This is the driver-level function to invoke the tasks within this - script. - - """ - - # Take configuration from environment and cast it as Python - # dictionary. - script_name = os.path.basename(__file__) - start_time = time.time() - config = cast_strdict_as_dtypedict(os.environ) - - # Launch the task. - task = FV3GFS(config=config) - task.initialize() - - stop_time = time.time() - msg = f"Completed application {script_name}." - logger.info(msg=msg) - total_time = stop_time - start_time - msg = f"Total Elapsed Time: {total_time} seconds." - logger.info(msg=msg) - -# ---- - - -if __name__ == "__main__": - main() From 7a063c1ecce56689db077e255f89ea6a69b2bb8d Mon Sep 17 00:00:00 2001 From: HenryWinterbottom-NOAA Date: Thu, 6 Apr 2023 14:20:25 -0600 Subject: [PATCH 17/31] Formatting update. --- ush/python/pygfs/ufswm.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/ush/python/pygfs/ufswm.py b/ush/python/pygfs/ufswm.py index 94b6496209a..03d1d912b53 100644 --- a/ush/python/pygfs/ufswm.py +++ b/ush/python/pygfs/ufswm.py @@ -32,8 +32,11 @@ from dataclasses import dataclass from typing import Dict +from pygw.decorators import private + from pygfs.utils.datetime import DateTime -from pygfs.utils.grids import FV3GFS +from pygfs.utils.grids import FV3GFS as FV3GFS_grids +from pygfs.utils.layout import FV3GFS as FV3GFS_layout from pygfs.utils.logger import Logger # ---- @@ -76,7 +79,8 @@ def __init__(self: dataclass, config: Dict, model: str): # attributes. self.configure() - def __fv3gfs(self: dataclass) -> None: + @private + def fv3gfs(self: dataclass) -> None: """ Description ----------- @@ -88,7 +92,7 @@ def __fv3gfs(self: dataclass) -> None: # Define the configuration attributes for the FV3 GFS forecast # model. - self.config.ufswm.atmos.grids = FV3GFS( + self.config.ufswm.atmos.grids = FV3GFS_grids( config=self.config, model="FV3GFS", res=self.config.CASE, @@ -110,6 +114,11 @@ def __fv3gfs(self: dataclass) -> None: ) self.logger.warn(msg=msg) + # Define the layout attribuites for the FV3 GFS forecast + # model. + self.config.ufswm.atmos.layout = FV3GFS_layout( + config=self.config, res=self.config.CASE).setup() + def configure(self: dataclass) -> None: """ Description @@ -122,4 +131,4 @@ def configure(self: dataclass) -> None: # UFS WM atmosphere FV3 GFS forecast model. if self.model == "fv3gfs": - self.__fv3gfs() + self.fv3gfs() From ce256dd0bd0c49c35e6735cdc33bf7367a7630b9 Mon Sep 17 00:00:00 2001 From: HenryWinterbottom-NOAA Date: Thu, 6 Apr 2023 14:50:16 -0600 Subject: [PATCH 18/31] Updates throughout. --- ush/python/pygfs/utils/timestamps.py | 195 +++++++++++++++++++++++++++ 1 file changed, 195 insertions(+) create mode 100644 ush/python/pygfs/utils/timestamps.py diff --git a/ush/python/pygfs/utils/timestamps.py b/ush/python/pygfs/utils/timestamps.py new file mode 100644 index 00000000000..276a936ffc9 --- /dev/null +++ b/ush/python/pygfs/utils/timestamps.py @@ -0,0 +1,195 @@ +""" +Module +------ + + pygfs.utils.timestamps (pygfs/utils/timestamps.py) + +Description +----------- + + This module contains the base-class module for all time-stamp + attributes definitions and derivations relative to a specified + time-stamp and optional formatting. + +Classes +------- + + Timestamps(datestr, fmt="%Y-%m-%d %H:%M:%S") + + This is the base-class object for all time-stamp attribute + definitions. + +""" + +# ---- + +__author__ = "Henry R. Winterbottom" +__maintainer__ = "Henry R. Winterbottom" +__email__ = "henry.winterbottom@noaa.gov" +__version__ = 0.0 + +# ---- + +import datetime +import sqlite3 +from dataclasses import dataclass + +from pygfs.exceptions import TimestampsError +from pygw.attrdict import AttrDict +from pygw.decorators import private +from pygw.timetools import strftime, strptime + +# ---- + + +@dataclass +class Timestamps: + """ + Description + ----------- + + This is the base-class object for all time-stamp attribute + definitions. + + Parameters + ---------- + + datestr: str + + A Python string specifying the the time-stamp; if the + parameter `fmt` is not specified upon entry, the module + assumes that the time-stamp is formatted as "%Y-%m-%d + %H:%M:%S". + + Keywords + -------- + + fmt: str + + A Python string specifying the POSIX-format for the `datestr` + parameter upon entry. + + Raises + ------ + + TimestampsError: + + - raised if an exception is encountered while discerning the + format of the attribute `datestr` upon entry. + + - raised if an exception is encountered while defining the + derived time-stamp attributes. + + """ + + def __init__(self: dataclass, datestr: str, fmt: str = "%Y-%m-%d %H:%M:%S"): + """ + Description + ----------- + + Creates a new Timestamps object. + + """ + + # Define the base-class attributes. + self.timestamps = AttrDict() + + # Define the time-stamp attributes relative to the application + # initialization time; enforce input attribute `datestr` to be + # Python type string. + try: + time_str = strptime(dtstr=str(datestr), fmt=fmt) + + except Exception as errmsg: + msg = ( + f"Initializing the attributes for input timestamp {datestr} " + f"failed with error {errmsg}. Aborting!!!" + ) + raise TimestampsError(msg=msg) from errmsg + + time_attr_dict = { + "year": "%Y", + "month": "%m", + "day": "%d", + "hour": "%H", + "minute": "%M", + "second": "%S", + "month_name_long": "%B", + "month_name_short": "%b", + "century_short": "%C", + "year_short": "%y", + "weekday_long": "%A", + "weekday_short": "%a", + "day_of_year": "%j", + "day_of_week": "%u", + "timezone": "%Z", + "week_of_year": "%W", + } + + for (time_attr, time_attr_value) in time_attr_dict.items(): + value = strftime(dt=time_str, fmt=time_attr_value) + try: + setattr(self.timestamps, time_attr, int(value)) + except ValueError: + setattr(self.timestamps, time_attr, value) + + # Define the derived time-stamp attributes; proceed + # accordingly. + try: + self.julianday() + self.epoch() + + except Exception as errmsg: + msg = ( + "Defining timestamp attributes failed with error " + f"{errmsg}. Aborting!!!" + ) + raise TimestampsError(msg=msg) from errmsg + + @private + def epoch(self: dataclass): + """ + Description + ----------- + + This method defines the epoch time relative to the respective + timestamp specified upon entry. + + """ + + # Define the epoch time (i.e., number of seconds since 0000 + # UTC 01 January 1970). + self.timestamps.epoch = datetime.datetime( + int(self.timestamps.year), + int(self.timestamps.month), + int(self.timestamps.day), + int(self.timestamps.hour), + int(self.timestamps.minute), + int(self.timestamps.second), + ).timestamp() + + @private + def julianday(self: dataclass): + """ + Description + ----------- + + This method define the Julian day relative to the respective + timestamp specified upon entry. + + """ + + # Define the Julian day. + connect = sqlite3.connect(":memory:") + datestr = ( + f"{self.timestamps.year}-" + f"{self.timestamps.month}-" + f"{self.timestamps.day} " + f"{self.timestamps.hour}:" + f"{self.timestamps.minute}:" + f"{self.timestamps.second}" + ) + + self.timestamps.julian_day = list( + connect.execute(f"select julianday('{datestr}')") + )[0][0] From 3a899db4aaaea9ad9725217b9541eea24a03368c Mon Sep 17 00:00:00 2001 From: HenryWinterbottom-NOAA Date: Thu, 6 Apr 2023 14:50:39 -0600 Subject: [PATCH 19/31] Updates for renamed dependencies. --- ush/python/pygfs/ufswm.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/ush/python/pygfs/ufswm.py b/ush/python/pygfs/ufswm.py index 03d1d912b53..63f41a33b62 100644 --- a/ush/python/pygfs/ufswm.py +++ b/ush/python/pygfs/ufswm.py @@ -34,10 +34,10 @@ from pygw.decorators import private -from pygfs.utils.datetime import DateTime from pygfs.utils.grids import FV3GFS as FV3GFS_grids from pygfs.utils.layout import FV3GFS as FV3GFS_layout from pygfs.utils.logger import Logger +from pygfs.utils.timestamps import Timestamps # ---- @@ -102,9 +102,9 @@ def fv3gfs(self: dataclass) -> None: # HRW: The following may eventually be moved out of this # method into `configure`; this is dependent on the additional # UFS component model needs; TBD. - self.config.ufswm.atmos.datetime = DateTime( + self.config.ufswm.atmos.timestamps = Timestamps( datestr=self.config.CDATE, fmt="%Y-%m-%d %H:%M:%S" - ).datetime + ).timestamps msg = ( "\nThe atmosphere model configuration is as follows:\n\n" @@ -117,7 +117,8 @@ def fv3gfs(self: dataclass) -> None: # Define the layout attribuites for the FV3 GFS forecast # model. self.config.ufswm.atmos.layout = FV3GFS_layout( - config=self.config, res=self.config.CASE).setup() + config=self.config, res=self.config.CASE + ).setup() def configure(self: dataclass) -> None: """ From f533cc341d484e64dd6036f699c8c26753ff366d Mon Sep 17 00:00:00 2001 From: HenryWinterbottom-NOAA Date: Thu, 6 Apr 2023 15:50:18 -0600 Subject: [PATCH 20/31] Layouts module. --- ush/python/pygfs/utils/layout.py | 182 +++++++++++++++++++++++++++++++ 1 file changed, 182 insertions(+) create mode 100644 ush/python/pygfs/utils/layout.py diff --git a/ush/python/pygfs/utils/layout.py b/ush/python/pygfs/utils/layout.py new file mode 100644 index 00000000000..aca176a4f6f --- /dev/null +++ b/ush/python/pygfs/utils/layout.py @@ -0,0 +1,182 @@ +""" +Module +------ + + pygfs.utils.layout (pygfs/utils/layout.py) + +Description +----------- + + This module contains the base-class objects for all Unified + Forecast System (UFS) Weather Model (WM) component model layout + attributes. + +Classes +------- + + Layout() + + This is the base-class object for all Unified Forecast System + (UFS) Weather Model (WM) configuration file attributes. + + FV3GFS(config, res) + + This is the base-class object for defining all FV3 GFS layout + attributes as a function of cubed-sphere resolution; it is a + sub-class of Layout. + +""" + +# ---- + +__author__ = "Henry R. Winterbottom" +__maintainer__ = "Henry R. Winterbottom" +__email__ = "henry.winterbottom@noaa.gov" +__version__ = 0.0 + +# ---- + +from dataclasses import dataclass +from typing import Dict + +from pygw.attrdict import AttrDict +from pygw.decorators import private + +# ---- + + +@dataclass +class Layout: + """ + Description + ----------- + + This is the base-class object for all Unified Forecast System + (UFS) Weather Model (WM) configuration file attributes. + + """ + + def __init__(self: dataclass): + """ + Description + ----------- + + Creates a new Layout object. + + """ + + # Define the base-class attributes. + self.layout = AttrDict() + + +# ---- + + +class FV3GFS(Layout): + """ + Description + ----------- + + This is the base-class object for defining all FV3 GFS layout + attributes as a function of cubed-sphere resolution; it is a + sub-class of Layout. + + Parameters + ---------- + + config: Dict + + A Python dictionary containing the run-time environment + configuration. + + res: str + + A Python string specifying a valid cubed-sphere resolution + (e.g., C48, C96, etc.,) + + """ + + def __init__(self: Layout, config: Dict, res: str): + """ + Description + ----------- + + Creates a new FV3GFS object. + + """ + + # Define the base-class attributes. + super().__init__() + self.config = config + self.csres = res.lower() + self.res = int(self.csres[1:]) + + @private + def c48(self: Layout) -> Dict: + """ + Description + ----------- + + This method defines the FV3 GFS layout attributes for a C48 + cubed-sphere resolution. + + Returns + ------- + + layout_dict: Dict + + A Python dictionary containing the layout attributes for + the respective FV3 GFS cubed-sphere resolution. + + """ + + # Define the FV3GFS C48 cubed-sphere resolution attributes. + layout_dict = { + "deltim": 1200, + "layout_x": 1, + "layout_y": 1, + "layout_x_gfs": 1, + "layout_y_gfs": 1, + "nthreads_fv3": 1, + "nthreads_fv3_gfs": 1, + "cdmbgwd": [0.071, 2.1, 1.0, 1.0], + "write_group": 1, + "wrttask_per_group": 2, + "write_group_gfs": 1, + "wrttask_per_group_gfs": 2, + "quilting": ".false.", + } + + return layout_dict + + def setup(self: Layout): + """ + Description + ----------- + + This method defines the FV3 GFS cubed-sphere resolution layout + attributes. + + """ + + # Define the FV3 GFS layout attributes as a function of the + # cubed-sphere resolution. + layout_methods_dict = {"c48": self.c48} + + layout_dict = layout_methods_dict[self.csres]() + + # Compute additional FV3 GFS layout attributes as a function + # of the cubed-sphere resolution. + layout_chunks_dict = { + "ichunk2d": (4 * self.res), + "ichunk3d": (4 * self.res), + "jchunk2d": (2 * self.res), + "jchunk3d": (2 * self.res), + "kchunk3d": 1, + } + + layout_dict = {**layout_dict, **layout_chunks_dict} + + # Update the base-class Python dictionary. + for (layout_key, layout_value) in layout_dict.items(): + setattr(self.layout, layout_key, layout_value) From a72918762c497fd8b1732e7e734eb839f9c7b953 Mon Sep 17 00:00:00 2001 From: HenryWinterbottom-NOAA Date: Thu, 6 Apr 2023 15:50:47 -0600 Subject: [PATCH 21/31] Renamed module. --- ush/python/pygfs/utils/datetime.py | 192 ----------------------------- 1 file changed, 192 deletions(-) delete mode 100644 ush/python/pygfs/utils/datetime.py diff --git a/ush/python/pygfs/utils/datetime.py b/ush/python/pygfs/utils/datetime.py deleted file mode 100644 index 055fc7f14b4..00000000000 --- a/ush/python/pygfs/utils/datetime.py +++ /dev/null @@ -1,192 +0,0 @@ -""" -Module ------- - - pygfs.utils.datetime (pygfs/utils/datetime.py) - -Description ------------ - - This module contains the base-class module for all time-stamp - attributes definitions and derivations relative to a specified - time-stamp and optional formatting. - -Classes -------- - - DateTime(datestr, fmt="%Y-%m-%d %H:%M:%S") - - This is the base-class object for all time-stamp attribute - definitions. - -""" - -# ---- - -__author__ = "Henry R. Winterbottom" -__maintainer__ = "Henry R. Winterbottom" -__email__ = "henry.winterbottom@noaa.gov" -__version__ = 0.0 - -# ---- - -import datetime -import sqlite3 -from dataclasses import dataclass - -from pygfs.exceptions import DateTimeError -from pygw.attrdict import AttrDict -from pygw.timetools import strftime, strptime - -# ---- - - -@dataclass -class DateTime: - """ - Description - ----------- - - This is the base-class object for all time-stamp attribute - definitions. - - Parameters - ---------- - - datestr: str - - A Python string specifying the the time-stamp; if the - parameter `fmt` is not specified upon entry, the module - assumes that the time-stamp is formatted as "%Y-%m-%d - %H:%M:%S". - - Keywords - -------- - - fmt: str - - A Python string specifying the POSIX-format for the `datestr` - parameter upon entry. - - Raises - ------ - - DateTimeError: - - - raised if an exception is encountered while discerning the - format of the attribute `datestr` upon entry. - - - raised if an exception is encountered while defining the - derived time-stamp attributes. - - """ - - def __init__(self: dataclass, datestr: str, fmt: str = "%Y-%m-%d %H:%M:%S"): - """ - Description - ----------- - - Creates a new DateTime object. - - """ - - # Define the base-class attributes. - self.datetime = AttrDict() - - # Define the time-stamp attributes relative to the application - # initialization time; enforce input attribute `datestr` to be - # Python type string. - try: - time_str = strptime(dtstr=str(datestr), fmt=fmt) - - except Exception as errmsg: - msg = ( - f"Initializing the attributes for input timestamp {datestr} " - f"failed with error {errmsg}. Aborting!!!" - ) - raise DateTimeError(msg=msg) from errmsg - - time_attr_dict = { - "year": "%Y", - "month": "%m", - "day": "%d", - "hour": "%H", - "minute": "%M", - "second": "%S", - "month_name_long": "%B", - "month_name_short": "%b", - "century_short": "%C", - "year_short": "%y", - "weekday_long": "%A", - "weekday_short": "%a", - "day_of_year": "%j", - "day_of_week": "%u", - "timezone": "%Z", - "week_of_year": "%W", - } - - for (time_attr, time_attr_value) in time_attr_dict.items(): - value = strftime(dt=time_str, fmt=time_attr_value) - try: - setattr(self.datetime, time_attr, int(value)) - except ValueError: - setattr(self.datetime, time_attr, value) - - # Define the derived time-stamp attributes; proceed - # accordingly. - try: - self.__julianday() - self.__epoch() - - except Exception as errmsg: - msg = ( - "Defining timestamp attributes failed with error " - f"{errmsg}. Aborting!!!" - ) - raise DateTimeError(msg=msg) from errmsg - - def __epoch(self: dataclass): - """ - Description - ----------- - - This method defines the epoch time relative to the respective - timestamp specified upon entry. - - """ - - # Define the epoch time (i.e., number of seconds since 0000 - # UTC 01 January 1970). - self.datetime.epoch = datetime.datetime( - int(self.datetime.year), - int(self.datetime.month), - int(self.datetime.day), - int(self.datetime.hour), - int(self.datetime.minute), - int(self.datetime.second), - ).timestamp() - - def __julianday(self: dataclass): - """ - Description - ----------- - - This method define the Julian day relative to the respective - timestamp specified upon entry. - - """ - - # Define the Julian day. - connect = sqlite3.connect(":memory:") - datestr = ( - f"{self.datetime.year}-" - f"{self.datetime.month}-" - f"{self.datetime.day} " - f"{self.datetime.hour}:" - f"{self.datetime.minute}:" - f"{self.datetime.second}" - ) - - self.datetime.julian_day = list( - connect.execute(f"select julianday('{datestr}')") - )[0][0] From 77dbeac3f50bf917ab6862b551b1b8801f6d850c Mon Sep 17 00:00:00 2001 From: HenryWinterbottom-NOAA Date: Thu, 6 Apr 2023 15:51:07 -0600 Subject: [PATCH 22/31] Docstring updates. --- ush/python/pygfs/utils/grids.py | 105 ++++++++++++++++++++++++-------- 1 file changed, 81 insertions(+), 24 deletions(-) diff --git a/ush/python/pygfs/utils/grids.py b/ush/python/pygfs/utils/grids.py index d912b0dfe06..534c9e7e645 100644 --- a/ush/python/pygfs/utils/grids.py +++ b/ush/python/pygfs/utils/grids.py @@ -7,12 +7,24 @@ Description ----------- - + This module contains the base-class object for all grid definition + applications; the module also contains sub-classes for the + respective, and supported, Unified Forecast System (UFS) Weather + Model (WM) component models. Classes ------- + FV3GFS(config, res, nlevs) + + This is the base-class object for all FV3 GFS grid attribute + defintions as a function grid-spacing resolution; it is a + sub-class of Grids. + Grids() + + This is the base-class object for all forecast model grid + attribute computations and definitions. """ @@ -31,13 +43,7 @@ from pygfs.exceptions import GridsError from pygw.attrdict import AttrDict - -# ---- - -# Define the supported options for the respective forecast model -# attributes; only GFS configurations are currently supported. -FV3GFS_RES_LIST = ["c48", "c96", "c192", "c384"] -FV3GFS_LEVS_LIST = [64, 128] +from pygw.decorators import private # ---- @@ -48,9 +54,12 @@ class Grids: Description ----------- + This is the base-class object for all forecast model grid + attribute computations and definitions. + """ - def __init__(self: dataclass, model: str, res: Union[str, int]): + def __init__(self: dataclass): """ Description ----------- @@ -61,17 +70,45 @@ def __init__(self: dataclass, model: str, res: Union[str, int]): # Define the base-class attributes. self.grids = AttrDict() - self.model = model.lower() - self.res = str(res) # ---- class FV3GFS(Grids): - """ """ + """ + Description + ----------- + + This is the base-class object for all FV3 GFS grid attribute + defintions as a function grid-spacing resolution; it is a + sub-class of Grids. + + Parameters + ---------- + + config: Dict + + A Python dictionary containing the run-time environment + configuration. - def __init__(self: Grids, config: Dict, model: str, res: str, - nlevs: int): + res: str + + A Python string specifying a valid cubed-sphere resolution + (e.g., C48, C96, etc.,) + + nlevs: int + + A Python integer specifying a valid number of staggered + vertical levels. + + """ + + # Define the supported options for the respective forecast model + # attributes; only GFS configurations are currently supported. + FV3GFS_RES_LIST = ["c48", "c96", "c192", "c384"] + FV3GFS_LEVS_LIST = [64, 128] + + def __init__(self: Grids, config: Dict, res: str, nlevs: int): """ Description ----------- @@ -81,38 +118,56 @@ def __init__(self: Grids, config: Dict, model: str, res: str, """ # Define the base-class attributes. - super().__init__(model=model, res=res) + super().__init__() self.grids.ntiles = 6 self.grids.nlevs = nlevs - self.grids.csres = self.res - self.grids.res = int(self.res[1:]) + self.grids.csres = res + self.grids.res = int(self.grids.csres[1:]) # Validate and build the grids information attributes. - self.__check() - self.__config() + self.check() + self.config() - def __check(self: Grids): + @private + def check(self: Grids): """ + Description + ----------- + + This method checks the validity of both the cubed-sphere + resolution and total number of staggered vertical levels. + + Raises + ------ + + GridsError: + + - raised if the specified cubed-sphere resolution is not a + valid cubed-sphere resolution. + + - raised if the number of staggered vertical levels is not + supported. """ # Check that the base-class object containing the FV3 GFS # forecast model attributes is valid; proceed accordingly. - if self.grids.csres.lower() not in FV3GFS_RES_LIST: + if self.grids.csres.lower() not in self.FV3GFS_RES_LIST: msg = ( - f"The cubed sphere resolution {self.grids.csres.upper()} is not " + f"The cubed-sphere resolution {self.grids.csres.upper()} is not " "supported; valid values are: " f"{', '.join([res.upper() for res in FV3GFS_RES_LIST])}. Aborting!!!" ) raise GridsError(msg=msg) - if self.grids.nlevs not in FV3GFS_LEVS_LIST: + if self.grids.nlevs not in self.FV3GFS_LEVS_LIST: msg = (f"The specified number of vertical levels {self.grids.levs}" f"is not supported; valid values are {','.join(FV3GFS_LEVS_LIST)}. " "Aborting!!!" ) raise GridsError(msg=msg) - def __config(self: Grids): + @private + def config(self: Grids): """ Description ----------- @@ -122,6 +177,8 @@ def __config(self: Grids): """ + # Compute and define the grid attributes using the resolution + # of the cubed-sphere. grids_dict = { "jcap": int(self.grids.res * 2) - 2, "lonb": int(4*self.grids.res), From 00d8e1cba64501400d68b3b25a178c814e1b824a Mon Sep 17 00:00:00 2001 From: HenryWinterbottom-NOAA Date: Thu, 6 Apr 2023 15:51:43 -0600 Subject: [PATCH 23/31] Updated/added exception sub-classes. --- ush/python/pygfs/exceptions.py | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/ush/python/pygfs/exceptions.py b/ush/python/pygfs/exceptions.py index 8a6d004f97c..29a7a601391 100644 --- a/ush/python/pygfs/exceptions.py +++ b/ush/python/pygfs/exceptions.py @@ -12,12 +12,6 @@ Classes ------- - DateTimeError(msg) - - This is the base-class for exceptions encountered within the - ush/python/pygfs/utils/datetime module; it is a sub-class of - WorkflowException. - ForecastError(msg) This is the base-class for exceptions encountered within the @@ -36,6 +30,12 @@ ush/python/pygfs/utils/grids module; it is a sub-class of WorkflowException. + TimestampsError(msg) + + This is the base-class for exceptions encountered within the + ush/python/pygfs/utils/timestamps module; it is a sub-class of + WorkflowException. + UFSWMError(msg) This is the base-class for exceptions encountered within the @@ -58,19 +58,19 @@ # ---- # Define all available classes. -__all__ = ["DateTimeError", "ForecastError", "GFSError", "GridsInfoError", +__all__ = ["ForecastError", "GFSError", "GridsInfoError", "TimestampsError", "UFSWMError"] # ---- -class DateTimeError(WorkflowException): +class ForecastError(WorkflowException): """ Description ----------- This is the base-class for exceptions encountered within the - ush/python/pygfs/utils/datetime module; it is a sub-class of + ush/python/pygfs/task/forecast module; it is a sub-class of WorkflowException. """ @@ -78,13 +78,13 @@ class DateTimeError(WorkflowException): # ---- -class ForecastError(WorkflowException): +class GFSError(WorkflowException): """ Description ----------- This is the base-class for exceptions encountered within the - ush/python/pygfs/task/forecast module; it is a sub-class of + ush/python/pygfs/task/gfs module; it is a sub-class of WorkflowException. """ @@ -92,13 +92,13 @@ class ForecastError(WorkflowException): # ---- -class GFSError(WorkflowException): +class GridsError(WorkflowException): """ Description ----------- This is the base-class for exceptions encountered within the - ush/python/pygfs/task/gfs module; it is a sub-class of + ush/python/pygfs/utils/grids module; it is a sub-class of WorkflowException. """ @@ -106,18 +106,17 @@ class GFSError(WorkflowException): # ---- -class GridsError(WorkflowException): +class TimestampsError(WorkflowException): """ Description ----------- This is the base-class for exceptions encountered within the - ush/python/pygfs/utils/grids module; it is a sub-class of + ush/python/pygfs/utils/timestamps module; it is a sub-class of WorkflowException. """ - # ---- From 9fc5907d91c52e3e3b94a1e8d5b781bce2d787b3 Mon Sep 17 00:00:00 2001 From: HenryWinterbottom-NOAA Date: Thu, 6 Apr 2023 15:52:20 -0600 Subject: [PATCH 24/31] Added a module for decorators; this may be temporary. --- ush/python/pygw/src/pygw/decorators.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 ush/python/pygw/src/pygw/decorators.py diff --git a/ush/python/pygw/src/pygw/decorators.py b/ush/python/pygw/src/pygw/decorators.py new file mode 100644 index 00000000000..8e4fbd2eca6 --- /dev/null +++ b/ush/python/pygw/src/pygw/decorators.py @@ -0,0 +1,13 @@ +import sys +import functools + + +def private(member): + @functools.wraps(member) + def wrapper(*function_args): + myself = member.__name__ + caller = sys._getframe(1).f_code.co_name + if (not caller in dir(function_args[0]) and not caller is myself): + raise Exception("%s called by %s is private" % (myself, caller)) + return member(*function_args) + return wrapper From c92a5a471b12c377463c3c10bd71b6edddda7ec6 Mon Sep 17 00:00:00 2001 From: HenryWinterbottom-NOAA Date: Thu, 6 Apr 2023 15:53:03 -0600 Subject: [PATCH 25/31] Cosmetic updates. --- ush/python/pygfs/task/forecast.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ush/python/pygfs/task/forecast.py b/ush/python/pygfs/task/forecast.py index 47ae4fcc3e7..fab8e89329e 100644 --- a/ush/python/pygfs/task/forecast.py +++ b/ush/python/pygfs/task/forecast.py @@ -149,7 +149,7 @@ def config_dirtree(self: Task) -> None: path=dirtree_config.fixed_files.land, data=self.config) FileHandler(land_fcst_config).sync() - if self.config.coupled: # HRW: THIS NEEDS TO BE UPDATED. + if self.config.coupled: # HRW: THIS NEEDS TO BE UPDATED. FileHandler(dirtree_config.dirtree_ocean).sync() ocean_fcst_config = parse_yamltmpl( path=dirtree_config.fixed_files.ocean, data=self.config) From 6fa2423df800ef808bae402d9a0bf929b6b6aeb4 Mon Sep 17 00:00:00 2001 From: HenryWinterbottom-NOAA Date: Thu, 6 Apr 2023 15:53:47 -0600 Subject: [PATCH 26/31] Class constructor instance update. --- ush/python/pygfs/ufswm.py | 1 - 1 file changed, 1 deletion(-) diff --git a/ush/python/pygfs/ufswm.py b/ush/python/pygfs/ufswm.py index 63f41a33b62..b2c7248f35f 100644 --- a/ush/python/pygfs/ufswm.py +++ b/ush/python/pygfs/ufswm.py @@ -94,7 +94,6 @@ def fv3gfs(self: dataclass) -> None: # model. self.config.ufswm.atmos.grids = FV3GFS_grids( config=self.config, - model="FV3GFS", res=self.config.CASE, nlevs=self.config.LEVS, ).grids From 024a9bc558f47dd4b13f5799d48fe85f7e4fdca1 Mon Sep 17 00:00:00 2001 From: HenryWinterbottom-NOAA Date: Thu, 6 Apr 2023 15:54:12 -0600 Subject: [PATCH 27/31] Templated model_configure file. --- parm/ufs/forecast/gfs/model_configure.IN | 65 +++++++++++------------- 1 file changed, 30 insertions(+), 35 deletions(-) diff --git a/parm/ufs/forecast/gfs/model_configure.IN b/parm/ufs/forecast/gfs/model_configure.IN index bacb7d1fd2b..99959de98fc 100644 --- a/parm/ufs/forecast/gfs/model_configure.IN +++ b/parm/ufs/forecast/gfs/model_configure.IN @@ -1,41 +1,36 @@ -start_year: {{ ufswm.atmos.datetime.year }} -start_month: {{ ufswm.atmos.datetime.month }} -start_day: {{ ufswm.atmos.datetime.day }} -start_hour: {{ ufswm.atmos.datetime.hour }} -start_minute: {{ ufswm.atmos.datetime.minute }} -start_second: {{ ufswm.atmos.datetime.second }} +start_year: {{ ufswm.atmos.timestamps.year }} +start_month: {{ ufswm.atmos.timestamps.month }} +start_day: {{ ufswm.atmos.timestamps.day }} +start_hour: {{ ufswm.atmos.timestamps.hour }} +start_minute: {{ ufswm.atmos.timestamps.minute }} +start_second: {{ ufswm.atmos.timestamps.second }} nhours_fcst {{ FHMAX }} fhrot: {{ 0 }} -dt_atmos: {{ DELTIM }} -calendar: "julian" +dt_atmos: {{ ufswm.atmos.layout.deltim }} +calendar: "{{ calendar | default("julian") }}" restart_interval: {{ restart_interval }} -output_1st_tstep_rst: .false. +output_1st_tstep_rst: {{ output_1st_tstep_rst | default(".false.") }} -quilting: {{ QUILTING }} ### NEED TO CREATE A MODEL - # THAT TERMS I/O AND - # COMPUTING CONFIGURATIONS - # AS A FUNCTION OF UFS WM - # RESOLUTIONS. -write_groups: {{ WRITE_GROUP|1 }} -write_tasks_per_group: {{ WRTTASK_PER_GROUP|24 }} +quilting: {{ ufswm.atmos.layout.quilting }} +write_groups: {{ ufswm.atmos.layout.write_group }} +write_tasks_per_group: {{ ufswm.atmos.layout.wrttask_per_group }} itasks: 1 -output_history: {{ OUTPUT_HISTORY|.true. }} -write_dopost: {{ WRITE_DOPOST|.false. }} -write_nsflip: {{ WRITE_NSFLIP|.false. }} -num_files: {{ NUM_FILES|2 }} -filename_base: "atm" "sfc" -output_grid: {{ OUTPUT_GRID }} -output_file: "{{ OUTPUT_FILETYPE_ATM }}" "{{ OUTPUT_FILETYPE_SFC }}" -ichunk2d: {{ ichunk2d|0 }} -jchunk2d: {{ jchunk2d|0 }} -ichunk3d: {{ ichunk3d|0 }} -jchunk3d: {{ jchunk3d|0 }} -kchunk3d: {{ kchunk3d|0 }} -ideflate: {{ ideflate|1 }} -nbits: {{ nbits|14 }} -imo: {{ LONB_IMO }} -jmo: {{ LATB_JMO }} -output_fh: {{ OUTPUT_FH }} -iau_offset: {{ IAU_OFFSET|0 }} - +output_history: {{ OUTPUT_HISTORY | default(".true.") }} +write_dopost: {{ WRITE_DOPOST }} +write_nsflip: {{ WRITE_NSFLIP }} +num_files: {{ numfiles | default(2) }} +filename_base: "{{ filename_base_atm | default("atm") }}" "{{ filename_base_sfc | default("sfc") }}" +output_grid: {{ OUTPUT_GRID | default("gaussian_grid") }} +output_file: "{{ OUTPUT_FILETYPE_ATM | default("netcdf") }}" "{{ OUTPUT_FILETYPE_SFC | default("netcdf") }}" +ichunk2d: {{ ufswm.atmos.layout.ichunk2d }} +jchunk2d: {{ ufswm.atmos.layout.jchunk2d }} +ichunk3d: {{ ufswm.atmos.layout.ichunk3d }} +jchunk3d: {{ ufswm.atmos.layout.jchunk3d }} +kchunk3d: {{ ufswm.atmos.layout.kchunk3d }} +ideflate: {{ ideflate | default(1) }} +nbits: {{ nbits | default(14) }} +imo: {{ ufswm.atmos.grids.lonb }} +jmo: {{ ufswm.atmos.grids.latb }} +output_fh: {{ OUTPUT_FH | default(0) }} +iau_offset: {{ IAU_OFFSET | default(0) }} From 7d54d39a57229dc854637bfe1c26dd0d9b69c835 Mon Sep 17 00:00:00 2001 From: HenryWinterbottom-NOAA Date: Thu, 6 Apr 2023 16:25:09 -0600 Subject: [PATCH 28/31] Formatting updates. --- ush/python/pygfs/task/forecast.py | 137 ++++++++++++++++++------------ 1 file changed, 84 insertions(+), 53 deletions(-) diff --git a/ush/python/pygfs/task/forecast.py b/ush/python/pygfs/task/forecast.py index fab8e89329e..d8ec68267dc 100644 --- a/ush/python/pygfs/task/forecast.py +++ b/ush/python/pygfs/task/forecast.py @@ -1,28 +1,45 @@ +""" +Module +------ -import os -from typing import Dict, List + pygfs.ufswm (pygfs/ufswm.py) -from pygw.task import Task -# from pygw.logger import Logger, logit +Description +----------- -from pygw.attrdict import AttrDict + This module contains the base-class module for all Unified + Forecast System (UFS) Weather Model (WM) applications. -from pygfs.ufswm import UFSWM -from pygfs.exceptions import ForecastError -from pygfs.utils.logger import Logger +Classes +------- -from pygw.jinja import Jinja -from pygw.yaml_file import YAMLFile, parse_yamltmpl + Forecast(config, model, *args, **kwargs) -from pygw.file_utils import FileHandler + This is the base-class object for the respective Unified + Forecast System (UFS) forecast task; it is a sub-class of + Task. + +""" # ---- -# Define the valid forecast model list. -VALID_MODEL_LIST = ["fv3gfs"] +__author__ = "Henry R. Winterbottom" +__maintainer__ = "Henry R. Winterbottom" +__email__ = "henry.winterbottom@noaa.gov" +__version__ = 0.0 -# The following are the supported GFS applications. -GFS_APP_LIST = ["atm", "atmw", "atma", "s2s", "s2sw", "s2swa"] +# ---- + +import os +from typing import Dict + +from pygfs.exceptions import ForecastError +from pygfs.ufswm import UFSWM +from pygfs.utils.logger import Logger +from pygw.file_utils import FileHandler +from pygw.jinja import Jinja +from pygw.task import Task +from pygw.yaml_file import YAMLFile, parse_yamltmpl # ---- @@ -38,9 +55,24 @@ class Forecast(Task): Parameters ---------- + config: Dict + + A Python dictionary containing the application configuration + attributes. + + model: str + + A Python string specifying the valid forecast model. + Raises ------ + ForecastError: + + - raised if the YAML key `forecast` could not be determined + from the YAML-formatted configuration file path; see + configuration variable `FCSTYAML`. + """ def __init__(self: Task, config: Dict, model: str, *args, **kwargs): @@ -57,46 +89,40 @@ def __init__(self: Task, config: Dict, model: str, *args, **kwargs): self.config = config self.model = model.lower() self.logger = Logger(config=self.config).logger - UFSWM(config=self.config, model=self.model) + # Collect the forecast configuration attributes from the + # external YAML-formatted file path. try: - self.config.forecast = YAMLFile( - path=self.config.FCSTYAML).as_dict()["forecast"] - - except KeyError: - msg = ("The attribute (e.g., YAML-key) `forecast` could not be determined " - f"from YAML-formatted file {self.fcst_config.FCSTYAML}. Aborting!!!" - ) - raise ForecastError(msg=msg) - - # try: - # self.config.yaml_config = YAMLFile( - # path=self.config.FCSTYAML).as_dict()["forecast"] - # except KeyError: - # msg = ("The attribute (e.g., YAML-key) `forecast` could not be determined " - # f"from YAML-formatted file {self.fcst_config.FCSTYAML}. Aborting!!!" - # ) - # raise ForecastError(msg=msg) - - # if self.model not in VALID_MODEL_LIST: - # msg = f"Forecast model {self.model} is not (yet) supported. Aborting!!!" - # raise ForecastError(msg=msg) - - # if getattr(self.config, "loglev") is None: - # self.config.loglev = "info" - # self.logger = Logger( - # level=self.config.loglev, colored_log=True) + self.config.forecast = YAMLFile(path=self.config.FCSTYAML).as_dict()[ + "forecast" + ] + + except KeyError as exc: + msg = ( + "The attribute (e.g., YAML-key) `forecast` could not be determined " + f"from YAML-formatted file {self.config.FCSTYAML}. Aborting!!!" + ) + raise ForecastError(msg=msg) from exc def build_model_configure(self: Task) -> None: - """ """ + """ + Description + ----------- + + This method parses a Jinja2-formatted template and builds the + UFS weather model forecast application `model_configure` file + within the forecast application working directory. + + """ model_configure_tmpl = self.config.forecast.model_configure model_configure_path = os.path.join( self.runtime_config.DATA, "model_configure") - Jinja(model_configure_tmpl, data=self.config, - allow_missing=True).save(model_configure_path) + Jinja(model_configure_tmpl, data=self.config, allow_missing=True).save( + model_configure_path + ) def build_nems_configure(self: Task) -> None: """ @@ -104,8 +130,8 @@ def build_nems_configure(self: Task) -> None: ----------- This method parses a Jinja2-formatted template and builds the - UFS forecast application nems.configure file within the - forecast task application working directory. + UFS weather model forecast application `nems.configure` file + within the forecast application working directory. """ @@ -120,8 +146,9 @@ def build_nems_configure(self: Task) -> None: # IF A VARIABLE IS NOT RENDERED CORRECTLY THE FORECAST # MODEL WILL FAIL; THIS IS ALSO USEFUL IN THE CASES WHEN A # USER HAS DEFINE THE INCORRECT nems.configure TEMPLATE. - Jinja(nems_configure_tmpl, data=self.config, - allow_missing=False).save(nems_configure_path) + Jinja(nems_configure_tmpl, data=self.config, allow_missing=False).save( + nems_configure_path + ) with open(nems_configure_path, "a", encoding="utf-8") as fout: fout.write(f"\n\n# Template: {nems_configure_tmpl}.") @@ -139,18 +166,22 @@ def config_dirtree(self: Task) -> None: # Build the directory tree and link the fixed files to the # working directory; proceed accordingly. dirtree_config = parse_yamltmpl( - path=self.config.FCSTYAML, data=self.runtime_config)["forecast"] + path=self.config.FCSTYAML, data=self.runtime_config + )["forecast"] FileHandler(dirtree_config.dirtree_atmos).sync() atmos_fcst_config = parse_yamltmpl( - path=dirtree_config.fixed_files.atmos, data=self.config) + path=dirtree_config.fixed_files.atmos, data=self.config + ) FileHandler(atmos_fcst_config).sync() land_fcst_config = parse_yamltmpl( - path=dirtree_config.fixed_files.land, data=self.config) + path=dirtree_config.fixed_files.land, data=self.config + ) FileHandler(land_fcst_config).sync() if self.config.coupled: # HRW: THIS NEEDS TO BE UPDATED. FileHandler(dirtree_config.dirtree_ocean).sync() ocean_fcst_config = parse_yamltmpl( - path=dirtree_config.fixed_files.ocean, data=self.config) + path=dirtree_config.fixed_files.ocean, data=self.config + ) FileHandler(ocean_fcst_config).sync() From c87daaf2ad3d875230703968ba11facbe106fc11 Mon Sep 17 00:00:00 2001 From: HenryWinterbottom-NOAA Date: Thu, 6 Apr 2023 16:27:30 -0600 Subject: [PATCH 29/31] Updates. --- ush/python/pygfs/exceptions.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ush/python/pygfs/exceptions.py b/ush/python/pygfs/exceptions.py index 29a7a601391..8ceaff01a61 100644 --- a/ush/python/pygfs/exceptions.py +++ b/ush/python/pygfs/exceptions.py @@ -18,10 +18,10 @@ ush/python/pygfs/task/forecast module; it is a sub-class of WorkflowException. - GFSError(msg) + FV3GFSError(msg) This is the base-class for exceptions encountered within the - ush/python/pygfs/task/gfs module; it is a sub-class of + ush/python/pygfs/task/fv3gfs module; it is a sub-class of WorkflowException. GridsError(msg) @@ -58,8 +58,8 @@ # ---- # Define all available classes. -__all__ = ["ForecastError", "GFSError", "GridsInfoError", "TimestampsError", - "UFSWMError"] +__all__ = ["ForecastError", "FV3GFSError", "GridsInfoError", + "TimestampsError", "UFSWMError"] # ---- @@ -78,13 +78,13 @@ class ForecastError(WorkflowException): # ---- -class GFSError(WorkflowException): +class FV3GFSError(WorkflowException): """ Description ----------- This is the base-class for exceptions encountered within the - ush/python/pygfs/task/gfs module; it is a sub-class of + ush/python/pygfs/task/fv3gfs module; it is a sub-class of WorkflowException. """ From 5d132bb079e68d98b5d2872d05cf1441dee6fc08 Mon Sep 17 00:00:00 2001 From: HenryWinterbottom-NOAA Date: Thu, 6 Apr 2023 17:00:15 -0600 Subject: [PATCH 30/31] Docstring updates. --- ush/python/pygfs/task/forecast.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/ush/python/pygfs/task/forecast.py b/ush/python/pygfs/task/forecast.py index d8ec68267dc..739a91d10f4 100644 --- a/ush/python/pygfs/task/forecast.py +++ b/ush/python/pygfs/task/forecast.py @@ -2,13 +2,13 @@ Module ------ - pygfs.ufswm (pygfs/ufswm.py) + pygfs.task.forecast (pygfs/task/forecast.py) Description ----------- - This module contains the base-class module for all Unified - Forecast System (UFS) Weather Model (WM) applications. + This module contains the base-class module for forecast model + applications. Classes ------- @@ -117,8 +117,7 @@ def build_model_configure(self: Task) -> None: """ model_configure_tmpl = self.config.forecast.model_configure - model_configure_path = os.path.join( - self.runtime_config.DATA, "model_configure") + model_configure_path = os.path.join(self.runtime_config.DATA, "model_configure") Jinja(model_configure_tmpl, data=self.config, allow_missing=True).save( model_configure_path @@ -138,8 +137,7 @@ def build_nems_configure(self: Task) -> None: # Define then NEMS configuration template and write the file # accordingly. nems_configure_tmpl = self.config.forecast.nems_configure - nems_configure_path = os.path.join( - self.runtime_config.DATA, "nems.configure") + nems_configure_path = os.path.join(self.runtime_config.DATA, "nems.configure") # HRW: FORCING ALL TEMPLATE VARIABLES TO BE RENDERED IF # THEY ARE WITHOUT DEFAULT VALUES (IN THE JINJA TEMPLATE); From 1843b5f7fb4eccd5d14aed79f7ad4857a7f948b9 Mon Sep 17 00:00:00 2001 From: HenryWinterbottom-NOAA Date: Thu, 6 Apr 2023 17:03:53 -0600 Subject: [PATCH 31/31] Documentation and formatting updates. --- ush/python/pygfs/task/fv3gfs_forecast.py | 162 +++++++++++++++-------- 1 file changed, 105 insertions(+), 57 deletions(-) diff --git a/ush/python/pygfs/task/fv3gfs_forecast.py b/ush/python/pygfs/task/fv3gfs_forecast.py index 36f407f4f4a..c1eb25506c2 100644 --- a/ush/python/pygfs/task/fv3gfs_forecast.py +++ b/ush/python/pygfs/task/fv3gfs_forecast.py @@ -1,37 +1,75 @@ +""" +Module +------ -import os + pygfs.task.fv3gfs_forecast (pygfs/task/fv3gfs_forecast.py) + +Description +----------- + + This module contains the base-class module for Finite-Volume + Cubed-Sphere (FV3) Global Forecast System (GFS) forecast model + applications. -from pygfs.task.forecast import Forecast, GFS_APP_LIST -from pygfs.exceptions import GFSError -from typing import Dict, List -from pygw.logger import Logger, logit +Classes +------- -from pygw.attrdict import AttrDict -from pygw.yaml_file import YAMLFile + FV3GFS(config) + + This is the base-class object for all Finite-Volume + Cubed-Sphere (FV3) Global Forecast System (GFS) forecast model + applications; it is a sub-class of Forecast. + +""" # ---- -logger = Logger(name="GFS", colored_log=True) +__author__ = "Henry R. Winterbottom" +__maintainer__ = "Henry R. Winterbottom" +__email__ = "henry.winterbottom@noaa.gov" +__version__ = 0.0 # ---- -# The following attributes are mandatory for fixed-file syncing; -# this should not be changed unless absolutely necessary. -FIXED_MAND_ATTR_DICT = {"DATA": "DATA", - "FIXgfs": "FIXgfs", - "HOMEgfs": "HOMEgfs", - "atm_res": "CASE", - "ocn_res": "OCNRES" - } +import os +from typing import Dict + +from pygfs.exceptions import FV3GFSError +from pygfs.task.forecast import Forecast +from pygw.decorators import private # ---- class FV3GFS(Forecast): """ + Description + ----------- + + This is the base-class object for all Finite-Volume Cubed-Sphere + (FV3) Global Forecast System (GFS) forecast model applications; it + is a sub-class of Forecast. + + Parameters + ---------- + + config: Dict + + A Python dictionary containing the application configuration + attributes. """ + # The following attributes are mandatory for fixed-file syncing; + # this should not be changed unless absolutely necessary. + FIXED_MAND_ATTR_DICT = { + "DATA": "DATA", + "FIXgfs": "FIXgfs", + "HOMEgfs": "HOMEgfs", + "atm_res": "CASE", + "ocn_res": "OCNRES", + } + def __init__(self: Forecast, config: Dict): """ Description @@ -44,18 +82,6 @@ def __init__(self: Forecast, config: Dict): # Define the base-class attributes. super().__init__(config=config, model="FV3GFS") - print(self.config.ufswm.atmos.grids) - -# if self.fcst_config.APP.lower() not in GFS_APP_LIST: -# msg = (f"The GFS forecast application {self.config.APP} is not " -# "supported. Aborting!!!" -# ) -# raise GFSError(msg=msg) - - # self.fcst_config.model = "gfs" - # self.fcst_config.ntiles = 6 - # self.fcst_config.fix_path = self.config.FIXgfs - # HRW: THIS IS ALREADY DEFINED IN THE RUN-TIME ENVIRONMENT; DO # WE WANT TO KEEP IT HERE AS IT CURRENTLY HAS NOT BEARING BUT # COULD BE AN ATTRIBUTE COLLECTED FROM A YAML-FORMATTED @@ -63,7 +89,8 @@ def __init__(self: Forecast, config: Dict): # self.fcst_config.coupled = (self.fcst_config.app in [ # "s2s", "s2sw", "s2swa"]) - def __fixedfiles(self: Forecast) -> Dict: + @private + def fixedfiles(self: Forecast) -> Dict: """ Description ----------- @@ -85,7 +112,7 @@ def __fixedfiles(self: Forecast) -> Dict: Raises ------ - GFSError: + FV3GFSError: - raised a mandatory configuration attribute (see `FIXED_MAND_ATTR_DICT`) is not specified within the @@ -101,9 +128,7 @@ def __fixedfiles(self: Forecast) -> Dict: """ # Define the fixed-file attributes. - fixedfiles_dict = AttrDict() - - for (fixed_attr_key, fixed_attr_value) in FIXED_MAND_ATTR_DICT.items(): + for (fixed_attr_key, fixed_attr_value) in self.FIXED_MAND_ATTR_DICT.items(): # Define the respective configuration attribute (i.e., # `fixed_attr_key`) and assign the corresponding value @@ -114,10 +139,11 @@ def __fixedfiles(self: Forecast) -> Dict: value = self.runtime_config[fixed_attr_value] if value is None: - msg = (f"The configuration attribute {fixed_attr_key} could not " - "be determined from the run-time environment. Aborting!!!" - ) - raise GFSError(msg=msg) + msg = ( + f"The configuration attribute {fixed_attr_key} could not " + "be determined from the run-time environment. Aborting!!!" + ) + raise FV3GFSError(msg=msg) setattr(self.config, fixed_attr_key, value) @@ -127,35 +153,57 @@ def __fixedfiles(self: Forecast) -> Dict: fix_dirpath = self.config.FIXgfs if (not os.path.isdir(fix_dirpath)) or (not os.path.exists(fix_dirpath)): - msg = (f"The directory tree {fix_dirpath} is either not a directory " - "or does not exist. Aborting!!!" - ) - raise GFSError(msg=msg) + msg = ( + f"The directory tree {fix_dirpath} is either not a directory " + "or does not exist. Aborting!!!" + ) + raise FV3GFSError(msg=msg) # Define the fixed-file directory tree attributes. - fixed_attr_dict = {"FIX_aer": "aer", - "FIX_am": "am", - "FIX_lut": "lut", - "FIX_orog": "orog", - "FIX_lut": "lut", - "FIX_ugwd": "ugwd", - } + fixed_attr_dict = { + "FIX_aer": "aer", + "FIX_am": "am", + "FIX_lut": "lut", + "FIX_orog": "orog", + "FIX_ugwd": "ugwd", + } for (fixed_attr_key, fixed_attr_value) in fixed_attr_dict.items(): fix_subdirpath = os.path.join(fix_dirpath, fixed_attr_value) - if (not os.path.isdir(fix_subdirpath)) and (not os.path.isfile(fix_subdirpath)): - msg = (f"The directory tree path {fix_subdirpath} is either not " - "a directory does not exist; this may cause unexpected results " - "or failures." - ) + if (not os.path.isdir(fix_subdirpath)) and ( + not os.path.isfile(fix_subdirpath) + ): + msg = ( + f"The directory tree path {fix_subdirpath} is either not " + "a directory does not exist; this may cause unexpected results " + "or failures." + ) self.logger.warning(msg=msg) - setattr(self.config, fixed_attr_key, os.path.join( - self.config.FIXgfs, fixed_attr_value)) + setattr( + self.config, + fixed_attr_key, + os.path.join(self.config.FIXgfs, fixed_attr_value), + ) - def initialize(self: Forecast): + def initialize(self: Forecast) -> None: """ + Description + ----------- + + This method initializes the FV3 GFS forecast application + working directory; this includes the following: + + - Configuring the directory tree; + + - Copying the relevant fixed-files; + + - Building of the `model_configure` file for the respective + application; + + - Building the `nems.configure` file for the respective + application. """ @@ -164,7 +212,7 @@ def initialize(self: Forecast): # Build the directory tree and copy the required fixed files # accordingly. - self.__fixedfiles() + self.fixedfiles() self.config_dirtree() # Build the respective UFS configuration files.