From c0102bf2ebd0db2d355a4932fecfe3ea9d8d8bf7 Mon Sep 17 00:00:00 2001 From: David Huber Date: Thu, 19 Sep 2024 16:34:24 +0000 Subject: [PATCH 01/36] Additions to allow APP to differ by RUN --- parm/config/gfs/config.base | 31 ++++++++++---- workflow/applications/applications.py | 47 ++++++++++++++-------- workflow/applications/gefs.py | 12 +++++- workflow/applications/gfs_cycled.py | 29 ++++++++++++- workflow/applications/gfs_forecast_only.py | 12 +++++- workflow/rocoto/gfs_tasks.py | 6 +-- workflow/rocoto/workflow_xml.py | 4 +- 7 files changed, 107 insertions(+), 34 deletions(-) diff --git a/parm/config/gfs/config.base b/parm/config/gfs/config.base index 784c334d826..a72a7a242d7 100644 --- a/parm/config/gfs/config.base +++ b/parm/config/gfs/config.base @@ -151,8 +151,25 @@ export SENDDBN_NTC=${SENDDBN_NTC:-"NO"} export SENDDBN=${SENDDBN:-"NO"} export DBNROOT=${DBNROOT:-${UTILROOT:-}/fakedbn} -# APP settings -export APP=@APP@ +# APP settings; configurable by RUN +case "${RUN}" in + "gfs") + export APP=@APP@ + ;; + "gdas") + export APP=@APP@ + ;; + "enkfgfs") + export APP=@APP@ + ;; + "enkfgdas") + export APP=@APP@ + ;; + *) + echo "FATAL ERROR: Unrecognized RUN (${RUN})!" + exit 1 + ;; +esac shopt -s extglob # Adjust APP based on RUN @@ -217,7 +234,7 @@ case "${CASE}" in ;; *) echo "FATAL ERROR: Unrecognized CASE ${CASE}, ABORT!" - exit 1 + exit 2 ;; esac @@ -256,8 +273,8 @@ case "${APP}" in fi ;; *) - echo "Unrecognized APP: '${APP}'" - exit 1 + echo "FATAL ERROR: Unrecognized APP: '${APP}'" + exit 3 ;; esac @@ -461,8 +478,8 @@ export FHMAX_FITS=132 export HPSSARCH="@HPSSARCH@" # save data to HPSS archive export LOCALARCH="@LOCALARCH@" # save data to local archive if [[ ${HPSSARCH} = "YES" ]] && [[ ${LOCALARCH} = "YES" ]]; then - echo "Both HPSS and local archiving selected. Please choose one or the other." - exit 2 + echo "FATAL ERROR: Both HPSS and local archiving selected. Please choose one or the other." + exit 4 fi export ARCH_CYC=00 # Archive data at this cycle for warm_start capability export ARCH_WARMICFREQ=4 # Archive frequency in days for warm_start capability diff --git a/workflow/applications/applications.py b/workflow/applications/applications.py index a694129e386..ee721907e87 100644 --- a/workflow/applications/applications.py +++ b/workflow/applications/applications.py @@ -41,7 +41,6 @@ def __init__(self, conf: Configuration) -> None: f'{", ".join(self.VALID_MODES)}\n') self.net = base['NET'] - self.model_app = base.get('APP', 'ATM') self.do_atm = base.get('DO_ATM', True) self.do_wave = base.get('DO_WAVE', False) self.do_wave_bnd = base.get('DOBNDPNT_WAVE', False) @@ -63,6 +62,7 @@ def __init__(self, conf: Configuration) -> None: self.do_goes = base.get('DO_GOES', False) self.do_mos = base.get('DO_MOS', False) self.do_extractvars = base.get('DO_EXTRACTVARS', False) + self.gfs_cyc = base.get('gfs_cyc') self.do_hpssarch = base.get('HPSSARCH', False) @@ -97,28 +97,27 @@ def __init__(self, conf: Configuration) -> None: def _init_finalize(self, conf: Configuration): print("Finalizing initialize") - # Get a list of all possible config_files that would be part of the application + # Get a list of all possible config files that would be part of the application self.configs_names = self._get_app_configs() - # Source the config files for the jobs in the application without specifying a RUN - self.configs = {'_no_run': self._source_configs(conf)} - - # Update the base config dictionary based on application - self.configs['_no_run']['base'] = self._update_base(self.configs['_no_run']['base']) + # Get the list of valid runs for the configuration + self.runs = self.get_valid_runs() - # Save base in the internal state since it is often needed - base = self.configs['_no_run']['base'] + # Initialize the task_names, configs, and model_apps dictionaries + self.task_names = dict.fromkeys(self.runs) + self.model_apps = dict.fromkeys(self.runs) + self.configs = dict.fromkeys(self.runs) - # Get more configuration options into the class attributes - self.gfs_cyc = base.get('gfs_cyc') + # Now configure the experiment for each valid run + for run in self.runs: - # Get task names for the application - self.task_names = self.get_task_names() + # Get task names, configs, and APPs for the application + self.task_names[run] = self.get_task_names(run) - # Finally, source the configuration files for each valid `RUN` - for run in self.task_names.keys(): self.configs[run] = self._source_configs(conf, run=run, log=False) + self.model_apps[run] = self.configs[run]['base'].get('APP', 'ATM') + # Update the base config dictionary based on application and RUN self.configs[run]['base'] = self._update_base(self.configs[run]['base']) @@ -184,7 +183,23 @@ def _source_configs(self, conf: Configuration, run: str = "gfs", log: bool = Tru return configs @abstractmethod - def get_task_names(self, run="_no_run") -> Dict[str, List[str]]: + def get_valid_runs(self) -> List[str]: + ''' + Create a list of RUNs for the configuation. + + Parameters + ---------- + None + + Returns + ------- + Dict[str, List[str]]: Lists of tasks for each RUN. + + ''' + pass + + @abstractmethod + def get_task_names(self, run: str) -> Dict[str, List[str]]: ''' Create a list of task names for each RUN valid for the configuation. diff --git a/workflow/applications/gefs.py b/workflow/applications/gefs.py index afb4072596f..3a27808045d 100644 --- a/workflow/applications/gefs.py +++ b/workflow/applications/gefs.py @@ -47,7 +47,15 @@ def _update_base(base_in): return base_out - def get_task_names(self): + def get_valid_runs(self): + """ + Return the GEFS RUN. + """ + + # Only one RUN (should be gefs) is allowed as specified in __init__ + return [self.run] + + def get_task_names(self, run): tasks = ['stage_ic'] @@ -84,4 +92,4 @@ def get_task_names(self): tasks += ['arch', 'cleanup'] - return {f"{self.run}": tasks} + return tasks diff --git a/workflow/applications/gfs_cycled.py b/workflow/applications/gfs_cycled.py index 4bb473f454e..87a6ab43e24 100644 --- a/workflow/applications/gfs_cycled.py +++ b/workflow/applications/gfs_cycled.py @@ -130,7 +130,28 @@ def _update_base(base_in): return GFSCycledAppConfig.get_gfs_cyc_dates(base_in) - def get_task_names(self): + def get_valid_runs(self): + """ + Get a list of valid RUNs in cycled MODE. + """ + + # The gdas run is always present for the cycled application + runs = ["gdas"] + + # Are we running the early cycle deterministic forecast? + if self.gfs_cyc > 0: + runs.append("gfs") + + # Ensembles? Add the valid run based on eupd_runs. + if self.do_hybvar: + if 'gdas' in self.eupd_runs: + runs.append("enkfgdas") + if 'gfs' in self.eupd_runs: + runs.append("enkfgfs") + + return runs + + def get_task_names(self, run: str): """ Get the task names for all the tasks in the cycled application. Note that the order of the task names matters in the XML. @@ -306,7 +327,11 @@ def get_task_names(self): enkfgfs_tasks.remove("esnowrecen") tasks['enkfgfs'] = enkfgfs_tasks - return tasks + if run not in tasks: + raise KeyError(f"FATAL ERROR: GFS cycled experiment is not configured " + f"for the input run ({run})") + + return tasks[run] @staticmethod def get_gfs_cyc_dates(base: Dict[str, Any]) -> Dict[str, Any]: diff --git a/workflow/applications/gfs_forecast_only.py b/workflow/applications/gfs_forecast_only.py index 93551ac0cc0..421d046b80c 100644 --- a/workflow/applications/gfs_forecast_only.py +++ b/workflow/applications/gfs_forecast_only.py @@ -82,7 +82,15 @@ def _update_base(base_in): return base_out - def get_task_names(self): + def get_valid_runs(self): + """ + Return the specified RUN for forecast-only MODE. + """ + + # Only one RUN is allowed for forecast-only mode as specified in __init__ + return [self.run] + + def get_task_names(self, run: str): """ Get the task names for all the tasks in the forecast-only application. Note that the order of the task names matters in the XML. @@ -157,4 +165,4 @@ def get_task_names(self): tasks += ['arch', 'cleanup'] # arch and cleanup **must** be the last tasks - return {f"{self.run}": tasks} + return tasks diff --git a/workflow/rocoto/gfs_tasks.py b/workflow/rocoto/gfs_tasks.py index 89da933d00c..8f2f612ea60 100644 --- a/workflow/rocoto/gfs_tasks.py +++ b/workflow/rocoto/gfs_tasks.py @@ -855,8 +855,8 @@ def fcst(self): try: task = fcst_map[self.app_config.mode]() except KeyError: - raise NotImplementedError(f'{self.app_config.mode} is not a valid type.\n' + - 'Currently supported forecast types are:\n' + + raise NotImplementedError(f'{self.app_config.mode} is not a valid type.\n' + f'Currently supported forecast types are:\n' f'{" | ".join(fcst_map.keys())}') return task @@ -868,7 +868,7 @@ def _fcst_forecast_only(self): dependencies.append(rocoto.add_dependency(dep_dict)) if self.app_config.do_wave and self.run in self.app_config.wave_runs: - wave_job = 'waveprep' if self.app_config.model_app in ['ATMW'] else 'waveinit' + wave_job = 'waveprep' if self.app_config.model_apps[self.run] in ['ATMW'] else 'waveinit' dep_dict = {'type': 'task', 'name': f'{self.run}{wave_job}'} dependencies.append(rocoto.add_dependency(dep_dict)) diff --git a/workflow/rocoto/workflow_xml.py b/workflow/rocoto/workflow_xml.py index 3ad7c4bd917..03c7d8b4b90 100644 --- a/workflow/rocoto/workflow_xml.py +++ b/workflow/rocoto/workflow_xml.py @@ -18,8 +18,8 @@ def __init__(self, app_config: AppConfig, rocoto_config: Dict) -> None: self._app_config = app_config self.rocoto_config = rocoto_config - # Use the generic config.base (without RUN specified) - self._base = self._app_config.configs['_no_run']['base'] + # Use the first config.base (sourced with an arbitrary RUN) + self._base = self._app_config.configs[next(iter(self._app_config.configs))]['base'] self.preamble = self._get_preamble() self.definitions = self._get_definitions() From d987c385cd3afb9e7232dcb51bbd10af5ccf52e5 Mon Sep 17 00:00:00 2001 From: David Huber Date: Thu, 19 Sep 2024 16:38:50 +0000 Subject: [PATCH 02/36] Make cleanup and stage jobs run on service partitions --- parm/config/gefs/config.resources | 2 +- parm/config/gfs/config.resources | 2 +- workflow/rocoto/tasks.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/parm/config/gefs/config.resources b/parm/config/gefs/config.resources index 690fdf919ab..3d080b6317a 100644 --- a/parm/config/gefs/config.resources +++ b/parm/config/gefs/config.resources @@ -65,7 +65,7 @@ case ${step} in export ntasks=1 export tasks_per_node=1 export threads_per_task=1 - export is_exclusive=True + export memory="4096M" ;; "waveinit") diff --git a/parm/config/gfs/config.resources b/parm/config/gfs/config.resources index afc5939fcdd..218c06a9b6b 100644 --- a/parm/config/gfs/config.resources +++ b/parm/config/gfs/config.resources @@ -1042,7 +1042,7 @@ case ${step} in ntasks=1 tasks_per_node=1 threads_per_task=1 - export is_exclusive=True + memory="4096M" ;; "atmensanlinit") diff --git a/workflow/rocoto/tasks.py b/workflow/rocoto/tasks.py index df2b0467db7..43b2166b034 100644 --- a/workflow/rocoto/tasks.py +++ b/workflow/rocoto/tasks.py @@ -10,7 +10,7 @@ class Tasks: - SERVICE_TASKS = ['arch', 'earc'] + SERVICE_TASKS = ['arch', 'earc', 'stage_ic', 'cleanup'] VALID_TASKS = ['aerosol_init', 'stage_ic', 'prep', 'anal', 'sfcanl', 'analcalc', 'analdiag', 'arch', "cleanup", 'prepatmiodaobs', 'atmanlinit', 'atmanlvar', 'atmanlfv3inc', 'atmanlfinal', From 599beeba81a332fbf7996f06037de0ac92ab6bff Mon Sep 17 00:00:00 2001 From: David Huber Date: Thu, 19 Sep 2024 18:17:09 +0000 Subject: [PATCH 03/36] Combine get_valid_runs and get_task_names --- workflow/applications/applications.py | 34 +++++--------------- workflow/applications/gefs.py | 12 ++------ workflow/applications/gfs_cycled.py | 36 +++++----------------- workflow/applications/gfs_forecast_only.py | 12 ++------ 4 files changed, 19 insertions(+), 75 deletions(-) diff --git a/workflow/applications/applications.py b/workflow/applications/applications.py index ee721907e87..d262c76fa5a 100644 --- a/workflow/applications/applications.py +++ b/workflow/applications/applications.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 -from typing import Dict, List, Any +from typing import Dict, List, Tuple, Any from datetime import timedelta from hosts import Host from wxflow import Configuration, to_timedelta @@ -100,20 +100,16 @@ def _init_finalize(self, conf: Configuration): # Get a list of all possible config files that would be part of the application self.configs_names = self._get_app_configs() - # Get the list of valid runs for the configuration - self.runs = self.get_valid_runs() + # Get task names, configs, and APPs for the application + self.runs, self.task_names = self.get_task_names() - # Initialize the task_names, configs, and model_apps dictionaries - self.task_names = dict.fromkeys(self.runs) + # Initialize the configs and model_apps dictionaries self.model_apps = dict.fromkeys(self.runs) self.configs = dict.fromkeys(self.runs) # Now configure the experiment for each valid run for run in self.runs: - # Get task names, configs, and APPs for the application - self.task_names[run] = self.get_task_names(run) - self.configs[run] = self._source_configs(conf, run=run, log=False) self.model_apps[run] = self.configs[run]['base'].get('APP', 'ATM') @@ -183,25 +179,9 @@ def _source_configs(self, conf: Configuration, run: str = "gfs", log: bool = Tru return configs @abstractmethod - def get_valid_runs(self) -> List[str]: - ''' - Create a list of RUNs for the configuation. - - Parameters - ---------- - None - - Returns - ------- - Dict[str, List[str]]: Lists of tasks for each RUN. - - ''' - pass - - @abstractmethod - def get_task_names(self, run: str) -> Dict[str, List[str]]: + def get_task_names(self, run: str) -> Tuple[List[str], Dict[str, List[str]]]: ''' - Create a list of task names for each RUN valid for the configuation. + Create a list of valid RUNs and a dict of task names for each RUN valid for the configuation. Parameters ---------- @@ -209,7 +189,7 @@ def get_task_names(self, run: str) -> Dict[str, List[str]]: Returns ------- - Dict[str, List[str]]: Lists of tasks for each RUN. + Tuple[List[str], Dict[str, List[str]]]: List of valid runs and lists of all tasks for each RUN. ''' pass diff --git a/workflow/applications/gefs.py b/workflow/applications/gefs.py index 3a27808045d..44aee7e1990 100644 --- a/workflow/applications/gefs.py +++ b/workflow/applications/gefs.py @@ -47,15 +47,7 @@ def _update_base(base_in): return base_out - def get_valid_runs(self): - """ - Return the GEFS RUN. - """ - - # Only one RUN (should be gefs) is allowed as specified in __init__ - return [self.run] - - def get_task_names(self, run): + def get_task_names(self): tasks = ['stage_ic'] @@ -92,4 +84,4 @@ def get_task_names(self, run): tasks += ['arch', 'cleanup'] - return tasks + return [self.run], {f"{self.run}": tasks} diff --git a/workflow/applications/gfs_cycled.py b/workflow/applications/gfs_cycled.py index 87a6ab43e24..de1c6336b17 100644 --- a/workflow/applications/gfs_cycled.py +++ b/workflow/applications/gfs_cycled.py @@ -130,34 +130,15 @@ def _update_base(base_in): return GFSCycledAppConfig.get_gfs_cyc_dates(base_in) - def get_valid_runs(self): + def get_task_names(self): """ - Get a list of valid RUNs in cycled MODE. - """ - - # The gdas run is always present for the cycled application - runs = ["gdas"] - - # Are we running the early cycle deterministic forecast? - if self.gfs_cyc > 0: - runs.append("gfs") - - # Ensembles? Add the valid run based on eupd_runs. - if self.do_hybvar: - if 'gdas' in self.eupd_runs: - runs.append("enkfgdas") - if 'gfs' in self.eupd_runs: - runs.append("enkfgfs") - - return runs - - def get_task_names(self, run: str): - """ - Get the task names for all the tasks in the cycled application. + Get the task names in this cycled configuration and all of the valid runs. Note that the order of the task names matters in the XML. This is the place where that order is set. """ + runs = ["gdas"] + gdas_gfs_common_tasks_before_fcst = ['prep'] gdas_gfs_common_cleanup_tasks = ['arch', 'cleanup'] @@ -314,24 +295,23 @@ def get_task_names(self, run: str): tasks['gdas'] = gdas_tasks if self.do_hybvar and 'gdas' in self.eupd_runs: + runs.append("enkfgdas") enkfgdas_tasks = hybrid_tasks + hybrid_after_eupd_tasks tasks['enkfgdas'] = enkfgdas_tasks # Add RUN=gfs tasks if running early cycle if self.gfs_cyc > 0: + runs.append("gfs") tasks['gfs'] = gfs_tasks if self.do_hybvar and 'gfs' in self.eupd_runs: + runs.append("enkfgfs") enkfgfs_tasks = hybrid_tasks + hybrid_after_eupd_tasks enkfgfs_tasks.remove("echgres") enkfgfs_tasks.remove("esnowrecen") tasks['enkfgfs'] = enkfgfs_tasks - if run not in tasks: - raise KeyError(f"FATAL ERROR: GFS cycled experiment is not configured " - f"for the input run ({run})") - - return tasks[run] + return runs, tasks @staticmethod def get_gfs_cyc_dates(base: Dict[str, Any]) -> Dict[str, Any]: diff --git a/workflow/applications/gfs_forecast_only.py b/workflow/applications/gfs_forecast_only.py index 421d046b80c..b48924da6af 100644 --- a/workflow/applications/gfs_forecast_only.py +++ b/workflow/applications/gfs_forecast_only.py @@ -82,15 +82,7 @@ def _update_base(base_in): return base_out - def get_valid_runs(self): - """ - Return the specified RUN for forecast-only MODE. - """ - - # Only one RUN is allowed for forecast-only mode as specified in __init__ - return [self.run] - - def get_task_names(self, run: str): + def get_task_names(self): """ Get the task names for all the tasks in the forecast-only application. Note that the order of the task names matters in the XML. @@ -165,4 +157,4 @@ def get_task_names(self, run: str): tasks += ['arch', 'cleanup'] # arch and cleanup **must** be the last tasks - return tasks + return [self.run], {f"{self.run}": tasks} From eaa10495846fd36196e4a5a0947388e33c8f75bd Mon Sep 17 00:00:00 2001 From: David Huber Date: Thu, 19 Sep 2024 18:36:35 +0000 Subject: [PATCH 04/36] Simplify get_task_names --- workflow/applications/applications.py | 12 ++++++------ workflow/applications/gefs.py | 2 +- workflow/applications/gfs_cycled.py | 9 ++------- workflow/applications/gfs_forecast_only.py | 2 +- 4 files changed, 10 insertions(+), 15 deletions(-) diff --git a/workflow/applications/applications.py b/workflow/applications/applications.py index d262c76fa5a..77c2ebafa35 100644 --- a/workflow/applications/applications.py +++ b/workflow/applications/applications.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 -from typing import Dict, List, Tuple, Any +from typing import Dict, List, Any from datetime import timedelta from hosts import Host from wxflow import Configuration, to_timedelta @@ -100,8 +100,9 @@ def _init_finalize(self, conf: Configuration): # Get a list of all possible config files that would be part of the application self.configs_names = self._get_app_configs() - # Get task names, configs, and APPs for the application - self.runs, self.task_names = self.get_task_names() + # Get task names and runs for the application + self.task_names = self.get_task_names() + self.runs = list(self.task_names.keys()) # Initialize the configs and model_apps dictionaries self.model_apps = dict.fromkeys(self.runs) @@ -111,7 +112,6 @@ def _init_finalize(self, conf: Configuration): for run in self.runs: self.configs[run] = self._source_configs(conf, run=run, log=False) - self.model_apps[run] = self.configs[run]['base'].get('APP', 'ATM') # Update the base config dictionary based on application and RUN @@ -179,7 +179,7 @@ def _source_configs(self, conf: Configuration, run: str = "gfs", log: bool = Tru return configs @abstractmethod - def get_task_names(self, run: str) -> Tuple[List[str], Dict[str, List[str]]]: + def get_task_names(self, run: str) -> Dict[str, List[str]]: ''' Create a list of valid RUNs and a dict of task names for each RUN valid for the configuation. @@ -189,7 +189,7 @@ def get_task_names(self, run: str) -> Tuple[List[str], Dict[str, List[str]]]: Returns ------- - Tuple[List[str], Dict[str, List[str]]]: List of valid runs and lists of all tasks for each RUN. + Dict[str, List[str]]: Lists of all tasks for each RUN. ''' pass diff --git a/workflow/applications/gefs.py b/workflow/applications/gefs.py index 44aee7e1990..afb4072596f 100644 --- a/workflow/applications/gefs.py +++ b/workflow/applications/gefs.py @@ -84,4 +84,4 @@ def get_task_names(self): tasks += ['arch', 'cleanup'] - return [self.run], {f"{self.run}": tasks} + return {f"{self.run}": tasks} diff --git a/workflow/applications/gfs_cycled.py b/workflow/applications/gfs_cycled.py index de1c6336b17..bbd342784cc 100644 --- a/workflow/applications/gfs_cycled.py +++ b/workflow/applications/gfs_cycled.py @@ -132,13 +132,11 @@ def _update_base(base_in): def get_task_names(self): """ - Get the task names in this cycled configuration and all of the valid runs. + Get the task names for each valid run in this cycled configuration. Note that the order of the task names matters in the XML. This is the place where that order is set. """ - runs = ["gdas"] - gdas_gfs_common_tasks_before_fcst = ['prep'] gdas_gfs_common_cleanup_tasks = ['arch', 'cleanup'] @@ -295,23 +293,20 @@ def get_task_names(self): tasks['gdas'] = gdas_tasks if self.do_hybvar and 'gdas' in self.eupd_runs: - runs.append("enkfgdas") enkfgdas_tasks = hybrid_tasks + hybrid_after_eupd_tasks tasks['enkfgdas'] = enkfgdas_tasks # Add RUN=gfs tasks if running early cycle if self.gfs_cyc > 0: - runs.append("gfs") tasks['gfs'] = gfs_tasks if self.do_hybvar and 'gfs' in self.eupd_runs: - runs.append("enkfgfs") enkfgfs_tasks = hybrid_tasks + hybrid_after_eupd_tasks enkfgfs_tasks.remove("echgres") enkfgfs_tasks.remove("esnowrecen") tasks['enkfgfs'] = enkfgfs_tasks - return runs, tasks + return tasks @staticmethod def get_gfs_cyc_dates(base: Dict[str, Any]) -> Dict[str, Any]: diff --git a/workflow/applications/gfs_forecast_only.py b/workflow/applications/gfs_forecast_only.py index b48924da6af..93551ac0cc0 100644 --- a/workflow/applications/gfs_forecast_only.py +++ b/workflow/applications/gfs_forecast_only.py @@ -157,4 +157,4 @@ def get_task_names(self): tasks += ['arch', 'cleanup'] # arch and cleanup **must** be the last tasks - return [self.run], {f"{self.run}": tasks} + return {f"{self.run}": tasks} From 3787be7f098cb9106c1240610300675d57e56c47 Mon Sep 17 00:00:00 2001 From: David Huber Date: Fri, 20 Sep 2024 13:13:20 -0500 Subject: [PATCH 05/36] Make APP-specific options based on RUN --- workflow/applications/applications.py | 158 ++++++++++++++------- workflow/applications/gefs.py | 1 + workflow/applications/gfs_cycled.py | 57 +++++--- workflow/applications/gfs_forecast_only.py | 1 + workflow/rocoto/gfs_tasks.py | 6 +- 5 files changed, 143 insertions(+), 80 deletions(-) diff --git a/workflow/applications/applications.py b/workflow/applications/applications.py index 77c2ebafa35..0c81a5d7228 100644 --- a/workflow/applications/applications.py +++ b/workflow/applications/applications.py @@ -31,78 +31,29 @@ def __init__(self, conf: Configuration) -> None: self.scheduler = Host().scheduler + # Get the most basic settings from config.base to determine + # experiment type ({NET}_{MODE}) base = conf.parse_config('config.base') self.mode = base['MODE'] - if self.mode not in self.VALID_MODES: raise NotImplementedError(f'{self.mode} is not a valid application mode.\n' f'Valid application modes are:\n' f'{", ".join(self.VALID_MODES)}\n') self.net = base['NET'] - self.do_atm = base.get('DO_ATM', True) - self.do_wave = base.get('DO_WAVE', False) - self.do_wave_bnd = base.get('DOBNDPNT_WAVE', False) - self.do_ocean = base.get('DO_OCN', False) - self.do_ice = base.get('DO_ICE', False) - self.do_aero = base.get('DO_AERO', False) - self.do_prep_obs_aero = base.get('DO_PREP_OBS_AERO', False) - self.do_bufrsnd = base.get('DO_BUFRSND', False) - self.do_gempak = base.get('DO_GEMPAK', False) - self.do_awips = base.get('DO_AWIPS', False) - self.do_verfozn = base.get('DO_VERFOZN', True) - self.do_verfrad = base.get('DO_VERFRAD', True) - self.do_vminmon = base.get('DO_VMINMON', True) - self.do_tracker = base.get('DO_TRACKER', True) - self.do_genesis = base.get('DO_GENESIS', True) - self.do_genesis_fsu = base.get('DO_GENESIS_FSU', False) - self.do_metp = base.get('DO_METP', False) - self.do_upp = not base.get('WRITE_DOPOST', True) - self.do_goes = base.get('DO_GOES', False) - self.do_mos = base.get('DO_MOS', False) - self.do_extractvars = base.get('DO_EXTRACTVARS', False) self.gfs_cyc = base.get('gfs_cyc') - self.do_hpssarch = base.get('HPSSARCH', False) - - self.nens = base.get('NMEM_ENS', 0) - self.fcst_segments = base.get('FCST_SEGMENTS', None) - - if not AppConfig.is_monotonic(self.fcst_segments): - raise ValueError(f'Forecast segments do not increase monotonically: {",".join(self.fcst_segments)}') - - self.wave_runs = None - if self.do_wave: - wave_run = base.get('WAVE_RUN', 'BOTH').lower() - if wave_run in ['both']: - self.wave_runs = ['gfs', 'gdas'] - elif wave_run in ['gfs', 'gdas']: - self.wave_runs = [wave_run] - - self.aero_anl_runs = None - self.aero_fcst_runs = None - if self.do_aero: - aero_anl_run = base.get('AERO_ANL_RUN', 'BOTH').lower() - if aero_anl_run in ['both']: - self.aero_anl_runs = ['gfs', 'gdas'] - elif aero_anl_run in ['gfs', 'gdas']: - self.aero_anl_runs = [aero_anl_run] - aero_fcst_run = base.get('AERO_FCST_RUN', None).lower() - if aero_fcst_run in ['both']: - self.aero_fcst_runs = ['gfs', 'gdas'] - elif aero_fcst_run in ['gfs', 'gdas']: - self.aero_fcst_runs = [aero_fcst_run] + print(f"Generating the XML for a {self.mode}_{self.net} case") def _init_finalize(self, conf: Configuration): print("Finalizing initialize") - # Get a list of all possible config files that would be part of the application - self.configs_names = self._get_app_configs() - # Get task names and runs for the application self.task_names = self.get_task_names() - self.runs = list(self.task_names.keys()) + + # Get a list of all possible config files that could be part of the application + self.configs_names = self._get_app_configs() # Initialize the configs and model_apps dictionaries self.model_apps = dict.fromkeys(self.runs) @@ -117,6 +68,103 @@ def _init_finalize(self, conf: Configuration): # Update the base config dictionary based on application and RUN self.configs[run]['base'] = self._update_base(self.configs[run]['base']) + def _get_run_options(self, conf: Configuration) -> Dict[str, Any]: + ''' + Determine the do_* and APP options for each RUN by sourcing config.base + for each RUN and collecting the flags into self.run_options + ''' + + run_options = dict.fromkeys(self.runs) + for run in self.runs: + #Read config.base with RUN specified + run_base = conf.parse_config('config.base', RUN=run) + + run_options[run]['do_wave_bnd'] = run_base.get('DOBNDPNT_WAVE', False) + run_options[run]['do_bufrsnd'] = run_base.get('DO_BUFRSND', False) + run_options[run]['do_gempak'] = run_base.get('DO_GEMPAK', False) + run_options[run]['do_awips'] = run_base.get('DO_AWIPS', False) + run_options[run]['do_verfozn'] = run_base.get('DO_VERFOZN', True) + run_options[run]['do_verfrad'] = run_base.get('DO_VERFRAD', True) + run_options[run]['do_vminmon'] = run_base.get('DO_VMINMON', True) + run_options[run]['do_tracker'] = run_base.get('DO_TRACKER', True) + run_options[run]['do_genesis'] = run_base.get('DO_GENESIS', True) + run_options[run]['do_genesis_fsu'] = run_base.get('DO_GENESIS_FSU', False) + run_options[run]['do_metp'] = run_base.get('DO_METP', False) + run_options[run]['do_upp'] = not run_base.get('WRITE_DOPOST', True) + run_options[run]['do_goes'] = run_base.get('DO_GOES', False) + run_options[run]['do_mos'] = run_base.get('DO_MOS', False) + run_options[run]['do_extractvars'] = run_base.get('DO_EXTRACTVARS', False) + + run_options[run]['do_atm'] = run_base.get('DO_ATM', True) + run_options[run]['do_wave'] = run_base.get('DO_WAVE', False) + run_options[run]['do_ocean'] = run_base.get('DO_OCN', False) + run_options[run]['do_ice'] = run_base.get('DO_ICE', False) + run_options[run]['do_aero'] = run_base.get('DO_AERO', False) + run_options[run]['do_prep_obs_aero'] = run_base.get('DO_PREP_OBS_AERO', False) + + run_options[run]['do_hpssarch'] = run_base.get('HPSSARCH', False) + run_options[run]['fcst_segments'] = run_base.get('FCST_SEGMENTS', None) + + if not AppConfig.is_monotonic(run_options[run]['fcst_segments']): + raise ValueError(f'Forecast segments do not increase monotonically: {",".join(self.fcst_segments)}') + + .wave_runs = None + if run_options[run]['do_wave']: + wave_run = run_base.get('WAVE_RUN', 'BOTH').lower() + if wave_run in ['both']: + wave_runs = ['gfs', 'gdas'] + elif wave_run in ['gfs', 'gdas']: + wave_runs = [wave_run] + + run_options[run]['do_wave'] = True if run in wave_runs else False + + aero_anl_runs = None + aero_fcst_runs = None + if run_options[run]['do_aero']: + aero_anl_run = run_base.get('AERO_ANL_RUN', 'BOTH').lower() + if aero_anl_run in ['both']: + aero_anl_runs = ['gfs', 'gdas'] + elif aero_anl_run in ['gfs', 'gdas']: + aero_anl_runs = [aero_anl_run] + + aero_fcst_run = base.get('AERO_FCST_RUN', None).lower() + if aero_fcst_run in ['both']: + aero_fcst_runs = ['gfs', 'gdas'] + elif aero_fcst_run in ['gfs', 'gdas']: + aero_fcst_runs = [aero_fcst_run] + + run_options[run]['do_aero_anl'] = True if run in aero_anl_runs else False + run_options[run]['do_aero_fcst'] = True if run in aero_fcst_runs else False + + # Append any MODE-specific options + run_options = self._netmode_run_options(run_base, run_options) + + # Return the dictionary of run options + return run_options + + @abstractmethod + def _netmode_run_options(self, base: Dict[str, Any], run_options: Dict[str, Any]) -> Dict[str, Any]: + ''' + Defines run-based options for a given NET_MODE case. + + Parameters + ---------- + base: Dict + Parsed config.base settings + + run_options: Dict + A dictionary with valid RUN-based sub-dictionaries containing generic options. + + Returns + ------- + run_options: Dict + Output dictionary with additional options valid for the given NET and MODE. + ''' + + # Valid NET_MODE options are defined in the appropriate subclass. + + pass + @abstractmethod def _get_app_configs(self): pass diff --git a/workflow/applications/gefs.py b/workflow/applications/gefs.py index afb4072596f..c4f6a7feda8 100644 --- a/workflow/applications/gefs.py +++ b/workflow/applications/gefs.py @@ -12,6 +12,7 @@ def __init__(self, conf: Configuration): base = conf.parse_config('config.base') self.run = base.get('RUN', 'gefs') + self.runs = [self.run] def _get_app_configs(self): """ diff --git a/workflow/applications/gfs_cycled.py b/workflow/applications/gfs_cycled.py index bbd342784cc..93d523b5855 100644 --- a/workflow/applications/gfs_cycled.py +++ b/workflow/applications/gfs_cycled.py @@ -11,25 +11,40 @@ class GFSCycledAppConfig(AppConfig): def __init__(self, conf: Configuration): super().__init__(conf) + # Re-read config.base without RUN specified to get the basic settings for + # cycled cases to be able to determine valid runs base = conf.parse_config('config.base') - self.do_hybvar = base.get('DOHYBVAR', False) - self.do_fit2obs = base.get('DO_FIT2OBS', True) - self.do_jediatmvar = base.get('DO_JEDIATMVAR', False) - self.do_jediatmens = base.get('DO_JEDIATMENS', False) - self.do_jediocnvar = base.get('DO_JEDIOCNVAR', False) - self.do_jedisnowda = base.get('DO_JEDISNOWDA', False) - self.do_mergensst = base.get('DO_MERGENSST', False) - self.do_vrfy_oceanda = base.get('DO_VRFY_OCEANDA', False) - - self.lobsdiag_forenkf = False - self.eupd_runs = None + self.runs = ["gdas"] + self.runs.append("gfs") if gfs_cyc > 0 else 0 + + self.ens_runs = None + if self.do_hybvar: - self.lobsdiag_forenkf = base.get('lobsdiag_forenkf', False) - eupd_run = base.get('EUPD_CYC', 'gdas').lower() - if eupd_run in ['both']: - self.eupd_runs = ['gfs', 'gdas'] - elif eupd_run in ['gfs', 'gdas']: - self.eupd_runs = [eupd_run] + ens_run = base.get('EUPD_CYC', 'gdas').lower() + if ens_run in ['both']: + self.ens_runs = ['gfs', 'gdas'] + elif ens_run in ['gfs', 'gdas']: + self.ens_runs = [ens_run] + + for ens_run in self.ens_runs: + self.runs.append(f"enkf{ens_run}") if ens_run in self.runs else 0 + + def _netmode_run_options(self, base: Dict[str, Any], run_options: Dict[str, Any]) -> Dict[str, Any]: + + run_options[run]['do_hybvar'] = base.get('DOHYBVAR', False) + run_options[run]['nens'] = base.get('NMEM_ENS', 0) + if run_options[run]['do_hybvar']: + run_options[run]['lobsdiag_forenkf'] = base.get('lobsdiag_forenkf', False) + + run_options[run]['do_fit2obs'] = base.get('DO_FIT2OBS', True) + run_options[run]['do_jediatmvar'] = base.get('DO_JEDIATMVAR', False) + run_options[run]['do_jediatmens'] = base.get('DO_JEDIATMENS', False) + run_options[run]['do_jediocnvar'] = base.get('DO_JEDIOCNVAR', False) + run_options[run]['do_jedisnowda'] = base.get('DO_JEDISNOWDA', False) + run_options[run]['do_mergensst'] = base.get('DO_MERGENSST', False) + run_options[run]['do_vrfy_oceanda'] = base.get('DO_VRFY_OCEANDA', False) + + return run_options def _get_app_configs(self): """ @@ -292,16 +307,16 @@ def get_task_names(self): tasks = dict() tasks['gdas'] = gdas_tasks - if self.do_hybvar and 'gdas' in self.eupd_runs: - enkfgdas_tasks = hybrid_tasks + hybrid_after_eupd_tasks + if self.do_hybvar and 'gdas' in self.ens_runs: + enkfgdas_tasks = hybrid_tasks + hybrid_after_ens_tasks tasks['enkfgdas'] = enkfgdas_tasks # Add RUN=gfs tasks if running early cycle if self.gfs_cyc > 0: tasks['gfs'] = gfs_tasks - if self.do_hybvar and 'gfs' in self.eupd_runs: - enkfgfs_tasks = hybrid_tasks + hybrid_after_eupd_tasks + if self.do_hybvar and 'gfs' in self.ens_runs: + enkfgfs_tasks = hybrid_tasks + hybrid_after_ens_tasks enkfgfs_tasks.remove("echgres") enkfgfs_tasks.remove("esnowrecen") tasks['enkfgfs'] = enkfgfs_tasks diff --git a/workflow/applications/gfs_forecast_only.py b/workflow/applications/gfs_forecast_only.py index 93551ac0cc0..d0b1a9f82c9 100644 --- a/workflow/applications/gfs_forecast_only.py +++ b/workflow/applications/gfs_forecast_only.py @@ -13,6 +13,7 @@ def __init__(self, conf: Configuration): base = conf.parse_config('config.base') self.aero_fcst_run = base.get('AERO_FCST_RUN', 'BOTH').lower() self.run = base.get('RUN', 'gfs') + self.runs = [self.run] self.exp_warm_start = base.get('EXP_WARM_START', False) def _get_app_configs(self): diff --git a/workflow/rocoto/gfs_tasks.py b/workflow/rocoto/gfs_tasks.py index 8f2f612ea60..34b21fdf24f 100644 --- a/workflow/rocoto/gfs_tasks.py +++ b/workflow/rocoto/gfs_tasks.py @@ -45,7 +45,7 @@ def prep(self): dump_path = self._template_to_rocoto_cycstring(self._base["COM_OBSDMP_TMPL"], {'DMPDIR': dmpdir, 'DUMP_SUFFIX': dump_suffix}) - gfs_enkf = True if self.app_config.do_hybvar and 'gfs' in self.app_config.eupd_runs else False + gfs_enkf = True if self.app_config.do_hybvar and 'gfs' in self.app_config.ens_runs else False deps = [] dep_dict = {'type': 'metatask', 'name': 'gdasatmos_prod', 'offset': f"-{timedelta_to_HMS(self._base['cycle_interval'])}"} @@ -337,7 +337,7 @@ def atmanlinit(self): dependencies = rocoto.create_dependency(dep=deps) gfs_cyc = self._base["gfs_cyc"] - gfs_enkf = True if self.app_config.do_hybvar and 'gfs' in self.app_config.eupd_runs else False + gfs_enkf = True if self.app_config.do_hybvar and 'gfs' in self.app_config.ens_runs else False cycledef = self.run if self.run in ['gfs'] and gfs_enkf and gfs_cyc != 4: @@ -2686,8 +2686,6 @@ def _get_ecengroups(): def esfc(self): - # eupd_run = 'gdas' if 'gdas' in self.app_config.eupd_runs else 'gfs' - deps = [] dep_dict = {'type': 'task', 'name': f'{self.run.replace("enkf","")}analcalc'} deps.append(rocoto.add_dependency(dep_dict)) From a4ab75c8a28409a18d982831b59c7247892a66cb Mon Sep 17 00:00:00 2001 From: David Huber Date: Fri, 20 Sep 2024 14:03:45 -0500 Subject: [PATCH 06/36] Move _get_app_configs invokation to _source_configs --- workflow/applications/applications.py | 14 ++++---- workflow/applications/gefs.py | 39 +++++++++++++--------- workflow/applications/gfs_cycled.py | 5 +-- workflow/applications/gfs_forecast_only.py | 8 +++-- 4 files changed, 38 insertions(+), 28 deletions(-) diff --git a/workflow/applications/applications.py b/workflow/applications/applications.py index 0c81a5d7228..0e0ce05b371 100644 --- a/workflow/applications/applications.py +++ b/workflow/applications/applications.py @@ -49,12 +49,12 @@ def __init__(self, conf: Configuration) -> None: def _init_finalize(self, conf: Configuration): print("Finalizing initialize") + # Get run-, net-, and mode-based options + self.run_options = self._get_run_options(conf) + # Get task names and runs for the application self.task_names = self.get_task_names() - # Get a list of all possible config files that could be part of the application - self.configs_names = self._get_app_configs() - # Initialize the configs and model_apps dictionaries self.model_apps = dict.fromkeys(self.runs) self.configs = dict.fromkeys(self.runs) @@ -195,13 +195,11 @@ def _source_configs(self, conf: Configuration, run: str = "gfs", log: bool = Tru Every config depends on "config.base" """ - configs = dict() - - # Return config.base as well - configs['base'] = conf.parse_config('config.base', RUN=run) + # Include config.base by its lonesome + configs{'base': conf.parse_config('config.base', RUN=run)} # Source the list of all config_files involved in the application - for config in self.configs_names: + for config in self._get_app_configs(run): # All must source config.base first files = ['config.base'] diff --git a/workflow/applications/gefs.py b/workflow/applications/gefs.py index c4f6a7feda8..b8e042e0856 100644 --- a/workflow/applications/gefs.py +++ b/workflow/applications/gefs.py @@ -14,27 +14,33 @@ def __init__(self, conf: Configuration): self.run = base.get('RUN', 'gefs') self.runs = [self.run] - def _get_app_configs(self): + def _netmode_run_options(self, base: Dict[str, Any], run_options: Dict[str, Any]) -> Dict[str, Any]: + + # Nothing specific to do for gefs (yet). + return run_options + + def _get_app_configs(self, run): """ Returns the config_files that are involved in gefs """ + options = self.run_options[run] configs = ['stage_ic', 'fcst', 'atmos_products', 'arch', 'cleanup'] - if self.nens > 0: + if options['nens'] > 0: configs += ['efcs', 'atmos_ensstat'] - if self.do_wave: + if options['do_wave']: configs += ['waveinit', 'wavepostsbs', 'wavepostpnt'] - if self.do_wave_bnd: + if options['do_wave_bnd']: configs += ['wavepostbndpnt', 'wavepostbndpntbll'] - if self.do_ocean or self.do_ice: + if options['do_ocean'] or options['do_ice']: configs += ['oceanice_products'] - if self.do_aero: + if options['do_aero']: configs += ['prep_emissions'] - if self.do_extractvars: + if options['do_extractvars']: configs += ['extractvars'] return configs @@ -50,37 +56,38 @@ def _update_base(base_in): def get_task_names(self): + options = self.run_options[self.run] tasks = ['stage_ic'] - if self.do_wave: + if options['do_wave']: tasks += ['waveinit'] - if self.do_aero: + if options['do_aero']: tasks += ['prep_emissions'] tasks += ['fcst'] - if self.nens > 0: + if options['nens'] > 0: tasks += ['efcs'] tasks += ['atmos_prod'] - if self.nens > 0: + if options['nens'] > 0: tasks += ['atmos_ensstat'] - if self.do_ocean: + if options['do_ocean']: tasks += ['ocean_prod'] - if self.do_ice: + if options['do_ice']: tasks += ['ice_prod'] - if self.do_wave: + if options['do_wave']: tasks += ['wavepostsbs'] - if self.do_wave_bnd: + if options['do_wave_bnd']: tasks += ['wavepostbndpnt', 'wavepostbndpntbll'] tasks += ['wavepostpnt'] - if self.do_extractvars: + if options['do_extractvars']: tasks += ['extractvars'] tasks += ['arch', 'cleanup'] diff --git a/workflow/applications/gfs_cycled.py b/workflow/applications/gfs_cycled.py index 93d523b5855..22549489d05 100644 --- a/workflow/applications/gfs_cycled.py +++ b/workflow/applications/gfs_cycled.py @@ -46,10 +46,11 @@ def _netmode_run_options(self, base: Dict[str, Any], run_options: Dict[str, Any] return run_options - def _get_app_configs(self): + def _get_app_configs(self, run): """ - Returns the config_files that are involved in the cycled app + Returns the config files that are involved in the cycled app """ + options = self.run_options[run] configs = ['prep'] diff --git a/workflow/applications/gfs_forecast_only.py b/workflow/applications/gfs_forecast_only.py index d0b1a9f82c9..01c13467b81 100644 --- a/workflow/applications/gfs_forecast_only.py +++ b/workflow/applications/gfs_forecast_only.py @@ -11,10 +11,14 @@ def __init__(self, conf: Configuration): super().__init__(conf) base = conf.parse_config('config.base') - self.aero_fcst_run = base.get('AERO_FCST_RUN', 'BOTH').lower() self.run = base.get('RUN', 'gfs') self.runs = [self.run] - self.exp_warm_start = base.get('EXP_WARM_START', False) + + def _netmode_run_options(self, base: Dict[str, Any], run_options: Dict[str, Any]) -> Dict[str, Any]: + + run_options[self.run]['exp_warm_start'] = base.get('EXP_WARM_START', False) + + return run_options def _get_app_configs(self): """ From 4ae0f3610c958563ecf379f6ed62a13109cd52f1 Mon Sep 17 00:00:00 2001 From: David Huber Date: Tue, 24 Sep 2024 08:36:06 -0500 Subject: [PATCH 07/36] Make the Application classes run-configurable --- workflow/applications/applications.py | 32 +- workflow/applications/gefs.py | 1 + workflow/applications/gfs_cycled.py | 378 ++++++++++----------- workflow/applications/gfs_forecast_only.py | 81 ++--- 4 files changed, 239 insertions(+), 253 deletions(-) diff --git a/workflow/applications/applications.py b/workflow/applications/applications.py index 0e0ce05b371..d977c434266 100644 --- a/workflow/applications/applications.py +++ b/workflow/applications/applications.py @@ -47,7 +47,9 @@ def __init__(self, conf: Configuration) -> None: print(f"Generating the XML for a {self.mode}_{self.net} case") def _init_finalize(self, conf: Configuration): - print("Finalizing initialize") + ''' + Finalize object initialization calling subclass methods + ''' # Get run-, net-, and mode-based options self.run_options = self._get_run_options(conf) @@ -56,17 +58,11 @@ def _init_finalize(self, conf: Configuration): self.task_names = self.get_task_names() # Initialize the configs and model_apps dictionaries - self.model_apps = dict.fromkeys(self.runs) self.configs = dict.fromkeys(self.runs) # Now configure the experiment for each valid run for run in self.runs: - self.configs[run] = self._source_configs(conf, run=run, log=False) - self.model_apps[run] = self.configs[run]['base'].get('APP', 'ATM') - - # Update the base config dictionary based on application and RUN - self.configs[run]['base'] = self._update_base(self.configs[run]['base']) def _get_run_options(self, conf: Configuration) -> Dict[str, Any]: ''' @@ -74,11 +70,12 @@ def _get_run_options(self, conf: Configuration) -> Dict[str, Any]: for each RUN and collecting the flags into self.run_options ''' - run_options = dict.fromkeys(self.runs) + run_options = {run: {} for run in dict.fromkeys(self.runs)} for run in self.runs: - #Read config.base with RUN specified + # Read config.base with RUN specified run_base = conf.parse_config('config.base', RUN=run) + run_options[run]['app'] = run_base.get('APP', 'ATM') run_options[run]['do_wave_bnd'] = run_base.get('DOBNDPNT_WAVE', False) run_options[run]['do_bufrsnd'] = run_base.get('DO_BUFRSND', False) run_options[run]['do_gempak'] = run_base.get('DO_GEMPAK', False) @@ -108,7 +105,7 @@ def _get_run_options(self, conf: Configuration) -> Dict[str, Any]: if not AppConfig.is_monotonic(run_options[run]['fcst_segments']): raise ValueError(f'Forecast segments do not increase monotonically: {",".join(self.fcst_segments)}') - .wave_runs = None + wave_runs = [] if run_options[run]['do_wave']: wave_run = run_base.get('WAVE_RUN', 'BOTH').lower() if wave_run in ['both']: @@ -116,10 +113,8 @@ def _get_run_options(self, conf: Configuration) -> Dict[str, Any]: elif wave_run in ['gfs', 'gdas']: wave_runs = [wave_run] - run_options[run]['do_wave'] = True if run in wave_runs else False - - aero_anl_runs = None - aero_fcst_runs = None + aero_anl_runs = [] + aero_fcst_runs = [] if run_options[run]['do_aero']: aero_anl_run = run_base.get('AERO_ANL_RUN', 'BOTH').lower() if aero_anl_run in ['both']: @@ -133,8 +128,8 @@ def _get_run_options(self, conf: Configuration) -> Dict[str, Any]: elif aero_fcst_run in ['gfs', 'gdas']: aero_fcst_runs = [aero_fcst_run] - run_options[run]['do_aero_anl'] = True if run in aero_anl_runs else False - run_options[run]['do_aero_fcst'] = True if run in aero_fcst_runs else False + run_options[run]['do_aero_anl'] = True if run in aero_anl_runs else False + run_options[run]['do_aero_fcst'] = True if run in aero_fcst_runs else False # Append any MODE-specific options run_options = self._netmode_run_options(run_base, run_options) @@ -195,8 +190,9 @@ def _source_configs(self, conf: Configuration, run: str = "gfs", log: bool = Tru Every config depends on "config.base" """ - # Include config.base by its lonesome - configs{'base': conf.parse_config('config.base', RUN=run)} + # Include config.base by its lonesome and update it + configs = {'base': conf.parse_config('config.base', RUN=run)} + configs['base'] = self._update_base(configs['base']) # Source the list of all config_files involved in the application for config in self._get_app_configs(run): diff --git a/workflow/applications/gefs.py b/workflow/applications/gefs.py index b8e042e0856..5bfaf6ac1ba 100644 --- a/workflow/applications/gefs.py +++ b/workflow/applications/gefs.py @@ -1,4 +1,5 @@ from applications.applications import AppConfig +from typing import Dict, Any from wxflow import Configuration diff --git a/workflow/applications/gfs_cycled.py b/workflow/applications/gfs_cycled.py index 22549489d05..783ce984ba0 100644 --- a/workflow/applications/gfs_cycled.py +++ b/workflow/applications/gfs_cycled.py @@ -1,5 +1,5 @@ -from typing import Dict, Any from applications.applications import AppConfig +from typing import Dict, Any from wxflow import Configuration, to_timedelta from datetime import timedelta @@ -54,86 +54,87 @@ def _get_app_configs(self, run): configs = ['prep'] - if self.do_jediatmvar: + if options['do_jediatmvar']: configs += ['prepatmiodaobs', 'atmanlinit', 'atmanlvar', 'atmanlfv3inc', 'atmanlfinal'] else: configs += ['anal', 'analdiag'] - if self.do_jediocnvar: + if options['do_jediocnvar']: configs += ['prepoceanobs', 'ocnanalprep', 'marinebmat', 'ocnanalrun'] - if self.do_hybvar: + if options['do_hybvar']: configs += ['ocnanalecen'] configs += ['ocnanalchkpt', 'ocnanalpost'] - if self.do_vrfy_oceanda: + if options['do_vrfy_oceanda']: configs += ['ocnanalvrfy'] - if self.do_ocean or self.do_ice: + if options['do_ocean'] or options['do_ice']: configs += ['oceanice_products'] configs += ['stage_ic', 'sfcanl', 'analcalc', 'fcst', 'upp', 'atmos_products', 'arch', 'cleanup'] - if self.do_hybvar: - if self.do_jediatmens: - configs += ['atmensanlinit', 'atmensanlobs', 'atmensanlsol', 'atmensanlletkf', 'atmensanlfv3inc', 'atmensanlfinal'] + if options['do_hybvar']: + if options['do_jediatmens']: + configs += ['atmensanlinit', 'atmensanlobs', 'atmensanlsol', + 'atmensanlletkf', 'atmensanlfv3inc', 'atmensanlfinal'] else: configs += ['eobs', 'eomg', 'ediag', 'eupd'] configs += ['ecen', 'esfc', 'efcs', 'echgres', 'epos', 'earc'] - if self.do_fit2obs: + if options['do_fit2obs']: configs += ['fit2obs'] - if self.do_verfozn: + if options['do_verfozn']: configs += ['verfozn'] - if self.do_verfrad: + if options['do_verfrad']: configs += ['verfrad'] - if self.do_vminmon: + if options['do_vminmon']: configs += ['vminmon'] - if self.do_tracker: + if options['do_tracker']: configs += ['tracker'] - if self.do_genesis: + if options['do_genesis']: configs += ['genesis'] - if self.do_genesis_fsu: + if options['do_genesis_fsu']: configs += ['genesis_fsu'] - if self.do_metp: + if options['do_metp']: configs += ['metp'] - if self.do_gempak: + if options['do_gempak']: configs += ['gempak'] - if self.do_goes: + if options['do_goes']: configs += ['npoess'] - if self.do_bufrsnd: + if options['do_bufrsnd']: configs += ['postsnd'] - if self.do_awips: + if options['do_awips']: configs += ['awips'] - if self.do_wave: + if options['do_wave']: configs += ['waveinit', 'waveprep', 'wavepostsbs', 'wavepostpnt'] - if self.do_wave_bnd: + if options['do_wave_bnd']: configs += ['wavepostbndpnt', 'wavepostbndpntbll'] - if self.do_gempak: + if options['do_gempak']: configs += ['wavegempak'] - if self.do_awips: + if options['do_awips']: configs += ['waveawipsbulls', 'waveawipsgridded'] - if self.do_aero: + if options['do_aero']: configs += ['aeroanlgenb', 'aeroanlinit', 'aeroanlvar', 'aeroanlfinal'] - if self.do_prep_obs_aero: + if options['do_prep_obs_aero']: configs += ['prepobsaero'] - if self.do_jedisnowda: + if options['do_jedisnowda']: configs += ['prepsnowobs', 'snowanl'] - if self.do_hybvar: + if options['do_hybvar']: configs += ['esnowrecen'] - if self.do_mos: + if options['do_mos']: configs += ['mos_stn_prep', 'mos_grd_prep', 'mos_ext_stn_prep', 'mos_ext_grd_prep', 'mos_stn_fcst', 'mos_grd_fcst', 'mos_ext_stn_fcst', 'mos_ext_grd_fcst', 'mos_stn_prdgen', 'mos_grd_prdgen', 'mos_ext_stn_prdgen', 'mos_ext_grd_prdgen', @@ -149,178 +150,163 @@ def _update_base(base_in): def get_task_names(self): """ Get the task names for each valid run in this cycled configuration. - Note that the order of the task names matters in the XML. - This is the place where that order is set. + NOTE: The order of the task names matters in the XML. + This is the place where that order is set. """ - gdas_gfs_common_tasks_before_fcst = ['prep'] - gdas_gfs_common_cleanup_tasks = ['arch', 'cleanup'] - - if self.do_jediatmvar: - gdas_gfs_common_tasks_before_fcst += ['prepatmiodaobs', 'atmanlinit', 'atmanlvar', 'atmanlfv3inc', 'atmanlfinal'] - else: - gdas_gfs_common_tasks_before_fcst += ['anal'] - - if self.do_jediocnvar: - gdas_gfs_common_tasks_before_fcst += ['prepoceanobs', 'ocnanalprep', 'marinebmat', 'ocnanalrun'] - if self.do_hybvar: - gdas_gfs_common_tasks_before_fcst += ['ocnanalecen'] - gdas_gfs_common_tasks_before_fcst += ['ocnanalchkpt', 'ocnanalpost'] - if self.do_vrfy_oceanda: - gdas_gfs_common_tasks_before_fcst += ['ocnanalvrfy'] - - gdas_gfs_common_tasks_before_fcst += ['sfcanl', 'analcalc'] - - if self.do_jedisnowda: - gdas_gfs_common_tasks_before_fcst += ['prepsnowobs', 'snowanl'] - - wave_prep_tasks = ['waveinit', 'waveprep'] - wave_bndpnt_tasks = ['wavepostbndpnt', 'wavepostbndpntbll'] - wave_post_tasks = ['wavepostsbs', 'wavepostpnt'] - - hybrid_tasks = [] - hybrid_after_eupd_tasks = [] - if self.do_hybvar: - if self.do_jediatmens: - hybrid_tasks += ['atmensanlinit', 'atmensanlfv3inc', 'atmensanlfinal', 'echgres'] - hybrid_tasks += ['atmensanlobs', 'atmensanlsol'] if self.lobsdiag_forenkf else ['atmensanlletkf'] - else: - hybrid_tasks += ['eobs', 'eupd', 'echgres'] - hybrid_tasks += ['ediag'] if self.lobsdiag_forenkf else ['eomg'] - if self.do_jedisnowda: - hybrid_tasks += ['esnowrecen'] - hybrid_after_eupd_tasks += ['stage_ic', 'ecen', 'esfc', 'efcs', 'epos', 'earc', 'cleanup'] - - # Collect all "gdas" cycle tasks - gdas_tasks = gdas_gfs_common_tasks_before_fcst.copy() - - if not self.do_jediatmvar: - gdas_tasks += ['analdiag'] - - if self.do_wave and 'gdas' in self.wave_runs: - gdas_tasks += wave_prep_tasks - - if self.do_aero and 'gdas' in self.aero_anl_runs: - gdas_tasks += ['aeroanlgenb', 'aeroanlinit', 'aeroanlvar', 'aeroanlfinal'] - if self.do_prep_obs_aero: - gdas_tasks += ['prepobsaero'] - - gdas_tasks += ['stage_ic', 'atmanlupp', 'atmanlprod', 'fcst'] - - if self.do_upp: - gdas_tasks += ['atmupp'] - gdas_tasks += ['atmos_prod'] - - if self.do_wave and 'gdas' in self.wave_runs: - if self.do_wave_bnd: - gdas_tasks += wave_bndpnt_tasks - gdas_tasks += wave_post_tasks - - if self.do_fit2obs: - gdas_tasks += ['fit2obs'] - - if self.do_verfozn: - gdas_tasks += ['verfozn'] - - if self.do_verfrad: - gdas_tasks += ['verfrad'] - - if self.do_vminmon: - gdas_tasks += ['vminmon'] - - if self.do_gempak: - gdas_tasks += ['gempak', 'gempakmetancdc'] - - gdas_tasks += gdas_gfs_common_cleanup_tasks - - # Collect "gfs" cycle tasks - gfs_tasks = gdas_gfs_common_tasks_before_fcst.copy() - - if self.do_wave and 'gfs' in self.wave_runs: - gfs_tasks += wave_prep_tasks - - if self.do_aero and 'gfs' in self.aero_anl_runs: - gfs_tasks += ['aeroanlinit', 'aeroanlvar', 'aeroanlfinal'] - if self.do_prep_obs_aero: - gfs_tasks += ['prepobsaero'] - - gfs_tasks += ['atmanlupp', 'atmanlprod', 'fcst'] - - if self.do_ocean: - gfs_tasks += ['ocean_prod'] - - if self.do_ice: - gfs_tasks += ['ice_prod'] - - if self.do_upp: - gfs_tasks += ['atmupp'] - gfs_tasks += ['atmos_prod'] - - if self.do_goes: - gfs_tasks += ['goesupp'] - - if self.do_vminmon: - gfs_tasks += ['vminmon'] - - if self.do_tracker: - gfs_tasks += ['tracker'] - - if self.do_genesis: - gfs_tasks += ['genesis'] - - if self.do_genesis_fsu: - gfs_tasks += ['genesis_fsu'] - - if self.do_metp: - gfs_tasks += ['metp'] - - if self.do_wave and 'gfs' in self.wave_runs: - if self.do_wave_bnd: - gfs_tasks += wave_bndpnt_tasks - gfs_tasks += wave_post_tasks - if self.do_gempak: - gfs_tasks += ['wavegempak'] - if self.do_awips: - gfs_tasks += ['waveawipsbulls', 'waveawipsgridded'] - - if self.do_bufrsnd: - gfs_tasks += ['postsnd'] - - if self.do_gempak: - gfs_tasks += ['gempak'] - gfs_tasks += ['gempakmeta'] - gfs_tasks += ['gempakncdcupapgif'] - if self.do_goes: - gfs_tasks += ['npoess_pgrb2_0p5deg'] - gfs_tasks += ['gempakpgrb2spec'] - - if self.do_awips: - gfs_tasks += ['awips_20km_1p0deg', 'fbwind'] - - if self.do_mos: - gfs_tasks += ['mos_stn_prep', 'mos_grd_prep', 'mos_ext_stn_prep', 'mos_ext_grd_prep', - 'mos_stn_fcst', 'mos_grd_fcst', 'mos_ext_stn_fcst', 'mos_ext_grd_fcst', - 'mos_stn_prdgen', 'mos_grd_prdgen', 'mos_ext_stn_prdgen', 'mos_ext_grd_prdgen', - 'mos_wx_prdgen', 'mos_wx_ext_prdgen'] - - gfs_tasks += gdas_gfs_common_cleanup_tasks + # Start with a dictionary of empty task lists for each valid run + task_names = {run: [] for run in self.runs} - tasks = dict() - tasks['gdas'] = gdas_tasks + for run in self.runs: + options = self.run_options(run) - if self.do_hybvar and 'gdas' in self.ens_runs: - enkfgdas_tasks = hybrid_tasks + hybrid_after_ens_tasks - tasks['enkfgdas'] = enkfgdas_tasks + # Common gdas and gfs tasks before fcst + if run in ['gdas', 'gfs']: + task_names[run] = ['prep'] + if options['do_jediatmvar']: + task_names[run] += ['prepatmiodaobs', 'atmanlinit', 'atmanlvar', 'atmanlfv3inc', 'atmanlfinal'] + else: + task_names[run] += ['anal'] - # Add RUN=gfs tasks if running early cycle - if self.gfs_cyc > 0: - tasks['gfs'] = gfs_tasks + if options['do_jediocnvar']: + task_names[run] += ['prepoceanobs', 'ocnanalprep', 'marinebmat', 'ocnanalrun'] + if options['do_hybvar']: + task_names[run] += ['ocnanalecen', 'ocnanalchkpt', 'ocnanalpost'] + if options['do_vrfy_oceanda']: + task_names[run] += ['ocnanalvrfy'] + + task_names[run] += ['sfcanl', 'analcalc'] + + if options['do_jedisnowda']: + task_names[run] += ['prepsnowobs', 'snowanl'] - if self.do_hybvar and 'gfs' in self.ens_runs: - enkfgfs_tasks = hybrid_tasks + hybrid_after_ens_tasks - enkfgfs_tasks.remove("echgres") - enkfgfs_tasks.remove("esnowrecen") - tasks['enkfgfs'] = enkfgfs_tasks + if options['do_wave'] and run in options['wave_runs']: + task_names[run] += ['waveinit', 'waveprep'] + + # gdas-specific analysis tasks + if run == 'gdas': + if not options['do_jediatmvar']: + task_names[run] += ['analdiag'] + + if options['do_aero'] and run in options['aero_anl_runs']: + task_names[run] += ['aeroanlgenb'] + + if options['do_aero'] and run in options[aero_anl_runs]: + task_names[run] = ['aeroanlinit', 'aeroanlvar', 'aeroanlfinal'] + + if options['do_aero'] and run in options['aero_anl_runs']: + task_names[run] += ['aeroanlgenb', 'aeroanlinit', 'aeroanlvar', 'aeroanlfinal'] + if options['do_prep_obs_aero']: + task_names[run] += ['prepobsaero'] + + # Staging is gdas-specific + if run == 'gdas': + task_names[run] += ['stage_ic'] + + task_name[run] += ['atmanlupp', 'atmanlprod', 'fcst'] + + # gfs-specific products + if run == 'gfs': + if options['do_ocean']: + task_names[run] += ['ocean_prod'] + + if options['do_ice']: + task_names[run] += ['ice_prod'] + + if options['do_upp']: + task_names[run] += ['atmupp'] + task_names[run] += ['atmos_prod'] + + # GOES post-processing (gfs only) + if run == 'gfs': + if options['do_goes']: + task_names[run] += ['goesupp'] + + if options['do_vminmon']: + task_names[run] += ['vminmon'] + + if options['do_fit2obs']: + task_names[run] += ['fit2obs'] + + if options['do_verfozn']: + task_names[run] += ['verfozn'] + + if options['do_verfrad']: + task_names[run] += ['verfrad'] + + # gfs-only verification/tracking + if run == 'gfs': + if options['do_tracker']: + task_names[run] += ['tracker'] + + if options['do_genesis']: + task_names[run] += ['genesis'] + + if options['do_genesis_fsu']: + task_names[run] += ['genesis_fsu'] + + if options['do_metp']: + task_names[run] += ['metp'] + + if options['do_wave'] and run in options['wave_runs']: + if options['do_wave_bnd']: + task_names[run] += ['wavepostbndpnt', 'wavepostbndpntbll'] + task_names[run] += ['wavepostsbs', 'wavepostpnt'] + # wave gempak and awips jobs are gfs-specific + if run == 'gfs': + if options['do_gempak']: + task_names[run] += ['wavegempak'] + if options['do_awips']: + task_names[run] += ['waveawipsbulls', 'waveawipsgridded'] + + # gdas-specific gempak jobs + if run == 'gdas': + if options['do_gempak']: + task_names[run] += ['gempak', 'gempakmetancdc'] + + # gfs-specific post products + if run == 'gfs': + if options['do_bufrsnd']: + task_names[run] += ['postsnd'] + + if options['do_gempak']: + task_names[run] += ['gempak'] + task_names[run] += ['gempakmeta'] + task_names[run] += ['gempakncdcupapgif'] + if options['do_goes']: + task_names[run] += ['npoess_pgrb2_0p5deg'] + task_names[run] += ['gempakpgrb2spec'] + + if options['do_awips']: + task_names[run] += ['awips_20km_1p0deg', 'fbwind'] + + if options['do_mos']: + task_names[run] += ['mos_stn_prep', 'mos_grd_prep', 'mos_ext_stn_prep', 'mos_ext_grd_prep', + 'mos_stn_fcst', 'mos_grd_fcst', 'mos_ext_stn_fcst', 'mos_ext_grd_fcst', + 'mos_stn_prdgen', 'mos_grd_prdgen', 'mos_ext_stn_prdgen', + 'mos_ext_grd_prdgen', 'mos_wx_prdgen', 'mos_wx_ext_prdgen'] + + # Last two items + task_names[run] += ['arch', 'cleanup'] + + # Ensemble tasks + elif 'enkf' in run: + + if options['do_jediatmens']: + task_names[run] += ['atmensanlinit', 'atmensanlfv3inc', 'atmensanlfinal'] + # Only run echgres for the gdas cycle + task_names[run] += ['echgres'] if 'gdas' in run else 0 + if options['lobsdiag_forenkf']: + task_names[run] += ['atmensanlobs', 'atmensanlsol'] + else: + task_names[run] += ['atmensanlletkf'] + + else: + task_names[run] += ['eobs', 'eupd'] + task_names[run] += ['echgres'] if 'gdas' in run else 0 + task_names[run] += ['ediag'] if options['lobsdiag_forenkf'] else ['eomg'] + + task_names[run] += ['stage_ic', 'ecen', 'esfc', 'efcs', 'epos', 'earc', 'cleanup'] return tasks diff --git a/workflow/applications/gfs_forecast_only.py b/workflow/applications/gfs_forecast_only.py index 01c13467b81..11f00a614a5 100644 --- a/workflow/applications/gfs_forecast_only.py +++ b/workflow/applications/gfs_forecast_only.py @@ -1,5 +1,6 @@ from applications.applications import AppConfig from wxflow import Configuration +from typing import Dict, Any class GFSForecastOnlyAppConfig(AppConfig): @@ -20,58 +21,59 @@ def _netmode_run_options(self, base: Dict[str, Any], run_options: Dict[str, Any] return run_options - def _get_app_configs(self): + def _get_app_configs(self, run): """ Returns the config_files that are involved in the forecast-only app """ + options = self.run_options[run] configs = ['stage_ic', 'fcst', 'arch', 'cleanup'] - if self.do_atm: + if options['do_atm']: - if self.do_upp or self.do_goes: + if options['do_upp'] or options['do_goes']: configs += ['upp'] configs += ['atmos_products'] - if self.do_aero: - if not self.exp_warm_start: + if options['do_aero']: + if not options['exp_warm_start']: configs += ['aerosol_init'] - if self.do_tracker: + if options['do_tracker']: configs += ['tracker'] - if self.do_genesis: + if options['do_genesis']: configs += ['genesis'] - if self.do_genesis_fsu: + if options['do_genesis_fsu']: configs += ['genesis_fsu'] - if self.do_metp: + if options['do_metp']: configs += ['metp'] - if self.do_bufrsnd: + if options['do_bufrsnd']: configs += ['postsnd'] - if self.do_gempak: + if options['do_gempak']: configs += ['gempak'] - if self.do_awips: + if options['do_awips']: configs += ['awips'] - if self.do_ocean or self.do_ice: + if options['do_ocean'] or options['do_ice']: configs += ['oceanice_products'] - if self.do_wave: + if options['do_wave']: configs += ['waveinit', 'waveprep', 'wavepostsbs', 'wavepostpnt'] - if self.do_wave_bnd: + if options['do_wave_bnd']: configs += ['wavepostbndpnt', 'wavepostbndpntbll'] - if self.do_gempak: + if options['do_gempak']: configs += ['wavegempak'] - if self.do_awips: + if options['do_awips']: configs += ['waveawipsbulls', 'waveawipsgridded'] - if self.do_mos: + if options['do_mos']: configs += ['mos_stn_prep', 'mos_grd_prep', 'mos_ext_stn_prep', 'mos_ext_grd_prep', 'mos_stn_fcst', 'mos_grd_fcst', 'mos_ext_stn_fcst', 'mos_ext_grd_fcst', 'mos_stn_prdgen', 'mos_grd_prdgen', 'mos_ext_stn_prdgen', 'mos_ext_grd_prdgen', @@ -95,66 +97,67 @@ def get_task_names(self): """ tasks = ['stage_ic'] + options = self.run_options[self.run] - if self.do_aero: - aero_fcst_run = self.aero_fcst_run + if options['do_aero']: + aero_fcst_run = options['aero_fcst_run'] if self.run in aero_fcst_run or aero_fcst_run == "both": - if not self.exp_warm_start: + if not options['exp_warm_start']: tasks += ['aerosol_init'] - if self.do_wave: + if options['do_wave']: tasks += ['waveinit'] # tasks += ['waveprep'] # TODO - verify if waveprep is executed in forecast-only mode when APP=ATMW|S2SW tasks += ['fcst'] - if self.do_atm: + if options['do_atm']: - if self.do_upp: + if options['do_upp']: tasks += ['atmupp'] tasks += ['atmos_prod'] - if self.do_goes: + if options['do_goes']: tasks += ['goesupp'] - if self.do_tracker: + if options['do_tracker']: tasks += ['tracker'] - if self.do_genesis: + if options['do_genesis']: tasks += ['genesis'] - if self.do_genesis_fsu: + if options['do_genesis_fsu']: tasks += ['genesis_fsu'] - if self.do_metp: + if options['do_metp']: tasks += ['metp'] - if self.do_bufrsnd: + if options['do_bufrsnd']: tasks += ['postsnd'] - if self.do_gempak: + if options['do_gempak']: tasks += ['gempak', 'gempakmeta', 'gempakncdcupapgif', 'gempakpgrb2spec'] - if self.do_awips: + if options['do_awips']: tasks += ['awips_20km_1p0deg', 'fbwind'] - if self.do_ocean: + if options['do_ocean']: tasks += ['ocean_prod'] - if self.do_ice: + if options['do_ice']: tasks += ['ice_prod'] - if self.do_wave: - if self.do_wave_bnd: + if options['do_wave']: + if options['do_wave_bnd']: tasks += ['wavepostbndpnt', 'wavepostbndpntbll'] tasks += ['wavepostsbs', 'wavepostpnt'] - if self.do_gempak: + if options['do_gempak']: tasks += ['wavegempak'] - if self.do_awips: + if options['do_awips']: tasks += ['waveawipsbulls', 'waveawipsgridded'] - if self.do_mos: + if options['do_mos']: tasks += ['mos_stn_prep', 'mos_grd_prep', 'mos_ext_stn_prep', 'mos_ext_grd_prep', 'mos_stn_fcst', 'mos_grd_fcst', 'mos_ext_stn_fcst', 'mos_ext_grd_fcst', 'mos_stn_prdgen', 'mos_grd_prdgen', 'mos_ext_stn_prdgen', 'mos_ext_grd_prdgen', From 4374bad3c18e0fb63afd39149f12fa62b7d77447 Mon Sep 17 00:00:00 2001 From: David Huber Date: Tue, 24 Sep 2024 08:56:02 -0500 Subject: [PATCH 08/36] Fix f-string formating --- workflow/rocoto/tasks.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/workflow/rocoto/tasks.py b/workflow/rocoto/tasks.py index 43b2166b034..ea84f5143a3 100644 --- a/workflow/rocoto/tasks.py +++ b/workflow/rocoto/tasks.py @@ -244,6 +244,6 @@ def get_task(self, task_name, *args, **kwargs): try: return getattr(self, task_name, *args, **kwargs)() except AttributeError: - raise AttributeError(f'"{task_name}" is not a valid task.\n' + - 'Valid tasks are:\n' + + raise AttributeError(f'"{task_name}" is not a valid task.\n' + f'Valid tasks are:\n' f'{", ".join(Tasks.VALID_TASKS)}') From e6b1920b5207f3b742a29c473a8dde2fe434d6c0 Mon Sep 17 00:00:00 2001 From: David Huber Date: Tue, 24 Sep 2024 15:02:14 -0500 Subject: [PATCH 09/36] Application bug fixes --- workflow/applications/applications.py | 7 ++++- workflow/applications/gefs.py | 3 ++- workflow/applications/gfs_cycled.py | 39 ++++++++++++++------------- 3 files changed, 28 insertions(+), 21 deletions(-) diff --git a/workflow/applications/applications.py b/workflow/applications/applications.py index d977c434266..03e47089d78 100644 --- a/workflow/applications/applications.py +++ b/workflow/applications/applications.py @@ -113,6 +113,8 @@ def _get_run_options(self, conf: Configuration) -> Dict[str, Any]: elif wave_run in ['gfs', 'gdas']: wave_runs = [wave_run] + run_options[run]['wave_runs'] = wave_runs + aero_anl_runs = [] aero_fcst_runs = [] if run_options[run]['do_aero']: @@ -122,7 +124,7 @@ def _get_run_options(self, conf: Configuration) -> Dict[str, Any]: elif aero_anl_run in ['gfs', 'gdas']: aero_anl_runs = [aero_anl_run] - aero_fcst_run = base.get('AERO_FCST_RUN', None).lower() + aero_fcst_run = run_base.get('AERO_FCST_RUN', None).lower() if aero_fcst_run in ['both']: aero_fcst_runs = ['gfs', 'gdas'] elif aero_fcst_run in ['gfs', 'gdas']: @@ -131,6 +133,9 @@ def _get_run_options(self, conf: Configuration) -> Dict[str, Any]: run_options[run]['do_aero_anl'] = True if run in aero_anl_runs else False run_options[run]['do_aero_fcst'] = True if run in aero_fcst_runs else False + run_options[run]['aero_anl_runs'] = aero_anl_runs + run_options[run]['aero_fcst_runs'] = aero_fcst_runs + # Append any MODE-specific options run_options = self._netmode_run_options(run_base, run_options) diff --git a/workflow/applications/gefs.py b/workflow/applications/gefs.py index 5bfaf6ac1ba..b1a8965ef42 100644 --- a/workflow/applications/gefs.py +++ b/workflow/applications/gefs.py @@ -17,7 +17,8 @@ def __init__(self, conf: Configuration): def _netmode_run_options(self, base: Dict[str, Any], run_options: Dict[str, Any]) -> Dict[str, Any]: - # Nothing specific to do for gefs (yet). + run_options[self.run]['nens'] = base.get('NMEM_ENS', 0) + print(run_options[self.run]['nens']) return run_options def _get_app_configs(self, run): diff --git a/workflow/applications/gfs_cycled.py b/workflow/applications/gfs_cycled.py index 783ce984ba0..7bfa8fd1230 100644 --- a/workflow/applications/gfs_cycled.py +++ b/workflow/applications/gfs_cycled.py @@ -15,11 +15,11 @@ def __init__(self, conf: Configuration): # cycled cases to be able to determine valid runs base = conf.parse_config('config.base') self.runs = ["gdas"] - self.runs.append("gfs") if gfs_cyc > 0 else 0 + self.runs.append("gfs") if base['gfs_cyc'] > 0 else 0 self.ens_runs = None - if self.do_hybvar: + if base.get('DOHYBVAR', False): ens_run = base.get('EUPD_CYC', 'gdas').lower() if ens_run in ['both']: self.ens_runs = ['gfs', 'gdas'] @@ -31,18 +31,19 @@ def __init__(self, conf: Configuration): def _netmode_run_options(self, base: Dict[str, Any], run_options: Dict[str, Any]) -> Dict[str, Any]: - run_options[run]['do_hybvar'] = base.get('DOHYBVAR', False) - run_options[run]['nens'] = base.get('NMEM_ENS', 0) - if run_options[run]['do_hybvar']: - run_options[run]['lobsdiag_forenkf'] = base.get('lobsdiag_forenkf', False) - - run_options[run]['do_fit2obs'] = base.get('DO_FIT2OBS', True) - run_options[run]['do_jediatmvar'] = base.get('DO_JEDIATMVAR', False) - run_options[run]['do_jediatmens'] = base.get('DO_JEDIATMENS', False) - run_options[run]['do_jediocnvar'] = base.get('DO_JEDIOCNVAR', False) - run_options[run]['do_jedisnowda'] = base.get('DO_JEDISNOWDA', False) - run_options[run]['do_mergensst'] = base.get('DO_MERGENSST', False) - run_options[run]['do_vrfy_oceanda'] = base.get('DO_VRFY_OCEANDA', False) + for run in self.runs: + run_options[run]['do_hybvar'] = base.get('DOHYBVAR', False) + run_options[run]['nens'] = base.get('NMEM_ENS', 0) + if run_options[run]['do_hybvar']: + run_options[run]['lobsdiag_forenkf'] = base.get('lobsdiag_forenkf', False) + + run_options[run]['do_fit2obs'] = base.get('DO_FIT2OBS', True) + run_options[run]['do_jediatmvar'] = base.get('DO_JEDIATMVAR', False) + run_options[run]['do_jediatmens'] = base.get('DO_JEDIATMENS', False) + run_options[run]['do_jediocnvar'] = base.get('DO_JEDIOCNVAR', False) + run_options[run]['do_jedisnowda'] = base.get('DO_JEDISNOWDA', False) + run_options[run]['do_mergensst'] = base.get('DO_MERGENSST', False) + run_options[run]['do_vrfy_oceanda'] = base.get('DO_VRFY_OCEANDA', False) return run_options @@ -158,7 +159,7 @@ def get_task_names(self): task_names = {run: [] for run in self.runs} for run in self.runs: - options = self.run_options(run) + options = self.run_options[run] # Common gdas and gfs tasks before fcst if run in ['gdas', 'gfs']: @@ -180,7 +181,7 @@ def get_task_names(self): if options['do_jedisnowda']: task_names[run] += ['prepsnowobs', 'snowanl'] - if options['do_wave'] and run in options['wave_runs']: + if options['do_wave'] and run in self.wave_runs: task_names[run] += ['waveinit', 'waveprep'] # gdas-specific analysis tasks @@ -191,7 +192,7 @@ def get_task_names(self): if options['do_aero'] and run in options['aero_anl_runs']: task_names[run] += ['aeroanlgenb'] - if options['do_aero'] and run in options[aero_anl_runs]: + if options['do_aero'] and run in options['aero_anl_runs']: task_names[run] = ['aeroanlinit', 'aeroanlvar', 'aeroanlfinal'] if options['do_aero'] and run in options['aero_anl_runs']: @@ -203,7 +204,7 @@ def get_task_names(self): if run == 'gdas': task_names[run] += ['stage_ic'] - task_name[run] += ['atmanlupp', 'atmanlprod', 'fcst'] + task_names[run] += ['atmanlupp', 'atmanlprod', 'fcst'] # gfs-specific products if run == 'gfs': @@ -308,7 +309,7 @@ def get_task_names(self): task_names[run] += ['stage_ic', 'ecen', 'esfc', 'efcs', 'epos', 'earc', 'cleanup'] - return tasks + return task_names @staticmethod def get_gfs_cyc_dates(base: Dict[str, Any]) -> Dict[str, Any]: From d94b685ac2b4c93ea20d7025256f57d2141e854c Mon Sep 17 00:00:00 2001 From: David Huber Date: Wed, 25 Sep 2024 06:24:04 -0500 Subject: [PATCH 10/36] Update app_config options in the Tasks classes --- workflow/rocoto/gefs_tasks.py | 32 ++++++------- workflow/rocoto/gfs_tasks.py | 84 +++++++++++++++++------------------ workflow/rocoto/tasks.py | 3 ++ 3 files changed, 61 insertions(+), 58 deletions(-) diff --git a/workflow/rocoto/gefs_tasks.py b/workflow/rocoto/gefs_tasks.py index 8a4f148f246..35e1dd2ae55 100644 --- a/workflow/rocoto/gefs_tasks.py +++ b/workflow/rocoto/gefs_tasks.py @@ -69,17 +69,17 @@ def fcst(self): dep_dict = {'type': 'task', 'name': f'stage_ic'} dependencies.append(rocoto.add_dependency(dep_dict)) - if self.app_config.do_wave: + if self.options['do_wave']: dep_dict = {'type': 'task', 'name': f'wave_init'} dependencies.append(rocoto.add_dependency(dep_dict)) - if self.app_config.do_aero: + if self.options['do_aero']: dep_dict = {'type': 'task', 'name': f'prep_emissions'} dependencies.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep_condition='and', dep=dependencies) - num_fcst_segments = len(self.app_config.fcst_segments) - 1 + num_fcst_segments = len(self.options['fcst_segments']) - 1 fcst_vars = self.envars.copy() fcst_envars_dict = {'FCST_SEGMENT': '#seg#'} @@ -115,17 +115,17 @@ def efcs(self): dep_dict = {'type': 'task', 'name': f'stage_ic'} dependencies.append(rocoto.add_dependency(dep_dict)) - if self.app_config.do_wave: + if self.options['do_wave']: dep_dict = {'type': 'task', 'name': f'wave_init'} dependencies.append(rocoto.add_dependency(dep_dict)) - if self.app_config.do_aero: + if self.options['do_aero']: dep_dict = {'type': 'task', 'name': f'prep_emissions'} dependencies.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep_condition='and', dep=dependencies) - num_fcst_segments = len(self.app_config.fcst_segments) - 1 + num_fcst_segments = len(self.options['fcst_segments']) - 1 resources = self.get_resource('efcs') # Kludge to work around bug in rocoto with serial metatasks nested @@ -444,7 +444,7 @@ def wavepostpnt(self): deps = [] dep_dict = {'type': 'metatask', 'name': f'fcst_mem#member#'} deps.append(rocoto.add_dependency(dep_dict)) - if self.app_config.do_wave_bnd: + if self.options['do_wave_bnd']: dep_dict = {'type': 'task', 'name': f'wave_post_bndpnt_bull_mem#member#'} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep_condition='and', dep=deps) @@ -481,16 +481,16 @@ def wavepostpnt(self): def extractvars(self): deps = [] - if self.app_config.do_wave: + if self.options['do_wave']: dep_dict = {'type': 'task', 'name': 'wave_post_grid_mem#member#'} deps.append(rocoto.add_dependency(dep_dict)) - if self.app_config.do_ocean: + if self.options['do_ocean']: dep_dict = {'type': 'metatask', 'name': 'ocean_prod_#member#'} deps.append(rocoto.add_dependency(dep_dict)) - if self.app_config.do_ice: + if self.options['do_ice']: dep_dict = {'type': 'metatask', 'name': 'ice_prod_#member#'} deps.append(rocoto.add_dependency(dep_dict)) - if self.app_config.do_atm: + if self.options['do_atm']: dep_dict = {'type': 'metatask', 'name': 'atmos_prod_#member#'} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep_condition='and', dep=deps) @@ -530,23 +530,23 @@ def arch(self): deps.append(rocoto.add_dependency(dep_dict)) dep_dict = {'type': 'metatask', 'name': 'atmos_ensstat'} deps.append(rocoto.add_dependency(dep_dict)) - if self.app_config.do_ice: + if self.options['do_ice']: dep_dict = {'type': 'metatask', 'name': 'ice_prod'} deps.append(rocoto.add_dependency(dep_dict)) - if self.app_config.do_ocean: + if self.options['do_ocean']: dep_dict = {'type': 'metatask', 'name': 'ocean_prod'} deps.append(rocoto.add_dependency(dep_dict)) - if self.app_config.do_wave: + if self.options['do_wave']: dep_dict = {'type': 'metatask', 'name': 'wave_post_grid'} deps.append(rocoto.add_dependency(dep_dict)) dep_dict = {'type': 'metatask', 'name': 'wave_post_pnt'} deps.append(rocoto.add_dependency(dep_dict)) - if self.app_config.do_wave_bnd: + if self.options['do_wave_bnd']: dep_dict = {'type': 'metatask', 'name': 'wave_post_bndpnt'} deps.append(rocoto.add_dependency(dep_dict)) dep_dict = {'type': 'metatask', 'name': 'wave_post_bndpnt_bull'} deps.append(rocoto.add_dependency(dep_dict)) - if self.app_config.do_extractvars: + if self.options['do_extractvars']: dep_dict = {'type': 'metatask', 'name': 'extractvars'} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep=deps, dep_condition='and') diff --git a/workflow/rocoto/gfs_tasks.py b/workflow/rocoto/gfs_tasks.py index 34b21fdf24f..fb3f8227412 100644 --- a/workflow/rocoto/gfs_tasks.py +++ b/workflow/rocoto/gfs_tasks.py @@ -45,7 +45,7 @@ def prep(self): dump_path = self._template_to_rocoto_cycstring(self._base["COM_OBSDMP_TMPL"], {'DMPDIR': dmpdir, 'DUMP_SUFFIX': dump_suffix}) - gfs_enkf = True if self.app_config.do_hybvar and 'gfs' in self.app_config.ens_runs else False + gfs_enkf = True if self.options['do_hybvar'] and 'gfs' in self.app_config.ens_runs else False deps = [] dep_dict = {'type': 'metatask', 'name': 'gdasatmos_prod', 'offset': f"-{timedelta_to_HMS(self._base['cycle_interval'])}"} @@ -188,7 +188,7 @@ def anal(self): deps = [] dep_dict = {'type': 'task', 'name': f'{self.run}prep'} deps.append(rocoto.add_dependency(dep_dict)) - if self.app_config.do_hybvar: + if self.options['do_hybvar']: dep_dict = {'type': 'metatask', 'name': 'enkfgdasepmn', 'offset': f"-{timedelta_to_HMS(self._base['cycle_interval'])}"} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep_condition='and', dep=deps) @@ -215,12 +215,12 @@ def anal(self): def sfcanl(self): deps = [] - if self.app_config.do_jediatmvar: + if self.options['do_jediatmvar']: dep_dict = {'type': 'task', 'name': f'{self.run}atmanlfinal'} else: dep_dict = {'type': 'task', 'name': f'{self.run}anal'} deps.append(rocoto.add_dependency(dep_dict)) - if self.app_config.do_jedisnowda: + if self.options['do_jedisnowda']: dep_dict = {'type': 'task', 'name': f'{self.run}snowanl'} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep_condition='and', dep=deps) @@ -247,14 +247,14 @@ def sfcanl(self): def analcalc(self): deps = [] - if self.app_config.do_jediatmvar: + if self.options['do_jediatmvar']: dep_dict = {'type': 'task', 'name': f'{self.run}atmanlfinal'} else: dep_dict = {'type': 'task', 'name': f'{self.run}anal'} deps.append(rocoto.add_dependency(dep_dict)) dep_dict = {'type': 'task', 'name': f'{self.run}sfcanl'} deps.append(rocoto.add_dependency(dep_dict)) - if self.app_config.do_hybvar and self.run in ['gdas']: + if self.options['do_hybvar'] and self.run in ['gdas']: dep_dict = {'type': 'task', 'name': 'enkfgdasechgres', 'offset': f"-{timedelta_to_HMS(self._base['cycle_interval'])}"} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep_condition='and', dep=deps) @@ -329,7 +329,7 @@ def atmanlinit(self): deps = [] dep_dict = {'type': 'task', 'name': f'{self.run}prepatmiodaobs'} deps.append(rocoto.add_dependency(dep_dict)) - if self.app_config.do_hybvar: + if self.options['do_hybvar']: dep_dict = {'type': 'metatask', 'name': 'enkfgdasepmn', 'offset': f"-{timedelta_to_HMS(self._base['cycle_interval'])}"} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep_condition='and', dep=deps) @@ -337,7 +337,7 @@ def atmanlinit(self): dependencies = rocoto.create_dependency(dep=deps) gfs_cyc = self._base["gfs_cyc"] - gfs_enkf = True if self.app_config.do_hybvar and 'gfs' in self.app_config.ens_runs else False + gfs_enkf = True if self.options['do_hybvar'] and 'gfs' in self.options['ens_runs'] else False cycledef = self.run if self.run in ['gfs'] and gfs_enkf and gfs_cyc != 4: @@ -487,7 +487,7 @@ def aeroanlinit(self): dep_dict = {'type': 'task', 'name': f'{self.run}prep'} deps.append(rocoto.add_dependency(dep_dict)) - if self.app_config.do_prep_obs_aero: + if self.options['do_prep_obs_aero']: dep_dict = {'type': 'task', 'name': f'{self.run}prepobsaero'} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep_condition='and', dep=deps) @@ -771,12 +771,12 @@ def ocnanalecen(self): def ocnanalchkpt(self): deps = [] - if self.app_config.do_hybvar: + if self.options['do_hybvar']: dep_dict = {'type': 'task', 'name': f'{self.run}ocnanalecen'} else: dep_dict = {'type': 'task', 'name': f'{self.run}ocnanalrun'} deps.append(rocoto.add_dependency(dep_dict)) - if self.app_config.do_mergensst: + if self.options['do_mergensst']: data = f'&ROTDIR;/{self.run}.@Y@m@d/@H/atmos/{self.run}.t@Hz.sfcanl.nc' dep_dict = {'type': 'data', 'data': data} deps.append(rocoto.add_dependency(dep_dict)) @@ -867,13 +867,13 @@ def _fcst_forecast_only(self): dep_dict = {'type': 'task', 'name': f'{self.run}stage_ic'} dependencies.append(rocoto.add_dependency(dep_dict)) - if self.app_config.do_wave and self.run in self.app_config.wave_runs: - wave_job = 'waveprep' if self.app_config.model_apps[self.run] in ['ATMW'] else 'waveinit' + if self.options['do_wave'] and self.run in self.options['wave_runs']: + wave_job = 'waveprep' if self.options['app'] in ['ATMW'] else 'waveinit' dep_dict = {'type': 'task', 'name': f'{self.run}{wave_job}'} dependencies.append(rocoto.add_dependency(dep_dict)) - if self.app_config.do_aero and \ - self.run in self.app_config.aero_fcst_runs and \ + if self.options['do_aero'] and \ + self.run in self.options['aero_fcst_runs'] and \ not self._base['EXP_WARM_START']: # Calculate offset based on RUN = gfs | gdas interval = None @@ -892,7 +892,7 @@ def _fcst_forecast_only(self): dependencies = rocoto.create_dependency(dep_condition='and', dep=dependencies) if self.run in ['gfs']: - num_fcst_segments = len(self.app_config.fcst_segments) - 1 + num_fcst_segments = len(self.options['fcst_segments']) - 1 else: num_fcst_segments = 1 @@ -931,15 +931,15 @@ def _fcst_cycled(self): dep = rocoto.add_dependency(dep_dict) dependencies = rocoto.create_dependency(dep=dep) - if self.app_config.do_jediocnvar: + if self.options['do_jediocnvar']: dep_dict = {'type': 'task', 'name': f'{self.run}ocnanalpost'} dependencies.append(rocoto.add_dependency(dep_dict)) - if self.app_config.do_aero and self.run in self.app_config.aero_anl_runs: + if self.options['do_aero'] and self.run in self.options['aero_anl_runs']: dep_dict = {'type': 'task', 'name': f'{self.run}aeroanlfinal'} dependencies.append(rocoto.add_dependency(dep_dict)) - if self.app_config.do_jedisnowda: + if self.options['do_jedisnowda']: dep_dict = {'type': 'task', 'name': f'{self.run}snowanl'} dependencies.append(rocoto.add_dependency(dep_dict)) @@ -950,7 +950,7 @@ def _fcst_cycled(self): dependencies.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep_condition='or', dep=dependencies) - if self.app_config.do_wave and self.run in self.app_config.wave_runs: + if self.options['do_wave'] and self.run in self.options['wave_runs']: dep_dict = {'type': 'task', 'name': f'{self.run}waveprep'} dependencies.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep_condition='and', dep=dependencies) @@ -958,7 +958,7 @@ def _fcst_cycled(self): cycledef = 'gdas_half,gdas' if self.run in ['gdas'] else self.run if self.run in ['gfs']: - num_fcst_segments = len(self.app_config.fcst_segments) - 1 + num_fcst_segments = len(self.options['fcst_segments']) - 1 else: num_fcst_segments = 1 @@ -1265,7 +1265,7 @@ def wavepostpnt(self): deps = [] dep_dict = {'type': 'metatask', 'name': f'{self.run}fcst'} deps.append(rocoto.add_dependency(dep_dict)) - if self.app_config.do_wave_bnd: + if self.options['do_wave_bnd']: dep_dict = {'type': 'task', 'name': f'{self.run}wavepostbndpntbll'} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep_condition='and', dep=deps) @@ -2230,54 +2230,54 @@ def arch(self): if self.run in ['gfs']: dep_dict = {'type': 'task', 'name': f'{self.run}atmanlprod'} deps.append(rocoto.add_dependency(dep_dict)) - if self.app_config.do_vminmon: + if self.options['do_vminmon']: dep_dict = {'type': 'task', 'name': f'{self.run}vminmon'} deps.append(rocoto.add_dependency(dep_dict)) elif self.run in ['gdas']: dep_dict = {'type': 'task', 'name': f'{self.run}atmanlprod'} deps.append(rocoto.add_dependency(dep_dict)) - if self.app_config.do_fit2obs: + if self.options['do_fit2obs']: dep_dict = {'type': 'task', 'name': f'{self.run}fit2obs'} deps.append(rocoto.add_dependency(dep_dict)) - if self.app_config.do_verfozn: + if self.options['do_verfozn']: dep_dict = {'type': 'task', 'name': f'{self.run}verfozn'} deps.append(rocoto.add_dependency(dep_dict)) - if self.app_config.do_verfrad: + if self.options['do_verfrad']: dep_dict = {'type': 'task', 'name': f'{self.run}verfrad'} deps.append(rocoto.add_dependency(dep_dict)) - if self.app_config.do_vminmon: + if self.options['do_vminmon']: dep_dict = {'type': 'task', 'name': f'{self.run}vminmon'} deps.append(rocoto.add_dependency(dep_dict)) - if self.run in ['gfs'] and self.app_config.do_tracker: + if self.run in ['gfs'] and self.options['do_tracker']: dep_dict = {'type': 'task', 'name': f'{self.run}tracker'} deps.append(rocoto.add_dependency(dep_dict)) - if self.run in ['gfs'] and self.app_config.do_genesis: + if self.run in ['gfs'] and self.options['do_genesis']: dep_dict = {'type': 'task', 'name': f'{self.run}genesis'} deps.append(rocoto.add_dependency(dep_dict)) - if self.run in ['gfs'] and self.app_config.do_genesis_fsu: + if self.run in ['gfs'] and self.options['do_genesis_fsu']: dep_dict = {'type': 'task', 'name': f'{self.run}genesis_fsu'} deps.append(rocoto.add_dependency(dep_dict)) # Post job dependencies dep_dict = {'type': 'metatask', 'name': f'{self.run}atmos_prod'} deps.append(rocoto.add_dependency(dep_dict)) - if self.app_config.do_wave: + if self.options['do_wave']: dep_dict = {'type': 'task', 'name': f'{self.run}wavepostsbs'} deps.append(rocoto.add_dependency(dep_dict)) dep_dict = {'type': 'task', 'name': f'{self.run}wavepostpnt'} deps.append(rocoto.add_dependency(dep_dict)) - if self.app_config.do_wave_bnd: + if self.options['do_wave_bnd']: dep_dict = {'type': 'task', 'name': f'{self.run}wavepostbndpnt'} deps.append(rocoto.add_dependency(dep_dict)) - if self.app_config.do_ocean: + if self.options['do_ocean']: if self.run in ['gfs']: dep_dict = {'type': 'metatask', 'name': f'{self.run}ocean_prod'} deps.append(rocoto.add_dependency(dep_dict)) - if self.app_config.do_ice: + if self.options['do_ice']: if self.run in ['gfs']: dep_dict = {'type': 'metatask', 'name': f'{self.run}ice_prod'} deps.append(rocoto.add_dependency(dep_dict)) # MOS job dependencies - if self.run in ['gfs'] and self.app_config.do_mos: + if self.run in ['gfs'] and self.options['do_mos']: mos_jobs = ["stn_prep", "grd_prep", "ext_stn_prep", "ext_grd_prep", "stn_fcst", "grd_fcst", "ext_stn_fcst", "ext_grd_fcst", "stn_prdgen", "grd_prdgen", "ext_stn_prdgen", "ext_grd_prdgen", @@ -2315,7 +2315,7 @@ def cleanup(self): dep_dict = {'type': 'task', 'name': f'{self.run}arch'} deps.append(rocoto.add_dependency(dep_dict)) - if self.app_config.do_gempak: + if self.options['do_gempak']: if self.run in ['gdas']: dep_dict = {'type': 'task', 'name': f'{self.run}gempakmetancdc'} deps.append(rocoto.add_dependency(dep_dict)) @@ -2324,7 +2324,7 @@ def cleanup(self): deps.append(rocoto.add_dependency(dep_dict)) dep_dict = {'type': 'task', 'name': f'{self.run}gempakncdcupapgif'} deps.append(rocoto.add_dependency(dep_dict)) - if self.app_config.do_goes: + if self.options['do_goes']: dep_dict = {'type': 'metatask', 'name': f'{self.run}gempakgrb2spec'} deps.append(rocoto.add_dependency(dep_dict)) dep_dict = {'type': 'task', 'name': f'{self.run}npoess_pgrb2_0p5deg'} @@ -2436,7 +2436,7 @@ def ediag(self): def eupd(self): deps = [] - if self.app_config.lobsdiag_forenkf: + if self.options['lobsdiag_forenkf']: dep_dict = {'type': 'task', 'name': f'{self.run}ediag'} else: dep_dict = {'type': 'metatask', 'name': f'{self.run}eomg'} @@ -2567,7 +2567,7 @@ def atmensanlletkf(self): def atmensanlfv3inc(self): deps = [] - if self.app_config.lobsdiag_forenkf: + if self.options['lobsdiag_forenkf']: dep_dict = {'type': 'task', 'name': f'{self.run}atmensanlsol'} else: dep_dict = {'type': 'task', 'name': f'{self.run}atmensanlletkf'} @@ -2645,7 +2645,7 @@ def _get_ecengroups(): deps = [] dep_dict = {'type': 'task', 'name': f'{self.run.replace("enkf","")}analcalc'} deps.append(rocoto.add_dependency(dep_dict)) - if self.app_config.do_jediatmens: + if self.options['do_jediatmens']: dep_dict = {'type': 'task', 'name': f'{self.run}atmensanlfinal'} else: dep_dict = {'type': 'task', 'name': f'{self.run}eupd'} @@ -2689,12 +2689,12 @@ def esfc(self): deps = [] dep_dict = {'type': 'task', 'name': f'{self.run.replace("enkf","")}analcalc'} deps.append(rocoto.add_dependency(dep_dict)) - if self.app_config.do_jediatmens: + if self.options['do_jediatmens']: dep_dict = {'type': 'task', 'name': f'{self.run}atmensanlfinal'} else: dep_dict = {'type': 'task', 'name': f'{self.run}eupd'} deps.append(rocoto.add_dependency(dep_dict)) - if self.app_config.do_jedisnowda: + if self.options['do_jedisnowda']: dep_dict = {'type': 'task', 'name': f'{self.run}esnowrecen'} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep_condition='and', dep=deps) diff --git a/workflow/rocoto/tasks.py b/workflow/rocoto/tasks.py index ea84f5143a3..21107afaa09 100644 --- a/workflow/rocoto/tasks.py +++ b/workflow/rocoto/tasks.py @@ -44,6 +44,9 @@ def __init__(self, app_config: AppConfig, run: str) -> None: # Get the configs for the specified RUN self._configs = self.app_config.configs[run] + # Get the workflow options for the specified RUN + self.options = self.app_config.run_options[run] + # Update the base config for the application self._configs['base'] = self.app_config._update_base(self._configs['base']) From 10763cc8bddb163d6fc63b110febfeb2cbb80509 Mon Sep 17 00:00:00 2001 From: David Huber Date: Wed, 25 Sep 2024 15:16:25 +0000 Subject: [PATCH 11/36] Fix hybvar ocn analysis task tabbing --- workflow/applications/gfs_cycled.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/workflow/applications/gfs_cycled.py b/workflow/applications/gfs_cycled.py index 7bfa8fd1230..57f0a0654cd 100644 --- a/workflow/applications/gfs_cycled.py +++ b/workflow/applications/gfs_cycled.py @@ -171,12 +171,12 @@ def get_task_names(self): if options['do_jediocnvar']: task_names[run] += ['prepoceanobs', 'ocnanalprep', 'marinebmat', 'ocnanalrun'] - if options['do_hybvar']: - task_names[run] += ['ocnanalecen', 'ocnanalchkpt', 'ocnanalpost'] - if options['do_vrfy_oceanda']: - task_names[run] += ['ocnanalvrfy'] + if options['do_hybvar']: + task_names[run] += ['ocnanalecen', 'ocnanalchkpt', 'ocnanalpost'] + if options['do_vrfy_oceanda']: + task_names[run] += ['ocnanalvrfy'] - task_names[run] += ['sfcanl', 'analcalc'] + task_names[run] += ['sfcanl', 'analcalc'] if options['do_jedisnowda']: task_names[run] += ['prepsnowobs', 'snowanl'] From 7dbf11aac7412f918b20ceba4f7256bc9ca32a72 Mon Sep 17 00:00:00 2001 From: David Huber Date: Wed, 25 Sep 2024 15:30:29 +0000 Subject: [PATCH 12/36] do_extractvars bug fix --- workflow/applications/gefs.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/workflow/applications/gefs.py b/workflow/applications/gefs.py index 52ef622da5c..3c5d0b7bb38 100644 --- a/workflow/applications/gefs.py +++ b/workflow/applications/gefs.py @@ -18,7 +18,6 @@ def __init__(self, conf: Configuration): def _netmode_run_options(self, base: Dict[str, Any], run_options: Dict[str, Any]) -> Dict[str, Any]: run_options[self.run]['nens'] = base.get('NMEM_ENS', 0) - print(run_options[self.run]['nens']) return run_options def _get_app_configs(self, run): @@ -89,7 +88,7 @@ def get_task_names(self): tasks += ['wavepostbndpnt', 'wavepostbndpntbll'] tasks += ['wavepostpnt'] - if self.do_extractvars: + if options['do_extractvars']: tasks += ['extractvars', 'arch'] tasks += ['cleanup'] From de57402563e4f1305ba811e359a77c6e15917a9f Mon Sep 17 00:00:00 2001 From: David Huber Date: Wed, 25 Sep 2024 15:44:33 +0000 Subject: [PATCH 13/36] Fix do_extractvars bug, remove unused modules --- workflow/rocoto/gefs_tasks.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/workflow/rocoto/gefs_tasks.py b/workflow/rocoto/gefs_tasks.py index cab31922dc4..f94a60c0e26 100644 --- a/workflow/rocoto/gefs_tasks.py +++ b/workflow/rocoto/gefs_tasks.py @@ -1,7 +1,6 @@ from applications.applications import AppConfig from rocoto.tasks import Tasks import rocoto.rocoto as rocoto -from datetime import datetime, timedelta class GEFSTasks(Tasks): @@ -560,7 +559,7 @@ def arch(self): def cleanup(self): deps = [] - if self.app_config.do_extractvars: + if self.options['do_extractvars']: dep_dict = {'type': 'task', 'name': 'arch'} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep=deps) From 7253eef49df4c344a170d66afdd1a4a7c94e1984 Mon Sep 17 00:00:00 2001 From: David Huber Date: Wed, 25 Sep 2024 15:47:53 +0000 Subject: [PATCH 14/36] Fix additional do_* bugs --- workflow/rocoto/gefs_tasks.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/workflow/rocoto/gefs_tasks.py b/workflow/rocoto/gefs_tasks.py index f94a60c0e26..0496f6405ac 100644 --- a/workflow/rocoto/gefs_tasks.py +++ b/workflow/rocoto/gefs_tasks.py @@ -568,18 +568,18 @@ def cleanup(self): deps.append(rocoto.add_dependency(dep_dict)) dep_dict = {'type': 'metatask', 'name': 'atmos_ensstat'} deps.append(rocoto.add_dependency(dep_dict)) - if self.app_config.do_ice: + if self.options['do_ice']: dep_dict = {'type': 'metatask', 'name': 'ice_prod'} deps.append(rocoto.add_dependency(dep_dict)) - if self.app_config.do_ocean: + if self.options['do_ocean']: dep_dict = {'type': 'metatask', 'name': 'ocean_prod'} deps.append(rocoto.add_dependency(dep_dict)) - if self.app_config.do_wave: + if self.options['do_wave']: dep_dict = {'type': 'metatask', 'name': 'wave_post_grid'} deps.append(rocoto.add_dependency(dep_dict)) dep_dict = {'type': 'metatask', 'name': 'wave_post_pnt'} deps.append(rocoto.add_dependency(dep_dict)) - if self.app_config.do_wave_bnd: + if self.options['do_wave_bnd']: dep_dict = {'type': 'metatask', 'name': 'wave_post_bndpnt'} deps.append(rocoto.add_dependency(dep_dict)) dep_dict = {'type': 'metatask', 'name': 'wave_post_bndpnt_bull'} From 9e9684457260d409efba1457eff2fb83e43932f2 Mon Sep 17 00:00:00 2001 From: David Huber Date: Mon, 30 Sep 2024 20:18:30 +0000 Subject: [PATCH 15/36] Rearrange gfs_cycled task order --- workflow/applications/gfs_cycled.py | 51 ++++++++++++++++------------- 1 file changed, 29 insertions(+), 22 deletions(-) diff --git a/workflow/applications/gfs_cycled.py b/workflow/applications/gfs_cycled.py index 57f0a0654cd..e235d5ee741 100644 --- a/workflow/applications/gfs_cycled.py +++ b/workflow/applications/gfs_cycled.py @@ -172,33 +172,40 @@ def get_task_names(self): if options['do_jediocnvar']: task_names[run] += ['prepoceanobs', 'ocnanalprep', 'marinebmat', 'ocnanalrun'] if options['do_hybvar']: - task_names[run] += ['ocnanalecen', 'ocnanalchkpt', 'ocnanalpost'] + task_names[run] += ['ocnanalecen'] + task_names[run] += ['ocnanalchkpt', 'ocnanalpost'] if options['do_vrfy_oceanda']: task_names[run] += ['ocnanalvrfy'] - task_names[run] += ['sfcanl', 'analcalc'] + task_names[run] += ['sfcanl', 'analcalc'] if options['do_jedisnowda']: task_names[run] += ['prepsnowobs', 'snowanl'] - if options['do_wave'] and run in self.wave_runs: - task_names[run] += ['waveinit', 'waveprep'] + wave_prep_tasks = ['waveinit', 'waveprep'] + wave_bndpnt_tasks = ['wavepostbndpnt', 'wavepostbndpntbll'] + wave_post_tasks = ['wavepostsbs', 'wavepostpnt'] - # gdas-specific analysis tasks + # gdas- and gfs-specific analysis tasks if run == 'gdas': if not options['do_jediatmvar']: task_names[run] += ['analdiag'] + if options['do_wave'] and run in self.wave_runs: + task_names[run] += wave_prep_tasks + if options['do_aero'] and run in options['aero_anl_runs']: task_names[run] += ['aeroanlgenb'] + else: + if options['do_wave'] and run in self.wave_runs: + task_names[run] += wave_prep_tasks + if options['do_aero'] and run in options['aero_anl_runs']: task_names[run] = ['aeroanlinit', 'aeroanlvar', 'aeroanlfinal'] - if options['do_aero'] and run in options['aero_anl_runs']: - task_names[run] += ['aeroanlgenb', 'aeroanlinit', 'aeroanlvar', 'aeroanlfinal'] - if options['do_prep_obs_aero']: - task_names[run] += ['prepobsaero'] + if options['do_prep_obs_aero']: + task_names[run] += ['prepobsaero'] # Staging is gdas-specific if run == 'gdas': @@ -223,17 +230,19 @@ def get_task_names(self): if options['do_goes']: task_names[run] += ['goesupp'] - if options['do_vminmon']: - task_names[run] += ['vminmon'] - if options['do_fit2obs']: task_names[run] += ['fit2obs'] - if options['do_verfozn']: - task_names[run] += ['verfozn'] + # Only verify ozone and radiance during gdas cycles + if run == "gdas": + if options['do_verfozn']: + task_names[run] += ['verfozn'] - if options['do_verfrad']: - task_names[run] += ['verfrad'] + if options['do_verfrad']: + task_names[run] += ['verfrad'] + + if options['do_vminmon']: + task_names[run] += ['vminmon'] # gfs-only verification/tracking if run == 'gfs': @@ -251,8 +260,8 @@ def get_task_names(self): if options['do_wave'] and run in options['wave_runs']: if options['do_wave_bnd']: - task_names[run] += ['wavepostbndpnt', 'wavepostbndpntbll'] - task_names[run] += ['wavepostsbs', 'wavepostpnt'] + task_names[run] += wave_bndpnt_tasks + task_names[run] += wave_post_tasks # wave gempak and awips jobs are gfs-specific if run == 'gfs': if options['do_gempak']: @@ -260,13 +269,11 @@ def get_task_names(self): if options['do_awips']: task_names[run] += ['waveawipsbulls', 'waveawipsgridded'] - # gdas-specific gempak jobs + # gdas- and gfs-specific downstream products if run == 'gdas': if options['do_gempak']: task_names[run] += ['gempak', 'gempakmetancdc'] - - # gfs-specific post products - if run == 'gfs': + else: if options['do_bufrsnd']: task_names[run] += ['postsnd'] From 8275caf86507fc926688265e09bd7ae95d3bc14a Mon Sep 17 00:00:00 2001 From: David Huber Date: Tue, 1 Oct 2024 11:34:18 +0000 Subject: [PATCH 16/36] Rearrange XML RUN order to match develop --- workflow/applications/gfs_cycled.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/workflow/applications/gfs_cycled.py b/workflow/applications/gfs_cycled.py index e235d5ee741..7995efb0611 100644 --- a/workflow/applications/gfs_cycled.py +++ b/workflow/applications/gfs_cycled.py @@ -14,10 +14,8 @@ def __init__(self, conf: Configuration): # Re-read config.base without RUN specified to get the basic settings for # cycled cases to be able to determine valid runs base = conf.parse_config('config.base') - self.runs = ["gdas"] - self.runs.append("gfs") if base['gfs_cyc'] > 0 else 0 - self.ens_runs = None + self.ens_runs = [] if base.get('DOHYBVAR', False): ens_run = base.get('EUPD_CYC', 'gdas').lower() @@ -26,8 +24,11 @@ def __init__(self, conf: Configuration): elif ens_run in ['gfs', 'gdas']: self.ens_runs = [ens_run] - for ens_run in self.ens_runs: - self.runs.append(f"enkf{ens_run}") if ens_run in self.runs else 0 + # Now construct self.runs the desired XML order (gdas, enkfgdas, gfs, enkfgfs) + self.runs = ["gdas"] # We always have a 'gdas' run + self.runs.append('enkfgdas') if 'gdas' in self.ens_runs else 0 + self.runs.append("gfs") if base['gfs_cyc'] > 0 else 0 + self.runs.append('enkfgfs') if 'gfs' in self.ens_runs and "gfs" in self.runs else 0 def _netmode_run_options(self, base: Dict[str, Any], run_options: Dict[str, Any]) -> Dict[str, Any]: @@ -230,14 +231,12 @@ def get_task_names(self): if options['do_goes']: task_names[run] += ['goesupp'] - if options['do_fit2obs']: - task_names[run] += ['fit2obs'] - - # Only verify ozone and radiance during gdas cycles + # Only fit to obs and verify ozone and radiance during gdas cycles if run == "gdas": + if options['do_fit2obs']: + task_names[run] += ['fit2obs'] if options['do_verfozn']: task_names[run] += ['verfozn'] - if options['do_verfrad']: task_names[run] += ['verfrad'] @@ -311,7 +310,7 @@ def get_task_names(self): else: task_names[run] += ['eobs', 'eupd'] - task_names[run] += ['echgres'] if 'gdas' in run else 0 + task_names[run].append('echgres') if 'gdas' in run else 0 task_names[run] += ['ediag'] if options['lobsdiag_forenkf'] else ['eomg'] task_names[run] += ['stage_ic', 'ecen', 'esfc', 'efcs', 'epos', 'earc', 'cleanup'] From 4477e609f633d526bf6bdef35d61f3d9a9735865 Mon Sep 17 00:00:00 2001 From: David Huber Date: Tue, 1 Oct 2024 14:15:24 -0500 Subject: [PATCH 17/36] Fix merge bugs --- workflow/applications/gfs_cycled.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/workflow/applications/gfs_cycled.py b/workflow/applications/gfs_cycled.py index 69fb1acff72..7f5504df3ab 100644 --- a/workflow/applications/gfs_cycled.py +++ b/workflow/applications/gfs_cycled.py @@ -164,7 +164,7 @@ def get_task_names(self): # Common gdas and gfs tasks before fcst if run in ['gdas', 'gfs']: - task_names[run] = ['prep'] + task_names[run] += ['prep'] if options['do_jediatmvar']: task_names[run] += ['prepatmiodaobs', 'atmanlinit', 'atmanlvar', 'atmanlfv3inc', 'atmanlfinal'] else: @@ -203,7 +203,7 @@ def get_task_names(self): task_names[run] += wave_prep_tasks if options['do_aero'] and run in options['aero_anl_runs']: - task_names[run] = ['aeroanlinit', 'aeroanlvar', 'aeroanlfinal'] + task_names[run] += ['aeroanlinit', 'aeroanlvar', 'aeroanlfinal'] if options['do_prep_obs_aero']: task_names[run] += ['prepobsaero'] @@ -312,6 +312,7 @@ def get_task_names(self): task_names[run] += ['eobs', 'eupd'] task_names[run].append('echgres') if 'gdas' in run else 0 task_names[run] += ['ediag'] if options['lobsdiag_forenkf'] else ['eomg'] + task_names[run].append('esnowrecen') if options['do_jedisnowda'] and 'gdas' in run else 0 task_names[run] += ['stage_ic', 'ecen', 'esfc', 'efcs', 'epos', 'earc', 'cleanup'] From e8a9b9613e331c0c2ef2a7afaef01faa560d2a95 Mon Sep 17 00:00:00 2001 From: David Huber Date: Fri, 4 Oct 2024 13:38:27 -0500 Subject: [PATCH 18/36] Increase max allocatable memory on Orion Hercules was already correct Refs #2956 --- parm/config/gfs/config.resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/parm/config/gfs/config.resources b/parm/config/gfs/config.resources index 71c71af60ba..bb76f76764f 100644 --- a/parm/config/gfs/config.resources +++ b/parm/config/gfs/config.resources @@ -55,7 +55,7 @@ case ${machine} in "ORION") max_tasks_per_node=40 # shellcheck disable=SC2034 - mem_node_max="180GB" + mem_node_max="188GB" ;; "HERCULES") max_tasks_per_node=80 From 731f16e0a8290e4bfcdec2ef4d4251b9cb78fd86 Mon Sep 17 00:00:00 2001 From: David Huber Date: Thu, 10 Oct 2024 16:27:11 +0000 Subject: [PATCH 19/36] simplify aerosol/wave RUN-based options --- parm/archive/gdas.yaml.j2 | 2 +- parm/archive/gfs_arcdir.yaml.j2 | 2 +- parm/archive/gfsa.yaml.j2 | 2 +- parm/archive/master_gfs.yaml.j2 | 2 +- parm/config/gefs/config.base | 12 ++------ parm/config/gefs/config.fcst | 13 ++------- parm/config/gfs/config.base | 31 ++++++++++++++------ parm/config/gfs/config.fcst | 15 +++------- scripts/exglobal_archive.py | 4 +-- workflow/applications/applications.py | 33 ++-------------------- workflow/applications/gfs_cycled.py | 10 +++---- workflow/applications/gfs_forecast_only.py | 7 ++--- workflow/rocoto/gfs_tasks.py | 10 +++---- 13 files changed, 51 insertions(+), 92 deletions(-) diff --git a/parm/archive/gdas.yaml.j2 b/parm/archive/gdas.yaml.j2 index 030678f31f4..b630563c02d 100644 --- a/parm/archive/gdas.yaml.j2 +++ b/parm/archive/gdas.yaml.j2 @@ -67,7 +67,7 @@ gdas: - "{{ COMIN_ATMOS_ANALYSIS | relpath(ROTDIR) }}/{{ head }}oznstat" - "{{ COMIN_ATMOS_ANALYSIS | relpath(ROTDIR) }}/{{ head }}radstat" {% endif %} - {% if AERO_ANL_RUN == "gdas" or AERO_ANL_RUN == "both" %} + {% if DO_AERO_ANL %} - "{{ COMIN_CHEM_ANALYSIS | relpath(ROTDIR) }}/{{ head }}aerostat" {% endif %} {% if DO_PREP_OBS_AERO %} diff --git a/parm/archive/gfs_arcdir.yaml.j2 b/parm/archive/gfs_arcdir.yaml.j2 index 57dbc78885e..98803b473c0 100644 --- a/parm/archive/gfs_arcdir.yaml.j2 +++ b/parm/archive/gfs_arcdir.yaml.j2 @@ -50,7 +50,7 @@ ARCDIR ~ "/snowstat." ~ RUN ~ "." ~ cycle_YMDH ~ ".tgz"]) %} {% endif %} - {% if AERO_ANL_RUN == RUN or AERO_ANL_RUN == "both" %} + {% if DO_AERO_ANL %} {% do det_anl_files.append([COMIN_CHEM_ANALYSIS ~ "/" ~ head ~ "aerostat", ARCDIR ~ "/aerostat." ~ RUN ~ "." ~ cycle_YMDH ]) %} {% endif %} diff --git a/parm/archive/gfsa.yaml.j2 b/parm/archive/gfsa.yaml.j2 index 226a7178fa3..b0397818cf6 100644 --- a/parm/archive/gfsa.yaml.j2 +++ b/parm/archive/gfsa.yaml.j2 @@ -38,7 +38,7 @@ gfsa: {% else %} - "{{ COMIN_ATMOS_ANALYSIS | relpath(ROTDIR) }}/{{ head }}gsistat" {% endif %} - {% if AERO_ANL_RUN == "gfs" or AERO_ANL_RUN == "both" %} + {% if DO_AERO_ANL %} - "{{ COMIN_CHEM_ANALYSIS | relpath(ROTDIR) }}/{{ head }}aerostat" {% endif %} {% if DO_PREP_OBS_AERO %} diff --git a/parm/archive/master_gfs.yaml.j2 b/parm/archive/master_gfs.yaml.j2 index ab9a00c95e3..e7187d70d51 100644 --- a/parm/archive/master_gfs.yaml.j2 +++ b/parm/archive/master_gfs.yaml.j2 @@ -33,7 +33,7 @@ datasets: {% endfilter %} {% endif %} -{% if AERO_FCST_RUN == "gfs" or AERO_FCST_RUN == "both" %} +{% if DO_AERO_FCST %} # Aerosol forecasts {% filter indent(width=4) %} {% include "chem.yaml.j2" %} diff --git a/parm/config/gefs/config.base b/parm/config/gefs/config.base index 6cf8488f916..b6e7616155d 100644 --- a/parm/config/gefs/config.base +++ b/parm/config/gefs/config.base @@ -135,9 +135,7 @@ export DO_OCN="NO" export DO_ICE="NO" export DO_AERO="NO" export DO_EXTRACTVARS="@DO_EXTRACTVARS@" # Option to process and extract a subset of products to save on disk -export AERO_FCST_RUN="" # When to run aerosol forecast: gdas, gfs, or both -export AERO_ANL_RUN="" # When to run aerosol analysis: gdas, gfs, or both -export WAVE_RUN="" # When to include wave suite: gdas, gfs, or both +export DO_AERO_FCST="NO" export DOBNDPNT_WAVE="NO" # The GEFS buoys file does not currently have any boundary points export DOIBP_WAV="NO" # Option to create point outputs from input boundary points export FRAC_GRID=".true." @@ -183,13 +181,11 @@ case "${APP}" in ;; ATMA) export DO_AERO="YES" - export AERO_ANL_RUN="both" - export AERO_FCST_RUN="gdas" + export DO_AERO_FCST="NO" ;; ATMW) export DO_COUPLED="YES" export DO_WAVE="YES" - export WAVE_RUN="both" ;; NG-GODAS) export DO_ATM="NO" @@ -203,13 +199,11 @@ case "${APP}" in if [[ "${APP}" =~ A$ ]]; then export DO_AERO="YES" - export AERO_ANL_RUN="both" - export AERO_FCST_RUN="gdas" + export DO_AERO_FCST="NO" fi if [[ "${APP}" =~ ^S2SW ]]; then export DO_WAVE="YES" - export WAVE_RUN="both" export cplwav2atm=".true." fi ;; diff --git a/parm/config/gefs/config.fcst b/parm/config/gefs/config.fcst index efdedb24f41..8d9fb0bea5e 100644 --- a/parm/config/gefs/config.fcst +++ b/parm/config/gefs/config.fcst @@ -8,17 +8,10 @@ echo "BEGIN: config.fcst" export USE_ESMF_THREADING="YES" # Toggle to use ESMF-managed threading or traditional threading in UFSWM export COPY_FINAL_RESTARTS="NO" # Toggle to copy restarts from the end of GFS/GEFS Run (GDAS is handled seperately) -# Turn off waves if not used for this RUN -case ${WAVE_RUN} in - both | "${RUN/enkf}" ) ;; # Don't change - *) DO_WAVE="NO" ;; # Turn waves off -esac - # Turn off aerosols if not used for this RUN -case ${AERO_FCST_RUN} in - both | "${RUN/enkf}" ) ;; # Don't change - *) DO_AERO="NO" ;; # Turn waves off -esac +if [[ "${DO_AERO_FCST}" == "NO" ]]; then + DO_AERO="NO" +fi # Source model specific information that is resolution dependent string="--fv3 ${CASE}" diff --git a/parm/config/gfs/config.base b/parm/config/gfs/config.base index 328721cacdb..2a45e63d83a 100644 --- a/parm/config/gfs/config.base +++ b/parm/config/gfs/config.base @@ -155,6 +155,7 @@ export SENDDBN=${SENDDBN:-"NO"} export DBNROOT=${DBNROOT:-${UTILROOT:-}/fakedbn} # APP settings; configurable by RUN +# If a component (WAVES, etc) needs to be turned on/off by RUN, set it here case "${RUN}" in "gfs") export APP=@APP@ @@ -193,9 +194,11 @@ export DO_OCN="NO" export DO_ICE="NO" export DO_AERO="NO" export DO_PREP_OBS_AERO="NO" -export AERO_FCST_RUN="" # When to run aerosol forecast: gdas, gfs, or both -export AERO_ANL_RUN="" # When to run aerosol analysis: gdas, gfs, or both -export WAVE_RUN="" # When to include wave suite: gdas, gfs, or both +aero_fcst_runs="gdas" # When to run aerosol forecast: gdas, gfs, or both +aero_anl_runs="gdas gfs" # When to run aerosol analysis: gdas, gfs, or both +wave_runs="gdas gfs" # When to include wave suite: gdas, gfs, or both +export DO_AERO_FCST="NO" +export DO_AERO_ANL="NO" export DOBNDPNT_WAVE="NO" export DOIBP_WAV="NO" # Option to create point outputs from input boundary points export FRAC_GRID=".true." @@ -246,13 +249,10 @@ case "${APP}" in ;; ATMA) export DO_AERO="YES" - export AERO_ANL_RUN="both" - export AERO_FCST_RUN="gdas" ;; ATMW) export DO_COUPLED="YES" export DO_WAVE="YES" - export WAVE_RUN="both" ;; NG-GODAS) export DO_ATM="NO" @@ -266,13 +266,10 @@ case "${APP}" in if [[ "${APP}" =~ A$ ]]; then export DO_AERO="YES" - export AERO_ANL_RUN="both" - export AERO_FCST_RUN="gdas" fi if [[ "${APP}" =~ ^S2SW ]]; then export DO_WAVE="YES" - export WAVE_RUN="both" fi ;; *) @@ -281,6 +278,22 @@ case "${APP}" in ;; esac +# Aerosol forecasts and analyses may be RUN-dependent +if [[ "${DO_AERO}" == "YES" ]]; then + for aero_run in ${aero_anl_runs}; do + if [[ "${aero_run}" == "${RUN}" ]]; then + export DO_AERO_ANL="YES" + break + fi + done + for aero_run in ${aero_fcst_runs}; do + if [[ "${aero_run}" == "${RUN}" ]]; then + export DO_AERO_FCST="YES" + break + fi + done +fi + # Surface cycle update frequency if [[ "${RUN}" =~ "gdas" ]] ; then export FHCYC=1 diff --git a/parm/config/gfs/config.fcst b/parm/config/gfs/config.fcst index da336ff73bd..1c19cd1c03b 100644 --- a/parm/config/gfs/config.fcst +++ b/parm/config/gfs/config.fcst @@ -8,17 +8,10 @@ echo "BEGIN: config.fcst" export USE_ESMF_THREADING="YES" # Toggle to use ESMF-managed threading or traditional threading in UFSWM export COPY_FINAL_RESTARTS="NO" # Toggle to copy restarts from the end of GFS/GEFS Run (GDAS is handled seperately) -# Turn off waves if not used for this RUN -case ${WAVE_RUN} in - both | "${RUN/enkf}" ) ;; # Don't change - *) DO_WAVE="NO" ;; # Turn waves off -esac - -# Turn off aerosols if not used for this RUN -case ${AERO_FCST_RUN} in - both | "${RUN/enkf}" ) ;; # Don't change - *) DO_AERO="NO" ;; # Turn aerosols off -esac +# Turn off aerosols if they are not being forecasted +if [[ "${DO_AERO_FCST}" == "NO" ]]; then + DO_AERO="NO" +fi # Source model specific information that is resolution dependent string="--fv3 ${CASE}" diff --git a/scripts/exglobal_archive.py b/scripts/exglobal_archive.py index 4ee9e5ed0ef..cf885b028db 100755 --- a/scripts/exglobal_archive.py +++ b/scripts/exglobal_archive.py @@ -3,7 +3,7 @@ import os from pygfs.task.archive import Archive -from wxflow import AttrDict, Logger, cast_strdict_as_dtypedict, chdir, logit +from wxflow import AttrDict, Logger, cast_strdict_as_dtypedict, logit # initialize root logger logger = Logger(level=os.environ.get("LOGGING_LEVEL", "DEBUG"), colored_log=True) @@ -29,7 +29,7 @@ def main(): 'DOIAU', 'OCNRES', 'ICERES', 'NUM_SND_COLLECTIVES', 'FHOUT_WAV', 'FHOUT_HF_WAV', 'FHMAX_WAV', 'FHMAX_HF_WAV', 'FHMAX_WAV_GFS', 'restart_interval_gdas', 'restart_interval_gfs', - 'AERO_ANL_RUN', 'AERO_FCST_RUN', 'DOIBP_WAV', 'DO_JEDIOCNVAR', + 'DO_AERO_ANL', 'DO_AERO_FCST', 'DOIBP_WAV', 'DO_JEDIOCNVAR', 'NMEM_ENS', 'DO_JEDIATMVAR', 'DO_VRFY_OCEANDA', 'FHMAX_FITS', 'waveGRD', 'IAUFHRS', 'DO_FIT2OBS', 'NET', 'FHOUT_HF_GFS', 'FHMAX_HF_GFS', 'REPLAY_ICS', 'OFFSET_START_HOUR'] diff --git a/workflow/applications/applications.py b/workflow/applications/applications.py index 03e47089d78..67eceb02d29 100644 --- a/workflow/applications/applications.py +++ b/workflow/applications/applications.py @@ -98,6 +98,8 @@ def _get_run_options(self, conf: Configuration) -> Dict[str, Any]: run_options[run]['do_ice'] = run_base.get('DO_ICE', False) run_options[run]['do_aero'] = run_base.get('DO_AERO', False) run_options[run]['do_prep_obs_aero'] = run_base.get('DO_PREP_OBS_AERO', False) + run_options[run]['do_aero_anl'] = run_base.get('DO_AERO_ANL', False) + run_options[run]['do_aero_fcst'] = run_base.get('DO_AERO_FCST', False) run_options[run]['do_hpssarch'] = run_base.get('HPSSARCH', False) run_options[run]['fcst_segments'] = run_base.get('FCST_SEGMENTS', None) @@ -105,37 +107,6 @@ def _get_run_options(self, conf: Configuration) -> Dict[str, Any]: if not AppConfig.is_monotonic(run_options[run]['fcst_segments']): raise ValueError(f'Forecast segments do not increase monotonically: {",".join(self.fcst_segments)}') - wave_runs = [] - if run_options[run]['do_wave']: - wave_run = run_base.get('WAVE_RUN', 'BOTH').lower() - if wave_run in ['both']: - wave_runs = ['gfs', 'gdas'] - elif wave_run in ['gfs', 'gdas']: - wave_runs = [wave_run] - - run_options[run]['wave_runs'] = wave_runs - - aero_anl_runs = [] - aero_fcst_runs = [] - if run_options[run]['do_aero']: - aero_anl_run = run_base.get('AERO_ANL_RUN', 'BOTH').lower() - if aero_anl_run in ['both']: - aero_anl_runs = ['gfs', 'gdas'] - elif aero_anl_run in ['gfs', 'gdas']: - aero_anl_runs = [aero_anl_run] - - aero_fcst_run = run_base.get('AERO_FCST_RUN', None).lower() - if aero_fcst_run in ['both']: - aero_fcst_runs = ['gfs', 'gdas'] - elif aero_fcst_run in ['gfs', 'gdas']: - aero_fcst_runs = [aero_fcst_run] - - run_options[run]['do_aero_anl'] = True if run in aero_anl_runs else False - run_options[run]['do_aero_fcst'] = True if run in aero_fcst_runs else False - - run_options[run]['aero_anl_runs'] = aero_anl_runs - run_options[run]['aero_fcst_runs'] = aero_fcst_runs - # Append any MODE-specific options run_options = self._netmode_run_options(run_base, run_options) diff --git a/workflow/applications/gfs_cycled.py b/workflow/applications/gfs_cycled.py index 7f5504df3ab..e3c50a13eec 100644 --- a/workflow/applications/gfs_cycled.py +++ b/workflow/applications/gfs_cycled.py @@ -192,17 +192,17 @@ def get_task_names(self): if not options['do_jediatmvar']: task_names[run] += ['analdiag'] - if options['do_wave'] and run in self.wave_runs: + if options['do_wave']: task_names[run] += wave_prep_tasks - if options['do_aero'] and run in options['aero_anl_runs']: + if options['do_aero_anl']: task_names[run] += ['aeroanlgenb'] else: - if options['do_wave'] and run in self.wave_runs: + if options['do_wave']: task_names[run] += wave_prep_tasks - if options['do_aero'] and run in options['aero_anl_runs']: + if options['do_aero_anl']: task_names[run] += ['aeroanlinit', 'aeroanlvar', 'aeroanlfinal'] if options['do_prep_obs_aero']: @@ -257,7 +257,7 @@ def get_task_names(self): if options['do_metp']: task_names[run] += ['metp'] - if options['do_wave'] and run in options['wave_runs']: + if options['do_wave']: if options['do_wave_bnd']: task_names[run] += wave_bndpnt_tasks task_names[run] += wave_post_tasks diff --git a/workflow/applications/gfs_forecast_only.py b/workflow/applications/gfs_forecast_only.py index 11f00a614a5..665cbf78055 100644 --- a/workflow/applications/gfs_forecast_only.py +++ b/workflow/applications/gfs_forecast_only.py @@ -99,11 +99,8 @@ def get_task_names(self): tasks = ['stage_ic'] options = self.run_options[self.run] - if options['do_aero']: - aero_fcst_run = options['aero_fcst_run'] - if self.run in aero_fcst_run or aero_fcst_run == "both": - if not options['exp_warm_start']: - tasks += ['aerosol_init'] + if options['do_aero_fcst'] and not options['exp_warm_start']: + tasks += ['aerosol_init'] if options['do_wave']: tasks += ['waveinit'] diff --git a/workflow/rocoto/gfs_tasks.py b/workflow/rocoto/gfs_tasks.py index ca2a73af82f..e41dc9404ad 100644 --- a/workflow/rocoto/gfs_tasks.py +++ b/workflow/rocoto/gfs_tasks.py @@ -867,14 +867,12 @@ def _fcst_forecast_only(self): dep_dict = {'type': 'task', 'name': f'{self.run}stage_ic'} dependencies.append(rocoto.add_dependency(dep_dict)) - if self.options['do_wave'] and self.run in self.options['wave_runs']: + if self.options['do_wave']: wave_job = 'waveprep' if self.options['app'] in ['ATMW'] else 'waveinit' dep_dict = {'type': 'task', 'name': f'{self.run}{wave_job}'} dependencies.append(rocoto.add_dependency(dep_dict)) - if self.options['do_aero'] and \ - self.run in self.options['aero_fcst_runs'] and \ - not self._base['EXP_WARM_START']: + if self.options['do_aero_fcst'] and not self._base['EXP_WARM_START']: # Calculate offset based on RUN = gfs | gdas interval = None if self.run in ['gfs']: @@ -935,7 +933,7 @@ def _fcst_cycled(self): dep_dict = {'type': 'task', 'name': f'{self.run}marineanlfinal'} dependencies.append(rocoto.add_dependency(dep_dict)) - if self.options['do_aero'] and self.run in self.options['aero_anl_runs']: + if self.options['do_aero_anl']: dep_dict = {'type': 'task', 'name': f'{self.run}aeroanlfinal'} dependencies.append(rocoto.add_dependency(dep_dict)) @@ -950,7 +948,7 @@ def _fcst_cycled(self): dependencies.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep_condition='or', dep=dependencies) - if self.options['do_wave'] and self.run in self.options['wave_runs']: + if self.options['do_wave']: dep_dict = {'type': 'task', 'name': f'{self.run}waveprep'} dependencies.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep_condition='and', dep=dependencies) From d829a3a722187a8ae3f11c7d736ea05fc95fc7cf Mon Sep 17 00:00:00 2001 From: David Huber Date: Fri, 11 Oct 2024 12:39:12 +0000 Subject: [PATCH 20/36] Remove unreferenced stage_ic->prep_emissions dependency --- workflow/rocoto/gefs_tasks.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/workflow/rocoto/gefs_tasks.py b/workflow/rocoto/gefs_tasks.py index 7905675c349..7103ec3a425 100644 --- a/workflow/rocoto/gefs_tasks.py +++ b/workflow/rocoto/gefs_tasks.py @@ -43,10 +43,6 @@ def waveinit(self): return task def prep_emissions(self): - deps = [] - dep_dict = {'type': 'task', 'name': f'gefs_stage_ic'} - deps.append(rocoto.add_dependency(dep_dict)) - dependencies = rocoto.create_dependency(dep=deps) resources = self.get_resource('prep_emissions') task_name = 'gefs_prep_emissions' From f0a99c6d0dedd950f0843e01bf4e75f77bbeca4b Mon Sep 17 00:00:00 2001 From: David Huber Date: Fri, 11 Oct 2024 12:44:27 +0000 Subject: [PATCH 21/36] Remove unused wave_runs variable --- parm/config/gfs/config.base | 1 - 1 file changed, 1 deletion(-) diff --git a/parm/config/gfs/config.base b/parm/config/gfs/config.base index 2a45e63d83a..24ba7192fd8 100644 --- a/parm/config/gfs/config.base +++ b/parm/config/gfs/config.base @@ -196,7 +196,6 @@ export DO_AERO="NO" export DO_PREP_OBS_AERO="NO" aero_fcst_runs="gdas" # When to run aerosol forecast: gdas, gfs, or both aero_anl_runs="gdas gfs" # When to run aerosol analysis: gdas, gfs, or both -wave_runs="gdas gfs" # When to include wave suite: gdas, gfs, or both export DO_AERO_FCST="NO" export DO_AERO_ANL="NO" export DOBNDPNT_WAVE="NO" From 6fe6014cb39646c49762e92d0113d8c234c3a13f Mon Sep 17 00:00:00 2001 From: David Huber Date: Wed, 16 Oct 2024 17:16:55 +0000 Subject: [PATCH 22/36] Replace references to DO_AERO with DO_AERO_FCST --- jobs/JGLOBAL_FORECAST | 2 +- parm/config/gefs/config.base | 3 --- parm/config/gefs/config.efcs | 4 ++-- parm/config/gefs/config.fcst | 9 ++------- parm/config/gefs/config.resources | 2 +- parm/config/gfs/config.base | 6 +++--- parm/config/gfs/config.efcs | 2 +- parm/config/gfs/config.fcst | 9 ++------- parm/config/gfs/config.resources | 2 +- scripts/exglobal_archive.py | 2 +- ush/python/pygfs/task/archive.py | 2 +- 11 files changed, 15 insertions(+), 28 deletions(-) diff --git a/jobs/JGLOBAL_FORECAST b/jobs/JGLOBAL_FORECAST index e64a91d21c6..0572f1d2d9a 100755 --- a/jobs/JGLOBAL_FORECAST +++ b/jobs/JGLOBAL_FORECAST @@ -77,7 +77,7 @@ if [[ "${DO_ICE}" == "YES" ]]; then COMIN_ICE_RESTART_PREV:COM_ICE_RESTART_TMPL fi -if [[ "${DO_AERO}" == "YES" ]]; then +if [[ "${DO_AERO_FCST}" == "YES" ]]; then YMD="${PDY}" HH="${cyc}" declare_from_tmpl -rx \ COMOUT_CHEM_HISTORY:COM_CHEM_HISTORY_TMPL fi diff --git a/parm/config/gefs/config.base b/parm/config/gefs/config.base index b6e7616155d..888f91b8b81 100644 --- a/parm/config/gefs/config.base +++ b/parm/config/gefs/config.base @@ -133,7 +133,6 @@ export DO_COUPLED="NO" export DO_WAVE="NO" export DO_OCN="NO" export DO_ICE="NO" -export DO_AERO="NO" export DO_EXTRACTVARS="@DO_EXTRACTVARS@" # Option to process and extract a subset of products to save on disk export DO_AERO_FCST="NO" export DOBNDPNT_WAVE="NO" # The GEFS buoys file does not currently have any boundary points @@ -180,7 +179,6 @@ case "${APP}" in ATM) ;; ATMA) - export DO_AERO="YES" export DO_AERO_FCST="NO" ;; ATMW) @@ -198,7 +196,6 @@ case "${APP}" in export DO_ICE="YES" if [[ "${APP}" =~ A$ ]]; then - export DO_AERO="YES" export DO_AERO_FCST="NO" fi diff --git a/parm/config/gefs/config.efcs b/parm/config/gefs/config.efcs index 9bd55afa54e..50ab04191ca 100644 --- a/parm/config/gefs/config.efcs +++ b/parm/config/gefs/config.efcs @@ -6,7 +6,7 @@ echo "BEGIN: config.efcs" # Turn off components in ensemble -# export DO_AERO="NO" +# export DO_AERO_FCST="NO" # export DO_OCN="NO" # export DO_ICE="NO" # export DO_WAVE="NO" @@ -19,7 +19,7 @@ string="--fv3 ${CASE}" [[ "${DO_OCN}" == "YES" ]] && string="${string} --mom6 ${OCNRES}" [[ "${DO_ICE}" == "YES" ]] && string="${string} --cice6 ${ICERES}" [[ "${DO_WAVE}" == "YES" ]] && string="${string} --ww3 ${waveGRD// /;}" -[[ "${DO_AERO}" == "YES" ]] && string="${string} --gocart" +[[ "${DO_AERO_FCST}" == "YES" ]] && string="${string} --gocart" # shellcheck disable=SC2086 source "${EXPDIR}/config.ufs" ${string} diff --git a/parm/config/gefs/config.fcst b/parm/config/gefs/config.fcst index 8d9fb0bea5e..b8ce1bde8c4 100644 --- a/parm/config/gefs/config.fcst +++ b/parm/config/gefs/config.fcst @@ -8,17 +8,12 @@ echo "BEGIN: config.fcst" export USE_ESMF_THREADING="YES" # Toggle to use ESMF-managed threading or traditional threading in UFSWM export COPY_FINAL_RESTARTS="NO" # Toggle to copy restarts from the end of GFS/GEFS Run (GDAS is handled seperately) -# Turn off aerosols if not used for this RUN -if [[ "${DO_AERO_FCST}" == "NO" ]]; then - DO_AERO="NO" -fi - # Source model specific information that is resolution dependent string="--fv3 ${CASE}" [[ "${DO_OCN}" == "YES" ]] && string="${string} --mom6 ${OCNRES}" [[ "${DO_ICE}" == "YES" ]] && string="${string} --cice6 ${ICERES}" [[ "${DO_WAVE}" == "YES" ]] && string="${string} --ww3 ${waveGRD// /;}" -[[ "${DO_AERO}" == "YES" ]] && string="${string} --gocart" +[[ "${DO_AERO_FCST}" == "YES" ]] && string="${string} --gocart" # We are counting on $string being multiple arguments # shellcheck disable=SC2086 source "${EXPDIR}/config.ufs" ${string} @@ -133,7 +128,7 @@ tbp="" if [[ "${progsigma}" == ".true." ]]; then tbp="_progsigma" ; fi # Radiation options -if [[ "${DO_AERO}" == "YES" ]]; then +if [[ "${DO_AERO_FCST}" == "YES" ]]; then export IAER=2011 # spectral band mapping method for aerosol optical properties else export IAER=1011 diff --git a/parm/config/gefs/config.resources b/parm/config/gefs/config.resources index 3d080b6317a..a730ea401c6 100644 --- a/parm/config/gefs/config.resources +++ b/parm/config/gefs/config.resources @@ -144,7 +144,7 @@ case ${step} in echo "MEDIATOR using (threads, PETS) = (${MEDTHREADS}, ${MEDPETS})" CHMPETS=0; CHMTHREADS=0 - if [[ "${DO_AERO}" == "YES" ]]; then + if [[ "${DO_AERO_FCST}" == "YES" ]]; then # GOCART shares the same grid and forecast tasks as FV3 (do not add write grid component tasks). (( CHMTHREADS = ATMTHREADS )) (( CHMPETS = FV3PETS )) diff --git a/parm/config/gfs/config.base b/parm/config/gfs/config.base index 24ba7192fd8..83f96ca09a4 100644 --- a/parm/config/gfs/config.base +++ b/parm/config/gfs/config.base @@ -192,7 +192,7 @@ export DO_COUPLED="NO" export DO_WAVE="NO" export DO_OCN="NO" export DO_ICE="NO" -export DO_AERO="NO" +DO_AERO="NO" export DO_PREP_OBS_AERO="NO" aero_fcst_runs="gdas" # When to run aerosol forecast: gdas, gfs, or both aero_anl_runs="gdas gfs" # When to run aerosol analysis: gdas, gfs, or both @@ -247,7 +247,7 @@ case "${APP}" in ATM) ;; ATMA) - export DO_AERO="YES" + DO_AERO="YES" ;; ATMW) export DO_COUPLED="YES" @@ -264,7 +264,7 @@ case "${APP}" in export DO_ICE="YES" if [[ "${APP}" =~ A$ ]]; then - export DO_AERO="YES" + DO_AERO="YES" fi if [[ "${APP}" =~ ^S2SW ]]; then diff --git a/parm/config/gfs/config.efcs b/parm/config/gfs/config.efcs index 1837cf06196..d27fd13cfab 100644 --- a/parm/config/gfs/config.efcs +++ b/parm/config/gfs/config.efcs @@ -13,7 +13,7 @@ string="--fv3 ${CASE}" [[ "${DO_OCN}" == "YES" ]] && string="${string} --mom6 ${OCNRES}" [[ "${DO_ICE}" == "YES" ]] && string="${string} --cice6 ${ICERES}" [[ "${DO_WAVE}" == "YES" ]] && string="${string} --ww3 ${waveGRD// /;}" -[[ "${DO_AERO}" == "YES" ]] && string="${string} --gocart" +[[ "${DO_AERO_FCST}" == "YES" ]] && string="${string} --gocart" # We are counting on $string being multiple arguments # shellcheck disable=SC2086 source "${EXPDIR}/config.ufs" ${string} diff --git a/parm/config/gfs/config.fcst b/parm/config/gfs/config.fcst index 1c19cd1c03b..6042ab3384c 100644 --- a/parm/config/gfs/config.fcst +++ b/parm/config/gfs/config.fcst @@ -8,17 +8,12 @@ echo "BEGIN: config.fcst" export USE_ESMF_THREADING="YES" # Toggle to use ESMF-managed threading or traditional threading in UFSWM export COPY_FINAL_RESTARTS="NO" # Toggle to copy restarts from the end of GFS/GEFS Run (GDAS is handled seperately) -# Turn off aerosols if they are not being forecasted -if [[ "${DO_AERO_FCST}" == "NO" ]]; then - DO_AERO="NO" -fi - # Source model specific information that is resolution dependent string="--fv3 ${CASE}" [[ "${DO_OCN}" == "YES" ]] && string="${string} --mom6 ${OCNRES}" [[ "${DO_ICE}" == "YES" ]] && string="${string} --cice6 ${ICERES}" [[ "${DO_WAVE}" == "YES" ]] && string="${string} --ww3 ${waveGRD// /;}" -[[ "${DO_AERO}" == "YES" ]] && string="${string} --gocart" +[[ "${DO_AERO_FCST}" == "YES" ]] && string="${string} --gocart" # We are counting on $string being multiple arguments # shellcheck disable=SC2086 source "${EXPDIR}/config.ufs" ${string} @@ -149,7 +144,7 @@ tbp="" if [[ "${progsigma}" == ".true." ]]; then tbp="_progsigma" ; fi # Radiation options -if [[ "${DO_AERO}" == "YES" ]]; then +if [[ "${DO_AERO_FCST}" == "YES" ]]; then export IAER=2011 # spectral band mapping method for aerosol optical properties else export IAER=1011 diff --git a/parm/config/gfs/config.resources b/parm/config/gfs/config.resources index 988eeb97d51..bad52267387 100644 --- a/parm/config/gfs/config.resources +++ b/parm/config/gfs/config.resources @@ -816,7 +816,7 @@ case ${step} in echo "MEDIATOR using (threads, PETS) = (${MEDTHREADS}, ${MEDPETS})" CHMPETS=0; CHMTHREADS=0 - if [[ "${DO_AERO}" == "YES" ]]; then + if [[ "${DO_AERO_FCST}" == "YES" ]]; then # GOCART shares the same grid and forecast tasks as FV3 (do not add write grid component tasks). (( CHMTHREADS = ATMTHREADS )) (( CHMPETS = FV3PETS )) diff --git a/scripts/exglobal_archive.py b/scripts/exglobal_archive.py index cf885b028db..2d3fa58313c 100755 --- a/scripts/exglobal_archive.py +++ b/scripts/exglobal_archive.py @@ -19,7 +19,7 @@ def main(): # Pull out all the configuration keys needed to run the rest of archive steps keys = ['ATARDIR', 'current_cycle', 'FHMIN', 'FHMAX', 'FHOUT', 'RUN', 'PDY', - 'DO_VERFRAD', 'DO_VMINMON', 'DO_VERFOZN', 'DO_ICE', 'DO_AERO', 'DO_PREP_OBS_AERO', + 'DO_VERFRAD', 'DO_VMINMON', 'DO_VERFOZN', 'DO_ICE', 'DO_PREP_OBS_AERO', 'PARMgfs', 'DO_OCN', 'DO_WAVE', 'WRITE_DOPOST', 'PSLOT', 'HPSSARCH', 'DO_MOS', 'DO_JEDISNOWDA', 'LOCALARCH', 'REALTIME', 'ROTDIR', 'ARCH_WARMICFREQ', 'ARCH_FCSTICFREQ', 'ARCH_CYC', 'assim_freq', 'ARCDIR', 'SDATE', diff --git a/ush/python/pygfs/task/archive.py b/ush/python/pygfs/task/archive.py index d138474e9af..fc5b871499d 100644 --- a/ush/python/pygfs/task/archive.py +++ b/ush/python/pygfs/task/archive.py @@ -50,7 +50,7 @@ def configure(self, arch_dict: Dict[str, Any]) -> (Dict[str, Any], List[Dict[str Parameters ---------- arch_dict : Dict[str, Any] - Task specific keys, e.g. runtime options (DO_AERO, DO_ICE, etc) + Task specific keys, e.g. runtime options (DO_AERO_FCST, DO_ICE, etc) Return ------ From 889daf7392a168e108fd64523c90e6aecbfb398e Mon Sep 17 00:00:00 2001 From: David Huber Date: Wed, 16 Oct 2024 17:50:02 +0000 Subject: [PATCH 23/36] Remove redundant RUN-case block --- parm/config/gfs/config.base | 22 +--------------------- 1 file changed, 1 insertion(+), 21 deletions(-) diff --git a/parm/config/gfs/config.base b/parm/config/gfs/config.base index 83f96ca09a4..df3795de4a5 100644 --- a/parm/config/gfs/config.base +++ b/parm/config/gfs/config.base @@ -154,29 +154,9 @@ export SENDDBN_NTC=${SENDDBN_NTC:-"NO"} export SENDDBN=${SENDDBN:-"NO"} export DBNROOT=${DBNROOT:-${UTILROOT:-}/fakedbn} -# APP settings; configurable by RUN -# If a component (WAVES, etc) needs to be turned on/off by RUN, set it here -case "${RUN}" in - "gfs") - export APP=@APP@ - ;; - "gdas") - export APP=@APP@ - ;; - "enkfgfs") - export APP=@APP@ - ;; - "enkfgdas") - export APP=@APP@ - ;; - *) - echo "FATAL ERROR: Unrecognized RUN (${RUN})!" - exit 1 - ;; -esac - shopt -s extglob # Adjust APP based on RUN +# If a component (WAVES, etc) needs to be turned on/off by RUN, set it here case "${RUN}" in enkf*) # Turn off aerosols and waves APP="${APP/%+([WA])}" From 2e92009081df873a169cc40a9af700b44ab74d4b Mon Sep 17 00:00:00 2001 From: David Huber Date: Wed, 16 Oct 2024 19:00:07 +0000 Subject: [PATCH 24/36] Add APP initialization back in --- parm/config/gfs/config.base | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/parm/config/gfs/config.base b/parm/config/gfs/config.base index df3795de4a5..583a1255cb7 100644 --- a/parm/config/gfs/config.base +++ b/parm/config/gfs/config.base @@ -154,9 +154,12 @@ export SENDDBN_NTC=${SENDDBN_NTC:-"NO"} export SENDDBN=${SENDDBN:-"NO"} export DBNROOT=${DBNROOT:-${UTILROOT:-}/fakedbn} +# APP settings +export APP=@APP@ + shopt -s extglob # Adjust APP based on RUN -# If a component (WAVES, etc) needs to be turned on/off by RUN, set it here +# If a component (WAVES, etc) needs to be turned off by RUN, set it here case "${RUN}" in enkf*) # Turn off aerosols and waves APP="${APP/%+([WA])}" From daecdc98f25baa072d1f8e491ea041898ac8e8e2 Mon Sep 17 00:00:00 2001 From: David Huber Date: Wed, 16 Oct 2024 19:11:04 +0000 Subject: [PATCH 25/36] Move NET/MODE options to _get_run_options --- workflow/applications/applications.py | 32 +++------------------- workflow/applications/gefs.py | 11 +++++--- workflow/applications/gfs_cycled.py | 8 ++++-- workflow/applications/gfs_forecast_only.py | 8 ++++-- workflow/rocoto/gefs_tasks.py | 4 +-- 5 files changed, 24 insertions(+), 39 deletions(-) diff --git a/workflow/applications/applications.py b/workflow/applications/applications.py index 67eceb02d29..1398bd5d065 100644 --- a/workflow/applications/applications.py +++ b/workflow/applications/applications.py @@ -64,10 +64,13 @@ def _init_finalize(self, conf: Configuration): for run in self.runs: self.configs[run] = self._source_configs(conf, run=run, log=False) + @abstractmethod def _get_run_options(self, conf: Configuration) -> Dict[str, Any]: ''' Determine the do_* and APP options for each RUN by sourcing config.base - for each RUN and collecting the flags into self.run_options + for each RUN and collecting the flags into self.run_options. Note that + this method is overloaded so additional NET- and MODE-dependent flags + can be set. ''' run_options = {run: {} for run in dict.fromkeys(self.runs)} @@ -96,7 +99,6 @@ def _get_run_options(self, conf: Configuration) -> Dict[str, Any]: run_options[run]['do_wave'] = run_base.get('DO_WAVE', False) run_options[run]['do_ocean'] = run_base.get('DO_OCN', False) run_options[run]['do_ice'] = run_base.get('DO_ICE', False) - run_options[run]['do_aero'] = run_base.get('DO_AERO', False) run_options[run]['do_prep_obs_aero'] = run_base.get('DO_PREP_OBS_AERO', False) run_options[run]['do_aero_anl'] = run_base.get('DO_AERO_ANL', False) run_options[run]['do_aero_fcst'] = run_base.get('DO_AERO_FCST', False) @@ -107,35 +109,9 @@ def _get_run_options(self, conf: Configuration) -> Dict[str, Any]: if not AppConfig.is_monotonic(run_options[run]['fcst_segments']): raise ValueError(f'Forecast segments do not increase monotonically: {",".join(self.fcst_segments)}') - # Append any MODE-specific options - run_options = self._netmode_run_options(run_base, run_options) - # Return the dictionary of run options return run_options - @abstractmethod - def _netmode_run_options(self, base: Dict[str, Any], run_options: Dict[str, Any]) -> Dict[str, Any]: - ''' - Defines run-based options for a given NET_MODE case. - - Parameters - ---------- - base: Dict - Parsed config.base settings - - run_options: Dict - A dictionary with valid RUN-based sub-dictionaries containing generic options. - - Returns - ------- - run_options: Dict - Output dictionary with additional options valid for the given NET and MODE. - ''' - - # Valid NET_MODE options are defined in the appropriate subclass. - - pass - @abstractmethod def _get_app_configs(self): pass diff --git a/workflow/applications/gefs.py b/workflow/applications/gefs.py index 3c5d0b7bb38..1463967f86b 100644 --- a/workflow/applications/gefs.py +++ b/workflow/applications/gefs.py @@ -15,9 +15,12 @@ def __init__(self, conf: Configuration): self.run = base.get('RUN', 'gefs') self.runs = [self.run] - def _netmode_run_options(self, base: Dict[str, Any], run_options: Dict[str, Any]) -> Dict[str, Any]: + def _get_run_options(self, conf: Configuration) -> Dict[str, Any]: + + run_options = super()._get_run_options(conf) + + run_options[self.run]['nens'] = conf.parse_config('config.base').get('NMEM_ENS', 0) - run_options[self.run]['nens'] = base.get('NMEM_ENS', 0) return run_options def _get_app_configs(self, run): @@ -38,7 +41,7 @@ def _get_app_configs(self, run): if options['do_ocean'] or options['do_ice']: configs += ['oceanice_products'] - if options['do_aero']: + if options['do_aero_fcst']: configs += ['prep_emissions'] if options['do_extractvars']: @@ -63,7 +66,7 @@ def get_task_names(self): if options['do_wave']: tasks += ['waveinit'] - if options['do_aero']: + if options['do_aero_fcst']: tasks += ['prep_emissions'] tasks += ['fcst'] diff --git a/workflow/applications/gfs_cycled.py b/workflow/applications/gfs_cycled.py index e3c50a13eec..e6f14cbb9b2 100644 --- a/workflow/applications/gfs_cycled.py +++ b/workflow/applications/gfs_cycled.py @@ -30,9 +30,13 @@ def __init__(self, conf: Configuration): self.runs.append("gfs") if base['gfs_cyc'] > 0 else 0 self.runs.append('enkfgfs') if 'gfs' in self.ens_runs and "gfs" in self.runs else 0 - def _netmode_run_options(self, base: Dict[str, Any], run_options: Dict[str, Any]) -> Dict[str, Any]: + def _get_run_options(self, conf: Configuration) -> Dict[str, Any]: + + run_options = super()._get_run_options(conf) for run in self.runs: + base = conf.parse_config('config.base', RUN=run) + run_options[run]['do_hybvar'] = base.get('DOHYBVAR', False) run_options[run]['nens'] = base.get('NMEM_ENS', 0) if run_options[run]['do_hybvar']: @@ -126,7 +130,7 @@ def _get_app_configs(self, run): if options['do_awips']: configs += ['waveawipsbulls', 'waveawipsgridded'] - if options['do_aero']: + if options['do_aero_anl']: configs += ['aeroanlgenb', 'aeroanlinit', 'aeroanlvar', 'aeroanlfinal'] if options['do_prep_obs_aero']: configs += ['prepobsaero'] diff --git a/workflow/applications/gfs_forecast_only.py b/workflow/applications/gfs_forecast_only.py index 665cbf78055..f825a25616e 100644 --- a/workflow/applications/gfs_forecast_only.py +++ b/workflow/applications/gfs_forecast_only.py @@ -15,9 +15,11 @@ def __init__(self, conf: Configuration): self.run = base.get('RUN', 'gfs') self.runs = [self.run] - def _netmode_run_options(self, base: Dict[str, Any], run_options: Dict[str, Any]) -> Dict[str, Any]: + def _get_run_options(self, conf: Configuration) -> Dict[str, Any]: - run_options[self.run]['exp_warm_start'] = base.get('EXP_WARM_START', False) + run_options = super()._get_run_options(conf) + + run_options[self.run]['exp_warm_start'] = conf.parse_config('config.base').get('EXP_WARM_START', False) return run_options @@ -36,7 +38,7 @@ def _get_app_configs(self, run): configs += ['atmos_products'] - if options['do_aero']: + if options['do_aero_fcst']: if not options['exp_warm_start']: configs += ['aerosol_init'] diff --git a/workflow/rocoto/gefs_tasks.py b/workflow/rocoto/gefs_tasks.py index 7103ec3a425..dfa8ec5858f 100644 --- a/workflow/rocoto/gefs_tasks.py +++ b/workflow/rocoto/gefs_tasks.py @@ -68,7 +68,7 @@ def fcst(self): dep_dict = {'type': 'task', 'name': f'gefs_wave_init'} dependencies.append(rocoto.add_dependency(dep_dict)) - if self.options['do_aero']: + if self.options['do_aero_fcst']: dep_dict = {'type': 'task', 'name': f'gefs_prep_emissions'} dependencies.append(rocoto.add_dependency(dep_dict)) @@ -114,7 +114,7 @@ def efcs(self): dep_dict = {'type': 'task', 'name': f'gefs_wave_init'} dependencies.append(rocoto.add_dependency(dep_dict)) - if self.options['do_aero']: + if self.options['do_aero_fcst']: dep_dict = {'type': 'task', 'name': f'gefs_prep_emissions'} dependencies.append(rocoto.add_dependency(dep_dict)) From 1975b2f33e45f4824ed946ee51875315b8f8d2fa Mon Sep 17 00:00:00 2001 From: David Huber Date: Thu, 17 Oct 2024 12:04:30 +0000 Subject: [PATCH 26/36] Reenable GEFS aerosol forecasts --- parm/config/gefs/config.base | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/parm/config/gefs/config.base b/parm/config/gefs/config.base index 888f91b8b81..d606eeccc42 100644 --- a/parm/config/gefs/config.base +++ b/parm/config/gefs/config.base @@ -179,7 +179,7 @@ case "${APP}" in ATM) ;; ATMA) - export DO_AERO_FCST="NO" + export DO_AERO_FCST="YES" ;; ATMW) export DO_COUPLED="YES" @@ -196,7 +196,7 @@ case "${APP}" in export DO_ICE="YES" if [[ "${APP}" =~ A$ ]]; then - export DO_AERO_FCST="NO" + export DO_AERO_FCST="YES" fi if [[ "${APP}" =~ ^S2SW ]]; then From 76a62d07a0371259899c850a82e05f9b21e0f79f Mon Sep 17 00:00:00 2001 From: David Huber Date: Fri, 18 Oct 2024 09:48:51 -0500 Subject: [PATCH 27/36] Reduce Orion available memory --- parm/config/gfs/config.resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/parm/config/gfs/config.resources b/parm/config/gfs/config.resources index bad52267387..ddf42410354 100644 --- a/parm/config/gfs/config.resources +++ b/parm/config/gfs/config.resources @@ -55,7 +55,7 @@ case ${machine} in "ORION") max_tasks_per_node=40 # shellcheck disable=SC2034 - mem_node_max="188GB" + mem_node_max="180GB" ;; "HERCULES") max_tasks_per_node=80 From 2d4139b72049dd8af2b90a001d58c0d7bb798282 Mon Sep 17 00:00:00 2001 From: David Huber Date: Wed, 23 Oct 2024 15:13:41 +0000 Subject: [PATCH 28/36] Fix interval_gfs conflicts --- workflow/applications/applications.py | 3 +-- workflow/applications/gfs_cycled.py | 4 ++-- workflow/rocoto/gefs_xml.py | 2 +- workflow/rocoto/gfs_cycled_xml.py | 2 +- workflow/rocoto/gfs_forecast_only_xml.py | 6 +++--- workflow/rocoto/gfs_tasks.py | 8 ++++---- 6 files changed, 12 insertions(+), 13 deletions(-) diff --git a/workflow/applications/applications.py b/workflow/applications/applications.py index b7fb605e099..d73f2dc9968 100644 --- a/workflow/applications/applications.py +++ b/workflow/applications/applications.py @@ -2,7 +2,7 @@ from typing import Dict, List, Any from hosts import Host -from wxflow import Configuration, to_timedelta +from wxflow import Configuration from abc import ABC, ABCMeta, abstractmethod __all__ = ['AppConfig'] @@ -41,7 +41,6 @@ def __init__(self, conf: Configuration) -> None: f'{", ".join(self.VALID_MODES)}\n') self.net = base['NET'] - self.interval_gfs = to_timedelta(f"{base.get('INTERVAL_GFS')}H") print(f"Generating the XML for a {self.mode}_{self.net} case") def _init_finalize(self, conf: Configuration): diff --git a/workflow/applications/gfs_cycled.py b/workflow/applications/gfs_cycled.py index 25977192a71..bc24cd84d4d 100644 --- a/workflow/applications/gfs_cycled.py +++ b/workflow/applications/gfs_cycled.py @@ -1,6 +1,6 @@ from applications.applications import AppConfig from typing import Dict, Any -from wxflow import Configuration, to_timedelta +from wxflow import Configuration class GFSCycledAppConfig(AppConfig): @@ -26,7 +26,7 @@ def __init__(self, conf: Configuration): # Now construct self.runs the desired XML order (gdas, enkfgdas, gfs, enkfgfs) self.runs = ["gdas"] # We always have a 'gdas' run self.runs.append('enkfgdas') if 'gdas' in self.ens_runs else 0 - self.runs.append("gfs") if base['interval_gfs'] > to_timedelta("0H") else 0 + self.runs.append("gfs") if base['INTERVAL_GFS'] > 0 else 0 self.runs.append('enkfgfs') if 'gfs' in self.ens_runs and "gfs" in self.runs else 0 def _get_run_options(self, conf: Configuration) -> Dict[str, Any]: diff --git a/workflow/rocoto/gefs_xml.py b/workflow/rocoto/gefs_xml.py index a5dfd5140ef..b41a172a2a4 100644 --- a/workflow/rocoto/gefs_xml.py +++ b/workflow/rocoto/gefs_xml.py @@ -16,7 +16,7 @@ def __init__(self, app_config: AppConfig, rocoto_config: Dict) -> None: def get_cycledefs(self): sdate = self._base['SDATE_GFS'] edate = self._base['EDATE'] - interval = self._app_config.interval_gfs + interval = to_timedelta("{self._base['INTERVAL_GFS']}H") sdate_str = sdate.strftime("%Y%m%d%H%M") edate_str = edate.strftime("%Y%m%d%H%M") interval_str = timedelta_to_HMS(interval) diff --git a/workflow/rocoto/gfs_cycled_xml.py b/workflow/rocoto/gfs_cycled_xml.py index eef77ba7fcc..000a7adef8d 100644 --- a/workflow/rocoto/gfs_cycled_xml.py +++ b/workflow/rocoto/gfs_cycled_xml.py @@ -24,7 +24,7 @@ def get_cycledefs(self): sdate_str = sdate.strftime("%Y%m%d%H%M") strings.append(f'\t{sdate_str} {edate_str} {interval_str}') - interval_gfs = self._app_config.interval_gfs + interval_gfs = to_timedelta("{self._base['INTERVAL_GFS']}H") if interval_gfs > to_timedelta("0H"): sdate_gfs = self._base['SDATE_GFS'] diff --git a/workflow/rocoto/gfs_forecast_only_xml.py b/workflow/rocoto/gfs_forecast_only_xml.py index a4d5b0878b8..fa313291761 100644 --- a/workflow/rocoto/gfs_forecast_only_xml.py +++ b/workflow/rocoto/gfs_forecast_only_xml.py @@ -14,15 +14,15 @@ def __init__(self, app_config: AppConfig, rocoto_config: Dict) -> None: def get_cycledefs(self): sdate_gfs = self._base['SDATE_GFS'] edate_gfs = self._base['EDATE'] - interval_gfs = self._app_config.interval_gfs + interval_gfs = to_timedelta("{self._base['INTERVAL_GFS']}H") strings = [] sdate_gfs_str = sdate_gfs.strftime("%Y%m%d%H%M") edate_gfs_str = edate_gfs.strftime("%Y%m%d%H%M") interval_gfs_str = timedelta_to_HMS(interval_gfs) strings.append(f'\t{sdate_gfs_str} {edate_gfs_str} {interval_gfs_str}') - date2 = sdate_gfs + interval_gfs - if date2 <= edate_gfs: + date2_gfs = sdate_gfs + interval_gfs + if date2_gfs <= edate_gfs: date2_gfs_str = date2_gfs.strftime("%Y%m%d%H%M") strings.append(f'\t{date2_gfs_str} {edate_gfs_str} {interval_gfs_str}') diff --git a/workflow/rocoto/gfs_tasks.py b/workflow/rocoto/gfs_tasks.py index 7f92bd1c828..172d38a56b4 100644 --- a/workflow/rocoto/gfs_tasks.py +++ b/workflow/rocoto/gfs_tasks.py @@ -336,7 +336,7 @@ def atmanlinit(self): dependencies = rocoto.create_dependency(dep=deps) interval_gfs = self._base["INTERVAL_GFS"] - gfs_enkf = True if self.options['do_hybvar'] and 'gfs' in self.options['ens_runs'] else False + gfs_enkf = True if self.options['do_hybvar'] and 'gfs' in self.app_config.ens_runs else False cycledef = self.run if self.run in ['gfs'] and gfs_enkf and interval_gfs != 6: @@ -875,7 +875,7 @@ def _fcst_forecast_only(self): # Calculate offset based on RUN = gfs | gdas interval = None if self.run in ['gfs']: - interval = to_timedelta(f"{self._base['INTERVAL_GFS']}H") + interval = self._base['INTERVAL_GFS'] elif self.run in ['gdas']: interval = self._base['assim_freq'] offset = timedelta_to_HMS(-interval) @@ -1832,8 +1832,8 @@ def metp(self): deps = [] dep_dict = {'type': 'task', 'name': f'{self.run}_arch'} deps.append(rocoto.add_dependency(dep_dict)) - if self.app_config.interval_gfs < to_timedelta('24H'): - n_lookback = self.app_config.interval_gfs // to_timedelta('6H') + if self._base["interval_gfs"] < to_timedelta("24H"): + n_lookback = self._base["interval_gfs"] // to_timedelta("6H") for lookback in range(1, n_lookback + 1): deps2 = [] dep_dict = {'type': 'taskvalid', 'name': f'{self.run}_arch', 'condition': 'not'} From b342a67f9616e9b56cd2fee3f148d568231cf75b Mon Sep 17 00:00:00 2001 From: David Huber Date: Wed, 23 Oct 2024 19:57:35 +0000 Subject: [PATCH 29/36] Resolve additional interval_gfs conflicts --- workflow/rocoto/gefs_xml.py | 2 +- workflow/rocoto/gfs_cycled_xml.py | 2 +- workflow/rocoto/gfs_forecast_only_xml.py | 2 +- workflow/rocoto/gfs_tasks.py | 8 ++++---- workflow/rocoto/workflow_xml.py | 3 +++ 5 files changed, 10 insertions(+), 7 deletions(-) diff --git a/workflow/rocoto/gefs_xml.py b/workflow/rocoto/gefs_xml.py index b41a172a2a4..f0ea407e34b 100644 --- a/workflow/rocoto/gefs_xml.py +++ b/workflow/rocoto/gefs_xml.py @@ -16,7 +16,7 @@ def __init__(self, app_config: AppConfig, rocoto_config: Dict) -> None: def get_cycledefs(self): sdate = self._base['SDATE_GFS'] edate = self._base['EDATE'] - interval = to_timedelta("{self._base['INTERVAL_GFS']}H") + interval = self._base['interval_gfs'] sdate_str = sdate.strftime("%Y%m%d%H%M") edate_str = edate.strftime("%Y%m%d%H%M") interval_str = timedelta_to_HMS(interval) diff --git a/workflow/rocoto/gfs_cycled_xml.py b/workflow/rocoto/gfs_cycled_xml.py index 000a7adef8d..dfeefd1402e 100644 --- a/workflow/rocoto/gfs_cycled_xml.py +++ b/workflow/rocoto/gfs_cycled_xml.py @@ -24,7 +24,7 @@ def get_cycledefs(self): sdate_str = sdate.strftime("%Y%m%d%H%M") strings.append(f'\t{sdate_str} {edate_str} {interval_str}') - interval_gfs = to_timedelta("{self._base['INTERVAL_GFS']}H") + interval_gfs = self._base['interval_gfs'] if interval_gfs > to_timedelta("0H"): sdate_gfs = self._base['SDATE_GFS'] diff --git a/workflow/rocoto/gfs_forecast_only_xml.py b/workflow/rocoto/gfs_forecast_only_xml.py index fa313291761..018bdfaef28 100644 --- a/workflow/rocoto/gfs_forecast_only_xml.py +++ b/workflow/rocoto/gfs_forecast_only_xml.py @@ -14,7 +14,7 @@ def __init__(self, app_config: AppConfig, rocoto_config: Dict) -> None: def get_cycledefs(self): sdate_gfs = self._base['SDATE_GFS'] edate_gfs = self._base['EDATE'] - interval_gfs = to_timedelta("{self._base['INTERVAL_GFS']}H") + interval_gfs = self._base['interval_gfs'] strings = [] sdate_gfs_str = sdate_gfs.strftime("%Y%m%d%H%M") edate_gfs_str = edate_gfs.strftime("%Y%m%d%H%M") diff --git a/workflow/rocoto/gfs_tasks.py b/workflow/rocoto/gfs_tasks.py index 172d38a56b4..74252a61f92 100644 --- a/workflow/rocoto/gfs_tasks.py +++ b/workflow/rocoto/gfs_tasks.py @@ -58,7 +58,7 @@ def prep(self): dependencies = rocoto.create_dependency(dep_condition='and', dep=deps) cycledef = self.run - if self.run in ['gfs'] and gfs_enkf and self.app_config.interval_gfs != 6: + if self.run in ['gfs'] and gfs_enkf and self._base['INTERVAL_GFS'] != 6: cycledef = 'gdas' resources = self.get_resource('prep') @@ -148,9 +148,9 @@ def aerosol_init(self): # Calculate offset based on RUN = gfs | gdas interval = None if self.run in ['gfs']: - interval = self._base['INTERVAL_GFS'] + interval = self._base['interval_gfs'] elif self.run in ['gdas']: - interval = self._base['INTERVAL'] + interval = self._base['interval'] offset = timedelta_to_HMS(-interval) # Files from previous cycle @@ -875,7 +875,7 @@ def _fcst_forecast_only(self): # Calculate offset based on RUN = gfs | gdas interval = None if self.run in ['gfs']: - interval = self._base['INTERVAL_GFS'] + interval = self._base['interval_gfs'] elif self.run in ['gdas']: interval = self._base['assim_freq'] offset = timedelta_to_HMS(-interval) diff --git a/workflow/rocoto/workflow_xml.py b/workflow/rocoto/workflow_xml.py index 03c7d8b4b90..bed19ad5eea 100644 --- a/workflow/rocoto/workflow_xml.py +++ b/workflow/rocoto/workflow_xml.py @@ -7,6 +7,7 @@ from typing import Dict from applications.applications import AppConfig from rocoto.workflow_tasks import get_wf_tasks +from wxflow import to_timedelta import rocoto.rocoto as rocoto from abc import ABC, abstractmethod @@ -20,6 +21,8 @@ def __init__(self, app_config: AppConfig, rocoto_config: Dict) -> None: # Use the first config.base (sourced with an arbitrary RUN) self._base = self._app_config.configs[next(iter(self._app_config.configs))]['base'] + self._base['interval_gdas'] = to_timedelta(f'{self._base["assim_freq"]}H') + self._base['interval_gfs'] = to_timedelta(f'{self._base["INTERVAL_GFS"]}H') self.preamble = self._get_preamble() self.definitions = self._get_definitions() From 03fb60e5440fdd74183a4007ff31f4cefed879e2 Mon Sep 17 00:00:00 2001 From: David Huber Date: Tue, 12 Nov 2024 13:12:46 -0600 Subject: [PATCH 30/36] Do not try to copy the first GOCART forecast file for a segment --- ush/forecast_postdet.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ush/forecast_postdet.sh b/ush/forecast_postdet.sh index 25b2e28d754..0ba7d15ee1b 100755 --- a/ush/forecast_postdet.sh +++ b/ush/forecast_postdet.sh @@ -734,7 +734,7 @@ GOCART_out() { local fhr local vdate for fhr in ${GOCART_OUTPUT_FH}; do - if (( fhr == 0 )); then continue; fi + if (( fhr == FHMIN )); then continue; fi vdate=$(date --utc -d "${current_cycle:0:8} ${current_cycle:8:2} + ${fhr} hours" +%Y%m%d%H) ${NCP} "${DATA}/gocart.inst_aod.${vdate:0:8}_${vdate:8:2}00z.nc4" \ "${COMOUT_CHEM_HISTORY}/gocart.inst_aod.${vdate:0:8}_${vdate:8:2}00z.nc4" From a91e75cdd4519517365c25aa32df7e2863b5e57c Mon Sep 17 00:00:00 2001 From: David Huber Date: Wed, 13 Nov 2024 07:52:45 -0600 Subject: [PATCH 31/36] Temporarilly disable UFS DA tests on WCOSS2 --- ci/cases/pr/C96C48_ufs_hybatmDA.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/cases/pr/C96C48_ufs_hybatmDA.yaml b/ci/cases/pr/C96C48_ufs_hybatmDA.yaml index 41a8baa725b..031054079a9 100644 --- a/ci/cases/pr/C96C48_ufs_hybatmDA.yaml +++ b/ci/cases/pr/C96C48_ufs_hybatmDA.yaml @@ -21,4 +21,4 @@ skip_ci_on_hosts: - gaea - orion - hercules - + - wcoss2 From 00ad99b525e580bfc4c0ee60275dddfcc4684fe0 Mon Sep 17 00:00:00 2001 From: David Huber <69919478+DavidHuber-NOAA@users.noreply.github.com> Date: Fri, 22 Nov 2024 10:50:42 -0500 Subject: [PATCH 32/36] Clean config.base and de-abstract get_run_options. Co-authored-by: Walter Kolczynski - NOAA --- parm/config/gfs/config.base | 18 ++++++------------ workflow/applications/applications.py | 1 - 2 files changed, 6 insertions(+), 13 deletions(-) diff --git a/parm/config/gfs/config.base b/parm/config/gfs/config.base index bef54644dda..286de5965f7 100644 --- a/parm/config/gfs/config.base +++ b/parm/config/gfs/config.base @@ -262,18 +262,12 @@ esac # Aerosol forecasts and analyses may be RUN-dependent if [[ "${DO_AERO}" == "YES" ]]; then - for aero_run in ${aero_anl_runs}; do - if [[ "${aero_run}" == "${RUN}" ]]; then - export DO_AERO_ANL="YES" - break - fi - done - for aero_run in ${aero_fcst_runs}; do - if [[ "${aero_run}" == "${RUN}" ]]; then - export DO_AERO_FCST="YES" - break - fi - done + if [[ ${aero_anl_runs} =~ ${RUN} ]]; then + export DO_AERO_ANL="YES" + fi + if [[ ${aero_fcst_runs} =~ ${RUN} ]]; then + export DO_AERO_FSCT="YES" + fi fi # Surface cycle update frequency diff --git a/workflow/applications/applications.py b/workflow/applications/applications.py index d73f2dc9968..22e299df209 100644 --- a/workflow/applications/applications.py +++ b/workflow/applications/applications.py @@ -61,7 +61,6 @@ def _init_finalize(self, conf: Configuration): for run in self.runs: self.configs[run] = self._source_configs(conf, run=run, log=False) - @abstractmethod def _get_run_options(self, conf: Configuration) -> Dict[str, Any]: ''' Determine the do_* and APP options for each RUN by sourcing config.base From ad69015251c84719bf80aec6522a17c4ebaf1f90 Mon Sep 17 00:00:00 2001 From: David Huber Date: Mon, 25 Nov 2024 13:07:10 +0000 Subject: [PATCH 33/36] Null commit to trigger CI From 6a7ffa51bf904c34fb2dc12abbf7df0cbf405d17 Mon Sep 17 00:00:00 2001 From: David Huber Date: Mon, 25 Nov 2024 13:35:46 +0000 Subject: [PATCH 34/36] Try to use the gh specified by the launch script --- ci/Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/Jenkinsfile b/ci/Jenkinsfile index b3bd6a917a8..8cdc2929476 100644 --- a/ci/Jenkinsfile +++ b/ci/Jenkinsfile @@ -79,7 +79,7 @@ pipeline { echo "Getting Common Workspace for ${Machine}" ws("${custom_workspace[machine]}/${env.CHANGE_ID}") { properties([parameters([[$class: 'NodeParameterDefinition', allowedSlaves: ['built-in', 'Hercules-EMC', 'Hera-EMC', 'Orion-EMC', 'Gaea'], defaultSlaves: ['built-in'], name: '', nodeEligibility: [$class: 'AllNodeEligibility'], triggerIfResult: 'allCases']])]) - GH = sh(script: "which gh || echo '~/bin/gh'", returnStdout: true).trim() + GH = sh(script: "env ${GH} || which gh || echo '~/bin/gh'", returnStdout: true).trim() CUSTOM_WORKSPACE = "${WORKSPACE}" sh(script: "mkdir -p ${CUSTOM_WORKSPACE}/RUNTESTS;rm -Rf ${CUSTOM_WORKSPACE}/RUNTESTS/*") sh(script: """${GH} pr edit ${env.CHANGE_ID} --repo ${repo_url} --add-label "CI-${Machine}-Building" --remove-label "CI-${Machine}-Ready" """) From 74c08046c9b5e64d11b904f175e5b6debd442ad7 Mon Sep 17 00:00:00 2001 From: David Huber Date: Mon, 25 Nov 2024 13:50:54 +0000 Subject: [PATCH 35/36] Create a new custom_workspace --- ci/Jenkinsfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ci/Jenkinsfile b/ci/Jenkinsfile index 8cdc2929476..3c889e51d56 100644 --- a/ci/Jenkinsfile +++ b/ci/Jenkinsfile @@ -5,7 +5,7 @@ def cases = '' def GH = 'none' // Location of the custom workspaces for each machine in the CI system. They are persitent for each iteration of the PR. def NodeName = [hera: 'Hera-EMC', orion: 'Orion-EMC', hercules: 'Hercules-EMC', gaea: 'Gaea'] -def custom_workspace = [hera: '/scratch1/NCEPDEV/global/CI', orion: '/work2/noaa/stmp/CI/ORION', hercules: '/work2/noaa/global/CI/HERCULES', gaea: '/gpfs/f5/epic/proj-shared/global/CI'] +def custom_workspace = [hera: '/scratch1/NCEPDEV/global/CI_dh', orion: '/work2/noaa/stmp/CI/ORION', hercules: '/work2/noaa/global/CI/HERCULES', gaea: '/gpfs/f5/epic/proj-shared/global/CI'] def repo_url = 'git@github.com:NOAA-EMC/global-workflow.git' def STATUS = 'Passed' @@ -79,7 +79,7 @@ pipeline { echo "Getting Common Workspace for ${Machine}" ws("${custom_workspace[machine]}/${env.CHANGE_ID}") { properties([parameters([[$class: 'NodeParameterDefinition', allowedSlaves: ['built-in', 'Hercules-EMC', 'Hera-EMC', 'Orion-EMC', 'Gaea'], defaultSlaves: ['built-in'], name: '', nodeEligibility: [$class: 'AllNodeEligibility'], triggerIfResult: 'allCases']])]) - GH = sh(script: "env ${GH} || which gh || echo '~/bin/gh'", returnStdout: true).trim() + GH = sh(script: "which gh || echo '~/bin/gh'", returnStdout: true).trim() CUSTOM_WORKSPACE = "${WORKSPACE}" sh(script: "mkdir -p ${CUSTOM_WORKSPACE}/RUNTESTS;rm -Rf ${CUSTOM_WORKSPACE}/RUNTESTS/*") sh(script: """${GH} pr edit ${env.CHANGE_ID} --repo ${repo_url} --add-label "CI-${Machine}-Building" --remove-label "CI-${Machine}-Ready" """) From e5becab5a2b5b3bbcf03b97d702384509faad1a8 Mon Sep 17 00:00:00 2001 From: David Huber <69919478+DavidHuber-NOAA@users.noreply.github.com> Date: Mon, 25 Nov 2024 11:26:30 -0500 Subject: [PATCH 36/36] Fix typo. --- parm/config/gfs/config.base | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/parm/config/gfs/config.base b/parm/config/gfs/config.base index 74be457e6c0..91f353360fa 100644 --- a/parm/config/gfs/config.base +++ b/parm/config/gfs/config.base @@ -267,7 +267,7 @@ if [[ "${DO_AERO}" == "YES" ]]; then export DO_AERO_ANL="YES" fi if [[ ${aero_fcst_runs} =~ ${RUN} ]]; then - export DO_AERO_FSCT="YES" + export DO_AERO_FCST="YES" fi fi