diff --git a/dev/ci/cases/gfsv17/marine3dvar.yaml b/dev/ci/cases/gfsv17/marine3dvar.yaml index 259397b486e..54f6aaee611 100644 --- a/dev/ci/cases/gfsv17/marine3dvar.yaml +++ b/dev/ci/cases/gfsv17/marine3dvar.yaml @@ -20,6 +20,10 @@ prepoceanobs: # will not work for realtime or retros dmpdir_exp: "${BASE_DATA}/experimental_obs" +marinebmat: + SOCA_INPUT_FIX_DIR: {{ HOMEgfs }}/fix/gdas/soca/1440x1080x75/soca + SOCA_ANL_GEOM: {{ HOMEgfs }}/fix/gdas/soca/720x540x75/soca + marineanl: SOCA_INPUT_FIX_DIR: {{ HOMEgfs }}/fix/gdas/soca/1440x1080x75/soca SOCA_ANL_GEOM: {{ HOMEgfs }}/fix/gdas/soca/720x540x75/soca diff --git a/dev/ci/cases/gfsv17/marinehyb.yaml b/dev/ci/cases/gfsv17/marinehyb.yaml index ca6e0f6f311..6559dd96e66 100644 --- a/dev/ci/cases/gfsv17/marinehyb.yaml +++ b/dev/ci/cases/gfsv17/marinehyb.yaml @@ -20,8 +20,21 @@ prepoceanobs: # will not work for realtime or retros dmpdir_exp: "${BASE_DATA}/experimental_obs" +marinebmat: + SOCA_INPUT_FIX_DIR: {{ HOMEgfs }}/fix/gdas/soca/1440x1080x75/soca + SOCA_ANL_GEOM: {{ HOMEgfs }}/fix/gdas/soca/720x540x75/soca marineanl: SOCA_INPUT_FIX_DIR: {{ HOMEgfs }}/fix/gdas/soca/1440x1080x75/soca SOCA_ANL_GEOM: {{ HOMEgfs }}/fix/gdas/soca/720x540x75/soca + SOCA_OBS_LIST: {{ HOMEgfs }}/parm/gdas/marine/obs/obs_list_gfsv17.yaml.j2 SOCA_NINNER: 100 + +marineanlecen: + SOCA_INPUT_FIX_DIR: {{ HOMEgfs }}/fix/gdas/soca/1440x1080x75/soca + SOCA_ANL_GEOM: {{ HOMEgfs }}/fix/gdas/soca/720x540x75/soca + +marineanlletkf: + SOCA_INPUT_FIX_DIR: {{ HOMEgfs }}/fix/gdas/soca/1440x1080x75/soca + SOCA_ANL_GEOM: {{ HOMEgfs }}/fix/gdas/soca/720x540x75/soca + SOCA_OBS_LIST: {{ HOMEgfs }}/parm/gdas/marine/obs/obs_list_gfsv17.yaml.j2 diff --git a/dev/ci/cases/gfsv17/s2sw.yaml b/dev/ci/cases/gfsv17/s2sw.yaml index 6286d1a0a26..a7e5077cc09 100644 --- a/dev/ci/cases/gfsv17/s2sw.yaml +++ b/dev/ci/cases/gfsv17/s2sw.yaml @@ -26,13 +26,26 @@ base: prepoceanobs: use_exp_obs: "YES" - # retro + # retro # dmpdir_exp: /lfs/h2/emc/da/noscrub/common_obsForge # realtime dmpdir_exp: /lfs/h2/emc/da/noscrub/mindo.choi/MARINE_obs/COMROOT/realtime +marinebmat: + SOCA_INPUT_FIX_DIR: {{ HOMEgfs }}/fix/gdas/soca/1440x1080x75/soca + SOCA_ANL_GEOM: {{ HOMEgfs }}/fix/gdas/soca/720x540x75/soca + marineanl: - SOCA_INPUT_FIX_DIR: {{ HOMEgfs }}/fix/gdas/soca/1440x1080x75/soca + SOCA_INPUT_FIX_DIR: {{ HOMEgfs }}/fix/gdas/soca/1440x1080x75/soca SOCA_ANL_GEOM: {{ HOMEgfs }}/fix/gdas/soca/720x540x75/soca SOCA_OBS_LIST: {{ HOMEgfs }}/parm/gdas/marine/obs/obs_list_gfsv17.yaml.j2 SOCA_NINNER: 100 + +marineanlecen: + SOCA_INPUT_FIX_DIR: {{ HOMEgfs }}/fix/gdas/soca/1440x1080x75/soca + SOCA_ANL_GEOM: {{ HOMEgfs }}/fix/gdas/soca/720x540x75/soca + +marineanlletkf: + SOCA_INPUT_FIX_DIR: {{ HOMEgfs }}/fix/gdas/soca/1440x1080x75/soca + SOCA_ANL_GEOM: {{ HOMEgfs }}/fix/gdas/soca/720x540x75/soca + SOCA_OBS_LIST: {{ HOMEgfs }}/parm/gdas/marine/obs/obs_list_gfsv17.yaml.j2 diff --git a/dev/ci/cases/gfsv17/s2sw_rdhpcs.yaml b/dev/ci/cases/gfsv17/s2sw_rdhpcs.yaml index f4924de0895..b48f14d7fe1 100644 --- a/dev/ci/cases/gfsv17/s2sw_rdhpcs.yaml +++ b/dev/ci/cases/gfsv17/s2sw_rdhpcs.yaml @@ -31,8 +31,21 @@ prepoceanobs: # ursa # dmpdir_exp: /scratch3/NCEPDEV/da/common_obsForge +marinebmat: + SOCA_INPUT_FIX_DIR: {{ HOMEgfs }}/fix/gdas/soca/1440x1080x75/soca + SOCA_ANL_GEOM: {{ HOMEgfs }}/fix/gdas/soca/720x540x75/soca + marineanl: - SOCA_INPUT_FIX_DIR: {{ HOMEgfs }}/fix/gdas/soca/1440x1080x75/soca + SOCA_INPUT_FIX_DIR: {{ HOMEgfs }}/fix/gdas/soca/1440x1080x75/soca SOCA_ANL_GEOM: {{ HOMEgfs }}/fix/gdas/soca/720x540x75/soca SOCA_OBS_LIST: {{ HOMEgfs }}/parm/gdas/marine/obs/obs_list_gfsv17.yaml.j2 SOCA_NINNER: 100 + +marineanlecen: + SOCA_INPUT_FIX_DIR: {{ HOMEgfs }}/fix/gdas/soca/1440x1080x75/soca + SOCA_ANL_GEOM: {{ HOMEgfs }}/fix/gdas/soca/720x540x75/soca + +marineanlletkf: + SOCA_INPUT_FIX_DIR: {{ HOMEgfs }}/fix/gdas/soca/1440x1080x75/soca + SOCA_ANL_GEOM: {{ HOMEgfs }}/fix/gdas/soca/720x540x75/soca + SOCA_OBS_LIST: {{ HOMEgfs }}/parm/gdas/marine/obs/obs_list_gfsv17.yaml.j2 diff --git a/dev/ci/cases/yamls/soca_hyb_gfs_defaults_ci.yaml b/dev/ci/cases/yamls/soca_hyb_gfs_defaults_ci.yaml index d5f27efb4ec..cab6674c9ad 100644 --- a/dev/ci/cases/yamls/soca_hyb_gfs_defaults_ci.yaml +++ b/dev/ci/cases/yamls/soca_hyb_gfs_defaults_ci.yaml @@ -3,6 +3,6 @@ defaults: base: DO_JEDIOCNVAR: "YES" DOHYBVAR_OCN: "YES" - DOLETKF_OCN: "NO" + DOLETKF_OCN: "YES" marineanl: SOCA_NINNER: 1 diff --git a/dev/parm/config/gcafs/yaml/defaults.yaml b/dev/parm/config/gcafs/yaml/defaults.yaml index cd1b72b075f..62e4c78ac89 100644 --- a/dev/parm/config/gcafs/yaml/defaults.yaml +++ b/dev/parm/config/gcafs/yaml/defaults.yaml @@ -64,12 +64,24 @@ snowanl: IO_LAYOUT_X: 1 IO_LAYOUT_Y: 1 +marinebmat: + SOCA_INPUT_FIX_DIR: "${FIXgfs}/gdas/soca/72x35x25/soca" + SOCA_ANL_GEOM: "${FIXgfs}/gdas/soca/72x35x25/soca" + marineanl: SOCA_INPUT_FIX_DIR: "${FIXgfs}/gdas/soca/72x35x25/soca" SOCA_ANL_GEOM: "${FIXgfs}/gdas/soca/72x35x25/soca" SOCA_OBS_LIST: "${PARMgfs}/gdas/marine/obs/obs_list.yaml" # TODO: This is also repeated in oceanprepobs SOCA_NINNER: 100 - JCB_ALGO_YAML_VAR: "${PARMgfs}/gdas/marine/marine-jcb-3dfgat.yaml.j2" + +marineanlecen: + SOCA_INPUT_FIX_DIR: "${FIXgfs}/gdas/soca/72x35x25/soca" + SOCA_ANL_GEOM: "${FIXgfs}/gdas/soca/72x35x25/soca" + +marineanlletkf: + SOCA_INPUT_FIX_DIR: "${FIXgfs}/gdas/soca/72x35x25/soca" + SOCA_ANL_GEOM: "${FIXgfs}/gdas/soca/72x35x25/soca" + SOCA_OBS_LIST: "${PARMgfs}/gdas/marine/obs/obs_list.yaml.j2" prepoceanobs: SOCA_INPUT_FIX_DIR: "${FIXgfs}/gdas/soca/72x35x25/soca" diff --git a/dev/parm/config/gfs/config.marineanl.j2 b/dev/parm/config/gfs/config.marineanl.j2 index e1f81b87b97..04fa53411c0 100644 --- a/dev/parm/config/gfs/config.marineanl.j2 +++ b/dev/parm/config/gfs/config.marineanl.j2 @@ -8,20 +8,11 @@ echo "BEGIN: config.marineanl" # TODO: This should be sourced in a config file specific to marine deterministic analysis # and renamed to remove _DET -export JCB_ALGO_YAML_VAR="{{ JCB_ALGO_YAML_VAR }}" - -export OBS_LIST_YAML="{{ SOCA_OBS_LIST }}" export INPUT_FIX_DIR="{{ SOCA_INPUT_FIX_DIR }}" export ANL_GEOM="{{ SOCA_ANL_GEOM }}" export NINNER="{{ SOCA_NINNER }}" -export DOMAIN_STACK_SIZE=116640000 #TODO: Make the stack size resolution dependent -export MARINE_JCB_GDAS_OBS="${PARMgfs}/gdas/jcb-gdas/observations/marine" - -export JEDI_CONFIG_YAML_DET="${PARMgfs}/gdas/marine/marine_det_jedi_config.yaml.j2" -export STAGE_FIX_YAML="${PARMgfs}/gdas/marine/marine_stage_fix_${OCNRES}.yaml.j2" -export STAGE_UTILITIES_YAML="${PARMgfs}/gdas/marine/marine_stage_utilities.yaml.j2" -export STAGE_DET_BKG_YAML="${PARMgfs}/gdas/marine/marine_det_stage_bkg.yaml.j2" -export STAGE_ENS_BKG_YAML="${PARMgfs}/gdas/marine/marine_ens_stage_bkg.yaml.j2" +export OBS_LIST_YAML="{{ SOCA_OBS_LIST }}" +export TASK_CONFIG_YAML="${PARMgfs}/gdas/marine/marine_det_config.yaml.j2" echo "END: config.marineanl" diff --git a/dev/parm/config/gfs/config.marineanlecen b/dev/parm/config/gfs/config.marineanlecen.j2 similarity index 51% rename from dev/parm/config/gfs/config.marineanlecen rename to dev/parm/config/gfs/config.marineanlecen.j2 index b5bd66b3cf7..30c0d056804 100644 --- a/dev/parm/config/gfs/config.marineanlecen +++ b/dev/parm/config/gfs/config.marineanlecen.j2 @@ -8,8 +8,9 @@ echo "BEGIN: config.marineanlecen" # Get task specific resources source "${EXPDIR}/config.resources" marineanlecen -export JEDI_CONFIG_YAML="${PARMgfs}/gdas/marine/marine_ecen_jedi_config.yaml.j2" -export STAGE_YAML="${PARMgfs}/gdas/marine/marine_ecen_stage.yaml.j2" -export SAVE_YAML="${PARMgfs}/gdas/marine/marine_ecen_save.yaml.j2" +export INPUT_FIX_DIR="{{ SOCA_INPUT_FIX_DIR }}" +export ANL_GEOM="{{ SOCA_ANL_GEOM }}" + +export TASK_CONFIG_YAML="${PARMgfs}/gdas/marine/marine_ecen_config.yaml.j2" echo "END: config.marineanlecen" diff --git a/dev/parm/config/gfs/config.marineanlfinal b/dev/parm/config/gfs/config.marineanlfinal index 63c6d4762a1..ffc5bdaea55 100644 --- a/dev/parm/config/gfs/config.marineanlfinal +++ b/dev/parm/config/gfs/config.marineanlfinal @@ -5,10 +5,7 @@ echo "BEGIN: config.marineanlfinal" - # Get task specific resources source "${EXPDIR}/config.resources" marineanlfinal -export SAVE_YAML="${PARMgfs}/gdas/marine/marine_det_save.yaml.j2" - echo "END: config.marineanlfinal" diff --git a/dev/parm/config/gfs/config.marineanlinit b/dev/parm/config/gfs/config.marineanlinit index 181176e9551..22a3b2804da 100644 --- a/dev/parm/config/gfs/config.marineanlinit +++ b/dev/parm/config/gfs/config.marineanlinit @@ -9,6 +9,4 @@ echo "BEGIN: config.marineanlinit" # Get task specific resources source "${EXPDIR}/config.resources" marineanlinit -export STAGE_YAML="${PARMgfs}/gdas/marine/marine_det_stage.yaml.j2" - echo "END: config.marineanlinit" diff --git a/dev/parm/config/gfs/config.marineanlletkf b/dev/parm/config/gfs/config.marineanlletkf deleted file mode 100644 index 6fc218aafe1..00000000000 --- a/dev/parm/config/gfs/config.marineanlletkf +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/bash - -########## config.marineanlletkf ########## -# Ocn Analysis specific - -echo "BEGIN: config.marineanlletkf" - -# Get task specific resources -source "${EXPDIR}/config.resources" marineanlletkf - -export LETKF_EXEC="${EXECgfs}/gdas.x" -export LETKF_YAML="${PARMgfs}/gdas/marine/letkf/letkf.yaml.j2" - -export STAGE_YAML="${PARMgfs}/gdas/marine/marine_ens_stage.yaml.j2" -export SAVE_YAML="${PARMgfs}/gdas/marine/marine_ens_save.yaml.j2" - -export GRIDGEN_EXEC="${EXECgfs}/gdas_soca_gridgen.x" -export GRIDGEN_YAML="${HOMEgfs}/sorc/gdas.cd/parm/jcb-gdas/algorithm/marine/soca_gridgen.yaml.j2" -export DIST_HALO_SIZE=3500000 - -echo "END: config.marineanlletkf" diff --git a/dev/parm/config/gfs/config.marineanlletkf.j2 b/dev/parm/config/gfs/config.marineanlletkf.j2 new file mode 100644 index 00000000000..19e238e51e1 --- /dev/null +++ b/dev/parm/config/gfs/config.marineanlletkf.j2 @@ -0,0 +1,17 @@ +#!/bin/bash + +########## config.marineanlletkf ########## +# Ocn Analysis specific + +echo "BEGIN: config.marineanlletkf" + +# Get task specific resources +source "${EXPDIR}/config.resources" marineanlletkf + +export INPUT_FIX_DIR="{{ SOCA_INPUT_FIX_DIR }}" +export ANL_GEOM="{{ SOCA_ANL_GEOM }}" + +export OBS_LIST_YAML="{{ SOCA_OBS_LIST }}" +export TASK_CONFIG_YAML="${PARMgfs}/gdas/marine/marine_ens_config.yaml.j2" + +echo "END: config.marineanlletkf" diff --git a/dev/parm/config/gfs/config.marineanlvar b/dev/parm/config/gfs/config.marineanlvar index 7342396dc70..b9b6b7f1b90 100644 --- a/dev/parm/config/gfs/config.marineanlvar +++ b/dev/parm/config/gfs/config.marineanlvar @@ -8,7 +8,4 @@ echo "BEGIN: config.marineanlvar" # Get task specific resources source "${EXPDIR}/config.resources" marineanlvar -export JEDI_CONFIG_YAML="${PARMgfs}/gdas/marine/marine_det_jedi_config.yaml.j2" -export JCB_ALGO_YAML_VAR="{{ JCB_ALGO_YAML_VAR }}" - echo "END: config.marineanlvar" diff --git a/dev/parm/config/gfs/config.marinebmat b/dev/parm/config/gfs/config.marinebmat.j2 similarity index 50% rename from dev/parm/config/gfs/config.marinebmat rename to dev/parm/config/gfs/config.marinebmat.j2 index 6ab92953cce..ea342c0764d 100644 --- a/dev/parm/config/gfs/config.marinebmat +++ b/dev/parm/config/gfs/config.marinebmat.j2 @@ -8,8 +8,9 @@ echo "BEGIN: config.marinebmat" # Get task specific resources source "${EXPDIR}/config.resources" marinebmat -export JEDI_CONFIG_YAML="${PARMgfs}/gdas/marine/marine_bmat_jedi_config.yaml.j2" -export SAVE_YAML="${PARMgfs}/gdas/marine/marine_bmat_save.yaml.j2" -export COPY_BMAT_BKGERR_YAML="${PARMgfs}/gdas/marine/marine_bmat_copy_bkgerr.yaml.j2" +export INPUT_FIX_DIR="{{ SOCA_INPUT_FIX_DIR }}" +export ANL_GEOM="{{ SOCA_ANL_GEOM }}" + +export TASK_CONFIG_YAML="${PARMgfs}/gdas/marine/marine_bmat_config.yaml.j2" echo "END: config.marinebmat" diff --git a/dev/parm/config/gfs/config.marinebmatinit b/dev/parm/config/gfs/config.marinebmatinit index e746f1a6498..e42d10c7587 100644 --- a/dev/parm/config/gfs/config.marinebmatinit +++ b/dev/parm/config/gfs/config.marinebmatinit @@ -8,6 +8,4 @@ echo "BEGIN: config.marinebmatinit" # Get task specific resources source "${EXPDIR}/config.resources" marinebmatinit -export JEDI_CONFIG_YAML="${PARMgfs}/gdas/marine/marine_bmat_jedi_config.yaml.j2" - echo "END: config.marinebmatinit" diff --git a/dev/parm/config/gfs/config.prepoceanobs.j2 b/dev/parm/config/gfs/config.prepoceanobs.j2 index 4e3c34c5355..59ac7dbe8ae 100644 --- a/dev/parm/config/gfs/config.prepoceanobs.j2 +++ b/dev/parm/config/gfs/config.prepoceanobs.j2 @@ -12,6 +12,8 @@ fi export DMPDIR="${dmpdir_exp:-${DMPDIR}}" +export MARINE_JCB_GDAS_OBS="${PARMgfs}/gdas/jcb-gdas/observations/marine" + # Get task specific resources . "${EXPDIR}/config.resources" prepoceanobs echo "END: config.prepoceanobs" diff --git a/dev/parm/config/gfs/yaml/defaults.yaml b/dev/parm/config/gfs/yaml/defaults.yaml index d7e3c094680..15ed548d145 100644 --- a/dev/parm/config/gfs/yaml/defaults.yaml +++ b/dev/parm/config/gfs/yaml/defaults.yaml @@ -57,12 +57,24 @@ snowanl: IO_LAYOUT_X: 1 IO_LAYOUT_Y: 1 +marinebmat: + SOCA_INPUT_FIX_DIR: "${FIXgfs}/gdas/soca/72x35x25/soca" + SOCA_ANL_GEOM: "${FIXgfs}/gdas/soca/72x35x25/soca" + marineanl: SOCA_INPUT_FIX_DIR: "${FIXgfs}/gdas/soca/72x35x25/soca" SOCA_ANL_GEOM: "${FIXgfs}/gdas/soca/72x35x25/soca" SOCA_OBS_LIST: "${PARMgfs}/gdas/marine/obs/obs_list.yaml.j2" # TODO: This is also repeated in oceanprepobs SOCA_NINNER: 100 - JCB_ALGO_YAML_VAR: "${PARMgfs}/gdas/marine/jcb-prototype_3dfgat.yaml.j2" + +marineanlecen: + SOCA_INPUT_FIX_DIR: "${FIXgfs}/gdas/soca/72x35x25/soca" + SOCA_ANL_GEOM: "${FIXgfs}/gdas/soca/72x35x25/soca" + +marineanlletkf: + SOCA_INPUT_FIX_DIR: "${FIXgfs}/gdas/soca/72x35x25/soca" + SOCA_ANL_GEOM: "${FIXgfs}/gdas/soca/72x35x25/soca" + SOCA_OBS_LIST: "${PARMgfs}/gdas/marine/obs/obs_list.yaml.j2" prepoceanobs: use_exp_obs: "YES" diff --git a/dev/workflow/rocoto/gfs_tasks.py b/dev/workflow/rocoto/gfs_tasks.py index e7034e396ac..e5c95f8510e 100644 --- a/dev/workflow/rocoto/gfs_tasks.py +++ b/dev/workflow/rocoto/gfs_tasks.py @@ -3102,6 +3102,9 @@ def earc_tars(self): if not self.options['do_jediatmvar']: dep_dict = {'type': 'task', 'name': f'{self.run}_echgres'} deps.append(rocoto.add_dependency(dep_dict)) + if self._base.get('DOLETKF_OCN', True): + dep_dict = {'type': 'task', 'name': f'{self.run}_marineanlletkf'} + deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep_condition='and', dep=deps) else: # early cycle enkf run (enkfgfs) dep_dict = {'type': 'task', 'name': f'{self.run}_esfc'} @@ -3113,6 +3116,9 @@ def earc_tars(self): deps.append(rocoto.add_dependency(dep_dict)) dep_dict = {'type': 'task', 'name': f'gfs_marineanlfinal'} deps.append(rocoto.add_dependency(dep_dict)) + if self._base.get('DOLETKF_OCN', True): + dep_dict = {'type': 'task', 'name': f'enkfgfs_marineanlletkf'} + deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep_condition='and', dep=deps) earcenvars = self.envars.copy() diff --git a/jobs/JGLOBAL_MARINE_ANALYSIS_ECEN b/jobs/JGLOBAL_MARINE_ANALYSIS_ECEN index b67ea8474d1..e85cd6666c8 100755 --- a/jobs/JGLOBAL_MARINE_ANALYSIS_ECEN +++ b/jobs/JGLOBAL_MARINE_ANALYSIS_ECEN @@ -4,7 +4,7 @@ export DATAjob="${DATAROOT/enkf/}/marineanalysis.${PDY:-}${cyc}" export DATA="${DATAjob}/marineanlecen" export DATAens="${DATAjob}/ensdata" -source "${HOMEgfs}/ush/jjob_header.sh" -e "marineanlecen" -c "base marineanl marineanlecen" +source "${HOMEgfs}/ush/jjob_header.sh" -e "marineanlecen" -c "base marineanlecen" ############################################## # Set variables used in the script diff --git a/jobs/JGLOBAL_MARINE_ANALYSIS_LETKF b/jobs/JGLOBAL_MARINE_ANALYSIS_LETKF index 0539b8dc355..2b6552b78b6 100755 --- a/jobs/JGLOBAL_MARINE_ANALYSIS_LETKF +++ b/jobs/JGLOBAL_MARINE_ANALYSIS_LETKF @@ -6,7 +6,7 @@ export DATA="${DATAjob}/marineanlletkf" export DATAens="${DATAjob}/ensdata" if [[ ! -d "${DATAens}" ]]; then mkdir -p "${DATAens}"; fi -source "${HOMEgfs}/ush/jjob_header.sh" -e "marineanlletkf" -c "base marineanl marineanlletkf" +source "${HOMEgfs}/ush/jjob_header.sh" -e "marineanlletkf" -c "base marineanlletkf" ############################################## # Set variables used in the script @@ -43,6 +43,8 @@ YMD=${PDY} HH=${cyc} declare_from_tmpl -rx \ YMD=${PDY} HH=${cyc} MEMDIR="ensstat" declare_from_tmpl -rx \ COMOUT_CONF:COM_CONF_TMPL +if [[ ! -d "${COMOUT_OCEAN_LETKF}" ]]; then mkdir -p "${COMOUT_OCEAN_LETKF}"; fi +if [[ ! -d "${COMOUT_ICE_LETKF}" ]]; then mkdir -p "${COMOUT_ICE_LETKF}"; fi if [[ ! -d ${COMOUT_CONF} ]]; then mkdir -p "${COMOUT_CONF}"; fi ############################################## # Begin JOB SPECIFIC work diff --git a/jobs/JGLOBAL_MARINE_BMAT b/jobs/JGLOBAL_MARINE_BMAT index c5379030f61..e4cb3ef9bb3 100755 --- a/jobs/JGLOBAL_MARINE_BMAT +++ b/jobs/JGLOBAL_MARINE_BMAT @@ -12,7 +12,7 @@ if [[ ! -d "${DATAstaticb}" ]]; then mkdir -p "${DATAstaticb}"; fi # source config.base, config.ocnanal and config.marinebmat # and pass marinebmat to ${machine}.env -source "${HOMEgfs}/ush/jjob_header.sh" -e "marinebmat" -c "base marineanl marinebmat" +source "${HOMEgfs}/ush/jjob_header.sh" -e "marinebmat" -c "base marinebmat" ############################################## # Set variables used in the script diff --git a/jobs/JGLOBAL_MARINE_BMAT_INITIALIZE b/jobs/JGLOBAL_MARINE_BMAT_INITIALIZE index 470c5b253dc..ed6e74599dc 100755 --- a/jobs/JGLOBAL_MARINE_BMAT_INITIALIZE +++ b/jobs/JGLOBAL_MARINE_BMAT_INITIALIZE @@ -11,7 +11,7 @@ if [[ ! -d "${DATAstaticb}" ]]; then mkdir -p "${DATAstaticb}"; fi # source config.base, config.ocnanal and config.marinebmatinit # and pass marinebmat to ${machine}.env -source "${HOMEgfs}/ush/jjob_header.sh" -e "marinebmatinit" -c "base marineanl marinebmatinit" +source "${HOMEgfs}/ush/jjob_header.sh" -e "marinebmatinit" -c "base marinebmat marinebmatinit" ############################################## # Set variables used in the script diff --git a/scripts/exglobal_marine_analysis_letkf.py b/scripts/exglobal_marine_analysis_letkf.py index 37ca837889a..fad37d85904 100755 --- a/scripts/exglobal_marine_analysis_letkf.py +++ b/scripts/exglobal_marine_analysis_letkf.py @@ -20,5 +20,5 @@ # Instantiate the marine letkf task MarineLetkf = MarineLETKF(config) MarineLetkf.initialize() - MarineLetkf.run() + MarineLetkf.execute() MarineLetkf.finalize() diff --git a/sorc/gdas.cd b/sorc/gdas.cd index 37522a915df..a4893c1453a 160000 --- a/sorc/gdas.cd +++ b/sorc/gdas.cd @@ -1 +1 @@ -Subproject commit 37522a915df82b439eb22458f6403dfa6021c29a +Subproject commit a4893c1453ac8d587e8e5b5a452a1c035b22812a diff --git a/ush/python/pygfs/task/analysis.py b/ush/python/pygfs/task/analysis.py index 1998e5a6c04..2c9a98dc9d4 100644 --- a/ush/python/pygfs/task/analysis.py +++ b/ush/python/pygfs/task/analysis.py @@ -39,6 +39,7 @@ def __init__(self, config: Dict[str, Any]): # Get assimilation window times _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) _next_cycle = add_to_datetime(self.task_config.current_cycle, to_timedelta(f"{self.task_config.assim_freq}H")) # Get specific assimilation times within the assimulation window @@ -68,6 +69,8 @@ def __init__(self, config: Dict[str, Any]): self.task_config.update(AttrDict( { 'WINDOW_BEGIN': _window_begin, + 'WINDOW_MIDDLE': self.task_config.current_cycle, + 'WINDOW_END': _window_end, 'WINDOW_LENGTH': f"PT{self.task_config.assim_freq}H", 'next_cycle': _next_cycle, 'OPREFIX': f"{self.task_config.RUN.replace('enkf','')}.t{self.task_config.cyc:02d}z.", diff --git a/ush/python/pygfs/task/marine_analysis.py b/ush/python/pygfs/task/marine_analysis.py index b374c5f0ed5..872be06d2ab 100644 --- a/ush/python/pygfs/task/marine_analysis.py +++ b/ush/python/pygfs/task/marine_analysis.py @@ -5,16 +5,16 @@ import os import pygfs.utils.marine_da_utils as mdau from pygfs.jedi import Jedi - -from wxflow import (AttrDict, FileHandler, Task, - add_to_datetime, to_isotime, to_timedelta, to_YMD, +from pygfs.task.analysis import Analysis +from wxflow import (AttrDict, FileHandler, + to_timedelta, to_fv3time, parse_j2yaml, logit) logger = getLogger(__name__.split('.')[-1]) -class MarineAnalysis(Task): +class MarineAnalysis(Analysis): """ Class for global marine analysis tasks """ @@ -25,7 +25,8 @@ def __init__(self, config): This method will construct a marine analysis task This includes: - extending the task_config attribute AttrDict to include parameters required for this task - - instantiate the dictionary of Jedi objects for each JEDI application + - loading the task configuration YAML + - instantiating the dictionary of Jedi objects for each JEDI application Parameters ---------- @@ -38,9 +39,6 @@ def __init__(self, config): """ super().__init__(config) - _calc_scale_exec = os.path.join(self.task_config.HOMEgfs, 'ush', 'python', '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.DATAens if self.task_config.NMEM_ENS > 0: @@ -54,50 +52,47 @@ def __init__(self, config): else: _berror_model = 'marine_background_error_static_diffusion' + # Get restart date + if self.task_config.DOIAU: + _rst_date = to_fv3time(self.task_config.WINDOW_BEGIN) + _cice_rst_date = to_fv3time(self.task_config.WINDOW_BEGIN) + else: + _rst_date = to_fv3time(self.task_config.current_cycle) + _cice_rst_date = to_fv3time(self.task_config.current_cycle) + # Create a local dictionary that is repeatedly used across this class - local_dict = AttrDict( + self.task_config.update(AttrDict( { 'PARMmarine': os.path.join(self.task_config.PARMgfs, 'gdas', 'marine'), - 'MARINE_WINDOW_BEGIN': _window_begin, - 'MARINE_WINDOW_END': _window_end, - 'MARINE_WINDOW_MIDDLE': self.task_config.current_cycle, - 'MARINE_WINDOW_LENGTH': f"PT{self.task_config['assim_freq']}H", - 'MARINE_WINDOW_BEGIN_ISO': to_isotime(_window_begin), - 'MARINE_WINDOW_MIDDLE_ISO': to_isotime(self.task_config.current_cycle), 'ENSPERT_RELPATH': _enspert_relpath, - 'CALC_SCALE_EXEC': _calc_scale_exec, - '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.", 'berror_model': _berror_model, + 'rst_date': _rst_date, + 'cice_rst_date': _cice_rst_date, 'MOM6_LEVS': mdau.get_mom6_levels(str(self.task_config.OCNRES).zfill(3)), - 'app_path_observations': self.task_config.MARINE_JCB_GDAS_OBS, + 'DOMAIN_STACK_SIZE': 116640000, # TODO: Make the stack size resolution dependent 'marine_pseudo_model_states': mdau.gen_bkg_list(bkg_path='./bkg', - window_begin=_window_begin) + window_begin=self.task_config.WINDOW_BEGIN) } - ) + )) - # Extend task_config with local_dict - self.task_config.update(local_dict) + # Extend task_config with content of config yaml for this task + self.task_config.update(parse_j2yaml(self.task_config.TASK_CONFIG_YAML, self.task_config)) # Construct dictionary of JEDI objects, one for each JEDI application need for the analysis expected_keys = ['var', 'soca_incpostproc', 'soca_diag_stats'] - jedi_config_dict = parse_j2yaml(self.task_config.JEDI_CONFIG_YAML_DET, self.task_config) - self.jedi_dict = Jedi.get_jedi_dict(jedi_config_dict, self.task_config, expected_keys) + self.jedi_dict = Jedi.get_jedi_dict(self.task_config.jedi_config, self.task_config, expected_keys) @logit(logger) - def initialize(self: Task) -> None: + def initialize(self) -> None: """Initialize the marine analysis task This method will initialize the marine analysis. This includes: - - staging SOCA fix files + - staging input files from COM and create output directories - preparing the namelists for deterministic MOM6 and analysis geometry - - staging observations - - staging input YAMLs for SOCA utilities - - staging the deterministic backgrounds (middle of window) - - staging files and link directories from B-matrix job needed for deterministic analysis - - generating list of model pseudo states + - asserting that dates of the history files are correct - initializing all the JEDI applications required for the marine analysis + - initialize obs stats application Parameters ---------- @@ -108,10 +103,9 @@ def initialize(self: Task) -> None: None """ - # stage fix files - logger.info(f"Staging SOCA fix files from {self.task_config.INPUT_FIX_DIR}") - soca_fix_list = parse_j2yaml(self.task_config.STAGE_FIX_YAML, self.task_config) - FileHandler(soca_fix_list).sync() + # stage files from COM + logger.info(f"Staging files from COM and creating input/output directories") + FileHandler(self.task_config.data_in).sync() # prepare the deterministic MOM6 input.nml logger.info(f"Preparing deterministic MOM6 input namelist") @@ -122,38 +116,15 @@ def initialize(self: Task) -> None: mdau.prep_input_nml(self.task_config, output_nml="./anl_geom/mom_input.nml", simple_geom=True, mom_input="./anl_geom/MOM_input") - # fetch observations from COMROOT - # TODO(G.V. or A.E.): Keep a copy of the obs in the scratch fs after the obs prep job - logger.info(f"Staging observations from {self.task_config.COMIN_OBS}") - obs_list = self.jedi_dict['var'].render_jcb(self.task_config, 'soca_obs_staging') - FileHandler(obs_list).sync() - - # stage the soca utility yamls (gridgen, fields and ufo mapping yamls) - logger.info(f"Staging SOCA utility yaml files from {self.task_config.PARMmarine}") - soca_utility_list = parse_j2yaml(self.task_config.STAGE_UTILITIES_YAML, self.task_config) - FileHandler(soca_utility_list).sync() - - # stage the ocean and ice backgrounds for FGAT - logger.info(f"Staging files needed for deterministic analysis from COM") - bkg_list = parse_j2yaml(self.task_config.STAGE_DET_BKG_YAML, self.task_config) - FileHandler(bkg_list).sync() - - # stage files and link directories from B-matrix job needed for deterministic analysis - logger.info(f"Staging files needed for deterministic analysis from COM") - stage_dict = parse_j2yaml(self.task_config.STAGE_YAML, self.task_config) - FileHandler(stage_dict).sync() - # assert that dates of the history files are correct - mdau.test_hist_date('./INPUT/MOM.res.nc', self.task_config.MARINE_WINDOW_BEGIN) + mdau.test_hist_date('./INPUT/MOM.res.nc', self.task_config.WINDOW_BEGIN) for state in self.task_config.marine_pseudo_model_states: mdau.test_hist_date(state['basename'] + state['ocn_filename'], datetime.strptime(state['date'], '%Y-%m-%dT%H:%M:%SZ')) # initialize JEDI applications - logger.info(f"Initializing SOCA variational application") + logger.info(f"Initializing JEDI applications") self.jedi_dict['var'].initialize(self.task_config, clean_empty_obsspaces=True) - - logger.info(f"Initializing SOCA increment handler") self.jedi_dict['soca_incpostproc'].initialize(self.task_config) # This method is a bit of a hack that will be removed in the future when the anlstat @@ -180,7 +151,7 @@ def execute(self, jedi_dict_key: str) -> None: self.jedi_dict[jedi_dict_key].execute() @logit(logger) - def finalize(self: Task) -> None: + def finalize(self) -> None: """Finalize a global marine analysis This method will finalize a global marine analysis. @@ -197,10 +168,9 @@ def finalize(self: Task) -> None: None """ - # Save output files to COM - logger.info(f"Copy files to ROTDIR") - save_dict = parse_j2yaml(self.task_config.SAVE_YAML, self.task_config) - FileHandler(save_dict).sync() + # Save files from COM + logger.info(f"Saving files to COM") + FileHandler(self.task_config.data_out).sync() # Save obs diag statistics to COM (success is optional) logger.info(f"Copy observation statistics from {self.task_config.DATA} to {self.task_config.COMOUT_OCEAN_ANALYSIS}") diff --git a/ush/python/pygfs/task/marine_bmat.py b/ush/python/pygfs/task/marine_bmat.py index e2c2739f893..297d5e608a4 100644 --- a/ush/python/pygfs/task/marine_bmat.py +++ b/ush/python/pygfs/task/marine_bmat.py @@ -4,8 +4,8 @@ import glob from logging import getLogger import pygfs.utils.marine_da_utils as mdau - -from wxflow import (AttrDict, FileHandler, Executable, Task, +from pygfs.task.analysis import Analysis +from wxflow import (AttrDict, FileHandler, Executable, add_to_datetime, to_timedelta, to_isotime, chdir, parse_j2yaml, save_as_yaml, @@ -16,7 +16,7 @@ logger = getLogger(__name__.split('.')[-1]) -class MarineBMat(Task): +class MarineBMat(Analysis): """ Class for global marine B-matrix tasks. """ @@ -27,7 +27,8 @@ def __init__(self, config): This method will construct the marine B-matrix task object This includes: - extending the task_config AttrDict to include parameters required for this task - - instantiate the Jedi attribute objects + - loading the task configuration YAML + - instantiating the Jedi attribute objects Parameters ---------- @@ -40,57 +41,41 @@ 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', 'python', '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 _enspert_relpath = os.path.relpath(self.task_config.DATAens, self.task_config.DATA) # Create a local dictionary that is repeatedly used across this class - local_dict = AttrDict( + self.task_config.update(AttrDict( { 'PARMmarine': os.path.join(self.task_config.PARMgfs, 'gdas', 'marine'), 'CALC_SCALE_EXEC': _calc_scale_exec, - 'MARINE_WINDOW_BEGIN': _window_begin, - 'MARINE_WINDOW_MIDDLE': self.task_config.current_cycle, - 'MARINE_WINDOW_END': _window_end, - 'MARINE_WINDOW_LENGTH': f"PT{self.task_config['assim_freq']}H", - 'MARINE_WINDOW_BEGIN_ISO': to_isotime(_window_begin), - 'MARINE_WINDOW_MIDDLE_ISO': to_isotime(self.task_config.current_cycle), - 'MARINE_WINDOW_END_ISO': to_isotime(_window_end), 'ENSPERT_RELPATH': _enspert_relpath, 'CALC_SCALE_EXEC': _calc_scale_exec, - 'APREFIX': f"{self.task_config.RUN}.t{self.task_config.cyc:02d}z.", - 'MOM6_LEVS': mdau.get_mom6_levels(str(self.task_config.OCNRES)) + 'MOM6_LEVS': mdau.get_mom6_levels(str(self.task_config.OCNRES)), + 'DOMAIN_STACK_SIZE': 116640000, # TODO: Make the stack size resolution dependent } - ) + )) - # Extend task_config with local_dict - self.task_config = AttrDict(**self.task_config, **local_dict) + # Extend task_config with content of config yaml for this task + self.task_config.update(parse_j2yaml(self.task_config.TASK_CONFIG_YAML, self.task_config)) # Create dictionary of Jedi objects expected_keys = ['gridgen', 'soca_diagb', 'soca_parameters_diffusion_vt', 'soca_setcorscales', 'soca_parameters_diffusion_hz', 'soca_ensb', 'soca_ensweights', 'soca_chgres'] - jedi_config_dict = parse_j2yaml(self.task_config.JEDI_CONFIG_YAML, self.task_config) - self.jedi_dict = Jedi.get_jedi_dict(jedi_config_dict, self.task_config, expected_keys) + self.jedi_dict = Jedi.get_jedi_dict(self.task_config.jedi_config, self.task_config, expected_keys) @logit(logger) - def initialize(self: Task) -> None: + def initialize(self) -> None: """Initialize a global B-matrix This method will initialize a global B-Matrix. This includes: - - staging the deterministic backgrounds - - staging SOCA fix files - - staging static ensemble members (optional) - - staging ensemble members (optional) + - staging input files from COM and create output directories + - initializing input namelists for MOM6 - initializing the soca_vtscales Python script - initializing the JEDI applications - - creating output directories Parameters ---------- @@ -101,10 +86,9 @@ def initialize(self: Task) -> None: None """ - # stage fix files - logger.info(f"Staging SOCA fix files from {self.task_config.INPUT_FIX_DIR}") - soca_fix_list = parse_j2yaml(self.task_config.STAGE_FIX_YAML, self.task_config) - FileHandler(soca_fix_list).sync() + # stage files from COM + logger.info(f"Staging files from COM and creating input/output directories") + FileHandler(self.task_config.data_in).sync() # prepare the deterministic MOM6 input.nml mdau.prep_input_nml(self.task_config) @@ -113,17 +97,6 @@ def initialize(self: Task) -> None: mdau.prep_input_nml(self.task_config, output_nml="./anl_geom/mom_input.nml", simple_geom=True, mom_input="./anl_geom/MOM_input") - # stage backgrounds - # TODO(G): Check ocean backgrounds dates for consistency - logger.info(f"Staging SOCA backgrounds") - bkg_list = parse_j2yaml(self.task_config.STAGE_DET_BKG_YAML, self.task_config) - FileHandler(bkg_list).sync() - - # stage the soca utility yamls (fields and ufo mapping yamls) - logger.info(f"Staging SOCA utility yaml files") - soca_utility_list = parse_j2yaml(self.task_config.STAGE_UTILITIES_YAML, self.task_config) - FileHandler(soca_utility_list).sync() - # initialize vtscales python script vtscales_config = self.jedi_dict['soca_parameters_diffusion_vt'].render_jcb(self.task_config, 'soca_vtscales') save_as_yaml(vtscales_config, os.path.join(self.task_config.DATA, 'soca_vtscales.yaml')) @@ -139,16 +112,6 @@ def initialize(self: Task) -> None: self.jedi_dict['soca_ensb'].initialize(self.task_config) self.jedi_dict['soca_ensweights'].initialize(self.task_config) - # stage ensemble members for the hybrid background error - if self.task_config.DOHYBVAR_OCN == "YES" or self.task_config.NMEM_ENS >= 2: - logger.debug(f"Stage ensemble members for the hybrid background error") - letkf_stage_list = parse_j2yaml(self.task_config.STAGE_ENS_BKG_YAML, self.task_config) - FileHandler(letkf_stage_list).sync() - - # create the symbolic link to the static B-matrix directory - FileHandler({'link': [[self.task_config.DATAstaticb, - os.path.join(self.task_config.DATA, 'staticb')]]}).sync() - @logit(logger) def execute(self) -> None: """Generate the full B-matrix @@ -194,15 +157,12 @@ def execute(self) -> None: self.jedi_dict['soca_ensweights'].execute() @logit(logger) - def finalize(self: Task) -> None: + def finalize(self) -> 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 - - ... + - saving output files and YAMLs to COM Parameters ---------- @@ -213,11 +173,6 @@ def finalize(self: Task) -> None: None """ - logger.info(f"Copying background error files to new filenames") - bkgerr_list = parse_j2yaml(self.task_config.COPY_BMAT_BKGERR_YAML, self.task_config) - FileHandler(bkgerr_list).sync() - - # Save output files to COM - logger.info(f"Copy files to ROTDIR") - save_dict = parse_j2yaml(self.task_config.SAVE_YAML, self.task_config) - FileHandler(save_dict).sync() + # save files from COM + logger.info(f"Saving files to COM") + FileHandler(self.task_config.data_out).sync() diff --git a/ush/python/pygfs/task/marine_letkf.py b/ush/python/pygfs/task/marine_letkf.py index b8ec5cdddbb..79f87fa1e72 100644 --- a/ush/python/pygfs/task/marine_letkf.py +++ b/ush/python/pygfs/task/marine_letkf.py @@ -1,18 +1,15 @@ #!/usr/bin/env python3 -import f90nml +import os import pygfs.utils.marine_da_utils as mdau from logging import getLogger -import os from pygfs.task.analysis import Analysis +from pygfs.jedi import Jedi from typing import Dict -from wxflow import (AttrDict, - Executable, - FileHandler, - logit, - parse_j2yaml, - to_timedelta, - to_YMDH) +from wxflow import (AttrDict, Executable, FileHandler, + parse_j2yaml, save_as_yaml, + to_timedelta, to_YMDH, + logit) logger = getLogger(__name__.split('.')[-1]) @@ -25,6 +22,13 @@ class MarineLETKF(Analysis): @logit(logger, name="MarineLETKF") def __init__(self, config: Dict) -> None: """Constructor for ocean and sea ice LETKF task + + This method will construct a marine analysis task + This includes: + - extending the task_config attribute AttrDict to include parameters required for this task + - loading the task configuration YAML + - instantiating the dictionary of Jedi objects for each JEDI application + Parameters: ------------ config: Dict @@ -37,140 +41,37 @@ def __init__(self, config: Dict) -> 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.LETKF_EXEC, - 'soca', - 'localensembleda', - _letkf_yaml_file] # compute the relative path from self.task_config.DATA to self.task_config.DATAenspert _enspert_relpath = os.path.relpath(self.task_config.DATAens, self.task_config.DATA) - 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') - self.task_config.ENSPERT_RELPATH = _enspert_relpath - self.task_config.PARMmarine = os.path.join(self.task_config.PARMgfs, 'gdas', 'marine') - self.task_config.app_path_observations = self.task_config.MARINE_JCB_GDAS_OBS - self.task_config.letkf_app = "true" - self.task_config.OPREFIX = f"{self.task_config.RUN.replace('enkf','')}.t{self.task_config.cyc:02d}z." + # Create a local dictionary that is repeatedly used across this class + self.task_config.update(AttrDict( + { + 'PARMmarine': os.path.join(self.task_config.PARMgfs, 'gdas', 'marine'), + 'ENSPERT_RELPATH': _enspert_relpath, + 'letkf_app': 'true', + 'DIST_HALO_SIZE': 3500000, + 'DOMAIN_STACK_SIZE': 116640000, # TODO: Make the stack size resolution dependent + } + )) + + # Extend task_config with content of config yaml for this task + self.task_config.update(parse_j2yaml(self.task_config.TASK_CONFIG_YAML, self.task_config)) + + # Construct dictionary of JEDI objects, one for each JEDI application need for the analysis + expected_keys = ['gridgen', 'letkf'] + self.jedi_dict = Jedi.get_jedi_dict(self.task_config.jedi_config, self.task_config, expected_keys) @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 - soca_fix_stage_list = parse_j2yaml(self.task_config.STAGE_FIX_YAML, self.task_config) - FileHandler(soca_fix_stage_list).sync() - stageconfig = AttrDict() - keys = ['app_path_observations', - 'cyc', - 'current_cycle', - 'letkf_app', - 'mem_offset', - 'previous_cycle', - 'COM_ICE_LETKF_TMPL', - 'COM_OCEAN_LETKF_TMPL', - 'COM_ICE_HISTORY_TMPL', - 'COM_OCEAN_HISTORY_TMPL', - 'COMIN_OCEAN_HISTORY_PREV', - 'COMIN_ICE_HISTORY_PREV', - 'COMOUT_ICE_LETKF', - 'COMOUT_OCEAN_LETKF', - 'COMOUT_CONF', - 'DATA', - 'DIST_HALO_SIZE', - 'ENSPERT_RELPATH', - 'GDUMP_ENS', - 'NMEM_ENS', - 'NMEM_ENS_MAX', - 'OPREFIX', - 'PARMgfs', - 'PDY', - 'ROTDIR', - 'RUN', - 'WINDOW_BEGIN', - 'WINDOW_MIDDLE', - 'DATAens'] - for key in keys: - stageconfig[key] = self.task_config[key] - - jcb_base_yaml = os.path.join(self.task_config.PARMmarine, 'jcb-base.yaml.j2') - jcb_base_config = parse_j2yaml(path=jcb_base_yaml, data=stageconfig) - - jcb_config = {**jcb_base_config, **stageconfig} - - # stage letkf-specific files - stage_dict = parse_j2yaml(self.task_config.STAGE_YAML, jcb_config) - FileHandler(stage_dict).sync() - - # stage ensemble background files - soca_ens_bkg_stage_list = parse_j2yaml(self.task_config.STAGE_ENS_BKG_YAML, stageconfig) - FileHandler(soca_ens_bkg_stage_list).sync() - - # "observations" is expected by later JCB code to populate it with config info, - jcb_config['observations'] = parse_j2yaml(self.task_config.OBS_LIST_YAML, jcb_config)['observations'] - - obsconfigfile = os.path.join(self.task_config['PARMgfs'], 'gdas/marine/obs/obs_list_base.yaml.j2') - jcb_config['observations'] = parse_j2yaml(obsconfigfile, jcb_config) - - # get the list of expected observation files - obs_files = [] - for observer in jcb_config['observations']['observers']: - obs_name = observer['obs space']['name'].lower() - # TODO(AFE) - this should be removed when the obs config yamls are jinjafied - if 'distribution' not in observer['obs space']: - observer['obs space']['distribution'] = {'name': 'Halo', 'halo size': self.task_config['DIST_HALO_SIZE']} - obs_filename = f"{self.task_config.OPREFIX}{obs_name}.{to_YMDH(self.task_config.current_cycle)}.nc4" - obs_files.append((obs_filename, observer)) - - 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 - # TODO (AFE) switch to fully JCB version - letkf_yaml = parse_j2yaml(self.task_config.LETKF_YAML, jcb_config) - letkf_yaml.observations.observers = obs_to_use - letkf_yaml.save(self.task_config.letkf_yaml_file) - - # TODO(AFE) get rid of this, I think - # 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 + This method will initialize the marine analysis. + This includes: + - staging input files from COM and create output directories + - preparing the namelist for MOM6 + - initializing all the JEDI applications required for marine LETKF - @logit(logger) - def run(self): - """Method run for ocean and sea ice LETKF task Parameters: ------------ None @@ -179,23 +80,43 @@ def run(self): None """ - logger.info("run") + # stage files from COM + logger.info(f"Staging files from COM and creating input/output directories") + FileHandler(self.task_config.data_in).sync() - exec_cmd_gridgen = Executable(self.task_config.APRUN_MARINEANLLETKF) - exec_cmd_gridgen.add_default_arg(self.task_config.GRIDGEN_EXEC) - exec_cmd_gridgen.add_default_arg(self.task_config.GRIDGEN_YAML) + # prepare the ensemble MOM6 input.nml + logger.info(f"Preparing ensemble MOM6 input namelist") + mdau.prep_input_nml(self.task_config) - mdau.run(exec_cmd_gridgen) + # initialize JEDI applications + logger.info(f"Initializing JEDI applications") + self.jedi_dict['gridgen'].initialize(self.task_config) + self.jedi_dict['letkf'].initialize(self.task_config, clean_empty_obsspaces=True) - exec_cmd_letkf = Executable(self.task_config.APRUN_MARINEANLLETKF) - for letkf_exec_arg in self.task_config.letkf_exec_args: - exec_cmd_letkf.add_default_arg(letkf_exec_arg) + @logit(logger) + def execute(self) -> None: + """Execute JEDI application of marine analysis - mdau.run(exec_cmd_letkf) + Parameters + ---------- + None + + Returns + ---------- + None + """ + + self.jedi_dict['gridgen'].execute() + self.jedi_dict['letkf'].execute() @logit(logger) def finalize(self): """Method finalize for ocean and sea ice LETKF task + + This method will finalize a global marine analysis. + This includes: + - Saving output files to COM + Parameters: ------------ None @@ -204,33 +125,6 @@ def finalize(self): None """ - logger.info("finalize") - - letkfsaveconf = AttrDict() - keys = ['current_cycle', 'DATA', 'NMEM_ENS', 'WINDOW_BEGIN', 'GDUMP_ENS', - 'PARMgfs', 'ROTDIR', 'COM_OCEAN_LETKF_TMPL', 'COM_ICE_LETKF_TMPL', - 'COMOUT_OCEAN_LETKF', 'COMOUT_ICE_LETKF', 'WINDOW_MIDDLE', - 'OBS_LIST_YAML', 'COMOUT_CONF', 'letkf_yaml_file'] - for key in keys: - letkfsaveconf[key] = self.task_config[key] - - # get the list of obs output file - letkf yaml is already complete - letkf_config = parse_j2yaml(self.task_config.letkf_yaml_file, AttrDict()) - obs_files = [] - for observer in letkf_config['observations']['observers']: - obs_files.append(observer['obs space']['obsdataout']['engine']['obsfile']) - obs_files_to_copy = [] - # copy files from diags to COMOUT - for obs_src in obs_files: - obs_dst = os.path.join(letkfsaveconf.COMOUT_OCEAN_LETKF, 'diags', - os.path.basename(obs_src)) - if os.path.exists(obs_src): - obs_files_to_copy.append([obs_src, obs_dst]) - FileHandler({'mkdir': [os.path.join(letkfsaveconf.COMOUT_OCEAN_LETKF, 'diags')]}).sync() - FileHandler({'copy': obs_files_to_copy}).sync() - # yaml configurations - yamls_to_copy = [] - yamls_to_copy.append([letkfsaveconf.letkf_yaml_file, os.path.join(letkfsaveconf.COMOUT_CONF, 'soca_letkf.yaml')]) - FileHandler({'copy': yamls_to_copy}).sync() - save_dict = parse_j2yaml(self.task_config.SAVE_YAML, letkfsaveconf) - FileHandler(save_dict).sync() + # Save files from COM + logger.info(f"Saving files to COM") + FileHandler(self.task_config.data_out).sync() diff --git a/ush/python/pygfs/task/marine_recenter.py b/ush/python/pygfs/task/marine_recenter.py index 87bc8e8c785..d66a47e7202 100644 --- a/ush/python/pygfs/task/marine_recenter.py +++ b/ush/python/pygfs/task/marine_recenter.py @@ -5,7 +5,8 @@ from typing import Dict import pygfs.utils.marine_da_utils as mdau from pygfs.jedi import Jedi -from wxflow import (AttrDict, FileHandler, Task, +from pygfs.task.analysis import Analysis +from wxflow import (AttrDict, FileHandler, add_to_datetime, to_timedelta, to_fv3time, to_isotime, parse_j2yaml, logit) @@ -13,7 +14,7 @@ logger = getLogger(__name__.split('.')[-1]) -class MarineRecenter(Task): +class MarineRecenter(Analysis): """ Class for global ocean analysis recentering task """ @@ -21,6 +22,13 @@ class MarineRecenter(Task): @logit(logger, name="MarineRecenter") def __init__(self, config: Dict) -> None: """Constructor for ocean recentering task + + This method will construct a marine analysis task + This includes: + - extending the task_config attribute AttrDict to include parameters required for this task + - loading the task configuration YAML + - instantiating the dictionary of Jedi objects for each JEDI application + Parameters: ------------ config: Dict @@ -32,44 +40,42 @@ def __init__(self, config: Dict) -> None: super().__init__(config) - _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) _enspert_relpath = os.path.relpath(self.task_config.DATAens, self.task_config.DATA) + if self.task_config.DOIAU: # forecast initialized at the begining of the DA window - _cice_rst_date = to_fv3time(_window_begin) + _cice_rst_date = to_fv3time(self.task_config.WINDOW_BEGIN) else: # forecast initialized at the middle of the DA window _cice_rst_date = to_fv3time(self.task_config.current_cycle) - local_dict = AttrDict( + # Create a local dictionary that is repeatedly used across this class + self.task_config.update(AttrDict( { 'PARMmarine': os.path.join(self.task_config.PARMgfs, 'gdas', 'marine'), - 'MARINE_WINDOW_BEGIN': _window_begin, - 'MARINE_WINDOW_END': _window_end, - 'MARINE_WINDOW_MIDDLE': self.task_config.current_cycle, - 'MARINE_WINDOW_BEGIN_ISO': to_isotime(_window_begin), - 'MARINE_WINDOW_END_ISO': to_isotime(_window_end), - 'MARINE_WINDOW_MIDDLE_ISO': to_isotime(self.task_config.current_cycle), - 'MARINE_WINDOW_LENGTH': f"PT{self.task_config['assim_freq']}H", 'ENSPERT_RELPATH': _enspert_relpath, - '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.", - 'cice_rst_date': _cice_rst_date + 'cice_rst_date': _cice_rst_date, + 'DOMAIN_STACK_SIZE': 116640000, # TODO: Make the stack size resolution dependent } - ) + )) - # Extend task_config with local_dict - self.task_config.update(local_dict) + # Extend task_config with content of config yaml for this task + self.task_config.update(parse_j2yaml(self.task_config.TASK_CONFIG_YAML, self.task_config)) # Construct dictionary of JEDI objects, one for each JEDI application need for the analysis expected_keys = ['gridgen', 'ens_handler'] - jedi_config_dict = parse_j2yaml(self.task_config.JEDI_CONFIG_YAML, self.task_config) - self.jedi_dict = Jedi.get_jedi_dict(jedi_config_dict, self.task_config, expected_keys) + self.jedi_dict = Jedi.get_jedi_dict(self.task_config.jedi_config, self.task_config, expected_keys) @logit(logger) def initialize(self): """Method initialize for ocean recentering task + + This method will initialize the marine analysis. + This includes: + - staging input files from COM and create output directories + - preparing the namelist for MOM6 + - initializing all the JEDI applications required for the marine recentering + Parameters: ------------ None @@ -78,33 +84,16 @@ def initialize(self): None """ - # stage fix files - logger.info(f"Staging SOCA fix files from {self.task_config.INPUT_FIX_DIR}") - soca_fix_list = parse_j2yaml(self.task_config.STAGE_FIX_YAML, self.task_config) - FileHandler(soca_fix_list).sync() + # stage files from COM + logger.info(f"Staging files from COM and creating input/output directories") + FileHandler(self.task_config.data_in).sync() # prepare the MOM6 input.nml mdau.prep_input_nml(self.task_config) - # stage the soca utility yamls (gridgen, fields and ufo mapping yamls) - logger.info(f"Staging SOCA utility yaml files from {self.task_config.PARMmarine}") - soca_utility_list = parse_j2yaml(self.task_config.STAGE_UTILITIES_YAML, self.task_config) - FileHandler(soca_utility_list).sync() - - # stage backgrounds - bkg_list = parse_j2yaml(self.task_config.STAGE_DET_BKG_YAML, self.task_config) - FileHandler(bkg_list).sync() - - # stage the ensemble members and CICE restarts - logger.info("---------------- Stage ensemble members and CICE restarts") - stage_dict = parse_j2yaml(self.task_config.STAGE_YAML, self.task_config) - FileHandler(stage_dict).sync() - # initialize JEDI applications - logger.info(f"Initializing SOCA gridgen application") + logger.info(f"Initializing JEDI applications") self.jedi_dict['gridgen'].initialize(self.task_config) - - logger.info(f"Initializing SOCA ensemble handler") self.jedi_dict['ens_handler'].initialize(self.task_config) @logit(logger) @@ -126,6 +115,11 @@ def execute(self, jedi_dict_key: str) -> None: @logit(logger) def finalize(self): """Method finalize for ocean recentering task + + This method will finalize a global marine analysis. + This includes: + - Saving output files to COM + Parameters: ------------ None @@ -134,7 +128,6 @@ def finalize(self): None """ - # Save recentered increments and ensemble statistics - logger.info("---------------- Save recentered increments and ensemble statistics") - save_dict = parse_j2yaml(self.task_config.SAVE_YAML, self.task_config) - FileHandler(save_dict).sync() + # save files from COM + logger.info(f"Saving files to COM") + FileHandler(self.task_config.data_out).sync() diff --git a/ush/python/pygfs/utils/marine_da_utils.py b/ush/python/pygfs/utils/marine_da_utils.py index 543568d3abe..e185c43d578 100644 --- a/ush/python/pygfs/utils/marine_da_utils.py +++ b/ush/python/pygfs/utils/marine_da_utils.py @@ -57,7 +57,7 @@ def prep_input_nml(task_config: AttrDict, FileHandler({'copy': [[mom_input_nml_tmpl_src, mom_input_nml_tmpl]]}).sync() # swap date and stacksize - date_init = [int(s) for s in task_config.MARINE_WINDOW_END.strftime('%Y,%m,%d,%H,%M,%S').split(',')] + date_init = [int(s) for s in task_config.WINDOW_END.strftime('%Y,%m,%d,%H,%M,%S').split(',')] input_nml_config = {'domain_stack_size': task_config.DOMAIN_STACK_SIZE, 'date_init': date_init, 'simple_geom': simple_geom,