diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index dbebfe8f6e..3f8fe65065 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -33,6 +33,17 @@ # Change characteristics - Is this a breaking change (a change in existing functionality)? YES/NO - Does this change require a documentation update? YES/NO +- Does this change require an update to any of the following submodules? YES/NO (If YES, please add a link to any PRs that are pending.) + - [ ] EMC verif-global + - [ ] GDAS + - [ ] GFS-utils + - [ ] GSI + - [ ] GSI-monitor + - [ ] GSI-utils + - [ ] UFS-utils + - [ ] UFS-weather-model + - [ ] wxflow + # How has this been tested? _dir. For all other directories, the + names will follow --> _dir. + """ + + rel_path_dict = {} + for key, value in self.task_config.items(): + if isinstance(value, str): + if root_path in value: + rel_path = value.replace(root_path, "") + rel_key = (key[4:] if key.startswith("COMIN_") else key).lower() + "_dir" + rel_path_dict[rel_key] = rel_path + + return rel_path_dict + + @staticmethod + @logit(logger) + def _construct_arcdir_set(arcdir_j2yaml, arch_dict) -> Dict: + """Construct the list of files to send to the ARCDIR and Fit2Obs + directories from a template. + + TODO Copying Fit2Obs data doesn't belong in archiving should be + moved elsewhere. + + Parameters + ---------- + arcdir_j2yaml: str + The filename of the ARCDIR jinja template to parse. + + arch_dict: Dict + The context dictionary to parse arcdir_j2yaml with. + + Return + ------ + arcdir_set : Dict + FileHandler dictionary (i.e. with top level "mkdir" and "copy" keys) + containing all directories that need to be created and what data + files need to be copied to the ARCDIR and the Fit2Obs directory. + """ + + # Get the FileHandler dictionary for creating directories and copying + # to the ARCDIR and VFYARC directories. + arcdir_set = parse_j2yaml(arcdir_j2yaml, + arch_dict, + allow_missing=True) + + return arcdir_set + + @staticmethod + @logit(logger) + def _rename_cyclone_expt(arch_dict) -> None: + + # Rename the experiment in the tracker files from "AVNO" to the + # first 4 letters of PSLOT. + pslot4 = arch_dict.PSLOT.upper() + if len(arch_dict.PSLOT) > 4: + pslot4 = arch_dict.PSLOT[0:4].upper() + + track_dir_in = arch_dict.COMIN_ATMOS_TRACK + track_dir_out = arch_dict.COMOUT_ATMOS_TRACK + run = arch_dict.RUN + cycle_HH = strftime(arch_dict.current_cycle, "%H") + + if run == "gfs": + in_track_file = (track_dir_in + "/avno.t" + + cycle_HH + "z.cycle.trackatcfunix") + in_track_p_file = (track_dir_in + "/avnop.t" + + cycle_HH + "z.cycle.trackatcfunixp") + elif run == "gdas": + in_track_file = (track_dir_in + "/gdas.t" + + cycle_HH + "z.cycle.trackatcfunix") + in_track_p_file = (track_dir_in + "/gdasp.t" + + cycle_HH + "z.cycle.trackatcfunixp") + + if not os.path.isfile(in_track_file): + # Do not attempt to archive the outputs + return + + out_track_file = track_dir_out + "/atcfunix." + run + "." + to_YMDH(arch_dict.current_cycle) + out_track_p_file = track_dir_out + "/atcfunixp." + run + "." + to_YMDH(arch_dict.current_cycle) + + def replace_string_from_to_file(filename_in, filename_out, search_str, replace_str): + + """Write a new file from the contents of an input file while searching + and replacing ASCII strings. To prevent partial file creation, a + temporary file is created and moved to the final location only + after the search/replace is finished. + + Parameters + ---------- + filename_in : str + Input filename + + filename_out : str + Output filename + + search_str : str + ASCII string to search for + + replace_str : str + ASCII string to replace the search_str with + """ + with open(filename_in) as old_file: + lines = old_file.readlines() + + out_lines = [line.replace(search_str, replace_str) for line in lines] + + with open("/tmp/track_file", "w") as new_file: + new_file.writelines(out_lines) + + shutil.move("tmp/track_file", filename_out) + + replace_string_from_to_file(in_track_file, out_track_file, "AVNO", pslot4) + replace_string_from_to_file(in_track_p_file, out_track_p_file, "AVNO", pslot4) + + return diff --git a/ush/python/pygfs/task/atm_analysis.py b/ush/python/pygfs/task/atm_analysis.py index da41574fc9..4e9d37335c 100644 --- a/ush/python/pygfs/task/atm_analysis.py +++ b/ush/python/pygfs/task/atm_analysis.py @@ -11,7 +11,7 @@ FileHandler, add_to_datetime, to_fv3time, to_timedelta, to_YMDH, chdir, - parse_yamltmpl, parse_j2yaml, save_as_yaml, + parse_j2yaml, save_as_yaml, logit, Executable, WorkflowException) @@ -28,32 +28,35 @@ class AtmAnalysis(Analysis): def __init__(self, config): super().__init__(config) - _res = int(self.config.CASE[1:]) - _res_anl = int(self.config.CASE_ANL[1:]) - _window_begin = add_to_datetime(self.runtime_config.current_cycle, -to_timedelta(f"{self.config.assim_freq}H") / 2) - _fv3jedi_yaml = os.path.join(self.runtime_config.DATA, f"{self.runtime_config.CDUMP}.t{self.runtime_config.cyc:02d}z.atmvar.yaml") + _res = int(self.task_config.CASE[1:]) + _res_anl = int(self.task_config.CASE_ANL[1:]) + _window_begin = add_to_datetime(self.task_config.current_cycle, -to_timedelta(f"{self.task_config.assim_freq}H") / 2) + _jedi_yaml = os.path.join(self.task_config.DATA, f"{self.task_config.RUN}.t{self.task_config.cyc:02d}z.atmvar.yaml") # Create a local dictionary that is repeatedly used across this class local_dict = AttrDict( { 'npx_ges': _res + 1, 'npy_ges': _res + 1, - 'npz_ges': self.config.LEVS - 1, - 'npz': self.config.LEVS - 1, + 'npz_ges': self.task_config.LEVS - 1, + 'npz': self.task_config.LEVS - 1, 'npx_anl': _res_anl + 1, 'npy_anl': _res_anl + 1, - 'npz_anl': self.config.LEVS - 1, + 'npz_anl': self.task_config.LEVS - 1, 'ATM_WINDOW_BEGIN': _window_begin, - 'ATM_WINDOW_LENGTH': f"PT{self.config.assim_freq}H", - 'OPREFIX': f"{self.runtime_config.CDUMP}.t{self.runtime_config.cyc:02d}z.", # TODO: CDUMP is being replaced by RUN - 'APREFIX': f"{self.runtime_config.CDUMP}.t{self.runtime_config.cyc:02d}z.", # TODO: CDUMP is being replaced by RUN - 'GPREFIX': f"gdas.t{self.runtime_config.previous_cycle.hour:02d}z.", - 'fv3jedi_yaml': _fv3jedi_yaml, + 'ATM_WINDOW_LENGTH': f"PT{self.task_config.assim_freq}H", + 'OPREFIX': f"{self.task_config.RUN}.t{self.task_config.cyc:02d}z.", + 'APREFIX': f"{self.task_config.RUN}.t{self.task_config.cyc:02d}z.", + 'GPREFIX': f"gdas.t{self.task_config.previous_cycle.hour:02d}z.", + 'jedi_yaml': _jedi_yaml, + 'atm_obsdatain_path': f"{self.task_config.DATA}/obs/", + 'atm_obsdataout_path': f"{self.task_config.DATA}/diags/", + 'BKG_TSTEP': "PT1H" # Placeholder for 4D applications } ) - # task_config is everything that this task should need - self.task_config = AttrDict(**self.config, **self.runtime_config, **local_dict) + # Extend task_config with local_dict + self.task_config = AttrDict(**self.task_config, **local_dict) @logit(logger) def initialize(self: Analysis) -> None: @@ -71,41 +74,38 @@ def initialize(self: Analysis) -> None: super().initialize() # stage CRTM fix files - crtm_fix_list_path = os.path.join(self.task_config.HOMEgfs, 'parm', 'gdas', 'atm_crtm_coeff.yaml') - logger.debug(f"Staging CRTM fix files from {crtm_fix_list_path}") - crtm_fix_list = parse_j2yaml(crtm_fix_list_path, self.task_config) + logger.info(f"Staging CRTM fix files from {self.task_config.CRTM_FIX_YAML}") + crtm_fix_list = parse_j2yaml(self.task_config.CRTM_FIX_YAML, self.task_config) FileHandler(crtm_fix_list).sync() # stage fix files - jedi_fix_list_path = os.path.join(self.task_config.HOMEgfs, 'parm', 'gdas', 'atm_jedi_fix.yaml') - logger.debug(f"Staging JEDI fix files from {jedi_fix_list_path}") - jedi_fix_list = parse_j2yaml(jedi_fix_list_path, self.task_config) + logger.info(f"Staging JEDI fix files from {self.task_config.JEDI_FIX_YAML}") + jedi_fix_list = parse_j2yaml(self.task_config.JEDI_FIX_YAML, self.task_config) FileHandler(jedi_fix_list).sync() # stage static background error files, otherwise it will assume ID matrix - logger.debug(f"Stage files for STATICB_TYPE {self.task_config.STATICB_TYPE}") - FileHandler(self.get_berror_dict(self.task_config)).sync() + logger.info(f"Stage files for STATICB_TYPE {self.task_config.STATICB_TYPE}") + if self.task_config.STATICB_TYPE != 'identity': + berror_staging_dict = parse_j2yaml(self.task_config.BERROR_STAGING_YAML, self.task_config) + else: + berror_staging_dict = {} + FileHandler(berror_staging_dict).sync() # stage ensemble files for use in hybrid background error if self.task_config.DOHYBVAR: logger.debug(f"Stage ensemble files for DOHYBVAR {self.task_config.DOHYBVAR}") - localconf = AttrDict() - keys = ['COM_ATMOS_RESTART_TMPL', 'previous_cycle', 'ROTDIR', 'RUN', - 'NMEM_ENS', 'DATA', 'current_cycle', 'ntiles'] - for key in keys: - localconf[key] = self.task_config[key] - localconf.RUN = 'enkf' + self.task_config.RUN - localconf.dirname = 'ens' - FileHandler(self.get_fv3ens_dict(localconf)).sync() + fv3ens_staging_dict = parse_j2yaml(self.task_config.FV3ENS_STAGING_YAML, self.task_config) + FileHandler(fv3ens_staging_dict).sync() # stage backgrounds - FileHandler(self.get_bkg_dict(AttrDict(self.task_config))).sync() + logger.info(f"Staging background files from {self.task_config.VAR_BKG_STAGING_YAML}") + bkg_staging_dict = parse_j2yaml(self.task_config.VAR_BKG_STAGING_YAML, self.task_config) + FileHandler(bkg_staging_dict).sync() # generate variational YAML file - logger.debug(f"Generate variational YAML file: {self.task_config.fv3jedi_yaml}") - varda_yaml = parse_j2yaml(self.task_config.ATMVARYAML, self.task_config) - save_as_yaml(varda_yaml, self.task_config.fv3jedi_yaml) - logger.info(f"Wrote variational YAML to: {self.task_config.fv3jedi_yaml}") + logger.debug(f"Generate variational YAML file: {self.task_config.jedi_yaml}") + save_as_yaml(self.task_config.jedi_config, self.task_config.jedi_yaml) + logger.info(f"Wrote variational YAML to: {self.task_config.jedi_yaml}") # need output dir for diags and anl logger.debug("Create empty output [anl, diags] directories to receive output from executable") @@ -116,14 +116,16 @@ def initialize(self: Analysis) -> None: FileHandler({'mkdir': newdirs}).sync() @logit(logger) - def execute(self: Analysis) -> None: + def variational(self: Analysis) -> None: chdir(self.task_config.DATA) - exec_cmd = Executable(self.task_config.APRUN_ATMANL) - exec_name = os.path.join(self.task_config.DATA, 'fv3jedi_var.x') + exec_cmd = Executable(self.task_config.APRUN_ATMANLVAR) + exec_name = os.path.join(self.task_config.DATA, 'gdas.x') exec_cmd.add_default_arg(exec_name) - exec_cmd.add_default_arg(self.task_config.fv3jedi_yaml) + exec_cmd.add_default_arg('fv3jedi') + exec_cmd.add_default_arg('variational') + exec_cmd.add_default_arg(self.task_config.jedi_yaml) try: logger.debug(f"Executing {exec_cmd}") @@ -135,6 +137,31 @@ def execute(self: Analysis) -> None: pass + @logit(logger) + def init_fv3_increment(self: Analysis) -> None: + # Setup JEDI YAML file + self.task_config.jedi_yaml = os.path.join(self.task_config.DATA, + f"{self.task_config.JCB_ALGO}.yaml") + save_as_yaml(self.get_jedi_config(self.task_config.JCB_ALGO), self.task_config.jedi_yaml) + + # Link JEDI executable to run directory + self.task_config.jedi_exe = self.link_jediexe() + + @logit(logger) + def fv3_increment(self: Analysis) -> None: + # Run executable + exec_cmd = Executable(self.task_config.APRUN_ATMANLFV3INC) + exec_cmd.add_default_arg(self.task_config.jedi_exe) + exec_cmd.add_default_arg(self.task_config.jedi_yaml) + + try: + logger.debug(f"Executing {exec_cmd}") + exec_cmd() + except OSError: + raise OSError(f"Failed to execute {exec_cmd}") + except Exception: + raise WorkflowException(f"An error occured during execution of {exec_cmd}") + @logit(logger) def finalize(self: Analysis) -> None: """Finalize a global atm analysis @@ -152,7 +179,7 @@ def finalize(self: Analysis) -> None: atmstat = os.path.join(self.task_config.COM_ATMOS_ANALYSIS, f"{self.task_config.APREFIX}atmstat") # get list of diag files to put in tarball - diags = glob.glob(os.path.join(self.task_config.DATA, 'diags', 'diag*nc4')) + diags = glob.glob(os.path.join(self.task_config.DATA, 'diags', 'diag*nc')) logger.info(f"Compressing {len(diags)} diag files to {atmstat}.gz") @@ -170,9 +197,9 @@ def finalize(self: Analysis) -> None: archive.add(diaggzip, arcname=os.path.basename(diaggzip)) # copy full YAML from executable to ROTDIR - logger.info(f"Copying {self.task_config.fv3jedi_yaml} to {self.task_config.COM_ATMOS_ANALYSIS}") - src = os.path.join(self.task_config.DATA, f"{self.task_config.CDUMP}.t{self.task_config.cyc:02d}z.atmvar.yaml") - dest = os.path.join(self.task_config.COM_ATMOS_ANALYSIS, f"{self.task_config.CDUMP}.t{self.task_config.cyc:02d}z.atmvar.yaml") + logger.info(f"Copying {self.task_config.jedi_yaml} to {self.task_config.COM_ATMOS_ANALYSIS}") + src = os.path.join(self.task_config.DATA, f"{self.task_config.RUN}.t{self.task_config.cyc:02d}z.atmvar.yaml") + dest = os.path.join(self.task_config.COM_ATMOS_ANALYSIS, f"{self.task_config.RUN}.t{self.task_config.cyc:02d}z.atmvar.yaml") logger.debug(f"Copying {src} to {dest}") yaml_copy = { 'mkdir': [self.task_config.COM_ATMOS_ANALYSIS], @@ -212,235 +239,17 @@ def finalize(self: Analysis) -> None: } FileHandler(bias_copy).sync() - # Create UFS model readable atm increment file from UFS-DA atm increment - logger.info("Create UFS model readable atm increment file from UFS-DA atm increment") - self.jedi2fv3inc() + # Copy FV3 atm increment to comrot directory + logger.info("Copy UFS model readable atm increment file") + cdate = to_fv3time(self.task_config.current_cycle) + cdate_inc = cdate.replace('.', '_') + src = os.path.join(self.task_config.DATA, 'anl', f"atminc.{cdate_inc}z.nc4") + dest = os.path.join(self.task_config.COM_ATMOS_ANALYSIS, f'{self.task_config.RUN}.t{self.task_config.cyc:02d}z.atminc.nc') + logger.debug(f"Copying {src} to {dest}") + inc_copy = { + 'copy': [[src, dest]] + } + FileHandler(inc_copy).sync() def clean(self): super().clean() - - @logit(logger) - def get_bkg_dict(self, task_config: Dict[str, Any]) -> Dict[str, List[str]]: - """Compile a dictionary of model background files to copy - - This method constructs a dictionary of FV3 restart files (coupler, core, tracer) - that are needed for global atm DA and returns said dictionary for use by the FileHandler class. - - Parameters - ---------- - task_config: Dict - a dictionary containing all of the configuration needed for the task - - Returns - ---------- - bkg_dict: Dict - a dictionary containing the list of model background files to copy for FileHandler - """ - # NOTE for now this is FV3 restart files and just assumed to be fh006 - - # get FV3 restart files, this will be a lot simpler when using history files - rst_dir = os.path.join(task_config.COM_ATMOS_RESTART_PREV) # for now, option later? - run_dir = os.path.join(task_config.DATA, 'bkg') - - # Start accumulating list of background files to copy - bkglist = [] - - # atm DA needs coupler - basename = f'{to_fv3time(task_config.current_cycle)}.coupler.res' - bkglist.append([os.path.join(rst_dir, basename), os.path.join(run_dir, basename)]) - - # atm DA needs core, srf_wnd, tracer, phy_data, sfc_data - for ftype in ['core', 'srf_wnd', 'tracer']: - template = f'{to_fv3time(self.task_config.current_cycle)}.fv_{ftype}.res.tile{{tilenum}}.nc' - for itile in range(1, task_config.ntiles + 1): - basename = template.format(tilenum=itile) - bkglist.append([os.path.join(rst_dir, basename), os.path.join(run_dir, basename)]) - - for ftype in ['phy_data', 'sfc_data']: - template = f'{to_fv3time(self.task_config.current_cycle)}.{ftype}.tile{{tilenum}}.nc' - for itile in range(1, task_config.ntiles + 1): - basename = template.format(tilenum=itile) - bkglist.append([os.path.join(rst_dir, basename), os.path.join(run_dir, basename)]) - - bkg_dict = { - 'mkdir': [run_dir], - 'copy': bkglist, - } - return bkg_dict - - @logit(logger) - def get_berror_dict(self, config: Dict[str, Any]) -> Dict[str, List[str]]: - """Compile a dictionary of background error files to copy - - This method will construct a dictionary of either bump of gsibec background - error files for global atm DA and return said dictionary for use by the - FileHandler class. - - Parameters - ---------- - config: Dict - a dictionary containing all of the configuration needed - - Returns - ---------- - berror_dict: Dict - a dictionary containing the list of atm background error files to copy for FileHandler - """ - SUPPORTED_BERROR_STATIC_MAP = {'identity': self._get_berror_dict_identity, - 'bump': self._get_berror_dict_bump, - 'gsibec': self._get_berror_dict_gsibec} - - try: - berror_dict = SUPPORTED_BERROR_STATIC_MAP[config.STATICB_TYPE](config) - except KeyError: - raise KeyError(f"{config.STATICB_TYPE} is not a supported background error type.\n" + - f"Currently supported background error types are:\n" + - f'{" | ".join(SUPPORTED_BERROR_STATIC_MAP.keys())}') - - return berror_dict - - @staticmethod - @logit(logger) - def _get_berror_dict_identity(config: Dict[str, Any]) -> Dict[str, List[str]]: - """Identity BE does not need any files for staging. - - This is a private method and should not be accessed directly. - - Parameters - ---------- - config: Dict - a dictionary containing all of the configuration needed - Returns - ---------- - berror_dict: Dict - Empty dictionary [identity BE needs not files to stage] - """ - logger.info(f"Identity background error does not use staged files. Return empty dictionary") - return {} - - @staticmethod - @logit(logger) - def _get_berror_dict_bump(config: Dict[str, Any]) -> Dict[str, List[str]]: - """Compile a dictionary of atm bump background error files to copy - - This method will construct a dictionary of atm bump background error - files for global atm DA and return said dictionary to the parent - - This is a private method and should not be accessed directly. - - Parameters - ---------- - config: Dict - a dictionary containing all of the configuration needed - - Returns - ---------- - berror_dict: Dict - a dictionary of atm bump background error files to copy for FileHandler - """ - # BUMP atm static-B needs nicas, cor_rh, cor_rv and stddev files. - b_dir = config.BERROR_DATA_DIR - b_datestr = to_fv3time(config.BERROR_DATE) - berror_list = [] - for ftype in ['cor_rh', 'cor_rv', 'stddev']: - coupler = f'{b_datestr}.{ftype}.coupler.res' - berror_list.append([ - os.path.join(b_dir, coupler), os.path.join(config.DATA, 'berror', coupler) - ]) - - template = '{b_datestr}.{ftype}.fv_tracer.res.tile{{tilenum}}.nc' - for itile in range(1, config.ntiles + 1): - tracer = template.format(tilenum=itile) - berror_list.append([ - os.path.join(b_dir, tracer), os.path.join(config.DATA, 'berror', tracer) - ]) - - nproc = config.ntiles * config.layout_x * config.layout_y - for nn in range(1, nproc + 1): - berror_list.append([ - os.path.join(b_dir, f'nicas_aero_nicas_local_{nproc:06}-{nn:06}.nc'), - os.path.join(config.DATA, 'berror', f'nicas_aero_nicas_local_{nproc:06}-{nn:06}.nc') - ]) - - # create dictionary of background error files to stage - berror_dict = { - 'mkdir': [os.path.join(config.DATA, 'berror')], - 'copy': berror_list, - } - return berror_dict - - @staticmethod - @logit(logger) - def _get_berror_dict_gsibec(config: Dict[str, Any]) -> Dict[str, List[str]]: - """Compile a dictionary of atm gsibec background error files to copy - - This method will construct a dictionary of atm gsibec background error - files for global atm DA and return said dictionary to the parent - - This is a private method and should not be accessed directly. - - Parameters - ---------- - config: Dict - a dictionary containing all of the configuration needed - - Returns - ---------- - berror_dict: Dict - a dictionary of atm gsibec background error files to copy for FileHandler - """ - # GSI atm static-B needs namelist and coefficient files. - b_dir = os.path.join(config.HOMEgfs, 'fix', 'gdas', 'gsibec', config.CASE_ANL) - berror_list = [] - for ftype in ['gfs_gsi_global.nml', 'gsi-coeffs-gfs-global.nc4']: - berror_list.append([ - os.path.join(b_dir, ftype), - os.path.join(config.DATA, 'berror', ftype) - ]) - - # create dictionary of background error files to stage - berror_dict = { - 'mkdir': [os.path.join(config.DATA, 'berror')], - 'copy': berror_list, - } - return berror_dict - - @logit(logger) - def jedi2fv3inc(self: Analysis) -> None: - """Generate UFS model readable analysis increment - - This method writes a UFS DA atm increment in UFS model readable format. - This includes: - - write UFS-DA atm increments using variable names expected by UFS model - - compute and write delp increment - - compute and write hydrostatic delz increment - - Please note that some of these steps are temporary and will be modified - once the modle is able to directly read atm increments. - - """ - # Select the atm guess file based on the analysis and background resolutions - # Fields from the atm guess are used to compute the delp and delz increments - case_anl = int(self.task_config.CASE_ANL[1:]) - case = int(self.task_config.CASE[1:]) - - file = f"{self.task_config.GPREFIX}" + "atmf006" + f"{'' if case_anl == case else '.ensres'}" + ".nc" - atmges_fv3 = os.path.join(self.task_config.COM_ATMOS_HISTORY_PREV, file) - - # Set the path/name to the input UFS-DA atm increment file (atminc_jedi) - # and the output UFS model atm increment file (atminc_fv3) - cdate = to_fv3time(self.task_config.current_cycle) - cdate_inc = cdate.replace('.', '_') - atminc_jedi = os.path.join(self.task_config.DATA, 'anl', f'atminc.{cdate_inc}z.nc4') - atminc_fv3 = os.path.join(self.task_config.COM_ATMOS_ANALYSIS, f"{self.task_config.CDUMP}.t{self.task_config.cyc:02d}z.atminc.nc") - - # Reference the python script which does the actual work - incpy = os.path.join(self.task_config.HOMEgfs, 'ush/jediinc2fv3.py') - - # Execute incpy to create the UFS model atm increment file - cmd = Executable(incpy) - cmd.add_default_arg(atmges_fv3) - cmd.add_default_arg(atminc_jedi) - cmd.add_default_arg(atminc_fv3) - logger.debug(f"Executing {cmd}") - cmd(output='stdout', error='stderr') diff --git a/ush/python/pygfs/task/atmens_analysis.py b/ush/python/pygfs/task/atmens_analysis.py index 9cf84c07c7..bd5112050e 100644 --- a/ush/python/pygfs/task/atmens_analysis.py +++ b/ush/python/pygfs/task/atmens_analysis.py @@ -11,7 +11,7 @@ FileHandler, add_to_datetime, to_fv3time, to_timedelta, to_YMDH, to_YMD, chdir, - parse_yamltmpl, parse_j2yaml, save_as_yaml, + parse_j2yaml, save_as_yaml, logit, Executable, WorkflowException, @@ -29,28 +29,31 @@ class AtmEnsAnalysis(Analysis): def __init__(self, config): super().__init__(config) - _res = int(self.config.CASE_ENS[1:]) - _window_begin = add_to_datetime(self.runtime_config.current_cycle, -to_timedelta(f"{self.config.assim_freq}H") / 2) - _fv3jedi_yaml = os.path.join(self.runtime_config.DATA, f"{self.runtime_config.CDUMP}.t{self.runtime_config.cyc:02d}z.atmens.yaml") + _res = int(self.task_config.CASE_ENS[1:]) + _window_begin = add_to_datetime(self.task_config.current_cycle, -to_timedelta(f"{self.task_config.assim_freq}H") / 2) + _jedi_yaml = os.path.join(self.task_config.DATA, f"{self.task_config.RUN}.t{self.task_config.cyc:02d}z.atmens.yaml") # Create a local dictionary that is repeatedly used across this class local_dict = AttrDict( { 'npx_ges': _res + 1, 'npy_ges': _res + 1, - 'npz_ges': self.config.LEVS - 1, - 'npz': self.config.LEVS - 1, + 'npz_ges': self.task_config.LEVS - 1, + 'npz': self.task_config.LEVS - 1, 'ATM_WINDOW_BEGIN': _window_begin, - 'ATM_WINDOW_LENGTH': f"PT{self.config.assim_freq}H", - 'OPREFIX': f"{self.config.EUPD_CYC}.t{self.runtime_config.cyc:02d}z.", # TODO: CDUMP is being replaced by RUN - 'APREFIX': f"{self.runtime_config.CDUMP}.t{self.runtime_config.cyc:02d}z.", # TODO: CDUMP is being replaced by RUN - 'GPREFIX': f"gdas.t{self.runtime_config.previous_cycle.hour:02d}z.", - 'fv3jedi_yaml': _fv3jedi_yaml, + 'ATM_WINDOW_LENGTH': f"PT{self.task_config.assim_freq}H", + 'OPREFIX': f"{self.task_config.EUPD_CYC}.t{self.task_config.cyc:02d}z.", + 'APREFIX': f"{self.task_config.RUN}.t{self.task_config.cyc:02d}z.", + 'GPREFIX': f"gdas.t{self.task_config.previous_cycle.hour:02d}z.", + 'jedi_yaml': _jedi_yaml, + 'atm_obsdatain_path': f"./obs/", + 'atm_obsdataout_path': f"./diags/", + 'BKG_TSTEP': "PT1H" # Placeholder for 4D applications } ) - # task_config is everything that this task should need - self.task_config = AttrDict(**self.config, **self.runtime_config, **local_dict) + # Extend task_config with local_dict + self.task_config = AttrDict(**self.task_config, **local_dict) @logit(logger) def initialize(self: Analysis) -> None: @@ -74,54 +77,25 @@ def initialize(self: Analysis) -> None: """ super().initialize() - # Make member directories in DATA for background and in DATA and ROTDIR for analysis files - # create template dictionary for output member analysis directories - template_inc = self.task_config.COM_ATMOS_ANALYSIS_TMPL - tmpl_inc_dict = { - 'ROTDIR': self.task_config.ROTDIR, - 'RUN': self.task_config.RUN, - 'YMD': to_YMD(self.task_config.current_cycle), - 'HH': self.task_config.current_cycle.strftime('%H') - } - dirlist = [] - for imem in range(1, self.task_config.NMEM_ENS + 1): - dirlist.append(os.path.join(self.task_config.DATA, 'bkg', f'mem{imem:03d}')) - dirlist.append(os.path.join(self.task_config.DATA, 'anl', f'mem{imem:03d}')) - - # create output directory path for member analysis - tmpl_inc_dict['MEMDIR'] = f"mem{imem:03d}" - incdir = Template.substitute_structure(template_inc, TemplateConstants.DOLLAR_CURLY_BRACE, tmpl_inc_dict.get) - dirlist.append(incdir) - - FileHandler({'mkdir': dirlist}).sync() - # stage CRTM fix files - crtm_fix_list_path = os.path.join(self.task_config.HOMEgfs, 'parm', 'gdas', 'atm_crtm_coeff.yaml') - logger.debug(f"Staging CRTM fix files from {crtm_fix_list_path}") - crtm_fix_list = parse_j2yaml(crtm_fix_list_path, self.task_config) + logger.info(f"Staging CRTM fix files from {self.task_config.CRTM_FIX_YAML}") + crtm_fix_list = parse_j2yaml(self.task_config.CRTM_FIX_YAML, self.task_config) FileHandler(crtm_fix_list).sync() # stage fix files - jedi_fix_list_path = os.path.join(self.task_config.HOMEgfs, 'parm', 'gdas', 'atm_jedi_fix.yaml') - logger.debug(f"Staging JEDI fix files from {jedi_fix_list_path}") - jedi_fix_list = parse_j2yaml(jedi_fix_list_path, self.task_config) + logger.info(f"Staging JEDI fix files from {self.task_config.JEDI_FIX_YAML}") + jedi_fix_list = parse_j2yaml(self.task_config.JEDI_FIX_YAML, self.task_config) FileHandler(jedi_fix_list).sync() # stage backgrounds - logger.debug(f"Stage ensemble member background files") - localconf = AttrDict() - keys = ['COM_ATMOS_RESTART_TMPL', 'previous_cycle', 'ROTDIR', 'RUN', - 'NMEM_ENS', 'DATA', 'current_cycle', 'ntiles'] - for key in keys: - localconf[key] = self.task_config[key] - localconf.dirname = 'bkg' - FileHandler(self.get_fv3ens_dict(localconf)).sync() + logger.info(f"Stage ensemble member background files") + bkg_staging_dict = parse_j2yaml(self.task_config.LGETKF_BKG_STAGING_YAML, self.task_config) + FileHandler(bkg_staging_dict).sync() # generate ensemble da YAML file - logger.debug(f"Generate ensemble da YAML file: {self.task_config.fv3jedi_yaml}") - ensda_yaml = parse_j2yaml(self.task_config.ATMENSYAML, self.task_config) - save_as_yaml(ensda_yaml, self.task_config.fv3jedi_yaml) - logger.info(f"Wrote ensemble da YAML to: {self.task_config.fv3jedi_yaml}") + logger.debug(f"Generate ensemble da YAML file: {self.task_config.jedi_yaml}") + save_as_yaml(self.task_config.jedi_config, self.task_config.jedi_yaml) + logger.info(f"Wrote ensemble da YAML to: {self.task_config.jedi_yaml}") # need output dir for diags and anl logger.debug("Create empty output [anl, diags] directories to receive output from executable") @@ -132,7 +106,7 @@ def initialize(self: Analysis) -> None: FileHandler({'mkdir': newdirs}).sync() @logit(logger) - def execute(self: Analysis) -> None: + def letkf(self: Analysis) -> None: """Execute a global atmens analysis This method will execute a global atmens analysis using JEDI. @@ -150,10 +124,13 @@ def execute(self: Analysis) -> None: """ chdir(self.task_config.DATA) - exec_cmd = Executable(self.task_config.APRUN_ATMENSANL) - exec_name = os.path.join(self.task_config.DATA, 'fv3jedi_letkf.x') + exec_cmd = Executable(self.task_config.APRUN_ATMENSANLLETKF) + exec_name = os.path.join(self.task_config.DATA, 'gdas.x') + exec_cmd.add_default_arg(exec_name) - exec_cmd.add_default_arg(self.task_config.fv3jedi_yaml) + exec_cmd.add_default_arg('fv3jedi') + exec_cmd.add_default_arg('localensembleda') + exec_cmd.add_default_arg(self.task_config.jedi_yaml) try: logger.debug(f"Executing {exec_cmd}") @@ -165,6 +142,31 @@ def execute(self: Analysis) -> None: pass + @logit(logger) + def init_fv3_increment(self: Analysis) -> None: + # Setup JEDI YAML file + self.task_config.jedi_yaml = os.path.join(self.task_config.DATA, + f"{self.task_config.JCB_ALGO}.yaml") + save_as_yaml(self.get_jedi_config(self.task_config.JCB_ALGO), self.task_config.jedi_yaml) + + # Link JEDI executable to run directory + self.task_config.jedi_exe = self.link_jediexe() + + @logit(logger) + def fv3_increment(self: Analysis) -> None: + # Run executable + exec_cmd = Executable(self.task_config.APRUN_ATMENSANLFV3INC) + exec_cmd.add_default_arg(self.task_config.jedi_exe) + exec_cmd.add_default_arg(self.task_config.jedi_yaml) + + try: + logger.debug(f"Executing {exec_cmd}") + exec_cmd() + except OSError: + raise OSError(f"Failed to execute {exec_cmd}") + except Exception: + raise WorkflowException(f"An error occured during execution of {exec_cmd}") + @logit(logger) def finalize(self: Analysis) -> None: """Finalize a global atmens analysis @@ -188,7 +190,7 @@ def finalize(self: Analysis) -> None: atmensstat = os.path.join(self.task_config.COM_ATMOS_ANALYSIS_ENS, f"{self.task_config.APREFIX}atmensstat") # get list of diag files to put in tarball - diags = glob.glob(os.path.join(self.task_config.DATA, 'diags', 'diag*nc4')) + diags = glob.glob(os.path.join(self.task_config.DATA, 'diags', 'diag*nc')) logger.info(f"Compressing {len(diags)} diag files to {atmensstat}.gz") @@ -206,9 +208,9 @@ def finalize(self: Analysis) -> None: archive.add(diaggzip, arcname=os.path.basename(diaggzip)) # copy full YAML from executable to ROTDIR - logger.info(f"Copying {self.task_config.fv3jedi_yaml} to {self.task_config.COM_ATMOS_ANALYSIS_ENS}") - src = os.path.join(self.task_config.DATA, f"{self.task_config.CDUMP}.t{self.task_config.cyc:02d}z.atmens.yaml") - dest = os.path.join(self.task_config.COM_ATMOS_ANALYSIS_ENS, f"{self.task_config.CDUMP}.t{self.task_config.cyc:02d}z.atmens.yaml") + logger.info(f"Copying {self.task_config.jedi_yaml} to {self.task_config.COM_ATMOS_ANALYSIS_ENS}") + src = os.path.join(self.task_config.DATA, f"{self.task_config.RUN}.t{self.task_config.cyc:02d}z.atmens.yaml") + dest = os.path.join(self.task_config.COM_ATMOS_ANALYSIS_ENS, f"{self.task_config.RUN}.t{self.task_config.cyc:02d}z.atmens.yaml") logger.debug(f"Copying {src} to {dest}") yaml_copy = { 'mkdir': [self.task_config.COM_ATMOS_ANALYSIS_ENS], @@ -216,42 +218,6 @@ def finalize(self: Analysis) -> None: } FileHandler(yaml_copy).sync() - # Create UFS model readable atm increment file from UFS-DA atm increment - logger.info("Create UFS model readable atm increment file from UFS-DA atm increment") - self.jedi2fv3inc() - - def clean(self): - super().clean() - - @logit(logger) - def jedi2fv3inc(self: Analysis) -> None: - """Generate UFS model readable analysis increment - - This method writes a UFS DA atm increment in UFS model readable format. - This includes: - - write UFS-DA atm increments using variable names expected by UFS model - - compute and write delp increment - - compute and write hydrostatic delz increment - - Please note that some of these steps are temporary and will be modified - once the modle is able to directly read atm increments. - - Parameters - ---------- - Analysis: parent class for GDAS task - - Returns - ---------- - None - """ - # Select the atm guess file based on the analysis and background resolutions - # Fields from the atm guess are used to compute the delp and delz increments - cdate = to_fv3time(self.task_config.current_cycle) - cdate_inc = cdate.replace('.', '_') - - # Reference the python script which does the actual work - incpy = os.path.join(self.task_config.HOMEgfs, 'ush/jediinc2fv3.py') - # create template dictionaries template_inc = self.task_config.COM_ATMOS_ANALYSIS_TMPL tmpl_inc_dict = { @@ -261,14 +227,10 @@ def jedi2fv3inc(self: Analysis) -> None: 'HH': self.task_config.current_cycle.strftime('%H') } - template_ges = self.task_config.COM_ATMOS_HISTORY_TMPL - tmpl_ges_dict = { - 'ROTDIR': self.task_config.ROTDIR, - 'RUN': self.task_config.RUN, - 'YMD': to_YMD(self.task_config.previous_cycle), - 'HH': self.task_config.previous_cycle.strftime('%H') - } - + # copy FV3 atm increment to comrot directory + logger.info("Copy UFS model readable atm increment file") + cdate = to_fv3time(self.task_config.current_cycle) + cdate_inc = cdate.replace('.', '_') # loop over ensemble members for imem in range(1, self.task_config.NMEM_ENS + 1): memchar = f"mem{imem:03d}" @@ -276,20 +238,15 @@ def jedi2fv3inc(self: Analysis) -> None: # create output path for member analysis increment tmpl_inc_dict['MEMDIR'] = memchar incdir = Template.substitute_structure(template_inc, TemplateConstants.DOLLAR_CURLY_BRACE, tmpl_inc_dict.get) + src = os.path.join(self.task_config.DATA, 'anl', memchar, f"atminc.{cdate_inc}z.nc4") + dest = os.path.join(incdir, f"{self.task_config.RUN}.t{self.task_config.cyc:02d}z.atminc.nc") - # rewrite UFS-DA atmens increments - tmpl_ges_dict['MEMDIR'] = memchar - gesdir = Template.substitute_structure(template_ges, TemplateConstants.DOLLAR_CURLY_BRACE, tmpl_ges_dict.get) - atmges_fv3 = os.path.join(gesdir, f"{self.task_config.CDUMP}.t{self.task_config.previous_cycle.hour:02d}z.atmf006.nc") - atminc_jedi = os.path.join(self.task_config.DATA, 'anl', memchar, f'atminc.{cdate_inc}z.nc4') - atminc_fv3 = os.path.join(incdir, f"{self.task_config.CDUMP}.t{self.task_config.cyc:02d}z.atminc.nc") - - # Execute incpy to create the UFS model atm increment file - # TODO: use MPMD or parallelize with mpi4py - # See https://github.com/NOAA-EMC/global-workflow/pull/1373#discussion_r1173060656 - cmd = Executable(incpy) - cmd.add_default_arg(atmges_fv3) - cmd.add_default_arg(atminc_jedi) - cmd.add_default_arg(atminc_fv3) - logger.debug(f"Executing {cmd}") - cmd(output='stdout', error='stderr') + # copy increment + logger.debug(f"Copying {src} to {dest}") + inc_copy = { + 'copy': [[src, dest]] + } + FileHandler(inc_copy).sync() + + def clean(self): + super().clean() diff --git a/ush/python/pygfs/task/marine_bmat.py b/ush/python/pygfs/task/marine_bmat.py new file mode 100644 index 0000000000..9d64e621c9 --- /dev/null +++ b/ush/python/pygfs/task/marine_bmat.py @@ -0,0 +1,350 @@ +#!/usr/bin/env python3 + +import os +import glob +from logging import getLogger +import pygfs.utils.marine_da_utils as mdau + +from wxflow import (AttrDict, + FileHandler, + add_to_datetime, to_timedelta, + chdir, + parse_j2yaml, + logit, + Executable, + Task) + +logger = getLogger(__name__.split('.')[-1]) + + +class MarineBMat(Task): + """ + Class for global marine B-matrix tasks + """ + @logit(logger, name="MarineBMat") + def __init__(self, config): + super().__init__(config) + _home_gdas = os.path.join(self.task_config.HOMEgfs, 'sorc', 'gdas.cd') + _calc_scale_exec = os.path.join(self.task_config.HOMEgfs, 'ush', 'soca', 'calc_scales.py') + _window_begin = add_to_datetime(self.task_config.current_cycle, -to_timedelta(f"{self.task_config.assim_freq}H") / 2) + _window_end = add_to_datetime(self.task_config.current_cycle, to_timedelta(f"{self.task_config.assim_freq}H") / 2) + + # compute the relative path from self.task_config.DATA to self.task_config.DATAenspert + if self.task_config.NMEM_ENS > 0: + _enspert_relpath = os.path.relpath(self.task_config.DATAenspert, self.task_config.DATA) + else: + _enspert_relpath = None + + # Create a local dictionary that is repeatedly used across this class + local_dict = AttrDict( + { + 'HOMEgdas': _home_gdas, + 'MARINE_WINDOW_BEGIN': _window_begin, + 'MARINE_WINDOW_END': _window_end, + 'MARINE_WINDOW_MIDDLE': self.task_config.current_cycle, + 'BERROR_YAML_DIR': os.path.join(_home_gdas, 'parm', 'soca', 'berror'), + 'GRID_GEN_YAML': os.path.join(_home_gdas, 'parm', 'soca', 'gridgen', 'gridgen.yaml'), + 'MARINE_ENSDA_STAGE_BKG_YAML_TMPL': os.path.join(_home_gdas, 'parm', 'soca', 'ensda', 'stage_ens_mem.yaml.j2'), + 'MARINE_DET_STAGE_BKG_YAML_TMPL': os.path.join(_home_gdas, 'parm', 'soca', 'soca_det_bkg_stage.yaml.j2'), + 'ENSPERT_RELPATH': _enspert_relpath, + 'CALC_SCALE_EXEC': _calc_scale_exec, + 'APREFIX': f"{self.task_config.RUN}.t{self.task_config.cyc:02d}z.", + } + ) + + # Extend task_config with local_dict + self.task_config = AttrDict(**self.task_config, **local_dict) + + @logit(logger) + def initialize(self: Task) -> None: + """Initialize a global B-matrix + + This method will initialize a global B-Matrix. + This includes: + - staging the deterministic backgrounds (middle of window) + - staging SOCA fix files + - staging static ensemble members (optional) + - staging ensemble members (optional) + - generating the YAML files for the JEDI and GDASApp executables + - creating output directories + """ + super().initialize() + + # stage fix files + logger.info(f"Staging SOCA fix files from {self.task_config.SOCA_INPUT_FIX_DIR}") + soca_fix_list = parse_j2yaml(self.task_config.SOCA_FIX_YAML_TMPL, self.task_config) + FileHandler(soca_fix_list).sync() + + # prepare the MOM6 input.nml + mdau.prep_input_nml(self.task_config) + + # stage backgrounds + # TODO(G): Check ocean backgrounds dates for consistency + bkg_list = parse_j2yaml(self.task_config.MARINE_DET_STAGE_BKG_YAML_TMPL, self.task_config) + FileHandler(bkg_list).sync() + for cice_fname in ['./INPUT/cice.res.nc', './bkg/ice.bkg.f006.nc', './bkg/ice.bkg.f009.nc']: + mdau.cice_hist2fms(cice_fname, cice_fname) + + # stage the grid generation yaml + FileHandler({'copy': [[self.task_config.GRID_GEN_YAML, + os.path.join(self.task_config.DATA, 'gridgen.yaml')]]}).sync() + + # generate the variance partitioning YAML file + logger.debug("Generate variance partitioning YAML file") + diagb_config = parse_j2yaml(path=os.path.join(self.task_config.BERROR_YAML_DIR, 'soca_diagb.yaml.j2'), + data=self.task_config) + diagb_config.save(os.path.join(self.task_config.DATA, 'soca_diagb.yaml')) + + # generate the vertical decorrelation scale YAML file + logger.debug("Generate the vertical correlation scale YAML file") + vtscales_config = parse_j2yaml(path=os.path.join(self.task_config.BERROR_YAML_DIR, 'soca_vtscales.yaml.j2'), + data=self.task_config) + vtscales_config.save(os.path.join(self.task_config.DATA, 'soca_vtscales.yaml')) + + # generate vertical diffusion scale YAML file + logger.debug("Generate vertical diffusion YAML file") + diffvz_config = parse_j2yaml(path=os.path.join(self.task_config.BERROR_YAML_DIR, 'soca_parameters_diffusion_vt.yaml.j2'), + data=self.task_config) + diffvz_config.save(os.path.join(self.task_config.DATA, 'soca_parameters_diffusion_vt.yaml')) + + # generate the horizontal diffusion YAML files + if True: # TODO(G): skip this section once we have optimized the scales + # stage the correlation scale configuration + logger.debug("Generate correlation scale YAML file") + FileHandler({'copy': [[os.path.join(self.task_config.BERROR_YAML_DIR, 'soca_setcorscales.yaml'), + os.path.join(self.task_config.DATA, 'soca_setcorscales.yaml')]]}).sync() + + # generate horizontal diffusion scale YAML file + logger.debug("Generate horizontal diffusion scale YAML file") + diffhz_config = parse_j2yaml(path=os.path.join(self.task_config.BERROR_YAML_DIR, 'soca_parameters_diffusion_hz.yaml.j2'), + data=self.task_config) + diffhz_config.save(os.path.join(self.task_config.DATA, 'soca_parameters_diffusion_hz.yaml')) + + # hybrid EnVAR case + if self.task_config.DOHYBVAR == "YES" or self.task_config.NMEM_ENS > 2: + # stage ensemble membersfiles for use in hybrid background error + logger.debug(f"Stage ensemble members for the hybrid background error") + mdau.stage_ens_mem(self.task_config) + + # generate ensemble recentering/rebalancing YAML file + logger.debug("Generate ensemble recentering YAML file") + ensrecenter_config = parse_j2yaml(path=os.path.join(self.task_config.BERROR_YAML_DIR, 'soca_ensb.yaml.j2'), + data=self.task_config) + ensrecenter_config.save(os.path.join(self.task_config.DATA, 'soca_ensb.yaml')) + + # generate ensemble weights YAML file + logger.debug("Generate ensemble recentering YAML file: {self.task_config.abcd_yaml}") + hybridweights_config = parse_j2yaml(path=os.path.join(self.task_config.BERROR_YAML_DIR, 'soca_ensweights.yaml.j2'), + data=self.task_config) + hybridweights_config.save(os.path.join(self.task_config.DATA, 'soca_ensweights.yaml')) + + # need output dir for ensemble perturbations and static B-matrix + logger.debug("Create empty diagb directories to receive output from executables") + FileHandler({'mkdir': [os.path.join(self.task_config.DATA, 'diagb')]}).sync() + + @logit(logger) + def gridgen(self: Task) -> None: + # link gdas_soca_gridgen.x + mdau.link_executable(self.task_config, 'gdas_soca_gridgen.x') + exec_cmd = Executable(self.task_config.APRUN_MARINEBMAT) + exec_name = os.path.join(self.task_config.DATA, 'gdas_soca_gridgen.x') + exec_cmd.add_default_arg(exec_name) + exec_cmd.add_default_arg('gridgen.yaml') + + mdau.run(exec_cmd) + + @logit(logger) + def variance_partitioning(self: Task) -> None: + # link the variance partitioning executable, gdas_soca_diagb.x + mdau.link_executable(self.task_config, 'gdas_soca_diagb.x') + exec_cmd = Executable(self.task_config.APRUN_MARINEBMAT) + exec_name = os.path.join(self.task_config.DATA, 'gdas_soca_diagb.x') + exec_cmd.add_default_arg(exec_name) + exec_cmd.add_default_arg('soca_diagb.yaml') + + mdau.run(exec_cmd) + + @logit(logger) + def horizontal_diffusion(self: Task) -> None: + """Generate the horizontal diffusion coefficients + """ + # link the executable that computes the correlation scales, gdas_soca_setcorscales.x, + # and prepare the command to run it + mdau.link_executable(self.task_config, 'gdas_soca_setcorscales.x') + exec_cmd = Executable(self.task_config.APRUN_MARINEBMAT) + exec_name = os.path.join(self.task_config.DATA, 'gdas_soca_setcorscales.x') + exec_cmd.add_default_arg(exec_name) + exec_cmd.add_default_arg('soca_setcorscales.yaml') + + # create a files containing the correlation scales + mdau.run(exec_cmd) + + # link the executable that computes the correlation scales, gdas_soca_error_covariance_toolbox.x, + # and prepare the command to run it + mdau.link_executable(self.task_config, 'gdas_soca_error_covariance_toolbox.x') + exec_cmd = Executable(self.task_config.APRUN_MARINEBMAT) + exec_name = os.path.join(self.task_config.DATA, 'gdas_soca_error_covariance_toolbox.x') + exec_cmd.add_default_arg(exec_name) + exec_cmd.add_default_arg('soca_parameters_diffusion_hz.yaml') + + # compute the coefficients of the diffusion operator + mdau.run(exec_cmd) + + @logit(logger) + def vertical_diffusion(self: Task) -> None: + """Generate the vertical diffusion coefficients + """ + # compute the vertical correlation scales based on the MLD + FileHandler({'copy': [[os.path.join(self.task_config.CALC_SCALE_EXEC), + os.path.join(self.task_config.DATA, 'calc_scales.x')]]}).sync() + exec_cmd = Executable("python") + exec_name = os.path.join(self.task_config.DATA, 'calc_scales.x') + exec_cmd.add_default_arg(exec_name) + exec_cmd.add_default_arg('soca_vtscales.yaml') + mdau.run(exec_cmd) + + # link the executable that computes the correlation scales, gdas_soca_error_covariance_toolbox.x, + # and prepare the command to run it + mdau.link_executable(self.task_config, 'gdas_soca_error_covariance_toolbox.x') + exec_cmd = Executable(self.task_config.APRUN_MARINEBMAT) + exec_name = os.path.join(self.task_config.DATA, 'gdas_soca_error_covariance_toolbox.x') + exec_cmd.add_default_arg(exec_name) + exec_cmd.add_default_arg('soca_parameters_diffusion_vt.yaml') + + # compute the coefficients of the diffusion operator + mdau.run(exec_cmd) + + @logit(logger) + def ensemble_perturbations(self: Task) -> None: + """Generate the 3D ensemble of perturbation for the 3DEnVAR + + This method will generate ensemble perturbations re-balanced w.r.t the + deterministic background. + This includes: + - computing a storing the unbalanced ensemble perturbations' statistics + - recentering the ensemble members around the deterministic background and + accounting for the nonlinear steric recentering + - saving the recentered ensemble statistics + """ + mdau.link_executable(self.task_config, 'gdas_ens_handler.x') + exec_cmd = Executable(self.task_config.APRUN_MARINEBMAT) + exec_name = os.path.join(self.task_config.DATA, 'gdas_ens_handler.x') + exec_cmd.add_default_arg(exec_name) + exec_cmd.add_default_arg('soca_ensb.yaml') + + # generate the ensemble perturbations + mdau.run(exec_cmd) + + @logit(logger) + def hybrid_weight(self: Task) -> None: + """Generate the hybrid weights for the 3DEnVAR + + This method will generate the 3D fields hybrid weights for the 3DEnVAR for each + variables. + TODO(G): Currently implemented for the specific case of the static ensemble members only + """ + mdau.link_executable(self.task_config, 'gdas_socahybridweights.x') + exec_cmd = Executable(self.task_config.APRUN_MARINEBMAT) + exec_name = os.path.join(self.task_config.DATA, 'gdas_socahybridweights.x') + exec_cmd.add_default_arg(exec_name) + exec_cmd.add_default_arg('soca_ensweights.yaml') + + # compute the ensemble weights + mdau.run(exec_cmd) + + @logit(logger) + def execute(self: Task) -> None: + """Generate the full B-matrix + + This method will generate the full B-matrix according to the configuration. + """ + chdir(self.task_config.DATA) + self.gridgen() # TODO: This should be optional in case the geometry file was staged + self.variance_partitioning() + self.horizontal_diffusion() # TODO: Make this optional once we've converged on an acceptable set of scales + self.vertical_diffusion() + # hybrid EnVAR case + if self.task_config.DOHYBVAR == "YES" or self.task_config.NMEM_ENS > 2: + self.ensemble_perturbations() # TODO: refactor this from the old scripts + self.hybrid_weight() # TODO: refactor this from the old scripts + + @logit(logger) + def finalize(self: Task) -> None: + """Finalize the global B-matrix job + + This method will finalize the global B-matrix job. + This includes: + - copy the generated static, but cycle dependent background error files to the ROTDIR + - copy the generated YAML file from initialize to the ROTDIR + - keep the re-balanced ensemble perturbation files in DATAenspert + - ... + + """ + # Copy the soca grid if it was created + grid_file = os.path.join(self.task_config.DATA, 'soca_gridspec.nc') + if os.path.exists(grid_file): + logger.info(f"Copying the soca grid file to the ROTDIR") + FileHandler({'copy': [[grid_file, + os.path.join(self.task_config.COMOUT_OCEAN_BMATRIX, 'soca_gridspec.nc')]]}).sync() + + # Copy the diffusion coefficient files to the ROTDIR + logger.info(f"Copying the diffusion coefficient files to the ROTDIR") + diffusion_coeff_list = [] + for diff_type in ['hz', 'vt']: + src = os.path.join(self.task_config.DATA, f"{diff_type}_ocean.nc") + dest = os.path.join(self.task_config.COMOUT_OCEAN_BMATRIX, + f"{self.task_config.APREFIX}{diff_type}_ocean.nc") + diffusion_coeff_list.append([src, dest]) + + src = os.path.join(self.task_config.DATA, f"hz_ice.nc") + dest = os.path.join(self.task_config.COMOUT_ICE_BMATRIX, + f"{self.task_config.APREFIX}hz_ice.nc") + diffusion_coeff_list.append([src, dest]) + + FileHandler({'copy': diffusion_coeff_list}).sync() + + # Copy diag B files to ROTDIR + logger.info(f"Copying diag B files to the ROTDIR") + diagb_list = [] + window_end_iso = self.task_config.MARINE_WINDOW_END.strftime('%Y-%m-%dT%H:%M:%SZ') + + # ocean diag B + src = os.path.join(self.task_config.DATA, 'diagb', f"ocn.bkgerr_stddev.incr.{window_end_iso}.nc") + dst = os.path.join(self.task_config.COMOUT_OCEAN_BMATRIX, + f"{self.task_config.APREFIX}ocean.bkgerr_stddev.nc") + diagb_list.append([src, dst]) + + # ice diag B + src = os.path.join(self.task_config.DATA, 'diagb', f"ice.bkgerr_stddev.incr.{window_end_iso}.nc") + dst = os.path.join(self.task_config.COMOUT_ICE_BMATRIX, + f"{self.task_config.APREFIX}ice.bkgerr_stddev.nc") + diagb_list.append([src, dst]) + + FileHandler({'copy': diagb_list}).sync() + + # Copy the ensemble perturbation diagnostics to the ROTDIR + if self.task_config.DOHYBVAR == "YES" or self.task_config.NMEM_ENS > 3: + window_middle_iso = self.task_config.MARINE_WINDOW_MIDDLE.strftime('%Y-%m-%dT%H:%M:%SZ') + weight_list = [] + src = os.path.join(self.task_config.DATA, f"ocn.ens_weights.incr.{window_middle_iso}.nc") + dst = os.path.join(self.task_config.COMOUT_OCEAN_BMATRIX, + f"{self.task_config.APREFIX}ocean.ens_weights.nc") + weight_list.append([src, dst]) + + src = os.path.join(self.task_config.DATA, f"ice.ens_weights.incr.{window_middle_iso}.nc") + dst = os.path.join(self.task_config.COMOUT_ICE_BMATRIX, + f"{self.task_config.APREFIX}ice.ens_weights.nc") + weight_list.append([src, dst]) + + # TODO(G): missing ssh_steric_stddev, ssh_unbal_stddev, ssh_total_stddev and steric_explained_variance + + FileHandler({'copy': weight_list}).sync() + + # Copy the YAML files to the OCEAN ROTDIR + yamls = glob.glob(os.path.join(self.task_config.DATA, '*.yaml')) + yaml_list = [] + for yaml_file in yamls: + dest = os.path.join(self.task_config.COMOUT_OCEAN_BMATRIX, + f"{self.task_config.APREFIX}{os.path.basename(yaml_file)}") + yaml_list.append([yaml_file, dest]) + FileHandler({'copy': yaml_list}).sync() diff --git a/ush/python/pygfs/task/marine_letkf.py b/ush/python/pygfs/task/marine_letkf.py new file mode 100644 index 0000000000..36c26d594b --- /dev/null +++ b/ush/python/pygfs/task/marine_letkf.py @@ -0,0 +1,147 @@ +#!/usr/bin/env python3 + +import f90nml +from logging import getLogger +import os +from pygfs.task.analysis import Analysis +from typing import Dict +from wxflow import (AttrDict, + FileHandler, + logit, + parse_j2yaml, + to_timedelta, + to_YMDH) + +logger = getLogger(__name__.split('.')[-1]) + + +class MarineLETKF(Analysis): + """ + Class for global ocean and sea ice analysis LETKF task + """ + + @logit(logger, name="MarineLETKF") + def __init__(self, config: Dict) -> None: + """Constructor for ocean and sea ice LETKF task + Parameters: + ------------ + config: Dict + configuration, namely evironment variables + Returns: + -------- + None + """ + + logger.info("init") + super().__init__(config) + + _half_assim_freq = to_timedelta(f"{self.task_config.assim_freq}H") / 2 + _letkf_yaml_file = 'letkf.yaml' + _letkf_exec_args = [self.task_config.MARINE_LETKF_EXEC, + 'soca', + 'localensembleda', + _letkf_yaml_file] + + self.task_config.WINDOW_MIDDLE = self.task_config.current_cycle + self.task_config.WINDOW_BEGIN = self.task_config.current_cycle - _half_assim_freq + self.task_config.letkf_exec_args = _letkf_exec_args + self.task_config.letkf_yaml_file = _letkf_yaml_file + self.task_config.mom_input_nml_tmpl = os.path.join(self.task_config.DATA, 'mom_input.nml.tmpl') + self.task_config.mom_input_nml = os.path.join(self.task_config.DATA, 'mom_input.nml') + self.task_config.obs_dir = os.path.join(self.task_config.DATA, 'obs') + + @logit(logger) + def initialize(self): + """Method initialize for ocean and sea ice LETKF task + Parameters: + ------------ + None + Returns: + -------- + None + """ + + logger.info("initialize") + + # make directories and stage ensemble background files + ensbkgconf = AttrDict() + keys = ['previous_cycle', 'current_cycle', 'DATA', 'NMEM_ENS', + 'PARMgfs', 'ROTDIR', 'COM_OCEAN_HISTORY_TMPL', 'COM_ICE_HISTORY_TMPL'] + for key in keys: + ensbkgconf[key] = self.task_config[key] + ensbkgconf.RUN = 'enkfgdas' + soca_ens_bkg_stage_list = parse_j2yaml(self.task_config.SOCA_ENS_BKG_STAGE_YAML_TMPL, ensbkgconf) + FileHandler(soca_ens_bkg_stage_list).sync() + soca_fix_stage_list = parse_j2yaml(self.task_config.SOCA_FIX_YAML_TMPL, self.task_config) + FileHandler(soca_fix_stage_list).sync() + letkf_stage_list = parse_j2yaml(self.task_config.MARINE_LETKF_STAGE_YAML_TMPL, self.task_config) + FileHandler(letkf_stage_list).sync() + + obs_list = parse_j2yaml(self.task_config.OBS_YAML, self.task_config) + + # get the list of observations + obs_files = [] + for ob in obs_list['observers']: + obs_name = ob['obs space']['name'].lower() + obs_filename = f"{self.task_config.RUN}.t{self.task_config.cyc}z.{obs_name}.{to_YMDH(self.task_config.current_cycle)}.nc" + obs_files.append((obs_filename, ob)) + + obs_files_to_copy = [] + obs_to_use = [] + # copy obs from COMIN_OBS to DATA/obs + for obs_file, ob in obs_files: + obs_src = os.path.join(self.task_config.COMIN_OBS, obs_file) + obs_dst = os.path.join(self.task_config.DATA, self.task_config.obs_dir, obs_file) + if os.path.exists(obs_src): + obs_files_to_copy.append([obs_src, obs_dst]) + obs_to_use.append(ob) + else: + logger.warning(f"{obs_file} is not available in {self.task_config.COMIN_OBS}") + + # stage the desired obs files + FileHandler({'copy': obs_files_to_copy}).sync() + + # make the letkf.yaml + letkfconf = AttrDict() + keys = ['WINDOW_BEGIN', 'WINDOW_MIDDLE', 'RUN', 'gcyc', 'NMEM_ENS'] + for key in keys: + letkfconf[key] = self.task_config[key] + letkfconf.RUN = 'enkfgdas' + letkf_yaml = parse_j2yaml(self.task_config.MARINE_LETKF_YAML_TMPL, letkfconf) + letkf_yaml.observations.observers = obs_to_use + letkf_yaml.save(self.task_config.letkf_yaml_file) + + # swap date and stack size in mom_input.nml + domain_stack_size = self.task_config.DOMAIN_STACK_SIZE + ymdhms = [int(s) for s in self.task_config.WINDOW_BEGIN.strftime('%Y,%m,%d,%H,%M,%S').split(',')] + with open(self.task_config.mom_input_nml_tmpl, 'r') as nml_file: + nml = f90nml.read(nml_file) + nml['ocean_solo_nml']['date_init'] = ymdhms + nml['fms_nml']['domains_stack_size'] = int(domain_stack_size) + nml.write(self.task_config.mom_input_nml, force=True) # force to overwrite if necessary + + @logit(logger) + def run(self): + """Method run for ocean and sea ice LETKF task + Parameters: + ------------ + None + Returns: + -------- + None + """ + + logger.info("run") + + @logit(logger) + def finalize(self): + """Method finalize for ocean and sea ice LETKF task + Parameters: + ------------ + None + Returns: + -------- + None + """ + + logger.info("finalize") diff --git a/ush/python/pygfs/task/oceanice_products.py b/ush/python/pygfs/task/oceanice_products.py new file mode 100644 index 0000000000..98b57ae801 --- /dev/null +++ b/ush/python/pygfs/task/oceanice_products.py @@ -0,0 +1,356 @@ +#!/usr/bin/env python3 + +import os +from logging import getLogger +from typing import List, Dict, Any +from pprint import pformat +import xarray as xr + +from wxflow import (AttrDict, + parse_j2yaml, + FileHandler, + Jinja, + logit, + Task, + add_to_datetime, to_timedelta, + WorkflowException, + Executable) + +logger = getLogger(__name__.split('.')[-1]) + + +class OceanIceProducts(Task): + """Ocean Ice Products Task + """ + + VALID_COMPONENTS = ['ocean', 'ice'] + COMPONENT_RES_MAP = {'ocean': 'OCNRES', 'ice': 'ICERES'} + VALID_PRODUCT_GRIDS = {'mx025': ['1p00', '0p25'], + 'mx050': ['1p00', '0p50'], + 'mx100': ['1p00'], + 'mx500': ['5p00']} + + # These could be read from the yaml file + TRIPOLE_DIMS_MAP = {'mx025': [1440, 1080], 'mx050': [720, 526], 'mx100': [360, 320], 'mx500': [72, 35]} + LATLON_DIMS_MAP = {'0p25': [1440, 721], '0p50': [720, 361], '1p00': [360, 181], '5p00': [72, 36]} + + @logit(logger, name="OceanIceProducts") + def __init__(self, config: Dict[str, Any]) -> None: + """Constructor for the Ocean/Ice Productstask + + Parameters + ---------- + config : Dict[str, Any] + Incoming configuration for the task from the environment + + Returns + ------- + None + """ + super().__init__(config) + + if self.task_config.COMPONENT not in self.VALID_COMPONENTS: + raise NotImplementedError(f'{self.task_config.COMPONENT} is not a valid model component.\n' + + 'Valid model components are:\n' + + f'{", ".join(self.VALID_COMPONENTS)}') + + model_grid = f"mx{self.task_config[self.COMPONENT_RES_MAP[self.task_config.COMPONENT]]:03d}" + + valid_datetime = add_to_datetime(self.task_config.current_cycle, to_timedelta(f"{self.task_config.FORECAST_HOUR}H")) + + if self.task_config.COMPONENT == 'ice': + offset = int(self.task_config.current_cycle.strftime("%H")) % self.task_config.FHOUT_ICE_GFS + # For CICE cases where offset is not 0, forecast_hour needs to be adjusted based on the offset. + # TODO: Consider FHMIN when calculating offset. + if offset != 0: + forecast_hour = self.task_config.FORECAST_HOUR - int(self.task_config.current_cycle.strftime("%H")) + # For the first forecast hour, the interval may be different from the intervals of subsequent forecast hours + if forecast_hour <= self.task_config.FHOUT_ICE_GFS: + interval = self.task_config.FHOUT_ICE_GFS - int(self.task_config.current_cycle.strftime("%H")) + else: + interval = self.task_config.FHOUT_ICE_GFS + else: + forecast_hour = self.task_config.FORECAST_HOUR + interval = self.task_config.FHOUT_ICE_GFS + if self.task_config.COMPONENT == 'ocean': + forecast_hour = self.task_config.FORECAST_HOUR + interval = self.task_config.FHOUT_OCN_GFS + + # TODO: This is a bit of a hack, but it works for now + # FIXME: find a better way to provide the averaging period + avg_period = f"{forecast_hour-interval:03d}-{forecast_hour:03d}" + + # Extend task_config with localdict + localdict = AttrDict( + {'component': self.task_config.COMPONENT, + 'forecast_hour': forecast_hour, + 'valid_datetime': valid_datetime, + 'avg_period': avg_period, + 'model_grid': model_grid, + 'interval': interval, + 'product_grids': self.VALID_PRODUCT_GRIDS[model_grid]} + ) + self.task_config = AttrDict(**self.task_config, **localdict) + + # Read the oceanice_products.yaml file for common configuration + logger.info(f"Read the ocean ice products configuration yaml file {self.task_config.OCEANICEPRODUCTS_CONFIG}") + self.task_config.oceanice_yaml = parse_j2yaml(self.task_config.OCEANICEPRODUCTS_CONFIG, self.task_config) + logger.debug(f"oceanice_yaml:\n{pformat(self.task_config.oceanice_yaml)}") + + @staticmethod + @logit(logger) + def initialize(config: Dict) -> None: + """Initialize the work directory by copying all the common fix data + + Parameters + ---------- + config : Dict + Configuration dictionary for the task + + Returns + ------- + None + """ + + # Copy static data to run directory + logger.info("Copy static data to run directory") + FileHandler(config.oceanice_yaml.ocnicepost.fix_data).sync() + + # Copy "component" specific model data to run directory (e.g. ocean/ice forecast output) + logger.info(f"Copy {config.component} data to run directory") + FileHandler(config.oceanice_yaml[config.component].data_in).sync() + + @staticmethod + @logit(logger) + def configure(config: Dict, product_grid: str) -> None: + """Configure the namelist for the product_grid in the work directory. + Create namelist 'ocnicepost.nml' from template + + Parameters + ---------- + config : Dict + Configuration dictionary for the task + product_grid : str + Target product grid to process + + Returns + ------- + None + """ + + # Make a localconf with the "component" specific configuration for parsing the namelist + localconf = AttrDict() + localconf.DATA = config.DATA + localconf.component = config.component + + localconf.source_tripole_dims = ', '.join(map(str, OceanIceProducts.TRIPOLE_DIMS_MAP[config.model_grid])) + localconf.target_latlon_dims = ', '.join(map(str, OceanIceProducts.LATLON_DIMS_MAP[product_grid])) + + localconf.maskvar = config.oceanice_yaml[config.component].namelist.maskvar + localconf.sinvar = config.oceanice_yaml[config.component].namelist.sinvar + localconf.cosvar = config.oceanice_yaml[config.component].namelist.cosvar + localconf.angvar = config.oceanice_yaml[config.component].namelist.angvar + localconf.debug = ".true." if config.oceanice_yaml.ocnicepost.namelist.debug else ".false." + + logger.debug(f"localconf:\n{pformat(localconf)}") + + # Configure the namelist and write to file + logger.info("Create namelist for ocnicepost.x") + nml_template = os.path.join(localconf.DATA, "ocnicepost.nml.jinja2") + nml_data = Jinja(nml_template, localconf).render + logger.debug(f"ocnicepost_nml:\n{nml_data}") + nml_file = os.path.join(localconf.DATA, "ocnicepost.nml") + with open(nml_file, "w") as fho: + fho.write(nml_data) + + @staticmethod + @logit(logger) + def execute(config: Dict, product_grid: str) -> None: + """Run the ocnicepost.x executable to interpolate and convert to grib2 + + Parameters + ---------- + config : Dict + Configuration dictionary for the task + product_grid : str + Target product grid to process + + Returns + ------- + None + """ + + # Run the ocnicepost.x executable + OceanIceProducts.interp(config.DATA, config.APRUN_OCNICEPOST, exec_name="ocnicepost.x") + + # Convert interpolated netCDF file to grib2 + OceanIceProducts.netCDF_to_grib2(config, product_grid) + + @staticmethod + @logit(logger) + def interp(workdir: str, aprun_cmd: str, exec_name: str = "ocnicepost.x") -> None: + """ + Run the interpolation executable to generate rectilinear netCDF file + + Parameters + ---------- + config : Dict + Configuration dictionary for the task + workdir : str + Working directory for the task + aprun_cmd : str + aprun command to use + exec_name : str + Name of the executable e.g. ocnicepost.x + + Returns + ------- + None + """ + os.chdir(workdir) + logger.debug(f"Current working directory: {os.getcwd()}") + + exec_cmd = Executable(aprun_cmd) + exec_cmd.add_default_arg(os.path.join(workdir, exec_name)) + + OceanIceProducts._call_executable(exec_cmd) + + @staticmethod + @logit(logger) + def netCDF_to_grib2(config: Dict, grid: str) -> None: + """Convert interpolated netCDF file to grib2 + + Parameters + ---------- + config : Dict + Configuration dictionary for the task + grid : str + Target product grid to process + + Returns + ------ + None + """ + + os.chdir(config.DATA) + + exec_cmd = Executable(config.oceanice_yaml.nc2grib2.script) + arguments = [config.component, grid, config.current_cycle.strftime("%Y%m%d%H"), config.avg_period] + if config.component == 'ocean': + levs = config.oceanice_yaml.ocean.namelist.ocean_levels + arguments.append(':'.join(map(str, levs))) + + logger.info(f"Executing {exec_cmd} with arguments {arguments}") + try: + exec_cmd(*arguments) + except OSError: + logger.exception(f"FATAL ERROR: Failed to execute {exec_cmd}") + raise OSError(f"{exec_cmd}") + except Exception: + logger.exception(f"FATAL ERROR: Error occurred during execution of {exec_cmd}") + raise WorkflowException(f"{exec_cmd}") + + @staticmethod + @logit(logger) + def subset(config: Dict) -> None: + """ + Subset a list of variables from a netcdf file and save to a new netcdf file. + Also save global attributes and history from the old netcdf file into new netcdf file + + Parameters + ---------- + config : Dict + Configuration dictionary for the task + + Returns + ------- + None + """ + + os.chdir(config.DATA) + + input_file = f"{config.component}.nc" + output_file = f"{config.component}_subset.nc" + varlist = config.oceanice_yaml[config.component].subset + + logger.info(f"Subsetting {varlist} from {input_file} to {output_file}") + + try: + # open the netcdf file + ds = xr.open_dataset(input_file) + + # subset the variables + ds_subset = ds[varlist] + + # save global attributes from the old netcdf file into new netcdf file + ds_subset.attrs = ds.attrs + + # save subsetted variables to a new netcdf file + ds_subset.to_netcdf(output_file) + + except FileNotFoundError: + logger.exception(f"FATAL ERROR: Input file not found: {input_file}") + raise FileNotFoundError(f"File not found: {input_file}") + + except IOError as err: + logger.exception(f"FATAL ERROR: IOError occurred during netCDF subset: {input_file}") + raise IOError(f"An I/O error occurred: {err}") + + except Exception as err: + logger.exception(f"FATAL ERROR: Error occurred during netCDF subset: {input_file}") + raise WorkflowException(f"{err}") + + finally: + # close the netcdf files + ds.close() + ds_subset.close() + + @staticmethod + @logit(logger) + def _call_executable(exec_cmd: Executable) -> None: + """Internal method to call executable + + Parameters + ---------- + exec_cmd : Executable + Executable to run + + Raises + ------ + OSError + Failure due to OS issues + WorkflowException + All other exceptions + """ + + logger.info(f"Executing {exec_cmd}") + try: + exec_cmd() + except OSError: + logger.exception(f"FATAL ERROR: Failed to execute {exec_cmd}") + raise OSError(f"{exec_cmd}") + except Exception: + logger.exception(f"FATAL ERROR: Error occurred during execution of {exec_cmd}") + raise WorkflowException(f"{exec_cmd}") + + @staticmethod + @logit(logger) + def finalize(config: Dict) -> None: + """Perform closing actions of the task. + Copy data back from the DATA/ directory to COM/ + + Parameters + ---------- + config: Dict + Configuration dictionary for the task + + Returns + ------- + None + """ + + # Copy "component" specific generated data to COM/ directory + data_out = config.oceanice_yaml[config.component].data_out + + logger.info(f"Copy processed data to COM/ directory") + FileHandler(data_out).sync() diff --git a/ush/python/pygfs/task/snow_analysis.py b/ush/python/pygfs/task/snow_analysis.py new file mode 100644 index 0000000000..9656b00a8e --- /dev/null +++ b/ush/python/pygfs/task/snow_analysis.py @@ -0,0 +1,627 @@ +#!/usr/bin/env python3 + +import os +from logging import getLogger +from typing import Dict, List +from pprint import pformat +import numpy as np +from netCDF4 import Dataset + +from wxflow import (AttrDict, + FileHandler, + to_fv3time, to_YMD, to_YMDH, to_timedelta, add_to_datetime, + rm_p, + parse_j2yaml, save_as_yaml, + Jinja, + logit, + Executable, + WorkflowException) +from pygfs.task.analysis import Analysis + +logger = getLogger(__name__.split('.')[-1]) + + +class SnowAnalysis(Analysis): + """ + Class for global snow analysis tasks + """ + + NMEM_SNOWENS = 2 + + @logit(logger, name="SnowAnalysis") + def __init__(self, config): + super().__init__(config) + + _res = int(self.task_config['CASE'][1:]) + _window_begin = add_to_datetime(self.task_config.current_cycle, -to_timedelta(f"{self.task_config['assim_freq']}H") / 2) + _letkfoi_yaml = os.path.join(self.task_config.DATA, f"{self.task_config.RUN}.t{self.task_config['cyc']:02d}z.letkfoi.yaml") + + # Create a local dictionary that is repeatedly used across this class + local_dict = AttrDict( + { + 'npx_ges': _res + 1, + 'npy_ges': _res + 1, + 'npz_ges': self.task_config.LEVS - 1, + 'npz': self.task_config.LEVS - 1, + 'SNOW_WINDOW_BEGIN': _window_begin, + 'SNOW_WINDOW_LENGTH': f"PT{self.task_config['assim_freq']}H", + 'OPREFIX': f"{self.task_config.RUN}.t{self.task_config.cyc:02d}z.", + 'APREFIX': f"{self.task_config.RUN}.t{self.task_config.cyc:02d}z.", + 'jedi_yaml': _letkfoi_yaml + } + ) + + # Extend task_config with local_dict + self.task_config = AttrDict(**self.task_config, **local_dict) + + @logit(logger) + def prepare_GTS(self) -> None: + """Prepare the GTS data for a global snow analysis + + This method will prepare GTS data for a global snow analysis using JEDI. + This includes: + - processing GTS bufr snow depth observation data to IODA format + + Parameters + ---------- + Analysis: parent class for GDAS task + + Returns + ---------- + None + """ + + # create a temporary dict of all keys needed in this method + localconf = AttrDict() + keys = ['HOMEgfs', 'DATA', 'current_cycle', 'COM_OBS', 'COM_ATMOS_RESTART_PREV', + 'OPREFIX', 'CASE', 'OCNRES', 'ntiles'] + for key in keys: + localconf[key] = self.task_config[key] + + # Read and render the GTS_OBS_LIST yaml + logger.info(f"Reading {self.task_config.GTS_OBS_LIST}") + prep_gts_config = parse_j2yaml(self.task_config.GTS_OBS_LIST, localconf) + logger.debug(f"{self.task_config.GTS_OBS_LIST}:\n{pformat(prep_gts_config)}") + + # copy the GTS obs files from COM_OBS to DATA/obs + logger.info("Copying GTS obs for bufr2ioda.x") + FileHandler(prep_gts_config.gtsbufr).sync() + + logger.info("Link BUFR2IODAX into DATA/") + exe_src = self.task_config.BUFR2IODAX + exe_dest = os.path.join(localconf.DATA, os.path.basename(exe_src)) + if os.path.exists(exe_dest): + rm_p(exe_dest) + os.symlink(exe_src, exe_dest) + + # Create executable instance + exe = Executable(self.task_config.BUFR2IODAX) + + def _gtsbufr2iodax(exe, yaml_file): + if not os.path.isfile(yaml_file): + logger.exception(f"FATAL ERROR: {yaml_file} not found") + raise FileNotFoundError(yaml_file) + + logger.info(f"Executing {exe}") + try: + exe(yaml_file) + except OSError: + raise OSError(f"Failed to execute {exe} {yaml_file}") + except Exception: + raise WorkflowException(f"An error occured during execution of {exe} {yaml_file}") + + # Loop over entries in prep_gts_config.bufr2ioda keys + # 1. generate bufr2ioda YAML files + # 2. execute bufr2ioda.x + for name in prep_gts_config.bufr2ioda.keys(): + gts_yaml = os.path.join(self.task_config.DATA, f"bufr_{name}_snow.yaml") + logger.info(f"Generate BUFR2IODA YAML file: {gts_yaml}") + temp_yaml = parse_j2yaml(prep_gts_config.bufr2ioda[name], localconf) + save_as_yaml(temp_yaml, gts_yaml) + logger.info(f"Wrote bufr2ioda YAML to: {gts_yaml}") + + # execute BUFR2IODAX to convert {name} bufr data into IODA format + _gtsbufr2iodax(exe, gts_yaml) + + # Ensure the IODA snow depth GTS file is produced by the IODA converter + # If so, copy to COM_OBS/ + try: + FileHandler(prep_gts_config.gtsioda).sync() + except OSError as err: + logger.exception(f"{self.task_config.BUFR2IODAX} failed to produce GTS ioda files") + raise OSError(err) + + @logit(logger) + def prepare_IMS(self) -> None: + """Prepare the IMS data for a global snow analysis + + This method will prepare IMS data for a global snow analysis using JEDI. + This includes: + - staging model backgrounds + - processing raw IMS observation data and prepare for conversion to IODA + - creating IMS snowdepth data in IODA format. + + Parameters + ---------- + Analysis: parent class for GDAS task + + Returns + ---------- + None + """ + + # create a temporary dict of all keys needed in this method + localconf = AttrDict() + keys = ['DATA', 'current_cycle', 'COM_OBS', 'COM_ATMOS_RESTART_PREV', + 'OPREFIX', 'CASE', 'OCNRES', 'ntiles', 'FIXgfs'] + for key in keys: + localconf[key] = self.task_config[key] + + # stage backgrounds + logger.info("Staging backgrounds") + FileHandler(self.get_bkg_dict(localconf)).sync() + + # Read and render the IMS_OBS_LIST yaml + logger.info(f"Reading {self.task_config.IMS_OBS_LIST}") + prep_ims_config = parse_j2yaml(self.task_config.IMS_OBS_LIST, localconf) + logger.debug(f"{self.task_config.IMS_OBS_LIST}:\n{pformat(prep_ims_config)}") + + # copy the IMS obs files from COM_OBS to DATA/obs + logger.info("Copying IMS obs for CALCFIMSEXE") + FileHandler(prep_ims_config.calcfims).sync() + + logger.info("Create namelist for CALCFIMSEXE") + nml_template = self.task_config.FIMS_NML_TMPL + nml_data = Jinja(nml_template, localconf).render + logger.debug(f"fims.nml:\n{nml_data}") + + nml_file = os.path.join(localconf.DATA, "fims.nml") + with open(nml_file, "w") as fho: + fho.write(nml_data) + + logger.info("Link CALCFIMSEXE into DATA/") + exe_src = self.task_config.CALCFIMSEXE + exe_dest = os.path.join(localconf.DATA, os.path.basename(exe_src)) + if os.path.exists(exe_dest): + rm_p(exe_dest) + os.symlink(exe_src, exe_dest) + + # execute CALCFIMSEXE to calculate IMS snowdepth + exe = Executable(self.task_config.APRUN_CALCFIMS) + exe.add_default_arg(os.path.join(localconf.DATA, os.path.basename(exe_src))) + logger.info(f"Executing {exe}") + try: + exe() + except OSError: + raise OSError(f"Failed to execute {exe}") + except Exception: + raise WorkflowException(f"An error occured during execution of {exe}") + + # Ensure the snow depth IMS file is produced by the above executable + input_file = f"IMSscf.{to_YMD(localconf.current_cycle)}.{localconf.CASE}_oro_data.nc" + if not os.path.isfile(f"{os.path.join(localconf.DATA, input_file)}"): + logger.exception(f"{self.task_config.CALCFIMSEXE} failed to produce {input_file}") + raise FileNotFoundError(f"{os.path.join(localconf.DATA, input_file)}") + + # Execute imspy to create the IMS obs data in IODA format + logger.info("Create IMS obs data in IODA format") + + output_file = f"ims_snow_{to_YMDH(localconf.current_cycle)}.nc4" + if os.path.isfile(f"{os.path.join(localconf.DATA, output_file)}"): + rm_p(output_file) + + exe = Executable(self.task_config.IMS2IODACONV) + exe.add_default_arg(["-i", f"{os.path.join(localconf.DATA, input_file)}"]) + exe.add_default_arg(["-o", f"{os.path.join(localconf.DATA, output_file)}"]) + try: + logger.debug(f"Executing {exe}") + exe() + except OSError: + raise OSError(f"Failed to execute {exe}") + except Exception: + raise WorkflowException(f"An error occured during execution of {exe}") + + # Ensure the IODA snow depth IMS file is produced by the IODA converter + # If so, copy to COM_OBS/ + if not os.path.isfile(f"{os.path.join(localconf.DATA, output_file)}"): + logger.exception(f"{self.task_config.IMS2IODACONV} failed to produce {output_file}") + raise FileNotFoundError(f"{os.path.join(localconf.DATA, output_file)}") + else: + logger.info(f"Copy {output_file} to {self.task_config.COM_OBS}") + FileHandler(prep_ims_config.ims2ioda).sync() + + @logit(logger) + def initialize(self) -> None: + """Initialize method for snow analysis + This method: + - creates artifacts in the DATA directory by copying fix files + - creates the JEDI LETKF yaml from the template + - stages backgrounds, observations and ensemble members + + Parameters + ---------- + self : Analysis + Instance of the SnowAnalysis object + """ + + super().initialize() + + # create a temporary dict of all keys needed in this method + localconf = AttrDict() + keys = ['DATA', 'current_cycle', 'COM_OBS', 'COM_ATMOS_RESTART_PREV', + 'OPREFIX', 'CASE', 'OCNRES', 'ntiles'] + for key in keys: + localconf[key] = self.task_config[key] + + # Make member directories in DATA for background + dirlist = [] + for imem in range(1, SnowAnalysis.NMEM_SNOWENS + 1): + dirlist.append(os.path.join(localconf.DATA, 'bkg', f'mem{imem:03d}')) + FileHandler({'mkdir': dirlist}).sync() + + # stage fix files + logger.info(f"Staging JEDI fix files from {self.task_config.JEDI_FIX_YAML}") + jedi_fix_list = parse_j2yaml(self.task_config.JEDI_FIX_YAML, self.task_config) + FileHandler(jedi_fix_list).sync() + + # stage backgrounds + logger.info("Staging ensemble backgrounds") + FileHandler(self.get_ens_bkg_dict(localconf)).sync() + + # Write out letkfoi YAML file + save_as_yaml(self.task_config.jedi_config, self.task_config.jedi_yaml) + logger.info(f"Wrote letkfoi YAML to: {self.task_config.jedi_yaml}") + + # need output dir for diags and anl + logger.info("Create empty output [anl, diags] directories to receive output from executable") + newdirs = [ + os.path.join(localconf.DATA, "anl"), + os.path.join(localconf.DATA, "diags"), + ] + FileHandler({'mkdir': newdirs}).sync() + + @logit(logger) + def execute(self) -> None: + """Run a series of tasks to create Snow analysis + This method: + - creates an 2 member ensemble + - runs the JEDI LETKF executable to produce increments + - creates analysis from increments + + Parameters + ---------- + self : Analysis + Instance of the SnowAnalysis object + """ + + # create a temporary dict of all keys needed in this method + localconf = AttrDict() + keys = ['HOMEgfs', 'DATA', 'current_cycle', + 'COM_ATMOS_RESTART_PREV', 'COM_SNOW_ANALYSIS', 'APREFIX', + 'SNOWDEPTHVAR', 'BESTDDEV', 'CASE', 'OCNRES', 'ntiles', + 'APRUN_SNOWANL', 'JEDIEXE', 'jedi_yaml', 'DOIAU', 'SNOW_WINDOW_BEGIN', + 'APPLY_INCR_NML_TMPL', 'APPLY_INCR_EXE', 'APRUN_APPLY_INCR'] + for key in keys: + localconf[key] = self.task_config[key] + + logger.info("Creating ensemble") + self.create_ensemble(localconf.SNOWDEPTHVAR, + localconf.BESTDDEV, + AttrDict({key: localconf[key] for key in ['DATA', 'ntiles', 'current_cycle']})) + + logger.info("Running JEDI LETKF") + exec_cmd = Executable(localconf.APRUN_SNOWANL) + exec_name = os.path.join(localconf.DATA, 'gdas.x') + exec_cmd.add_default_arg(exec_name) + exec_cmd.add_default_arg('fv3jedi') + exec_cmd.add_default_arg('localensembleda') + exec_cmd.add_default_arg(localconf.jedi_yaml) + + try: + logger.debug(f"Executing {exec_cmd}") + exec_cmd() + except OSError: + raise OSError(f"Failed to execute {exec_cmd}") + except Exception: + raise WorkflowException(f"An error occured during execution of {exec_cmd}") + + logger.info("Creating analysis from backgrounds and increments") + self.add_increments(localconf) + + @logit(logger) + def finalize(self) -> None: + """Performs closing actions of the Snow analysis task + This method: + - tar and gzip the output diag files and place in COM/ + - copy the generated YAML file from initialize to the COM/ + - copy the analysis files to the COM/ + - copy the increment files to the COM/ + + Parameters + ---------- + self : Analysis + Instance of the SnowAnalysis object + """ + + logger.info("Create diagnostic tarball of diag*.nc4 files") + statfile = os.path.join(self.task_config.COM_SNOW_ANALYSIS, f"{self.task_config.APREFIX}snowstat.tgz") + self.tgz_diags(statfile, self.task_config.DATA) + + logger.info("Copy full YAML to COM") + src = os.path.join(self.task_config['DATA'], f"{self.task_config.APREFIX}letkfoi.yaml") + dest = os.path.join(self.task_config.COM_CONF, f"{self.task_config.APREFIX}letkfoi.yaml") + yaml_copy = { + 'mkdir': [self.task_config.COM_CONF], + 'copy': [[src, dest]] + } + FileHandler(yaml_copy).sync() + + logger.info("Copy analysis to COM") + bkgtimes = [] + if self.task_config.DOIAU: + # need both beginning and middle of window + bkgtimes.append(self.task_config.SNOW_WINDOW_BEGIN) + bkgtimes.append(self.task_config.current_cycle) + anllist = [] + for bkgtime in bkgtimes: + template = f'{to_fv3time(bkgtime)}.sfc_data.tile{{tilenum}}.nc' + for itile in range(1, self.task_config.ntiles + 1): + filename = template.format(tilenum=itile) + src = os.path.join(self.task_config.DATA, 'anl', filename) + dest = os.path.join(self.task_config.COM_SNOW_ANALYSIS, filename) + anllist.append([src, dest]) + FileHandler({'copy': anllist}).sync() + + logger.info('Copy increments to COM') + template = f'snowinc.{to_fv3time(self.task_config.current_cycle)}.sfc_data.tile{{tilenum}}.nc' + inclist = [] + for itile in range(1, self.task_config.ntiles + 1): + filename = template.format(tilenum=itile) + src = os.path.join(self.task_config.DATA, 'anl', filename) + dest = os.path.join(self.task_config.COM_SNOW_ANALYSIS, filename) + inclist.append([src, dest]) + FileHandler({'copy': inclist}).sync() + + @staticmethod + @logit(logger) + def get_bkg_dict(config: Dict) -> Dict[str, List[str]]: + """Compile a dictionary of model background files to copy + + This method constructs a dictionary of FV3 RESTART files (coupler, sfc_data) + that are needed for global snow DA and returns said dictionary for use by the FileHandler class. + + Parameters + ---------- + config: Dict + Dictionary of key-value pairs needed in this method + Should contain the following keys: + COM_ATMOS_RESTART_PREV + DATA + current_cycle + ntiles + + Returns + ---------- + bkg_dict: Dict + a dictionary containing the list of model background files to copy for FileHandler + """ + # NOTE for now this is FV3 RESTART files and just assumed to be fh006 + + # get FV3 sfc_data RESTART files, this will be a lot simpler when using history files + rst_dir = os.path.join(config.COM_ATMOS_RESTART_PREV) # for now, option later? + run_dir = os.path.join(config.DATA, 'bkg') + + # Start accumulating list of background files to copy + bkglist = [] + + # snow DA needs coupler + basename = f'{to_fv3time(config.current_cycle)}.coupler.res' + bkglist.append([os.path.join(rst_dir, basename), os.path.join(run_dir, basename)]) + + # snow DA only needs sfc_data + for ftype in ['sfc_data']: + template = f'{to_fv3time(config.current_cycle)}.{ftype}.tile{{tilenum}}.nc' + for itile in range(1, config.ntiles + 1): + basename = template.format(tilenum=itile) + bkglist.append([os.path.join(rst_dir, basename), os.path.join(run_dir, basename)]) + + bkg_dict = { + 'mkdir': [run_dir], + 'copy': bkglist + } + return bkg_dict + + @staticmethod + @logit(logger) + def get_ens_bkg_dict(config: Dict) -> Dict: + """Compile a dictionary of model background files to copy for the ensemble + Note that a "Fake" 2-member ensemble backgroud is being created by copying FV3 RESTART files (coupler, sfc_data) + from the deterministic background to DATA/bkg/mem001, 002. + + Parameters + ---------- + config: Dict + Dictionary of key-value pairs needed in this method + Should contain the following keys: + COM_ATMOS_RESTART_PREV + DATA + current_cycle + ntiles + + Returns + ---------- + bkg_dict: Dict + a dictionary containing the list of model background files to copy for FileHandler + """ + + dirlist = [] + bkglist = [] + + # get FV3 sfc_data RESTART files; Note an ensemble is being created + rst_dir = os.path.join(config.COM_ATMOS_RESTART_PREV) + + for imem in range(1, SnowAnalysis.NMEM_SNOWENS + 1): + memchar = f"mem{imem:03d}" + + run_dir = os.path.join(config.DATA, 'bkg', memchar, 'RESTART') + dirlist.append(run_dir) + + # Snow DA needs coupler + basename = f'{to_fv3time(config.current_cycle)}.coupler.res' + bkglist.append([os.path.join(rst_dir, basename), os.path.join(run_dir, basename)]) + + # Snow DA only needs sfc_data + for ftype in ['sfc_data']: + template = f'{to_fv3time(config.current_cycle)}.{ftype}.tile{{tilenum}}.nc' + for itile in range(1, config.ntiles + 1): + basename = template.format(tilenum=itile) + bkglist.append([os.path.join(rst_dir, basename), os.path.join(run_dir, basename)]) + + bkg_dict = { + 'mkdir': dirlist, + 'copy': bkglist + } + + return bkg_dict + + @staticmethod + @logit(logger) + def create_ensemble(vname: str, bestddev: float, config: Dict) -> None: + """Create a 2-member ensemble for Snow Depth analysis by perturbing snow depth with a prescribed variance. + Additionally, remove glacier locations + + Parameters + ---------- + vname : str + snow depth variable to perturb: "snodl" + bestddev : float + Background Error Standard Deviation to perturb around to create ensemble + config: Dict + Dictionary of key-value pairs needed in this method. It must contain the following keys: + DATA + current_cycle + ntiles + """ + + # 2 ens members + offset = bestddev / np.sqrt(SnowAnalysis.NMEM_SNOWENS) + + logger.info(f"Creating ensemble for LETKFOI by offsetting with {offset}") + + workdir = os.path.join(config.DATA, 'bkg') + + sign = [1, -1] + ens_dirs = ['mem001', 'mem002'] + + for (memchar, value) in zip(ens_dirs, sign): + logger.debug(f"creating ensemble member {memchar} with sign {value}") + for tt in range(1, config.ntiles + 1): + logger.debug(f"perturbing tile {tt}") + # open file + out_netcdf = os.path.join(workdir, memchar, 'RESTART', f"{to_fv3time(config.current_cycle)}.sfc_data.tile{tt}.nc") + logger.debug(f"creating member {out_netcdf}") + with Dataset(out_netcdf, "r+") as ncOut: + slmsk_array = ncOut.variables['slmsk'][:] + vtype_array = ncOut.variables['vtype'][:] + slmsk_array[vtype_array == 15] = 0 # remove glacier locations + var_array = ncOut.variables[vname][:] + var_array[slmsk_array == 1] = var_array[slmsk_array == 1] + value * offset + ncOut.variables[vname][0, :, :] = var_array[:] + + @staticmethod + @logit(logger) + def add_increments(config: Dict) -> None: + """Executes the program "apply_incr.exe" to create analysis "sfc_data" files by adding increments to backgrounds + + Parameters + ---------- + config: Dict + Dictionary of key-value pairs needed in this method + Should contain the following keys: + HOMEgfs + COM_ATMOS_RESTART_PREV + DATA + current_cycle + CASE + OCNRES + ntiles + APPLY_INCR_NML_TMPL + APPLY_INCR_EXE + APRUN_APPLY_INCR + DOIAU + SNOW_WINDOW_BEGIN + + Raises + ------ + OSError + Failure due to OS issues + WorkflowException + All other exceptions + """ + + # need backgrounds to create analysis from increments after LETKF + logger.info("Copy backgrounds into anl/ directory for creating analysis from increments") + bkgtimes = [] + if config.DOIAU: + # want analysis at beginning and middle of window + bkgtimes.append(config.SNOW_WINDOW_BEGIN) + bkgtimes.append(config.current_cycle) + anllist = [] + for bkgtime in bkgtimes: + template = f'{to_fv3time(bkgtime)}.sfc_data.tile{{tilenum}}.nc' + for itile in range(1, config.ntiles + 1): + filename = template.format(tilenum=itile) + src = os.path.join(config.COM_ATMOS_RESTART_PREV, filename) + dest = os.path.join(config.DATA, "anl", filename) + anllist.append([src, dest]) + FileHandler({'copy': anllist}).sync() + + if config.DOIAU: + logger.info("Copying increments to beginning of window") + template_in = f'snowinc.{to_fv3time(config.current_cycle)}.sfc_data.tile{{tilenum}}.nc' + template_out = f'snowinc.{to_fv3time(config.SNOW_WINDOW_BEGIN)}.sfc_data.tile{{tilenum}}.nc' + inclist = [] + for itile in range(1, config.ntiles + 1): + filename_in = template_in.format(tilenum=itile) + filename_out = template_out.format(tilenum=itile) + src = os.path.join(config.DATA, 'anl', filename_in) + dest = os.path.join(config.DATA, 'anl', filename_out) + inclist.append([src, dest]) + FileHandler({'copy': inclist}).sync() + + # loop over times to apply increments + for bkgtime in bkgtimes: + logger.info("Processing analysis valid: {bkgtime}") + logger.info("Create namelist for APPLY_INCR_EXE") + nml_template = config.APPLY_INCR_NML_TMPL + nml_config = { + 'current_cycle': bkgtime, + 'CASE': config.CASE, + 'DATA': config.DATA, + 'HOMEgfs': config.HOMEgfs, + 'OCNRES': config.OCNRES, + } + nml_data = Jinja(nml_template, nml_config).render + logger.debug(f"apply_incr_nml:\n{nml_data}") + + nml_file = os.path.join(config.DATA, "apply_incr_nml") + with open(nml_file, "w") as fho: + fho.write(nml_data) + + logger.info("Link APPLY_INCR_EXE into DATA/") + exe_src = config.APPLY_INCR_EXE + exe_dest = os.path.join(config.DATA, os.path.basename(exe_src)) + if os.path.exists(exe_dest): + rm_p(exe_dest) + os.symlink(exe_src, exe_dest) + + # execute APPLY_INCR_EXE to create analysis files + exe = Executable(config.APRUN_APPLY_INCR) + exe.add_default_arg(os.path.join(config.DATA, os.path.basename(exe_src))) + logger.info(f"Executing {exe}") + try: + exe() + except OSError: + raise OSError(f"Failed to execute {exe}") + except Exception: + raise WorkflowException(f"An error occured during execution of {exe}") diff --git a/ush/python/pygfs/task/upp.py b/ush/python/pygfs/task/upp.py index 7db50e1582..7e42e07c64 100644 --- a/ush/python/pygfs/task/upp.py +++ b/ush/python/pygfs/task/upp.py @@ -46,26 +46,27 @@ def __init__(self, config: Dict[str, Any]) -> None: """ super().__init__(config) - if self.config.UPP_RUN not in self.VALID_UPP_RUN: - raise NotImplementedError(f'{self.config.UPP_RUN} is not a valid UPP run type.\n' + + if self.task_config.UPP_RUN not in self.VALID_UPP_RUN: + raise NotImplementedError(f'{self.task_config.UPP_RUN} is not a valid UPP run type.\n' + 'Valid UPP_RUN values are:\n' + f'{", ".join(self.VALID_UPP_RUN)}') - valid_datetime = add_to_datetime(self.runtime_config.current_cycle, to_timedelta(f"{self.config.FORECAST_HOUR}H")) + valid_datetime = add_to_datetime(self.task_config.current_cycle, to_timedelta(f"{self.task_config.FORECAST_HOUR}H")) + # Extend task_config with localdict localdict = AttrDict( - {'upp_run': self.config.UPP_RUN, - 'forecast_hour': self.config.FORECAST_HOUR, + {'upp_run': self.task_config.UPP_RUN, + 'forecast_hour': self.task_config.FORECAST_HOUR, 'valid_datetime': valid_datetime, 'atmos_filename': f"atm_{valid_datetime.strftime('%Y%m%d%H%M%S')}.nc", 'flux_filename': f"sfc_{valid_datetime.strftime('%Y%m%d%H%M%S')}.nc" } ) - self.task_config = AttrDict(**self.config, **self.runtime_config, **localdict) + self.task_config = AttrDict(**self.task_config, **localdict) # Read the upp.yaml file for common configuration - logger.info(f"Read the UPP configuration yaml file {self.config.UPP_CONFIG}") - self.task_config.upp_yaml = parse_j2yaml(self.config.UPP_CONFIG, self.task_config) + logger.info(f"Read the UPP configuration yaml file {self.task_config.UPP_CONFIG}") + self.task_config.upp_yaml = parse_j2yaml(self.task_config.UPP_CONFIG, self.task_config) logger.debug(f"upp_yaml:\n{pformat(self.task_config.upp_yaml)}") @staticmethod diff --git a/ush/python/pygfs/utils/__init__.py b/ush/python/pygfs/utils/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/ush/python/pygfs/utils/marine_da_utils.py b/ush/python/pygfs/utils/marine_da_utils.py new file mode 100644 index 0000000000..016551878b --- /dev/null +++ b/ush/python/pygfs/utils/marine_da_utils.py @@ -0,0 +1,99 @@ +import f90nml +import os +from logging import getLogger +import xarray as xr + +from wxflow import (FileHandler, + logit, + WorkflowException, + AttrDict, + parse_j2yaml, + Executable, + jinja) + +logger = getLogger(__name__.split('.')[-1]) + + +@logit(logger) +def run(exec_cmd: Executable) -> None: + """Run the executable command + """ + logger.info(f"Executing {exec_cmd}") + try: + logger.debug(f"Executing {exec_cmd}") + exec_cmd() + except OSError: + raise OSError(f"Failed to execute {exec_cmd}") + except Exception: + raise WorkflowException(f"An error occured during execution of {exec_cmd}") + + +@logit(logger) +def link_executable(task_config: AttrDict, exe_name: str) -> None: + """Link the executable to the DATA directory + """ + logger.info(f"Link executable {exe_name}") + logger.warn("WARNING: Linking is not permitted per EE2.") + exe_src = os.path.join(task_config.EXECgfs, exe_name) + exe_dest = os.path.join(task_config.DATA, exe_name) + if os.path.exists(exe_dest): + os.remove(exe_dest) + os.symlink(exe_src, exe_dest) + + +@logit(logger) +def prep_input_nml(task_config: AttrDict) -> None: + """Prepare the input.nml file + TODO: Use jinja2 instead of f90nml + """ + # stage input.nml + mom_input_nml_tmpl_src = os.path.join(task_config.HOMEgdas, 'parm', 'soca', 'fms', 'input.nml') + mom_input_nml_tmpl = os.path.join(task_config.DATA, 'mom_input.nml.tmpl') + FileHandler({'copy': [[mom_input_nml_tmpl_src, mom_input_nml_tmpl]]}).sync() + + # swap date and stacksize + domain_stack_size = task_config.DOMAIN_STACK_SIZE + ymdhms = [int(s) for s in task_config.MARINE_WINDOW_END.strftime('%Y,%m,%d,%H,%M,%S').split(',')] + with open(mom_input_nml_tmpl, 'r') as nml_file: + nml = f90nml.read(nml_file) + nml['ocean_solo_nml']['date_init'] = ymdhms + nml['fms_nml']['domains_stack_size'] = int(domain_stack_size) + nml.write('mom_input.nml') + + +@logit(logger) +def cice_hist2fms(input_filename: str, output_filename: str) -> None: + """ Reformat the CICE history file so it can be read by SOCA/FMS + Simple reformatting utility to allow soca/fms to read the CICE history files + """ + + # open the CICE history file + ds = xr.open_dataset(input_filename) + + if 'aicen' in ds.variables and 'hicen' in ds.variables and 'hsnon' in ds.variables: + logger.info(f"*** Already reformatted, skipping.") + return + + # rename the dimensions to xaxis_1 and yaxis_1 + ds = ds.rename({'ni': 'xaxis_1', 'nj': 'yaxis_1'}) + + # rename the variables + ds = ds.rename({'aice_h': 'aicen', 'hi_h': 'hicen', 'hs_h': 'hsnon'}) + + # Save the new netCDF file + ds.to_netcdf(output_filename, mode='w') + + +@logit(logger) +def stage_ens_mem(task_config: AttrDict) -> None: + """ Copy the ensemble members to the DATA directory + Copy the ensemble members to the DATA directory and reformat the CICE history files + """ + # Copy the ensemble members to the DATA directory + logger.info("---------------- Stage ensemble members") + ensbkgconf = AttrDict(task_config) + ensbkgconf.RUN = task_config.GDUMP_ENS + logger.debug(f"{jinja.Jinja(task_config.MARINE_ENSDA_STAGE_BKG_YAML_TMPL, ensbkgconf).render}") + letkf_stage_list = parse_j2yaml(task_config.MARINE_ENSDA_STAGE_BKG_YAML_TMPL, ensbkgconf) + logger.info(f"{letkf_stage_list}") + FileHandler(letkf_stage_list).sync() diff --git a/ush/radmon_err_rpt.sh b/ush/radmon_err_rpt.sh index 6ae6505624..c3d251d5cd 100755 --- a/ush/radmon_err_rpt.sh +++ b/ush/radmon_err_rpt.sh @@ -1,6 +1,6 @@ #! /usr/bin/env bash -source "${HOMEgfs}/ush/preamble.sh" +source "${USHgfs}/preamble.sh" ################################################################################ #### UNIX Script Documentation Block @@ -55,9 +55,6 @@ cycle2=${5:-${cycle2:?}} diag_rpt=${6:-${diag_rpt:?}} outfile=${7:-${outfile:?}} -# Directories -HOMEradmon=${HOMEradmon:-$(pwd)} - # Other variables err=0 RADMON_SUFFIX=${RADMON_SUFFIX} diff --git a/ush/radmon_verf_angle.sh b/ush/radmon_verf_angle.sh index f68d7c88cc..3dff2a6f98 100755 --- a/ush/radmon_verf_angle.sh +++ b/ush/radmon_verf_angle.sh @@ -1,6 +1,6 @@ #! /usr/bin/env bash -source "${HOMEgfs}/ush/preamble.sh" +source "${USHgfs}/preamble.sh" ################################################################################ #### UNIX Script Documentation Block @@ -29,8 +29,6 @@ source "${HOMEgfs}/ush/preamble.sh" # Imported Shell Variables: # RADMON_SUFFIX data source suffix # defauls to opr -# EXECgfs executable directory -# PARMmonitor parm directory # RAD_AREA global or regional flag # defaults to global # TANKverf_rad data repository @@ -83,7 +81,6 @@ which prep_step which startmsg # File names -export pgmout=${pgmout:-${jlogfile}} touch "${pgmout}" # Other variables @@ -101,7 +98,7 @@ fi err=0 angle_exec=radmon_angle.x -shared_scaninfo="${shared_scaninfo:-${PARMmonitor}/gdas_radmon_scaninfo.txt}" +shared_scaninfo="${shared_scaninfo:-${PARMgfs}/monitor/gdas_radmon_scaninfo.txt}" scaninfo=scaninfo.txt #-------------------------------------------------------------------- diff --git a/ush/radmon_verf_bcoef.sh b/ush/radmon_verf_bcoef.sh index ab1058711e..4274436154 100755 --- a/ush/radmon_verf_bcoef.sh +++ b/ush/radmon_verf_bcoef.sh @@ -1,6 +1,6 @@ #! /usr/bin/env bash -source "${HOMEgfs}/ush/preamble.sh" +source "${USHgfs}/preamble.sh" ################################################################################ #### UNIX Script Documentation Block @@ -69,7 +69,6 @@ fi echo " RADMON_NETCDF, netcdf_boolean = ${RADMON_NETCDF}, ${netcdf_boolean}" # File names -pgmout=${pgmout:-${jlogfile}} touch "${pgmout}" # Other variables diff --git a/ush/radmon_verf_bcor.sh b/ush/radmon_verf_bcor.sh index f1f97c247e..ea0a7842e6 100755 --- a/ush/radmon_verf_bcor.sh +++ b/ush/radmon_verf_bcor.sh @@ -1,6 +1,6 @@ #! /usr/bin/env bash -source "${HOMEgfs}/ush/preamble.sh" +source "${USHgfs}/preamble.sh" ################################################################################ #### UNIX Script Documentation Block @@ -65,7 +65,6 @@ source "${HOMEgfs}/ush/preamble.sh" #################################################################### # File names -pgmout=${pgmout:-${jlogfile}} touch "${pgmout}" # Other variables diff --git a/ush/radmon_verf_time.sh b/ush/radmon_verf_time.sh index 7f98407ec5..0e935826dd 100755 --- a/ush/radmon_verf_time.sh +++ b/ush/radmon_verf_time.sh @@ -1,6 +1,6 @@ #! /usr/bin/env bash -source "${HOMEgfs}/ush/preamble.sh" +source "${USHgfs}/preamble.sh" ################################################################################ #### UNIX Script Documentation Block @@ -33,8 +33,6 @@ source "${HOMEgfs}/ush/preamble.sh" # defaults to 1 (on) # RADMON_SUFFIX data source suffix # defauls to opr -# EXECgfs executable directory -# PARMmonitor parm data directory # RAD_AREA global or regional flag # defaults to global # TANKverf_rad data repository @@ -75,11 +73,9 @@ source "${HOMEgfs}/ush/preamble.sh" #################################################################### # File names -#pgmout=${pgmout:-${jlogfile}} -#touch $pgmout radmon_err_rpt=${radmon_err_rpt:-${USHgfs}/radmon_err_rpt.sh} -base_file=${base_file:-${PARMmonitor}/gdas_radmon_base.tar} +base_file=${base_file:-${PARMgfs}/monitor/gdas_radmon_base.tar} report=report.txt disclaimer=disclaimer.txt diff --git a/ush/rstprod.sh b/ush/rstprod.sh index acac0340bb..b48a6817e0 100755 --- a/ush/rstprod.sh +++ b/ush/rstprod.sh @@ -1,6 +1,6 @@ #! /usr/bin/env bash -source "$HOMEgfs/ush/preamble.sh" +source "${USHgfs}/preamble.sh" #--------------------------------------------------------- # rstprod.sh diff --git a/ush/run_mpmd.sh b/ush/run_mpmd.sh index 24cb3f2656..e3fc2b7512 100755 --- a/ush/run_mpmd.sh +++ b/ush/run_mpmd.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -source "${HOMEgfs}/ush/preamble.sh" +source "${USHgfs}/preamble.sh" cmdfile=${1:?"run_mpmd requires an input file containing commands to execute in MPMD mode"} diff --git a/ush/syndat_getjtbul.sh b/ush/syndat_getjtbul.sh index c17067ff72..6596c6ef96 100755 --- a/ush/syndat_getjtbul.sh +++ b/ush/syndat_getjtbul.sh @@ -18,17 +18,10 @@ # Imported variables that must be passed in: # DATA - path to working directory # pgmout - string indicating path to for standard output file -# EXECSYND - path to syndat executable directory # TANK_TROPCY - path to home directory containing tropical cyclone record # data base -# Imported variables that can be passed in: -# jlogfile - path to job log file (skipped over by this script if not -# passed in) - -source "$HOMEgfs/ush/preamble.sh" - -EXECSYND=${EXECSYND:-${HOMESYND}/exec} +source "${USHgfs}/preamble.sh" cd $DATA @@ -52,8 +45,6 @@ hour=$(echo $CDATE10 | cut -c9-10) echo $PDYm1 pdym1=$PDYm1 -#pdym1=$(sh $utilscript/finddate.sh $pdy d-1) - echo " " >> $pgmout echo "Entering sub-shell syndat_getjtbul.sh to recover JTWC Bulletins" \ >> $pgmout @@ -123,7 +114,7 @@ fi [ -s jtwcbul ] && echo "Processing JTWC bulletin halfs into tcvitals records" >> $pgmout -pgm=$(basename $EXECSYND/syndat_getjtbul.x) +pgm=$(basename ${EXECgfs}/syndat_getjtbul.x) export pgm if [ -s prep_step ]; then set +u @@ -138,7 +129,7 @@ rm -f fnoc export FORT11=jtwcbul export FORT51=fnoc -time -p ${EXECSYND}/${pgm} >> $pgmout 2> errfile +time -p ${EXECgfs}/${pgm} >> $pgmout 2> errfile errget=$? ###cat errfile cat errfile >> $pgmout diff --git a/ush/syndat_qctropcy.sh b/ush/syndat_qctropcy.sh index cda9030577..8ec8f70b14 100755 --- a/ush/syndat_qctropcy.sh +++ b/ush/syndat_qctropcy.sh @@ -44,10 +44,6 @@ # COMSP - path to both output jtwc-fnoc file and output tcvitals file (this # tcvitals file is read by subsequent relocation processing and/or # subsequent program SYNDAT_SYNDATA) -# PARMSYND - path to syndat parm field directory -# EXECSYND - path to syndat executable directory -# FIXam - path to syndat fix field directory -# USHSYND - path to syndat ush directory # Imported variables that can be passed in: # ARCHSYND - path to syndat archive directory @@ -59,7 +55,7 @@ # data base # (Default: /dcom/us007003) # slmask - path to t126 32-bit gaussian land/sea mask file -# (Default: $FIXam/syndat_slmask.t126.gaussian) +# (Default: ${FIXgfs}/am/syndat_slmask.t126.gaussian) # copy_back - switch to copy updated files back to archive directory and # to tcvitals directory # (Default: YES) @@ -67,19 +63,13 @@ # (Default: not set) # TIMEIT - optional time and resource reporting (Default: not set) -source "$HOMEgfs/ush/preamble.sh" +source "${USHgfs}/preamble.sh" ARCHSYND=${ARCHSYND:-$COMROOTp3/gfs/prod/syndat} -HOMENHCp1=${HOMENHCp1:-/gpfs/?p1/nhc/save/guidance/storm-data/ncep} HOMENHC=${HOMENHC:-/gpfs/dell2/nhc/save/guidance/storm-data/ncep} TANK_TROPCY=${TANK_TROPCY:-${DCOMROOT}/us007003} -FIXam=${FIXam:-$HOMEgfs/fix/am} -USHSYND=${USHSYND:-$HOMEgfs/ush} -EXECSYND=${EXECSYND:-$HOMEgfs/exec} -PARMSYND=${PARMSYND:-$HOMEgfs/parm/relo} - -slmask=${slmask:-$FIXam/syndat_slmask.t126.gaussian} +slmask=${slmask:-${FIXgfs}/am/syndat_slmask.t126.gaussian} copy_back=${copy_back:-YES} files_override=${files_override:-""} @@ -188,12 +178,12 @@ if [ -n "$files_override" ]; then # for testing, typically want FILES=F fi echo " &INPUT RUNID = '${net}_${tmmark}_${cyc}', FILES = $files " > vitchk.inp -cat $PARMSYND/syndat_qctropcy.${RUN}.parm >> vitchk.inp +cat ${PARMgfs}/relo/syndat_qctropcy.${RUN}.parm >> vitchk.inp -# Copy the fixed fields from FIXam +# Copy the fixed fields -cp $FIXam/syndat_fildef.vit fildef.vit -cp $FIXam/syndat_stmnames stmnames +cp ${FIXgfs}/am/syndat_fildef.vit fildef.vit +cp ${FIXgfs}/am/syndat_stmnames stmnames rm -f nhc fnoc lthistry @@ -205,12 +195,9 @@ rm -f nhc fnoc lthistry # All are input to program syndat_qctropcy # ------------------------------------------------------------------ -if [ -s $HOMENHC/tcvitals ]; then - echo "tcvitals found" >> $pgmout - cp $HOMENHC/tcvitals nhc -elif [ -s $HOMENHCp1/tcvitals ]; then +if [ -s ${HOMENHC}/tcvitals ]; then echo "tcvitals found" >> $pgmout - cp $HOMENHCp1/tcvitals nhc + cp ${HOMENHC}/tcvitals nhc else echo "WARNING: tcvitals not found, create empty tcvitals" >> $pgmout > nhc @@ -221,17 +208,17 @@ touch nhc [ "$copy_back" = 'YES' ] && cat nhc >> $ARCHSYND/syndat_tcvitals.$year mv -f nhc nhc1 -$USHSYND/parse-storm-type.pl nhc1 > nhc +${USHgfs}/parse-storm-type.pl nhc1 > nhc cp -p nhc nhc.ORIG # JTWC/FNOC ... execute syndat_getjtbul script to write into working directory # as fnoc; copy to archive -$USHSYND/syndat_getjtbul.sh $CDATE10 +${USHgfs}/syndat_getjtbul.sh $CDATE10 touch fnoc [ "$copy_back" = 'YES' ] && cat fnoc >> $ARCHSYND/syndat_tcvitals.$year mv -f fnoc fnoc1 -$USHSYND/parse-storm-type.pl fnoc1 > fnoc +${USHgfs}/parse-storm-type.pl fnoc1 > fnoc if [ $SENDDBN = YES ]; then $DBNROOT/bin/dbn_alert MODEL SYNDAT_TCVITALS $job $ARCHSYND/syndat_tcvitals.$year @@ -245,7 +232,7 @@ cp $slmask slmask.126 # Execute program syndat_qctropcy -pgm=$(basename $EXECSYND/syndat_qctropcy.x) +pgm=$(basename ${EXECgfs}/syndat_qctropcy.x) export pgm if [ -s prep_step ]; then set +u @@ -259,7 +246,7 @@ fi echo "$CDATE10" > cdate10.dat export FORT11=slmask.126 export FORT12=cdate10.dat -${EXECSYND}/${pgm} >> $pgmout 2> errfile +${EXECgfs}/${pgm} >> $pgmout 2> errfile errqct=$? ###cat errfile cat errfile >> $pgmout @@ -323,28 +310,25 @@ diff nhc nhc.ORIG > /dev/null errdiff=$? ################################### -# Update NHC file in $HOMENHC +# Update NHC file in ${HOMENHC} ################################### if test "$errdiff" -ne '0' then if [ "$copy_back" = 'YES' -a ${envir} = 'prod' ]; then - if [ -s $HOMENHC/tcvitals ]; then - cp nhc $HOMENHC/tcvitals - fi - if [ -s $HOMENHCp1/tcvitals ]; then - cp nhc $HOMENHCp1/tcvitals + if [ -s ${HOMENHC}/tcvitals ]; then + cp nhc ${HOMENHC}/tcvitals fi err=$? if [ "$err" -ne '0' ]; then msg="###ERROR: Previous NHC Synthetic Data Record File \ -$HOMENHC/tcvitals not updated by syndat_qctropcy" +${HOMENHC}/tcvitals not updated by syndat_qctropcy" else msg="Previous NHC Synthetic Data Record File \ -$HOMENHC/tcvitals successfully updated by syndat_qctropcy" +${HOMENHC}/tcvitals successfully updated by syndat_qctropcy" fi set +x @@ -357,7 +341,7 @@ $HOMENHC/tcvitals successfully updated by syndat_qctropcy" else - msg="Previous NHC Synthetic Data Record File $HOMENHC/tcvitals \ + msg="Previous NHC Synthetic Data Record File ${HOMENHC}/tcvitals \ not changed by syndat_qctropcy" set +x echo diff --git a/ush/tropcy_relocate.sh b/ush/tropcy_relocate.sh index 01a21bd12c..11c0afb990 100755 --- a/ush/tropcy_relocate.sh +++ b/ush/tropcy_relocate.sh @@ -84,20 +84,13 @@ # envir String indicating environment under which job runs ('prod' # or 'test') # Default is "prod" -# HOMEALL String indicating parent directory path for some or -# all files under which job runs. -# If the imported variable MACHINE!=sgi, then the default is -# "/nw${envir}"; otherwise the default is -# "/disk1/users/snake/prepobs" -# HOMERELO String indicating parent directory path for relocation -# specific files. (May be under HOMEALL) # envir_getges String indicating environment under which GETGES utility -# ush runs (see documentation in $USHGETGES/getges.sh for +# ush runs (see documentation in ${USHgfs}/getges.sh for # more information) # Default is "$envir" # network_getges # String indicating job network under which GETGES utility -# ush runs (see documentation in $USHGETGES/getges.sh for +# ush runs (see documentation in ${USHgfs}/getges.sh for # more information) # Default is "global" unless the center relocation processing # date/time is not a multiple of 3-hrs, then the default is @@ -122,34 +115,20 @@ # POE_OPTS String indicating options to use with poe command # Default is "-pgmmodel mpmd -ilevel 2 -labelio yes \ # -stdoutmode ordered" -# USHGETGES String indicating directory path for GETGES utility ush -# file -# USHRELO String indicating directory path for RELOCATE ush files -# Default is "${HOMERELO}/ush" -# EXECRELO String indicating directory path for RELOCATE executables -# Default is "${HOMERELO}/exec" -# FIXRELO String indicating directory path for RELOCATE data fix- -# field files -# Default is "${HOMERELO}/fix" -# EXECUTIL String indicating directory path for utility program -# executables -# If the imported variable MACHINE!=sgi, then the default is -# "/nwprod/util/exec"; otherwise the default is -# "${HOMEALL}/util/exec" # RELOX String indicating executable path for RELOCATE_MV_NVORTEX # program -# Default is "$EXECRELO/relocate_mv_nvortex" +# Default is "${EXECgfs}/relocate_mv_nvortex" # SUPVX String indicating executable path for SUPVIT utility # program -# Default is "$EXECUTIL/supvit.x" +# Default is "${EXECgfs}/supvit.x" # GETTX String indicating executable path for GETTRK utility # program -# Default is "$EXECUTIL/gettrk" +# Default is "${EXECgfs}/gettrk" # BKGFREQ Frequency of background files for relocation # Default is "3" # SENDDBN String when set to "YES" alerts output files to $COMSP # NDATE String indicating executable path for NDATE utility program -# Default is "$EXECUTIL/ndate" +# Default is "${EXECgfs}/ndate" # # These do not have to be exported to this script. If they are, they will # be used by the script. If they are not, they will be skipped @@ -166,18 +145,18 @@ # # Modules and files referenced: # Herefile: RELOCATE_GES -# $USHRELO/tropcy_relocate_extrkr.sh -# $USHGETGES/getges.sh +# ${USHgfs}/tropcy_relocate_extrkr.sh +# ${USHgfs}/getges.sh # $NDATE (here and in child script -# $USHRELO/tropcy_relocate_extrkr.sh) +# ${USHgfs}/tropcy_relocate_extrkr.sh) # /usr/bin/poe # postmsg # $DATA/prep_step (here and in child script -# $USHRELO/tropcy_relocate_extrkr.sh) +# ${USHgfs}/tropcy_relocate_extrkr.sh) # $DATA/err_exit (here and in child script -# $USHRELO/tropcy_relocate_extrkr.sh) +# ${USHgfs}/tropcy_relocate_extrkr.sh) # $DATA/err_chk (here and in child script -# $USHRELO/tropcy_relocate_extrkr.sh) +# ${USHgfs}/tropcy_relocate_extrkr.sh) # NOTE: The last three scripts above are NOT REQUIRED utilities. # If $DATA/prep_step not found, a scaled down version of it is # executed in-line. If $DATA/err_exit or $DATA/err_chk are not @@ -188,7 +167,7 @@ # programs : # RELOCATE_MV_NVORTEX - executable $RELOX # T126 GRIB global land/sea mask: -# $FIXRELO/global_slmask.t126.grb +# ${FIXgfs}/am/global_slmask.t126.grb # SUPVIT - executable $SUPVX # GETTRK - executable $GETTX # @@ -204,7 +183,7 @@ # #### -source "$HOMEgfs/ush/preamble.sh" +source "${USHgfs}/preamble.sh" MACHINE=${MACHINE:-$(hostname -s | cut -c 1-3)} @@ -275,14 +254,6 @@ set_trace envir=${envir:-prod} -if [ $MACHINE != sgi ]; then - HOMEALL=${HOMEALL:-$OPSROOT} -else - HOMEALL=${HOMEALL:-/disk1/users/snake/prepobs} -fi - -HOMERELO=${HOMERELO:-${shared_global_home}} - envir_getges=${envir_getges:-$envir} if [ $modhr -eq 0 ]; then network_getges=${network_getges:-global} @@ -295,21 +266,12 @@ pgmout=${pgmout:-/dev/null} tstsp=${tstsp:-/tmp/null/} tmmark=${tmmark:-tm00} -USHRELO=${USHRELO:-${HOMERELO}/ush} -##USHGETGES=${USHGETGES:-/nwprod/util/ush} -##USHGETGES=${USHGETGES:-${HOMERELO}/ush} -USHGETGES=${USHGETGES:-${USHRELO}} - -EXECRELO=${EXECRELO:-${HOMERELO}/exec} - -FIXRELO=${FIXRELO:-${HOMERELO}/fix} - -RELOX=${RELOX:-$EXECRELO/relocate_mv_nvortex} +RELOX=${RELOX:-${EXECgfs}/relocate_mv_nvortex} export BKGFREQ=${BKGFREQ:-1} -SUPVX=${SUPVX:-$EXECRELO/supvit.x} -GETTX=${GETTX:-$EXECRELO/gettrk} +SUPVX=${SUPVX:-${EXECgfs}/supvit.x} +GETTX=${GETTX:-${EXECgfs}/gettrk} ################################################ # EXECUTE TROPICAL CYCLONE RELOCATION PROCESSING @@ -355,7 +317,7 @@ echo " relocation processing date/time" echo "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" echo set_trace - $USHGETGES/getges.sh -e $envir_getges -n $network_getges \ + ${USHgfs}/getges.sh -e $envir_getges -n $network_getges \ -v $CDATE10 -f $fhr -t tcvges tcvitals.m${fhr} set +x echo @@ -405,7 +367,7 @@ echo " relocation processing date/time" echo "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" echo set_trace - $USHGETGES/getges.sh -e $envir_getges -n $network_getges \ + ${USHgfs}/getges.sh -e $envir_getges -n $network_getges \ -v $CDATE10 -t $stype $sges errges=$? if test $errges -ne 0; then @@ -439,7 +401,7 @@ to center relocation date/time;" # ---------------------------------------------------------------------------- if [ $fhr = "0" ]; then - "${USHGETGES}/getges.sh" -e "${envir_getges}" -n "${network_getges}" -v "${CDATE10}" \ + "${USHgfs}/getges.sh" -e "${envir_getges}" -n "${network_getges}" -v "${CDATE10}" \ -t "${stype}" > "${COM_OBS}/${RUN}.${cycle}.sgesprep_pre-relocate_pathname.${tmmark}" cp "${COM_OBS}/${RUN}.${cycle}.sgesprep_pre-relocate_pathname.${tmmark}" \ "${COM_OBS}/${RUN}.${cycle}.sgesprep_pathname.${tmmark}" @@ -459,7 +421,7 @@ echo " relocation processing date/time" echo "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" echo set_trace - $USHGETGES/getges.sh -e $envir_getges -n $network_getges \ + ${USHgfs}/getges.sh -e $envir_getges -n $network_getges \ -v $CDATE10 -t $ptype $pges errges=$? if test $errges -ne 0; then @@ -541,7 +503,7 @@ else # $DATA/$RUN.$cycle.relocate.model_track.tm00 # -------------------------------------------- - $USHRELO/tropcy_relocate_extrkr.sh + ${USHgfs}/tropcy_relocate_extrkr.sh err=$? if [ $err -ne 0 ]; then @@ -550,12 +512,12 @@ else set +x echo - echo "$USHRELO/tropcy_relocate_extrkr.sh failed" + echo "${USHgfs}/tropcy_relocate_extrkr.sh failed" echo "ABNORMAL EXIT!!!!!!!!!!!" echo set_trace if [ -s $DATA/err_exit ]; then - $DATA/err_exit "Script $USHRELO/tropcy_relocate_extrkr.sh failed" + $DATA/err_exit "Script ${USHgfs}/tropcy_relocate_extrkr.sh failed" else exit 555 fi @@ -569,10 +531,10 @@ else rm fort.* fi - ln -sf $DATA/tcvitals.now1 fort.11 - ln -sf $DATA/model_track.all fort.30 - ln -sf $DATA/rel_inform1 fort.62 - ln -sf $DATA/tcvitals.relocate0 fort.65 + ${NLN} $DATA/tcvitals.now1 fort.11 + ${NLN} $DATA/model_track.all fort.30 + ${NLN} $DATA/rel_inform1 fort.62 + ${NLN} $DATA/tcvitals.relocate0 fort.65 i1=20 i2=53 @@ -586,8 +548,8 @@ else tpref=p$fhr fi - ln -sf $DATA/sg${tpref}prep fort.$i1 - ln -sf $DATA/sg${tpref}prep.relocate fort.$i2 + ${NLN} $DATA/sg${tpref}prep fort.$i1 + ${NLN} $DATA/sg${tpref}prep.relocate fort.$i2 i1=$((i1+1)) i2=$((i2+BKGFREQ)) diff --git a/ush/tropcy_relocate_extrkr.sh b/ush/tropcy_relocate_extrkr.sh index ede2318c4a..18e0851368 100755 --- a/ush/tropcy_relocate_extrkr.sh +++ b/ush/tropcy_relocate_extrkr.sh @@ -3,7 +3,7 @@ # This script is executed by the script tropcy_relocate.sh # -------------------------------------------------------- -source "$HOMEgfs/ush/preamble.sh" +source "${USHgfs}/preamble.sh" export machine=${machine:-ZEUS} export machine=$(echo $machine|tr '[a-z]' '[A-Z]') @@ -592,8 +592,8 @@ if [ -s fort.* ]; then rm fort.* fi -ln -s -f ${vdir}/vitals.${symd}${dishh} fort.31 -ln -s -f ${vdir}/vitals.upd.${cmodel}.${symd}${dishh} fort.51 +${NLN} ${vdir}/vitals.${symd}${dishh} fort.31 +${NLN} ${vdir}/vitals.upd.${cmodel}.${symd}${dishh} fort.51 ##$XLF_LINKSSH #if [ -z $XLF_LINKSSH ] ; then @@ -1528,19 +1528,19 @@ if [ -s fort.* ]; then rm fort.* fi -ln -s -f ${gribfile} fort.11 -ln -s -f ${vdir}/tmp.gfs.atcfunix.${symdh} fort.14 -ln -s -f ${vdir}/vitals.upd.${cmodel}.${symd}${dishh} fort.12 -ln -s -f ${ixfile} fort.31 -ln -s -f ${vdir}/trak.${cmodel}.all.${symdh} fort.61 -ln -s -f ${vdir}/trak.${cmodel}.atcf.${symdh} fort.62 -ln -s -f ${vdir}/trak.${cmodel}.radii.${symdh} fort.63 -ln -s -f ${vdir}/trak.${cmodel}.atcfunix.${symdh} fort.64 +${NLN} ${gribfile} fort.11 +${NLN} ${vdir}/tmp.gfs.atcfunix.${symdh} fort.14 +${NLN} ${vdir}/vitals.upd.${cmodel}.${symd}${dishh} fort.12 +${NLN} ${ixfile} fort.31 +${NLN} ${vdir}/trak.${cmodel}.all.${symdh} fort.61 +${NLN} ${vdir}/trak.${cmodel}.atcf.${symdh} fort.62 +${NLN} ${vdir}/trak.${cmodel}.radii.${symdh} fort.63 +${NLN} ${vdir}/trak.${cmodel}.atcfunix.${symdh} fort.64 if [ $BKGFREQ -eq 1 ]; then - ln -s -f ${FIXRELO}/${cmodel}.tracker_leadtimes_hrly fort.15 + ${NLN} ${FIXgfs}/am/${cmodel}.tracker_leadtimes_hrly fort.15 elif [ $BKGFREQ -eq 3 ]; then - ln -s -f ${FIXRELO}/${cmodel}.tracker_leadtimes fort.15 + ${NLN} ${FIXgfs}/am/${cmodel}.tracker_leadtimes fort.15 fi ##$XLF_LINKSSH diff --git a/ush/wafs_mkgbl.sh b/ush/wafs_mkgbl.sh new file mode 100755 index 0000000000..e6139bc9d3 --- /dev/null +++ b/ush/wafs_mkgbl.sh @@ -0,0 +1,152 @@ +# UTILITY SCRIPT NAME : wafs_mkgbl.sh +# AUTHOR : Mary Jacobs +# DATE WRITTEN : 11/06/96 +# +# Abstract: This utility script produces the GFS WAFS +# bulletins. +# +# Input: 2 arguments are passed to this script. +# 1st argument - Forecast Hour - format of 2I +# 2nd argument - In hours 12-30, the designator of +# a or b. +# +# Logic: If we are processing hours 12-30, we have the +# added variable of the a or b, and process +# accordingly. The other hours, the a or b is dropped. +# +echo "History: SEPT 1996 - First implementation of this utility script" +echo "History: AUG 1999 - Modified for implementation on IBM SP" +echo " - Allows users to run interactively" +# + +set -x +hour_list="$1" +sets_key=$2 +num=$# + +if test $num -ge 2 +then + echo " Appropriate number of arguments were passed" + set -x + if [ -z "$DATA" ] + then + export DATA=`pwd` + cd $DATA + setpdy.sh + . PDY + fi +else + echo "" + echo "Usage: wafs_mkgbl.sh \$hour [a|b]" + echo "" + exit 16 +fi + +echo " ------------------------------------------" +echo " BEGIN MAKING ${NET} WAFS PRODUCTS" +echo " ------------------------------------------" + +echo "Enter Make WAFS utility." + +for hour in $hour_list +do + ############################## + # Copy Input Field to $DATA + ############################## + + if test ! -f pgrbf${hour} + then +# cp $COMIN/${RUN}.${cycle}.pgrbf${hour} pgrbf${hour} + +# file name and forecast hour of GFS model data in Grib2 are 3 digits +# export fhr3=$hour +# if test $fhr3 -lt 100 +# then +# export fhr3="0$fhr3" +# fi + fhr3="$(printf "%03d" $(( 10#$hour )) )" + +# To solve Bugzilla #408: remove the dependency of grib1 files in gfs wafs job in next GFS upgrade +# Reason: It's not efficent if simply converting from grib2 to grib1 (costs 6 seconds with 415 records) +# Solution: Need to grep 'selected fields on selected levels' before CNVGRIB (costs 1 second with 92 records) + ${NLN} $COMIN/${RUN}.${cycle}.pgrb2.1p00.f$fhr3 pgrb2f${hour} + $WGRIB2 pgrb2f${hour} | grep -F -f $FIXgfs/grib_wafs.grb2to1.list | $WGRIB2 -i pgrb2f${hour} -grib pgrb2f${hour}.tmp +# on Cray, IOBUF_PARAMS has to used to speed up CNVGRIB +# export IOBUF_PARAMS='*:size=32M:count=4:verbose' + $CNVGRIB -g21 pgrb2f${hour}.tmp pgrbf${hour} +# unset IOBUF_PARAMS + fi + + # + # BAG - Put in fix on 20070925 to force the percision of U and V winds + # to default to 1 through the use of the grib_wafs.namelist file. + # + $COPYGB -g3 -i0 -N$FIXgfs/grib_wafs.namelist -x pgrbf${hour} tmp + mv tmp pgrbf${hour} + $GRBINDEX pgrbf${hour} pgrbif${hour} + + ############################## + # Process WAFS + ############################## + + if test $hour -ge '12' -a $hour -le '30' + then + sets=$sets_key + set +x + echo "We are processing the primary and secondary sets of hours." + echo "These sets are the a and b of hours 12-30." + set -x + else + # This is for hours 00/06 and 36-72. + unset sets + fi + + export pgm=wafs_makewafs + . prep_step + + export FORT11="pgrbf${hour}" + export FORT31="pgrbif${hour}" + export FORT51="xtrn.wfs${NET}${hour}${sets}" + export FORT53="com.wafs${hour}${sets}" + + startmsg + $EXECgfs/wafs_makewafs.x < $FIXgfs/grib_wfs${NET}${hour}${sets} >>$pgmout 2>errfile + export err=$?;err_chk + + + ############################## + # Post Files to PCOM + ############################## + + if test "$SENDCOM" = 'YES' + then + cp xtrn.wfs${NET}${hour}${sets} $PCOM/xtrn.wfs${NET}${cyc}${hour}${sets}.$jobsuffix +# cp com.wafs${hour}${sets} $PCOM/com.wafs${cyc}${hour}${sets}.$jobsuffix + +# if test "$SENDDBN_NTC" = 'YES' +# then +# if test "$NET" = 'gfs' +# then +# $DBNROOT/bin/dbn_alert MODEL GFS_WAFS $job \ +# $PCOM/com.wafs${cyc}${hour}${sets}.$jobsuffix +# $DBNROOT/bin/dbn_alert MODEL GFS_XWAFS $job \ +# $PCOM/xtrn.wfs${NET}${cyc}${hour}${sets}.$jobsuffix +# fi +# fi + fi + + ############################## + # Distribute Data + ############################## + + if [ "$SENDDBN_NTC" = 'YES' ] ; then + $DBNROOT/bin/dbn_alert GRIB_LOW $NET $job $PCOM/xtrn.wfs${NET}${cyc}${hour}${sets}.$jobsuffix + else + echo "xtrn.wfs${NET}${cyc}${hour}${sets}.$job file not posted to db_net." + fi + + echo "Wafs Processing $hour hour completed normally" + +done + +exit diff --git a/ush/wave_extractvars.sh b/ush/wave_extractvars.sh new file mode 100755 index 0000000000..32ee44986b --- /dev/null +++ b/ush/wave_extractvars.sh @@ -0,0 +1,34 @@ +#! /usr/bin/env bash + +################################################################################ +## UNIX Script Documentation Block +## Script name: wave_extractvars.sh +## Script description: Extracts variables from wave products +## and saves these variables in arcdir +####################### +# Main body starts here +####################### + +source "${USHgfs}/preamble.sh" + +subdata=${1} + +[[ -d "${subdata}" ]] || mkdir -p "${subdata}" + +for (( nh = FHOUT_WAV_EXTRACT; nh <= FHMAX_WAV; nh = nh + FHOUT_WAV_EXTRACT )); do + fnh=$(printf "%3.3d" "${nh}") + + infile=${COMIN_WAVE_GRID}/${RUN}wave.t${cyc}z.global.${wavres}.f${fnh}.grib2 + outfile=${subdata}/${RUN}wave.t${cyc}z.global.${wavres}.f${fnh}.grib2 + rm -f "${outfile}" # Remove outfile if it already exists before extraction + + if [[ -f "${infile}" ]]; then # Check if input file exists before extraction + # shellcheck disable=SC2312 + ${WGRIB2} "${infile}" | grep -F -f "${varlist_wav}" | ${WGRIB2} -i "${infile}" -append -grib "${outfile}" + else + echo "WARNING: ${infile} does not exist." + fi + copy_to_comout "${outfile}" "${ARC_RFCST_PROD_WAV}" +done # nh + +exit 0 diff --git a/ush/wave_grib2_sbs.sh b/ush/wave_grib2_sbs.sh index af28760269..99f89f3f37 100755 --- a/ush/wave_grib2_sbs.sh +++ b/ush/wave_grib2_sbs.sh @@ -25,7 +25,7 @@ # --------------------------------------------------------------------------- # # 0. Preparations -source "${HOMEgfs}/ush/preamble.sh" +source "${USHgfs}/preamble.sh" # 0.a Basic modes of operation @@ -72,7 +72,7 @@ if [[ -n ${waveMEMB} ]]; then ENSTAG=".${membTAG}${waveMEMB}" ; fi outfile="${WAV_MOD_TAG}.${cycle}${ENSTAG}.${grdnam}.${grdres}.f${FH3}.grib2" # Only create file if not present in COM -if [[ ! -s "${COM_WAVE_GRID}/${outfile}.idx" ]]; then +if [[ ! -s "${COMOUT_WAVE_GRID}/${outfile}.idx" ]]; then set +x echo ' ' @@ -82,8 +82,8 @@ if [[ ! -s "${COM_WAVE_GRID}/${outfile}.idx" ]]; then echo " Model ID : $WAV_MOD_TAG" set_trace - if [[ -z "${PDY}" ]] || [[ -z ${cyc} ]] || [[ -z "${cycle}" ]] || [[ -z "${EXECwave}" ]] || \ - [[ -z "${COM_WAVE_GRID}" ]] || [[ -z "${WAV_MOD_TAG}" ]] || [[ -z "${gribflags}" ]] || \ + if [[ -z "${PDY}" ]] || [[ -z ${cyc} ]] || [[ -z "${cycle}" ]] || [[ -z "${EXECgfs}" ]] || \ + [[ -z "${COMOUT_WAVE_GRID}" ]] || [[ -z "${WAV_MOD_TAG}" ]] || [[ -z "${gribflags}" ]] || \ [[ -z "${GRIDNR}" ]] || [[ -z "${MODNR}" ]] || \ [[ -z "${SENDDBN}" ]]; then set +x @@ -110,8 +110,8 @@ if [[ ! -s "${COM_WAVE_GRID}/${outfile}.idx" ]]; then # 0.e Links to working directory - ln -s "${DATA}/mod_def.${grdID}" "mod_def.ww3" - ln -s "${DATA}/output_${ymdh}0000/out_grd.${grdID}" "out_grd.ww3" + ${NLN} "${DATA}/mod_def.${grdID}" "mod_def.ww3" + ${NLN} "${DATA}/output_${ymdh}0000/out_grd.${grdID}" "out_grd.ww3" # --------------------------------------------------------------------------- # # 1. Generate GRIB file with all data @@ -138,11 +138,11 @@ if [[ ! -s "${COM_WAVE_GRID}/${outfile}.idx" ]]; then set +x echo " Run ww3_grib2" - echo " Executing ${EXECwave}/ww3_grib" + echo " Executing ${EXECgfs}/ww3_grib" set_trace export pgm=ww3_grib;. prep_step - "${EXECwave}/ww3_grib" > "grib2_${grdnam}_${FH3}.out" 2>&1 + "${EXECgfs}/ww3_grib" > "grib2_${grdnam}_${FH3}.out" 2>&1 export err=$?;err_chk if [ ! -s gribfile ]; then @@ -157,11 +157,11 @@ if [[ ! -s "${COM_WAVE_GRID}/${outfile}.idx" ]]; then fi if (( fhr > 0 )); then - ${WGRIB2} gribfile -set_date "${PDY}${cyc}" -set_ftime "${fhr} hour fcst" -grib "${COM_WAVE_GRID}/${outfile}" + ${WGRIB2} gribfile -set_date "${PDY}${cyc}" -set_ftime "${fhr} hour fcst" -grib "${COMOUT_WAVE_GRID}/${outfile}" err=$? else ${WGRIB2} gribfile -set_date "${PDY}${cyc}" -set_ftime "${fhr} hour fcst" \ - -set table_1.4 1 -set table_1.2 1 -grib "${COM_WAVE_GRID}/${outfile}" + -set table_1.4 1 -set table_1.2 1 -grib "${COMOUT_WAVE_GRID}/${outfile}" err=$? fi @@ -177,7 +177,7 @@ if [[ ! -s "${COM_WAVE_GRID}/${outfile}.idx" ]]; then fi # Create index - ${WGRIB2} -s "${COM_WAVE_GRID}/${outfile}" > "${COM_WAVE_GRID}/${outfile}.idx" + ${WGRIB2} -s "${COMOUT_WAVE_GRID}/${outfile}" > "${COMOUT_WAVE_GRID}/${outfile}.idx" # Create grib2 subgrid is this is the source grid if [[ "${grdID}" = "${WAV_SUBGRBSRC}" ]]; then @@ -186,14 +186,14 @@ if [[ ! -s "${COM_WAVE_GRID}/${outfile}.idx" ]]; then subgrbnam=$(echo ${!subgrb} | cut -d " " -f 21) subgrbres=$(echo ${!subgrb} | cut -d " " -f 22) subfnam="${WAV_MOD_TAG}.${cycle}${ENSTAG}.${subgrbnam}.${subgrbres}.f${FH3}.grib2" - ${COPYGB2} -g "${subgrbref}" -i0 -x "${COM_WAVE_GRID}/${outfile}" "${COM_WAVE_GRID}/${subfnam}" - ${WGRIB2} -s "${COM_WAVE_GRID}/${subfnam}" > "${COM_WAVE_GRID}/${subfnam}.idx" + ${COPYGB2} -g "${subgrbref}" -i0 -x "${COMOUT_WAVE_GRID}/${outfile}" "${COMOUT_WAVE_GRID}/${subfnam}" + ${WGRIB2} -s "${COMOUT_WAVE_GRID}/${subfnam}" > "${COMOUT_WAVE_GRID}/${subfnam}.idx" done fi # 1.e Save in /com - if [[ ! -s "${COM_WAVE_GRID}/${outfile}" ]]; then + if [[ ! -s "${COMOUT_WAVE_GRID}/${outfile}" ]]; then set +x echo ' ' echo '********************************************* ' @@ -205,7 +205,7 @@ if [[ ! -s "${COM_WAVE_GRID}/${outfile}.idx" ]]; then set_trace exit 4 fi - if [[ ! -s "${COM_WAVE_GRID}/${outfile}.idx" ]]; then + if [[ ! -s "${COMOUT_WAVE_GRID}/${outfile}.idx" ]]; then set +x echo ' ' echo '*************************************************** ' @@ -220,11 +220,11 @@ if [[ ! -s "${COM_WAVE_GRID}/${outfile}.idx" ]]; then if [[ "${SENDDBN}" = 'YES' ]] && [[ ${outfile} != *global.0p50* ]]; then set +x - echo " Alerting GRIB file as ${COM_WAVE_GRID}/${outfile}" - echo " Alerting GRIB index file as ${COM_WAVE_GRID}/${outfile}.idx" + echo " Alerting GRIB file as ${COMOUT_WAVE_GRID}/${outfile}" + echo " Alerting GRIB index file as ${COMOUT_WAVE_GRID}/${outfile}.idx" set_trace - "${DBNROOT}/bin/dbn_alert" MODEL "${alertName}_WAVE_GB2" "${job}" "${COM_WAVE_GRID}/${outfile}" - "${DBNROOT}/bin/dbn_alert" MODEL "${alertName}_WAVE_GB2_WIDX" "${job}" "${COM_WAVE_GRID}/${outfile}.idx" + "${DBNROOT}/bin/dbn_alert" MODEL "${alertName}_WAVE_GB2" "${job}" "${COMOUT_WAVE_GRID}/${outfile}" + "${DBNROOT}/bin/dbn_alert" MODEL "${alertName}_WAVE_GB2_WIDX" "${job}" "${COMOUT_WAVE_GRID}/${outfile}.idx" else echo "${outfile} is global.0p50 or SENDDBN is NO, no alert sent" fi @@ -245,7 +245,7 @@ if [[ ! -s "${COM_WAVE_GRID}/${outfile}.idx" ]]; then else set +x echo ' ' - echo " File ${COM_WAVE_GRID}/${outfile} found, skipping generation process" + echo " File ${COMOUT_WAVE_GRID}/${outfile} found, skipping generation process" echo ' ' set_trace fi diff --git a/ush/wave_grid_interp_sbs.sh b/ush/wave_grid_interp_sbs.sh index c11a75f89d..31b7808c16 100755 --- a/ush/wave_grid_interp_sbs.sh +++ b/ush/wave_grid_interp_sbs.sh @@ -25,7 +25,7 @@ # --------------------------------------------------------------------------- # # 0. Preparations -source "$HOMEgfs/ush/preamble.sh" +source "${USHgfs}/preamble.sh" # 0.a Basic modes of operation @@ -65,8 +65,8 @@ source "$HOMEgfs/ush/preamble.sh" echo " Model ID : $WAV_MOD_TAG" set_trace - if [[ -z "${PDY}" ]] || [[ -z "${cyc}" ]] || [[ -z "${cycle}" ]] || [[ -z "${EXECwave}" ]] || \ - [[ -z "${COM_WAVE_PREP}" ]] || [[ -z "${WAV_MOD_TAG}" ]] || [[ -z "${SENDDBN}" ]] || \ + if [[ -z "${PDY}" ]] || [[ -z "${cyc}" ]] || [[ -z "${cycle}" ]] || [[ -z "${EXECgfs}" ]] || \ + [[ -z "${COMOUT_WAVE_PREP}" ]] || [[ -z "${WAV_MOD_TAG}" ]] || [[ -z "${SENDDBN}" ]] || \ [ -z "${waveGRD}" ] then set +x @@ -75,7 +75,7 @@ source "$HOMEgfs/ush/preamble.sh" echo '*** EXPORTED VARIABLES IN postprocessor NOT SET ***' echo '***************************************************' echo ' ' - echo "${PDY}${cyc} ${cycle} ${EXECwave} ${COM_WAVE_PREP} ${WAV_MOD_TAG} ${SENDDBN} ${waveGRD}" + echo "${PDY}${cyc} ${cycle} ${EXECgfs} ${COMOUT_WAVE_PREP} ${WAV_MOD_TAG} ${SENDDBN} ${waveGRD}" set_trace exit 1 fi @@ -85,18 +85,16 @@ source "$HOMEgfs/ush/preamble.sh" rm -f ${DATA}/output_${ymdh}0000/out_grd.$grdID if [ ! -f ${DATA}/${grdID}_interp.inp.tmpl ]; then - cp $PARMwave/${grdID}_interp.inp.tmpl ${DATA} + cp "${PARMgfs}/wave/${grdID}_interp.inp.tmpl" "${DATA}/${grdID}_interp.inp.tmpl" fi - ln -sf ${DATA}/${grdID}_interp.inp.tmpl . + ${NLN} "${DATA}/${grdID}_interp.inp.tmpl" "${grdID}_interp.inp.tmpl" - for ID in $waveGRD - do - ln -sf ${DATA}/output_${ymdh}0000/out_grd.$ID . + for ID in ${waveGRD}; do + ${NLN} "${DATA}/output_${ymdh}0000/out_grd.${ID}" "out_grd.${ID}" done - for ID in $waveGRD $grdID - do - ln -sf ${DATA}/mod_def.$ID . + for ID in ${waveGRD} ${grdID}; do + ${NLN} "${DATA}/mod_def.${ID}" "mod_def.${ID}" done # --------------------------------------------------------------------------- # @@ -113,42 +111,42 @@ source "$HOMEgfs/ush/preamble.sh" wht_OK='no' if [ ! -f ${DATA}/ww3_gint.WHTGRIDINT.bin.${grdID} ]; then - if [ -f $FIXwave/ww3_gint.WHTGRIDINT.bin.${grdID} ] + if [ -f ${FIXgfs}/wave/ww3_gint.WHTGRIDINT.bin.${grdID} ] then set +x echo ' ' - echo " Copying $FIXwave/ww3_gint.WHTGRIDINT.bin.${grdID} " + echo " Copying ${FIXgfs}/wave/ww3_gint.WHTGRIDINT.bin.${grdID} " set_trace - cp $FIXwave/ww3_gint.WHTGRIDINT.bin.${grdID} ${DATA} + cp ${FIXgfs}/wave/ww3_gint.WHTGRIDINT.bin.${grdID} ${DATA} wht_OK='yes' else set +x echo ' ' - echo " Not found: $FIXwave/ww3_gint.WHTGRIDINT.bin.${grdID} " + echo " Not found: ${FIXgfs}/wave/ww3_gint.WHTGRIDINT.bin.${grdID} " fi fi # Check and link weights file if [ -f ${DATA}/ww3_gint.WHTGRIDINT.bin.${grdID} ] then - ln -s ${DATA}/ww3_gint.WHTGRIDINT.bin.${grdID} ./WHTGRIDINT.bin + ${NLN} ${DATA}/ww3_gint.WHTGRIDINT.bin.${grdID} ./WHTGRIDINT.bin fi # 1.b Run interpolation code set +x echo " Run ww3_gint - echo " Executing $EXECwave/ww3_gint + echo " Executing ${EXECgfs}/ww3_gint set_trace export pgm=ww3_gint;. prep_step - $EXECwave/ww3_gint 1> gint.${grdID}.out 2>&1 + ${EXECgfs}/ww3_gint 1> gint.${grdID}.out 2>&1 export err=$?;err_chk # Write interpolation file to main TEMP dir area if not there yet if [ "wht_OK" = 'no' ] then cp -f ./WHTGRIDINT.bin ${DATA}/ww3_gint.WHTGRIDINT.bin.${grdID} - cp -f ./WHTGRIDINT.bin ${FIXwave}/ww3_gint.WHTGRIDINT.bin.${grdID} + cp -f ./WHTGRIDINT.bin ${FIXgfs}/wave/ww3_gint.WHTGRIDINT.bin.${grdID} fi @@ -173,9 +171,9 @@ source "$HOMEgfs/ush/preamble.sh" # 1.c Save in /com set +x - echo " Saving GRID file as ${COM_WAVE_PREP}/${WAV_MOD_TAG}.out_grd.${grdID}.${PDY}${cyc}" + echo " Saving GRID file as ${COMOUT_WAVE_PREP}/${WAV_MOD_TAG}.out_grd.${grdID}.${PDY}${cyc}" set_trace - cp "${DATA}/output_${ymdh}0000/out_grd.${grdID}" "${COM_WAVE_PREP}/${WAV_MOD_TAG}.out_grd.${grdID}.${PDY}${cyc}" + cp "${DATA}/output_${ymdh}0000/out_grd.${grdID}" "${COMOUT_WAVE_PREP}/${WAV_MOD_TAG}.out_grd.${grdID}.${PDY}${cyc}" # if [ "$SENDDBN" = 'YES' ] # then diff --git a/ush/wave_grid_moddef.sh b/ush/wave_grid_moddef.sh index 5b1b212a16..1e8c44054a 100755 --- a/ush/wave_grid_moddef.sh +++ b/ush/wave_grid_moddef.sh @@ -20,7 +20,7 @@ # --------------------------------------------------------------------------- # # 0. Preparations -source "$HOMEgfs/ush/preamble.sh" +source "${USHgfs}/preamble.sh" # 0.a Basic modes of operation @@ -59,7 +59,7 @@ source "$HOMEgfs/ush/preamble.sh" # 0.c Define directories and the search path. # The tested variables should be exported by the postprocessor script. - if [ -z "$grdID" ] || [ -z "$EXECwave" ] || [ -z "$wave_sys_ver" ] + if [ -z "$grdID" ] || [ -z "${EXECgfs}" ] then set +x echo ' ' @@ -77,14 +77,22 @@ source "$HOMEgfs/ush/preamble.sh" set +x echo ' ' echo ' Creating mod_def file ...' - echo " Executing $EXECwave/ww3_grid" + echo " Executing ${EXECgfs}/ww3_grid" echo ' ' set_trace rm -f ww3_grid.inp - ln -sf ../ww3_grid.inp.$grdID ww3_grid.inp + ${NLN} ../ww3_grid.inp.$grdID ww3_grid.inp + + if [ -f ../${grdID}.msh ] + then + rm -f ${grdID}.msh + ${NLN} ../${grdID}.msh ${grdID}.msh + fi + + - $EXECwave/ww3_grid 1> grid_${grdID}.out 2>&1 + "${EXECgfs}/ww3_grid" 1> "grid_${grdID}.out" 2>&1 err=$? if [ "$err" != '0' ] @@ -99,10 +107,10 @@ source "$HOMEgfs/ush/preamble.sh" exit 3 fi - if [ -f mod_def.ww3 ] + if [[ -f mod_def.ww3 ]] then - cp mod_def.ww3 "${COM_WAVE_PREP}/${RUN}wave.mod_def.${grdID}" - mv mod_def.ww3 ../mod_def.$grdID + cp mod_def.ww3 "${COMOUT_WAVE_PREP}/${RUN}wave.mod_def.${grdID}" + mv mod_def.ww3 "../mod_def.${grdID}" else set +x echo ' ' @@ -118,6 +126,6 @@ source "$HOMEgfs/ush/preamble.sh" # 3. Clean up cd .. -rm -rf moddef_$grdID +rm -rf "moddef_${grdID}" # End of ww3_mod_def.sh ------------------------------------------------- # diff --git a/ush/wave_outp_cat.sh b/ush/wave_outp_cat.sh index f4bf6b2294..6ce3ce06cf 100755 --- a/ush/wave_outp_cat.sh +++ b/ush/wave_outp_cat.sh @@ -21,7 +21,7 @@ # --------------------------------------------------------------------------- # # 0. Preparations -source "$HOMEgfs/ush/preamble.sh" +source "${USHgfs}/preamble.sh" # 0.a Basic modes of operation bloc=$1 diff --git a/ush/wave_outp_spec.sh b/ush/wave_outp_spec.sh index 5acc0f95ab..37accbae49 100755 --- a/ush/wave_outp_spec.sh +++ b/ush/wave_outp_spec.sh @@ -22,7 +22,7 @@ # --------------------------------------------------------------------------- # # 0. Preparations -source "$HOMEgfs/ush/preamble.sh" +source "${USHgfs}/preamble.sh" # 0.a Basic modes of operation bloc=$1 @@ -31,6 +31,7 @@ source "$HOMEgfs/ush/preamble.sh" workdir=$4 YMDHE=$($NDATE $FHMAX_WAV_PNT $CDATE) + model_start_date=$(${NDATE} ${OFFSET_START_HOUR} "${PDY}${cyc}") cd $workdir @@ -73,21 +74,7 @@ source "$HOMEgfs/ush/preamble.sh" exit 1 else buoy=$bloc - grep $buoy ${DATA}/buoy_log.ww3 > tmp_list.loc - while read line - do - buoy_name=$(echo $line | awk '{print $2}') - if [ $buoy = $buoy_name ] - then - point=$(echo $line | awk '{ print $1 }') - set +x - echo " Location ID/# : $buoy (${point})" - echo " Spectral output start time : $ymdh " - echo ' ' - set_trace - break - fi - done < tmp_list.loc + point=$(awk "{if (\$2 == \"${buoy}\"){print \$1; exit} }" "${DATA}/buoy_log.ww3") if [ -z "$point" ] then set +x @@ -97,6 +84,11 @@ source "$HOMEgfs/ush/preamble.sh" echo ' ' set_trace exit 2 + else + set +x + echo " Location ID/# : $buoy (${point})" + echo " Spectral output start time : $ymdh " + echo ' ' fi fi @@ -104,7 +96,7 @@ source "$HOMEgfs/ush/preamble.sh" # 0.c Define directories and the search path. # The tested variables should be exported by the postprocessor script. - if [ -z "$CDATE" ] || [ -z "$dtspec" ] || [ -z "$EXECwave" ] || \ + if [ -z "$CDATE" ] || [ -z "$dtspec" ] || [ -z "${EXECgfs}" ] || \ [ -z "$WAV_MOD_TAG" ] || [ -z "${STA_DIR}" ] then set +x @@ -135,8 +127,8 @@ source "$HOMEgfs/ush/preamble.sh" # 0.f Links to mother directory - ln -s ${DATA}/output_${ymdh}0000/mod_def.${waveuoutpGRD} ./mod_def.ww3 - ln -s ${DATA}/output_${ymdh}0000/out_pnt.${waveuoutpGRD} ./out_pnt.ww3 + ${NLN} ${DATA}/output_${ymdh}0000/mod_def.${waveuoutpGRD} ./mod_def.ww3 + ${NLN} ${DATA}/output_${ymdh}0000/out_pnt.${waveuoutpGRD} ./out_pnt.ww3 # --------------------------------------------------------------------------- # # 2. Generate spectral data file @@ -170,11 +162,11 @@ source "$HOMEgfs/ush/preamble.sh" # 2.b Run the postprocessor set +x - echo " Executing $EXECwave/ww3_outp" + echo " Executing ${EXECgfs}/ww3_outp" set_trace export pgm=ww3_outp;. prep_step - $EXECwave/ww3_outp 1> outp_${specdir}_${buoy}.out 2>&1 + ${EXECgfs}/ww3_outp 1> outp_${specdir}_${buoy}.out 2>&1 export err=$?;err_chk @@ -196,31 +188,31 @@ source "$HOMEgfs/ush/preamble.sh" if [ -f $outfile ] then - if [ "${ymdh}" = "${CDATE}" ] + if [ "${ymdh}" = "${model_start_date}" ] then if [ "$specdir" = "bull" ] then - cat $outfile | sed -e '9,$d' >> ${STA_DIR}/${specdir}fhr/$WAV_MOD_TAG.${ymdh}.$buoy.bull - cat $coutfile | sed -e '8,$d' >> ${STA_DIR}/c${specdir}fhr/$WAV_MOD_TAG.${ymdh}.$buoy.cbull + sed '9,$d' "${outfile}" >> "${STA_DIR}/${specdir}fhr/${WAV_MOD_TAG}.${ymdh}.${buoy}.bull" + sed '8,$d' "${coutfile}" >> "${STA_DIR}/c${specdir}fhr/${WAV_MOD_TAG}.${ymdh}.${buoy}.cbull" else - cat $outfile >> ${STA_DIR}/${specdir}fhr/$WAV_MOD_TAG.${ymdh}.$buoy.spec + cat $outfile >> "${STA_DIR}/${specdir}fhr/${WAV_MOD_TAG}.${ymdh}.${buoy}.spec" fi elif [ "${ymdh}" = "${YMDHE}" ] then if [ "$specdir" = "bull" ] then - cat $outfile | sed -e '1,7d' >> ${STA_DIR}/${specdir}fhr/$WAV_MOD_TAG.${ymdh}.$buoy.bull - cat $coutfile | sed -e '1,6d' >> ${STA_DIR}/c${specdir}fhr/$WAV_MOD_TAG.${ymdh}.$buoy.cbull + sed '1,7d' "${outfile}" >> "${STA_DIR}/${specdir}fhr/${WAV_MOD_TAG}.${ymdh}.${buoy}.bull" + sed '1,6d' "${coutfile}" >> "${STA_DIR}/c${specdir}fhr/${WAV_MOD_TAG}.${ymdh}.${buoy}.cbull" else - cat $outfile | sed -n "/^${YMD} ${HMS}$/,\$p" >> ${STA_DIR}/${specdir}fhr/$WAV_MOD_TAG.${ymdh}.$buoy.spec + sed -n "/^${YMD} ${HMS}$/,\$p" "${outfile}" >> "${STA_DIR}/${specdir}fhr/${WAV_MOD_TAG}.${ymdh}.${buoy}.spec" fi else if [ "$specdir" = "bull" ] then - cat $outfile | sed -e '1,7d' | sed -e '2,$d' >> ${STA_DIR}/${specdir}fhr/$WAV_MOD_TAG.${ymdh}.$buoy.bull - cat $coutfile | sed -e '1,6d' | sed -e '2,$d' >> ${STA_DIR}/c${specdir}fhr/$WAV_MOD_TAG.${ymdh}.$buoy.cbull + sed '8q;d' "${outfile}" >> "${STA_DIR}/${specdir}fhr/${WAV_MOD_TAG}.${ymdh}.${buoy}.bull" + sed '7q;d' "${coutfile}" >> "${STA_DIR}/c${specdir}fhr/${WAV_MOD_TAG}.${ymdh}.${buoy}.cbull" else - cat $outfile | sed -n "/^${YMD} ${HMS}$/,\$p" >> ${STA_DIR}/${specdir}fhr/$WAV_MOD_TAG.${ymdh}.$buoy.spec + sed -n "/^${YMD} ${HMS}$/,\$p" "${outfile}" >> "${STA_DIR}/${specdir}fhr/${WAV_MOD_TAG}.${ymdh}.${buoy}.spec" fi fi else @@ -237,6 +229,6 @@ source "$HOMEgfs/ush/preamble.sh" # 3.b Clean up the rest cd .. -rm -rf ${specdir}_${bloc} +rm -rf "${specdir}_${bloc}" # End of ww3_outp_spec.sh ---------------------------------------------------- # diff --git a/ush/wave_prnc_cur.sh b/ush/wave_prnc_cur.sh index 6b1ab19db2..927710c581 100755 --- a/ush/wave_prnc_cur.sh +++ b/ush/wave_prnc_cur.sh @@ -22,7 +22,7 @@ ################################################################################ # -source "$HOMEgfs/ush/preamble.sh" +source "${USHgfs}/preamble.sh" ymdh_rtofs=$1 curfile=$2 @@ -46,7 +46,7 @@ mv -f "cur_temp3.nc" "cur_uv_${PDY}_${fext}${fh3}_flat.nc" # Convert to regular lat lon file # If weights need to be regenerated due to CDO ver change, use: # $CDO genbil,r4320x2160 rtofs_glo_2ds_f000_3hrly_prog.nc weights.nc -cp ${FIXwave}/weights_rtofs_to_r4320x2160.nc ./weights.nc +cp ${FIXgfs}/wave/weights_rtofs_to_r4320x2160.nc ./weights.nc # Interpolate to regular 5 min grid ${CDO} remap,r4320x2160,weights.nc "cur_uv_${PDY}_${fext}${fh3}_flat.nc" "cur_5min_01.nc" @@ -65,17 +65,17 @@ rm -f cur_temp[123].nc cur_5min_??.nc "cur_glo_uv_${PDY}_${fext}${fh3}.nc weight if [ ${flagfirst} = "T" ] then - sed -e "s/HDRFL/T/g" ${PARMwave}/ww3_prnc.cur.${WAVECUR_FID}.inp.tmpl > ww3_prnc.inp + sed -e "s/HDRFL/T/g" ${PARMgfs}/wave/ww3_prnc.cur.${WAVECUR_FID}.inp.tmpl > ww3_prnc.inp else - sed -e "s/HDRFL/F/g" ${PARMwave}/ww3_prnc.cur.${WAVECUR_FID}.inp.tmpl > ww3_prnc.inp + sed -e "s/HDRFL/F/g" ${PARMgfs}/wave/ww3_prnc.cur.${WAVECUR_FID}.inp.tmpl > ww3_prnc.inp fi rm -f cur.nc -ln -s "cur_glo_uv_${PDY}_${fext}${fh3}_5min.nc" "cur.nc" -ln -s "${DATA}/mod_def.${WAVECUR_FID}" ./mod_def.ww3 +${NLN} "cur_glo_uv_${PDY}_${fext}${fh3}_5min.nc" "cur.nc" +${NLN} "${DATA}/mod_def.${WAVECUR_FID}" ./mod_def.ww3 export pgm=ww3_prnc;. prep_step -$EXECwave/ww3_prnc 1> prnc_${WAVECUR_FID}_${ymdh_rtofs}.out 2>&1 +${EXECgfs}/ww3_prnc 1> prnc_${WAVECUR_FID}_${ymdh_rtofs}.out 2>&1 export err=$?; err_chk diff --git a/ush/wave_prnc_ice.sh b/ush/wave_prnc_ice.sh index 5ec1d7fc2e..be089c30bd 100755 --- a/ush/wave_prnc_ice.sh +++ b/ush/wave_prnc_ice.sh @@ -27,7 +27,7 @@ # --------------------------------------------------------------------------- # # 0. Preparations -source "$HOMEgfs/ush/preamble.sh" +source "${USHgfs}/preamble.sh" # 0.a Basic modes of operation @@ -36,7 +36,7 @@ source "$HOMEgfs/ush/preamble.sh" rm -rf ice mkdir ice cd ice - ln -s ${DATA}/postmsg . + ${NLN} "${DATA}/postmsg" postmsg # 0.b Define directories and the search path. # The tested variables should be exported by the postprocessor script. @@ -55,8 +55,8 @@ source "$HOMEgfs/ush/preamble.sh" echo "Making ice fields." if [[ -z "${YMDH}" ]] || [[ -z "${cycle}" ]] || \ - [[ -z "${COM_WAVE_PREP}" ]] || [[ -z "${FIXwave}" ]] || [[ -z "${EXECwave}" ]] || \ - [[ -z "${WAV_MOD_TAG}" ]] || [[ -z "${WAVEICE_FID}" ]] || [[ -z "${COM_OBS}" ]]; then + [[ -z "${COMOUT_WAVE_PREP}" ]] || [[ -z "${FIXgfs}" ]] || [[ -z "${EXECgfs}" ]] || \ + [[ -z "${WAV_MOD_TAG}" ]] || [[ -z "${WAVEICE_FID}" ]] || [[ -z "${COMIN_OBS}" ]]; then set +x echo ' ' @@ -71,13 +71,13 @@ source "$HOMEgfs/ush/preamble.sh" # 0.c Links to working directory - ln -s ${DATA}/mod_def.$WAVEICE_FID mod_def.ww3 + ${NLN} ${DATA}/mod_def.$WAVEICE_FID mod_def.ww3 # --------------------------------------------------------------------------- # # 1. Get the necessary files # 1.a Copy the ice data file - file=${COM_OBS}/${WAVICEFILE} + file=${COMIN_OBS}/${WAVICEFILE} if [ -f $file ] then @@ -144,7 +144,7 @@ source "$HOMEgfs/ush/preamble.sh" export pgm=ww3_prnc;. prep_step - $EXECwave/ww3_prnc 1> prnc_${WAVEICE_FID}_${cycle}.out 2>&1 + ${EXECgfs}/ww3_prnc 1> prnc_${WAVEICE_FID}_${cycle}.out 2>&1 export err=$?; err_chk if [ "$err" != '0' ] @@ -178,9 +178,9 @@ source "$HOMEgfs/ush/preamble.sh" fi set +x - echo " Saving ice.ww3 as ${COM_WAVE_PREP}/${icefile}" + echo " Saving ice.ww3 as ${COMOUT_WAVE_PREP}/${icefile}" set_trace - cp ice.ww3 "${COM_WAVE_PREP}/${icefile}" + cp ice.ww3 "${COMOUT_WAVE_PREP}/${icefile}" rm -f ice.ww3 # --------------------------------------------------------------------------- # diff --git a/ush/wave_tar.sh b/ush/wave_tar.sh index 1a8d6d6cc5..f82849854f 100755 --- a/ush/wave_tar.sh +++ b/ush/wave_tar.sh @@ -25,11 +25,11 @@ # --------------------------------------------------------------------------- # # 0. Preparations -source "$HOMEgfs/ush/preamble.sh" +source "${USHgfs}/preamble.sh" # 0.a Basic modes of operation - cd $DATA + cd "${DATA}" echo "Making TAR FILE" alertName=$(echo $RUN|tr [a-z] [A-Z]) @@ -47,7 +47,7 @@ source "$HOMEgfs/ush/preamble.sh" # 0.b Check if type set - if [ "$#" -lt '3' ] + if [[ "$#" -lt '3' ]] then set +x echo ' ' @@ -64,9 +64,9 @@ source "$HOMEgfs/ush/preamble.sh" fi filext=$type - if [ "$type" = "ibp" ]; then filext='spec'; fi - if [ "$type" = "ibpbull" ]; then filext='bull'; fi - if [ "$type" = "ibpcbull" ]; then filext='cbull'; fi + if [[ "$type" = "ibp" ]]; then filext='spec'; fi + if [[ "$type" = "ibpbull" ]]; then filext='bull'; fi + if [[ "$type" = "ibpcbull" ]]; then filext='cbull'; fi rm -rf TAR_${filext}_$ID @@ -76,7 +76,7 @@ source "$HOMEgfs/ush/preamble.sh" # 0.c Define directories and the search path. # The tested variables should be exported by the postprocessor script. - if [[ -z "${cycle}" ]] || [[ -z "${COM_WAVE_STATION}" ]] || [[ -z "${WAV_MOD_TAG}" ]] || \ + if [[ -z "${cycle}" ]] || [[ -z "${COMOUT_WAVE_STATION}" ]] || [[ -z "${WAV_MOD_TAG}" ]] || \ [[ -z "${SENDDBN}" ]] || [[ -z "${STA_DIR}" ]]; then set +x echo ' ' @@ -88,7 +88,7 @@ source "$HOMEgfs/ush/preamble.sh" exit 2 fi - cd ${STA_DIR}/${filext} + cd "${STA_DIR}/${filext}" # --------------------------------------------------------------------------- # # 2. Generate tar file (spectral files are compressed) @@ -98,21 +98,27 @@ source "$HOMEgfs/ush/preamble.sh" echo ' Making tar file ...' set_trace - count=0 countMAX=5 tardone='no' - - while [ "$count" -lt "$countMAX" ] && [ "$tardone" = 'no' ] + sleep_interval=10 + + while [[ "${tardone}" = "no" ]] do nf=$(ls | awk '/'$ID.*.$filext'/ {a++} END {print a}') nbm2=$(( $nb - 2 )) - if [ $nf -ge $nbm2 ] - then - tar -cf $ID.$cycle.${type}_tar ./$ID.*.$filext + if [[ "${nf}" -ge "${nbm2}" ]] + then + + tar -cf "${ID}.${cycle}.${type}_tar" ./${ID}.*.${filext} exit=$? + filename="${ID}.${cycle}.${type}_tar" + if ! wait_for_file "${filename}" "${sleep_interval}" "${countMAX}" ; then + echo "FATAL ERROR: File ${filename} not found after waiting $(( sleep_interval * (countMAX + 1) )) secs" + exit 3 + fi - if [ "$exit" != '0' ] + if [[ "${exit}" != '0' ]] then set +x echo ' ' @@ -124,21 +130,15 @@ source "$HOMEgfs/ush/preamble.sh" exit 3 fi - if [ -f "$ID.$cycle.${type}_tar" ] + if [[ -f "${ID}.${cycle}.${type}_tar" ]] then tardone='yes' fi - else - set +x - echo ' All files not found for tar. Sleeping 10 seconds and trying again ..' - set_trace - sleep 10 - count=$(expr $count + 1) fi done - if [ "$tardone" = 'no' ] + if [[ "${tardone}" = 'no' ]] then set +x echo ' ' @@ -150,15 +150,15 @@ source "$HOMEgfs/ush/preamble.sh" exit 3 fi - if [ "$type" = 'spec' ] + if [[ "${type}" = 'spec' ]] then - if [ -s $ID.$cycle.${type}_tar ] + if [[ -s "${ID}.${cycle}.${type}_tar" ]] then - file_name=$ID.$cycle.${type}_tar.gz - /usr/bin/gzip -c $ID.$cycle.${type}_tar > ${file_name} + file_name="${ID}.${cycle}.${type}_tar.gz" + /usr/bin/gzip -c "${ID}.${cycle}.${type}_tar" > "${file_name}" exit=$? - if [ "$exit" != '0' ] + if [[ "${exit}" != '0' ]] then set +x echo ' ' @@ -171,7 +171,7 @@ source "$HOMEgfs/ush/preamble.sh" fi fi else - file_name=$ID.$cycle.${type}_tar + file_name="${ID}.${cycle}.${type}_tar" fi # --------------------------------------------------------------------------- # @@ -179,14 +179,14 @@ source "$HOMEgfs/ush/preamble.sh" set +x echo ' ' - echo " Moving tar file ${file_name} to ${COM_WAVE_STATION} ..." + echo " Moving tar file ${file_name} to ${COMOUT_WAVE_STATION} ..." set_trace - cp "${file_name}" "${COM_WAVE_STATION}/." + cp "${file_name}" "${COMOUT_WAVE_STATION}/." exit=$? - if [ "$exit" != '0' ] + if [[ "${exit}" != '0' ]] then set +x echo ' ' @@ -198,21 +198,21 @@ source "$HOMEgfs/ush/preamble.sh" exit 4 fi - if [ "$SENDDBN" = 'YES' ] + if [[ "${SENDDBN}" = 'YES' ]] then set +x echo ' ' - echo " Alerting TAR file as ${COM_WAVE_STATION}/${file_name}" + echo " Alerting TAR file as ${COMOUT_WAVE_STATION}/${file_name}" echo ' ' set_trace "${DBNROOT}/bin/dbn_alert MODEL" "${alertName}_WAVE_TAR" "${job}" \ - "${COM_WAVE_STATION}/${file_name}" + "${COMOUT_WAVE_STATION}/${file_name}" fi # --------------------------------------------------------------------------- # # 4. Final clean up -cd $DATA +cd "${DATA}" if [[ ${KEEPDATA:-NO} == "NO" ]]; then set -v diff --git a/versions/build.gaea.ver b/versions/build.gaea.ver new file mode 100644 index 0000000000..b92fe8c1db --- /dev/null +++ b/versions/build.gaea.ver @@ -0,0 +1,6 @@ +export stack_intel_ver=2023.1.0 +export stack_cray_mpich_ver=8.1.25 +export spack_env=gsi-addon-dev + +source "${HOMEgfs:-}/versions/run.spack.ver" +export spack_mod_path="/ncrc/proj/epic/spack-stack/spack-stack-${spack_stack_ver}/envs/${spack_env}/install/modulefiles/Core" diff --git a/versions/build.hercules.ver b/versions/build.hercules.ver index 5513466631..cab0c92111 100644 --- a/versions/build.hercules.ver +++ b/versions/build.hercules.ver @@ -1,3 +1,6 @@ export stack_intel_ver=2021.9.0 export stack_impi_ver=2021.9.0 +export intel_mkl_ver=2023.1.0 +export spack_env=gsi-addon-env source "${HOMEgfs:-}/versions/build.spack.ver" +export spack_mod_path="/work/noaa/epic/role-epic/spack-stack/hercules/spack-stack-${spack_stack_ver}/envs/${spack_env}/install/modulefiles/Core" diff --git a/versions/build.jet.ver b/versions/build.jet.ver index ff85b1a801..55c0ea0bd1 100644 --- a/versions/build.jet.ver +++ b/versions/build.jet.ver @@ -1,3 +1,5 @@ export stack_intel_ver=2021.5.0 export stack_impi_ver=2021.5.1 +export spack_env=gsi-addon-dev source "${HOMEgfs:-}/versions/build.spack.ver" +export spack_mod_path="/lfs4/HFIP/hfv3gfs/role.epic/spack-stack/spack-stack-${spack_stack_ver}/envs/${spack_env}/install/modulefiles/Core" diff --git a/versions/build.orion.ver b/versions/build.orion.ver index ff85b1a801..834ecfc166 100644 --- a/versions/build.orion.ver +++ b/versions/build.orion.ver @@ -1,3 +1,5 @@ -export stack_intel_ver=2021.5.0 -export stack_impi_ver=2021.5.1 +export stack_intel_ver=2021.9.0 +export stack_impi_ver=2021.9.0 +export spack_env=gsi-addon-env-rocky9 source "${HOMEgfs:-}/versions/build.spack.ver" +export spack_mod_path="/work/noaa/epic/role-epic/spack-stack/orion/spack-stack-${spack_stack_ver}/envs/${spack_env}/install/modulefiles/Core" diff --git a/versions/build.s4.ver b/versions/build.s4.ver index a0aae51d87..e2731ccfb3 100644 --- a/versions/build.s4.ver +++ b/versions/build.s4.ver @@ -1,3 +1,5 @@ export stack_intel_ver=2021.5.0 export stack_impi_ver=2021.5.0 +export spack_env=gsi-addon-env source "${HOMEgfs:-}/versions/build.spack.ver" +export spack_mod_path="/data/prod/jedi/spack-stack/spack-stack-${spack_stack_ver}/envs/${spack_env}/install/modulefiles/Core" diff --git a/versions/build.spack.ver b/versions/build.spack.ver index ec6e508f97..808f85dd16 100644 --- a/versions/build.spack.ver +++ b/versions/build.spack.ver @@ -1,7 +1,6 @@ -export spack_stack_ver=1.5.1 -export spack_env=gsi-addon +export spack_stack_ver=1.6.0 -export cmake_ver=3.28.1 +export cmake_ver=3.23.1 export jasper_ver=2.0.32 export libpng_ver=1.6.37 diff --git a/versions/build.wcoss2.ver b/versions/build.wcoss2.ver index 046ff5c64e..3ae0b3a1cc 100644 --- a/versions/build.wcoss2.ver +++ b/versions/build.wcoss2.ver @@ -28,6 +28,6 @@ export wrf_io_ver=1.2.0 export ncio_ver=1.1.2 export ncdiag_ver=1.0.0 export g2tmpl_ver=1.10.2 -export crtm_ver=2.4.0 +export crtm_ver=2.4.0.1 export upp_ver=10.0.8 diff --git a/versions/fix.ver b/versions/fix.ver index 13d9b56dd2..5ca044ae3d 100644 --- a/versions/fix.ver +++ b/versions/fix.ver @@ -4,19 +4,23 @@ export aer_ver=20220805 export am_ver=20220805 export chem_ver=20220805 -export cice_ver=20231219 +export cice_ver=20240416 export cpl_ver=20230526 export datm_ver=20220805 export gdas_crtm_ver=20220805 export gdas_fv3jedi_ver=20220805 -export gdas_gsibec_ver=20221031 +export gdas_soca_ver=20240624 +export gdas_gsibec_ver=20240416 +export gdas_obs_ver=20240213 export glwu_ver=20220805 -export gsi_ver=20230911 +export gsi_ver=20240208 export lut_ver=20220805 -export mom6_ver=20231219 +export mom6_ver=20240416 export orog_ver=20231027 export reg2grb2_ver=20220805 export sfc_climo_ver=20220805 export ugwd_ver=20220805 export verif_ver=20220805 export wave_ver=20240105 +export orog_nest_ver=global-nest.20240419 +export ugwd_nest_ver=global-nest.20240419 diff --git a/versions/run.gaea.ver b/versions/run.gaea.ver new file mode 100644 index 0000000000..b92fe8c1db --- /dev/null +++ b/versions/run.gaea.ver @@ -0,0 +1,6 @@ +export stack_intel_ver=2023.1.0 +export stack_cray_mpich_ver=8.1.25 +export spack_env=gsi-addon-dev + +source "${HOMEgfs:-}/versions/run.spack.ver" +export spack_mod_path="/ncrc/proj/epic/spack-stack/spack-stack-${spack_stack_ver}/envs/${spack_env}/install/modulefiles/Core" diff --git a/versions/run.hera.ver b/versions/run.hera.ver index b358f9d495..34f81bfe96 100644 --- a/versions/run.hera.ver +++ b/versions/run.hera.ver @@ -4,8 +4,10 @@ export spack_env=gsi-addon-dev-rocky8 export hpss_ver=hpss export ncl_ver=6.6.2 -export R_ver=3.5.0 -export gempak_ver=7.4.2 +export R_ver=3.6.1 + +export gempak_ver=7.17.0 +export perl_ver=5.38.0 source "${HOMEgfs:-}/versions/run.spack.ver" export spack_mod_path="/scratch1/NCEPDEV/nems/role.epic/spack-stack/spack-stack-${spack_stack_ver}/envs/${spack_env}/install/modulefiles/Core" diff --git a/versions/run.hercules.ver b/versions/run.hercules.ver index 43f1b2181d..ee8e4f8aea 100644 --- a/versions/run.hercules.ver +++ b/versions/run.hercules.ver @@ -1,12 +1,7 @@ export stack_intel_ver=2021.9.0 export stack_impi_ver=2021.9.0 export intel_mkl_ver=2023.1.0 - -export ncl_ver=6.6.2 -export perl_ver=5.36.0 +export spack_env=gsi-addon-env source "${HOMEgfs:-}/versions/run.spack.ver" - -# wgrib2 and cdo are different on Hercules from all the other systems -export wgrib2_ver=3.1.1 -export cdo_ver=2.2.0 +export spack_mod_path="/work/noaa/epic/role-epic/spack-stack/hercules/spack-stack-${spack_stack_ver}/envs/${spack_env}/install/modulefiles/Core" diff --git a/versions/run.jet.ver b/versions/run.jet.ver index 18a82cab4f..3aa586ee42 100644 --- a/versions/run.jet.ver +++ b/versions/run.jet.ver @@ -1,9 +1,14 @@ export stack_intel_ver=2021.5.0 export stack_impi_ver=2021.5.1 +export spack_env=gsi-addon-dev-rocky8 export hpss_ver= export ncl_ver=6.6.2 export R_ver=4.0.2 export gempak_ver=7.4.2 +# Adding perl as a module; With Rocky8, perl packages will not be from the OS +export perl_ver=5.38.0 + source "${HOMEgfs:-}/versions/run.spack.ver" +export spack_mod_path="/lfs4/HFIP/hfv3gfs/role.epic/spack-stack/spack-stack-${spack_stack_ver}/envs/${spack_env}/install/modulefiles/Core" diff --git a/versions/run.orion.ver b/versions/run.orion.ver index 7671bc028d..59adda6b50 100644 --- a/versions/run.orion.ver +++ b/versions/run.orion.ver @@ -1,11 +1,10 @@ -export stack_intel_ver=2022.0.2 -export stack_impi_ver=2021.5.1 - -export ncl_ver=6.6.2 -export gempak_ver=7.5.1 +export stack_intel_ver=2021.9.0 +export stack_impi_ver=2021.9.0 +export spack_env=gsi-addon-env-rocky9 #For metplus jobs, not currently working with spack-stack #export met_ver=9.1.3 #export metplus_ver=3.1.1 source "${HOMEgfs:-}/versions/run.spack.ver" +export spack_mod_path="/work/noaa/epic/role-epic/spack-stack/orion/spack-stack-${spack_stack_ver}/envs/${spack_env}/install/modulefiles/Core" diff --git a/versions/run.s4.ver b/versions/run.s4.ver index 56817ef439..6d0f4cbaca 100644 --- a/versions/run.s4.ver +++ b/versions/run.s4.ver @@ -1,6 +1,8 @@ export stack_intel_ver=2021.5.0 export stack_impi_ver=2021.5.0 +export spack_env=gsi-addon-env export ncl_ver=6.4.0-precompiled source "${HOMEgfs:-}/versions/run.spack.ver" +export spack_mod_path="/data/prod/jedi/spack-stack/spack-stack-${spack_stack_ver}/envs/${spack_env}/install/modulefiles/Core" diff --git a/versions/run.spack.ver b/versions/run.spack.ver index 80fa6acd1a..9aa5460c80 100644 --- a/versions/run.spack.ver +++ b/versions/run.spack.ver @@ -1,29 +1,35 @@ -export spack_stack_ver=1.5.1 -export spack_env=gsi-addon-dev-rocky8 -export python_ver=3.10.8 +export spack_stack_ver=1.6.0 +export python_ver=3.11.6 export jasper_ver=2.0.32 export libpng_ver=1.6.37 -export cdo_ver=2.0.5 +export cdo_ver=2.2.0 export nco_ver=5.0.6 export hdf5_ver=1.14.0 export netcdf_c_ver=4.9.2 -export netcdf_fortran_ver=4.6.0 +export netcdf_fortran_ver=4.6.1 export bufr_ver=11.7.0 export gsi_ncdiag_ver=1.1.2 export g2tmpl_ver=1.10.2 -export crtm_ver=2.4.0 +export crtm_ver=2.4.0.1 export wgrib2_ver=2.0.8 export grib_util_ver=1.3.0 -export prod_util_ver=1.2.2 +export prod_util_ver=2.1.1 export py_netcdf4_ver=1.5.8 -export py_pyyaml_ver=5.4.1 +export py_pyyaml_ver=6.0 export py_jinja2_ver=3.1.2 +export py_pandas_ver=1.5.3 +export py_python_dateutil_ver=2.8.2 +export py_f90nml_ver=1.4.3 + +export met_ver=9.1.3 +export metplus_ver=3.1.1 +export py_xarray_ver=2023.7.0 export obsproc_run_ver=1.1.2 -export prepobs_run_ver=1.0.1 +export prepobs_run_ver=1.0.2 export ens_tracker_ver=feature-GFSv17_com_reorg -export fit2obs_ver=1.0.0 +export fit2obs_ver=1.1.2 diff --git a/versions/run.wcoss2.ver b/versions/run.wcoss2.ver index a188cdea74..7f653dd50e 100644 --- a/versions/run.wcoss2.ver +++ b/versions/run.wcoss2.ver @@ -37,15 +37,17 @@ export bufr_dump_ver=1.0.0 export util_shared_ver=1.4.0 export g2tmpl_ver=1.10.2 export ncdiag_ver=1.0.0 -export crtm_ver=2.4.0 +export crtm_ver=2.4.0.1 export wgrib2_ver=2.0.8 +export met_ver=9.1.3 +export metplus_ver=3.1.1 # Development-only below export obsproc_run_ver=1.1.2 -export prepobs_run_ver=1.0.1 +export prepobs_run_ver=1.0.2 export ens_tracker_ver=feature-GFSv17_com_reorg -export fit2obs_ver=1.0.0 +export fit2obs_ver=1.1.2 export mos_ver=5.4.3 export mos_shared_ver=2.7.2 diff --git a/workflow/applications/applications.py b/workflow/applications/applications.py index d45b6a9abc..97a77c2c21 100644 --- a/workflow/applications/applications.py +++ b/workflow/applications/applications.py @@ -3,6 +3,7 @@ from typing import Dict, List, Any from datetime import timedelta from hosts import Host +from pathlib import Path from wxflow import Configuration, to_timedelta from abc import ABC, ABCMeta, abstractmethod @@ -31,7 +32,11 @@ def __init__(self, conf: Configuration) -> None: self.scheduler = Host().scheduler - _base = conf.parse_config('config.base') + # Save the configuration so we can source the config files when + # determining task resources + self.conf = conf + + _base = self.conf.parse_config('config.base') # Define here so the child __init__ functions can use it; will # be overwritten later during _init_finalize(). self._base = _base @@ -51,6 +56,7 @@ def __init__(self, conf: Configuration) -> None: 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) @@ -64,30 +70,45 @@ def __init__(self, conf: Configuration) -> None: 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.do_hpssarch = _base.get('HPSSARCH', False) self.nens = _base.get('NMEM_ENS', 0) - self.wave_cdumps = None + self.wave_runs = None if self.do_wave: - wave_cdump = _base.get('WAVE_CDUMP', 'BOTH').lower() - if wave_cdump in ['both']: - self.wave_cdumps = ['gfs', 'gdas'] - elif wave_cdump in ['gfs', 'gdas']: - self.wave_cdumps = [wave_cdump] - - def _init_finalize(self, conf: Configuration): + 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] + + def _init_finalize(self, *args): 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() # Source the config_files for the jobs in the application - self.configs = self._source_configs(conf) + self.configs = self.source_configs() # Update the base config dictionary base on application - self.configs['base'] = self._update_base(self.configs['base']) + self.configs['base'] = self.update_base(self.configs['base']) # Save base in the internal state since it is often needed self._base = self.configs['base'] @@ -104,7 +125,7 @@ def _get_app_configs(self): @staticmethod @abstractmethod - def _update_base(base_in: Dict[str, Any]) -> Dict[str, Any]: + def update_base(base_in: Dict[str, Any]) -> Dict[str, Any]: ''' Make final updates to base and return an updated copy @@ -121,9 +142,9 @@ def _update_base(base_in: Dict[str, Any]) -> Dict[str, Any]: ''' pass - def _source_configs(self, conf: Configuration) -> Dict[str, Any]: + def source_configs(self, run: str = "gfs", log: bool = True) -> Dict[str, Any]: """ - Given the configuration object and jobs, + Given the configuration object used to initialize this application, source the configurations for each config and return a dictionary Every config depends on "config.base" """ @@ -131,7 +152,7 @@ def _source_configs(self, conf: Configuration) -> Dict[str, Any]: configs = dict() # Return config.base as well - configs['base'] = conf.parse_config('config.base') + configs['base'] = self.conf.parse_config('config.base') # Source the list of all config_files involved in the application for config in self.configs_names: @@ -145,20 +166,24 @@ def _source_configs(self, conf: Configuration) -> Dict[str, Any]: files += ['config.anal', 'config.eupd'] elif config in ['efcs']: files += ['config.fcst', 'config.efcs'] + elif config in ['atmanlinit', 'atmanlvar', 'atmanlfv3inc']: + files += ['config.atmanl', f'config.{config}'] + elif config in ['atmensanlinit', 'atmensanlletkf', 'atmensanlfv3inc']: + files += ['config.atmensanl', f'config.{config}'] elif 'wave' in config: files += ['config.wave', f'config.{config}'] else: files += [f'config.{config}'] - print(f'sourcing config.{config}') - configs[config] = conf.parse_config(files) + print(f'sourcing config.{config}') if log else 0 + configs[config] = self.conf.parse_config(files, RUN=run) return configs @abstractmethod def get_task_names(self) -> Dict[str, List[str]]: ''' - Create a list of task names for each CDUMP valid for the configuation. + Create a list of task names for each RUN valid for the configuation. Parameters ---------- @@ -166,7 +191,7 @@ def get_task_names(self) -> Dict[str, List[str]]: Returns ------- - Dict[str, List[str]]: Lists of tasks for each CDUMP. + Dict[str, List[str]]: Lists of tasks for each RUN. ''' pass diff --git a/workflow/applications/gefs.py b/workflow/applications/gefs.py index b2369e8dfc..364ee2c48b 100644 --- a/workflow/applications/gefs.py +++ b/workflow/applications/gefs.py @@ -14,22 +14,33 @@ def _get_app_configs(self): """ Returns the config_files that are involved in gefs """ - configs = ['stage_ic', 'fcst'] + configs = ['stage_ic', 'fcst', 'atmos_products'] if self.nens > 0: - configs += ['efcs'] + configs += ['efcs', 'atmos_ensstat'] if self.do_wave: - configs += ['waveinit'] + configs += ['waveinit', 'wavepostsbs', 'wavepostpnt'] + if self.do_wave_bnd: + configs += ['wavepostbndpnt', 'wavepostbndpntbll'] + + if self.do_ocean or self.do_ice: + configs += ['oceanice_products'] + + if self.do_aero: + configs += ['prep_emissions'] + + if self.do_extractvars: + configs += ['extractvars'] return configs @staticmethod - def _update_base(base_in): + def update_base(base_in): base_out = base_in.copy() base_out['INTERVAL_GFS'] = AppConfig.get_gfs_interval(base_in['gfs_cyc']) - base_out['CDUMP'] = 'gefs' + base_out['RUN'] = 'gefs' return base_out @@ -40,9 +51,32 @@ def get_task_names(self): if self.do_wave: tasks += ['waveinit'] + if self.do_aero: + tasks += ['prep_emissions'] + tasks += ['fcst'] if self.nens > 0: tasks += ['efcs'] - return {f"{self._base['CDUMP']}": tasks} + tasks += ['atmos_prod'] + + if self.nens > 0: + tasks += ['atmos_ensstat'] + + if self.do_ocean: + tasks += ['ocean_prod'] + + if self.do_ice: + tasks += ['ice_prod'] + + if self.do_wave: + tasks += ['wavepostsbs'] + if self.do_wave_bnd: + tasks += ['wavepostbndpnt', 'wavepostbndpntbll'] + tasks += ['wavepostpnt'] + + if self.do_extractvars: + tasks += ['extractvars'] + + return {f"{self._base['RUN']}": tasks} diff --git a/workflow/applications/gfs_cycled.py b/workflow/applications/gfs_cycled.py index 1ff6cc3723..e049a7d422 100644 --- a/workflow/applications/gfs_cycled.py +++ b/workflow/applications/gfs_cycled.py @@ -16,18 +16,19 @@ def __init__(self, conf: Configuration): self.do_jediatmvar = self._base.get('DO_JEDIATMVAR', False) self.do_jediatmens = self._base.get('DO_JEDIATMENS', False) self.do_jediocnvar = self._base.get('DO_JEDIOCNVAR', False) - self.do_jedilandda = self._base.get('DO_JEDILANDDA', False) + self.do_jedisnowda = self._base.get('DO_JEDISNOWDA', False) self.do_mergensst = self._base.get('DO_MERGENSST', False) + self.do_vrfy_oceanda = self._base.get('DO_VRFY_OCEANDA', False) self.lobsdiag_forenkf = False - self.eupd_cdumps = None + self.eupd_runs = None if self.do_hybvar: self.lobsdiag_forenkf = self._base.get('lobsdiag_forenkf', False) - eupd_cdump = self._base.get('EUPD_CYC', 'gdas').lower() - if eupd_cdump in ['both']: - self.eupd_cdumps = ['gfs', 'gdas'] - elif eupd_cdump in ['gfs', 'gdas']: - self.eupd_cdumps = [eupd_cdump] + eupd_run = self._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] def _get_app_configs(self): """ @@ -37,23 +38,26 @@ def _get_app_configs(self): configs = ['prep'] if self.do_jediatmvar: - configs += ['prepatmiodaobs', 'atmanlinit', 'atmanlrun', 'atmanlfinal'] + configs += ['prepatmiodaobs', 'atmanlinit', 'atmanlvar', 'atmanlfv3inc', 'atmanlfinal'] else: configs += ['anal', 'analdiag'] if self.do_jediocnvar: - configs += ['prepoceanobs', 'ocnanalprep', 'ocnanalbmat', - 'ocnanalrun', 'ocnanalchkpt', 'ocnanalpost', - 'ocnanalvrfy'] + configs += ['prepoceanobs', 'ocnanalprep', 'marinebmat', 'ocnanalrun'] + if self.do_hybvar: + configs += ['ocnanalecen'] + configs += ['ocnanalchkpt', 'ocnanalpost'] + if self.do_vrfy_oceanda: + configs += ['ocnanalvrfy'] - if self.do_ocean: - configs += ['ocnpost'] + if self.do_ocean or self.do_ice: + configs += ['oceanice_products'] configs += ['sfcanl', 'analcalc', 'fcst', 'upp', 'atmos_products', 'arch', 'cleanup'] if self.do_hybvar: if self.do_jediatmens: - configs += ['atmensanlinit', 'atmensanlrun', 'atmensanlfinal'] + configs += ['atmensanlinit', 'atmensanlletkf', 'atmensanlfv3inc', 'atmensanlfinal'] else: configs += ['eobs', 'eomg', 'ediag', 'eupd'] configs += ['ecen', 'esfc', 'efcs', 'echgres', 'epos', 'earc'] @@ -83,7 +87,9 @@ def _get_app_configs(self): configs += ['metp'] if self.do_gempak: - configs += ['gempak', 'npoess'] + configs += ['gempak'] + if self.do_goes: + configs += ['npoess'] if self.do_bufrsnd: configs += ['postsnd'] @@ -102,9 +108,11 @@ def _get_app_configs(self): if self.do_aero: configs += ['aeroanlinit', 'aeroanlrun', 'aeroanlfinal'] + if self.do_prep_obs_aero: + configs += ['prepobsaero'] - if self.do_jedilandda: - configs += ['preplandobs', 'landanl'] + if self.do_jedisnowda: + configs += ['prepsnowobs', 'snowanl'] if self.do_mos: configs += ['mos_stn_prep', 'mos_grd_prep', 'mos_ext_stn_prep', 'mos_ext_grd_prep', @@ -115,7 +123,7 @@ def _get_app_configs(self): return configs @staticmethod - def _update_base(base_in): + def update_base(base_in): return GFSCycledAppConfig.get_gfs_cyc_dates(base_in) @@ -130,23 +138,22 @@ def get_task_names(self): gdas_gfs_common_cleanup_tasks = ['arch', 'cleanup'] if self.do_jediatmvar: - gdas_gfs_common_tasks_before_fcst += ['prepatmiodaobs', 'atmanlinit', 'atmanlrun', 'atmanlfinal'] + 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', - 'ocnanalbmat', 'ocnanalrun', - 'ocnanalchkpt', 'ocnanalpost', - 'ocnanalvrfy'] + 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_aero: - gdas_gfs_common_tasks_before_fcst += ['aeroanlinit', 'aeroanlrun', 'aeroanlfinal'] - - if self.do_jedilandda: - gdas_gfs_common_tasks_before_fcst += ['preplandobs', 'landanl'] + if self.do_jedisnowda: + gdas_gfs_common_tasks_before_fcst += ['prepsnowobs', 'snowanl'] wave_prep_tasks = ['waveinit', 'waveprep'] wave_bndpnt_tasks = ['wavepostbndpnt', 'wavepostbndpntbll'] @@ -156,7 +163,7 @@ def get_task_names(self): hybrid_after_eupd_tasks = [] if self.do_hybvar: if self.do_jediatmens: - hybrid_tasks += ['atmensanlinit', 'atmensanlrun', 'atmensanlfinal', 'echgres'] + hybrid_tasks += ['atmensanlinit', 'atmensanlletkf', 'atmensanlfv3inc', 'atmensanlfinal', 'echgres'] else: hybrid_tasks += ['eobs', 'eupd', 'echgres'] hybrid_tasks += ['ediag'] if self.lobsdiag_forenkf else ['eomg'] @@ -168,16 +175,21 @@ def get_task_names(self): if not self.do_jediatmvar: gdas_tasks += ['analdiag'] - if self.do_wave and 'gdas' in self.wave_cdumps: + 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 += ['aeroanlinit', 'aeroanlrun', 'aeroanlfinal'] + if self.do_prep_obs_aero: + gdas_tasks += ['prepobsaero'] + gdas_tasks += ['atmanlupp', 'atmanlprod', 'fcst'] if self.do_upp: gdas_tasks += ['atmupp'] - gdas_tasks += ['atmprod'] + gdas_tasks += ['atmos_prod'] - if self.do_wave and 'gdas' in self.wave_cdumps: + 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 @@ -202,14 +214,25 @@ def get_task_names(self): # Collect "gfs" cycle tasks gfs_tasks = gdas_gfs_common_tasks_before_fcst.copy() - if self.do_wave and 'gfs' in self.wave_cdumps: + 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', 'aeroanlrun', '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 += ['atmprod'] + gfs_tasks += ['atmos_prod'] if self.do_goes: gfs_tasks += ['goesupp'] @@ -229,7 +252,7 @@ def get_task_names(self): if self.do_metp: gfs_tasks += ['metp'] - if self.do_wave and 'gfs' in self.wave_cdumps: + 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 @@ -245,11 +268,12 @@ def get_task_names(self): gfs_tasks += ['gempak'] gfs_tasks += ['gempakmeta'] gfs_tasks += ['gempakncdcupapgif'] - gfs_tasks += ['npoess_pgrb2_0p5deg'] - gfs_tasks += ['gempakpgrb2spec'] + if self.do_goes: + gfs_tasks += ['npoess_pgrb2_0p5deg'] + gfs_tasks += ['gempakpgrb2spec'] if self.do_awips: - gfs_tasks += ['awips_20km_1p0deg', 'awips_g2', 'fbwind'] + 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', @@ -262,15 +286,15 @@ def get_task_names(self): tasks = dict() tasks['gdas'] = gdas_tasks - if self.do_hybvar and 'gdas' in self.eupd_cdumps: + if self.do_hybvar and 'gdas' in self.eupd_runs: enkfgdas_tasks = hybrid_tasks + hybrid_after_eupd_tasks tasks['enkfgdas'] = enkfgdas_tasks - # Add CDUMP=gfs tasks if running early cycle + # 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_cdumps: + if self.do_hybvar and 'gfs' in self.eupd_runs: enkfgfs_tasks = hybrid_tasks + hybrid_after_eupd_tasks enkfgfs_tasks.remove("echgres") tasks['enkfgfs'] = enkfgfs_tasks @@ -321,9 +345,4 @@ def get_gfs_cyc_dates(base: Dict[str, Any]) -> Dict[str, Any]: base_out['EDATE_GFS'] = edate_gfs base_out['INTERVAL_GFS'] = interval_gfs - fhmax_gfs = {} - for hh in ['00', '06', '12', '18']: - fhmax_gfs[hh] = base.get(f'FHMAX_GFS_{hh}', base.get('FHMAX_GFS_00', 120)) - base_out['FHMAX_GFS'] = fhmax_gfs - return base_out diff --git a/workflow/applications/gfs_forecast_only.py b/workflow/applications/gfs_forecast_only.py index 1145863210..caa545d1e1 100644 --- a/workflow/applications/gfs_forecast_only.py +++ b/workflow/applications/gfs_forecast_only.py @@ -25,7 +25,8 @@ def _get_app_configs(self): configs += ['atmos_products'] if self.do_aero: - configs += ['aerosol_init'] + if not self._base['EXP_WARM_START']: + configs += ['aerosol_init'] if self.do_tracker: configs += ['tracker'] @@ -49,7 +50,7 @@ def _get_app_configs(self): configs += ['awips'] if self.do_ocean or self.do_ice: - configs += ['ocnpost'] + configs += ['oceanice_products'] if self.do_wave: configs += ['waveinit', 'waveprep', 'wavepostsbs', 'wavepostpnt'] @@ -69,11 +70,11 @@ def _get_app_configs(self): return configs @staticmethod - def _update_base(base_in): + def update_base(base_in): base_out = base_in.copy() base_out['INTERVAL_GFS'] = AppConfig.get_gfs_interval(base_in['gfs_cyc']) - base_out['CDUMP'] = 'gfs' + base_out['RUN'] = 'gfs' return base_out @@ -87,7 +88,10 @@ def get_task_names(self): tasks = ['stage_ic'] if self.do_aero: - tasks += ['aerosol_init'] + aero_fcst_run = self._base.get('AERO_FCST_RUN', 'BOTH').lower() + if self._base['RUN'] in aero_fcst_run or aero_fcst_run == "both": + if not self._base['EXP_WARM_START']: + tasks += ['aerosol_init'] if self.do_wave: tasks += ['waveinit'] @@ -100,7 +104,10 @@ def get_task_names(self): if self.do_upp: tasks += ['atmupp'] - tasks += ['atmprod'] + tasks += ['atmos_prod'] + + if self.do_goes: + tasks += ['goesupp'] if self.do_goes: tasks += ['goesupp'] @@ -124,10 +131,13 @@ def get_task_names(self): tasks += ['gempak', 'gempakmeta', 'gempakncdcupapgif', 'gempakpgrb2spec'] if self.do_awips: - tasks += ['awips_20km_1p0deg', 'awips_g2', 'fbwind'] + tasks += ['awips_20km_1p0deg', 'fbwind'] - if self.do_ocean or self.do_ice: - tasks += ['ocnpost'] + if self.do_ocean: + tasks += ['ocean_prod'] + + if self.do_ice: + tasks += ['ice_prod'] if self.do_wave: if self.do_wave_bnd: @@ -146,4 +156,4 @@ def get_task_names(self): tasks += ['arch', 'cleanup'] # arch and cleanup **must** be the last tasks - return {f"{self._base['CDUMP']}": tasks} + return {f"{self._base['RUN']}": tasks} diff --git a/workflow/create_experiment.py b/workflow/create_experiment.py index 7e0f350c0f..1317f7be28 100755 --- a/workflow/create_experiment.py +++ b/workflow/create_experiment.py @@ -11,6 +11,14 @@ The yaml file are simply the arguments for these two scripts. After this scripts runs the experiment is ready for launch. +Environmental variables +----------------------- + pslot + Name of the experiment + + RUNTESTS + Root directory where the test EXPDIR and COMROOT will be placed + Output ------ Functionally an experiment is setup as a result running the two scripts described above @@ -18,7 +26,6 @@ """ import os -import sys from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter from pathlib import Path @@ -28,8 +35,6 @@ import setup_expt import setup_xml -from hosts import Host - _here = os.path.dirname(__file__) _top = os.path.abspath(os.path.join(os.path.abspath(_here), '..')) @@ -63,7 +68,9 @@ def input_args(): formatter_class=ArgumentDefaultsHelpFormatter) parser.add_argument( - '--yaml', help='full path to yaml file describing the experiment configuration', type=Path, required=True) + '-y', '--yaml', help='full path to yaml file describing the experiment configuration', type=Path, required=True) + parser.add_argument( + '-o', '--overwrite', help='overwrite previously created experiment', action="store_true", required=False) return parser.parse_args() @@ -77,18 +84,15 @@ def input_args(): data.update(os.environ) testconf = parse_j2yaml(path=user_inputs.yaml, data=data) - if 'skip_ci_on_hosts' in testconf: - host = Host() - if host.machine.lower() in [machine.lower() for machine in testconf.skip_ci_on_hosts]: - logger.info(f'Skipping creation of case: {testconf.arguments.pslot} on {host.machine.capitalize()}') - sys.exit(0) - # Create a list of arguments to setup_expt.py setup_expt_args = [testconf.experiment.system, testconf.experiment.mode] for kk, vv in testconf.arguments.items(): setup_expt_args.append(f"--{kk}") setup_expt_args.append(str(vv)) + if user_inputs.overwrite: + setup_expt_args.append("--overwrite") + logger.info(f"Call: setup_expt.main()") logger.debug(f"setup_expt.py {' '.join(setup_expt_args)}") setup_expt.main(setup_expt_args) diff --git a/workflow/gsl_template_hera.xml b/workflow/gsl_template_hera.xml index b796f4ae82..43c9aca9b7 100644 --- a/workflow/gsl_template_hera.xml +++ b/workflow/gsl_template_hera.xml @@ -6,7 +6,7 @@ Main workflow manager for Global Forecast System NOTES: - This workflow was automatically generated at 2023-06-13 23:31:49.582810 + This workflow was automatically generated at 2024-09-05 15:37:41.961069 --> &EXPDIR;/logs/@Y@m@d@H.log - 202401140000 202401140000 24:00:00 + 202409050000 202409050000 24:00:00 @@ -42,23 +42,25 @@ &ROTDIR;/logs/@Y@m@d@H/gfsinit.log - RUN_ENVIRemc - HOMEgfs&HOMEgfs; - EXPDIR&EXPDIR; - ROTDIR&ROTDIR; - ICSDIR&ICSDIR; - CASE&CASE; - COMPONENT&COMPONENT; - NETgfs - CDUMPgfs - RUNgfs - CDATE@Y@m@d@H - PDY@Y@m@d - cyc@H - COMROOT/scratch1/NCEPDEV/global/glopara/com - DATAROOT&ROTDIR;/../RUNDIRS/&PSLOT; - - + RUN_ENVIRemc + HOMEgfs&HOMEgfs; + EXPDIR&EXPDIR; + ROTDIR&ROTDIR; + ICSDIR&ICSDIR; + CASE&CASE; + COMPONENT&COMPONENT; + NETgfs + CDUMPgfs + RUNgfs + CDATE@Y@m@d@H + PDY@Y@m@d + cyc@H + COMROOT/scratch1/NCEPDEV/global/glopara/com + DATAROOT&ROTDIR;/../RUNDIRS/&PSLOT; + FHR3#fhr# + COMPONENTatmos + + &ROTDIR;/gfs.@Y@m@d/@H/model_data/atmos/input @@ -113,15 +115,13 @@ - _f000-f012 _f018-f030 _f036-f048 _f054-f066 _f072-f084 _f090-f102 _f108-f120 - f012 f030 f048 f066 f084 f102 f120 - f000_f006_f012 f018_f024_f030 f036_f042_f048 f054_f060_f066 f072_f078_f084 f090_f096_f102 f108_f114_f120 + 000 003 006 009 012 015 018 021 024 027 030 033 036 039 042 045 048 051 054 057 060 063 066 069 072 075 078 081 084 087 090 093 096 099 102 105 108 111 114 117 120 - + &JOBS_DIR;/atmos_products.sh - &PSLOT;_gfsatmprod#grp#_@H + &PSLOT;_gfsatmprod_f#fhr#_@H gsd-fv3 batch hera @@ -129,24 +129,23 @@ 1:ppn=24:tpp=1 &NATIVE_STR; - &ROTDIR;/logs/@Y@m@d@H/gfsatmprod#grp#.log + &ROTDIR;/logs/@Y@m@d@H/gfsatmprod_f#fhr#.log RUN_ENVIRemc HOMEgfs&HOMEgfs; EXPDIR&EXPDIR; - ROTDIR&ROTDIR; NETgfs - CDUMPgfs RUNgfs CDATE@Y@m@d@H PDY@Y@m@d cyc@H COMROOT/scratch1/NCEPDEV/global/glopara/com DATAROOT&ROTDIR;/../RUNDIRS/&PSLOT; - FHRLST#lst# + FHR3#fhr# + COMPONENTatmos - &ROTDIR;/gfs.@Y@m@d/@H//model_data/atmos/master/gfs.t@Hz.master.grb2#dep# + &ROTDIR;/gfs.@Y@m@d/@H//model_data/atmos/master/gfs.t@Hz.master.grb2#fhr# diff --git a/workflow/hosts.py b/workflow/hosts.py index a17cd3f4a8..cd0cfe0083 100644 --- a/workflow/hosts.py +++ b/workflow/hosts.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 import os +import socket from pathlib import Path from wxflow import YAMLFile @@ -15,7 +16,7 @@ class Host: """ SUPPORTED_HOSTS = ['HERA', 'ORION', 'JET', 'HERCULES', - 'WCOSS2', 'S4', 'CONTAINER', 'AWSPW'] + 'WCOSS2', 'S4', 'CONTAINER', 'AWSPW', 'GAEA'] def __init__(self, host=None): @@ -39,16 +40,15 @@ def detect(cls): if os.path.exists('/scratch1/NCEPDEV'): machine = 'HERA' elif os.path.exists('/work/noaa'): - if os.path.exists('/apps/other'): - machine = 'HERCULES' - else: - machine = 'ORION' + machine = socket.gethostname().split("-", 1)[0].upper() elif os.path.exists('/lfs4/HFIP'): machine = 'JET' elif os.path.exists('/lfs/f1'): machine = 'WCOSS2' elif os.path.exists('/data/prod'): machine = 'S4' + elif os.path.exists('/gpfs/f5'): + machine = 'GAEA' elif container is not None: machine = 'CONTAINER' elif pw_csp is not None: diff --git a/workflow/hosts/awspw.yaml b/workflow/hosts/awspw.yaml index c683010e0e..046dafcfa7 100644 --- a/workflow/hosts/awspw.yaml +++ b/workflow/hosts/awspw.yaml @@ -12,6 +12,8 @@ QUEUE: batch QUEUE_SERVICE: batch PARTITION_BATCH: compute PARTITION_SERVICE: compute +RESERVATION: '' +CLUSTERS: '' CHGRP_RSTPROD: 'YES' CHGRP_CMD: 'chgrp rstprod' # TODO: This is not yet supported. HPSSARCH: 'YES' diff --git a/workflow/hosts/container.yaml b/workflow/hosts/container.yaml index 3fd3856679..d7924724ae 100644 --- a/workflow/hosts/container.yaml +++ b/workflow/hosts/container.yaml @@ -12,6 +12,8 @@ QUEUE: '' QUEUE_SERVICE: '' PARTITION_BATCH: '' PARTITION_SERVICE: '' +RESERVATION: '' +CLUSTERS: '' CHGRP_RSTPROD: 'YES' CHGRP_CMD: 'chgrp rstprod' HPSSARCH: 'NO' diff --git a/workflow/hosts/gaea.yaml b/workflow/hosts/gaea.yaml new file mode 100644 index 0000000000..619a86f2e5 --- /dev/null +++ b/workflow/hosts/gaea.yaml @@ -0,0 +1,27 @@ +BASE_GIT: '/gpfs/f5/ufs-ard/world-shared/global/glopara/data/git' +DMPDIR: '/gpfs/f5/ufs-ard/world-shared/global/glopara/data/dump' +BASE_CPLIC: '/gpfs/f5/ufs-ard/world-shared/global/glopara/data/ICSDIR/prototype_ICs' +PACKAGEROOT: '/gpfs/f5/ufs-ard/world-shared/global/glopara/data/nwpara' +COMROOT: '/gpfs/f5/ufs-ard/world-shared/global/glopara/data/com' +COMINsyn: '${COMROOT}/gfs/prod/syndat' +HOMEDIR: '/gpfs/f5/ufs-ard/scratch/${USER}' +STMP: '/gpfs/f5/ufs-ard/scratch/${USER}' +PTMP: '/gpfs/f5/ufs-ard/scratch/${USER}' +NOSCRUB: $HOMEDIR +ACCOUNT: ufs-ard +SCHEDULER: slurm +QUEUE: normal +QUEUE_SERVICE: normal +PARTITION_BATCH: batch +PARTITION_SERVICE: batch +RESERVATION: '' +CLUSTERS: 'c5' +CHGRP_RSTPROD: 'NO' +CHGRP_CMD: 'chgrp rstprod' +HPSSARCH: 'NO' +HPSS_PROJECT: emc-global +LOCALARCH: 'NO' +ATARDIR: '${NOSCRUB}/archive_rotdir/${PSLOT}' +MAKE_NSSTBUFR: 'NO' +MAKE_ACFTBUFR: 'NO' +SUPPORTED_RESOLUTIONS: ['C1152', 'C768', 'C384', 'C192', 'C96', 'C48'] diff --git a/workflow/hosts/hera_gsl.yaml b/workflow/hosts/hera_gsl.yaml index ecc7ddb28e..31274dc707 100644 --- a/workflow/hosts/hera_gsl.yaml +++ b/workflow/hosts/hera_gsl.yaml @@ -13,7 +13,9 @@ QUEUE: batch QUEUE_SERVICE: batch PARTITION_BATCH: hera PARTITION_SERVICE: service +RESERVATION: '' CHGRP_RSTPROD: 'YES' +CLUSTERS: '' CHGRP_CMD: 'chgrp rstprod' HPSSARCH: 'YES' HPSS_PROJECT: fim @@ -22,3 +24,6 @@ ATARDIR: '/BMC/${HPSS_PROJECT}/1year/${USER}/${machine}/scratch/${PSLOT}' MAKE_NSSTBUFR: 'NO' MAKE_ACFTBUFR: 'NO' SUPPORTED_RESOLUTIONS: ['C1152', 'C768', 'C384', 'C192', 'C96', 'C48'] +COMINecmwf: /scratch1/NCEPDEV/global/glopara/data/external_gempak/ecmwf +COMINnam: /scratch1/NCEPDEV/global/glopara/data/external_gempak/nam +COMINukmet: /scratch1/NCEPDEV/global/glopara/data/external_gempak/ukmet diff --git a/workflow/hosts/hercules.yaml b/workflow/hosts/hercules.yaml index 58a9589f2f..b513bfd57a 100644 --- a/workflow/hosts/hercules.yaml +++ b/workflow/hosts/hercules.yaml @@ -1,11 +1,11 @@ -BASE_GIT: '/work/noaa/global/glopara/git' +BASE_GIT: '/work/noaa/global/glopara/git_rocky9' DMPDIR: '/work/noaa/rstprod/dump' BASE_CPLIC: '/work/noaa/global/glopara/data/ICSDIR/prototype_ICs' PACKAGEROOT: '/work/noaa/global/glopara/nwpara' COMINsyn: '/work/noaa/global/glopara/com/gfs/prod/syndat' HOMEDIR: '/work/noaa/global/${USER}' -STMP: '/work/noaa/stmp/${USER}' -PTMP: '/work/noaa/stmp/${USER}' +STMP: '/work/noaa/stmp/${USER}/HERCULES' +PTMP: '/work/noaa/stmp/${USER}/HERCULES' NOSCRUB: $HOMEDIR SCHEDULER: slurm ACCOUNT: fv3-cpu @@ -13,7 +13,9 @@ QUEUE: batch QUEUE_SERVICE: batch PARTITION_BATCH: hercules PARTITION_SERVICE: service +RESERVATION: '' CHGRP_RSTPROD: 'YES' +CLUSTERS: '' CHGRP_CMD: 'chgrp rstprod' HPSSARCH: 'NO' HPSS_PROJECT: emc-global @@ -22,3 +24,6 @@ ATARDIR: '${NOSCRUB}/archive_rotdir/${PSLOT}' MAKE_NSSTBUFR: 'NO' MAKE_ACFTBUFR: 'NO' SUPPORTED_RESOLUTIONS: ['C1152', 'C768', 'C384', 'C192', 'C96', 'C48'] +COMINecmwf: /work/noaa/global/glopara/data/external_gempak/ecmwf +COMINnam: /work/noaa/global/glopara/data/external_gempak/nam +COMINukmet: /work/noaa/global/glopara/data/external_gempak/ukmet diff --git a/workflow/hosts/jet_gsl.yaml b/workflow/hosts/jet_gsl.yaml index 9da6f2b2aa..e556ca4663 100644 --- a/workflow/hosts/jet_gsl.yaml +++ b/workflow/hosts/jet_gsl.yaml @@ -23,3 +23,6 @@ ATARDIR: '/BMC/${HPSS_PROJECT}/1year/${USER}/${machine}/scratch/${PSLOT}' MAKE_NSSTBUFR: 'NO' MAKE_ACFTBUFR: 'NO' SUPPORTED_RESOLUTIONS: ['C768', 'C384', 'C192', 'C96', 'C48'] +COMINecmwf: /mnt/lfs4/HFIP/hfv3gfs/glopara/data/external_gempak/ecmwf +COMINnam: /mnt/lfs4/HFIP/hfv3gfs/glopara/data/external_gempak/nam +COMINukmet: /mnt/lfs4/HFIP/hfv3gfs/glopara/data/external_gempak/ukmet diff --git a/workflow/hosts/orion.yaml b/workflow/hosts/orion.yaml index 4c08a878dc..f0f807aacf 100644 --- a/workflow/hosts/orion.yaml +++ b/workflow/hosts/orion.yaml @@ -4,8 +4,8 @@ BASE_CPLIC: '/work/noaa/global/glopara/data/ICSDIR/prototype_ICs' PACKAGEROOT: '/work/noaa/global/glopara/nwpara' COMINsyn: '/work/noaa/global/glopara/com/gfs/prod/syndat' HOMEDIR: '/work/noaa/global/${USER}' -STMP: '/work/noaa/stmp/${USER}' -PTMP: '/work/noaa/stmp/${USER}' +STMP: '/work/noaa/stmp/${USER}/ORION' +PTMP: '/work/noaa/stmp/${USER}/ORION' NOSCRUB: $HOMEDIR SCHEDULER: slurm ACCOUNT: fv3-cpu @@ -13,7 +13,9 @@ QUEUE: batch QUEUE_SERVICE: batch PARTITION_BATCH: orion PARTITION_SERVICE: service +RESERVATION: '' CHGRP_RSTPROD: 'YES' +CLUSTERS: '' CHGRP_CMD: 'chgrp rstprod' HPSSARCH: 'NO' HPSS_PROJECT: emc-global @@ -22,3 +24,6 @@ ATARDIR: '${NOSCRUB}/archive_rotdir/${PSLOT}' MAKE_NSSTBUFR: 'NO' MAKE_ACFTBUFR: 'NO' SUPPORTED_RESOLUTIONS: ['C1152', 'C768', 'C384', 'C192', 'C96', 'C48'] +COMINecmwf: /work/noaa/global/glopara/data/external_gempak/ecmwf +COMINnam: /work/noaa/global/glopara/data/external_gempak/nam +COMINukmet: /work/noaa/global/glopara/data/external_gempak/ukmet diff --git a/workflow/hosts/s4.yaml b/workflow/hosts/s4.yaml index 52a9f7a365..aea807da63 100644 --- a/workflow/hosts/s4.yaml +++ b/workflow/hosts/s4.yaml @@ -13,7 +13,9 @@ QUEUE: s4 QUEUE_SERVICE: serial PARTITION_BATCH: s4 PARTITION_SERVICE: serial +RESERVATION: '' CHGRP_RSTPROD: 'NO' +CLUSTERS: '' CHGRP_CMD: 'ls' HPSSARCH: 'NO' HPSS_PROJECT: emc-global diff --git a/workflow/hosts/wcoss2.yaml b/workflow/hosts/wcoss2.yaml index cfb141061c..7ae2be1424 100644 --- a/workflow/hosts/wcoss2.yaml +++ b/workflow/hosts/wcoss2.yaml @@ -13,7 +13,9 @@ QUEUE: 'dev' QUEUE_SERVICE: 'dev_transfer' PARTITION_BATCH: '' PARTITION_SERVICE: '' +RESERVATION: '' CHGRP_RSTPROD: 'YES' +CLUSTERS: '' CHGRP_CMD: 'chgrp rstprod' HPSSARCH: 'NO' HPSS_PROJECT: emc-global @@ -22,3 +24,6 @@ ATARDIR: '/NCEPDEV/${HPSS_PROJECT}/1year/${USER}/${machine}/scratch/${PSLOT}' MAKE_NSSTBUFR: 'NO' MAKE_ACFTBUFR: 'NO' SUPPORTED_RESOLUTIONS: ['C1152', 'C768', 'C384', 'C192', 'C96', 'C48'] +COMINecmwf: /lfs/h2/emc/global/noscrub/emc.global/data/external_gempak/ecmwf +COMINnam: /lfs/h2/emc/global/noscrub/emc.global/data/external_gempak/nam +COMINukmet: /lfs/h2/emc/global/noscrub/emc.global/data/external_gempak/ukmet diff --git a/workflow/prod.yml b/workflow/prod.yml index 64783dd611..55717772b5 100644 --- a/workflow/prod.yml +++ b/workflow/prod.yml @@ -113,17 +113,6 @@ suites: jgfs_atmos_awips_f( 3,27,6 ): edits: TRDRUN: 'NO' - awips_g2: - tasks: - jgfs_atmos_awips_g2_f( 0,64,6 ): - template: jgfs_atmos_awips_g2_master - triggers: - - task: jgfs_atmos_post_f( ) - edits: - FHRGRP: '( )' - FHRLST: 'f( )' - FCSTHR: '( )' - TRDRUN: 'YES' gempak: tasks: jgfs_atmos_gempak: diff --git a/workflow/rocoto/gefs_tasks.py b/workflow/rocoto/gefs_tasks.py index c46d9ad452..1b357d8ee3 100644 --- a/workflow/rocoto/gefs_tasks.py +++ b/workflow/rocoto/gefs_tasks.py @@ -1,57 +1,70 @@ from applications.applications import AppConfig from rocoto.tasks import Tasks import rocoto.rocoto as rocoto +from datetime import datetime, timedelta class GEFSTasks(Tasks): - def __init__(self, app_config: AppConfig, cdump: str) -> None: - super().__init__(app_config, cdump) + def __init__(self, app_config: AppConfig, run: str) -> None: + super().__init__(app_config, run) def stage_ic(self): - cpl_ic = self._configs['stage_ic'] - deps = [] - + dtg_prefix = "@Y@m@d.@H0000" + offset = str(self._configs['base']['OFFSET_START_HOUR']).zfill(2) + ":00:00" # Atm ICs if self.app_config.do_atm: - prefix = f"{cpl_ic['BASE_CPLIC']}/{cpl_ic['CPL_ATMIC']}/@Y@m@d@H/mem000/atmos" - for file in ['gfs_ctrl.nc'] + \ - [f'{datatype}_data.tile{tile}.nc' - for datatype in ['gfs', 'sfc'] - for tile in range(1, self.n_tiles + 1)]: - data = f"{prefix}/{file}" - dep_dict = {'type': 'data', 'data': data} + prefix = f"{cpl_ic['BASE_CPLIC']}/{cpl_ic['CPL_ATMIC']}/@Y@m@d@H/mem000/atmos/" + if self._base['EXP_WARM_START']: + for file in ['fv_core.res.nc'] + \ + [f'{datatype}.tile{tile}.nc' + for datatype in ['ca_data', 'fv_core.res', 'fv_srf_wnd.res', 'fv_tracer.res', 'phy_data', 'sfc_data'] + for tile in range(1, self.n_tiles + 1)]: + data = [prefix, f"{dtg_prefix}.{file}"] + dep_dict = {'type': 'data', 'data': data, 'offset': [None, offset]} + deps.append(rocoto.add_dependency(dep_dict)) + prefix = f"{cpl_ic['BASE_CPLIC']}/{cpl_ic['CPL_ATMIC']}/@Y@m@d@H/mem000/med/" + data = [prefix, f"{dtg_prefix}.ufs.cpld.cpl.r.nc"] + dep_dict = {'type': 'data', 'data': data, 'offset': [None, offset]} deps.append(rocoto.add_dependency(dep_dict)) + else: + for file in ['gfs_ctrl.nc'] + \ + [f'{datatype}_data.tile{tile}.nc' + for datatype in ['gfs', 'sfc'] + for tile in range(1, self.n_tiles + 1)]: + data = f"{prefix}/{file}" + dep_dict = {'type': 'data', 'data': data} + deps.append(rocoto.add_dependency(dep_dict)) # Ocean ICs if self.app_config.do_ocean: ocn_res = f"{self._base.get('OCNRES', '025'):03d}" - prefix = f"{cpl_ic['BASE_CPLIC']}/{cpl_ic['CPL_OCNIC']}/@Y@m@d@H/mem000/ocean" - data = f"{prefix}/@Y@m@d.@H0000.MOM.res.nc" - dep_dict = {'type': 'data', 'data': data} + prefix = f"{cpl_ic['BASE_CPLIC']}/{cpl_ic['CPL_OCNIC']}/@Y@m@d@H/mem000/ocean/" + data = [prefix, f"{dtg_prefix}.MOM.res.nc"] + dep_dict = {'type': 'data', 'data': data, 'offset': [None, offset]} deps.append(rocoto.add_dependency(dep_dict)) if ocn_res in ['025']: # 0.25 degree ocean model also has these additional restarts for res in [f'res_{res_index}' for res_index in range(1, 4)]: - data = f"{prefix}/@Y@m@d.@H0000.MOM.{res}.nc" - dep_dict = {'type': 'data', 'data': data} + data = [prefix, f"{dtg_prefix}.MOM.{res}.nc"] + dep_dict = {'type': 'data', 'data': data, 'offset': [None, offset]} deps.append(rocoto.add_dependency(dep_dict)) # Ice ICs if self.app_config.do_ice: - prefix = f"{cpl_ic['BASE_CPLIC']}/{cpl_ic['CPL_ICEIC']}/@Y@m@d@H/mem000/ice" - data = f"{prefix}/@Y@m@d.@H0000.cice_model.res.nc" - dep_dict = {'type': 'data', 'data': data} + prefix = f"{cpl_ic['BASE_CPLIC']}/{cpl_ic['CPL_ICEIC']}/@Y@m@d@H/mem000/ice/" + data = [prefix, f"{dtg_prefix}.cice_model.res.nc"] + dep_dict = {'type': 'data', 'data': data, 'offset': [None, offset]} deps.append(rocoto.add_dependency(dep_dict)) # Wave ICs if self.app_config.do_wave: - prefix = f"{cpl_ic['BASE_CPLIC']}/{cpl_ic['CPL_WAVIC']}/@Y@m@d@H/mem000/wave" + prefix = f"{cpl_ic['BASE_CPLIC']}/{cpl_ic['CPL_WAVIC']}/@Y@m@d@H/mem000/wave/" for wave_grid in self._configs['waveinit']['waveGRD'].split(): - data = f"{prefix}/@Y@m@d.@H0000.restart.{wave_grid}" - dep_dict = {'type': 'data', 'data': data} + data = [prefix, f"{dtg_prefix}.restart.{wave_grid}"] + dep_dict = {'type': 'data', 'data': data, 'offset': [None, offset]} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep_condition='and', dep=deps) @@ -75,7 +88,7 @@ def stage_ic(self): def waveinit(self): resources = self.get_resource('waveinit') - task_name = f'waveinit' + task_name = f'wave_init' task_dict = {'task_name': task_name, 'resources': resources, 'envars': self.envars, @@ -89,21 +102,44 @@ def waveinit(self): return task - def fcst(self): + def prep_emissions(self): + deps = [] + dep_dict = {'type': 'task', 'name': f'stage_ic'} + deps.append(rocoto.add_dependency(dep_dict)) + dependencies = rocoto.create_dependency(dep=deps) - # TODO: Add real dependencies + resources = self.get_resource('prep_emissions') + task_name = 'prep_emissions' + task_dict = {'task_name': task_name, + 'resources': resources, + 'envars': self.envars, + 'cycledef': 'gefs', + 'command': f'{self.HOMEgfs}/jobs/rocoto/prep_emissions.sh', + 'job_name': f'{self.pslot}_{task_name}_@H', + 'log': f'{self.rotdir}/logs/@Y@m@d@H/{task_name}.log', + 'maxtries': '&MAXTRIES;' + } + task = rocoto.create_task(task_dict) + + return task + + def fcst(self): dependencies = [] dep_dict = {'type': 'task', 'name': f'stage_ic'} dependencies.append(rocoto.add_dependency(dep_dict)) if self.app_config.do_wave: - dep_dict = {'type': 'task', 'name': f'waveinit'} + dep_dict = {'type': 'task', 'name': f'wave_init'} + dependencies.append(rocoto.add_dependency(dep_dict)) + + if self.app_config.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) resources = self.get_resource('fcst') - task_name = f'fcst' + task_name = f'fcst_mem000' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -124,36 +160,389 @@ def efcs(self): dependencies.append(rocoto.add_dependency(dep_dict)) if self.app_config.do_wave: - dep_dict = {'type': 'task', 'name': f'waveinit'} + dep_dict = {'type': 'task', 'name': f'wave_init'} + dependencies.append(rocoto.add_dependency(dep_dict)) + + if self.app_config.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) efcsenvars = self.envars.copy() - efcsenvars.append(rocoto.create_envar(name='ENSGRP', value='#grp#')) - - groups = self._get_hybgroups(self._base['NMEM_ENS'], self._configs['efcs']['NMEM_EFCSGRP']) - var_dict = {'grp': groups} + efcsenvars_dict = {'ENSMEM': '#member#', + 'MEMDIR': 'mem#member#' + } + for key, value in efcsenvars_dict.items(): + efcsenvars.append(rocoto.create_envar(name=key, value=str(value))) resources = self.get_resource('efcs') - task_name = f'efcs#grp#' + task_name = f'fcst_mem#member#' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, 'envars': efcsenvars, 'cycledef': 'gefs', - 'command': f'{self.HOMEgfs}/jobs/rocoto/efcs.sh', + 'command': f'{self.HOMEgfs}/jobs/rocoto/fcst.sh', 'job_name': f'{self.pslot}_{task_name}_@H', 'log': f'{self.rotdir}/logs/@Y@m@d@H/{task_name}.log', 'maxtries': '&MAXTRIES;' } - metatask_dict = {'task_name': 'efmn', - 'var_dict': var_dict, + member_var_dict = {'member': ' '.join([f"{mem:03d}" for mem in range(1, self.nmem + 1)])} + metatask_dict = {'task_name': 'fcst_ens', + 'var_dict': member_var_dict, 'task_dict': task_dict } task = rocoto.create_task(metatask_dict) return task + + def atmos_prod(self): + return self._atmosoceaniceprod('atmos') + + def ocean_prod(self): + return self._atmosoceaniceprod('ocean') + + def ice_prod(self): + return self._atmosoceaniceprod('ice') + + def _atmosoceaniceprod(self, component: str): + + fhout_ocn_gfs = self._configs['base']['FHOUT_OCN_GFS'] + fhout_ice_gfs = self._configs['base']['FHOUT_ICE_GFS'] + products_dict = {'atmos': {'config': 'atmos_products', + 'history_path_tmpl': 'COM_ATMOS_MASTER_TMPL', + 'history_file_tmpl': f'{self.run}.t@Hz.master.grb2f#fhr#'}, + 'ocean': {'config': 'oceanice_products', + 'history_path_tmpl': 'COM_OCEAN_HISTORY_TMPL', + 'history_file_tmpl': f'{self.run}.ocean.t@Hz.{fhout_ocn_gfs}hr_avg.f#fhr_next#.nc'}, + 'ice': {'config': 'oceanice_products', + 'history_path_tmpl': 'COM_ICE_HISTORY_TMPL', + 'history_file_tmpl': f'{self.run}.ice.t@Hz.{fhout_ice_gfs}hr_avg.f#fhr#.nc'}} + + component_dict = products_dict[component] + config = component_dict['config'] + history_path_tmpl = component_dict['history_path_tmpl'] + history_file_tmpl = component_dict['history_file_tmpl'] + + resources = self.get_resource(config) + + history_path = self._template_to_rocoto_cycstring(self._base[history_path_tmpl], {'MEMDIR': 'mem#member#'}) + deps = [] + data = f'{history_path}/{history_file_tmpl}' + if component in ['ocean']: + dep_dict = {'type': 'data', 'data': data, 'age': 120} + deps.append(rocoto.add_dependency(dep_dict)) + dep_dict = {'type': 'task', 'name': 'fcst_mem#member#'} + deps.append(rocoto.add_dependency(dep_dict)) + dependencies = rocoto.create_dependency(dep=deps, dep_condition='or') + elif component in ['ice']: + command = f"{self.HOMEgfs}/ush/check_ice_netcdf.sh @Y @m @d @H #fhr# &ROTDIR; #member# {fhout_ice_gfs}" + dep_dict = {'type': 'sh', 'command': command} + deps.append(rocoto.add_dependency(dep_dict)) + dependencies = rocoto.create_dependency(dep=deps) + else: + dep_dict = {'type': 'data', 'data': data, 'age': 120} + deps.append(rocoto.add_dependency(dep_dict)) + dependencies = rocoto.create_dependency(dep=deps) + + postenvars = self.envars.copy() + postenvar_dict = {'ENSMEM': '#member#', + 'MEMDIR': 'mem#member#', + 'FHR3': '#fhr#', + 'COMPONENT': component} + for key, value in postenvar_dict.items(): + postenvars.append(rocoto.create_envar(name=key, value=str(value))) + + task_name = f'{component}_prod_mem#member#_f#fhr#' + task_dict = {'task_name': task_name, + 'resources': resources, + 'dependency': dependencies, + 'envars': postenvars, + 'cycledef': 'gefs', + 'command': f'{self.HOMEgfs}/jobs/rocoto/{config}.sh', + 'job_name': f'{self.pslot}_{task_name}_@H', + 'log': f'{self.rotdir}/logs/@Y@m@d@H/{task_name}.log', + 'maxtries': '&MAXTRIES;'} + + fhrs = self._get_forecast_hours('gefs', self._configs[config], component) + + # when replaying, atmos component does not have fhr 0, therefore remove 0 from fhrs + is_replay = self._configs[config]['REPLAY_ICS'] + if is_replay and component in ['atmos'] and 0 in fhrs: + fhrs.remove(0) + + # ocean/ice components do not have fhr 0 as they are averaged output + if component in ['ocean', 'ice'] and 0 in fhrs: + fhrs.remove(0) + + fhr_var_dict = {'fhr': ' '.join([f"{fhr:03d}" for fhr in fhrs])} + if component in ['ocean']: + fhrs_next = fhrs[1:] + [fhrs[-1] + (fhrs[-1] - fhrs[-2])] + fhr_var_dict['fhr_next'] = ' '.join([f"{fhr:03d}" for fhr in fhrs_next]) + + fhr_metatask_dict = {'task_name': f'{component}_prod_#member#', + 'task_dict': task_dict, + 'var_dict': fhr_var_dict} + + member_var_dict = {'member': ' '.join([f"{mem:03d}" for mem in range(0, self.nmem + 1)])} + member_metatask_dict = {'task_name': f'{component}_prod', + 'task_dict': fhr_metatask_dict, + 'var_dict': member_var_dict} + + task = rocoto.create_task(member_metatask_dict) + + return task + + def atmos_ensstat(self): + + resources = self.get_resource('atmos_ensstat') + + deps = [] + for member in range(0, self.nmem + 1): + task = f'atmos_prod_mem{member:03d}_f#fhr#' + dep_dict = {'type': 'task', 'name': task} + deps.append(rocoto.add_dependency(dep_dict)) + + dependencies = rocoto.create_dependency(dep_condition='and', dep=deps) + + postenvars = self.envars.copy() + postenvar_dict = {'FHR3': '#fhr#'} + for key, value in postenvar_dict.items(): + postenvars.append(rocoto.create_envar(name=key, value=str(value))) + + task_name = f'atmos_ensstat_f#fhr#' + task_dict = {'task_name': task_name, + 'resources': resources, + 'dependency': dependencies, + 'envars': postenvars, + 'cycledef': 'gefs', + 'command': f'{self.HOMEgfs}/jobs/rocoto/atmos_ensstat.sh', + 'job_name': f'{self.pslot}_{task_name}_@H', + 'log': f'{self.rotdir}/logs/@Y@m@d@H/{task_name}.log', + 'maxtries': '&MAXTRIES;'} + + fhrs = self._get_forecast_hours('gefs', self._configs['atmos_ensstat']) + + # when replaying, atmos component does not have fhr 0, therefore remove 0 from fhrs + is_replay = self._configs['atmos_ensstat']['REPLAY_ICS'] + if is_replay and 0 in fhrs: + fhrs.remove(0) + + fhr_var_dict = {'fhr': ' '.join([f"{fhr:03d}" for fhr in fhrs])} + + fhr_metatask_dict = {'task_name': f'atmos_ensstat', + 'task_dict': task_dict, + 'var_dict': fhr_var_dict} + + task = rocoto.create_task(fhr_metatask_dict) + + return task + + def wavepostsbs(self): + deps = [] + for wave_grid in self._configs['wavepostsbs']['waveGRD'].split(): + wave_hist_path = self._template_to_rocoto_cycstring(self._base["COM_WAVE_HISTORY_TMPL"], {'MEMDIR': 'mem#member#'}) + data = f'{wave_hist_path}/gefswave.out_grd.{wave_grid}.@Y@m@d.@H0000' + dep_dict = {'type': 'data', 'data': data} + deps.append(rocoto.add_dependency(dep_dict)) + dependencies = rocoto.create_dependency(dep_condition='and', dep=deps) + + wave_post_envars = self.envars.copy() + postenvar_dict = {'ENSMEM': '#member#', + 'MEMDIR': 'mem#member#', + } + for key, value in postenvar_dict.items(): + wave_post_envars.append(rocoto.create_envar(name=key, value=str(value))) + + resources = self.get_resource('wavepostsbs') + + task_name = f'wave_post_grid_mem#member#' + task_dict = {'task_name': task_name, + 'resources': resources, + 'dependency': dependencies, + 'envars': wave_post_envars, + 'cycledef': 'gefs', + 'command': f'{self.HOMEgfs}/jobs/rocoto/wavepostsbs.sh', + 'job_name': f'{self.pslot}_{task_name}_@H', + 'log': f'{self.rotdir}/logs/@Y@m@d@H/{task_name}.log', + 'maxtries': '&MAXTRIES;' + } + + member_var_dict = {'member': ' '.join([str(mem).zfill(3) for mem in range(0, self.nmem + 1)])} + member_metatask_dict = {'task_name': 'wave_post_grid', + 'task_dict': task_dict, + 'var_dict': member_var_dict + } + + task = rocoto.create_task(member_metatask_dict) + + return task + + def wavepostbndpnt(self): + deps = [] + dep_dict = {'type': 'task', 'name': f'fcst_mem#member#'} + deps.append(rocoto.add_dependency(dep_dict)) + dependencies = rocoto.create_dependency(dep=deps) + + wave_post_bndpnt_envars = self.envars.copy() + postenvar_dict = {'ENSMEM': '#member#', + 'MEMDIR': 'mem#member#', + } + for key, value in postenvar_dict.items(): + wave_post_bndpnt_envars.append(rocoto.create_envar(name=key, value=str(value))) + + resources = self.get_resource('wavepostbndpnt') + task_name = f'wave_post_bndpnt_mem#member#' + task_dict = {'task_name': task_name, + 'resources': resources, + 'dependency': dependencies, + 'envars': wave_post_bndpnt_envars, + 'cycledef': 'gefs', + 'command': f'{self.HOMEgfs}/jobs/rocoto/wavepostbndpnt.sh', + 'job_name': f'{self.pslot}_{task_name}_@H', + 'log': f'{self.rotdir}/logs/@Y@m@d@H/{task_name}.log', + 'maxtries': '&MAXTRIES;' + } + + member_var_dict = {'member': ' '.join([str(mem).zfill(3) for mem in range(0, self.nmem + 1)])} + member_metatask_dict = {'task_name': 'wave_post_bndpnt', + 'task_dict': task_dict, + 'var_dict': member_var_dict + } + + task = rocoto.create_task(member_metatask_dict) + + return task + + def wavepostbndpntbll(self): + deps = [] + atmos_hist_path = self._template_to_rocoto_cycstring(self._base["COM_ATMOS_HISTORY_TMPL"], {'MEMDIR': 'mem#member#'}) + + # The wavepostbndpntbll job runs on forecast hours up to FHMAX_WAV_IBP + last_fhr = self._configs['wave']['FHMAX_WAV_IBP'] + + data = f'{atmos_hist_path}/{self.run}.t@Hz.atm.logf{last_fhr:03d}.txt' + dep_dict = {'type': 'data', 'data': data} + deps.append(rocoto.add_dependency(dep_dict)) + + dep_dict = {'type': 'task', 'name': f'fcst_mem#member#'} + deps.append(rocoto.add_dependency(dep_dict)) + dependencies = rocoto.create_dependency(dep_condition='or', dep=deps) + + wave_post_bndpnt_bull_envars = self.envars.copy() + postenvar_dict = {'ENSMEM': '#member#', + 'MEMDIR': 'mem#member#', + } + for key, value in postenvar_dict.items(): + wave_post_bndpnt_bull_envars.append(rocoto.create_envar(name=key, value=str(value))) + + resources = self.get_resource('wavepostbndpntbll') + task_name = f'wave_post_bndpnt_bull_mem#member#' + task_dict = {'task_name': task_name, + 'resources': resources, + 'dependency': dependencies, + 'envars': wave_post_bndpnt_bull_envars, + 'cycledef': 'gefs', + 'command': f'{self.HOMEgfs}/jobs/rocoto/wavepostbndpntbll.sh', + 'job_name': f'{self.pslot}_{task_name}_@H', + 'log': f'{self.rotdir}/logs/@Y@m@d@H/{task_name}.log', + 'maxtries': '&MAXTRIES;' + } + + member_var_dict = {'member': ' '.join([str(mem).zfill(3) for mem in range(0, self.nmem + 1)])} + member_metatask_dict = {'task_name': 'wave_post_bndpnt_bull', + 'task_dict': task_dict, + 'var_dict': member_var_dict + } + + task = rocoto.create_task(member_metatask_dict) + + return task + + def wavepostpnt(self): + deps = [] + dep_dict = {'type': 'task', 'name': f'fcst_mem#member#'} + deps.append(rocoto.add_dependency(dep_dict)) + if self.app_config.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) + + wave_post_pnt_envars = self.envars.copy() + postenvar_dict = {'ENSMEM': '#member#', + 'MEMDIR': 'mem#member#', + } + for key, value in postenvar_dict.items(): + wave_post_pnt_envars.append(rocoto.create_envar(name=key, value=str(value))) + + resources = self.get_resource('wavepostpnt') + task_name = f'wave_post_pnt_mem#member#' + task_dict = {'task_name': task_name, + 'resources': resources, + 'dependency': dependencies, + 'envars': wave_post_pnt_envars, + 'cycledef': 'gefs', + 'command': f'{self.HOMEgfs}/jobs/rocoto/wavepostpnt.sh', + 'job_name': f'{self.pslot}_{task_name}_@H', + 'log': f'{self.rotdir}/logs/@Y@m@d@H/{task_name}.log', + 'maxtries': '&MAXTRIES;' + } + + member_var_dict = {'member': ' '.join([str(mem).zfill(3) for mem in range(0, self.nmem + 1)])} + member_metatask_dict = {'task_name': 'wave_post_pnt', + 'task_dict': task_dict, + 'var_dict': member_var_dict + } + + task = rocoto.create_task(member_metatask_dict) + + return task + + def extractvars(self): + deps = [] + if self.app_config.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: + dep_dict = {'type': 'metatask', 'name': 'ocean_prod_#member#'} + deps.append(rocoto.add_dependency(dep_dict)) + if self.app_config.do_ice: + dep_dict = {'type': 'metatask', 'name': 'ice_prod_#member#'} + deps.append(rocoto.add_dependency(dep_dict)) + if self.app_config.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) + extractvars_envars = self.envars.copy() + extractvars_dict = {'ENSMEM': '#member#', + 'MEMDIR': 'mem#member#', + } + for key, value in extractvars_dict.items(): + extractvars_envars.append(rocoto.create_envar(name=key, value=str(value))) + + resources = self.get_resource('extractvars') + task_name = f'extractvars_mem#member#' + task_dict = {'task_name': task_name, + 'resources': resources, + 'dependency': dependencies, + 'envars': extractvars_envars, + 'cycledef': 'gefs', + 'command': f'{self.HOMEgfs}/jobs/rocoto/extractvars.sh', + 'job_name': f'{self.pslot}_{task_name}_@H', + 'log': f'{self.rotdir}/logs/@Y@m@d@H/{task_name}.log', + 'maxtries': '&MAXTRIES;' + } + + member_var_dict = {'member': ' '.join([str(mem).zfill(3) for mem in range(0, self.nmem + 1)])} + member_metatask_dict = {'task_name': 'extractvars', + 'task_dict': task_dict, + 'var_dict': member_var_dict + } + + task = rocoto.create_task(member_metatask_dict) + + return task diff --git a/workflow/rocoto/gfs_tasks.py b/workflow/rocoto/gfs_tasks.py index 0f5e184192..960a7548ab 100644 --- a/workflow/rocoto/gfs_tasks.py +++ b/workflow/rocoto/gfs_tasks.py @@ -7,13 +7,13 @@ class GFSTasks(Tasks): - def __init__(self, app_config: AppConfig, cdump: str) -> None: - super().__init__(app_config, cdump) + def __init__(self, app_config: AppConfig, run: str) -> None: + super().__init__(app_config, run) @staticmethod - def _is_this_a_gdas_task(cdump, task_name): - if cdump != 'enkfgdas': - raise TypeError(f'{task_name} must be part of the "enkfgdas" cycle and not {cdump}') + def _is_this_a_gdas_task(run, task_name): + if run != 'enkfgdas': + raise TypeError(f'{task_name} must be part of the "enkfgdas" cycle and not {run}') # Specific Tasks begin here def stage_ic(self): @@ -71,12 +71,12 @@ def stage_ic(self): dependencies = rocoto.create_dependency(dep_condition='and', dep=deps) resources = self.get_resource('stage_ic') - task_name = f'{self.cdump}stage_ic' + task_name = f'{self.run}stage_ic' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, 'envars': self.envars, - 'cycledef': self.cdump, + 'cycledef': self.run, 'command': f'{self.HOMEgfs}/jobs/rocoto/stage_ic.sh', 'job_name': f'{self.pslot}_{task_name}_@H', 'log': f'{self.rotdir}/logs/@Y@m@d@H/{task_name}.log', @@ -96,25 +96,25 @@ 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_cdumps else False + gfs_enkf = True if self.app_config.do_hybvar and 'gfs' in self.app_config.eupd_runs else False deps = [] - dep_dict = {'type': 'metatask', 'name': 'gdasatmprod', 'offset': f"-{timedelta_to_HMS(self._base['cycle_interval'])}"} + dep_dict = {'type': 'metatask', 'name': 'gdasatmos_prod', 'offset': f"-{timedelta_to_HMS(self._base['cycle_interval'])}"} deps.append(rocoto.add_dependency(dep_dict)) data = f'{atm_hist_path}/gdas.t@Hz.atmf009.nc' dep_dict = {'type': 'data', 'data': data, 'offset': f"-{timedelta_to_HMS(self._base['cycle_interval'])}"} deps.append(rocoto.add_dependency(dep_dict)) - data = f'{dump_path}/{self.cdump}.t@Hz.updated.status.tm00.bufr_d' + data = f'{dump_path}/{self.run}.t@Hz.updated.status.tm00.bufr_d' dep_dict = {'type': 'data', 'data': data} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep_condition='and', dep=deps) - cycledef = self.cdump - if self.cdump in ['gfs'] and gfs_enkf and gfs_cyc != 4: + cycledef = self.run + if self.run in ['gfs'] and gfs_enkf and gfs_cyc != 4: cycledef = 'gdas' resources = self.get_resource('prep') - task_name = f'{self.cdump}prep' + task_name = f'{self.run}prep' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -134,17 +134,17 @@ def waveinit(self): resources = self.get_resource('waveinit') dependencies = None - cycledef = 'gdas_half,gdas' if self.cdump in ['gdas'] else self.cdump + cycledef = 'gdas_half,gdas' if self.run in ['gdas'] else self.run if self.app_config.mode in ['cycled']: deps = [] - dep_dict = {'type': 'task', 'name': f'{self.cdump}prep'} + dep_dict = {'type': 'task', 'name': f'{self.run}prep'} deps.append(rocoto.add_dependency(dep_dict)) - if self.cdump in ['gdas']: + if self.run in ['gdas']: dep_dict = {'type': 'cycleexist', 'condition': 'not', 'offset': f"-{timedelta_to_HMS(self._base['cycle_interval'])}"} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep_condition='or', dep=deps) - task_name = f'{self.cdump}waveinit' + task_name = f'{self.run}waveinit' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -163,12 +163,12 @@ def waveinit(self): def waveprep(self): deps = [] - dep_dict = {'type': 'task', 'name': f'{self.cdump}waveinit'} + dep_dict = {'type': 'task', 'name': f'{self.run}waveinit'} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep=deps) - cycledef = 'gdas_half,gdas' if self.cdump in ['gdas'] else self.cdump + cycledef = 'gdas_half,gdas' if self.run in ['gdas'] else self.run resources = self.get_resource('waveprep') - task_name = f'{self.cdump}waveprep' + task_name = f'{self.run}waveprep' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -197,11 +197,11 @@ def aerosol_init(self): dep_dict = {'type': 'data', 'data': data} deps.append(rocoto.add_dependency(dep_dict)) - # Calculate offset based on CDUMP = gfs | gdas + # Calculate offset based on RUN = gfs | gdas interval = None - if self.cdump in ['gfs']: + if self.run in ['gfs']: interval = self._base['INTERVAL_GFS'] - elif self.cdump in ['gdas']: + elif self.run in ['gdas']: interval = self._base['INTERVAL'] offset = timedelta_to_HMS(-interval) @@ -219,7 +219,7 @@ def aerosol_init(self): cycledef = 'gfs_seq' resources = self.get_resource('aerosol_init') - task_name = f'{self.cdump}aerosol_init' + task_name = f'{self.run}aerosol_init' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -237,7 +237,7 @@ def aerosol_init(self): def anal(self): deps = [] - dep_dict = {'type': 'task', 'name': f'{self.cdump}prep'} + dep_dict = {'type': 'task', 'name': f'{self.run}prep'} deps.append(rocoto.add_dependency(dep_dict)) if self.app_config.do_hybvar: dep_dict = {'type': 'metatask', 'name': 'enkfgdasepmn', 'offset': f"-{timedelta_to_HMS(self._base['cycle_interval'])}"} @@ -247,12 +247,12 @@ def anal(self): dependencies = rocoto.create_dependency(dep=deps) resources = self.get_resource('anal') - task_name = f'{self.cdump}anal' + task_name = f'{self.run}anal' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, 'envars': self.envars, - 'cycledef': self.cdump.replace('enkf', ''), + 'cycledef': self.run.replace('enkf', ''), 'command': f'{self.HOMEgfs}/jobs/rocoto/anal.sh', 'job_name': f'{self.pslot}_{task_name}_@H', 'log': f'{self.rotdir}/logs/@Y@m@d@H/{task_name}.log', @@ -267,24 +267,24 @@ def sfcanl(self): deps = [] if self.app_config.do_jediatmvar: - dep_dict = {'type': 'task', 'name': f'{self.cdump}atmanlfinal'} + dep_dict = {'type': 'task', 'name': f'{self.run}atmanlfinal'} else: - dep_dict = {'type': 'task', 'name': f'{self.cdump}anal'} + dep_dict = {'type': 'task', 'name': f'{self.run}anal'} deps.append(rocoto.add_dependency(dep_dict)) - if self.app_config.do_jedilandda: - dep_dict = {'type': 'task', 'name': f'{self.cdump}landanl'} + if self.app_config.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) else: dependencies = rocoto.create_dependency(dep=deps) resources = self.get_resource('sfcanl') - task_name = f'{self.cdump}sfcanl' + task_name = f'{self.run}sfcanl' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, 'envars': self.envars, - 'cycledef': self.cdump.replace('enkf', ''), + 'cycledef': self.run.replace('enkf', ''), 'command': f'{self.HOMEgfs}/jobs/rocoto/sfcanl.sh', 'job_name': f'{self.pslot}_{task_name}_@H', 'log': f'{self.rotdir}/logs/@Y@m@d@H/{task_name}.log', @@ -299,24 +299,24 @@ def analcalc(self): deps = [] if self.app_config.do_jediatmvar: - dep_dict = {'type': 'task', 'name': f'{self.cdump}atmanlfinal'} + dep_dict = {'type': 'task', 'name': f'{self.run}atmanlfinal'} else: - dep_dict = {'type': 'task', 'name': f'{self.cdump}anal'} + dep_dict = {'type': 'task', 'name': f'{self.run}anal'} deps.append(rocoto.add_dependency(dep_dict)) - dep_dict = {'type': 'task', 'name': f'{self.cdump}sfcanl'} + dep_dict = {'type': 'task', 'name': f'{self.run}sfcanl'} deps.append(rocoto.add_dependency(dep_dict)) - if self.app_config.do_hybvar and self.cdump in ['gdas']: + if self.app_config.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) resources = self.get_resource('analcalc') - task_name = f'{self.cdump}analcalc' + task_name = f'{self.run}analcalc' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, 'envars': self.envars, - 'cycledef': self.cdump.replace('enkf', ''), + 'cycledef': self.run.replace('enkf', ''), 'command': f'{self.HOMEgfs}/jobs/rocoto/analcalc.sh', 'job_name': f'{self.pslot}_{task_name}_@H', 'log': f'{self.rotdir}/logs/@Y@m@d@H/{task_name}.log', @@ -330,17 +330,17 @@ def analcalc(self): def analdiag(self): deps = [] - dep_dict = {'type': 'task', 'name': f'{self.cdump}anal'} + dep_dict = {'type': 'task', 'name': f'{self.run}anal'} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep_condition='and', dep=deps) resources = self.get_resource('analdiag') - task_name = f'{self.cdump}analdiag' + task_name = f'{self.run}analdiag' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, 'envars': self.envars, - 'cycledef': self.cdump.replace('enkf', ''), + 'cycledef': self.run.replace('enkf', ''), 'command': f'{self.HOMEgfs}/jobs/rocoto/analdiag.sh', 'job_name': f'{self.pslot}_{task_name}_@H', 'log': f'{self.rotdir}/logs/@Y@m@d@H/{task_name}.log', @@ -354,17 +354,17 @@ def analdiag(self): def prepatmiodaobs(self): deps = [] - dep_dict = {'type': 'task', 'name': f'{self.cdump}prep'} + dep_dict = {'type': 'task', 'name': f'{self.run}prep'} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep=deps) resources = self.get_resource('prepatmiodaobs') - task_name = f'{self.cdump}prepatmiodaobs' + task_name = f'{self.run}prepatmiodaobs' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, 'envars': self.envars, - 'cycledef': self.cdump.replace('enkf', ''), + 'cycledef': self.run.replace('enkf', ''), 'command': f'{self.HOMEgfs}/jobs/rocoto/prepatmiodaobs.sh', 'job_name': f'{self.pslot}_{task_name}_@H', 'log': f'{self.rotdir}/logs/@Y@m@d@H/{task_name}.log', @@ -378,7 +378,7 @@ def prepatmiodaobs(self): def atmanlinit(self): deps = [] - dep_dict = {'type': 'task', 'name': f'{self.cdump}prepatmiodaobs'} + dep_dict = {'type': 'task', 'name': f'{self.run}prepatmiodaobs'} deps.append(rocoto.add_dependency(dep_dict)) if self.app_config.do_hybvar: dep_dict = {'type': 'metatask', 'name': 'enkfgdasepmn', 'offset': f"-{timedelta_to_HMS(self._base['cycle_interval'])}"} @@ -388,14 +388,14 @@ 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_cdumps else False + gfs_enkf = True if self.app_config.do_hybvar and 'gfs' in self.app_config.eupd_runs else False - cycledef = self.cdump - if self.cdump in ['gfs'] and gfs_enkf and gfs_cyc != 4: + cycledef = self.run + if self.run in ['gfs'] and gfs_enkf and gfs_cyc != 4: cycledef = 'gdas' resources = self.get_resource('atmanlinit') - task_name = f'{self.cdump}atmanlinit' + task_name = f'{self.run}atmanlinit' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -411,21 +411,45 @@ def atmanlinit(self): return task - def atmanlrun(self): + def atmanlvar(self): deps = [] - dep_dict = {'type': 'task', 'name': f'{self.cdump}atmanlinit'} + dep_dict = {'type': 'task', 'name': f'{self.run}atmanlinit'} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep=deps) - resources = self.get_resource('atmanlrun') - task_name = f'{self.cdump}atmanlrun' + resources = self.get_resource('atmanlvar') + task_name = f'{self.run}atmanlvar' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, 'envars': self.envars, - 'cycledef': self.cdump.replace('enkf', ''), - 'command': f'{self.HOMEgfs}/jobs/rocoto/atmanlrun.sh', + 'cycledef': self.run.replace('enkf', ''), + 'command': f'{self.HOMEgfs}/jobs/rocoto/atmanlvar.sh', + 'job_name': f'{self.pslot}_{task_name}_@H', + 'log': f'{self.rotdir}/logs/@Y@m@d@H/{task_name}.log', + 'maxtries': '&MAXTRIES;' + } + + task = rocoto.create_task(task_dict) + + return task + + def atmanlfv3inc(self): + + deps = [] + dep_dict = {'type': 'task', 'name': f'{self.run}atmanlvar'} + deps.append(rocoto.add_dependency(dep_dict)) + dependencies = rocoto.create_dependency(dep=deps) + + resources = self.get_resource('atmanlfv3inc') + task_name = f'{self.run}atmanlfv3inc' + task_dict = {'task_name': task_name, + 'resources': resources, + 'dependency': dependencies, + 'envars': self.envars, + 'cycledef': self.run.replace('enkf', ''), + 'command': f'{self.HOMEgfs}/jobs/rocoto/atmanlfv3inc.sh', 'job_name': f'{self.pslot}_{task_name}_@H', 'log': f'{self.rotdir}/logs/@Y@m@d@H/{task_name}.log', 'maxtries': '&MAXTRIES;' @@ -438,17 +462,17 @@ def atmanlrun(self): def atmanlfinal(self): deps = [] - dep_dict = {'type': 'task', 'name': f'{self.cdump}atmanlrun'} + dep_dict = {'type': 'task', 'name': f'{self.run}atmanlfv3inc'} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep_condition='and', dep=deps) resources = self.get_resource('atmanlfinal') - task_name = f'{self.cdump}atmanlfinal' + task_name = f'{self.run}atmanlfinal' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, 'envars': self.envars, - 'cycledef': self.cdump.replace('enkf', ''), + 'cycledef': self.run.replace('enkf', ''), 'command': f'{self.HOMEgfs}/jobs/rocoto/atmanlfinal.sh', 'job_name': f'{self.pslot}_{task_name}_@H', 'log': f'{self.rotdir}/logs/@Y@m@d@H/{task_name}.log', @@ -459,20 +483,45 @@ def atmanlfinal(self): return task + def prepobsaero(self): + deps = [] + dep_dict = {'type': 'task', 'name': f'{self.run}prep'} + deps.append(rocoto.add_dependency(dep_dict)) + dependencies = rocoto.create_dependency(dep_condition='and', dep=deps) + + resources = self.get_resource('prepobsaero') + task_name = f'{self.run}prepobsaero' + task_dict = {'task_name': task_name, + 'resources': resources, + 'dependency': dependencies, + 'envars': self.envars, + 'cycledef': self.run.replace('enkf', ''), + 'command': f'{self.HOMEgfs}/jobs/rocoto/prepobsaero.sh', + 'job_name': f'{self.pslot}_{task_name}_@H', + 'log': f'{self.rotdir}/logs/@Y@m@d@H/{task_name}.log', + 'maxtries': '&MAXTRIES;' + } + + task = rocoto.create_task(task_dict) + + return task + def aeroanlinit(self): deps = [] - dep_dict = {'type': 'task', 'name': f'{self.cdump}prep'} + dep_dict = {'type': 'task', 'name': f'{self.run}prep'} + if self.app_config.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) resources = self.get_resource('aeroanlinit') - task_name = f'{self.cdump}aeroanlinit' + task_name = f'{self.run}aeroanlinit' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, 'envars': self.envars, - 'cycledef': self.cdump.replace('enkf', ''), + 'cycledef': self.run.replace('enkf', ''), 'command': f'{self.HOMEgfs}/jobs/rocoto/aeroanlinit.sh', 'job_name': f'{self.pslot}_{task_name}_@H', 'log': f'{self.rotdir}/logs/@Y@m@d@H/{task_name}.log', @@ -486,17 +535,17 @@ def aeroanlinit(self): def aeroanlrun(self): deps = [] - dep_dict = {'type': 'task', 'name': f'{self.cdump}aeroanlinit'} + dep_dict = {'type': 'task', 'name': f'{self.run}aeroanlinit'} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep=deps) resources = self.get_resource('aeroanlrun') - task_name = f'{self.cdump}aeroanlrun' + task_name = f'{self.run}aeroanlrun' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, 'envars': self.envars, - 'cycledef': self.cdump.replace('enkf', ''), + 'cycledef': self.run.replace('enkf', ''), 'command': f'{self.HOMEgfs}/jobs/rocoto/aeroanlrun.sh', 'job_name': f'{self.pslot}_{task_name}_@H', 'log': f'{self.rotdir}/logs/@Y@m@d@H/{task_name}.log', @@ -510,17 +559,17 @@ def aeroanlrun(self): def aeroanlfinal(self): deps = [] - dep_dict = {'type': 'task', 'name': f'{self.cdump}aeroanlrun'} + dep_dict = {'type': 'task', 'name': f'{self.run}aeroanlrun'} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep_condition='and', dep=deps) resources = self.get_resource('aeroanlfinal') - task_name = f'{self.cdump}aeroanlfinal' + task_name = f'{self.run}aeroanlfinal' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, 'envars': self.envars, - 'cycledef': self.cdump.replace('enkf', ''), + 'cycledef': self.run.replace('enkf', ''), 'command': f'{self.HOMEgfs}/jobs/rocoto/aeroanlfinal.sh', 'job_name': f'{self.pslot}_{task_name}_@H', 'log': f'{self.rotdir}/logs/@Y@m@d@H/{task_name}.log', @@ -531,21 +580,21 @@ def aeroanlfinal(self): return task - def preplandobs(self): + def prepsnowobs(self): deps = [] - dep_dict = {'type': 'task', 'name': f'{self.cdump}prep'} + dep_dict = {'type': 'task', 'name': f'{self.run}prep'} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep=deps) - resources = self.get_resource('preplandobs') - task_name = f'{self.cdump}preplandobs' + resources = self.get_resource('prepsnowobs') + task_name = f'{self.run}prepsnowobs' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, 'envars': self.envars, - 'cycledef': self.cdump.replace('enkf', ''), - 'command': f'{self.HOMEgfs}/jobs/rocoto/preplandobs.sh', + 'cycledef': self.run.replace('enkf', ''), + 'command': f'{self.HOMEgfs}/jobs/rocoto/prepsnowobs.sh', 'job_name': f'{self.pslot}_{task_name}_@H', 'log': f'{self.rotdir}/logs/@Y@m@d@H/{task_name}.log', 'maxtries': '&MAXTRIES;' @@ -555,21 +604,21 @@ def preplandobs(self): return task - def landanl(self): + def snowanl(self): deps = [] - dep_dict = {'type': 'task', 'name': f'{self.cdump}preplandobs'} + dep_dict = {'type': 'task', 'name': f'{self.run}prepsnowobs'} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep=deps) - resources = self.get_resource('landanl') - task_name = f'{self.cdump}landanl' + resources = self.get_resource('snowanl') + task_name = f'{self.run}snowanl' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, 'envars': self.envars, - 'cycledef': self.cdump.replace('enkf', ''), - 'command': f'{self.HOMEgfs}/jobs/rocoto/landanl.sh', + 'cycledef': self.run.replace('enkf', ''), + 'command': f'{self.HOMEgfs}/jobs/rocoto/snowanl.sh', 'job_name': f'{self.pslot}_{task_name}_@H', 'log': f'{self.rotdir}/logs/@Y@m@d@H/{task_name}.log', 'maxtries': '&MAXTRIES;' @@ -583,18 +632,18 @@ def prepoceanobs(self): ocean_hist_path = self._template_to_rocoto_cycstring(self._base["COM_OCEAN_HISTORY_TMPL"], {'RUN': 'gdas'}) deps = [] - data = f'{ocean_hist_path}/gdas.t@Hz.ocnf009.nc' + data = f'{ocean_hist_path}/gdas.ocean.t@Hz.inst.f009.nc' dep_dict = {'type': 'data', 'data': data, 'offset': f"-{timedelta_to_HMS(self._base['cycle_interval'])}"} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep=deps) resources = self.get_resource('prepoceanobs') - task_name = f'{self.cdump}prepoceanobs' + task_name = f'{self.run}prepoceanobs' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, 'envars': self.envars, - 'cycledef': self.cdump.replace('enkf', ''), + 'cycledef': self.run.replace('enkf', ''), 'command': f'{self.HOMEgfs}/jobs/rocoto/prepoceanobs.sh', 'job_name': f'{self.pslot}_{task_name}_@H', 'log': f'{self.rotdir}/logs/@Y@m@d@H/{task_name}.log', @@ -605,21 +654,24 @@ def prepoceanobs(self): return task - def ocnanalprep(self): + def marinebmat(self): + + ocean_hist_path = self._template_to_rocoto_cycstring(self._base["COM_OCEAN_HISTORY_TMPL"], {'RUN': 'gdas'}) deps = [] - dep_dict = {'type': 'task', 'name': f'{self.cdump}prepoceanobs'} + data = f'{ocean_hist_path}/gdas.ocean.t@Hz.inst.f009.nc' + dep_dict = {'type': 'data', 'data': data, 'offset': f"-{timedelta_to_HMS(self._base['cycle_interval'])}"} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep=deps) - resources = self.get_resource('ocnanalprep') - task_name = f'{self.cdump}ocnanalprep' + resources = self.get_resource('marinebmat') + task_name = f'{self.run}marinebmat' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, 'envars': self.envars, - 'cycledef': self.cdump.replace('enkf', ''), - 'command': f'{self.HOMEgfs}/jobs/rocoto/ocnanalprep.sh', + 'cycledef': self.run.replace('enkf', ''), + 'command': f'{self.HOMEgfs}/jobs/rocoto/marinebmat.sh', 'job_name': f'{self.pslot}_{task_name}_@H', 'log': f'{self.rotdir}/logs/@Y@m@d@H/{task_name}.log', 'maxtries': '&MAXTRIES;' @@ -629,21 +681,25 @@ def ocnanalprep(self): return task - def ocnanalbmat(self): + def ocnanalprep(self): deps = [] - dep_dict = {'type': 'task', 'name': f'{self.cdump}ocnanalprep'} + dep_dict = {'type': 'task', 'name': f'{self.run}prepoceanobs'} deps.append(rocoto.add_dependency(dep_dict)) - dependencies = rocoto.create_dependency(dep=deps) + dep_dict = {'type': 'task', 'name': f'{self.run}marinebmat'} + deps.append(rocoto.add_dependency(dep_dict)) + dep_dict = {'type': 'task', 'name': 'gdasfcst', '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) - resources = self.get_resource('ocnanalbmat') - task_name = f'{self.cdump}ocnanalbmat' + resources = self.get_resource('ocnanalprep') + task_name = f'{self.run}ocnanalprep' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, 'envars': self.envars, - 'cycledef': self.cdump.replace('enkf', ''), - 'command': f'{self.HOMEgfs}/jobs/rocoto/ocnanalbmat.sh', + 'cycledef': self.run.replace('enkf', ''), + 'command': f'{self.HOMEgfs}/jobs/rocoto/ocnanalprep.sh', 'job_name': f'{self.pslot}_{task_name}_@H', 'log': f'{self.rotdir}/logs/@Y@m@d@H/{task_name}.log', 'maxtries': '&MAXTRIES;' @@ -656,17 +712,17 @@ def ocnanalbmat(self): def ocnanalrun(self): deps = [] - dep_dict = {'type': 'task', 'name': f'{self.cdump}ocnanalbmat'} + dep_dict = {'type': 'task', 'name': f'{self.run}ocnanalprep'} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep=deps) resources = self.get_resource('ocnanalrun') - task_name = f'{self.cdump}ocnanalrun' + task_name = f'{self.run}ocnanalrun' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, 'envars': self.envars, - 'cycledef': self.cdump.replace('enkf', ''), + 'cycledef': self.run.replace('enkf', ''), 'command': f'{self.HOMEgfs}/jobs/rocoto/ocnanalrun.sh', 'job_name': f'{self.pslot}_{task_name}_@H', 'log': f'{self.rotdir}/logs/@Y@m@d@H/{task_name}.log', @@ -677,24 +733,51 @@ def ocnanalrun(self): return task + def ocnanalecen(self): + + deps = [] + dep_dict = {'type': 'task', 'name': f'{self.run}ocnanalrun'} + deps.append(rocoto.add_dependency(dep_dict)) + dependencies = rocoto.create_dependency(dep=deps) + + resources = self.get_resource('ocnanalecen') + task_name = f'{self.run}ocnanalecen' + task_dict = {'task_name': task_name, + 'resources': resources, + 'dependency': dependencies, + 'envars': self.envars, + 'cycledef': self.run.replace('enkf', ''), + 'command': f'{self.HOMEgfs}/jobs/rocoto/ocnanalecen.sh', + 'job_name': f'{self.pslot}_{task_name}_@H', + 'log': f'{self.rotdir}/logs/@Y@m@d@H/{task_name}.log', + 'maxtries': '&MAXTRIES;' + } + + task = rocoto.create_task(task_dict) + + return task + def ocnanalchkpt(self): deps = [] - dep_dict = {'type': 'task', 'name': f'{self.cdump}ocnanalrun'} + if self.app_config.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: - data = f'&ROTDIR;/{self.cdump}.@Y@m@d/@H/atmos/{self.cdump}.t@Hz.sfcanl.nc' + 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)) dependencies = rocoto.create_dependency(dep_condition='and', dep=deps) resources = self.get_resource('ocnanalchkpt') - task_name = f'{self.cdump}ocnanalchkpt' + task_name = f'{self.run}ocnanalchkpt' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, 'envars': self.envars, - 'cycledef': self.cdump.replace('enkf', ''), + 'cycledef': self.run.replace('enkf', ''), 'command': f'{self.HOMEgfs}/jobs/rocoto/ocnanalchkpt.sh', 'job_name': f'{self.pslot}_{task_name}_@H', 'log': f'{self.rotdir}/logs/@Y@m@d@H/{task_name}.log', @@ -708,17 +791,17 @@ def ocnanalchkpt(self): def ocnanalpost(self): deps = [] - dep_dict = {'type': 'task', 'name': f'{self.cdump}ocnanalchkpt'} + dep_dict = {'type': 'task', 'name': f'{self.run}ocnanalchkpt'} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep_condition='and', dep=deps) resources = self.get_resource('ocnanalpost') - task_name = f'{self.cdump}ocnanalpost' + task_name = f'{self.run}ocnanalpost' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, 'envars': self.envars, - 'cycledef': self.cdump.replace('enkf', ''), + 'cycledef': self.run.replace('enkf', ''), 'command': f'{self.HOMEgfs}/jobs/rocoto/ocnanalpost.sh', 'job_name': f'{self.pslot}_{task_name}_@H', 'log': f'{self.rotdir}/logs/@Y@m@d@H/{task_name}.log', @@ -732,17 +815,17 @@ def ocnanalpost(self): def ocnanalvrfy(self): deps = [] - dep_dict = {'type': 'task', 'name': f'{self.cdump}ocnanalpost'} + dep_dict = {'type': 'task', 'name': f'{self.run}ocnanalpost'} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep_condition='and', dep=deps) resources = self.get_resource('ocnanalvrfy') - task_name = f'{self.cdump}ocnanalvrfy' + task_name = f'{self.run}ocnanalvrfy' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, 'envars': self.envars, - 'cycledef': self.cdump.replace('enkf', ''), + 'cycledef': self.run.replace('enkf', ''), 'command': f'{self.HOMEgfs}/jobs/rocoto/ocnanalvrfy.sh', 'job_name': f'{self.pslot}_{task_name}_@H', 'log': f'{self.rotdir}/logs/@Y@m@d@H/{task_name}.log', @@ -770,24 +853,26 @@ def fcst(self): def _fcst_forecast_only(self): dependencies = [] - dep_dict = {'type': 'task', 'name': f'{self.cdump}stage_ic'} + 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.cdump in self.app_config.wave_cdumps: + 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' - dep_dict = {'type': 'task', 'name': f'{self.cdump}{wave_job}'} + dep_dict = {'type': 'task', 'name': f'{self.run}{wave_job}'} dependencies.append(rocoto.add_dependency(dep_dict)) - if self.app_config.do_aero: - # Calculate offset based on CDUMP = gfs | gdas + if self.app_config.do_aero and \ + self.run in self.app_config.aero_fcst_runs and \ + not self._base['EXP_WARM_START']: + # Calculate offset based on RUN = gfs | gdas interval = None - if self.cdump in ['gfs']: + if self.run in ['gfs']: interval = self._base['INTERVAL_GFS'] - elif self.cdump in ['gdas']: + elif self.run in ['gdas']: interval = self._base['INTERVAL'] offset = timedelta_to_HMS(-interval) deps = [] - dep_dict = {'type': 'task', 'name': f'{self.cdump}aerosol_init'} + dep_dict = {'type': 'task', 'name': f'{self.run}aerosol_init'} deps.append(rocoto.add_dependency(dep_dict)) dep_dict = {'type': 'cycleexist', 'condition': 'not', 'offset': offset} deps.append(rocoto.add_dependency(dep_dict)) @@ -796,12 +881,12 @@ def _fcst_forecast_only(self): dependencies = rocoto.create_dependency(dep_condition='and', dep=dependencies) resources = self.get_resource('fcst') - task_name = f'{self.cdump}fcst' + task_name = f'{self.run}fcst' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, 'envars': self.envars, - 'cycledef': self.cdump.replace('enkf', ''), + 'cycledef': self.run.replace('enkf', ''), 'command': f'{self.HOMEgfs}/jobs/rocoto/fcst.sh', 'job_name': f'{self.pslot}_{task_name}_@H', 'log': f'{self.rotdir}/logs/@Y@m@d@H/{task_name}.log', @@ -814,38 +899,38 @@ def _fcst_forecast_only(self): def _fcst_cycled(self): - dep_dict = {'type': 'task', 'name': f'{self.cdump}sfcanl'} + dep_dict = {'type': 'task', 'name': f'{self.run}sfcanl'} dep = rocoto.add_dependency(dep_dict) dependencies = rocoto.create_dependency(dep=dep) if self.app_config.do_jediocnvar: - dep_dict = {'type': 'task', 'name': f'{self.cdump}ocnanalpost'} + dep_dict = {'type': 'task', 'name': f'{self.run}ocnanalpost'} dependencies.append(rocoto.add_dependency(dep_dict)) - if self.app_config.do_aero: - dep_dict = {'type': 'task', 'name': f'{self.cdump}aeroanlfinal'} + if self.app_config.do_aero and self.run in self.app_config.aero_anl_runs: + dep_dict = {'type': 'task', 'name': f'{self.run}aeroanlfinal'} dependencies.append(rocoto.add_dependency(dep_dict)) - if self.app_config.do_jedilandda: - dep_dict = {'type': 'task', 'name': f'{self.cdump}landanl'} + if self.app_config.do_jedisnowda: + dep_dict = {'type': 'task', 'name': f'{self.run}snowanl'} dependencies.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep_condition='and', dep=dependencies) - if self.cdump in ['gdas']: + if self.run in ['gdas']: dep_dict = {'type': 'cycleexist', 'condition': 'not', 'offset': f"-{timedelta_to_HMS(self._base['cycle_interval'])}"} dependencies.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep_condition='or', dep=dependencies) - if self.app_config.do_wave and self.cdump in self.app_config.wave_cdumps: - dep_dict = {'type': 'task', 'name': f'{self.cdump}waveprep'} + if self.app_config.do_wave and self.run in self.app_config.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) - cycledef = 'gdas_half,gdas' if self.cdump in ['gdas'] else self.cdump + cycledef = 'gdas_half,gdas' if self.run in ['gdas'] else self.run resources = self.get_resource('fcst') - task_name = f'{self.cdump}fcst' + task_name = f'{self.run}fcst' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -863,30 +948,30 @@ def _fcst_cycled(self): def atmanlupp(self): postenvars = self.envars.copy() - postenvar_dict = {'FHRLST': 'f000', + postenvar_dict = {'FHR3': '000', 'UPP_RUN': 'analysis'} for key, value in postenvar_dict.items(): postenvars.append(rocoto.create_envar(name=key, value=str(value))) atm_anl_path = self._template_to_rocoto_cycstring(self._base["COM_ATMOS_ANALYSIS_TMPL"]) deps = [] - data = f'{atm_anl_path}/{self.cdump}.t@Hz.atmanl.nc' + data = f'{atm_anl_path}/{self.run}.t@Hz.atmanl.nc' dep_dict = {'type': 'data', 'data': data, 'age': 120} deps.append(rocoto.add_dependency(dep_dict)) - data = f'{atm_anl_path}/{self.cdump}.t@Hz.sfcanl.nc' + data = f'{atm_anl_path}/{self.run}.t@Hz.sfcanl.nc' dep_dict = {'type': 'data', 'data': data, 'age': 120} deps.append(rocoto.add_dependency(dep_dict)) - data = f'{atm_anl_path}/{self.cdump}.t@Hz.loganl.txt' + data = f'{atm_anl_path}/{self.run}.t@Hz.loganl.txt' dep_dict = {'type': 'data', 'data': data, 'age': 60} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep=deps, dep_condition='and') resources = self.get_resource('upp') - task_name = f'{self.cdump}atmanlupp' + task_name = f'{self.run}atmanlupp' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, 'envars': postenvars, - 'cycledef': self.cdump.replace('enkf', ''), + 'cycledef': self.run.replace('enkf', ''), 'command': f'{self.HOMEgfs}/jobs/rocoto/upp.sh', 'job_name': f'{self.pslot}_{task_name}_@H', 'log': f'{self.rotdir}/logs/@Y@m@d@H/{task_name}.log', @@ -899,23 +984,23 @@ def atmanlupp(self): def atmanlprod(self): postenvars = self.envars.copy() - postenvar_dict = {'FHRLST': '-f001'} + postenvar_dict = {'FHR3': '-001'} for key, value in postenvar_dict.items(): postenvars.append(rocoto.create_envar(name=key, value=str(value))) atm_master_path = self._template_to_rocoto_cycstring(self._base["COM_ATMOS_MASTER_TMPL"]) deps = [] - data = f'{atm_master_path}/{self.cdump}.t@Hz.master.grb2anl' + data = f'{atm_master_path}/{self.run}.t@Hz.master.grb2anl' dep_dict = {'type': 'data', 'data': data, 'age': 120} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep=deps) resources = self.get_resource('atmos_products') - task_name = f'{self.cdump}atmanlprod' + task_name = f'{self.run}atmanlprod' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, 'envars': postenvars, - 'cycledef': self.cdump.replace('enkf', ''), + 'cycledef': self.run.replace('enkf', ''), 'command': f'{self.HOMEgfs}/jobs/rocoto/atmos_products.sh', 'job_name': f'{self.pslot}_{task_name}_@H', 'log': f'{self.rotdir}/logs/@Y@m@d@H/{task_name}.log', @@ -926,39 +1011,6 @@ def atmanlprod(self): return task - @staticmethod - def _get_ufs_postproc_grps(cdump, config): - - fhmin = config['FHMIN'] - fhmax = config['FHMAX'] - fhout = config['FHOUT'] - - # Get a list of all forecast hours - fhrs = [] - if cdump in ['gdas']: - fhrs = range(fhmin, fhmax + fhout, fhout) - elif cdump in ['gfs']: - fhmax = np.max( - [config['FHMAX_GFS_00'], config['FHMAX_GFS_06'], config['FHMAX_GFS_12'], config['FHMAX_GFS_18']]) - fhout = config['FHOUT_GFS'] - fhmax_hf = config['FHMAX_HF_GFS'] - fhout_hf = config['FHOUT_HF_GFS'] - fhrs_hf = range(fhmin, fhmax_hf + fhout_hf, fhout_hf) - fhrs = list(fhrs_hf) + list(range(fhrs_hf[-1] + fhout, fhmax + fhout, fhout)) - - nfhrs_per_grp = config.get('NFHRS_PER_GROUP', 1) - ngrps = len(fhrs) // nfhrs_per_grp if len(fhrs) % nfhrs_per_grp == 0 else len(fhrs) // nfhrs_per_grp + 1 - - fhrs = [f'f{fhr:03d}' for fhr in fhrs] - fhrs = np.array_split(fhrs, ngrps) - fhrs = [fhr.tolist() for fhr in fhrs] - - grp = ' '.join(f'_{fhr[0]}-{fhr[-1]}' if len(fhr) > 1 else f'_{fhr[0]}' for fhr in fhrs) - dep = ' '.join([fhr[-1] for fhr in fhrs]) - lst = ' '.join(['_'.join(fhr) for fhr in fhrs]) - - return grp, dep, lst - def atmupp(self): return self._upptask(upp_run='forecast', task_id='atmupp') @@ -971,32 +1023,28 @@ def _upptask(self, upp_run="forecast", task_id="atmupp"): if upp_run not in VALID_UPP_RUN: raise KeyError(f"{upp_run} is invalid; UPP_RUN options are: {('|').join(VALID_UPP_RUN)}") - varname1, varname2, varname3 = 'grp', 'dep', 'lst' - varval1, varval2, varval3 = self._get_ufs_postproc_grps(self.cdump, self._configs['upp']) - var_dict = {varname1: varval1, varname2: varval2, varname3: varval3} - postenvars = self.envars.copy() - postenvar_dict = {'FHRLST': '#lst#', + postenvar_dict = {'FHR3': '#fhr#', 'UPP_RUN': upp_run} for key, value in postenvar_dict.items(): postenvars.append(rocoto.create_envar(name=key, value=str(value))) atm_hist_path = self._template_to_rocoto_cycstring(self._base["COM_ATMOS_HISTORY_TMPL"]) deps = [] - data = f'{atm_hist_path}/{self.cdump}.t@Hz.atm#dep#.nc' + data = f'{atm_hist_path}/{self.run}.t@Hz.atmf#fhr#.nc' dep_dict = {'type': 'data', 'data': data, 'age': 120} deps.append(rocoto.add_dependency(dep_dict)) - data = f'{atm_hist_path}/{self.cdump}.t@Hz.sfc#dep#.nc' + data = f'{atm_hist_path}/{self.run}.t@Hz.sfcf#fhr#.nc' dep_dict = {'type': 'data', 'data': data, 'age': 120} deps.append(rocoto.add_dependency(dep_dict)) - data = f'{atm_hist_path}/{self.cdump}.t@Hz.atm.log#dep#.txt' + data = f'{atm_hist_path}/{self.run}.t@Hz.atm.logf#fhr#.txt' dep_dict = {'type': 'data', 'data': data, 'age': 60} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep=deps, dep_condition='and') - cycledef = 'gdas_half,gdas' if self.cdump in ['gdas'] else self.cdump + cycledef = 'gdas_half,gdas' if self.run in ['gdas'] else self.run resources = self.get_resource('upp') - task_name = f'{self.cdump}{task_id}#{varname1}#' + task_name = f'{self.run}{task_id}_f#fhr#' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -1008,95 +1056,86 @@ def _upptask(self, upp_run="forecast", task_id="atmupp"): 'maxtries': '&MAXTRIES;' } - metatask_dict = {'task_name': f'{self.cdump}{task_id}', + fhrs = self._get_forecast_hours(self.run, self._configs['upp']) + fhr_var_dict = {'fhr': ' '.join([f"{fhr:03d}" for fhr in fhrs])} + + metatask_dict = {'task_name': f'{self.run}{task_id}', 'task_dict': task_dict, - 'var_dict': var_dict + 'var_dict': fhr_var_dict } task = rocoto.create_task(metatask_dict) return task - def atmprod(self): + def atmos_prod(self): + return self._atmosoceaniceprod('atmos') - varname1, varname2, varname3 = 'grp', 'dep', 'lst' - varval1, varval2, varval3 = self._get_ufs_postproc_grps(self.cdump, self._configs['atmos_products']) - var_dict = {varname1: varval1, varname2: varval2, varname3: varval3} + def ocean_prod(self): + return self._atmosoceaniceprod('ocean') - postenvars = self.envars.copy() - postenvar_dict = {'FHRLST': '#lst#'} - for key, value in postenvar_dict.items(): - postenvars.append(rocoto.create_envar(name=key, value=str(value))) + def ice_prod(self): + return self._atmosoceaniceprod('ice') - atm_master_path = self._template_to_rocoto_cycstring(self._base["COM_ATMOS_MASTER_TMPL"]) - deps = [] - data = f'{atm_master_path}/{self.cdump}.t@Hz.master.grb2#dep#' - dep_dict = {'type': 'data', 'data': data, 'age': 120} - deps.append(rocoto.add_dependency(dep_dict)) - dependencies = rocoto.create_dependency(dep=deps) - cycledef = 'gdas_half,gdas' if self.cdump in ['gdas'] else self.cdump - resources = self.get_resource('atmos_products') + def _atmosoceaniceprod(self, component: str): - task_name = f'{self.cdump}atmprod#{varname1}#' - task_dict = {'task_name': task_name, - 'resources': resources, - 'dependency': dependencies, - 'envars': postenvars, - 'cycledef': cycledef, - 'command': f'{self.HOMEgfs}/jobs/rocoto/atmos_products.sh', - 'job_name': f'{self.pslot}_{task_name}_@H', - 'log': f'{self.rotdir}/logs/@Y@m@d@H/{task_name}.log', - 'maxtries': '&MAXTRIES;' - } + products_dict = {'atmos': {'config': 'atmos_products', + 'history_path_tmpl': 'COM_ATMOS_MASTER_TMPL', + 'history_file_tmpl': f'{self.run}.t@Hz.master.grb2f#fhr#'}, + 'ocean': {'config': 'oceanice_products', + 'history_path_tmpl': 'COM_OCEAN_HISTORY_TMPL', + 'history_file_tmpl': f'{self.run}.ocean.t@Hz.6hr_avg.f#fhr_next#.nc'}, + 'ice': {'config': 'oceanice_products', + 'history_path_tmpl': 'COM_ICE_HISTORY_TMPL', + 'history_file_tmpl': f'{self.run}.ice.t@Hz.6hr_avg.f#fhr#.nc'}} - metatask_dict = {'task_name': f'{self.cdump}atmprod', - 'task_dict': task_dict, - 'var_dict': var_dict - } - - task = rocoto.create_task(metatask_dict) - - return task - - def ocnpost(self): - - varname1, varname2, varname3 = 'grp', 'dep', 'lst' - varval1, varval2, varval3 = self._get_ufs_postproc_grps(self.cdump, self._configs['ocnpost']) - var_dict = {varname1: varval1, varname2: varval2, varname3: varval3} + component_dict = products_dict[component] + config = component_dict['config'] + history_path_tmpl = component_dict['history_path_tmpl'] + history_file_tmpl = component_dict['history_file_tmpl'] postenvars = self.envars.copy() - postenvar_dict = {'FHRLST': '#lst#', - 'ROTDIR': self.rotdir} + postenvar_dict = {'FHR3': '#fhr#', 'COMPONENT': component} for key, value in postenvar_dict.items(): postenvars.append(rocoto.create_envar(name=key, value=str(value))) + history_path = self._template_to_rocoto_cycstring(self._base[history_path_tmpl]) deps = [] - atm_hist_path = self._template_to_rocoto_cycstring(self._base["COM_ATMOS_HISTORY_TMPL"]) - data = f'{atm_hist_path}/{self.cdump}.t@Hz.atm.log#dep#.txt' - dep_dict = {'type': 'data', 'data': data} + data = f'{history_path}/{history_file_tmpl}' + dep_dict = {'type': 'data', 'data': data, 'age': 120} deps.append(rocoto.add_dependency(dep_dict)) - dep_dict = {'type': 'task', 'name': f'{self.cdump}fcst'} + dep_dict = {'type': 'task', 'name': f'{self.run}fcst'} deps.append(rocoto.add_dependency(dep_dict)) - dependencies = rocoto.create_dependency(dep_condition='or', dep=deps) - cycledef = 'gdas_half,gdas' if self.cdump in ['gdas'] else self.cdump - resources = self.get_resource('ocnpost') + dependencies = rocoto.create_dependency(dep=deps, dep_condition='or') + + cycledef = 'gdas_half,gdas' if self.run in ['gdas'] else self.run + resources = self.get_resource(component_dict['config']) - task_name = f'{self.cdump}ocnpost#{varname1}#' + task_name = f'{self.run}{component}_prod_f#fhr#' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, 'envars': postenvars, 'cycledef': cycledef, - 'command': f'{self.HOMEgfs}/jobs/rocoto/ocnpost.sh', + 'command': f"{self.HOMEgfs}/jobs/rocoto/{config}.sh", 'job_name': f'{self.pslot}_{task_name}_@H', 'log': f'{self.rotdir}/logs/@Y@m@d@H/{task_name}.log', 'maxtries': '&MAXTRIES;' } - metatask_dict = {'task_name': f'{self.cdump}ocnpost', + fhrs = self._get_forecast_hours(self.run, self._configs[config], component) + + # ocean/ice components do not have fhr 0 as they are averaged output + if component in ['ocean', 'ice'] and 0 in fhrs: + fhrs.remove(0) + + fhr_var_dict = {'fhr': ' '.join([f"{fhr:03d}" for fhr in fhrs])} + if component in ['ocean']: + fhrs_next = fhrs[1:] + [fhrs[-1] + (fhrs[-1] - fhrs[-2])] + fhr_var_dict['fhr_next'] = ' '.join([f"{fhr:03d}" for fhr in fhrs_next]) + metatask_dict = {'task_name': f'{self.run}{component}_prod', 'task_dict': task_dict, - 'var_dict': var_dict - } + 'var_dict': fhr_var_dict} task = rocoto.create_task(metatask_dict) @@ -1106,18 +1145,18 @@ def wavepostsbs(self): deps = [] for wave_grid in self._configs['wavepostsbs']['waveGRD'].split(): wave_hist_path = self._template_to_rocoto_cycstring(self._base["COM_WAVE_HISTORY_TMPL"]) - data = f'{wave_hist_path}/{self.cdump}wave.out_grd.{wave_grid}.@Y@m@d.@H0000' + data = f'{wave_hist_path}/{self.run}wave.out_grd.{wave_grid}.@Y@m@d.@H0000' dep_dict = {'type': 'data', 'data': data} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep_condition='and', dep=deps) resources = self.get_resource('wavepostsbs') - task_name = f'{self.cdump}wavepostsbs' + task_name = f'{self.run}wavepostsbs' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, 'envars': self.envars, - 'cycledef': self.cdump.replace('enkf', ''), + 'cycledef': self.run.replace('enkf', ''), 'command': f'{self.HOMEgfs}/jobs/rocoto/wavepostsbs.sh', 'job_name': f'{self.pslot}_{task_name}_@H', 'log': f'{self.rotdir}/logs/@Y@m@d@H/{task_name}.log', @@ -1130,17 +1169,17 @@ def wavepostsbs(self): def wavepostbndpnt(self): deps = [] - dep_dict = {'type': 'task', 'name': f'{self.cdump}fcst'} + dep_dict = {'type': 'task', 'name': f'{self.run}fcst'} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep=deps) resources = self.get_resource('wavepostbndpnt') - task_name = f'{self.cdump}wavepostbndpnt' + task_name = f'{self.run}wavepostbndpnt' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, 'envars': self.envars, - 'cycledef': self.cdump.replace('enkf', ''), + 'cycledef': self.run.replace('enkf', ''), 'command': f'{self.HOMEgfs}/jobs/rocoto/wavepostbndpnt.sh', 'job_name': f'{self.pslot}_{task_name}_@H', 'log': f'{self.rotdir}/logs/@Y@m@d@H/{task_name}.log', @@ -1152,20 +1191,24 @@ def wavepostbndpnt(self): return task def wavepostbndpntbll(self): + + # The wavepostbndpntbll job runs on forecast hours up to FHMAX_WAV_IBP + last_fhr = self._configs['wave']['FHMAX_WAV_IBP'] + deps = [] atmos_hist_path = self._template_to_rocoto_cycstring(self._base["COM_ATMOS_HISTORY_TMPL"]) - data = f'{atmos_hist_path}/{self.cdump}.t@Hz.atm.logf180.txt' + data = f'{atmos_hist_path}/{self.run}.t@Hz.atm.logf{last_fhr:03d}.txt' dep_dict = {'type': 'data', 'data': data} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep=deps) resources = self.get_resource('wavepostbndpntbll') - task_name = f'{self.cdump}wavepostbndpntbll' + task_name = f'{self.run}wavepostbndpntbll' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, 'envars': self.envars, - 'cycledef': self.cdump.replace('enkf', ''), + 'cycledef': self.run.replace('enkf', ''), 'command': f'{self.HOMEgfs}/jobs/rocoto/wavepostbndpntbll.sh', 'job_name': f'{self.pslot}_{task_name}_@H', 'log': f'{self.rotdir}/logs/@Y@m@d@H/{task_name}.log', @@ -1178,20 +1221,20 @@ def wavepostbndpntbll(self): def wavepostpnt(self): deps = [] - dep_dict = {'type': 'task', 'name': f'{self.cdump}fcst'} + dep_dict = {'type': 'task', 'name': f'{self.run}fcst'} deps.append(rocoto.add_dependency(dep_dict)) if self.app_config.do_wave_bnd: - dep_dict = {'type': 'task', 'name': f'{self.cdump}wavepostbndpntbll'} + 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) resources = self.get_resource('wavepostpnt') - task_name = f'{self.cdump}wavepostpnt' + task_name = f'{self.run}wavepostpnt' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, 'envars': self.envars, - 'cycledef': self.cdump.replace('enkf', ''), + 'cycledef': self.run.replace('enkf', ''), 'command': f'{self.HOMEgfs}/jobs/rocoto/wavepostpnt.sh', 'job_name': f'{self.pslot}_{task_name}_@H', 'log': f'{self.rotdir}/logs/@Y@m@d@H/{task_name}.log', @@ -1204,17 +1247,17 @@ def wavepostpnt(self): def wavegempak(self): deps = [] - dep_dict = {'type': 'task', 'name': f'{self.cdump}wavepostsbs'} + dep_dict = {'type': 'task', 'name': f'{self.run}wavepostsbs'} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep=deps) resources = self.get_resource('wavegempak') - task_name = f'{self.cdump}wavegempak' + task_name = f'{self.run}wavegempak' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, 'envars': self.envars, - 'cycledef': self.cdump.replace('enkf', ''), + 'cycledef': self.run.replace('enkf', ''), 'command': f'{self.HOMEgfs}/jobs/rocoto/wavegempak.sh', 'job_name': f'{self.pslot}_{task_name}_@H', 'log': f'{self.rotdir}/logs/@Y@m@d@H/{task_name}.log', @@ -1227,19 +1270,19 @@ def wavegempak(self): def waveawipsbulls(self): deps = [] - dep_dict = {'type': 'task', 'name': f'{self.cdump}wavepostsbs'} + dep_dict = {'type': 'task', 'name': f'{self.run}wavepostsbs'} deps.append(rocoto.add_dependency(dep_dict)) - dep_dict = {'type': 'task', 'name': f'{self.cdump}wavepostpnt'} + dep_dict = {'type': 'task', 'name': f'{self.run}wavepostpnt'} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep_condition='and', dep=deps) resources = self.get_resource('waveawipsbulls') - task_name = f'{self.cdump}waveawipsbulls' + task_name = f'{self.run}waveawipsbulls' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, 'envars': self.envars, - 'cycledef': self.cdump.replace('enkf', ''), + 'cycledef': self.run.replace('enkf', ''), 'command': f'{self.HOMEgfs}/jobs/rocoto/waveawipsbulls.sh', 'job_name': f'{self.pslot}_{task_name}_@H', 'log': f'{self.rotdir}/logs/@Y@m@d@H/{task_name}.log', @@ -1252,17 +1295,17 @@ def waveawipsbulls(self): def waveawipsgridded(self): deps = [] - dep_dict = {'type': 'task', 'name': f'{self.cdump}wavepostsbs'} + dep_dict = {'type': 'task', 'name': f'{self.run}wavepostsbs'} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep=deps) resources = self.get_resource('waveawipsgridded') - task_name = f'{self.cdump}waveawipsgridded' + task_name = f'{self.run}waveawipsgridded' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, 'envars': self.envars, - 'cycledef': self.cdump.replace('enkf', ''), + 'cycledef': self.run.replace('enkf', ''), 'command': f'{self.HOMEgfs}/jobs/rocoto/waveawipsgridded.sh', 'job_name': f'{self.pslot}_{task_name}_@H', 'log': f'{self.rotdir}/logs/@Y@m@d@H/{task_name}.log', @@ -1275,17 +1318,17 @@ def waveawipsgridded(self): def postsnd(self): deps = [] - dep_dict = {'type': 'task', 'name': f'{self.cdump}fcst'} + dep_dict = {'type': 'task', 'name': f'{self.run}fcst'} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep=deps) resources = self.get_resource('postsnd') - task_name = f'{self.cdump}postsnd' + task_name = f'{self.run}postsnd' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, 'envars': self.envars, - 'cycledef': self.cdump.replace('enkf', ''), + 'cycledef': self.run.replace('enkf', ''), 'command': f'{self.HOMEgfs}/jobs/rocoto/postsnd.sh', 'job_name': f'{self.pslot}_{task_name}_@H', 'log': f'{self.rotdir}/logs/@Y@m@d@H/{task_name}.log', @@ -1298,15 +1341,15 @@ def postsnd(self): def fbwind(self): - atmos_prod_path = self._template_to_rocoto_cycstring(self._base["COM_ATMOS_GRIB_GRID_TMPL"], {'RUN': self.cdump, 'GRID': '0p25'}) + atmos_prod_path = self._template_to_rocoto_cycstring(self._base["COM_ATMOS_GRIB_GRID_TMPL"], {'RUN': self.run, 'GRID': '0p25'}) deps = [] - data = f'{atmos_prod_path}/{self.cdump}.t@Hz.pgrb2.0p25.f006' + data = f'{atmos_prod_path}/{self.run}.t@Hz.pgrb2.0p25.f006' dep_dict = {'type': 'data', 'data': data, 'age': 120} deps.append(rocoto.add_dependency(dep_dict)) - data = f'{atmos_prod_path}/{self.cdump}.t@Hz.pgrb2.0p25.f012' + data = f'{atmos_prod_path}/{self.run}.t@Hz.pgrb2.0p25.f012' dep_dict = {'type': 'data', 'data': data, 'age': 120} deps.append(rocoto.add_dependency(dep_dict)) - data = f'{atmos_prod_path}/{self.cdump}.t@Hz.pgrb2.0p25.f024' + data = f'{atmos_prod_path}/{self.run}.t@Hz.pgrb2.0p25.f024' dep_dict = {'type': 'data', 'data': data, 'age': 120} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep=deps, dep_condition='and') @@ -1317,12 +1360,12 @@ def fbwind(self): # prematurely starting with partial files. Unfortunately, the # ability to "group" post would make this more convoluted than # it should be and not worth the complexity. - task_name = f'{self.cdump}fbwind' + task_name = f'{self.run}fbwind' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, 'envars': self.envars, - 'cycledef': self.cdump.replace('enkf', ''), + 'cycledef': self.run.replace('enkf', ''), 'command': f'{self.HOMEgfs}/jobs/rocoto/fbwind.sh', 'job_name': f'{self.pslot}_{task_name}_@H', 'log': f'{self.rotdir}/logs/@Y@m@d@H/{task_name}.log', @@ -1334,7 +1377,7 @@ def fbwind(self): return task @staticmethod - def _get_awipsgroups(cdump, config): + def _get_awipsgroups(run, config): fhmin = config['FHMIN'] fhmax = config['FHMAX'] @@ -1342,11 +1385,10 @@ def _get_awipsgroups(cdump, config): # Get a list of all forecast hours fhrs = [] - if cdump in ['gdas']: + if run in ['gdas']: fhrs = range(fhmin, fhmax + fhout, fhout) - elif cdump in ['gfs']: - fhmax = np.max( - [config['FHMAX_GFS_00'], config['FHMAX_GFS_06'], config['FHMAX_GFS_12'], config['FHMAX_GFS_18']]) + elif run in ['gfs']: + fhmax = config['FHMAX_GFS'] fhout = config['FHOUT_GFS'] fhmax_hf = config['FHMAX_HF_GFS'] fhout_hf = config['FHOUT_HF_GFS'] @@ -1373,7 +1415,7 @@ def _get_awipsgroups(cdump, config): def awips_20km_1p0deg(self): deps = [] - dep_dict = {'type': 'metatask', 'name': f'{self.cdump}atmprod'} + dep_dict = {'type': 'metatask', 'name': f'{self.run}atmos_prod'} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep=deps) @@ -1385,65 +1427,24 @@ def awips_20km_1p0deg(self): awipsenvars.append(rocoto.create_envar(name=key, value=str(value))) varname1, varname2, varname3 = 'grp', 'dep', 'lst' - varval1, varval2, varval3 = self._get_awipsgroups(self.cdump, self._configs['awips']) + varval1, varval2, varval3 = self._get_awipsgroups(self.run, self._configs['awips']) var_dict = {varname1: varval1, varname2: varval2, varname3: varval3} resources = self.get_resource('awips') - task_name = f'{self.cdump}awips_20km_1p0deg#{varname1}#' + task_name = f'{self.run}awips_20km_1p0deg#{varname1}#' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, 'envars': awipsenvars, - 'cycledef': self.cdump.replace('enkf', ''), + 'cycledef': self.run.replace('enkf', ''), 'command': f'{self.HOMEgfs}/jobs/rocoto/awips_20km_1p0deg.sh', 'job_name': f'{self.pslot}_{task_name}_@H', 'log': f'{self.rotdir}/logs/@Y@m@d@H/{task_name}.log', 'maxtries': '&MAXTRIES;' } - metatask_dict = {'task_name': f'{self.cdump}awips_20km_1p0deg', - 'task_dict': task_dict, - 'var_dict': var_dict - } - - task = rocoto.create_task(metatask_dict) - - return task - - def awips_g2(self): - - deps = [] - dep_dict = {'type': 'metatask', 'name': f'{self.cdump}atmprod'} - deps.append(rocoto.add_dependency(dep_dict)) - dependencies = rocoto.create_dependency(dep=deps) - - awipsenvars = self.envars.copy() - awipsenvar_dict = {'FHRGRP': '#grp#', - 'FHRLST': '#lst#', - 'ROTDIR': self.rotdir} - for key, value in awipsenvar_dict.items(): - awipsenvars.append(rocoto.create_envar(name=key, value=str(value))) - - varname1, varname2, varname3 = 'grp', 'dep', 'lst' - varval1, varval2, varval3 = self._get_awipsgroups(self.cdump, self._configs['awips']) - var_dict = {varname1: varval1, varname2: varval2, varname3: varval3} - - resources = self.get_resource('awips') - - task_name = f'{self.cdump}awips_g2#{varname1}#' - task_dict = {'task_name': task_name, - 'resources': resources, - 'dependency': dependencies, - 'envars': awipsenvars, - 'cycledef': self.cdump.replace('enkf', ''), - 'command': f'{self.HOMEgfs}/jobs/rocoto/awips_g2.sh', - 'job_name': f'{self.pslot}_{task_name}_@H', - 'log': f'{self.rotdir}/logs/@Y@m@d@H/{task_name}.log', - 'maxtries': '&MAXTRIES;' - } - - metatask_dict = {'task_name': f'{self.cdump}awips_g2', + metatask_dict = {'task_name': f'{self.run}awips_20km_1p0deg', 'task_dict': task_dict, 'var_dict': var_dict } @@ -1455,40 +1456,52 @@ def awips_g2(self): def gempak(self): deps = [] - dep_dict = {'type': 'metatask', 'name': f'{self.cdump}atmprod'} + dep_dict = {'type': 'task', 'name': f'{self.run}atmos_prod_f#fhr#'} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep=deps) + gempak_vars = self.envars.copy() + gempak_dict = {'FHR3': '#fhr#'} + for key, value in gempak_dict.items(): + gempak_vars.append(rocoto.create_envar(name=key, value=str(value))) + resources = self.get_resource('gempak') - task_name = f'{self.cdump}gempak' + task_name = f'{self.run}gempak_f#fhr#' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, - 'envars': self.envars, - 'cycledef': self.cdump.replace('enkf', ''), + 'envars': gempak_vars, + 'cycledef': self.run.replace('enkf', ''), 'command': f'{self.HOMEgfs}/jobs/rocoto/gempak.sh', 'job_name': f'{self.pslot}_{task_name}_@H', 'log': f'{self.rotdir}/logs/@Y@m@d@H/{task_name}.log', 'maxtries': '&MAXTRIES;' } - task = rocoto.create_task(task_dict) + fhrs = self._get_forecast_hours(self.run, self._configs['gempak']) + fhr_var_dict = {'fhr': ' '.join([f"{fhr:03d}" for fhr in fhrs])} + + fhr_metatask_dict = {'task_name': f'{self.run}gempak', + 'task_dict': task_dict, + 'var_dict': fhr_var_dict} + + task = rocoto.create_task(fhr_metatask_dict) return task def gempakmeta(self): deps = [] - dep_dict = {'type': 'metatask', 'name': f'{self.cdump}atmprod'} + dep_dict = {'type': 'metatask', 'name': f'{self.run}gempak'} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep=deps) resources = self.get_resource('gempak') - task_name = f'{self.cdump}gempakmeta' + task_name = f'{self.run}gempakmeta' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, 'envars': self.envars, - 'cycledef': self.cdump.replace('enkf', ''), + 'cycledef': self.run.replace('enkf', ''), 'command': f'{self.HOMEgfs}/jobs/rocoto/gempakmeta.sh', 'job_name': f'{self.pslot}_{task_name}_@H', 'log': f'{self.rotdir}/logs/@Y@m@d@H/{task_name}.log', @@ -1501,17 +1514,17 @@ def gempakmeta(self): def gempakmetancdc(self): deps = [] - dep_dict = {'type': 'metatask', 'name': f'{self.cdump}atmprod'} + dep_dict = {'type': 'metatask', 'name': f'{self.run}gempak'} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep=deps) resources = self.get_resource('gempak') - task_name = f'{self.cdump}gempakmetancdc' + task_name = f'{self.run}gempakmetancdc' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, 'envars': self.envars, - 'cycledef': self.cdump.replace('enkf', ''), + 'cycledef': self.run.replace('enkf', ''), 'command': f'{self.HOMEgfs}/jobs/rocoto/gempakmetancdc.sh', 'job_name': f'{self.pslot}_{task_name}_@H', 'log': f'{self.rotdir}/logs/@Y@m@d@H/{task_name}.log', @@ -1524,17 +1537,17 @@ def gempakmetancdc(self): def gempakncdcupapgif(self): deps = [] - dep_dict = {'type': 'metatask', 'name': f'{self.cdump}atmprod'} + dep_dict = {'type': 'metatask', 'name': f'{self.run}gempak'} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep=deps) resources = self.get_resource('gempak') - task_name = f'{self.cdump}gempakncdcupapgif' + task_name = f'{self.run}gempakncdcupapgif' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, 'envars': self.envars, - 'cycledef': self.cdump.replace('enkf', ''), + 'cycledef': self.run.replace('enkf', ''), 'command': f'{self.HOMEgfs}/jobs/rocoto/gempakncdcupapgif.sh', 'job_name': f'{self.pslot}_{task_name}_@H', 'log': f'{self.rotdir}/logs/@Y@m@d@H/{task_name}.log', @@ -1547,42 +1560,65 @@ def gempakncdcupapgif(self): def gempakpgrb2spec(self): deps = [] - dep_dict = {'type': 'task', 'name': f'{self.cdump}npoess_pgrb2_0p5deg'} + dep_dict = {'type': 'task', 'name': f'{self.run}npoess_pgrb2_0p5deg'} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep=deps) + gempak_vars = self.envars.copy() + gempak_dict = {'FHR3': '#fhr#'} + for key, value in gempak_dict.items(): + gempak_vars.append(rocoto.create_envar(name=key, value=str(value))) + resources = self.get_resource('gempak') - task_name = f'{self.cdump}gempakgrb2spec' + task_name = f'{self.run}gempakgrb2spec_f#fhr#' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, - 'envars': self.envars, - 'cycledef': self.cdump.replace('enkf', ''), + 'envars': gempak_vars, + 'cycledef': self.run.replace('enkf', ''), 'command': f'{self.HOMEgfs}/jobs/rocoto/gempakgrb2spec.sh', 'job_name': f'{self.pslot}_{task_name}_@H', 'log': f'{self.rotdir}/logs/@Y@m@d@H/{task_name}.log', 'maxtries': '&MAXTRIES;' } - task = rocoto.create_task(task_dict) + # Override forecast lengths locally to be that of gempak goes job + local_config = self._configs['gempak'] + goes_times = { + 'FHMAX_HF_GFS': 0, + 'FHMAX_GFS': local_config['FHMAX_GOES'], + 'FHOUT_GFS': local_config['FHOUT_GOES'], + } + local_config.update(goes_times) + + fhrs = self._get_forecast_hours(self.run, local_config) + fhr_var_dict = {'fhr': ' '.join([f"{fhr:03d}" for fhr in fhrs])} + + fhr_metatask_dict = {'task_name': f'{self.run}gempakgrb2spec', + 'task_dict': task_dict, + 'var_dict': fhr_var_dict} + + task = rocoto.create_task(fhr_metatask_dict) return task def npoess_pgrb2_0p5deg(self): deps = [] - dep_dict = {'type': 'task', 'name': f'{self.cdump}atmanlprod'} + dep_dict = {'type': 'task', 'name': f'{self.run}atmanlprod'} deps.append(rocoto.add_dependency(dep_dict)) - dependencies = rocoto.create_dependency(dep=deps) + dep_dict = {'type': 'metatask', 'name': f'{self.run}goesupp'} + deps.append(rocoto.add_dependency(dep_dict)) + dependencies = rocoto.create_dependency(dep=deps, dep_condition='and') resources = self.get_resource('npoess') - task_name = f'{self.cdump}npoess_pgrb2_0p5deg' + task_name = f'{self.run}npoess_pgrb2_0p5deg' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, 'envars': self.envars, - 'cycledef': self.cdump.replace('enkf', ''), - 'command': f'{self.HOMEgfs}/jobs/rocoto/npoess_pgrb2_0p5deg.sh', + 'cycledef': self.run.replace('enkf', ''), + 'command': f'{self.HOMEgfs}/jobs/rocoto/npoess.sh', 'job_name': f'{self.pslot}_{task_name}_@H', 'log': f'{self.rotdir}/logs/@Y@m@d@H/{task_name}.log', 'maxtries': '&MAXTRIES;' @@ -1594,17 +1630,17 @@ def npoess_pgrb2_0p5deg(self): def verfozn(self): deps = [] - dep_dict = {'type': 'task', 'name': f'{self.cdump}analdiag'} + dep_dict = {'type': 'task', 'name': f'{self.run}analdiag'} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep=deps) resources = self.get_resource('verfozn') - task_name = f'{self.cdump}verfozn' + task_name = f'{self.run}verfozn' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, 'envars': self.envars, - 'cycledef': self.cdump.replace('enkf', ''), + 'cycledef': self.run.replace('enkf', ''), 'command': f'{self.HOMEgfs}/jobs/rocoto/verfozn.sh', 'job_name': f'{self.pslot}_{task_name}_@H', 'log': f'{self.rotdir}/logs/@Y@m@d@H/{task_name}.log', @@ -1617,17 +1653,17 @@ def verfozn(self): def verfrad(self): deps = [] - dep_dict = {'type': 'task', 'name': f'{self.cdump}analdiag'} + dep_dict = {'type': 'task', 'name': f'{self.run}analdiag'} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep=deps) resources = self.get_resource('verfrad') - task_name = f'{self.cdump}verfrad' + task_name = f'{self.run}verfrad' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, 'envars': self.envars, - 'cycledef': self.cdump.replace('enkf', ''), + 'cycledef': self.run.replace('enkf', ''), 'command': f'{self.HOMEgfs}/jobs/rocoto/verfrad.sh', 'job_name': f'{self.pslot}_{task_name}_@H', 'log': f'{self.rotdir}/logs/@Y@m@d@H/{task_name}.log', @@ -1640,17 +1676,17 @@ def verfrad(self): def vminmon(self): deps = [] - dep_dict = {'type': 'task', 'name': f'{self.cdump}anal'} + dep_dict = {'type': 'task', 'name': f'{self.run}anal'} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep=deps) resources = self.get_resource('vminmon') - task_name = f'{self.cdump}vminmon' + task_name = f'{self.run}vminmon' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, 'envars': self.envars, - 'cycledef': self.cdump.replace('enkf', ''), + 'cycledef': self.run.replace('enkf', ''), 'command': f'{self.HOMEgfs}/jobs/rocoto/vminmon.sh', 'job_name': f'{self.pslot}_{task_name}_@H', 'log': f'{self.rotdir}/logs/@Y@m@d@H/{task_name}.log', @@ -1663,17 +1699,17 @@ def vminmon(self): def tracker(self): deps = [] - dep_dict = {'type': 'metatask', 'name': f'{self.cdump}atmprod'} + dep_dict = {'type': 'metatask', 'name': f'{self.run}atmos_prod'} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep=deps) resources = self.get_resource('tracker') - task_name = f'{self.cdump}tracker' + task_name = f'{self.run}tracker' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, 'envars': self.envars, - 'cycledef': self.cdump.replace('enkf', ''), + 'cycledef': self.run.replace('enkf', ''), 'command': f'{self.HOMEgfs}/jobs/rocoto/tracker.sh', 'job_name': f'{self.pslot}_{task_name}_@H', 'log': f'{self.rotdir}/logs/@Y@m@d@H/{task_name}.log', @@ -1686,17 +1722,17 @@ def tracker(self): def genesis(self): deps = [] - dep_dict = {'type': 'metatask', 'name': f'{self.cdump}atmprod'} + dep_dict = {'type': 'metatask', 'name': f'{self.run}atmos_prod'} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep=deps) resources = self.get_resource('genesis') - task_name = f'{self.cdump}genesis' + task_name = f'{self.run}genesis' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, 'envars': self.envars, - 'cycledef': self.cdump.replace('enkf', ''), + 'cycledef': self.run.replace('enkf', ''), 'command': f'{self.HOMEgfs}/jobs/rocoto/genesis.sh', 'job_name': f'{self.pslot}_{task_name}_@H', 'log': f'{self.rotdir}/logs/@Y@m@d@H/{task_name}.log', @@ -1709,17 +1745,17 @@ def genesis(self): def genesis_fsu(self): deps = [] - dep_dict = {'type': 'metatask', 'name': f'{self.cdump}atmprod'} + dep_dict = {'type': 'metatask', 'name': f'{self.run}atmos_prod'} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep=deps) resources = self.get_resource('genesis_fsu') - task_name = f'{self.cdump}genesis_fsu' + task_name = f'{self.run}genesis_fsu' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, 'envars': self.envars, - 'cycledef': self.cdump.replace('enkf', ''), + 'cycledef': self.run.replace('enkf', ''), 'command': f'{self.HOMEgfs}/jobs/rocoto/genesis_fsu.sh', 'job_name': f'{self.pslot}_{task_name}_@H', 'log': f'{self.rotdir}/logs/@Y@m@d@H/{task_name}.log', @@ -1732,17 +1768,17 @@ def genesis_fsu(self): def fit2obs(self): deps = [] - dep_dict = {'type': 'metatask', 'name': f'{self.cdump}atmprod'} + dep_dict = {'type': 'metatask', 'name': f'{self.run}atmos_prod'} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep=deps) resources = self.get_resource('fit2obs') - task_name = f'{self.cdump}fit2obs' + task_name = f'{self.run}fit2obs' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, 'envars': self.envars, - 'cycledef': self.cdump.replace('enkf', ''), + 'cycledef': self.run.replace('enkf', ''), 'command': f'{self.HOMEgfs}/jobs/rocoto/fit2obs.sh', 'job_name': f'{self.pslot}_{task_name}_@H', 'log': f'{self.rotdir}/logs/@Y@m@d@H/{task_name}.log', @@ -1755,13 +1791,14 @@ def fit2obs(self): def metp(self): deps = [] - dep_dict = {'type': 'task', 'name': f'{self.cdump}arch'} + dep_dict = {'type': 'task', 'name': f'{self.run}arch'} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep_condition='and', dep=deps) metpenvars = self.envars.copy() if self.app_config.mode in ['cycled']: - metpenvar_dict = {'SDATE_GFS': self._base.get('SDATE_GFS').strftime("%Y%m%d%H")} + metpenvar_dict = {'SDATE_GFS': self._base.get('SDATE_GFS').strftime("%Y%m%d%H"), + 'EDATE_GFS': self._base.get('EDATE_GFS').strftime("%Y%m%d%H")} elif self.app_config.mode in ['forecast-only']: metpenvar_dict = {'SDATE_GFS': self._base.get('SDATE').strftime("%Y%m%d%H")} metpenvar_dict['METPCASE'] = '#metpcase#' @@ -1774,19 +1811,19 @@ def metp(self): resources = self.get_resource('metp') - task_name = f'{self.cdump}metp#{varname1}#' + task_name = f'{self.run}metp#{varname1}#' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, 'envars': metpenvars, - 'cycledef': self.cdump.replace('enkf', ''), + 'cycledef': self.run.replace('enkf', ''), 'command': f'{self.HOMEgfs}/jobs/rocoto/metp.sh', 'job_name': f'{self.pslot}_{task_name}_@H', 'log': f'{self.rotdir}/logs/@Y@m@d@H/{task_name}.log', 'maxtries': '&MAXTRIES;' } - metatask_dict = {'task_name': f'{self.cdump}metp', + metatask_dict = {'task_name': f'{self.run}metp', 'task_dict': task_dict, 'var_dict': var_dict } @@ -1797,17 +1834,17 @@ def metp(self): def mos_stn_prep(self): deps = [] - dep_dict = {'type': 'metatask', 'name': f'{self.cdump}atmprod'} + dep_dict = {'type': 'metatask', 'name': f'{self.run}atmos_prod'} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep=deps) resources = self.get_resource('mos_stn_prep') - task_name = f'{self.cdump}mos_stn_prep' + task_name = f'{self.run}mos_stn_prep' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, 'envars': self.envars, - 'cycledef': self.cdump.replace('enkf', ''), + 'cycledef': self.run.replace('enkf', ''), 'command': f'{self.HOMEgfs}/jobs/rocoto/mos_stn_prep.sh', 'job_name': f'{self.pslot}_{task_name}_@H', 'log': f'{self.rotdir}/logs/@Y@m@d@H/{task_name}.log', @@ -1820,17 +1857,17 @@ def mos_stn_prep(self): def mos_grd_prep(self): deps = [] - dep_dict = {'type': 'metatask', 'name': f'{self.cdump}atmprod'} + dep_dict = {'type': 'metatask', 'name': f'{self.run}atmos_prod'} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep=deps) resources = self.get_resource('mos_grd_prep') - task_name = f'{self.cdump}mos_grd_prep' + task_name = f'{self.run}mos_grd_prep' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, 'envars': self.envars, - 'cycledef': self.cdump.replace('enkf', ''), + 'cycledef': self.run.replace('enkf', ''), 'command': f'{self.HOMEgfs}/jobs/rocoto/mos_grd_prep.sh', 'job_name': f'{self.pslot}_{task_name}_@H', 'log': f'{self.rotdir}/logs/@Y@m@d@H/{task_name}.log', @@ -1843,17 +1880,17 @@ def mos_grd_prep(self): def mos_ext_stn_prep(self): deps = [] - dep_dict = {'type': 'metatask', 'name': f'{self.cdump}atmprod'} + dep_dict = {'type': 'metatask', 'name': f'{self.run}atmos_prod'} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep=deps) resources = self.get_resource('mos_ext_stn_prep') - task_name = f'{self.cdump}mos_ext_stn_prep' + task_name = f'{self.run}mos_ext_stn_prep' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, 'envars': self.envars, - 'cycledef': self.cdump.replace('enkf', ''), + 'cycledef': self.run.replace('enkf', ''), 'command': f'{self.HOMEgfs}/jobs/rocoto/mos_ext_stn_prep.sh', 'job_name': f'{self.pslot}_{task_name}_@H', 'log': f'{self.rotdir}/logs/@Y@m@d@H/{task_name}.log', @@ -1866,17 +1903,17 @@ def mos_ext_stn_prep(self): def mos_ext_grd_prep(self): deps = [] - dep_dict = {'type': 'metatask', 'name': f'{self.cdump}atmprod'} + dep_dict = {'type': 'metatask', 'name': f'{self.run}atmos_prod'} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep=deps) resources = self.get_resource('mos_ext_grd_prep') - task_name = f'{self.cdump}mos_ext_grd_prep' + task_name = f'{self.run}mos_ext_grd_prep' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, 'envars': self.envars, - 'cycledef': self.cdump.replace('enkf', ''), + 'cycledef': self.run.replace('enkf', ''), 'command': f'{self.HOMEgfs}/jobs/rocoto/mos_ext_grd_prep.sh', 'job_name': f'{self.pslot}_{task_name}_@H', 'log': f'{self.rotdir}/logs/@Y@m@d@H/{task_name}.log', @@ -1889,17 +1926,17 @@ def mos_ext_grd_prep(self): def mos_stn_fcst(self): deps = [] - dep_dict = {'type': 'task', 'name': f'{self.cdump}mos_stn_prep'} + dep_dict = {'type': 'task', 'name': f'{self.run}mos_stn_prep'} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep=deps) resources = self.get_resource('mos_stn_fcst') - task_name = f'{self.cdump}mos_stn_fcst' + task_name = f'{self.run}mos_stn_fcst' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, 'envars': self.envars, - 'cycledef': self.cdump.replace('enkf', ''), + 'cycledef': self.run.replace('enkf', ''), 'command': f'{self.HOMEgfs}/jobs/rocoto/mos_stn_fcst.sh', 'job_name': f'{self.pslot}_{task_name}_@H', 'log': f'{self.rotdir}/logs/@Y@m@d@H/{task_name}.log', @@ -1912,20 +1949,20 @@ def mos_stn_fcst(self): def mos_grd_fcst(self): deps = [] - dep_dict = {'type': 'task', 'name': f'{self.cdump}mos_stn_prep'} + dep_dict = {'type': 'task', 'name': f'{self.run}mos_stn_prep'} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep=deps) - dep_dict = {'type': 'task', 'name': f'{self.cdump}mos_grd_prep'} + dep_dict = {'type': 'task', 'name': f'{self.run}mos_grd_prep'} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep_condition='and', dep=deps) resources = self.get_resource('mos_grd_fcst') - task_name = f'{self.cdump}mos_grd_fcst' + task_name = f'{self.run}mos_grd_fcst' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, 'envars': self.envars, - 'cycledef': self.cdump.replace('enkf', ''), + 'cycledef': self.run.replace('enkf', ''), 'command': f'{self.HOMEgfs}/jobs/rocoto/mos_grd_fcst.sh', 'job_name': f'{self.pslot}_{task_name}_@H', 'log': f'{self.rotdir}/logs/@Y@m@d@H/{task_name}.log', @@ -1938,20 +1975,20 @@ def mos_grd_fcst(self): def mos_ext_stn_fcst(self): deps = [] - dep_dict = {'type': 'task', 'name': f'{self.cdump}mos_ext_stn_prep'} + dep_dict = {'type': 'task', 'name': f'{self.run}mos_ext_stn_prep'} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep=deps) - dep_dict = {'type': 'task', 'name': f'{self.cdump}mos_stn_prdgen'} + dep_dict = {'type': 'task', 'name': f'{self.run}mos_stn_prdgen'} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep_condition='and', dep=deps) resources = self.get_resource('mos_ext_stn_fcst') - task_name = f'{self.cdump}mos_ext_stn_fcst' + task_name = f'{self.run}mos_ext_stn_fcst' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, 'envars': self.envars, - 'cycledef': self.cdump.replace('enkf', ''), + 'cycledef': self.run.replace('enkf', ''), 'command': f'{self.HOMEgfs}/jobs/rocoto/mos_ext_stn_fcst.sh', 'job_name': f'{self.pslot}_{task_name}_@H', 'log': f'{self.rotdir}/logs/@Y@m@d@H/{task_name}.log', @@ -1964,23 +2001,23 @@ def mos_ext_stn_fcst(self): def mos_ext_grd_fcst(self): deps = [] - dep_dict = {'type': 'task', 'name': f'{self.cdump}mos_ext_stn_prep'} + dep_dict = {'type': 'task', 'name': f'{self.run}mos_ext_stn_prep'} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep=deps) - dep_dict = {'type': 'task', 'name': f'{self.cdump}mos_ext_grd_prep'} + dep_dict = {'type': 'task', 'name': f'{self.run}mos_ext_grd_prep'} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep=deps) - dep_dict = {'type': 'task', 'name': f'{self.cdump}mos_grd_fcst'} + dep_dict = {'type': 'task', 'name': f'{self.run}mos_grd_fcst'} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep_condition='and', dep=deps) resources = self.get_resource('mos_ext_grd_fcst') - task_name = f'{self.cdump}mos_ext_grd_fcst' + task_name = f'{self.run}mos_ext_grd_fcst' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, 'envars': self.envars, - 'cycledef': self.cdump.replace('enkf', ''), + 'cycledef': self.run.replace('enkf', ''), 'command': f'{self.HOMEgfs}/jobs/rocoto/mos_ext_grd_fcst.sh', 'job_name': f'{self.pslot}_{task_name}_@H', 'log': f'{self.rotdir}/logs/@Y@m@d@H/{task_name}.log', @@ -1993,17 +2030,17 @@ def mos_ext_grd_fcst(self): def mos_stn_prdgen(self): deps = [] - dep_dict = {'type': 'task', 'name': f'{self.cdump}mos_stn_fcst'} + dep_dict = {'type': 'task', 'name': f'{self.run}mos_stn_fcst'} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep=deps) resources = self.get_resource('mos_stn_prdgen') - task_name = f'{self.cdump}mos_stn_prdgen' + task_name = f'{self.run}mos_stn_prdgen' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, 'envars': self.envars, - 'cycledef': self.cdump.replace('enkf', ''), + 'cycledef': self.run.replace('enkf', ''), 'command': f'{self.HOMEgfs}/jobs/rocoto/mos_stn_prdgen.sh', 'job_name': f'{self.pslot}_{task_name}_@H', 'log': f'{self.rotdir}/logs/@Y@m@d@H/{task_name}.log', @@ -2016,20 +2053,20 @@ def mos_stn_prdgen(self): def mos_grd_prdgen(self): deps = [] - dep_dict = {'type': 'task', 'name': f'{self.cdump}mos_grd_fcst'} + dep_dict = {'type': 'task', 'name': f'{self.run}mos_grd_fcst'} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep=deps) - dep_dict = {'type': 'task', 'name': f'{self.cdump}mos_stn_prdgen'} + dep_dict = {'type': 'task', 'name': f'{self.run}mos_stn_prdgen'} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep_condition='and', dep=deps) resources = self.get_resource('mos_grd_prdgen') - task_name = f'{self.cdump}mos_grd_prdgen' + task_name = f'{self.run}mos_grd_prdgen' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, 'envars': self.envars, - 'cycledef': self.cdump.replace('enkf', ''), + 'cycledef': self.run.replace('enkf', ''), 'command': f'{self.HOMEgfs}/jobs/rocoto/mos_grd_prdgen.sh', 'job_name': f'{self.pslot}_{task_name}_@H', 'log': f'{self.rotdir}/logs/@Y@m@d@H/{task_name}.log', @@ -2042,20 +2079,20 @@ def mos_grd_prdgen(self): def mos_ext_stn_prdgen(self): deps = [] - dep_dict = {'type': 'task', 'name': f'{self.cdump}mos_ext_stn_fcst'} + dep_dict = {'type': 'task', 'name': f'{self.run}mos_ext_stn_fcst'} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep=deps) - dep_dict = {'type': 'task', 'name': f'{self.cdump}mos_stn_prdgen'} + dep_dict = {'type': 'task', 'name': f'{self.run}mos_stn_prdgen'} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep_condition='and', dep=deps) resources = self.get_resource('mos_ext_stn_prdgen') - task_name = f'{self.cdump}mos_ext_stn_prdgen' + task_name = f'{self.run}mos_ext_stn_prdgen' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, 'envars': self.envars, - 'cycledef': self.cdump.replace('enkf', ''), + 'cycledef': self.run.replace('enkf', ''), 'command': f'{self.HOMEgfs}/jobs/rocoto/mos_ext_stn_prdgen.sh', 'job_name': f'{self.pslot}_{task_name}_@H', 'log': f'{self.rotdir}/logs/@Y@m@d@H/{task_name}.log', @@ -2068,23 +2105,23 @@ def mos_ext_stn_prdgen(self): def mos_ext_grd_prdgen(self): deps = [] - dep_dict = {'type': 'task', 'name': f'{self.cdump}mos_ext_grd_fcst'} + dep_dict = {'type': 'task', 'name': f'{self.run}mos_ext_grd_fcst'} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep=deps) - dep_dict = {'type': 'task', 'name': f'{self.cdump}mos_grd_prdgen'} + dep_dict = {'type': 'task', 'name': f'{self.run}mos_grd_prdgen'} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep=deps) - dep_dict = {'type': 'task', 'name': f'{self.cdump}mos_ext_stn_prdgen'} + dep_dict = {'type': 'task', 'name': f'{self.run}mos_ext_stn_prdgen'} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep_condition='and', dep=deps) resources = self.get_resource('mos_ext_grd_prdgen') - task_name = f'{self.cdump}mos_ext_grd_prdgen' + task_name = f'{self.run}mos_ext_grd_prdgen' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, 'envars': self.envars, - 'cycledef': self.cdump.replace('enkf', ''), + 'cycledef': self.run.replace('enkf', ''), 'command': f'{self.HOMEgfs}/jobs/rocoto/mos_ext_grd_prdgen.sh', 'job_name': f'{self.pslot}_{task_name}_@H', 'log': f'{self.rotdir}/logs/@Y@m@d@H/{task_name}.log', @@ -2097,17 +2134,17 @@ def mos_ext_grd_prdgen(self): def mos_wx_prdgen(self): deps = [] - dep_dict = {'type': 'task', 'name': f'{self.cdump}mos_grd_prdgen'} + dep_dict = {'type': 'task', 'name': f'{self.run}mos_grd_prdgen'} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep=deps) resources = self.get_resource('mos_wx_prdgen') - task_name = f'{self.cdump}mos_wx_prdgen' + task_name = f'{self.run}mos_wx_prdgen' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, 'envars': self.envars, - 'cycledef': self.cdump.replace('enkf', ''), + 'cycledef': self.run.replace('enkf', ''), 'command': f'{self.HOMEgfs}/jobs/rocoto/mos_wx_prdgen.sh', 'job_name': f'{self.pslot}_{task_name}_@H', 'log': f'{self.rotdir}/logs/@Y@m@d@H/{task_name}.log', @@ -2120,20 +2157,20 @@ def mos_wx_prdgen(self): def mos_wx_ext_prdgen(self): deps = [] - dep_dict = {'type': 'task', 'name': f'{self.cdump}mos_ext_grd_prdgen'} + dep_dict = {'type': 'task', 'name': f'{self.run}mos_ext_grd_prdgen'} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep=deps) - dep_dict = {'type': 'task', 'name': f'{self.cdump}mos_wx_prdgen'} + dep_dict = {'type': 'task', 'name': f'{self.run}mos_wx_prdgen'} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep_condition='and', dep=deps) resources = self.get_resource('mos_wx_ext_prdgen') - task_name = f'{self.cdump}mos_wx_ext_prdgen' + task_name = f'{self.run}mos_wx_ext_prdgen' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, 'envars': self.envars, - 'cycledef': self.cdump.replace('enkf', ''), + 'cycledef': self.run.replace('enkf', ''), 'command': f'{self.HOMEgfs}/jobs/rocoto/mos_wx_ext_prdgen.sh', 'job_name': f'{self.pslot}_{task_name}_@H', 'log': f'{self.rotdir}/logs/@Y@m@d@H/{task_name}.log', @@ -2146,79 +2183,75 @@ def mos_wx_ext_prdgen(self): def arch(self): deps = [] - dependencies = [] if self.app_config.mode in ['cycled']: - if self.cdump in ['gfs']: - dep_dict = {'type': 'task', 'name': f'{self.cdump}atmanlprod'} + 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: - dep_dict = {'type': 'task', 'name': f'{self.cdump}vminmon'} + dep_dict = {'type': 'task', 'name': f'{self.run}vminmon'} deps.append(rocoto.add_dependency(dep_dict)) - elif self.cdump in ['gdas']: # Block for handling half cycle dependencies - deps2 = [] - dep_dict = {'type': 'task', 'name': f'{self.cdump}atmanlprod'} - deps2.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: - dep_dict = {'type': 'task', 'name': f'{self.cdump}fit2obs'} - deps2.append(rocoto.add_dependency(dep_dict)) + dep_dict = {'type': 'task', 'name': f'{self.run}fit2obs'} + deps.append(rocoto.add_dependency(dep_dict)) if self.app_config.do_verfozn: - dep_dict = {'type': 'task', 'name': f'{self.cdump}verfozn'} - deps2.append(rocoto.add_dependency(dep_dict)) + dep_dict = {'type': 'task', 'name': f'{self.run}verfozn'} + deps.append(rocoto.add_dependency(dep_dict)) if self.app_config.do_verfrad: - dep_dict = {'type': 'task', 'name': f'{self.cdump}verfrad'} - deps2.append(rocoto.add_dependency(dep_dict)) + dep_dict = {'type': 'task', 'name': f'{self.run}verfrad'} + deps.append(rocoto.add_dependency(dep_dict)) if self.app_config.do_vminmon: - dep_dict = {'type': 'task', 'name': f'{self.cdump}vminmon'} - deps2.append(rocoto.add_dependency(dep_dict)) - dependencies = rocoto.create_dependency(dep_condition='and', dep=deps2) - dep_dict = {'type': 'cycleexist', 'condition': 'not', 'offset': f"-{timedelta_to_HMS(self._base['cycle_interval'])}"} - dependencies.append(rocoto.add_dependency(dep_dict)) - dependencies = rocoto.create_dependency(dep_condition='or', dep=dependencies) - if self.cdump in ['gfs'] and self.app_config.do_tracker: - dep_dict = {'type': 'task', 'name': f'{self.cdump}tracker'} + 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: + dep_dict = {'type': 'task', 'name': f'{self.run}tracker'} deps.append(rocoto.add_dependency(dep_dict)) - if self.cdump in ['gfs'] and self.app_config.do_genesis: - dep_dict = {'type': 'task', 'name': f'{self.cdump}genesis'} + if self.run in ['gfs'] and self.app_config.do_genesis: + dep_dict = {'type': 'task', 'name': f'{self.run}genesis'} deps.append(rocoto.add_dependency(dep_dict)) - if self.cdump in ['gfs'] and self.app_config.do_genesis_fsu: - dep_dict = {'type': 'task', 'name': f'{self.cdump}genesis_fsu'} + if self.run in ['gfs'] and self.app_config.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.cdump}atmprod'} + dep_dict = {'type': 'metatask', 'name': f'{self.run}atmos_prod'} deps.append(rocoto.add_dependency(dep_dict)) if self.app_config.do_wave: - dep_dict = {'type': 'task', 'name': f'{self.cdump}wavepostsbs'} + dep_dict = {'type': 'task', 'name': f'{self.run}wavepostsbs'} deps.append(rocoto.add_dependency(dep_dict)) - dep_dict = {'type': 'task', 'name': f'{self.cdump}wavepostpnt'} + dep_dict = {'type': 'task', 'name': f'{self.run}wavepostpnt'} deps.append(rocoto.add_dependency(dep_dict)) if self.app_config.do_wave_bnd: - dep_dict = {'type': 'task', 'name': f'{self.cdump}wavepostbndpnt'} + dep_dict = {'type': 'task', 'name': f'{self.run}wavepostbndpnt'} deps.append(rocoto.add_dependency(dep_dict)) if self.app_config.do_ocean: - if self.app_config.mode in ['forecast-only']: # TODO: fix ocnpost to run in cycled mode - dep_dict = {'type': 'metatask', 'name': f'{self.cdump}ocnpost'} + 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.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.cdump in ['gfs'] and self.app_config.do_mos: + if self.run in ['gfs'] and self.app_config.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", "wx_prdgen", "wx_ext_prdgen"] for job in mos_jobs: - dep_dict = {'type': 'task', 'name': f'{self.cdump}mos_{job}'} + dep_dict = {'type': 'task', 'name': f'{self.run}mos_{job}'} deps.append(rocoto.add_dependency(dep_dict)) - dependencies = rocoto.create_dependency(dep_condition='and', dep=deps + dependencies) - - cycledef = 'gdas_half,gdas' if self.cdump in ['gdas'] else self.cdump + dependencies = rocoto.create_dependency(dep_condition='and', dep=deps) resources = self.get_resource('arch') - task_name = f'{self.cdump}arch' + task_name = f'{self.run}arch' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, 'envars': self.envars, - 'cycledef': cycledef, + 'cycledef': self.run.replace('enkf', ''), 'command': f'{self.HOMEgfs}/jobs/rocoto/arch.sh', 'job_name': f'{self.pslot}_{task_name}_@H', 'log': f'{self.rotdir}/logs/@Y@m@d@H/{task_name}.log', @@ -2232,22 +2265,37 @@ def arch(self): # Cleanup def cleanup(self): deps = [] - if 'enkf' in self.cdump: - dep_dict = {'type': 'metatask', 'name': f'{self.cdump}eamn'} + if 'enkf' in self.run: + dep_dict = {'type': 'metatask', 'name': f'{self.run}eamn'} deps.append(rocoto.add_dependency(dep_dict)) else: - dep_dict = {'type': 'task', 'name': f'{self.cdump}arch'} + dep_dict = {'type': 'task', 'name': f'{self.run}arch'} deps.append(rocoto.add_dependency(dep_dict)) + if self.app_config.do_gempak: + if self.run in ['gdas']: + dep_dict = {'type': 'task', 'name': f'{self.run}gempakmetancdc'} + deps.append(rocoto.add_dependency(dep_dict)) + elif self.run in ['gfs']: + dep_dict = {'type': 'task', 'name': f'{self.run}gempakmeta'} + 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: + 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'} + deps.append(rocoto.add_dependency(dep_dict)) + dependencies = rocoto.create_dependency(dep_condition='and', dep=deps) resources = self.get_resource('cleanup') - task_name = f'{self.cdump}cleanup' + task_name = f'{self.run}cleanup' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, 'envars': self.envars, - 'cycledef': self.cdump.replace('enkf', ''), + 'cycledef': self.run.replace('enkf', ''), 'command': f'{self.HOMEgfs}/jobs/rocoto/cleanup.sh', 'job_name': f'{self.pslot}_{task_name}_@H', 'log': f'{self.rotdir}/logs/@Y@m@d@H/{task_name}.log', @@ -2261,19 +2309,19 @@ def cleanup(self): # Start of ensemble tasks def eobs(self): deps = [] - dep_dict = {'type': 'task', 'name': f'{self.cdump.replace("enkf","")}prep'} + dep_dict = {'type': 'task', 'name': f'{self.run.replace("enkf","")}prep'} deps.append(rocoto.add_dependency(dep_dict)) 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) resources = self.get_resource('eobs') - task_name = f'{self.cdump}eobs' + task_name = f'{self.run}eobs' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, 'envars': self.envars, - 'cycledef': self.cdump.replace('enkf', ''), + 'cycledef': self.run.replace('enkf', ''), 'command': f'{self.HOMEgfs}/jobs/rocoto/eobs.sh', 'job_name': f'{self.pslot}_{task_name}_@H', 'log': f'{self.rotdir}/logs/@Y@m@d@H/{task_name}.log', @@ -2286,32 +2334,33 @@ def eobs(self): def eomg(self): deps = [] - dep_dict = {'type': 'task', 'name': f'{self.cdump}eobs'} + dep_dict = {'type': 'task', 'name': f'{self.run}eobs'} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep=deps) eomgenvars = self.envars.copy() - eomgenvars.append(rocoto.create_envar(name='ENSGRP', value='#grp#')) - - groups = self._get_hybgroups(self._base['NMEM_ENS'], self._configs['eobs']['NMEM_EOMGGRP']) - - var_dict = {'grp': groups} + eomgenvars_dict = {'ENSMEM': '#member#', + 'MEMDIR': 'mem#member#' + } + for key, value in eomgenvars_dict.items(): + eomgenvars.append(rocoto.create_envar(name=key, value=str(value))) resources = self.get_resource('eomg') - task_name = f'{self.cdump}eomg#grp#' + task_name = f'{self.run}eomg_mem#member#' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, 'envars': eomgenvars, - 'cycledef': self.cdump.replace('enkf', ''), + 'cycledef': self.run.replace('enkf', ''), 'command': f'{self.HOMEgfs}/jobs/rocoto/eomg.sh', 'job_name': f'{self.pslot}_{task_name}_@H', 'log': f'{self.rotdir}/logs/@Y@m@d@H/{task_name}.log', 'maxtries': '&MAXTRIES;' } - metatask_dict = {'task_name': f'{self.cdump}eomn', - 'var_dict': var_dict, + member_var_dict = {'member': ' '.join([str(mem).zfill(3) for mem in range(1, self.nmem + 1)])} + metatask_dict = {'task_name': f'{self.run}eomg', + 'var_dict': member_var_dict, 'task_dict': task_dict, } @@ -2321,17 +2370,17 @@ def eomg(self): def ediag(self): deps = [] - dep_dict = {'type': 'task', 'name': f'{self.cdump}eobs'} + dep_dict = {'type': 'task', 'name': f'{self.run}eobs'} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep=deps) resources = self.get_resource('ediag') - task_name = f'{self.cdump}ediag' + task_name = f'{self.run}ediag' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, 'envars': self.envars, - 'cycledef': self.cdump.replace('enkf', ''), + 'cycledef': self.run.replace('enkf', ''), 'command': f'{self.HOMEgfs}/jobs/rocoto/ediag.sh', 'job_name': f'{self.pslot}_{task_name}_@H', 'log': f'{self.rotdir}/logs/@Y@m@d@H/{task_name}.log', @@ -2345,19 +2394,19 @@ def ediag(self): def eupd(self): deps = [] if self.app_config.lobsdiag_forenkf: - dep_dict = {'type': 'task', 'name': f'{self.cdump}ediag'} + dep_dict = {'type': 'task', 'name': f'{self.run}ediag'} else: - dep_dict = {'type': 'metatask', 'name': f'{self.cdump}eomn'} + dep_dict = {'type': 'metatask', 'name': f'{self.run}eomg'} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep=deps) resources = self.get_resource('eupd') - task_name = f'{self.cdump}eupd' + task_name = f'{self.run}eupd' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, 'envars': self.envars, - 'cycledef': self.cdump.replace('enkf', ''), + 'cycledef': self.run.replace('enkf', ''), 'command': f'{self.HOMEgfs}/jobs/rocoto/eupd.sh', 'job_name': f'{self.pslot}_{task_name}_@H', 'log': f'{self.rotdir}/logs/@Y@m@d@H/{task_name}.log', @@ -2370,7 +2419,7 @@ def eupd(self): def atmensanlinit(self): deps = [] - dep_dict = {'type': 'task', 'name': f'{self.cdump.replace("enkf","")}prepatmiodaobs'} + dep_dict = {'type': 'task', 'name': f'{self.run.replace("enkf","")}prepatmiodaobs'} deps.append(rocoto.add_dependency(dep_dict)) dep_dict = {'type': 'metatask', 'name': 'enkfgdasepmn', 'offset': f"-{timedelta_to_HMS(self._base['cycle_interval'])}"} deps.append(rocoto.add_dependency(dep_dict)) @@ -2378,7 +2427,7 @@ def atmensanlinit(self): cycledef = "gdas" resources = self.get_resource('atmensanlinit') - task_name = f'{self.cdump}atmensanlinit' + task_name = f'{self.run}atmensanlinit' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -2394,23 +2443,49 @@ def atmensanlinit(self): return task - def atmensanlrun(self): + def atmensanlletkf(self): deps = [] - dep_dict = {'type': 'task', 'name': f'{self.cdump}atmensanlinit'} + dep_dict = {'type': 'task', 'name': f'{self.run}atmensanlinit'} deps.append(rocoto.add_dependency(dep_dict)) 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) - resources = self.get_resource('atmensanlrun') - task_name = f'{self.cdump}atmensanlrun' + resources = self.get_resource('atmensanlletkf') + task_name = f'{self.run}atmensanlletkf' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, 'envars': self.envars, - 'cycledef': self.cdump.replace('enkf', ''), - 'command': f'{self.HOMEgfs}/jobs/rocoto/atmensanlrun.sh', + 'cycledef': self.run.replace('enkf', ''), + 'command': f'{self.HOMEgfs}/jobs/rocoto/atmensanlletkf.sh', + 'job_name': f'{self.pslot}_{task_name}_@H', + 'log': f'{self.rotdir}/logs/@Y@m@d@H/{task_name}.log', + 'maxtries': '&MAXTRIES;' + } + + task = rocoto.create_task(task_dict) + + return task + + def atmensanlfv3inc(self): + + deps = [] + dep_dict = {'type': 'task', 'name': f'{self.run}atmensanlletkf'} + deps.append(rocoto.add_dependency(dep_dict)) + 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) + + resources = self.get_resource('atmensanlfv3inc') + task_name = f'{self.run}atmensanlfv3inc' + task_dict = {'task_name': task_name, + 'resources': resources, + 'dependency': dependencies, + 'envars': self.envars, + 'cycledef': self.run.replace('enkf', ''), + 'command': f'{self.HOMEgfs}/jobs/rocoto/atmensanlfv3inc.sh', 'job_name': f'{self.pslot}_{task_name}_@H', 'log': f'{self.rotdir}/logs/@Y@m@d@H/{task_name}.log', 'maxtries': '&MAXTRIES;' @@ -2423,17 +2498,17 @@ def atmensanlrun(self): def atmensanlfinal(self): deps = [] - dep_dict = {'type': 'task', 'name': f'{self.cdump}atmensanlrun'} + dep_dict = {'type': 'task', 'name': f'{self.run}atmensanlfv3inc'} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep=deps) resources = self.get_resource('atmensanlfinal') - task_name = f'{self.cdump}atmensanlfinal' + task_name = f'{self.run}atmensanlfinal' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, 'envars': self.envars, - 'cycledef': self.cdump.replace('enkf', ''), + 'cycledef': self.run.replace('enkf', ''), 'command': f'{self.HOMEgfs}/jobs/rocoto/atmensanlfinal.sh', 'job_name': f'{self.pslot}_{task_name}_@H', 'log': f'{self.rotdir}/logs/@Y@m@d@H/{task_name}.log', @@ -2470,12 +2545,12 @@ def _get_ecengroups(): return grp, dep, lst deps = [] - dep_dict = {'type': 'task', 'name': f'{self.cdump.replace("enkf","")}analcalc'} + dep_dict = {'type': 'task', 'name': f'{self.run.replace("enkf","")}analcalc'} deps.append(rocoto.add_dependency(dep_dict)) if self.app_config.do_jediatmens: - dep_dict = {'type': 'task', 'name': f'{self.cdump}atmensanlfinal'} + dep_dict = {'type': 'task', 'name': f'{self.run}atmensanlfinal'} else: - dep_dict = {'type': 'task', 'name': f'{self.cdump}eupd'} + dep_dict = {'type': 'task', 'name': f'{self.run}eupd'} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep_condition='and', dep=deps) @@ -2491,19 +2566,19 @@ def _get_ecengroups(): resources = self.get_resource('ecen') - task_name = f'{self.cdump}ecen#{varname1}#' + task_name = f'{self.run}ecen#{varname1}#' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, 'envars': ecenenvars, - 'cycledef': self.cdump.replace('enkf', ''), + 'cycledef': self.run.replace('enkf', ''), 'command': f'{self.HOMEgfs}/jobs/rocoto/ecen.sh', 'job_name': f'{self.pslot}_{task_name}_@H', 'log': f'{self.rotdir}/logs/@Y@m@d@H/{task_name}.log', 'maxtries': '&MAXTRIES;' } - metatask_dict = {'task_name': f'{self.cdump}ecmn', + metatask_dict = {'task_name': f'{self.run}ecmn', 'var_dict': var_dict, 'task_dict': task_dict } @@ -2513,25 +2588,25 @@ def _get_ecengroups(): def esfc(self): - # eupd_cdump = 'gdas' if 'gdas' in self.app_config.eupd_cdumps else 'gfs' + # eupd_run = 'gdas' if 'gdas' in self.app_config.eupd_runs else 'gfs' deps = [] - dep_dict = {'type': 'task', 'name': f'{self.cdump.replace("enkf","")}analcalc'} + dep_dict = {'type': 'task', 'name': f'{self.run.replace("enkf","")}analcalc'} deps.append(rocoto.add_dependency(dep_dict)) if self.app_config.do_jediatmens: - dep_dict = {'type': 'task', 'name': f'{self.cdump}atmensanlfinal'} + dep_dict = {'type': 'task', 'name': f'{self.run}atmensanlfinal'} else: - dep_dict = {'type': 'task', 'name': f'{self.cdump}eupd'} + dep_dict = {'type': 'task', 'name': f'{self.run}eupd'} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep_condition='and', dep=deps) resources = self.get_resource('esfc') - task_name = f'{self.cdump}esfc' + task_name = f'{self.run}esfc' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, 'envars': self.envars, - 'cycledef': self.cdump.replace('enkf', ''), + 'cycledef': self.run.replace('enkf', ''), 'command': f'{self.HOMEgfs}/jobs/rocoto/esfc.sh', 'job_name': f'{self.pslot}_{task_name}_@H', 'log': f'{self.rotdir}/logs/@Y@m@d@H/{task_name}.log', @@ -2545,9 +2620,9 @@ def esfc(self): def efcs(self): deps = [] - dep_dict = {'type': 'metatask', 'name': f'{self.cdump}ecmn'} + dep_dict = {'type': 'metatask', 'name': f'{self.run}ecmn'} deps.append(rocoto.add_dependency(dep_dict)) - dep_dict = {'type': 'task', 'name': f'{self.cdump}esfc'} + dep_dict = {'type': 'task', 'name': f'{self.run}esfc'} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep_condition='and', dep=deps) dep_dict = {'type': 'cycleexist', 'condition': 'not', 'offset': f"-{timedelta_to_HMS(self._base['cycle_interval'])}"} @@ -2555,31 +2630,30 @@ def efcs(self): dependencies = rocoto.create_dependency(dep_condition='or', dep=dependencies) efcsenvars = self.envars.copy() - efcsenvars.append(rocoto.create_envar(name='ENSGRP', value='#grp#')) + efcsenvars_dict = {'ENSMEM': '#member#', + 'MEMDIR': 'mem#member#' + } + for key, value in efcsenvars_dict.items(): + efcsenvars.append(rocoto.create_envar(name=key, value=str(value))) - groups = self._get_hybgroups(self._base['NMEM_ENS'], self._configs['efcs']['NMEM_EFCSGRP']) - - if self.cdump == "enkfgfs": - groups = self._get_hybgroups(self._base['NMEM_ENS_GFS'], self._configs['efcs']['NMEM_EFCSGRP_GFS']) - cycledef = 'gdas_half,gdas' if self.cdump in ['enkfgdas'] else self.cdump.replace('enkf', '') + cycledef = 'gdas_half,gdas' if self.run in ['enkfgdas'] else self.run.replace('enkf', '') resources = self.get_resource('efcs') - var_dict = {'grp': groups} - - task_name = f'{self.cdump}efcs#grp#' + task_name = f'{self.run}fcst_mem#member#' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, 'envars': efcsenvars, 'cycledef': cycledef, - 'command': f'{self.HOMEgfs}/jobs/rocoto/efcs.sh', + 'command': f'{self.HOMEgfs}/jobs/rocoto/fcst.sh', 'job_name': f'{self.pslot}_{task_name}_@H', 'log': f'{self.rotdir}/logs/@Y@m@d@H/{task_name}.log', 'maxtries': '&MAXTRIES;' } - metatask_dict = {'task_name': f'{self.cdump}efmn', - 'var_dict': var_dict, + member_var_dict = {'member': ' '.join([str(mem).zfill(3) for mem in range(1, self.nmem + 1)])} + metatask_dict = {'task_name': f'{self.run}fcst', + 'var_dict': member_var_dict, 'task_dict': task_dict } @@ -2589,19 +2663,19 @@ def efcs(self): def echgres(self): - self._is_this_a_gdas_task(self.cdump, 'echgres') + self._is_this_a_gdas_task(self.run, 'echgres') deps = [] - dep_dict = {'type': 'task', 'name': f'{self.cdump.replace("enkf","")}fcst'} + dep_dict = {'type': 'task', 'name': f'{self.run.replace("enkf","")}fcst'} deps.append(rocoto.add_dependency(dep_dict)) - dep_dict = {'type': 'task', 'name': f'{self.cdump}efcs01'} + dep_dict = {'type': 'task', 'name': f'{self.run}fcst_mem001'} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep_condition='and', dep=deps) - cycledef = 'gdas_half,gdas' if self.cdump in ['enkfgdas'] else self.cdump + cycledef = 'gdas_half,gdas' if self.run in ['enkfgdas'] else self.run resources = self.get_resource('echgres') - task_name = f'{self.cdump}echgres' + task_name = f'{self.run}echgres' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -2623,7 +2697,7 @@ def _get_eposgroups(epos): fhmin = epos['FHMIN_ENKF'] fhmax = epos['FHMAX_ENKF'] fhout = epos['FHOUT_ENKF'] - if self.cdump == "enkfgfs": + if self.run == "enkfgfs": fhmax = epos['FHMAX_ENKF_GFS'] fhout = epos['FHOUT_ENKF_GFS'] fhrs = range(fhmin, fhmax + fhout, fhout) @@ -2642,7 +2716,7 @@ def _get_eposgroups(epos): return grp, dep, lst deps = [] - dep_dict = {'type': 'metatask', 'name': f'{self.cdump}efmn'} + dep_dict = {'type': 'metatask', 'name': f'{self.run}fcst'} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep=deps) @@ -2656,11 +2730,11 @@ def _get_eposgroups(epos): varval1, varval2, varval3 = _get_eposgroups(self._configs['epos']) var_dict = {varname1: varval1, varname2: varval2, varname3: varval3} - cycledef = 'gdas_half,gdas' if self.cdump in ['enkfgdas'] else self.cdump.replace('enkf', '') + cycledef = 'gdas_half,gdas' if self.run in ['enkfgdas'] else self.run.replace('enkf', '') resources = self.get_resource('epos') - task_name = f'{self.cdump}epos#{varname1}#' + task_name = f'{self.run}epos#{varname1}#' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -2672,7 +2746,7 @@ def _get_eposgroups(epos): 'maxtries': '&MAXTRIES;' } - metatask_dict = {'task_name': f'{self.cdump}epmn', + metatask_dict = {'task_name': f'{self.run}epmn', 'var_dict': var_dict, 'task_dict': task_dict } @@ -2684,34 +2758,34 @@ def _get_eposgroups(epos): def earc(self): deps = [] - dep_dict = {'type': 'metatask', 'name': f'{self.cdump}epmn'} + dep_dict = {'type': 'metatask', 'name': f'{self.run}epmn'} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep=deps) earcenvars = self.envars.copy() earcenvars.append(rocoto.create_envar(name='ENSGRP', value='#grp#')) - groups = self._get_hybgroups(self._base['NMEM_ENS'], self._configs['earc']['NMEM_EARCGRP'], start_index=0) - - cycledef = 'gdas_half,gdas' if self.cdump in ['enkfgdas'] else self.cdump.replace('enkf', '') + # Integer division is floor division, but we need ceiling division + n_groups = -(self.nmem // -self._configs['earc']['NMEM_EARCGRP']) + groups = ' '.join([f'{grp:02d}' for grp in range(0, n_groups + 1)]) resources = self.get_resource('earc') var_dict = {'grp': groups} - task_name = f'{self.cdump}earc#grp#' + task_name = f'{self.run}earc#grp#' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, 'envars': earcenvars, - 'cycledef': cycledef, + 'cycledef': self.run.replace('enkf', ''), 'command': f'{self.HOMEgfs}/jobs/rocoto/earc.sh', 'job_name': f'{self.pslot}_{task_name}_@H', 'log': f'{self.rotdir}/logs/@Y@m@d@H/{task_name}.log', 'maxtries': '&MAXTRIES;' } - metatask_dict = {'task_name': f'{self.cdump}eamn', + metatask_dict = {'task_name': f'{self.run}eamn', 'var_dict': var_dict, 'task_dict': task_dict } diff --git a/workflow/rocoto/rocoto.py b/workflow/rocoto/rocoto.py index 679c0952ed..0abb56cafb 100644 --- a/workflow/rocoto/rocoto.py +++ b/workflow/rocoto/rocoto.py @@ -8,6 +8,7 @@ ABOUT: Helper module to create tasks, metatasks, and dependencies for Rocoto + Rocoto documentation is available at https://christopherwharrop.github.io/rocoto ''' __all__ = ['create_task', @@ -182,7 +183,8 @@ def add_dependency(dep_dict: Dict[str, Any]) -> str: 'data': _add_data_tag, 'cycleexist': _add_cycle_tag, 'streq': _add_streq_tag, - 'strneq': _add_streq_tag} + 'strneq': _add_streq_tag, + 'sh': _add_sh_tag} dep_condition = dep_dict.get('condition', None) dep_type = dep_dict.get('type', None) @@ -333,6 +335,31 @@ def _add_streq_tag(dep_dict: Dict[str, Any]) -> str: return string +def _add_sh_tag(dep_dict: Dict[str, Any]) -> str: + """ + create a simple shell execution tag + :param: dep_dict: shell command to execute + :type dep_dict: dict + :return: Rocoto simple shell execution dependency + :rtype: str + """ + + shell = dep_dict.get('shell', '/bin/sh') + command = dep_dict.get('command', 'echo "Hello World"') + + if '@' in command: + offset_string_b = f'' + offset_string_e = '' + else: + offset_string_b = '' + offset_string_e = '' + cmd = f'{offset_string_b}{command}{offset_string_e}' + + string = f'{cmd}' + + return string + + def _traverse(o, tree_types=(list, tuple)): """ Traverse through a list of lists or tuples and yield the value diff --git a/workflow/rocoto/tasks_emc.py b/workflow/rocoto/tasks_emc.py index 1c79de0c19..353d2aa943 100644 --- a/workflow/rocoto/tasks_emc.py +++ b/workflow/rocoto/tasks_emc.py @@ -1,9 +1,11 @@ #!/usr/bin/env python3 +import copy import numpy as np from applications.applications import AppConfig import rocoto.rocoto as rocoto from wxflow import Template, TemplateConstants, to_timedelta +from typing import List __all__ = ['Tasks'] @@ -12,21 +14,21 @@ class Tasks: SERVICE_TASKS = ['arch', 'earc'] VALID_TASKS = ['aerosol_init', 'stage_ic', 'prep', 'anal', 'sfcanl', 'analcalc', 'analdiag', 'arch', "cleanup", - 'prepatmiodaobs', 'atmanlinit', 'atmanlrun', 'atmanlfinal', + 'prepatmiodaobs', 'atmanlinit', 'atmanlvar', 'atmanlfv3inc', 'atmanlfinal', 'prepoceanobs', - 'ocnanalprep', 'ocnanalbmat', 'ocnanalrun', 'ocnanalchkpt', 'ocnanalpost', 'ocnanalvrfy', + 'ocnanalprep', 'marinebmat', 'ocnanalrun', 'ocnanalecen', 'ocnanalchkpt', 'ocnanalpost', 'ocnanalvrfy', 'earc', 'ecen', 'echgres', 'ediag', 'efcs', 'eobs', 'eomg', 'epos', 'esfc', 'eupd', - 'atmensanlinit', 'atmensanlrun', 'atmensanlfinal', + 'atmensanlinit', 'atmensanlletkf', 'atmensanlfv3inc', 'atmensanlfinal', 'aeroanlinit', 'aeroanlrun', 'aeroanlfinal', - 'preplandobs', 'landanl', + 'prepsnowobs', 'snowanl', 'fcst', - 'atmanlupp', 'atmanlprod', 'atmupp', 'atmprod', 'goesupp', - 'ocnpost', + 'atmanlupp', 'atmanlprod', 'atmupp', 'goesupp', + 'atmos_prod', 'ocean_prod', 'ice_prod', 'verfozn', 'verfrad', 'vminmon', 'metp', 'tracker', 'genesis', 'genesis_fsu', - 'postsnd', 'awips_g2', 'awips_20km_1p0deg', 'fbwind', + 'postsnd', 'awips_20km_1p0deg', 'fbwind', 'gempak', 'gempakmeta', 'gempakmetancdc', 'gempakncdcupapgif', 'gempakpgrb2spec', 'npoess_pgrb2_0p5deg' 'waveawipsbulls', 'waveawipsgridded', 'wavegempak', 'waveinit', 'wavepostbndpnt', 'wavepostbndpntbll', 'wavepostpnt', 'wavepostsbs', 'waveprep', @@ -35,32 +37,43 @@ class Tasks: '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'] - def __init__(self, app_config: AppConfig, cdump: str) -> None: + def __init__(self, app_config: AppConfig, run: str) -> None: - self.app_config = app_config - self.cdump = cdump + self.app_config = copy.deepcopy(app_config) + self.run = run + # Re-source the configs with RUN specified + print(f"Source configs with RUN={run}") + self._configs = self.app_config.source_configs(run=run, log=False) + # Update the base config for the application + self._configs['base'] = self.app_config.update_base(self._configs['base']) # Save dict_configs and base in the internal state (never know where it may be needed) - self._configs = self.app_config.configs self._base = self._configs['base'] + self.HOMEgfs = self._base['HOMEgfs'] self.rotdir = self._base['ROTDIR'] self.pslot = self._base['PSLOT'] + if self.run == "enkfgfs": + self.nmem = int(self._base['NMEM_ENS_GFS']) + else: + self.nmem = int(self._base['NMEM_ENS']) self._base['cycle_interval'] = to_timedelta(f'{self._base["assim_freq"]}H') self.n_tiles = 6 # TODO - this needs to be elsewhere + # DATAROOT is set by prod_envir in ops. Here, we use `STMP` to construct DATAROOT + dataroot_str = f"{self._base.get('STMP')}/RUNDIRS/{self._base.get('PSLOT')}/{self.run}.@Y@m@d@H" envar_dict = {'RUN_ENVIR': self._base.get('RUN_ENVIR', 'emc'), 'HOMEgfs': self.HOMEgfs, 'EXPDIR': self._base.get('EXPDIR'), 'NET': self._base.get('NET'), - 'CDUMP': self.cdump, - 'RUN': self.cdump, + 'RUN': self.run, 'CDATE': '@Y@m@d@H', 'PDY': '@Y@m@d', 'cyc': '@H', 'COMROOT': self._base.get('COMROOT'), - 'DATAROOT': self._base.get('DATAROOT')} + 'DATAROOT': dataroot_str} + self.envars = self._set_envars(envar_dict) @staticmethod @@ -72,12 +85,6 @@ def _set_envars(envar_dict) -> list: return envars - @staticmethod - def _get_hybgroups(nens: int, nmem_per_group: int, start_index: int = 1): - ngrps = nens / nmem_per_group - groups = ' '.join([f'{x:02d}' for x in range(start_index, int(ngrps) + 1)]) - return groups - def _template_to_rocoto_cycstring(self, template: str, subs_dict: dict = {}) -> str: ''' Takes a string templated with ${ } and converts it into a string suitable @@ -87,8 +94,8 @@ def _template_to_rocoto_cycstring(self, template: str, subs_dict: dict = {}) -> Variables substitued by default: ${ROTDIR} -> '&ROTDIR;' - ${RUN} -> self.cdump - ${DUMP} -> self.cdump + ${RUN} -> self.run + ${DUMP} -> self.run ${MEMDIR} -> '' ${YMD} -> '@Y@m@d' ${HH} -> '@H' @@ -110,8 +117,8 @@ def _template_to_rocoto_cycstring(self, template: str, subs_dict: dict = {}) -> # Defaults rocoto_conversion_dict = { 'ROTDIR': '&ROTDIR;', - 'RUN': self.cdump, - 'DUMP': self.cdump, + 'RUN': self.run, + 'DUMP': self.run, 'MEMDIR': '', 'YMD': '@Y@m@d', 'HH': '@H' @@ -123,12 +130,49 @@ def _template_to_rocoto_cycstring(self, template: str, subs_dict: dict = {}) -> TemplateConstants.DOLLAR_CURLY_BRACE, rocoto_conversion_dict.get) + @staticmethod + def _get_forecast_hours(run, config, component='atmos') -> List[str]: + # Make a local copy of the config to avoid modifying the original + local_config = config.copy() + + # Ocean/Ice components do not have a HF output option like the atmosphere + if component in ['ocean', 'ice']: + local_config['FHMAX_HF_GFS'] = 0 + + if component in ['ocean']: + local_config['FHOUT_HF_GFS'] = config['FHOUT_OCN_GFS'] + local_config['FHOUT_GFS'] = config['FHOUT_OCN_GFS'] + local_config['FHOUT'] = config['FHOUT_OCN'] + + if component in ['ice']: + local_config['FHOUT_HF_GFS'] = config['FHOUT_ICE_GFS'] + local_config['FHOUT_GFS'] = config['FHOUT_ICE_GFS'] + local_config['FHOUT'] = config['FHOUT_ICE'] + + fhmin = local_config['FHMIN'] + + # Get a list of all forecast hours + fhrs = [] + if run in ['gdas']: + fhmax = local_config['FHMAX'] + fhout = local_config['FHOUT'] + fhrs = list(range(fhmin, fhmax + fhout, fhout)) + elif run in ['gfs', 'gefs']: + fhmax = local_config['FHMAX_GFS'] + fhout = local_config['FHOUT_GFS'] + fhmax_hf = local_config['FHMAX_HF_GFS'] + fhout_hf = local_config['FHOUT_HF_GFS'] + fhrs_hf = range(fhmin, fhmax_hf + fhout_hf, fhout_hf) + fhrs = list(fhrs_hf) + list(range(fhrs_hf[-1] + fhout, fhmax + fhout, fhout)) + + return fhrs + def get_resource(self, task_name): """ Given a task name (task_name) and its configuration (task_names), return a dictionary of resources (task_resource) used by the task. Task resource dictionary includes: - account, walltime, cores, nodes, ppn, threads, memory, queue, partition, native + account, walltime, ntasks, nodes, ppn, threads, memory, queue, partition, native """ scheduler = self.app_config.scheduler @@ -137,32 +181,28 @@ def get_resource(self, task_name): account = task_config['ACCOUNT'] - walltime = task_config[f'wtime_{task_name}'] - if self.cdump in ['gfs'] and f'wtime_{task_name}_gfs' in task_config.keys(): - walltime = task_config[f'wtime_{task_name}_gfs'] + walltime = task_config[f'walltime'] + ntasks = task_config[f'ntasks'] + ppn = task_config[f'tasks_per_node'] - cores = task_config[f'npe_{task_name}'] - if self.cdump in ['gfs'] and f'npe_{task_name}_gfs' in task_config.keys(): - cores = task_config[f'npe_{task_name}_gfs'] + nodes = int(np.ceil(float(ntasks) / float(ppn))) - ppn = task_config[f'npe_node_{task_name}'] - if self.cdump in ['gfs'] and f'npe_node_{task_name}_gfs' in task_config.keys(): - ppn = task_config[f'npe_node_{task_name}_gfs'] + threads = task_config[f'threads_per_task'] - nodes = int(np.ceil(float(cores) / float(ppn))) + # Memory is not required + memory = task_config.get(f'memory', None) - threads = task_config[f'nth_{task_name}'] - if self.cdump in ['gfs'] and f'nth_{task_name}_gfs' in task_config.keys(): - threads = task_config[f'nth_{task_name}_gfs'] - - memory = task_config.get(f'memory_{task_name}', None) if scheduler in ['pbspro']: if task_config.get('prepost', False): memory += ':prepost=true' native = None if scheduler in ['pbspro']: - native = '-l debug=true,place=vscatter' + # Set place=vscatter by default and debug=true if DEBUG_POSTSCRIPT="YES" + if self._base['DEBUG_POSTSCRIPT']: + native = '-l debug=true,place=vscatter' + else: + native = '-l place=vscatter' # Set either exclusive or shared - default on WCOSS2 is exclusive when not set if task_config.get('is_exclusive', False): native += ':exclhost' @@ -170,6 +210,10 @@ def get_resource(self, task_name): native += ':shared' elif scheduler in ['slurm']: native = '--export=NONE' + if task_config['RESERVATION'] != "": + native += '' if task_name in Tasks.SERVICE_TASKS else ' --reservation=' + task_config['RESERVATION'] + if task_config.get('CLUSTERS', "") not in ["", '@CLUSTERS@']: + native += ' --clusters=' + task_config['CLUSTERS'] queue = task_config['QUEUE_SERVICE'] if task_name in Tasks.SERVICE_TASKS else task_config['QUEUE'] @@ -181,7 +225,7 @@ def get_resource(self, task_name): task_resource = {'account': account, 'walltime': walltime, 'nodes': nodes, - 'cores': cores, + 'ntasks': ntasks, 'ppn': ppn, 'threads': threads, 'memory': memory, diff --git a/workflow/rocoto/tasks_gsl.py b/workflow/rocoto/tasks_gsl.py index 371721bbb9..3ebd2d9437 100644 --- a/workflow/rocoto/tasks_gsl.py +++ b/workflow/rocoto/tasks_gsl.py @@ -1,9 +1,11 @@ #!/usr/bin/env python3 +import copy import numpy as np from applications.applications import AppConfig import rocoto.rocoto as rocoto from wxflow import Template, TemplateConstants, to_timedelta +from typing import List __all__ = ['Tasks'] @@ -12,21 +14,21 @@ class Tasks: SERVICE_TASKS = ['arch', 'earc'] VALID_TASKS = ['aerosol_init', 'stage_ic', 'prep', 'anal', 'sfcanl', 'analcalc', 'analdiag', 'arch', "cleanup", - 'prepatmiodaobs', 'atmanlinit', 'atmanlrun', 'atmanlfinal', + 'prepatmiodaobs', 'atmanlinit', 'atmanlvar', 'atmanlfv3inc', 'atmanlfinal', 'prepoceanobs', - 'ocnanalprep', 'ocnanalbmat', 'ocnanalrun', 'ocnanalchkpt', 'ocnanalpost', 'ocnanalvrfy', + 'ocnanalprep', 'marinebmat', 'ocnanalrun', 'ocnanalecen', 'ocnanalchkpt', 'ocnanalpost', 'ocnanalvrfy', 'earc', 'ecen', 'echgres', 'ediag', 'efcs', 'eobs', 'eomg', 'epos', 'esfc', 'eupd', - 'atmensanlinit', 'atmensanlrun', 'atmensanlfinal', + 'atmensanlinit', 'atmensanlletkf', 'atmensanlfv3inc', 'atmensanlfinal', 'aeroanlinit', 'aeroanlrun', 'aeroanlfinal', - 'preplandobs', 'landanl', + 'prepsnowobs', 'snowanl', 'fcst', - 'atmanlupp', 'atmanlprod', 'atmupp', 'atmprod', 'goesupp', - 'ocnpost', + 'atmanlupp', 'atmanlprod', 'atmupp', 'goesupp', + 'atmos_prod', 'ocean_prod', 'ice_prod', 'verfozn', 'verfrad', 'vminmon', 'metp', 'tracker', 'genesis', 'genesis_fsu', - 'postsnd', 'awips_g2', 'awips_20km_1p0deg', 'fbwind', + 'postsnd', 'awips_20km_1p0deg', 'fbwind', 'gempak', 'gempakmeta', 'gempakmetancdc', 'gempakncdcupapgif', 'gempakpgrb2spec', 'npoess_pgrb2_0p5deg' 'waveawipsbulls', 'waveawipsgridded', 'wavegempak', 'waveinit', 'wavepostbndpnt', 'wavepostbndpntbll', 'wavepostpnt', 'wavepostsbs', 'waveprep', @@ -35,33 +37,45 @@ class Tasks: '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'] - def __init__(self, app_config: AppConfig, cdump: str) -> None: + def __init__(self, app_config: AppConfig, run: str) -> None: - self.app_config = app_config - self.cdump = cdump + self.app_config = copy.deepcopy(app_config) + self.run = run + # Re-source the configs with RUN specified + print(f"Source configs with RUN={run}") + self._configs = self.app_config.source_configs(run=run, log=False) + # Update the base config for the application + self._configs['base'] = self.app_config.update_base(self._configs['base']) # Save dict_configs and base in the internal state (never know where it may be needed) - self._configs = self.app_config.configs self._base = self._configs['base'] + self.HOMEgfs = self._base['HOMEgfs'] self.rotdir = self._base['ROTDIR'] self.pslot = self._base['PSLOT'] + if self.run == "enkfgfs": + self.nmem = int(self._base['NMEM_ENS_GFS']) + else: + self.nmem = int(self._base['NMEM_ENS']) self._base['cycle_interval'] = to_timedelta(f'{self._base["assim_freq"]}H') self.n_tiles = 6 # TODO - this needs to be elsewhere + # DATAROOT is set by prod_envir in ops. Here, we use `STMP` to construct DATAROOT + dataroot_str = f"{self._base.get('STMP')}/RUNDIRS/{self._base.get('PSLOT')}/{self.run}.@Y@m@d@H" envar_dict = {'RUN_ENVIR': self._base.get('RUN_ENVIR', 'emc'), 'HOMEgfs': self.HOMEgfs, 'EXPDIR': self._base.get('EXPDIR'), +#JKH 'ROTDIR': self._base.get('ROTDIR'), 'NET': self._base.get('NET'), - 'CDUMP': self.cdump, - 'RUN': self.cdump, + 'RUN': self.run, 'CDATE': '@Y@m@d@H', 'PDY': '@Y@m@d', 'cyc': '@H', 'COMROOT': self._base.get('COMROOT'), - 'DATAROOT': self._base.get('DATAROOT')} + 'DATAROOT': dataroot_str} + self.envars = self._set_envars(envar_dict) @staticmethod @@ -73,12 +87,6 @@ def _set_envars(envar_dict) -> list: return envars - @staticmethod - def _get_hybgroups(nens: int, nmem_per_group: int, start_index: int = 1): - ngrps = nens / nmem_per_group - groups = ' '.join([f'{x:02d}' for x in range(start_index, int(ngrps) + 1)]) - return groups - def _template_to_rocoto_cycstring(self, template: str, subs_dict: dict = {}) -> str: ''' Takes a string templated with ${ } and converts it into a string suitable @@ -88,8 +96,8 @@ def _template_to_rocoto_cycstring(self, template: str, subs_dict: dict = {}) -> Variables substitued by default: ${ROTDIR} -> '&ROTDIR;' - ${RUN} -> self.cdump - ${DUMP} -> self.cdump + ${RUN} -> self.run + ${DUMP} -> self.run ${MEMDIR} -> '' ${YMD} -> '@Y@m@d' ${HH} -> '@H' @@ -111,8 +119,8 @@ def _template_to_rocoto_cycstring(self, template: str, subs_dict: dict = {}) -> # Defaults rocoto_conversion_dict = { 'ROTDIR': '&ROTDIR;', - 'RUN': self.cdump, - 'DUMP': self.cdump, + 'RUN': self.run, + 'DUMP': self.run, 'MEMDIR': '', 'YMD': '@Y@m@d', 'HH': '@H' @@ -124,12 +132,49 @@ def _template_to_rocoto_cycstring(self, template: str, subs_dict: dict = {}) -> TemplateConstants.DOLLAR_CURLY_BRACE, rocoto_conversion_dict.get) + @staticmethod + def _get_forecast_hours(run, config, component='atmos') -> List[str]: + # Make a local copy of the config to avoid modifying the original + local_config = config.copy() + + # Ocean/Ice components do not have a HF output option like the atmosphere + if component in ['ocean', 'ice']: + local_config['FHMAX_HF_GFS'] = 0 + + if component in ['ocean']: + local_config['FHOUT_HF_GFS'] = config['FHOUT_OCN_GFS'] + local_config['FHOUT_GFS'] = config['FHOUT_OCN_GFS'] + local_config['FHOUT'] = config['FHOUT_OCN'] + + if component in ['ice']: + local_config['FHOUT_HF_GFS'] = config['FHOUT_ICE_GFS'] + local_config['FHOUT_GFS'] = config['FHOUT_ICE_GFS'] + local_config['FHOUT'] = config['FHOUT_ICE'] + + fhmin = local_config['FHMIN'] + + # Get a list of all forecast hours + fhrs = [] + if run in ['gdas']: + fhmax = local_config['FHMAX'] + fhout = local_config['FHOUT'] + fhrs = list(range(fhmin, fhmax + fhout, fhout)) + elif run in ['gfs', 'gefs']: + fhmax = local_config['FHMAX_GFS'] + fhout = local_config['FHOUT_GFS'] + fhmax_hf = local_config['FHMAX_HF_GFS'] + fhout_hf = local_config['FHOUT_HF_GFS'] + fhrs_hf = range(fhmin, fhmax_hf + fhout_hf, fhout_hf) + fhrs = list(fhrs_hf) + list(range(fhrs_hf[-1] + fhout, fhmax + fhout, fhout)) + + return fhrs + def get_resource(self, task_name): """ Given a task name (task_name) and its configuration (task_names), return a dictionary of resources (task_resource) used by the task. Task resource dictionary includes: - account, walltime, cores, nodes, ppn, threads, memory, queue, partition, native + account, walltime, ntasks, nodes, ppn, threads, memory, queue, partition, native """ scheduler = self.app_config.scheduler @@ -138,39 +183,40 @@ def get_resource(self, task_name): account = task_config['ACCOUNT'] - walltime = task_config[f'wtime_{task_name}'] - if self.cdump in ['gfs'] and f'wtime_{task_name}_gfs' in task_config.keys(): - walltime = task_config[f'wtime_{task_name}_gfs'] + walltime = task_config[f'walltime'] + ntasks = task_config[f'ntasks'] + ppn = task_config[f'tasks_per_node'] - cores = task_config[f'npe_{task_name}'] - if self.cdump in ['gfs'] and f'npe_{task_name}_gfs' in task_config.keys(): - cores = task_config[f'npe_{task_name}_gfs'] + nodes = int(np.ceil(float(ntasks) / float(ppn))) - ppn = task_config[f'npe_node_{task_name}'] - if self.cdump in ['gfs'] and f'npe_node_{task_name}_gfs' in task_config.keys(): - ppn = task_config[f'npe_node_{task_name}_gfs'] + threads = task_config[f'threads_per_task'] - nodes = int(np.ceil(float(cores) / float(ppn))) + # Memory is not required + memory = task_config.get(f'memory', None) - threads = task_config[f'nth_{task_name}'] - if self.cdump in ['gfs'] and f'nth_{task_name}_gfs' in task_config.keys(): - threads = task_config[f'nth_{task_name}_gfs'] - - memory = task_config.get(f'memory_{task_name}', None) if scheduler in ['pbspro']: if task_config.get('prepost', False): memory += ':prepost=true' native = None if scheduler in ['pbspro']: - native = '-l debug=true,place=vscatter' + # Set place=vscatter by default and debug=true if DEBUG_POSTSCRIPT="YES" + if self._base['DEBUG_POSTSCRIPT']: + native = '-l debug=true,place=vscatter' + else: + native = '-l place=vscatter' # Set either exclusive or shared - default on WCOSS2 is exclusive when not set if task_config.get('is_exclusive', False): native += ':exclhost' else: native += ':shared' elif scheduler in ['slurm']: +#JKH native = '--export=NONE' native = '&NATIVE_STR;' + if task_config['RESERVATION'] != "": + native += '' if task_name in Tasks.SERVICE_TASKS else ' --reservation=' + task_config['RESERVATION'] + if task_config.get('CLUSTERS', "") not in ["", '@CLUSTERS@']: + native += ' --clusters=' + task_config['CLUSTERS'] queue = task_config['QUEUE_SERVICE'] if task_name in Tasks.SERVICE_TASKS else task_config['QUEUE'] @@ -182,7 +228,7 @@ def get_resource(self, task_name): task_resource = {'account': account, 'walltime': walltime, 'nodes': nodes, - 'cores': cores, + 'ntasks': ntasks, 'ppn': ppn, 'threads': threads, 'memory': memory, diff --git a/workflow/rocoto/workflow_tasks.py b/workflow/rocoto/workflow_tasks.py index 84af898d36..78c31dba1b 100644 --- a/workflow/rocoto/workflow_tasks.py +++ b/workflow/rocoto/workflow_tasks.py @@ -14,10 +14,10 @@ def get_wf_tasks(app_config: AppConfig) -> List: """ tasks = [] - # Loop over all keys of cycles (CDUMP) - for cdump, cdump_tasks in app_config.task_names.items(): - task_obj = tasks_factory.create(app_config.net, app_config, cdump) # create Task object based on cdump - for task_name in cdump_tasks: + # Loop over all keys of cycles (RUN) + for run, run_tasks in app_config.task_names.items(): + task_obj = tasks_factory.create(app_config.net, app_config, run) # create Task object based on run + for task_name in run_tasks: tasks.append(task_obj.get_task(task_name)) return tasks diff --git a/workflow/rocoto_viewer.py b/workflow/rocoto_viewer.py index 95dd9e76dd..459381f601 100755 --- a/workflow/rocoto_viewer.py +++ b/workflow/rocoto_viewer.py @@ -1360,7 +1360,7 @@ def main(screen): screen.refresh() curses.mousemask(1) curses.noecho() - for i in range(0, curses.COLORS): + for i in range(0, curses.COLORS - 1): curses.init_pair(i + 1, i, curses.COLOR_BLACK) if i == 4: curses.init_pair(i + 1, i, curses.COLOR_WHITE) diff --git a/workflow/setup_expt.py b/workflow/setup_expt.py index 7d7ac84aad..3e70df0f02 100755 --- a/workflow/setup_expt.py +++ b/workflow/setup_expt.py @@ -73,10 +73,10 @@ def fill_ROTDIR_cycled(host, inputs): # Test if we are using the new COM structure or the old flat one for ICs if inputs.start in ['warm']: - pathstr = os.path.join(inputs.icsdir, f'{inputs.cdump}.{rdatestr[:8]}', + pathstr = os.path.join(inputs.icsdir, f'{inputs.run}.{rdatestr[:8]}', rdatestr[8:], 'model_data', 'atmos') else: - pathstr = os.path.join(inputs.icsdir, f'{inputs.cdump}.{idatestr[:8]}', + pathstr = os.path.join(inputs.icsdir, f'{inputs.run}.{idatestr[:8]}', idatestr[8:], 'model_data', 'atmos') if os.path.isdir(pathstr): @@ -96,6 +96,7 @@ def fill_ROTDIR_cycled(host, inputs): dst_ocn_rst_dir = os.path.join('model_data', 'ocean', 'restart') dst_ocn_anl_dir = os.path.join('analysis', 'ocean') dst_ice_rst_dir = os.path.join('model_data', 'ice', 'restart') + dst_ice_anl_dir = os.path.join('analysis', 'ice') dst_atm_anl_dir = os.path.join('analysis', 'atmos') if flat_structure: @@ -111,6 +112,7 @@ def fill_ROTDIR_cycled(host, inputs): src_ocn_rst_dir = os.path.join('ocean', 'RESTART') src_ocn_anl_dir = 'ocean' src_ice_rst_dir = os.path.join('ice', 'RESTART') + src_ice_anl_dir = dst_ice_anl_dir src_atm_anl_dir = 'atmos' else: src_atm_dir = dst_atm_dir @@ -118,6 +120,7 @@ def fill_ROTDIR_cycled(host, inputs): src_ocn_rst_dir = dst_ocn_rst_dir src_ocn_anl_dir = dst_ocn_anl_dir src_ice_rst_dir = dst_ice_rst_dir + src_ice_anl_dir = dst_ice_anl_dir src_atm_anl_dir = dst_atm_anl_dir def link_files_from_src_to_dst(src_dir, dst_dir): @@ -129,8 +132,8 @@ def link_files_from_src_to_dst(src_dir, dst_dir): # Link ensemble member initial conditions if inputs.nens > 0: - previous_cycle_dir = f'enkf{inputs.cdump}.{rdatestr[:8]}/{rdatestr[8:]}' - current_cycle_dir = f'enkf{inputs.cdump}.{idatestr[:8]}/{idatestr[8:]}' + previous_cycle_dir = f'enkf{inputs.run}.{rdatestr[:8]}/{rdatestr[8:]}' + current_cycle_dir = f'enkf{inputs.run}.{idatestr[:8]}/{idatestr[8:]}' for ii in range(1, inputs.nens + 1): memdir = f'mem{ii:03d}' @@ -152,7 +155,7 @@ def link_files_from_src_to_dst(src_dir, dst_dir): link_files_from_src_to_dst(src_dir, dst_dir) # First 1/2 cycle needs a MOM6 increment - incfile = f'enkf{inputs.cdump}.t{idatestr[8:]}z.ocninc.nc' + incfile = f'enkf{inputs.run}.t{idatestr[8:]}z.ocninc.nc' src_file = os.path.join(inputs.icsdir, current_cycle_dir, memdir, src_ocn_anl_dir, incfile) dst_file = os.path.join(rotdir, current_cycle_dir, memdir, dst_ocn_anl_dir, incfile) makedirs_if_missing(os.path.join(rotdir, current_cycle_dir, memdir, dst_ocn_anl_dir)) @@ -173,8 +176,8 @@ def link_files_from_src_to_dst(src_dir, dst_dir): link_files_from_src_to_dst(src_dir, dst_dir) # Link deterministic initial conditions - previous_cycle_dir = f'{inputs.cdump}.{rdatestr[:8]}/{rdatestr[8:]}' - current_cycle_dir = f'{inputs.cdump}.{idatestr[:8]}/{idatestr[8:]}' + previous_cycle_dir = f'{inputs.run}.{rdatestr[:8]}/{rdatestr[8:]}' + current_cycle_dir = f'{inputs.run}.{idatestr[:8]}/{idatestr[8:]}' # Link atmospheric files if inputs.start in ['warm']: @@ -195,7 +198,7 @@ def link_files_from_src_to_dst(src_dir, dst_dir): link_files_from_src_to_dst(src_dir, dst_dir) # First 1/2 cycle needs a MOM6 increment - incfile = f'{inputs.cdump}.t{idatestr[8:]}z.ocninc.nc' + incfile = f'{inputs.run}.t{idatestr[8:]}z.ocninc.nc' src_file = os.path.join(inputs.icsdir, current_cycle_dir, src_ocn_anl_dir, incfile) dst_file = os.path.join(rotdir, current_cycle_dir, dst_ocn_anl_dir, incfile) makedirs_if_missing(os.path.join(rotdir, current_cycle_dir, dst_ocn_anl_dir)) @@ -203,8 +206,9 @@ def link_files_from_src_to_dst(src_dir, dst_dir): # Link ice files if do_ice: - dst_dir = os.path.join(rotdir, previous_cycle_dir, dst_ice_rst_dir) - src_dir = os.path.join(inputs.icsdir, previous_cycle_dir, src_ice_rst_dir) + # First 1/2 cycle needs a CICE6 analysis restart + src_dir = os.path.join(inputs.icsdir, current_cycle_dir, src_ice_anl_dir) + dst_dir = os.path.join(rotdir, current_cycle_dir, src_ice_anl_dir) makedirs_if_missing(dst_dir) link_files_from_src_to_dst(src_dir, dst_dir) @@ -220,10 +224,29 @@ def link_files_from_src_to_dst(src_dir, dst_dir): dst_dir = os.path.join(rotdir, current_cycle_dir, dst_atm_anl_dir) makedirs_if_missing(dst_dir) for ftype in ['abias', 'abias_pc', 'abias_air', 'radstat']: - fname = f'{inputs.cdump}.t{idatestr[8:]}z.{ftype}' + fname = f'{inputs.run}.t{idatestr[8:]}z.{ftype}' src_file = os.path.join(src_dir, fname) if os.path.exists(src_file): os.symlink(src_file, os.path.join(dst_dir, fname)) + # First 1/2 cycle also needs a atmos increment if doing warm start + if inputs.start in ['warm']: + for ftype in ['atmi003.nc', 'atminc.nc', 'atmi009.nc']: + fname = f'{inputs.run}.t{idatestr[8:]}z.{ftype}' + src_file = os.path.join(src_dir, fname) + if os.path.exists(src_file): + os.symlink(src_file, os.path.join(dst_dir, fname)) + if inputs.nens > 0: + current_cycle_dir = f'enkf{inputs.run}.{idatestr[:8]}/{idatestr[8:]}' + for ii in range(1, inputs.nens + 1): + memdir = f'mem{ii:03d}' + src_dir = os.path.join(inputs.icsdir, current_cycle_dir, memdir, src_atm_anl_dir) + dst_dir = os.path.join(rotdir, current_cycle_dir, memdir, dst_atm_anl_dir) + makedirs_if_missing(dst_dir) + for ftype in ['ratmi003.nc', 'ratminc.nc', 'ratmi009.nc']: + fname = f'enkf{inputs.run}.t{idatestr[8:]}z.{ftype}' + src_file = os.path.join(src_dir, fname) + if os.path.exists(src_file): + os.symlink(src_file, os.path.join(dst_dir, fname)) return @@ -245,12 +268,6 @@ def fill_EXPDIR(inputs): expdir = os.path.join(inputs.expdir, inputs.pslot) configs = glob.glob(f'{configdir}/config.*') - exclude_configs = ['base', 'base.emc.dyn', 'base.nco.static', 'fv3.nco.static'] - for exclude in exclude_configs: - try: - configs.remove(f'{configdir}/config.{exclude}') - except ValueError: - pass if len(configs) == 0: raise IOError(f'no config files found in {configdir}') for config in configs: @@ -270,6 +287,8 @@ def _update_defaults(dict_in: dict) -> dict: data = AttrDict(host.info, **inputs.__dict__) data.HOMEgfs = _top yaml_path = inputs.yaml + if not os.path.exists(yaml_path): + raise IOError(f'YAML file does not exist, check path:' + yaml_path) yaml_dict = _update_defaults(AttrDict(parse_j2yaml(yaml_path, data))) # First update config.base @@ -288,7 +307,8 @@ def _update_defaults(dict_in: dict) -> dict: def edit_baseconfig(host, inputs, yaml_dict): """ - Parses and populates the templated `config.base.emc.dyn` to `config.base` + Parses and populates the templated `HOMEgfs/parm/config//config.base` + to `EXPDIR/pslot/config.base` """ tmpl_dict = { @@ -316,7 +336,8 @@ def edit_baseconfig(host, inputs, yaml_dict): "@EXP_WARM_START@": is_warm_start, "@MODE@": inputs.mode, "@gfs_cyc@": inputs.gfs_cyc, - "@APP@": inputs.app + "@APP@": inputs.app, + "@NMEM_ENS@": getattr(inputs, 'nens', 0) } tmpl_dict = dict(tmpl_dict, **extend_dict) @@ -324,7 +345,6 @@ def edit_baseconfig(host, inputs, yaml_dict): if getattr(inputs, 'nens', 0) > 0: extend_dict = { "@CASEENS@": f'C{inputs.resensatmos}', - "@NMEM_ENS@": inputs.nens, } tmpl_dict = dict(tmpl_dict, **extend_dict) @@ -340,7 +360,7 @@ def edit_baseconfig(host, inputs, yaml_dict): except KeyError: pass - base_input = f'{inputs.configdir}/config.base.emc.dyn' + base_input = f'{inputs.configdir}/config.base' base_output = f'{inputs.expdir}/{inputs.pslot}/config.base' edit_config(base_input, base_output, tmpl_dict) @@ -383,7 +403,7 @@ def input_args(*argv): Method to collect user arguments for `setup_expt.py` """ - ufs_apps = ['ATM', 'ATMA', 'ATMW', 'S2S', 'S2SA', 'S2SW'] + ufs_apps = ['ATM', 'ATMA', 'ATMW', 'S2S', 'S2SA', 'S2SW', 'S2SWA'] def _common_args(parser): parser.add_argument('--pslot', help='parallel experiment name', @@ -399,12 +419,14 @@ def _common_args(parser): parser.add_argument('--idate', help='starting date of experiment, initial conditions must exist!', required=True, type=lambda dd: to_datetime(dd)) parser.add_argument('--edate', help='end date experiment', required=True, type=lambda dd: to_datetime(dd)) + parser.add_argument('--overwrite', help='overwrite previously created experiment (if it exists)', + action='store_true', required=False) return parser def _gfs_args(parser): parser.add_argument('--start', help='restart mode: warm or cold', type=str, choices=['warm', 'cold'], required=False, default='cold') - parser.add_argument('--cdump', help='CDUMP to start the experiment', + parser.add_argument('--run', help='RUN to start the experiment', type=str, required=False, default='gdas') # --configdir is hidden from help parser.add_argument('--configdir', help=SUPPRESS, type=str, required=False, default=os.path.join(_top, 'parm/config/gfs')) @@ -429,7 +451,7 @@ def _gfs_or_gefs_ensemble_args(parser): def _gfs_or_gefs_forecast_args(parser): parser.add_argument('--app', help='UFS application', type=str, - choices=ufs_apps + ['S2SWA'], required=False, default='ATM') + choices=ufs_apps, required=False, default='ATM') parser.add_argument('--gfs_cyc', help='Number of forecasts per day', type=int, choices=[1, 2, 4], default=1, required=False) return parser @@ -493,17 +515,19 @@ def _gefs_args(parser): return parser.parse_args(list(*argv) if len(argv) else None) -def query_and_clean(dirname): +def query_and_clean(dirname, force_clean=False): """ Method to query if a directory exists and gather user input for further action """ create_dir = True if os.path.exists(dirname): - print() - print(f'directory already exists in {dirname}') - print() - overwrite = input('Do you wish to over-write [y/N]: ') + print(f'\ndirectory already exists in {dirname}') + if force_clean: + overwrite = True + print(f'removing directory ........ {dirname}\n') + else: + overwrite = input('Do you wish to over-write [y/N]: ') create_dir = True if overwrite in [ 'y', 'yes', 'Y', 'YES'] else False if create_dir: @@ -553,8 +577,8 @@ def main(*argv): rotdir = os.path.join(user_inputs.comroot, user_inputs.pslot) expdir = os.path.join(user_inputs.expdir, user_inputs.pslot) - create_rotdir = query_and_clean(rotdir) - create_expdir = query_and_clean(expdir) + create_rotdir = query_and_clean(rotdir, force_clean=user_inputs.overwrite) + create_expdir = query_and_clean(expdir, force_clean=user_inputs.overwrite) if create_rotdir: makedirs_if_missing(rotdir) @@ -565,6 +589,11 @@ def main(*argv): fill_EXPDIR(user_inputs) update_configs(host, user_inputs) + print(f"*" * 100) + print(f'EXPDIR: {expdir}') + print(f'ROTDIR: {rotdir}') + print(f"*" * 100) + if __name__ == '__main__':