diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000000..5068b961f7 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,213 @@ +# global-workflow is a collaborative space where contributions come from a variety of sources +# This file is to ensure that new development gets reviewed by the appropriate SME + +# global-workflow default owners (not a complete list) +@KateFriedman-NOAA +@WalterKolczynski-NOAA +@DavidHuber-NOAA + +# Specific directory owners +/ci/ @TerrenceMcGuinness-NOAA @WalterKolczynski-NOAA + +/ecf/ @lgannoaa + +/workflow/ @WalterKolczynski-NOAA @aerorahul @DavidHuber-NOAA + +# Specific file owners +# build scripts +sorc/build_*.sh @WalterKolczynski-NOAA @DavidHuber-NOAA @aerorahul @KateFriedman-NOAA +sorc/link_workflow.sh @WalterKolczynski-NOAA @DavidHuber-NOAA @aerorahul @KateFriedman-NOAA + +# jobs +jobs/JGDAS_AERO_ANALYSIS_GENERATE_BMATRIX @CoryMartin-NOAA +jobs/JGDAS_ATMOS_ANALYSIS_DIAG @RussTreadon-NOAA @CoryMartin-NOAA +jobs/JGDAS_ATMOS_CHGRES_FORENKF @RussTreadon-NOAA @CoryMartin-NOAA +jobs/JGDAS_ATMOS_GEMPAK @GwenChen-NOAA +jobs/JGDAS_ATMOS_GEMPAK_META_NCDC @GwenChen-NOAA +jobs/JGDAS_ATMOS_VERFOZN @EdwardSafford-NOAA +jobs/JGDAS_ATMOS_VERFRAD @EdwardSafford-NOAA +jobs/JGDAS_ENKF_* @RussTreadon-NOAA @CoryMartin-NOAA @CatherineThomas-NOAA +jobs/JGDAS_FIT2OBS @jack-woollen +jobs/JGDAS_GLOBAL_OCEAN_ANALYSIS_ECEN @guillaumevernieres +jobs/JGDAS_GLOBAL_OCEAN_ANALYSIS_VRFY @guillaumevernieres +jobs/JGFS_ATMOS_AWIPS_20KM_1P0DEG @GwenChen-NOAA +jobs/JGFS_ATMOS_CYCLONE_GENESIS @JiayiPeng-NOAA +jobs/JGFS_ATMOS_CYCLONE_TRACKER @JiayiPeng-NOAA +jobs/JGFS_ATMOS_FBWIND @GwenChen-NOAA +jobs/JGFS_ATMOS_FSU_GENESIS +jobs/JGFS_ATMOS_GEMPAK @GwenChen-NOAA +jobs/JGFS_ATMOS_GEMPAK_META @GwenChen-NOAA +jobs/JGFS_ATMOS_GEMPAK_NCDC_UPAPGIF @GwenChen-NOAA +jobs/JGFS_ATMOS_GEMPAK_PGRB2_SPEC @GwenChen-NOAA +jobs/JGFS_ATMOS_PGRB2_SPEC_NPOESS @WenMeng-NOAA +jobs/JGFS_ATMOS_POSTSND @BoCui-NOAA +jobs/JGFS_ATMOS_VERIFICATION +jobs/JGLOBAL_AERO_ANALYSIS_* @CoryMartin-NOAA +jobs/JGLOBAL_ARCHIVE @DavidHuber-NOAA +jobs/JGLOBAL_ATMENS_ANALYSIS_* @RussTreadon-NOAA @CoryMartin-NOAA @DavidNew-NOAA +jobs/JGLOBAL_ATMOS_ANALYSIS @RussTreadon-NOAA @CatherineThomas-NOAA +jobs/JGLOBAL_ATMOS_ANALYSIS_CALC @RussTreadon-NOAA @CatherineThomas-NOAA @CoryMartin-NOAA +jobs/JGLOBAL_ATMOS_EMCSFC_SFC_PREP @GeorgeGayno-NOAA +jobs/JGLOBAL_ATMOS_ENSSTAT +jobs/JGLOBAL_ATMOS_POST_MANAGER +jobs/JGLOBAL_ATMOS_PRODUCTS @WenMeng-NOAA +jobs/JGLOBAL_ATMOS_SFCANL @GeorgeGayno-NOAA +jobs/JGLOBAL_ATMOS_TROPCY_QC_RELOC +jobs/JGLOBAL_ATMOS_UPP @WenMeng-NOAA +jobs/JGLOBAL_ATMOS_VMINMON @EdwardSafford-NOAA +jobs/JGLOBAL_ATM_* @RussTreadon-NOAA @DavidNew-NOAA @CoryMartin-NOAA +jobs/JGLOBAL_CLEANUP @WalterKolczynski-NOAA @DavidHuber-NOAA @KateFriedman-NOAA +jobs/JGLOBAL_EXTRACTVARS @EricSinsky-NOAA +jobs/JGLOBAL_FORECAST @aerorahul +jobs/JGLOBAL_MARINE_* @guillaumevernieres @AndrewEichmann-NOAA +jobs/JGLOBAL_OCEANICE_PRODUCTS @GwenChen-NOAA +jobs/JGLOBAL_PREP_EMISSIONS @bbakernoaa +jobs/JGLOBAL_PREP_OBS_AERO @CoryMartin-NOAA +jobs/JGLOBAL_PREP_OCEAN_OBS @guillaumevernieres @AndrewEichmann-NOAA +jobs/JGLOBAL_*SNOW* @jiaruidong2017 +jobs/JGLOBAL_STAGE_IC @KateFriedman-NOAA +jobs/JGLOBAL_WAVE_* @JessicaMeixner-NOAA @sbanihash +jobs/rocoto/* @WalterKolczynski-NOAA @KateFriedman-NOAA @DavidHuber-NOAA + +# scripts +scripts/exgdas_aero_analysis_generate_bmatrix.py @CoryMartin-NOAA +scripts/exgdas_atmos_chgres_forenkf.sh @RussTreadon-NOAA @CoryMartin-NOAA +scripts/exgdas_atmos_gempak_gif_ncdc.sh @GwenChen-NOAA +scripts/exgdas_atmos_nawips.sh @GwenChen-NOAA +scripts/exgdas_atmos_verfozn.sh @EdwardSafford-NOAA +scripts/exgdas_atmos_verfrad.sh @EdwardSafford-NOAA +scripts/exgdas_enkf_earc.py @DavidHuber-NOAA +scripts/exgdas_enkf_ecen.sh @CoryMartin-NOAA @RussTreadon-NOAA @CatherineThomas-NOAA +scripts/exgdas_enkf_post.sh @CoryMartin-NOAA @RussTreadon-NOAA @CatherineThomas-NOAA +scripts/exgdas_enkf_select_obs.sh @CoryMartin-NOAA @RussTreadon-NOAA @CatherineThomas-NOAA +scripts/exgdas_enkf_sfc.sh @CoryMartin-NOAA @RussTreadon-NOAA @CatherineThomas-NOAA +scripts/exgdas_enkf_snow_recenter.py @jiaruidong2017 +scripts/exgdas_enkf_update.sh @CoryMartin-NOAA @RussTreadon-NOAA @CatherineThomas-NOAA +scripts/exgdas_global_marine_analysis_letkf.py @guillaumevernieres @AndrewEichmann-NOAA +scripts/exgfs_aero_init_aerosol.py @WalterKolczynski-NOAA +scripts/exgfs_atmos_awips_20km_1p0deg.sh @GwenChen-NOAA +scripts/exgfs_atmos_fbwind.sh @GwenChen-NOAA +scripts/exgfs_atmos_gempak_gif_ncdc_skew_t.sh @GwenChen-NOAA +scripts/exgfs_atmos_gempak_meta.sh @GwenChen-NOAA +scripts/exgfs_atmos_goes_nawips.sh @GwenChen-NOAA +scripts/exgfs_atmos_grib2_special_npoess.sh @WenMeng-NOAA +scripts/exgfs_atmos_nawips.sh @GwenChen-NOAA +scripts/exgfs_atmos_postsnd.sh @BoCui-NOAA +scripts/exgfs_pmgr.sh +scripts/exgfs_prdgen_manager.sh +scripts/exgfs_wave_* @JessicaMeixner-NOAA @sbanihash +scripts/exglobal_aero_analysis_* @CoryMartin-NOAA +scripts/exglobal_archive.py @DavidHuber-NOAA +scripts/exglobal_atm_analysis_* @RussTreadon-NOAA @DavidNew-NOAA +scripts/exglobal_atmens_analysis_* @RussTreadon-NOAA @DavidNew-NOAA +scripts/exglobal_atmos_analysis*.sh @RussTreadon-NOAA @CoryMartin-NOAA +scripts/exglobal_atmos_ensstat.sh @RussTreadon-NOAA +scripts/exglobal_atmos_pmgr.sh +scripts/exglobal_atmos_products.sh @WenMeng-NOAA +scripts/exglobal_atmos_sfcanl.sh @GeorgeGayno-NOAA +scripts/exglobal_atmos_tropcy_qc_reloc.sh +scripts/exglobal_atmos_upp.py @WenMeng-NOAA +scripts/exglobal_atmos_vminmon.sh @EdwardSafford-NOAA +scripts/exglobal_cleanup.sh @DavidHuber-NOAA +scripts/exglobal_diag.sh @RussTreadon-NOAA @CoryMartin-NOAA +scripts/exglobal_extractvars.sh @EricSinsky-NOAA +scripts/exglobal_forecast.py @aerorahul +scripts/exglobal_forecast.sh @aerorahul @WalterKolczynski-NOAA +scripts/exglobal_marine_analysis_* @guillaumevernieres @AndrewEichmann-NOAA +scripts/exglobal_marinebmat.py @guillaumevernieres @AndrewEichmann-NOAA +scripts/exglobal_oceanice_products.py @GwenChen-NOAA +scripts/exglobal_prep_emissions.py @bbakernoaa +scripts/exglobal_prep_obs_aero.py @CoryMartin-NOAA +scripts/exglobal_prep_snow_obs.py @jiaruidong2017 +scripts/exglobal_snow_analysis.py @jiaruidong2017 +scripts/exglobal_stage_ic.py @KateFriedman-NOAA + +# ush +WAM_XML_to_ASCII.pl +atmos_ensstat.sh +atmos_extractvars.sh @EricSinsky-NOAA +bash_utils.sh @WalterKolczynski-NOAA +calcanl_gfs.py @CoryMartin-NOAA +calcinc_gfs.py @CoryMartin-NOAA +compare_f90nml.py @WalterKolczynski-NOAA @aerorahul +detect_machine.sh @WalterKolczynski-NOAA +extractvars_tools.sh @EricSinsky-NOAA +file_utils.sh @WalterKolczynski-NOAA +forecast_det.sh @aerorahul @WalterKolczynski-NOAA +forecast_postdet.sh @aerorahul @WalterKolczynski-NOAA +forecast_predet.sh @aerorahul @WalterKolczynski-NOAA +fv3gfs_remap_weights.sh +gaussian_sfcanl.sh @GeorgeGayno-NOAA +getdump.sh @WalterKolczynski-NOAA @KateFriedman-NOAA +getges.sh @WalterKolczynski-NOAA @KateFriedman-NOAA +getgfsnctime @CoryMartin-NOAA +getncdimlen @CoryMartin-NOAA +gfs_bfr2gpk.sh @GwenChen-NOAA +gfs_bufr.sh @GwenChen-NOAA +gfs_bufr_netcdf.sh @GwenChen-NOAA +gfs_sndp.sh @BoCui-NOAA +gfs_truncate_enkf.sh @CoryMartin-NOAA +global_savefits.sh +gsi_utils.py @CoryMartin-NOAA +interp_atmos_master.sh @aerorahul @WenMeng-NOAA @WalterKolczynski-NOAA +interp_atmos_sflux.sh @aerorahul @WenMeng-NOAA @WalterKolczynski-NOAA +jjob_header.sh @WalterKolczynski-NOAA +link_crtm_fix.sh @WalterKolczynski-NOAA +load_fv3gfs_modules.sh @WalterKolczynski-NOAA @aerorahul +load_ufsda_modules.sh @WalterKolczynski-NOAA @aerorahul @CoryMartin-NOAA +load_ufswm_modules.sh @WalterKolczynski-NOAA @aerorahul @JessicaMeixner-NOAA +merge_fv3_aerosol_tile.py @WalterKolczynski-NOAA +minmon_xtrct_*.pl @EdwardSafford-NOAA +module-setup.sh @WalterKolczynski-NOAA @aerorahul +oceanice_nc2grib2.sh @GwenChen-NOAA +ocnice_extractvars.sh @EricSinsky-NOAA +ozn_xtrct.sh @EdwardSafford-NOAA +parse-storm-type.pl +parsing_model_configure_FV3.sh @WalterKolczynski-NOAA @aerorahul @junwang-noaa +parsing_namelists_CICE.sh @WalterKolczynski-NOAA @aerorahul @junwang-noaa @DeniseWorthen +parsing_namelists_FV3.sh @WalterKolczynski-NOAA @aerorahul @junwang-noaa +parsing_namelists_FV3_nest.sh @guoqing-noaa +parsing_namelists_MOM6.sh @WalterKolczynski-NOAA @aerorahul @junwang-noaa @jiandewang +parsing_namelists_WW3.sh @WalterKolczynski-NOAA @aerorahul @JessicaMeixner-NOAA @sbanihash +parsing_ufs_configure.sh @WalterKolczynski-NOAA @aerorahul @junwang-noaa +preamble.sh @WalterKolczynski-NOAA +product_functions.sh @WalterKolczynski-NOAA @aerorahul +radmon_*.sh @EdwardSafford-NOAA +rstprod.sh @WalterKolczynski-NOAA @DavidHuber-NOAA +run_mpmd.sh @WalterKolczynski-NOAA @aerorahul @DavidHuber-NOAA +syndat_getjtbul.sh @JiayiPeng-NOAA +syndat_qctropcy.sh @JiayiPeng-NOAA +tropcy_relocate.sh @JiayiPeng-NOAA +tropcy_relocate_extrkr.sh @JiayiPeng-NOAA +wave_*.sh @JessicaMeixner-NOAA @sbanihash + +# ush/python +ush/python/pygfs/jedi/__init__.py @aerorahul @DavidNew-NOAA +ush/python/pygfs/jedi/jedi.py @DavidNew-NOAA +ush/python/pygfs/task/__init__.py @aerorahul +ush/python/pygfs/task/aero_analysis.py @DavidNew-NOAA @CoryMartin-NOAA +ush/python/pygfs/task/aero_bmatrix.py @DavidNew-NOAA @CoryMartin-NOAA +ush/python/pygfs/task/aero_emissions.py @bbakernoaa +ush/python/pygfs/task/aero_prepobs.py @CoryMartin-NOAA +ush/python/pygfs/task/analysis.py @DavidNew-NOAA @RussTreadon-NOAA +ush/python/pygfs/task/archive.py @DavidHuber-NOAA +ush/python/pygfs/task/atm_analysis.py @DavidNew-NOAA @RussTreadon-NOAA +ush/python/pygfs/task/atmens_analysis.py @DavidNew-NOAA @RussTreadon-NOAA +ush/python/pygfs/task/bmatrix.py @DavidNew-NOAA +ush/python/pygfs/task/gfs_forecast.py @aerorahul +ush/python/pygfs/task/marine_analysis.py @guillaumevernieres @AndrewEichmann-NOAA +ush/python/pygfs/task/marine_bmat.py @guillaumevernieres @AndrewEichmann-NOAA +ush/python/pygfs/task/marine_letkf.py @guillaumevernieres @AndrewEichmann-NOAA +ush/python/pygfs/task/oceanice_products.py @aerorahul @GwenChen-NOAA +ush/python/pygfs/task/snow_analysis.py @jiaruidong2017 +ush/python/pygfs/task/snowens_analysis.py @jiaruidong2017 +ush/python/pygfs/task/stage_ic.py @KateFriedman-NOAA +ush/python/pygfs/task/upp.py @aerorahul @WenMeng-NOAA +ush/python/pygfs/ufswm/__init__.py @aerorahul +ush/python/pygfs/ufswm/gfs.py @aerorahul +ush/python/pygfs/ufswm/ufs.py @aerorahul +ush/python/pygfs/utils/__init__.py @aerorahul +ush/python/pygfs/utils/marine_da_utils.py @guillaumevernieres @AndrewEichmann-NOAA + +# Specific workflow scripts +workflow/generate_workflows.sh @DavidHuber-NOAA diff --git a/.github/ISSUE_TEMPLATE/dump_request.yml b/.github/ISSUE_TEMPLATE/dump_request.yml new file mode 100644 index 0000000000..4481e7f6e0 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/dump_request.yml @@ -0,0 +1,37 @@ +name: Global Observation Dump Request +description: Request additional dates be added to a machine's global dump archive (GDA) or introduce experimental dump data to the GDA +labels: ["Static Data Mgmt"] +assignees: + - KateFriedman-NOAA + - WalterKolczynski-NOAA + +body: + - type: dropdown + attributes: + label: Machine + options: + - WCOSS2 + - Hera/Ursa + - Orion/Hercules + - Jet + multiple: true + validations: + required: true + + - type: input + attributes: + label: Start date + validations: + required: true + + - type: input + attributes: + label: End date + validations: + required: true + + - type: textarea + attributes: + label: Additional information + placeholder: | + Any additional information needed (experimental obs, etc.) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 9a1d61eb30..9e9c9eccfe 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -72,4 +72,5 @@ Example: - [ ] My changes generate no new warnings - [ ] New and existing tests pass with my changes - [ ] This change is covered by an existing CI test or a new one has been added +- [ ] Any new scripts have been added to the .github/CODEOWNERS file with owners - [ ] I have made corresponding changes to the system documentation if necessary diff --git a/.github/workflows/ci_unit_tests.yaml b/.github/workflows/ci_unit_tests.yaml index 6dbc7ee52c..5fa2ec28c1 100644 --- a/.github/workflows/ci_unit_tests.yaml +++ b/.github/workflows/ci_unit_tests.yaml @@ -4,8 +4,8 @@ on: [pull_request, push, workflow_dispatch] jobs: ci_pytest: - runs-on: ubuntu-latest - name: Run unit tests on CI system + runs-on: ubuntu-22.04 + name: Run unit tests on CI system permissions: checks: write diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml index b04c85688b..89b5fb617a 100644 --- a/.github/workflows/docs.yaml +++ b/.github/workflows/docs.yaml @@ -21,7 +21,7 @@ jobs: permissions: pull-requests: 'write' - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 name: Build and deploy documentation steps: diff --git a/.github/workflows/globalworkflow-ci.yaml b/.github/workflows/globalworkflow-ci.yaml deleted file mode 100644 index 1474c79a48..0000000000 --- a/.github/workflows/globalworkflow-ci.yaml +++ /dev/null @@ -1,86 +0,0 @@ -name: gw-ci-orion - -on: [workflow_dispatch] - -# TEST_DIR contains 2 directories; -# 1. HOMEgfs: clone of the global-workflow -# 2. RUNTESTS: A directory containing EXPDIR and COMROT for experiments -# e.g. $> tree ./TEST_DIR -# ./TEST_DIR -# ├── HOMEgfs -# └── RUNTESTS -# ├── COMROT -# │   └── ${pslot} -# └── EXPDIR -# └── ${pslot} -env: - TEST_DIR: ${{ github.workspace }}/${{ github.run_id }} - MACHINE_ID: orion - -jobs: - checkout-build-link: - runs-on: [self-hosted, orion-ready] - timeout-minutes: 600 - - steps: - - name: Checkout global-workflow - uses: actions/checkout@v3 - with: - path: ${{ github.run_id }}/HOMEgfs # This path needs to be relative - - - name: Checkout components - run: | - cd ${{ env.TEST_DIR }}/HOMEgfs/sorc - ./checkout.sh -c -g # Options e.g. -u can be added late - - - name: Build components - run: | - cd ${{ env.TEST_DIR }}/HOMEgfs/sorc - ./build_all.sh - - - name: Link artifacts - run: | - cd ${{ env.TEST_DIR }}/HOMEgfs/sorc - ./link_workflow.sh - - create-experiments: - needs: checkout-build-link - runs-on: [self-hosted, orion-ready] - strategy: - matrix: - case: ["C48_S2S", "C96_atm3DVar"] - - steps: - - name: Create Experiments ${{ matrix.case }} - env: - HOMEgfs_PR: ${{ env.TEST_DIR }}/HOMEgfs - RUNTESTS: ${{ env.TEST_DIR }}/RUNTESTS - pslot: ${{ matrix.case }}.${{ github.run_id }} - run: | - cd ${{ env.TEST_DIR }}/HOMEgfs - source workflow/gw_setup.sh - source ci/platforms/orion.sh - ./ci/scripts/create_experiment.py --yaml ci/cases/${{ matrix.case }}.yaml --dir ${{ env.HOMEgfs_PR }} - - run-experiments: - needs: create-experiments - runs-on: [self-hosted, orion-ready] - strategy: - max-parallel: 2 - matrix: - case: ["C48_S2S", "C96_atm3DVar"] - steps: - - name: Run Experiment ${{ matrix.case }} - run: | - cd ${{ env.TEST_DIR }}/HOMEgfs - ./ci/scripts/run-check_ci.sh ${{ env.TEST_DIR }} ${{ matrix.case }}.${{ github.run_id }} - - clean-up: - needs: run-experiments - runs-on: [self-hosted, orion-ready] - steps: - - name: Clean-up - run: | - cd ${{ github.workspace }} - rm -rf ${{ github.run_id }} - diff --git a/.github/workflows/hera.yaml b/.github/workflows/hera.yaml index 800d87e55a..0dd9f2c356 100644 --- a/.github/workflows/hera.yaml +++ b/.github/workflows/hera.yaml @@ -9,7 +9,7 @@ on: jobs: getlabels: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 outputs: labels: ${{ steps.id.outputs.labels }} steps: diff --git a/.github/workflows/linters.yaml b/.github/workflows/linters.yaml index 488b6a1407..7816788b81 100644 --- a/.github/workflows/linters.yaml +++ b/.github/workflows/linters.yaml @@ -12,7 +12,7 @@ defaults: jobs: lint-shell: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 permissions: security-events: write diff --git a/.github/workflows/orion.yaml b/.github/workflows/orion.yaml index 2d17b3db63..aaf1e28370 100644 --- a/.github/workflows/orion.yaml +++ b/.github/workflows/orion.yaml @@ -9,7 +9,7 @@ on: jobs: getlabels: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 outputs: labels: ${{ steps.id.outputs.labels }} steps: @@ -27,7 +27,7 @@ jobs: passed: if: contains( needs.getlabels.outputs.labels, 'CI-Orion-Passed') && github.event.pull_request.merged - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 needs: - getlabels diff --git a/.github/workflows/pw_aws_ci.yaml b/.github/workflows/pw_aws_ci.yaml new file mode 100644 index 0000000000..245e219dd4 --- /dev/null +++ b/.github/workflows/pw_aws_ci.yaml @@ -0,0 +1,136 @@ +name: gw-ci-aws +# TEST_DIR contains 2 directories; +# 1. HOMEgfs: clone of the global-workflow +# 2. RUNTESTS: A directory containing EXPDIR and COMROT for experiments +# e.g. $> tree ./TEST_DIR +# ./TEST_DIR +# ├── HOMEgfs +# └── RUNTESTS +# ├── COMROT +# │ └── ${pslot} +# └── EXPDIR +# └── ${pslot} + +on: + workflow_dispatch: + inputs: + pr_number: + description: 'Pull Request Number (use 0 for non-PR)' + required: true + default: '0' + os: + description: 'Operating System' + required: true + type: choice + options: + - rocky + - centos + +env: + TEST_DIR: ${{ github.workspace }}/${{ github.run_id }} + MACHINE_ID: noaacloud + +jobs: + fetch-branch: + runs-on: ubuntu-latest + env: + GH_TOKEN: ${{ secrets.GITHUBTOKEN }} + outputs: + branch: ${{ steps.get-branch.outputs.branch }} + steps: + - name: Fetch branch name for PR + id: get-branch + run: | + pr_number=${{ github.event.inputs.pr_number }} + repo=${{ github.repository }} + if [ "$pr_number" -eq "0" ]; then + branch=${{ github.event.inputs.ref }} + else + branch=$(gh pr view $pr_number --repo $repo --json headRefName --jq '.headRefName') + fi + echo "::set-output name=branch::$branch" + + checkout: + needs: fetch-branch + runs-on: + - self-hosted + - aws + - parallelworks + - ${{ github.event.inputs.os }} + timeout-minutes: 600 + steps: + - name: Checkout global-workflow + uses: actions/checkout@v4 + with: + path: ${{ github.run_id }}/HOMEgfs + submodules: 'recursive' + ref: ${{ needs.fetch-branch.outputs.branch }} + + build-link: + needs: checkout + runs-on: + - self-hosted + - aws + - parallelworks + - ${{ github.event.inputs.os }} + steps: + - name: Build components + run: | + cd ${{ env.TEST_DIR }}/HOMEgfs/sorc + ./build_all.sh -j 8 + + - name: Link artifacts + run: | + cd ${{ env.TEST_DIR }}/HOMEgfs/sorc + ./link_workflow.sh + + create-experiments: + needs: build-link + runs-on: + - self-hosted + - aws + - parallelworks + - ${{ github.event.inputs.os }} + strategy: + matrix: + case: ["C48_ATM"] + steps: + - name: Create Experiments ${{ matrix.case }} + env: + RUNTESTS: ${{ env.TEST_DIR }}/RUNTESTS + pslot: ${{ matrix.case }}.${{ github.run_id }} + run: | + mkdir -p ${{ env.RUNTESTS }} + cd ${{ env.TEST_DIR }}/HOMEgfs + source workflow/gw_setup.sh + source ci/platforms/config.noaacloud + ./workflow/create_experiment.py --yaml ci/cases/pr/${{ matrix.case }}.yaml --overwrite + + run-experiments: + needs: create-experiments + runs-on: + - self-hosted + - aws + - parallelworks + - ${{ github.event.inputs.os }} + strategy: + matrix: + case: ["C48_ATM"] + steps: + - name: Run Experiment ${{ matrix.case }} + run: | + cd ${{ env.TEST_DIR }}/HOMEgfs + ./ci/scripts/run-check_ci.sh ${{ env.TEST_DIR }} ${{ matrix.case }}.${{ github.run_id }} HOMEgfs + + clean-up: + needs: run-experiments + runs-on: + - self-hosted + - aws + - parallelworks + - ${{ github.event.inputs.os }} + steps: + - name: Clean up workspace + run: | + echo "Cleaning up workspace" + rm -rf ${{ env.TEST_DIR }} diff --git a/.github/workflows/pynorms.yaml b/.github/workflows/pynorms.yaml index 6ea99b59ed..87915190ef 100644 --- a/.github/workflows/pynorms.yaml +++ b/.github/workflows/pynorms.yaml @@ -3,7 +3,7 @@ on: [push, pull_request] jobs: check_norms: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 name: Check Python coding norms with pycodestyle steps: diff --git a/ci/Jenkinsfile b/ci/Jenkinsfile index 2654adba29..6a2e064be0 100644 --- a/ci/Jenkinsfile +++ b/ci/Jenkinsfile @@ -5,7 +5,7 @@ def cases = '' def GH = 'none' // Location of the custom workspaces for each machine in the CI system. They are persitent for each iteration of the PR. def NodeName = [hera: 'Hera-EMC', orion: 'Orion-EMC', hercules: 'Hercules-EMC', gaea: 'Gaea'] -def custom_workspace = [hera: '/scratch1/NCEPDEV/global/CI', orion: '/work2/noaa/stmp/CI/ORION', hercules: '/work2/noaa/stmp/CI/HERCULES', gaea: '/gpfs/f5/epic/proj-shared/global/CI'] +def custom_workspace = [hera: '/scratch1/NCEPDEV/global/CI', orion: '/work2/noaa/stmp/CI/ORION', hercules: '/work2/noaa/global/CI/HERCULES', gaea: '/gpfs/f5/epic/proj-shared/global/CI'] def repo_url = 'git@github.com:NOAA-EMC/global-workflow.git' def STATUS = 'Passed' @@ -101,9 +101,10 @@ pipeline { stages { stage('Building') { steps { - catchError(buildResult: 'UNSTABLE', stageResult: 'FAILURE') { + catchError(buildResult: 'UNSTABLE', stageResult: 'FAILURE') { script { def HOMEgfs = "${CUSTOM_WORKSPACE}/${system}" // local HOMEgfs is used to build the system on per system basis under the custome workspace for each buile system + env.HOME_GFS = HOMEgfs // setting path in HOMEgfs as an environment variable HOME_GFS for some systems that using the path in its .bashrc sh(script: "mkdir -p ${HOMEgfs}") ws(HOMEgfs) { if (fileExists("${HOMEgfs}/sorc/BUILT_semaphor")) { // if the system is already built, skip the build in the case of re-runs @@ -172,9 +173,10 @@ pipeline { } if (system == 'gfs') { cases = sh(script: "${HOMEgfs}/ci/scripts/utils/get_host_case_list.py ${machine}", returnStdout: true).trim().split() + echo "Cases to run: ${cases}" } } - } + } } } } @@ -192,32 +194,34 @@ pipeline { def parallelStages = cases.collectEntries { caseName -> ["${caseName}": { stage("Create ${caseName}") { - catchError(buildResult: 'UNSTABLE', stageResult: 'FAILURE') { - script { - sh(script: "sed -n '/{.*}/!p' ${CUSTOM_WORKSPACE}/gfs/ci/cases/pr/${caseName}.yaml > ${CUSTOM_WORKSPACE}/gfs/ci/cases/pr/${caseName}.yaml.tmp") - def yaml_case = readYaml file: "${CUSTOM_WORKSPACE}/gfs/ci/cases/pr/${caseName}.yaml.tmp" - system = yaml_case.experiment.system - def HOMEgfs = "${CUSTOM_WORKSPACE}/${system}" // local HOMEgfs is used to populate the XML on per system basis - env.RUNTESTS = "${CUSTOM_WORKSPACE}/RUNTESTS" - try { - error_output = sh(script: "${HOMEgfs}/ci/scripts/utils/ci_utils_wrapper.sh create_experiment ${HOMEgfs}/ci/cases/pr/${caseName}.yaml", returnStdout: true).trim() - } catch (Exception error_create) { - sh(script: """${GH} pr comment ${env.CHANGE_ID} --repo ${repo_url} --body "${Case} **FAILED** to create experment on ${Machine} in BUILD# ${env.BUILD_NUMBER}\n with the error:\n\\`\\`\\`\n${error_output}\\`\\`\\`" """) - error("Case ${caseName} failed to create experment directory") - } + script { + sh(script: "sed -n '/{.*}/!p' ${CUSTOM_WORKSPACE}/gfs/ci/cases/pr/${caseName}.yaml > ${CUSTOM_WORKSPACE}/gfs/ci/cases/pr/${caseName}.yaml.tmp") + def yaml_case = readYaml file: "${CUSTOM_WORKSPACE}/gfs/ci/cases/pr/${caseName}.yaml.tmp" + def build_system = yaml_case.experiment.system + def HOMEgfs = "${CUSTOM_WORKSPACE}/${build_system}" // local HOMEgfs is used to populate the XML on per system basis + env.HOME_GFS = HOMEgfs // setting path in HOMEgfs as an environment variable HOME_GFS for some systems that using the path in its .bashrc + env.RUNTESTS = "${CUSTOM_WORKSPACE}/RUNTESTS" + try { + error_output = sh(script: "${HOMEgfs}/ci/scripts/utils/ci_utils_wrapper.sh create_experiment ${HOMEgfs}/ci/cases/pr/${caseName}.yaml", returnStdout: true).trim() + } catch (Exception error_create) { + sh(script: """${GH} pr comment ${env.CHANGE_ID} --repo ${repo_url} --body "${Case} **FAILED** to create experment on ${Machine} in BUILD# ${env.BUILD_NUMBER}\n with the error:\n\\`\\`\\`\n${error_output}\\`\\`\\`" """) + error("Case ${caseName} failed to create experment directory") } - } + } } stage("Running ${caseName}") { catchError(buildResult: 'FAILURE', stageResult: 'FAILURE') { script { HOMEgfs = "${CUSTOM_WORKSPACE}/gfs" // common HOMEgfs is used to launch the scripts that run the experiments + env.HOME_GFS = HOMEgfs // setting path in HOMEgfs as an environment variable HOME_GFS for some systems that using the path in its .bashrc def pslot = sh(script: "${HOMEgfs}/ci/scripts/utils/ci_utils_wrapper.sh get_pslot ${CUSTOM_WORKSPACE}/RUNTESTS ${caseName}", returnStdout: true).trim() def error_file = "${CUSTOM_WORKSPACE}/RUNTESTS/${pslot}_error.logs" sh(script: " rm -f ${error_file}") + def yaml_case = readYaml file: "${CUSTOM_WORKSPACE}/gfs/ci/cases/pr/${caseName}.yaml.tmp" + def build_system = yaml_case.experiment.system try { - sh(script: "${HOMEgfs}/ci/scripts/run-check_ci.sh ${CUSTOM_WORKSPACE} ${pslot} ${system}") + sh(script: "${HOMEgfs}/ci/scripts/run-check_ci.sh ${CUSTOM_WORKSPACE} ${pslot} ${build_system}") } catch (Exception error_experment) { sh(script: "${HOMEgfs}/ci/scripts/utils/ci_utils_wrapper.sh cancel_batch_jobs ${pslot}") ws(CUSTOM_WORKSPACE) { @@ -268,11 +272,11 @@ pipeline { } } - stage( '5. FINALIZE' ) { agent { label NodeName[machine].toLowerCase() } steps { script { + env.HOME_GFS = "${CUSTOM_WORKSPACE}/gfs" // setting path to HOMEgfs as an environment variable HOME_GFS for some systems that using the path in its .bashrc sh(script: """ labels=\$(${GH} pr view ${env.CHANGE_ID} --repo ${repo_url} --json labels --jq '.labels[].name') for label in \$labels; do diff --git a/ci/cases/gfsv17/C384mx025_3DVarAOWCDA.yaml b/ci/cases/gfsv17/C384mx025_3DVarAOWCDA.yaml index d97c9567e9..99ba7c3661 100644 --- a/ci/cases/gfsv17/C384mx025_3DVarAOWCDA.yaml +++ b/ci/cases/gfsv17/C384mx025_3DVarAOWCDA.yaml @@ -8,7 +8,7 @@ arguments: resdetatmos: 384 resdetocean: 0.25 nens: 0 - gfs_cyc: 4 + interval: 6 start: cold comroot: {{ 'RUNTESTS' | getenv }}/COMROOT expdir: {{ 'RUNTESTS' | getenv }}/EXPDIR diff --git a/ci/cases/gfsv17/ocnanal.yaml b/ci/cases/gfsv17/ocnanal.yaml index 483250db10..d559f544e4 100644 --- a/ci/cases/gfsv17/ocnanal.yaml +++ b/ci/cases/gfsv17/ocnanal.yaml @@ -16,7 +16,7 @@ base: FHMAX_GFS: 240 ACCOUNT: {{ 'HPC_ACCOUNT' | getenv }} -ocnanal: +marineanl: SOCA_INPUT_FIX_DIR: {{ HOMEgfs }}/fix/gdas/soca/1440x1080x75/soca SOCA_OBS_LIST: {{ HOMEgfs }}/sorc/gdas.cd/parm/soca/obs/obs_list.yaml SOCA_NINNER: 100 diff --git a/ci/cases/pr/C48_S2SWA_gefs.yaml b/ci/cases/pr/C48_S2SWA_gefs.yaml index a924b416c3..f39031f1a1 100644 --- a/ci/cases/pr/C48_S2SWA_gefs.yaml +++ b/ci/cases/pr/C48_S2SWA_gefs.yaml @@ -9,13 +9,13 @@ arguments: resdetocean: 5.0 resensatmos: 48 nens: 2 - gfs_cyc: 1 + interval: 24 start: cold comroot: {{ 'RUNTESTS' | getenv }}/COMROOT expdir: {{ 'RUNTESTS' | getenv }}/EXPDIR idate: 2021032312 edate: 2021032312 - yaml: {{ HOMEgfs }}/ci/cases/yamls/gefs_ci_defaults.yaml + yaml: {{ HOMEgfs }}/ci/cases/yamls/gefs_defaults_ci.yaml skip_ci_on_hosts: - wcoss2 diff --git a/ci/cases/pr/C48mx500_3DVarAOWCDA.yaml b/ci/cases/pr/C48mx500_3DVarAOWCDA.yaml index e1b76f0db8..2de5fea7ff 100644 --- a/ci/cases/pr/C48mx500_3DVarAOWCDA.yaml +++ b/ci/cases/pr/C48mx500_3DVarAOWCDA.yaml @@ -13,7 +13,7 @@ arguments: idate: 2021032412 edate: 2021032418 nens: 0 - gfs_cyc: 0 + interval: 0 start: warm yaml: {{ HOMEgfs }}/ci/cases/yamls/soca_gfs_defaults_ci.yaml diff --git a/ci/cases/pr/C96C48_hybatmDA.yaml b/ci/cases/pr/C96C48_hybatmDA.yaml index 7617e39217..b527903d69 100644 --- a/ci/cases/pr/C96C48_hybatmDA.yaml +++ b/ci/cases/pr/C96C48_hybatmDA.yaml @@ -14,6 +14,6 @@ arguments: idate: 2021122018 edate: 2021122106 nens: 2 - gfs_cyc: 1 + interval: 24 start: cold yaml: {{ HOMEgfs }}/ci/cases/yamls/gfs_defaults_ci.yaml diff --git a/ci/cases/pr/C96C48_hybatmaerosnowDA.yaml b/ci/cases/pr/C96C48_hybatmaerosnowDA.yaml index 7387e55b24..57d0989ae3 100644 --- a/ci/cases/pr/C96C48_hybatmaerosnowDA.yaml +++ b/ci/cases/pr/C96C48_hybatmaerosnowDA.yaml @@ -13,11 +13,12 @@ arguments: idate: 2021122012 edate: 2021122100 nens: 2 - gfs_cyc: 1 + interval: 24 start: cold yaml: {{ HOMEgfs }}/ci/cases/yamls/atmaerosnowDA_defaults_ci.yaml skip_ci_on_hosts: + - wcoss2 - orion - gaea - hercules diff --git a/ci/cases/pr/C96C48_ufs_hybatmDA.yaml b/ci/cases/pr/C96C48_ufs_hybatmDA.yaml index 0b5aa7b6ac..41a8baa725 100644 --- a/ci/cases/pr/C96C48_ufs_hybatmDA.yaml +++ b/ci/cases/pr/C96C48_ufs_hybatmDA.yaml @@ -11,9 +11,9 @@ arguments: expdir: {{ 'RUNTESTS' | getenv }}/EXPDIR icsdir: {{ 'ICSDIR_ROOT' | getenv }}/C96C48/20240610 idate: 2024022318 - edate: 2024022400 + edate: 2024022406 nens: 2 - gfs_cyc: 1 + interval: 24 start: warm yaml: {{ HOMEgfs }}/ci/cases/yamls/ufs_hybatmDA_defaults.ci.yaml diff --git a/ci/cases/pr/C96_S2SWA_gefs_replay_ics.yaml b/ci/cases/pr/C96_S2SWA_gefs_replay_ics.yaml new file mode 100644 index 0000000000..7118dde53f --- /dev/null +++ b/ci/cases/pr/C96_S2SWA_gefs_replay_ics.yaml @@ -0,0 +1,22 @@ +experiment: + system: gefs + mode: forecast-only + +arguments: + pslot: {{ 'pslot' | getenv }} + app: S2SWA + resdetatmos: 96 + resdetocean: 1.0 + resensatmos: 96 + nens: 2 + interval: 6 + start: warm + comroot: {{ 'RUNTESTS' | getenv }}/COMROOT + expdir: {{ 'RUNTESTS' | getenv }}/EXPDIR + idate: 2020110100 + edate: 2020110100 + yaml: {{ HOMEgfs }}/ci/cases/yamls/gefs_replay_ci.yaml + icsdir: {{ 'ICSDIR_ROOT' | getenv }}/C96mx100/20240610 + +skip_ci_on_hosts: + - wcoss2 diff --git a/ci/cases/pr/C96_atm3DVar.yaml b/ci/cases/pr/C96_atm3DVar.yaml index e9e6c2b31c..fc09beeacf 100644 --- a/ci/cases/pr/C96_atm3DVar.yaml +++ b/ci/cases/pr/C96_atm3DVar.yaml @@ -12,7 +12,7 @@ arguments: idate: 2021122018 edate: 2021122106 nens: 0 - gfs_cyc: 1 + interval: 24 start: cold yaml: {{ HOMEgfs }}/ci/cases/yamls/gfs_defaults_ci.yaml diff --git a/ci/cases/pr/C96_atm3DVar_extended.yaml b/ci/cases/pr/C96_atm3DVar_extended.yaml index cdf69f04e0..8ab67a750e 100644 --- a/ci/cases/pr/C96_atm3DVar_extended.yaml +++ b/ci/cases/pr/C96_atm3DVar_extended.yaml @@ -12,7 +12,7 @@ arguments: idate: 2021122018 edate: 2021122118 nens: 0 - gfs_cyc: 4 + interval: 6 start: cold yaml: {{ HOMEgfs }}/ci/cases/yamls/gfs_extended_ci.yaml diff --git a/ci/cases/weekly/C384C192_hybatmda.yaml b/ci/cases/weekly/C384C192_hybatmda.yaml index 131ada95d5..6053f73124 100644 --- a/ci/cases/weekly/C384C192_hybatmda.yaml +++ b/ci/cases/weekly/C384C192_hybatmda.yaml @@ -14,6 +14,6 @@ arguments: idate: 2023040118 edate: 2023040200 nens: 2 - gfs_cyc: 1 + interval: 24 start: cold yaml: {{ HOMEgfs }}/ci/cases/yamls/gfs_defaults_ci.yaml diff --git a/ci/cases/weekly/C384_atm3DVar.yaml b/ci/cases/weekly/C384_atm3DVar.yaml index 40487f3b47..1a14059ab1 100644 --- a/ci/cases/weekly/C384_atm3DVar.yaml +++ b/ci/cases/weekly/C384_atm3DVar.yaml @@ -14,6 +14,6 @@ arguments: idate: 2023040118 edate: 2023040200 nens: 0 - gfs_cyc: 1 + interval: 24 start: cold yaml: {{ HOMEgfs }}/ci/cases/yamls/gfs_defaults_ci.yaml diff --git a/ci/cases/yamls/gefs_ci_defaults.yaml b/ci/cases/yamls/gefs_defaults_ci.yaml similarity index 100% rename from ci/cases/yamls/gefs_ci_defaults.yaml rename to ci/cases/yamls/gefs_defaults_ci.yaml diff --git a/ci/cases/yamls/gefs_replay_ci.yaml b/ci/cases/yamls/gefs_replay_ci.yaml new file mode 100644 index 0000000000..dfbd9ae065 --- /dev/null +++ b/ci/cases/yamls/gefs_replay_ci.yaml @@ -0,0 +1,14 @@ +defaults: + !INC {{ HOMEgfs }}/parm/config/gefs/yaml/defaults.yaml +base: + ACCOUNT: {{ 'HPC_ACCOUNT' | getenv }} + REPLAY_ICS: "YES" + FCST_BREAKPOINTS: "" + FHMAX_GFS: 48 + FHMAX_HF_GFS: 24 + DO_EXTRACTVARS: "YES" + FHOUT_HF_GFS: 3 + FHOUT_OCN_GFS: 24 + FHOUT_ICE_GFS: 24 + HOMEDIR: {{ 'RUNTESTS' | getenv }}/GLOBAL + diff --git a/ci/scripts/utils/launch_java_agent.sh b/ci/scripts/utils/launch_java_agent.sh index 183e671b9d..eb78d3b1ef 100755 --- a/ci/scripts/utils/launch_java_agent.sh +++ b/ci/scripts/utils/launch_java_agent.sh @@ -106,7 +106,7 @@ export GH="${HOME}/bin/gh" [[ -f "${GH}" ]] || echo "gh is not installed in ${HOME}/bin" ${GH} --version -check_mark=$(gh auth status -t 2>&1 | grep "Token:" | awk '{print $1}') || true +check_mark=$("${GH}" auth status -t 2>&1 | grep "Token:" | awk '{print $1}') || true if [[ "${check_mark}" != "✓" ]]; then echo "gh not authenticating with emcbot token" exit 1 diff --git a/ci/scripts/utils/parallel_works/UserBootstrap_centos7.txt b/ci/scripts/utils/parallel_works/UserBootstrap_centos7.txt new file mode 100644 index 0000000000..ddc6b05706 --- /dev/null +++ b/ci/scripts/utils/parallel_works/UserBootstrap_centos7.txt @@ -0,0 +1,5 @@ +sudo yum -y install https://packages.endpointdev.com/rhel/7/os/x86_64/endpoint-repo.x86_64.rpm +sudo yum -y install git +/contrib/Terry.McGuinness/SETUP/provision_runner.sh +ALLNODES +/contrib/Terry.McGuinness/SETUP/mount-epic-contrib.sh \ No newline at end of file diff --git a/ci/scripts/utils/parallel_works/provision_runner.sh b/ci/scripts/utils/parallel_works/provision_runner.sh new file mode 100755 index 0000000000..cac18c9315 --- /dev/null +++ b/ci/scripts/utils/parallel_works/provision_runner.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env bash + +# This script provisions a GitHub Actions runner on a Rocky or CentOS system. +# It performs the following steps: +# 1. Checks the operating system from /etc/os-release. +# 2. Verifies if the operating system is either Rocky or CentOS. +# 3. Checks if an actions-runner process is already running for the current user. +# 4. Copies the actions-runner tar file from a specified directory to the home directory. +# 5. Extracts the tar file and starts the actions-runner in the background. +# +# The actions-runner tar file contains the necessary binaries and scripts to run +# a GitHub Actions runner. It is specific to the operating system and is expected +# to be located in the /contrib/${CI_USER}/SETUP/ directory. + +CI_USER="Terry.McGuinness" + +# Get the Operating System name from /etc/os-release +OS_NAME=$(grep -E '^ID=' /etc/os-release | sed -E 's/ID="?([^"]*)"?/\1/') || true + +# Check if the OS is Rocky or CentOS +if [[ "${OS_NAME}" == "rocky" || "${OS_NAME}" == "centos" ]]; then + echo "Operating System is ${OS_NAME}" +else + echo "Unsupported Operating System: ${OS_NAME}" + exit 1 +fi + +running=$(pgrep -u "${USER}" run-helper -c) || true +if [[ "${running}" -gt 0 ]]; then + echo "actions-runner is already running" + exit +fi + +cp "/contrib/${CI_USER}/SETUP/actions-runner_${OS_NAME}.tar.gz" "${HOME}" +cd "${HOME}" || exit +tar -xf "actions-runner_${OS_NAME}.tar.gz" +cd actions-runner || exit +d=$(date +%Y-%m-%d-%H:%M) +nohup ./run.sh >& "run_nohup${d}.log" & diff --git a/docs/note_fixfield.txt b/docs/note_fixfield.txt index a7a4001561..32e44b9d98 100644 --- a/docs/note_fixfield.txt +++ b/docs/note_fixfield.txt @@ -4,7 +4,7 @@ They are saved locally on all platforms Hera: /scratch1/NCEPDEV/global/glopara/fix Orion/Hercules: /work/noaa/global/glopara/fix -Jet: /mnt/lfs4/HFIP/hfv3gfs/glopara/git/fv3gfs/fix +Jet: /mnt/lfs5/HFIP/hfv3gfs/glopara/FIX/fix S4: /data/prod/glopara/fix ------------------------------------------------------------------------------ diff --git a/docs/source/clone.rst b/docs/source/clone.rst index c365f0ed0a..d3f81f2e47 100644 --- a/docs/source/clone.rst +++ b/docs/source/clone.rst @@ -9,7 +9,7 @@ Quick Instructions Quick clone/build/link instructions (more detailed instructions below). .. note:: - Here we are making the assumption that you are using the workflow to run an experiment and so are working from the authoritative repository. If you are using a development branch then follow the instructions in :doc:`development.rst`. Once you do that you can follow the instructions here with the only difference being the repository/fork you are cloning from. + Here we are making the assumption that you are using the workflow to run an experiment and so are working from the authoritative repository. If you are using a development branch then follow the instructions in :doc:`development`. Once you do that you can follow the instructions here with the only difference being the repository/fork you are cloning from. Clone the `global-workflow` and `cd` into the `sorc` directory: diff --git a/docs/source/components.rst b/docs/source/components.rst index 869ef89bab..f5a60a96af 100644 --- a/docs/source/components.rst +++ b/docs/source/components.rst @@ -59,7 +59,7 @@ Observation data, also known as dump data, is prepared in production and then ar * Hera: ``/scratch1/NCEPDEV/global/glopara/dump`` * Orion/Hercules: ``/work/noaa/rstprod/dump`` -* Jet: ``/mnt/lfs4/HFIP/hfv3gfs/glopara/dump`` +* Jet: ``/mnt/lfs5/HFIP/hfv3gfs/glopara/dump`` * WCOSS2: ``/lfs/h2/emc/global/noscrub/emc.global/dump`` * S4: ``/data/prod/glopara/dump`` diff --git a/docs/source/development.rst b/docs/source/development.rst index 4739d2b602..fc07f3f55e 100644 --- a/docs/source/development.rst +++ b/docs/source/development.rst @@ -12,6 +12,7 @@ Code managers * Kate Friedman - @KateFriedman-NOAA / kate.friedman@noaa.gov * Walter Kolczynski - @WalterKolczynski-NOAA / walter.kolczynski@noaa.gov + * David Huber - @DavidHuber-NOAA / david.huber@noaa.gov .. _development: @@ -70,7 +71,29 @@ The following steps should be followed in order to make changes to the develop b Development Tools ================= -See the ``/test`` folder in global-workflow for available development and testing tools. +Two sets of testing are available for use by developers. The first is the capability to run continuous integration tests locally and the second are a set of comparison tools. + +--------------------------- +Continuous Integration (CI) +--------------------------- + +The global workflow comes fitted with a suite of system tests that run various types of workflow. These tests are commonly run for pull requests before they may be merged into the develop branch. At a minimum, developers are expected to run the CI test(s) that will be impacted by their changes on at least one platform. + +The commonly run tests are written in YAML format and can be found in the ``ci/cases/pr`` directory. The ``workflow/generate_workflows.sh`` tool is available to aid running these cases. See the help documentation by running ``./generate_workflows.sh -h``. The script has the capability to prepare the EXPDIR and COMROOT directories for a specified or implied suite of CI tests (see :doc:`setup` for details on these directories). The script also has options to automatically build and run all tests for a given system (i.e. GFS or GEFS and a placeholder for SFS). For instance, to build the workflow and run all of the GFS tests, one would execute + +:: + + cd workflow + ./generate_workflows.sh -A "your_hpc_account" -b -G -c /path/to/root/directory + +where: + + * ``-A`` is used to specify the HPC (slurm or PBS) account to use + * ``-b`` indicates that the workflow should be built fresh + * ``-G`` specifies that all of the GFS cases should be run (this also influences the build flags to use) + * ``-c`` tells the tool to append the rocotorun commands for each experiment to your crontab + +More details on how to use the tool are provided by running ``generate_workflows.sh -h``. ---------------- Comparison Tools diff --git a/docs/source/hpc.rst b/docs/source/hpc.rst index 643cffdef0..e83851b1a2 100644 --- a/docs/source/hpc.rst +++ b/docs/source/hpc.rst @@ -90,9 +90,9 @@ Optimizing the global workflow on S4 The S4 cluster is relatively small and so optimizations are recommended to improve cycled runtimes. Please contact Innocent Souopgui (innocent.souopgui@noaa.gov) if you are planning on running a cycled experiment on this system to obtain optimized configuration files. -======================================== +================================================== Stacksize on R&Ds (Hera, Orion, Hercules, Jet, S4) -======================================== +================================================== Some GFS components, like the UPP, need an unlimited stacksize. Add the following setting into your appropriate .*rc file to support these components: diff --git a/docs/source/init.rst b/docs/source/init.rst index e1cabdc8e0..bd1bc1d2ce 100644 --- a/docs/source/init.rst +++ b/docs/source/init.rst @@ -111,7 +111,7 @@ Warm-start cycled w/ coupled (S2S) model C48 atmosphere C48 enkf (80 members) 5 Hera: /scratch1/NCEPDEV/global/glopara/data/ICSDIR/C48C48mx500 Orion/Hercules: /work/noaa/global/glopara/data/ICSDIR/C48C48mx500 WCOSS2: /lfs/h2/emc/global/noscrub/emc.global/data/ICSDIR/C48C48mx500 - Jet: /lfs4/HFIP/hfv3gfs/glopara/data/ICSDIR/C48C48mx500 + Jet: /lfs5/HFIP/hfv3gfs/glopara/data/ICSDIR/C48C48mx500 AWS: https://noaa-nws-global-pds.s3.amazonaws.com/index.html#data/ICSDIR/C48C48mx500 Start date = 2021032312 @@ -227,7 +227,7 @@ Forecast-only P8 prototype initial conditions are made available to users on sup WCOSS2: /lfs/h2/emc/global/noscrub/emc.global/IC/COUPLED HERA: /scratch1/NCEPDEV/climate/role.ufscpara/IC ORION/Hercules: /work/noaa/global/glopara/data/ICSDIR/prototype_ICs - JET: /mnt/lfs4/HFIP/hfv3gfs/glopara/data/ICSDIR/prototype_ICs + JET: /mnt/lfs5/HFIP/hfv3gfs/glopara/data/ICSDIR/prototype_ICs S4: /data/prod/glopara/coupled_ICs These locations are known within the workflow via paths set in ``parm/config/config.coupled_ic``. @@ -301,7 +301,7 @@ Operations/production output location on HPSS: /NCEPPROD/hpssprod/runhistory/rh | | | | | | | gfs.t. ``hh`` z.sfcanl.nc | | | +----------------+---------------------------------+-----------------------------------------------------------------------------+--------------------------------+ -| v16.2[3]+ ops | gfs.t. ``hh`` z.atmanl.nc | com_gfs_ ``gfs_ver`` _gfs. ``yyyymmdd`` _ ``hh`` .gfs_nca.tar | gfs. ``yyyymmdd`` /``hh``/atmos| +| v16.2[3]+ ops | gfs.t. ``hh`` z.atmanl.nc | com_gfs\_ ``gfs_ver`` _gfs. ``yyyymmdd`` _ ``hh`` .gfs_nca.tar | gfs. ``yyyymmdd`` /``hh``/atmos| | | | | | | | gfs.t. ``hh`` z.sfcanl.nc | | | +----------------+---------------------------------+-----------------------------------------------------------------------------+--------------------------------+ diff --git a/docs/source/jobs.rst b/docs/source/jobs.rst index 0e3700bf20..2cdecb01de 100644 --- a/docs/source/jobs.rst +++ b/docs/source/jobs.rst @@ -8,7 +8,7 @@ GFS Configuration The sequence of jobs that are run for an end-to-end (analysis+forecast+post processing+verification) GFS configuration using the Global Workflow is shown above. The system utilizes a collection of scripts that perform the tasks for each step. -For any cycle the system consists of two suites -- the "gdas" suite which provides the initial guess fields, and the "gfs" suite which creates the initial conditions and forecast of the system. As with the operational system, the gdas runs for each cycle (00, 06, 12, and 18 UTC), however, to save time and space in experiments, the gfs (right side of the diagram) is initially setup to run for only the 00 UTC cycle (See the "run GFS this cycle?" portion of the diagram). The option to run the GFS for all four cycles is available (see the ``gfs_cyc`` variable in configuration file). +For any cycle the system consists of two suites -- the "gdas" suite which provides the initial guess fields, and the "gfs" suite which creates the initial conditions and forecast of the system. An experimental run is different from operations in the following ways: diff --git a/docs/source/setup.rst b/docs/source/setup.rst index 1715899927..0916033cbd 100644 --- a/docs/source/setup.rst +++ b/docs/source/setup.rst @@ -1,3 +1,5 @@ +.. _experiment-setup: + ================ Experiment Setup ================ @@ -32,7 +34,7 @@ The following command examples include variables for reference but users should :: cd workflow - ./setup_expt.py gfs forecast-only --idate $IDATE --edate $EDATE [--app $APP] [--start $START] [--gfs_cyc $GFS_CYC] [--resdetatmos $RESDETATMOS] [--resdetocean $RESDETOCEAN] + ./setup_expt.py gfs forecast-only --idate $IDATE --edate $EDATE [--app $APP] [--start $START] [--interval $INTERVAL_GFS] [--resdetatmos $RESDETATMOS] [--resdetocean $RESDETOCEAN] [--pslot $PSLOT] [--configdir $CONFIGDIR] [--comroot $COMROOT] [--expdir $EXPDIR] where: @@ -51,12 +53,12 @@ where: * ``$START`` is the start type (warm or cold [default]) * ``$IDATE`` is the initial start date of your run (first cycle CDATE, YYYYMMDDCC) - * ``$EDATE`` is the ending date of your run (YYYYMMDDCC) and is the last cycle that will complete + * ``$EDATE`` is the ending date of your run (YYYYMMDDCC) and is the last cycle that will complete [default: $IDATE] * ``$PSLOT`` is the name of your experiment [default: test] * ``$CONFIGDIR`` is the path to the ``/config`` folder under the copy of the system you're using [default: $TOP_OF_CLONE/parm/config/] * ``$RESDETATMOS`` is the resolution of the atmosphere component of the system (i.e. 768 for C768) [default: 384] * ``$RESDETOCEAN`` is the resolution of the ocean component of the system (i.e. 0.25 for 1/4 degree) [default: 0.; determined based on atmosphere resolution] - * ``$GFS_CYC`` is the forecast frequency (0 = none, 1 = 00z only [default], 2 = 00z & 12z, 4 = all cycles) + * ``$INTERVAL_GFS`` is the forecast interval in hours [default: 6] * ``$COMROOT`` is the path to your experiment output directory. Your ``ROTDIR`` (rotating com directory) will be created using ``COMROOT`` and ``PSLOT``. [default: $HOME (but do not use default due to limited space in home directories normally, provide a path to a larger scratch space)] * ``$EXPDIR`` is the path to your experiment directory where your configs will be placed and where you will find your workflow monitoring files (i.e. rocoto database and xml file). DO NOT include PSLOT folder at end of path, it will be built for you. [default: $HOME] @@ -67,7 +69,7 @@ Atm-only: :: cd workflow - ./setup_expt.py gfs forecast-only --pslot test --idate 2020010100 --edate 2020010118 --resdetatmos 384 --gfs_cyc 4 --comroot /some_large_disk_area/Joe.Schmo/comroot --expdir /some_safe_disk_area/Joe.Schmo/expdir + ./setup_expt.py gfs forecast-only --pslot test --idate 2020010100 --edate 2020010118 --resdetatmos 384 --interval 6 --comroot /some_large_disk_area/Joe.Schmo/comroot --expdir /some_safe_disk_area/Joe.Schmo/expdir Coupled: @@ -144,7 +146,8 @@ The following command examples include variables for reference but users should :: cd workflow - ./setup_expt.py gfs cycled --idate $IDATE --edate $EDATE [--app $APP] [--start $START] [--gfs_cyc $GFS_CYC] + ./setup_expt.py gfs cycled --idate $IDATE --edate $EDATE [--app $APP] [--start $START] + [--interval $INTERVAL_GFS] [--sdate_gfs $SDATE_GFS] [--resdetatmos $RESDETATMOS] [--resdetocean $RESDETOCEAN] [--resensatmos $RESENSATMOS] [--nens $NENS] [--run $RUN] [--pslot $PSLOT] [--configdir $CONFIGDIR] [--comroot $COMROOT] [--expdir $EXPDIR] [--icsdir $ICSDIR] @@ -163,9 +166,10 @@ where: - S2SWA: atm-ocean-ice-wave-aerosols * ``$IDATE`` is the initial start date of your run (first cycle CDATE, YYYYMMDDCC) - * ``$EDATE`` is the ending date of your run (YYYYMMDDCC) and is the last cycle that will complete + * ``$EDATE`` is the ending date of your run (YYYYMMDDCC) and is the last cycle that will complete [default: $IDATE] * ``$START`` is the start type (warm or cold [default]) - * ``$GFS_CYC`` is the forecast frequency (0 = none, 1 = 00z only [default], 2 = 00z & 12z, 4 = all cycles) + * ``$INTERVAL_GFS`` is the forecast interval in hours [default: 6] + * ``$SDATE_GFS`` cycle to begin GFS forecast [default: $IDATE + 6] * ``$RESDETATMOS`` is the resolution of the atmosphere component of the deterministic forecast [default: 384] * ``$RESDETOCEAN`` is the resolution of the ocean component of the deterministic forecast [default: 0.; determined based on atmosphere resolution] * ``$RESENSATMOS`` is the resolution of the atmosphere component of the ensemble forecast [default: 192] @@ -177,14 +181,12 @@ where: * ``$EXPDIR`` is the path to your experiment directory where your configs will be placed and where you will find your workflow monitoring files (i.e. rocoto database and xml file). DO NOT include PSLOT folder at end of path, it will be built for you. [default: $HOME] * ``$ICSDIR`` is the path to the ICs for your run if generated separately. [default: None] -.. [#] More Coupled configurations in cycled mode are currently under development and not yet available - Example: :: cd workflow - ./setup_expt.py gfs cycled --pslot test --configdir /home/Joe.Schmo/git/global-workflow/parm/config --idate 2020010100 --edate 2020010118 --comroot /some_large_disk_area/Joe.Schmo/comroot --expdir /some_safe_disk_area/Joe.Schmo/expdir --resdetatmos 384 --resensatmos 192 --nens 80 --gfs_cyc 4 + ./setup_expt.py gfs cycled --pslot test --configdir /home/Joe.Schmo/git/global-workflow/parm/config --idate 2020010100 --edate 2020010118 --comroot /some_large_disk_area/Joe.Schmo/comroot --expdir /some_safe_disk_area/Joe.Schmo/expdir --resdetatmos 384 --resensatmos 192 --nens 80 --interval 6 Example ``setup_expt.py`` on Orion: diff --git a/docs/source/wave.rst b/docs/source/wave.rst index 7b4f7471b8..56aa34ce3b 100644 --- a/docs/source/wave.rst +++ b/docs/source/wave.rst @@ -79,7 +79,7 @@ While the creation of these files are generally considered out of scope of this * Instructions for creating mesh.${WAVEGRID}.nc can be found at https://ufs-weather-model.readthedocs.io/en/latest/InputsOutputs.html#ww3 * The ww3_gint.WHTGRIDINT.bin.${WAVEGRID} can be created by running the ww3_gint routine as desired and then saved. -Once the new fix files have been created, :ref:`open an issue to have the master fix file directory updated`. This is a separate step than the process to update the workflow below. +Once the new fix files have been created, `open an issue to have the master fix file directory updated `. This is a separate step than the process to update the workflow below. ******************************** Updating Config and Script Files @@ -87,8 +87,8 @@ Updating Config and Script Files You will need to update the following files: -* parm/config/*/config.ufs -* parm/config/*/config.wave +* parm/config/\*/config.ufs +* parm/config/\*/config.wave * scripts/exgfs_wave_post_gridded_sbs.sh You will need to add the following files: diff --git a/env/CONTAINER.env b/env/CONTAINER.env index c40543794b..ba01fcf0dd 100755 --- a/env/CONTAINER.env +++ b/env/CONTAINER.env @@ -26,7 +26,7 @@ ulimit -s unlimited ulimit -a -if [ "${step}" = "ocnanalrun" ]; then +if [ "${step}" = "marineanlvar" ]; then export NTHREADS_OCNANAL=1 - export APRUN_OCNANAL="${launcher} -n 2" + export APRUN_MARINEANLVAR="${launcher} -n 2" fi diff --git a/env/HERA.env b/env/HERA.env index 0d77547b5b..09743967b5 100755 --- a/env/HERA.env +++ b/env/HERA.env @@ -138,17 +138,15 @@ elif [[ "${step}" = "marinebmat" ]]; then export APRUNCFP="${launcher} -n \$ncmd --multi-prog" export APRUN_MARINEBMAT="${APRUN_default}" -elif [[ "${step}" = "ocnanalrun" ]]; then +elif [[ "${step}" = "marineanlvar" ]]; then export APRUNCFP="${launcher} -n \$ncmd --multi-prog" + export APRUN_MARINEANLVAR="${APRUN_default}" - export APRUN_OCNANAL="${APRUN_default}" - -elif [[ "${step}" = "ocnanalchkpt" ]]; then +elif [[ "${step}" = "marineanlchkpt" ]]; then export APRUNCFP="${launcher} -n \$ncmd --multi-prog" - - export APRUN_OCNANAL="${APRUN_default}" + export APRUN_MARINEANLCHKPT="${APRUN_default}" elif [[ "${step}" = "ocnanalecen" ]]; then diff --git a/env/HERCULES.env b/env/HERCULES.env index 0138e33645..9ec112c699 100755 --- a/env/HERCULES.env +++ b/env/HERCULES.env @@ -133,10 +133,10 @@ case ${step} in export APRUNCFP="${launcher} -n \$ncmd ${mpmd_opt}" export APRUN_MARINEBMAT="${APRUN_default}" ;; - "ocnanalrun") + "marineanlvar") export APRUNCFP="${launcher} -n \$ncmd ${mpmd_opt}" - export APRUN_OCNANAL="${APRUN_default}" + export APRUN_MARINEANLVAR="${APRUN_default}" ;; "ocnanalecen") @@ -148,12 +148,12 @@ case ${step} in [[ ${NTHREADS_OCNANALECEN} -gt ${max_threads_per_task} ]] && export NTHREADS_OCNANALECEN=${max_threads_per_task} export APRUN_OCNANALECEN="${launcher} -n ${ntasks_ocnanalecen} --cpus-per-task=${NTHREADS_OCNANALECEN}" ;; - "ocnanalchkpt") + "marineanlchkpt") export APRUNCFP="${launcher} -n \$ncmd ${mpmd_opt}" export NTHREADS_OCNANAL=${NTHREADSmax} - export APRUN_OCNANAL="${APRUN_default} --cpus-per-task=${NTHREADS_OCNANAL}" + export APRUN_MARINEANLCHKPT="${APRUN_default} --cpus-per-task=${NTHREADS_OCNANAL}" ;; "anal" | "analcalc") diff --git a/env/JET.env b/env/JET.env index f2b018d2d7..dbc249d4d6 100755 --- a/env/JET.env +++ b/env/JET.env @@ -69,7 +69,7 @@ elif [[ "${step}" = "atmensanlsol" ]]; then export NTHREADS_ATMENSANLSOL=${NTHREADSmax} export APRUN_ATMENSANLSOL="${APRUN_default}" - + elif [[ "${step}" = "atmensanlletkf" ]]; then export NTHREADS_ATMENSANLLETKF=${NTHREADSmax} @@ -121,10 +121,10 @@ elif [[ "${step}" = "marinebmat" ]]; then export APRUNCFP="${launcher} -n \$ncmd ${mpmd_opt}" export APRUN_MARINEBMAT="${APRUN_default}" -elif [[ "${step}" = "ocnanalrun" ]]; then +elif [[ "${step}" = "marineanlvar" ]]; then export APRUNCFP="${launcher} -n \$ncmd ${mpmd_opt}" - export APRUN_OCNANAL="${APRUN_default}" + export APRUN_MARINEANLVAR="${APRUN_default}" elif [[ "${step}" = "anal" ]] || [[ "${step}" = "analcalc" ]]; then diff --git a/env/ORION.env b/env/ORION.env index e8c1bcbf58..3b8053d060 100755 --- a/env/ORION.env +++ b/env/ORION.env @@ -130,18 +130,19 @@ elif [[ "${step}" = "marinebmat" ]]; then export NTHREADS_MARINEBMAT=${NTHREADSmax} export APRUN_MARINEBMAT="${APRUN_default}" -elif [[ "${step}" = "ocnanalrun" ]]; then +elif [[ "${step}" = "marineanlvar" ]]; then export APRUNCFP="${launcher} -n \$ncmd ${mpmd_opt}" - export APRUN_OCNANAL="${APRUN_default}" + export APRUN_MARINEANLVAR="${APRUN_default}" -elif [[ "${step}" = "ocnanalchkpt" ]]; then +elif [[ "${step}" = "marineanlchkpt" ]]; then export APRUNCFP="${launcher} -n \$ncmd ${mpmd_opt}" export NTHREADS_OCNANAL=${NTHREADSmax} - export APRUN_OCNANAL="${APRUN_default} --cpus-per-task=${NTHREADS_OCNANAL}" + + export APRUN_MARINEANLCHKPT="${APRUN_default} --cpus-per-task=${NTHREADS_OCNANAL}" elif [[ "${step}" = "ocnanalecen" ]]; then diff --git a/gempak/ush/gfs_meta_comp.sh b/gempak/ush/gfs_meta_comp.sh index 36d18d8659..38c15a60c7 100755 --- a/gempak/ush/gfs_meta_comp.sh +++ b/gempak/ush/gfs_meta_comp.sh @@ -24,7 +24,7 @@ device="nc | ${metaname}" export COMIN="gfs.multi" mkdir "${COMIN}" -for cycle in $(seq -f "%02g" -s ' ' 0 "${STEP_GFS}" "${cyc}"); do +for cycle in $(seq -f "%02g" -s ' ' 0 "${INTERVAL_GFS}" "${cyc}"); do YMD=${PDY} HH=${cycle} GRID="1p00" declare_from_tmpl gempak_dir:COM_ATMOS_GEMPAK_TMPL for file_in in "${gempak_dir}/gfs_1p00_${PDY}${cycle}f"*; do file_out="${COMIN}/$(basename "${file_in}")" diff --git a/gempak/ush/gfs_meta_mar_comp.sh b/gempak/ush/gfs_meta_mar_comp.sh index d25fc0dc9a..91f8a48876 100755 --- a/gempak/ush/gfs_meta_mar_comp.sh +++ b/gempak/ush/gfs_meta_mar_comp.sh @@ -15,7 +15,7 @@ cp "${HOMEgfs}/gempak/fix/datatype.tbl" datatype.tbl export COMIN="gfs.multi" mkdir -p "${COMIN}" -for cycle in $(seq -f "%02g" -s ' ' 0 "${STEP_GFS}" "${cyc}"); do +for cycle in $(seq -f "%02g" -s ' ' 0 "${INTERVAL_GFS}" "${cyc}"); do YMD=${PDY} HH=${cycle} GRID="1p00" declare_from_tmpl gempak_dir:COM_ATMOS_GEMPAK_TMPL for file_in in "${gempak_dir}/gfs_1p00_${PDY}${cycle}f"*; do file_out="${COMIN}/$(basename "${file_in}")" diff --git a/jobs/JGDAS_GLOBAL_OCEAN_ANALYSIS_CHKPT b/jobs/JGDAS_GLOBAL_OCEAN_ANALYSIS_CHKPT deleted file mode 100755 index 875fe9d0ee..0000000000 --- a/jobs/JGDAS_GLOBAL_OCEAN_ANALYSIS_CHKPT +++ /dev/null @@ -1,58 +0,0 @@ -#!/bin/bash -source "${HOMEgfs}/ush/preamble.sh" -export WIPE_DATA="NO" -export DATA="${DATAROOT}/${RUN}ocnanal_${cyc}" -source "${HOMEgfs}/ush/jjob_header.sh" -e "ocnanalchkpt" -c "base ocnanal ocnanalchkpt" - - -############################################## -# Set variables used in the script -############################################## -# Ignore possible spelling error (nothing is misspelled) -# shellcheck disable=SC2153 -GDATE=$(date --utc +%Y%m%d%H -d "${PDY} ${cyc} - ${assim_freq} hours") -export GDATE -export gPDY=${GDATE:0:8} -export gcyc=${GDATE:8:2} -export GDUMP=${GDUMP:-"gdas"} - -export GPREFIX="${GDUMP}.t${gcyc}z." -# Ignore possible spelling error (nothing is misspelled) -# shellcheck disable=SC2153 -export APREFIX="${RUN}.t${cyc}z." - -# Generate COM variables from templates -YMD=${PDY} HH=${cyc} declare_from_tmpl -rx COM_ATMOS_ANALYSIS - -RUN=${GDUMP} YMD=${gPDY} HH=${gcyc} declare_from_tmpl -rx COM_ATMOS_HISTORY_PREV:COM_ATMOS_HISTORY_TMPL - - -############################################## -# Begin JOB SPECIFIC work -############################################## - -############################################################### -# Run relevant script - -EXSCRIPT=${GDASOCNCHKPTSH:-${HOMEgfs}/sorc/gdas.cd/scripts/exgdas_global_marine_analysis_chkpt.sh} -${EXSCRIPT} -status=$? -[[ ${status} -ne 0 ]] && exit "${status}" - -############################################## -# End JOB SPECIFIC work -############################################## - -############################################## -# Final processing -############################################## -if [[ -e "${pgmout}" ]] ; then - cat "${pgmout}" -fi - -########################################## -# Do not remove the Temporary working directory (do this in POST) -########################################## -cd "${DATAROOT}" || exit 1 - -exit 0 diff --git a/jobs/JGDAS_GLOBAL_OCEAN_ANALYSIS_POST b/jobs/JGDAS_GLOBAL_OCEAN_ANALYSIS_POST deleted file mode 100755 index 00597f14f8..0000000000 --- a/jobs/JGDAS_GLOBAL_OCEAN_ANALYSIS_POST +++ /dev/null @@ -1,49 +0,0 @@ -#!/bin/bash -source "${HOMEgfs}/ush/preamble.sh" -export WIPE_DATA="NO" -export DATA="${DATAROOT}/${RUN}ocnanal_${cyc}" -source "${HOMEgfs}/ush/jjob_header.sh" -e "ocnanalpost" -c "base ocnanalpost" - - -############################################## -# Set variables used in the script -############################################## -# TODO remove this CDUMP declaration when the GDAS script -# exgdas_global_marine_analysis_post.py is updated to look for RUN instead -# of CDUMP. -export CDUMP=${CDUMP:-${RUN:-"gfs"}} -export CDATE=${CDATE:-${PDY}${cyc}} -export GDUMP=${GDUMP:-"gdas"} - -# Generate COM variables from templates -YMD=${PDY} HH=${cyc} declare_from_tmpl -rx COM_OCEAN_ANALYSIS COM_ICE_ANALYSIS COM_ICE_RESTART - -mkdir -p "${COM_OCEAN_ANALYSIS}" -mkdir -p "${COM_ICE_ANALYSIS}" -mkdir -p "${COM_ICE_RESTART}" - -############################################## -# Begin JOB SPECIFIC work -############################################## - -# Add UFSDA to PYTHONPATH -ufsdaPATH="${HOMEgfs}/sorc/gdas.cd/ush/" -PYTHONPATH="${PYTHONPATH:+${PYTHONPATH}:}${ufsdaPATH}" -export PYTHONPATH - -############################################################### -# Run relevant script -############################################################### - -EXSCRIPT=${GDASOCNPOSTPY:-${HOMEgfs}/sorc/gdas.cd/scripts/exgdas_global_marine_analysis_post.py} -${EXSCRIPT} -status=$? -[[ ${status} -ne 0 ]] && exit "${status}" - -########################################## -# Remove the Temporary working directory -########################################## -cd "${DATAROOT}" || exit 1 -[[ "${KEEPDATA}" = "NO" ]] && rm -rf "${DATA}" - -exit 0 diff --git a/jobs/JGDAS_GLOBAL_OCEAN_ANALYSIS_VRFY b/jobs/JGDAS_GLOBAL_OCEAN_ANALYSIS_VRFY index 0d90c46184..31df1e45c7 100755 --- a/jobs/JGDAS_GLOBAL_OCEAN_ANALYSIS_VRFY +++ b/jobs/JGDAS_GLOBAL_OCEAN_ANALYSIS_VRFY @@ -1,6 +1,6 @@ #!/bin/bash source "${HOMEgfs}/ush/preamble.sh" -source "${HOMEgfs}/ush/jjob_header.sh" -e "ocnanalprep" -c "base ocnanal ocnanalprep" +source "${HOMEgfs}/ush/jjob_header.sh" -e "marineanlinit" -c "base ocnanal marineanlinit" ############################################## diff --git a/jobs/JGFS_ATMOS_VERIFICATION b/jobs/JGFS_ATMOS_VERIFICATION index 48133364e5..fde0d73b1e 100755 --- a/jobs/JGFS_ATMOS_VERIFICATION +++ b/jobs/JGFS_ATMOS_VERIFICATION @@ -16,12 +16,6 @@ source "${HOMEgfs}/ush/jjob_header.sh" -e "metp" -c "base metp" ## METPCASE : METplus verification use case (g2g1 | g2o1 | pcp1) ############################################################### -# TODO: This should not be permitted as DATAROOT is set at the job-card level. -# TODO: DATAROOT is being used as DATA in metp jobs. This should be rectified in metp. -# TODO: The temporary directory is DATA and is created at the top of the J-Job. -# TODO: remove this line -export DATAROOT=${DATA} - VDATE=$(date --utc +%Y%m%d%H -d "${PDY} ${cyc} - ${VRFYBACK_HRS} hours") export VDATE=${VDATE:0:8} diff --git a/jobs/JGDAS_GLOBAL_OCEAN_ANALYSIS_RUN b/jobs/JGLOBAL_MARINE_ANALYSIS_CHECKPOINT similarity index 65% rename from jobs/JGDAS_GLOBAL_OCEAN_ANALYSIS_RUN rename to jobs/JGLOBAL_MARINE_ANALYSIS_CHECKPOINT index 5871497223..8cd7b1ab7c 100755 --- a/jobs/JGDAS_GLOBAL_OCEAN_ANALYSIS_RUN +++ b/jobs/JGLOBAL_MARINE_ANALYSIS_CHECKPOINT @@ -1,8 +1,9 @@ #!/bin/bash source "${HOMEgfs}/ush/preamble.sh" export WIPE_DATA="NO" -export DATA="${DATAROOT}/${RUN}ocnanal_${cyc}" -source "${HOMEgfs}/ush/jjob_header.sh" -e "ocnanalrun" -c "base ocnanal ocnanalrun" +export DATAjob="${DATAROOT}/${RUN}marineanalysis.${PDY:-}${cyc}" +export DATA="${DATAjob}/marinevariational" +source "${HOMEgfs}/ush/jjob_header.sh" -e "marineanlchkpt" -c "base marineanl marineanlchkpt" ############################################## @@ -13,11 +14,10 @@ source "${HOMEgfs}/ush/jjob_header.sh" -e "ocnanalrun" -c "base ocnanal ocnanalr # Begin JOB SPECIFIC work ############################################## - ############################################################### # Run relevant script -EXSCRIPT=${GDASOCNRUNSH:-${HOMEgfs}/sorc/gdas.cd/scripts/exgdas_global_marine_analysis_run.sh} +EXSCRIPT=${GDASMARINEANALYSIS:-${SCRgfs}/exglobal_marine_analysis_checkpoint.py} ${EXSCRIPT} status=$? [[ ${status} -ne 0 ]] && exit "${status}" @@ -33,9 +33,4 @@ if [[ -e "${pgmout}" ]] ; then cat "${pgmout}" fi -########################################## -# Do not remove the Temporary working directory (do this in POST) -########################################## -cd "${DATAROOT}" || exit 1 - exit 0 diff --git a/jobs/JGLOBAL_MARINE_ANALYSIS_FINALIZE b/jobs/JGLOBAL_MARINE_ANALYSIS_FINALIZE new file mode 100755 index 0000000000..2614639184 --- /dev/null +++ b/jobs/JGLOBAL_MARINE_ANALYSIS_FINALIZE @@ -0,0 +1,43 @@ +#!/bin/bash +source "${HOMEgfs}/ush/preamble.sh" +export WIPE_DATA="NO" +export DATAjob="${DATAROOT}/${RUN}marineanalysis.${PDY:-}${cyc}" +export DATA="${DATAjob}/marinevariational" +source "${HOMEgfs}/ush/jjob_header.sh" -e "marineanlfinal" -c "base marineanl marineanlfinal" + +############################################## +# Set variables used in the script +############################################## + +############################################## +# Begin JOB SPECIFIC work +############################################## + +# Generate COM variables from templates +YMD=${PDY} HH=${cyc} declare_from_tmpl -rx COMIN_OBS:COM_OBS_TMPL + +YMD=${PDY} HH=${cyc} declare_from_tmpl -rx \ + COMOUT_OCEAN_ANALYSIS:COM_OCEAN_ANALYSIS_TMPL \ + COMOUT_ICE_ANALYSIS:COM_ICE_ANALYSIS_TMPL \ + COMOUT_ICE_RESTART:COM_ICE_RESTART_TMPL + +mkdir -p "${COMOUT_OCEAN_ANALYSIS}" +mkdir -p "${COMOUT_ICE_ANALYSIS}" +mkdir -p "${COMOUT_ICE_RESTART}" + +############################################################### +# Run relevant script +############################################################### + +EXSCRIPT=${GDASMARINEANALYSIS:-${SCRgfs}/exglobal_marine_analysis_finalize.py} +${EXSCRIPT} +status=$? +[[ ${status} -ne 0 ]] && exit "${status}" + +########################################## +# Remove the Temporary working directory +########################################## +cd "${DATAROOT}" || exit 1 +[[ "${KEEPDATA}" = "NO" ]] && rm -rf "${DATA}" + +exit 0 diff --git a/jobs/JGDAS_GLOBAL_OCEAN_ANALYSIS_PREP b/jobs/JGLOBAL_MARINE_ANALYSIS_INITIALIZE similarity index 60% rename from jobs/JGDAS_GLOBAL_OCEAN_ANALYSIS_PREP rename to jobs/JGLOBAL_MARINE_ANALYSIS_INITIALIZE index 664df3aad6..eb167af94d 100755 --- a/jobs/JGDAS_GLOBAL_OCEAN_ANALYSIS_PREP +++ b/jobs/JGLOBAL_MARINE_ANALYSIS_INITIALIZE @@ -1,7 +1,9 @@ #!/bin/bash + source "${HOMEgfs}/ush/preamble.sh" -export DATA="${DATAROOT}/${RUN}ocnanal_${cyc}" -source "${HOMEgfs}/ush/jjob_header.sh" -e "ocnanalprep" -c "base ocnanal ocnanalprep" +export DATAjob="${DATAROOT}/${RUN}marineanalysis.${PDY:-}${cyc}" +export DATA="${DATAjob}/marinevariational" +source "${HOMEgfs}/ush/jjob_header.sh" -e "marineanlinit" -c "base marineanl marineanlinit" ############################################## @@ -10,42 +12,30 @@ source "${HOMEgfs}/ush/jjob_header.sh" -e "ocnanalprep" -c "base ocnanal ocnanal # Ignore possible spelling error (nothing is misspelled) # shellcheck disable=SC2153 GDATE=$(date --utc +%Y%m%d%H -d "${PDY} ${cyc} - ${assim_freq} hours") -export GDATE -export gPDY=${GDATE:0:8} -export gcyc=${GDATE:8:2} +gPDY=${GDATE:0:8} +gcyc=${GDATE:8:2} export GDUMP=${GDUMP:-"gdas"} -export OPREFIX="${RUN}.t${cyc}z." -export GPREFIX="${GDUMP}.t${gcyc}z." -export APREFIX="${RUN}.t${cyc}z." +############################################## +# Begin JOB SPECIFIC work +############################################## # Generate COM variables from templates YMD=${PDY} HH=${cyc} declare_from_tmpl -rx COM_OBS RUN=${GDUMP} YMD=${gPDY} HH=${gcyc} declare_from_tmpl -rx \ - COM_OCEAN_HISTORY_PREV:COM_OCEAN_HISTORY_TMPL \ - COM_ICE_HISTORY_PREV:COM_ICE_HISTORY_TMPL \ - COM_ICE_RESTART_PREV:COM_ICE_RESTART_TMPL + COMIN_OCEAN_HISTORY_PREV:COM_OCEAN_HISTORY_TMPL \ + COMIN_ICE_HISTORY_PREV:COM_ICE_HISTORY_TMPL \ + COMIN_ICE_RESTART_PREV:COM_ICE_RESTART_TMPL YMD=${PDY} HH=${cyc} declare_from_tmpl -rx \ COMIN_OCEAN_BMATRIX:COM_OCEAN_BMATRIX_TMPL \ COMIN_ICE_BMATRIX:COM_ICE_BMATRIX_TMPL -############################################## -# Begin JOB SPECIFIC work -############################################## - -# Add UFSDA to PYTHONPATH -ufsdaPATH="${HOMEgfs}/sorc/gdas.cd/ush/" -# shellcheck disable=SC2311 -pyiodaPATH="${HOMEgfs}/sorc/gdas.cd/build/lib/python$(detect_py_ver)/" -PYTHONPATH="${PYTHONPATH:+${PYTHONPATH}:}${ufsdaPATH}:${pyiodaPATH}" -export PYTHONPATH - ############################################################### # Run relevant script -EXSCRIPT=${GDASOCNPREPPY:-${HOMEgfs}/sorc/gdas.cd/scripts/exgdas_global_marine_analysis_prep.py} +EXSCRIPT=${GDASMARINEANALYSIS:-${SCRgfs}/exglobal_marine_analysis_initialize.py} ${EXSCRIPT} status=$? [[ ${status} -ne 0 ]] && exit "${status}" diff --git a/jobs/JGLOBAL_MARINE_ANALYSIS_VARIATIONAL b/jobs/JGLOBAL_MARINE_ANALYSIS_VARIATIONAL new file mode 100755 index 0000000000..7780353294 --- /dev/null +++ b/jobs/JGLOBAL_MARINE_ANALYSIS_VARIATIONAL @@ -0,0 +1,38 @@ +#! /usr/bin/env bash + +source "${HOMEgfs}/ush/preamble.sh" +export WIPE_DATA="NO" +export DATAjob="${DATAROOT}/${RUN}marineanalysis.${PDY:-}${cyc}" +export DATA="${DATAjob}/marinevariational" +source "${HOMEgfs}/ush/jjob_header.sh" -e "marineanlvar" -c "base marineanl marineanlvar" + +############################################## +# Set variables used in the script +############################################## + + +############################################## +# Begin JOB SPECIFIC work +############################################## + + +############################################################### +# Run relevant script + +EXSCRIPT=${GDASMARINERUNSH:-${SCRgfs}/exglobal_marine_analysis_variational.py} +${EXSCRIPT} +status=$? +[[ ${status} -ne 0 ]] && exit "${status}" + +############################################## +# End JOB SPECIFIC work +############################################## + +############################################## +# Final processing +############################################## +if [[ -e "${pgmout}" ]] ; then + cat "${pgmout}" +fi + +exit 0 diff --git a/jobs/JGLOBAL_MARINE_BMAT b/jobs/JGLOBAL_MARINE_BMAT index 3dacec9278..3189df0463 100755 --- a/jobs/JGLOBAL_MARINE_BMAT +++ b/jobs/JGLOBAL_MARINE_BMAT @@ -2,17 +2,17 @@ source "${HOMEgfs}/ush/preamble.sh" -if (( 10#${ENSMEM:-0} > 0 )); then - export DATAjob="${DATAROOT}/${RUN}marinebmat.${PDY:-}${cyc}" - export DATA="${DATAjob}/${jobid}" - # Create the directory to hold ensemble perturbations - export DATAenspert="${DATAjob}/enspert" - if [[ ! -d "${DATAenspert}" ]]; then mkdir -p "${DATAenspert}"; fi -fi +export DATAjob="${DATAROOT}/${RUN}marineanalysis.${PDY:-}${cyc}" +export DATA="${DATAjob}/${jobid}" +# Create the directory to hold ensemble perturbations +export DATAens="${DATAjob}/ensdata" +export DATAstaticb="${DATAjob}/staticb" +if [[ ! -d "${DATAens}" ]]; then mkdir -p "${DATAens}"; fi +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 ocnanal marinebmat" +source "${HOMEgfs}/ush/jjob_header.sh" -e "marinebmat" -c "base marineanl marinebmat" ############################################## # Set variables used in the script diff --git a/jobs/JGLOBAL_WAVE_POST_PNT b/jobs/JGLOBAL_WAVE_POST_PNT index 769159be61..6be2d88906 100755 --- a/jobs/JGLOBAL_WAVE_POST_PNT +++ b/jobs/JGLOBAL_WAVE_POST_PNT @@ -25,7 +25,7 @@ export WAV_MOD_TAG=${RUN}wave${waveMEMB} export CFP_VERBOSE=1 -export FHMAX_WAV_PNT=${FHMAX_WAV} +export FHMAX_WAV_PNT=$(( FHMAX_WAV - OFFSET_START_HOUR )) export DOSPC_WAV='YES' # Spectral post export DOBLL_WAV='YES' # Bulletin post export DOBNDPNT_WAV='NO' #not boundary points diff --git a/jobs/rocoto/ocnanalrun.sh b/jobs/rocoto/marineanlchkpt.sh similarity index 82% rename from jobs/rocoto/ocnanalrun.sh rename to jobs/rocoto/marineanlchkpt.sh index 5f998af989..69e10a7fa8 100755 --- a/jobs/rocoto/ocnanalrun.sh +++ b/jobs/rocoto/marineanlchkpt.sh @@ -8,11 +8,11 @@ source "${HOMEgfs}/ush/preamble.sh" status=$? [[ ${status} -ne 0 ]] && exit "${status}" -export job="ocnanalrun" +export job="marineanlchkpt" export jobid="${job}.$$" ############################################################### # Execute the JJOB -"${HOMEgfs}"/jobs/JGDAS_GLOBAL_OCEAN_ANALYSIS_RUN +"${HOMEgfs}"/jobs/JGLOBAL_MARINE_ANALYSIS_CHECKPOINT status=$? exit "${status}" diff --git a/jobs/rocoto/ocnanalchkpt.sh b/jobs/rocoto/marineanlfinal.sh similarity index 82% rename from jobs/rocoto/ocnanalchkpt.sh rename to jobs/rocoto/marineanlfinal.sh index ae98bc8e88..8f0c8fa3a3 100755 --- a/jobs/rocoto/ocnanalchkpt.sh +++ b/jobs/rocoto/marineanlfinal.sh @@ -8,11 +8,11 @@ source "${HOMEgfs}/ush/preamble.sh" status=$? [[ ${status} -ne 0 ]] && exit "${status}" -export job="ocnanalchkpt" +export job="marineanlfinal" export jobid="${job}.$$" ############################################################### # Execute the JJOB -"${HOMEgfs}"/jobs/JGDAS_GLOBAL_OCEAN_ANALYSIS_CHKPT +"${HOMEgfs}"/jobs/JGLOBAL_MARINE_ANALYSIS_FINALIZE status=$? exit "${status}" diff --git a/jobs/rocoto/ocnanalprep.sh b/jobs/rocoto/marineanlinit.sh similarity index 89% rename from jobs/rocoto/ocnanalprep.sh rename to jobs/rocoto/marineanlinit.sh index 3830fe1c39..953fc0dcfd 100755 --- a/jobs/rocoto/ocnanalprep.sh +++ b/jobs/rocoto/marineanlinit.sh @@ -14,6 +14,6 @@ export jobid="${job}.$$" ############################################################### # Execute the JJOB -"${HOMEgfs}"/jobs/JGDAS_GLOBAL_OCEAN_ANALYSIS_PREP +"${HOMEgfs}"/jobs/JGLOBAL_MARINE_ANALYSIS_INITIALIZE status=$? exit "${status}" diff --git a/jobs/rocoto/ocnanalpost.sh b/jobs/rocoto/marineanlvar.sh similarity index 82% rename from jobs/rocoto/ocnanalpost.sh rename to jobs/rocoto/marineanlvar.sh index b99a4e05ca..35a21a2bcb 100755 --- a/jobs/rocoto/ocnanalpost.sh +++ b/jobs/rocoto/marineanlvar.sh @@ -8,11 +8,11 @@ source "${HOMEgfs}/ush/preamble.sh" status=$? [[ ${status} -ne 0 ]] && exit "${status}" -export job="ocnanalpost" +export job="marineanlvar" export jobid="${job}.$$" ############################################################### # Execute the JJOB -"${HOMEgfs}"/jobs/JGDAS_GLOBAL_OCEAN_ANALYSIS_POST +"${HOMEgfs}/jobs/JGLOBAL_MARINE_ANALYSIS_VARIATIONAL" status=$? exit "${status}" diff --git a/modulefiles/module_base.jet.lua b/modulefiles/module_base.jet.lua index 2f00c301df..56735a2057 100644 --- a/modulefiles/module_base.jet.lua +++ b/modulefiles/module_base.jet.lua @@ -52,7 +52,7 @@ setenv("UTILROOT",(os.getenv("prod_util_ROOT") or "None")) prepend_path("MODULEPATH", pathJoin("/lfs5/HFIP/hfv3gfs/glopara/git/prepobs/v" .. (os.getenv("prepobs_run_ver") or "None"), "modulefiles")) load(pathJoin("prepobs", (os.getenv("prepobs_run_ver") or "None"))) -prepend_path("MODULEPATH", pathJoin("/lfs4/HFIP/hfv3gfs/glopara/git/Fit2Obs/v" .. (os.getenv("fit2obs_ver") or "None"), "modulefiles")) +prepend_path("MODULEPATH", pathJoin("/lfs5/HFIP/hfv3gfs/glopara/git/Fit2Obs/v" .. (os.getenv("fit2obs_ver") or "None"), "modulefiles")) load(pathJoin("fit2obs", (os.getenv("fit2obs_ver") or "None"))) whatis("Description: GFS run environment") diff --git a/modulefiles/module_gwsetup.jet.lua b/modulefiles/module_gwsetup.jet.lua index bc14b19a79..a722c812a5 100644 --- a/modulefiles/module_gwsetup.jet.lua +++ b/modulefiles/module_gwsetup.jet.lua @@ -4,7 +4,7 @@ Load environment to run GFS workflow setup scripts on Jet load(pathJoin("rocoto")) -prepend_path("MODULEPATH", "/mnt/lfs4/HFIP/hfv3gfs/role.epic/spack-stack/spack-stack-1.6.0/envs/gsi-addon-dev-rocky8/install/modulefiles/Core") +prepend_path("MODULEPATH", "/contrib/spack-stack/spack-stack-1.6.0/envs/gsi-addon-intel/install/modulefiles/Core") local stack_intel_ver=os.getenv("stack_intel_ver") or "2021.5.0" local python_ver=os.getenv("python_ver") or "3.11.6" diff --git a/parm/archive/enkf.yaml.j2 b/parm/archive/enkf.yaml.j2 index a95046d4d6..f5662bc687 100644 --- a/parm/archive/enkf.yaml.j2 +++ b/parm/archive/enkf.yaml.j2 @@ -4,15 +4,15 @@ enkf: required: # Logs {% for mem in range(1, nmem_ens + 1) %} - - "logs/{{ cycle_YMDH }}/{{ RUN }}fcst_mem{{ '%03d' % mem }}.log" + - "logs/{{ cycle_YMDH }}/{{ RUN }}_fcst_mem{{ '%03d' % mem }}.log" {% endfor %} {% for fhr in range(fhmin, fhmax + 1, fhout) %} - - "logs/{{ cycle_YMDH }}/{{ RUN }}epos{{ '%03d' % (fhr - fhmin) }}.log" + - "logs/{{ cycle_YMDH }}/{{ RUN }}_epos{{ '%03d' % (fhr - fhmin) }}.log" {% endfor %} - - "logs/{{ cycle_YMDH }}/{{ RUN }}echgres.log" - - "logs/{{ cycle_YMDH }}/{{ RUN }}esfc.log" + - "logs/{{ cycle_YMDH }}/{{ RUN }}_echgres.log" + - "logs/{{ cycle_YMDH }}/{{ RUN }}_esfc.log" {% for grp in range(IAUFHRS | length) %} - - "logs/{{ cycle_YMDH }}/{{ RUN }}ecen{{ '%03d' % grp }}.log" + - "logs/{{ cycle_YMDH }}/{{ RUN }}_ecen{{ '%03d' % grp }}.log" {% endfor %} {% if lobsdiag_forenkf %} @@ -27,13 +27,13 @@ enkf: {% else %} {% set steps = ["eobs", "eupd"] %} {% for mem in range(1, nmem_ens + 1) %} - {% do steps.append("eomg_mem{{ '%03d' % mem }}") %} + {% do steps.append("eomg_mem" ~ '%03d' % mem) %} {% endfor %} {% endif %} {% endif %} {% for step in steps %} - - "logs/{{ cycle_YMDH }}/{{ RUN }}{{ step }}.log" + - "logs/{{ cycle_YMDH }}/{{ RUN }}_{{ step }}.log" {% endfor %} # Ensemble mean and spread diff --git a/parm/archive/gdas.yaml.j2 b/parm/archive/gdas.yaml.j2 index 56e47e595a..1e9597ba1c 100644 --- a/parm/archive/gdas.yaml.j2 +++ b/parm/archive/gdas.yaml.j2 @@ -5,33 +5,33 @@ gdas: required: {% if MODE == "cycled" %} # Cycled logs - - "logs/{{ cycle_YMDH }}/{{ RUN }}atmanlprod.log" - - "logs/{{ cycle_YMDH }}/{{ RUN }}prep.log" + - "logs/{{ cycle_YMDH }}/{{ RUN }}_atmanlprod.log" + - "logs/{{ cycle_YMDH }}/{{ RUN }}_prep.log" {% if DO_JEDIATMVAR %} - - "logs/{{ cycle_YMDH }}/{{ RUN }}prepatmiodaobs.log" - - "logs/{{ cycle_YMDH }}/{{ RUN }}atmanlinit.log" - - "logs/{{ cycle_YMDH }}/{{ RUN }}atmanlprod.log" - - "logs/{{ cycle_YMDH }}/{{ RUN }}atmanlfinal.log" - - "logs/{{ cycle_YMDH }}/{{ RUN }}atmanlfv3inc.log" - - "logs/{{ cycle_YMDH }}/{{ RUN }}atmanlvar.log" + - "logs/{{ cycle_YMDH }}/{{ RUN }}_prepatmiodaobs.log" + - "logs/{{ cycle_YMDH }}/{{ RUN }}_atmanlinit.log" + - "logs/{{ cycle_YMDH }}/{{ RUN }}_atmanlprod.log" + - "logs/{{ cycle_YMDH }}/{{ RUN }}_atmanlfinal.log" + - "logs/{{ cycle_YMDH }}/{{ RUN }}_atmanlfv3inc.log" + - "logs/{{ cycle_YMDH }}/{{ RUN }}_atmanlvar.log" {% else %} - - "logs/{{ cycle_YMDH }}/{{ RUN }}anal.log" - - "logs/{{ cycle_YMDH }}/{{ RUN }}analdiag.log" + - "logs/{{ cycle_YMDH }}/{{ RUN }}_anal.log" + - "logs/{{ cycle_YMDH }}/{{ RUN }}_analdiag.log" {% endif %} - - "logs/{{ cycle_YMDH }}/{{ RUN }}atmanlupp.log" + - "logs/{{ cycle_YMDH }}/{{ RUN }}_atmanlupp.log" {% if DO_JEDIOCNVAR %} - - "logs/{{ cycle_YMDH }}/{{ RUN }}prepoceanobs.log" - - "logs/{{ cycle_YMDH }}/{{ RUN }}ocnanalprep.log" - - "logs/{{ cycle_YMDH }}/{{ RUN }}marinebmat.log" - - "logs/{{ cycle_YMDH }}/{{ RUN }}ocnanalrun.log" - - "logs/{{ cycle_YMDH }}/{{ RUN }}ocnanalpost.log" - - "logs/{{ cycle_YMDH }}/{{ RUN }}ocnanalchkpt.log" + - "logs/{{ cycle_YMDH }}/{{ RUN }}_prepoceanobs.log" + - "logs/{{ cycle_YMDH }}/{{ RUN }}_marineanlinit.log" + - "logs/{{ cycle_YMDH }}/{{ RUN }}_marinebmat.log" + - "logs/{{ cycle_YMDH }}/{{ RUN }}_marineanlvar.log" + - "logs/{{ cycle_YMDH }}/{{ RUN }}_marineanlfinal.log" + - "logs/{{ cycle_YMDH }}/{{ RUN }}_marineanlchkpt.log" {% if DOHYBVAR %} - - "logs/{{ cycle_YMDH }}/{{ RUN }}ocnanalecen.log" + - "logs/{{ cycle_YMDH }}/{{ RUN }}_ocnanalecen.log" {% endif %} {% endif %} {% if DO_VRFY_OCEANDA %} - - "logs/{{ cycle_YMDH }}/{{ RUN }}ocnanalvrfy.log" + - "logs/{{ cycle_YMDH }}/{{ RUN }}_ocnanalvrfy.log" {% endif %} # Analysis GRIB2 (sub-sampled) data @@ -85,7 +85,7 @@ gdas: - "{{ COMIN_ATMOS_OZNMON | relpath(ROTDIR) }}/time/bad_pen.{{ cycle_YMDH }}" - "{{ COMIN_ATMOS_OZNMON | relpath(ROTDIR) }}/time/stdout.time.tar.gz" - "{{ COMIN_ATMOS_OZNMON | relpath(ROTDIR) }}/horiz/stdout.horiz.tar.gz" - - "logs/{{ cycle_YMDH }}/{{ RUN }}verfozn.log" + - "logs/{{ cycle_YMDH }}/{{ RUN }}_verfozn.log" {% endif %} # Radiance verification @@ -94,7 +94,7 @@ gdas: - "{{ COMIN_ATMOS_RADMON | relpath(ROTDIR) }}/radmon_bcoef.tar.gz" - "{{ COMIN_ATMOS_RADMON | relpath(ROTDIR) }}/radmon_bcor.tar.gz" - "{{ COMIN_ATMOS_RADMON | relpath(ROTDIR) }}/radmon_time.tar.gz" - - "logs/{{ cycle_YMDH }}/{{ RUN }}verfrad.log" + - "logs/{{ cycle_YMDH }}/{{ RUN }}_verfrad.log" {% endif %} # Minimization monitor @@ -104,18 +104,18 @@ gdas: - "{{ COMIN_ATMOS_MINMON | relpath(ROTDIR) }}/{{ cycle_YMDH }}.gnorms.ieee_d" - "{{ COMIN_ATMOS_MINMON | relpath(ROTDIR) }}/{{ cycle_YMDH }}.reduction.ieee_d" - "{{ COMIN_ATMOS_MINMON | relpath(ROTDIR) }}/gnorm_data.txt" - - "logs/{{ cycle_YMDH }}/{{ RUN }}vminmon.log" + - "logs/{{ cycle_YMDH }}/{{ RUN }}_vminmon.log" {% endif %} {% endif %} # End of cycled data # Forecast and post logs - - "logs/{{ cycle_YMDH }}/{{ RUN }}fcst_seg0.log" + - "logs/{{ cycle_YMDH }}/{{ RUN }}_fcst_seg0.log" {% for fhr in range(0, FHMAX + 1, 3) %} {% set fhr3 = '%03d' % fhr %} - - "logs/{{ cycle_YMDH }}/{{ RUN }}atmos_prod_f{{ fhr3 }}.log" + - "logs/{{ cycle_YMDH }}/{{ RUN }}_atmos_prod_f{{ fhr3 }}.log" {% if not WRITE_DOPOST %} - - "logs/{{ cycle_YMDH }}/{{ RUN }}atmos_upp_f{{ fhr3 }}.log" + - "logs/{{ cycle_YMDH }}/{{ RUN }}_atmos_upp_f{{ fhr3 }}.log" {% endif %} ## not WRITE_DOPOST # Forecast GRIB2 data - "{{ COMIN_ATMOS_GRIB_0p25 | relpath(ROTDIR) }}/{{ head }}pgrb2.0p25.f{{ fhr3 }}" diff --git a/parm/archive/gdas_restarta.yaml.j2 b/parm/archive/gdas_restarta.yaml.j2 index 9d86292065..fc5ce9478d 100644 --- a/parm/archive/gdas_restarta.yaml.j2 +++ b/parm/archive/gdas_restarta.yaml.j2 @@ -32,6 +32,8 @@ gdas_restarta: - "{{ COMIN_ATMOS_ANALYSIS | relpath(ROTDIR) }}/{{ head }}abias_int" - "{{ COMIN_ATMOS_ANALYSIS | relpath(ROTDIR) }}/{{ head }}dtfanl.nc" - "{{ COMIN_ATMOS_ANALYSIS | relpath(ROTDIR) }}/{{ head }}loginc.txt" + {% else %} + - "{{ COMIN_ATMOS_ANALYSIS | relpath(ROTDIR) }}/{{ head }}rad_varbc_params.tar" {% endif %} # Snow surface data diff --git a/parm/archive/gefs_arcdir.yaml.j2 b/parm/archive/gefs_arcdir.yaml.j2 index a59a0e1a8f..d1008bc5c4 100644 --- a/parm/archive/gefs_arcdir.yaml.j2 +++ b/parm/archive/gefs_arcdir.yaml.j2 @@ -17,11 +17,17 @@ {% set COMIN_ATMOS_ENSSTAT_1p00 = COM_ATMOS_GRIB_GRID_TMPL | replace_tmpl(tmpl_dict) %} +{% if REPLAY_ICS %} + {% set ofst_hr = FHOUT_GFS %} +{% else %} + {% set ofst_hr = FHMIN_GFS %} +{% endif %} + # Select ensstat files to copy to the arcdir {% if RUN == "gefs" %} {% set ensstat_files = [] %} {% if path_exists(COMIN_ATMOS_ENSSTAT_1p00) %} - {% for fhr in range(FHMIN_GFS, FHMAX_GFS + FHOUT_GFS, FHOUT_GFS) %} + {% for fhr in range(ofst_hr, FHMAX_GFS + FHOUT_GFS, FHOUT_GFS) %} {% do ensstat_files.append([COMIN_ATMOS_ENSSTAT_1p00 ~ "/" ~ head ~ "mean.pres_." ~ "1p00" ~ ".f" ~ '%03d'|format(fhr) ~ ".grib2", GEFS_ARCH]) %} diff --git a/parm/archive/gfsa.yaml.j2 b/parm/archive/gfsa.yaml.j2 index 226a7178fa..dcf059d871 100644 --- a/parm/archive/gfsa.yaml.j2 +++ b/parm/archive/gfsa.yaml.j2 @@ -6,7 +6,7 @@ gfsa: # Logs # TODO explicitly name all logs to include {% for log in glob("logs/" ~ cycle_YMDH ~ "/gfs*.log") %} - {% if not "gfsarch.log" in log %} + {% if not "gfs_arch.log" in log %} - "{{ log }}" {% endif %} {% endfor %} diff --git a/parm/config/gefs/config.atmos_products b/parm/config/gefs/config.atmos_products index 4a0fb8b49f..73614ba08e 100644 --- a/parm/config/gefs/config.atmos_products +++ b/parm/config/gefs/config.atmos_products @@ -16,7 +16,12 @@ export INTERP_ATMOS_MASTERSH="${USHgfs}/interp_atmos_master.sh" export INTERP_ATMOS_SFLUXSH="${USHgfs}/interp_atmos_sflux.sh" export downset=2 -export FHOUT_PGBS=${FHOUT_GFS:-3} # Output frequency of supplemental gfs pgb file at 1.0 and 0.5 deg + +if [[ "${FHMAX_HF_GFS:-0}" == "0" ]]; then + export FHOUT_PGBS=${FHOUT_GFS:-3} # Output frequency of supplemental gfs pgb file at 1.0 and 0.5 deg +else + export FHOUT_PGBS=${FHOUT_HF_GFS:-1} +fi export FLXGF="NO" # Create interpolated sflux.1p00 file # paramlist files for the different forecast hours and downsets diff --git a/parm/config/gefs/config.base b/parm/config/gefs/config.base index 883957ed0c..05aabaa323 100644 --- a/parm/config/gefs/config.base +++ b/parm/config/gefs/config.base @@ -48,6 +48,9 @@ export NOSCRUB="@NOSCRUB@" # Base directories for various builds export BASE_GIT="@BASE_GIT@" +# Base directory for staged data +export BASE_DATA="@BASE_DATA@" + # Toggle to turn on/off GFS downstream processing. export DO_BUFRSND="@DO_BUFRSND@" # BUFR sounding products export DO_GEMPAK="@DO_GEMPAK@" # GEMPAK products @@ -224,7 +227,17 @@ export FHOUT_OCN=3 export FHOUT_ICE=3 # GFS cycle info -export gfs_cyc=@gfs_cyc@ # 0: no GFS cycle, 1: 00Z only, 2: 00Z and 12Z only, 4: all 4 cycles. +export INTERVAL_GFS=@INTERVAL_GFS@ # Frequency of GFS forecast +export SDATE_GFS=@SDATE_GFS@ + +# set variables needed for use with REPLAY ICs +export REPLAY_ICS=@REPLAY_ICS@ +if [[ "${REPLAY_ICS:-NO}" == "YES" ]]; then + export OFFSET_START_HOUR=$(( assim_freq / 2 )) +else + export OFFSET_START_HOUR=0 +fi + # GFS output and frequency export FHMIN_GFS=0 @@ -235,19 +248,14 @@ export FCST_SEGMENTS="${FHMIN_GFS},${breakpnts:+${breakpnts},}${FHMAX_GFS}" export FHOUT_GFS=6 export FHMAX_HF_GFS=@FHMAX_HF_GFS@ -export FHOUT_HF_GFS=1 -export FHOUT_OCN_GFS=6 -export FHOUT_ICE_GFS=6 -export FHMIN_WAV=0 +export FHOUT_HF_GFS=@FHOUT_HF_GFS@ +export FHOUT_OCN_GFS=@FHOUT_OCN_GFS@ +export FHOUT_ICE_GFS=@FHOUT_ICE_GFS@ +export FHMIN_WAV=${OFFSET_START_HOUR:-0} export FHOUT_WAV=3 export FHMAX_HF_WAV=120 export FHOUT_HF_WAV=1 export FHMAX_WAV=${FHMAX_GFS} -if (( gfs_cyc != 0 )); then - export STEP_GFS=$(( 24 / gfs_cyc )) -else - export STEP_GFS="0" -fi export ILPOST=1 # gempak output frequency up to F120 export FHMIN_ENKF=${FHMIN_GFS} @@ -287,20 +295,9 @@ export NMEM_ENS=@NMEM_ENS@ export ENSMEM=${ENSMEM:-"000"} export MEMDIR="mem${ENSMEM}" -# initialize ocean ensemble members with perturbations -# if true, only occurs for members greater than zero -export REPLAY_ICS=@REPLAY_ICS@ -if [[ "${REPLAY_ICS:-NO}" == "YES" ]]; then - export OFFSET_START_HOUR=$(( assim_freq / 2 )) -else - export OFFSET_START_HOUR=0 -fi - export DOIAU="NO" # While we are not doing IAU, we may want to warm start w/ IAU in the future -# Check if cycle is cold starting -if [[ "${EXP_WARM_START}" = ".false." ]]; then - export IAU_FHROT=${OFFSET_START_HOUR} -else +# Check if cycle is warm starting with IAU +if [[ "${EXP_WARM_START}" = ".true." ]]; then if [[ "${DOIAU}" = "YES" ]]; then export IAU_FHROT=3 else diff --git a/parm/config/gefs/config.efcs b/parm/config/gefs/config.efcs index 807ed66d48..9bd55afa54 100644 --- a/parm/config/gefs/config.efcs +++ b/parm/config/gefs/config.efcs @@ -46,10 +46,6 @@ export SKEB_LSCALE="500.E3,1000.E3,2000.E3,2000.E3,2000.E3" export SKEBNORM=1 export SKEB_NPASS=30 export SKEB_VDOF=5 -export DO_SHUM="YES" -export SHUM=0.005 -export SHUM_TAU=21600. -export SHUM_LSCALE=500000. export DO_SPPT="YES" export SPPT="0.56,0.28,0.14,0.056,0.028" export SPPT_TAU="2.16E4,2.592E5,2.592E6,7.776E6,3.1536E7" diff --git a/parm/config/gefs/config.extractvars b/parm/config/gefs/config.extractvars index cc93fcf5e0..7f1166a869 100644 --- a/parm/config/gefs/config.extractvars +++ b/parm/config/gefs/config.extractvars @@ -14,7 +14,7 @@ export compress_ice=1 #1: Compress extracted ice product, 0: Do not compress ext export ocnres="1p00" # Resolution of ocean products export iceres="native" # Resolution of ice products -export wavres="0p25" # Resolution of wave products +export wavres="${waveGRD:4:1}p${waveGRD:5:2}" # Resolution of wave products export depthvar_name="z_l" # Name of depth variable in NetCDF ocean products export zmin="0." # Minimum depth to extract from NetCDF ocean products diff --git a/parm/config/gefs/yaml/defaults.yaml b/parm/config/gefs/yaml/defaults.yaml index 382e60ee12..b5870b3e7e 100644 --- a/parm/config/gefs/yaml/defaults.yaml +++ b/parm/config/gefs/yaml/defaults.yaml @@ -11,7 +11,11 @@ base: DO_EXTRACTVARS: "NO" FHMAX_GFS: 120 FHMAX_HF_GFS: 0 + FHOUT_HF_GFS: 1 FCST_BREAKPOINTS: "48" REPLAY_ICS: "NO" USE_OCN_PERTURB_FILES: "false" + FHOUT_OCN_GFS: 6 + FHOUT_ICE_GFS: 6 + HPSSARCH: "NO" LOCALARCH: "NO" diff --git a/parm/config/gfs/config.aero b/parm/config/gfs/config.aero index 2fae019574..f49593a439 100644 --- a/parm/config/gfs/config.aero +++ b/parm/config/gfs/config.aero @@ -24,7 +24,7 @@ case ${machine} in AERO_INPUTS_DIR="/gpfs/f5/epic/proj-shared/global/glopara/data/gocart_emissions" ;; "JET") - AERO_INPUTS_DIR="/lfs4/HFIP/hfv3gfs/glopara/data/gocart_emissions" + AERO_INPUTS_DIR="/lfs5/HFIP/hfv3gfs/glopara/data/gocart_emissions" ;; *) echo "FATAL ERROR: Machine ${machine} unsupported for aerosols" diff --git a/parm/config/gfs/config.base b/parm/config/gfs/config.base index 27fcbdd055..ccb05abe88 100644 --- a/parm/config/gfs/config.base +++ b/parm/config/gfs/config.base @@ -63,6 +63,9 @@ export NOSCRUB="@NOSCRUB@" # Base directories for various builds export BASE_GIT="@BASE_GIT@" +# Base directory for staged data +export BASE_DATA="@BASE_DATA@" + # Toggle to turn on/off GFS downstream processing. export DO_GOES="@DO_GOES@" # GOES products export DO_BUFRSND="@DO_BUFRSND@" # BUFR sounding products @@ -280,7 +283,8 @@ export FHOUT_ICE=3 export EUPD_CYC="@EUPD_CYC@" # GFS cycle info -export gfs_cyc=@gfs_cyc@ # 0: no GFS cycle, 1: 00Z only, 2: 00Z and 12Z only, 4: all 4 cycles. +export INTERVAL_GFS=@INTERVAL_GFS@ # Frequency of GFS forecast +export SDATE_GFS=@SDATE_GFS@ # GFS output and frequency export FHMIN_GFS=0 @@ -299,11 +303,7 @@ export FHMAX_HF_WAV=120 export FHOUT_HF_WAV=1 export FHMAX_WAV=${FHMAX:-9} export FHMAX_WAV_GFS=${FHMAX_GFS} -if (( gfs_cyc != 0 )); then - export STEP_GFS=$(( 24 / gfs_cyc )) -else - export STEP_GFS="0" -fi + # TODO: Change gempak to use standard out variables (#2348) export ILPOST=${FHOUT_HF_GFS} # gempak output frequency up to F120 if (( FHMAX_HF_GFS < 120 )); then diff --git a/parm/config/gfs/config.marineanl b/parm/config/gfs/config.marineanl new file mode 100644 index 0000000000..a19fc015e2 --- /dev/null +++ b/parm/config/gfs/config.marineanl @@ -0,0 +1,20 @@ +#!/bin/bash + +########## config.marineanl ########## +# configuration common to all ocean analysis tasks + +echo "BEGIN: config.marineanl" + +export MARINE_OBS_YAML_DIR="${PARMgfs}/gdas/soca/obs/config" +export MARINE_OBS_LIST_YAML=@SOCA_OBS_LIST@ +export SOCA_INPUT_FIX_DIR=@SOCA_INPUT_FIX_DIR@ +export SOCA_NINNER=@SOCA_NINNER@ +export DOMAIN_STACK_SIZE=116640000 #TODO: Make the stack size resolution dependent +export SOCA_ENS_BKG_STAGE_YAML_TMPL="${PARMgfs}/gdas/soca/soca_ens_bkg_stage.yaml.j2" +export SOCA_FIX_YAML_TMPL="${PARMgfs}/gdas/soca/soca_fix_stage_${OCNRES}.yaml.j2" +export MARINE_UTILITY_YAML_TMPL="${PARMgfs}/gdas/soca/soca_utils_stage.yaml.j2" +export MARINE_ENSDA_STAGE_BKG_YAML_TMPL="${PARMgfs}/gdas/soca/ensda/stage_ens_mem.yaml.j2" +export MARINE_DET_STAGE_BKG_YAML_TMPL="${PARMgfs}/gdas/soca/soca_det_bkg_stage.yaml.j2" +export MARINE_JCB_GDAS_ALGO="${PARMgfs}/gdas/jcb-gdas/algorithm/marine" + +echo "END: config.marineanl" diff --git a/parm/config/gfs/config.marineanlchkpt b/parm/config/gfs/config.marineanlchkpt new file mode 100644 index 0000000000..7dd6ff79b2 --- /dev/null +++ b/parm/config/gfs/config.marineanlchkpt @@ -0,0 +1,11 @@ +#!/bin/bash + +########## config.marineanlchkpt ########## +# Marine Analysis specific + +echo "BEGIN: config.marineanlchkpt" + +# Get task specific resources +. "${EXPDIR}/config.resources" marineanlchkpt + +echo "END: config.marineanlchkpt" diff --git a/parm/config/gfs/config.marineanlfinal b/parm/config/gfs/config.marineanlfinal new file mode 100644 index 0000000000..ccde289088 --- /dev/null +++ b/parm/config/gfs/config.marineanlfinal @@ -0,0 +1,10 @@ +#!/bin/bash + +########## config.marineanlfinal ########## +# Post Ocn Analysis specific + +echo "BEGIN: config.marineanlfinal" + +# Get task specific resources +. "${EXPDIR}/config.resources" marineanlfinal +echo "END: config.marineanlfinal" diff --git a/parm/config/gfs/config.marineanlinit b/parm/config/gfs/config.marineanlinit new file mode 100644 index 0000000000..01489fc0b8 --- /dev/null +++ b/parm/config/gfs/config.marineanlinit @@ -0,0 +1,10 @@ +#!/bin/bash + +########## config.marineanlinit ########## +# Pre Marine Analysis specific + +echo "BEGIN: config.marineanlinit" + +# Get task specific resources +. "${EXPDIR}/config.resources" marineanlinit +echo "END: config.marineanlinit" diff --git a/parm/config/gfs/config.marineanlvar b/parm/config/gfs/config.marineanlvar new file mode 100644 index 0000000000..5ed6d444eb --- /dev/null +++ b/parm/config/gfs/config.marineanlvar @@ -0,0 +1,11 @@ +#!/bin/bash + +########## config.marineanlvar ########## +# Marine Analysis specific + +echo "BEGIN: config.marineanlvar" + +# Get task specific resources +. "${EXPDIR}/config.resources" marineanlvar + +echo "END: config.marineanlvar" diff --git a/parm/config/gfs/config.marinebmat b/parm/config/gfs/config.marinebmat index d88739dced..00352737d0 100644 --- a/parm/config/gfs/config.marinebmat +++ b/parm/config/gfs/config.marinebmat @@ -8,4 +8,12 @@ echo "BEGIN: config.marinebmat" # Get task specific resources . "${EXPDIR}/config.resources" marinebmat +export BERROR_DIAGB_YAML="${PARMgfs}/gdas/soca/berror/soca_diagb.yaml.j2" +export BERROR_VTSCALES_YAML="${PARMgfs}/gdas/soca/berror/soca_vtscales.yaml.j2" +export BERROR_DIFFV_YAML="${PARMgfs}/gdas/soca/berror/soca_parameters_diffusion_vt.yaml.j2" +export BERROR_HZSCALES_YAML="${PARMgfs}/gdas/soca/berror/soca_setcorscales.yaml" +export BERROR_DIFFH_YAML="${PARMgfs}/gdas/soca/berror/soca_parameters_diffusion_hz.yaml.j2" +export BERROR_ENS_RECENTER_YAML="${PARMgfs}/gdas/soca/berror/soca_ensb.yaml.j2" +export BERROR_HYB_WEIGHTS_YAML="${PARMgfs}/gdas/soca/berror/soca_ensweights.yaml.j2" + echo "END: config.marinebmat" diff --git a/parm/config/gfs/config.ocnanal b/parm/config/gfs/config.ocnanal deleted file mode 100644 index 4d58f2dedf..0000000000 --- a/parm/config/gfs/config.ocnanal +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/bash - -########## config.ocnanal ########## -# configuration common to all ocean analysis tasks - -echo "BEGIN: config.ocnanal" - -export OBS_YAML_DIR="${HOMEgfs}/sorc/gdas.cd/parm/soca/obs/config" -export OBS_LIST=@SOCA_OBS_LIST@ # TODO(GA): doesn't look necessary as is to have -export OBS_YAML="${OBS_LIST}" # OBS_LIST and OBS_YAML pick one or add logic -export SOCA_INPUT_FIX_DIR=@SOCA_INPUT_FIX_DIR@ -export SOCA_NINNER=@SOCA_NINNER@ -export DOMAIN_STACK_SIZE=116640000 #TODO: Make the stack size resolution dependent -export SOCA_ENS_BKG_STAGE_YAML_TMPL="${PARMgfs}/gdas/soca/soca_ens_bkg_stage.yaml.j2" -export SOCA_FIX_YAML_TMPL="${PARMgfs}/gdas/soca/soca_fix_stage_${OCNRES}.yaml.j2" - -export JEDI_BIN=${HOMEgfs}/sorc/gdas.cd/build/bin # TODO(GA): remove once analysis "run" - # and "checkpoint" are refactored - -echo "END: config.ocnanal" diff --git a/parm/config/gfs/config.ocnanalchkpt b/parm/config/gfs/config.ocnanalchkpt deleted file mode 100644 index c059fdba42..0000000000 --- a/parm/config/gfs/config.ocnanalchkpt +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/bash - -########## config.ocnanalchkpt ########## -# Ocn Analysis specific - -echo "BEGIN: config.ocnanalchkpt" - -# Get task specific resources -. "${EXPDIR}/config.resources" ocnanalchkpt - -echo "END: config.ocnanalchkpt" diff --git a/parm/config/gfs/config.ocnanalpost b/parm/config/gfs/config.ocnanalpost deleted file mode 100644 index bc4d945865..0000000000 --- a/parm/config/gfs/config.ocnanalpost +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash - -########## config.ocnanalpost ########## -# Post Ocn Analysis specific - -echo "BEGIN: config.ocnanalpost" - -# Get task specific resources -. "${EXPDIR}/config.resources" ocnanalpost -echo "END: config.ocnanalpost" diff --git a/parm/config/gfs/config.ocnanalprep b/parm/config/gfs/config.ocnanalprep deleted file mode 100644 index 225eb089c3..0000000000 --- a/parm/config/gfs/config.ocnanalprep +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash - -########## config.ocnanalprep ########## -# Pre Ocn Analysis specific - -echo "BEGIN: config.ocnanalprep" - -# Get task specific resources -. "${EXPDIR}/config.resources" ocnanalprep -echo "END: config.ocnanalprep" diff --git a/parm/config/gfs/config.ocnanalrun b/parm/config/gfs/config.ocnanalrun deleted file mode 100644 index 5345b6c684..0000000000 --- a/parm/config/gfs/config.ocnanalrun +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/bash - -########## config.ocnanalrun ########## -# Ocn Analysis specific - -echo "BEGIN: config.ocnanalrun" - -# Get task specific resources -. "${EXPDIR}/config.resources" ocnanalrun - -echo "END: config.ocnanalrun" diff --git a/parm/config/gfs/config.prepoceanobs b/parm/config/gfs/config.prepoceanobs index 746ce79257..0963a5c42d 100644 --- a/parm/config/gfs/config.prepoceanobs +++ b/parm/config/gfs/config.prepoceanobs @@ -8,7 +8,7 @@ export OCNOBS2IODAEXEC=${HOMEgfs}/sorc/gdas.cd/build/bin/gdas_obsprovider2ioda.x export SOCA_INPUT_FIX_DIR=@SOCA_INPUT_FIX_DIR@ -export OBS_YAML_DIR="${PARMgfs}/gdas/soca/obs/config" +export MARINE_OBS_YAML_DIR="${PARMgfs}/gdas/soca/obs/config" export OBSPREP_YAML=@OBSPREP_YAML@ export OBS_LIST=@SOCA_OBS_LIST@ export OBS_YAML=${OBS_LIST} diff --git a/parm/config/gfs/config.resources b/parm/config/gfs/config.resources index 0479543ebc..79dbb487db 100644 --- a/parm/config/gfs/config.resources +++ b/parm/config/gfs/config.resources @@ -26,7 +26,7 @@ if (( $# != 1 )); then echo "waveinit waveprep wavepostsbs wavepostbndpnt wavepostbndpntbll wavepostpnt" echo "wavegempak waveawipsbulls waveawipsgridded" echo "postsnd awips gempak npoess" - echo "ocnanalprep prepoceanobs marinebmat ocnanalrun ocnanalecen marineanalletkf ocnanalchkpt ocnanalpost ocnanalvrfy" + echo "marineanlinit prepoceanobs marinebmat marineanlvar ocnanalecen marineanalletkf marineanlchkpt marineanlfinal ocnanalvrfy" exit 1 fi @@ -506,7 +506,7 @@ case ${step} in memory="3072M" ;; - "ocnanalprep") + "marineanlinit") walltime="00:10:00" ntasks=1 threads_per_task=1 @@ -541,7 +541,7 @@ case ${step} in tasks_per_node=$(( max_tasks_per_node / threads_per_task )) ;; - "ocnanalrun") + "marineanlvar") ntasks=16 case ${OCNRES} in "025") @@ -632,7 +632,7 @@ case ${step} in ;; - "ocnanalchkpt") + "marineanlchkpt") walltime="00:10:00" ntasks=1 threads_per_task=1 @@ -656,7 +656,7 @@ case ${step} in esac ;; - "ocnanalpost") + "marineanlfinal") walltime="00:30:00" ntasks=${max_tasks_per_node} threads_per_task=1 @@ -1214,12 +1214,10 @@ case ${step} in "C768") tasks_per_node=21 threads_per_task=6 - memory="23GB" ;; "C1152") tasks_per_node=9 threads_per_task=14 - memory="50GB" ;; *) tasks_per_node=21 diff --git a/parm/config/gfs/config.resources.ORION b/parm/config/gfs/config.resources.ORION index d761df7b73..461b6f14f7 100644 --- a/parm/config/gfs/config.resources.ORION +++ b/parm/config/gfs/config.resources.ORION @@ -23,6 +23,16 @@ case ${step} in # Remove this block once the GSI issue is resolved. export walltime="00:45:00" ;; + "atmanlvar") + # Run on 8 nodes for memory requirement + export tasks_per_node=8 + export walltime="00:45:00" + ;; + "atmensanlobs") + # Run on 8 nodes for memory requirement + export tasks_per_node=8 + export walltime="00:45:00" + ;; *) ;; esac diff --git a/parm/config/gfs/config.wave b/parm/config/gfs/config.wave index db4eb9f708..ea68508547 100644 --- a/parm/config/gfs/config.wave +++ b/parm/config/gfs/config.wave @@ -117,7 +117,7 @@ if [[ "${RUN}" == "gdas" ]]; then export WAVNCYC=4 export WAVHCYC=${assim_freq:-6} export FHMAX_WAV_CUR=48 # RTOFS forecasts only out to 8 days -elif [[ ${gfs_cyc} -ne 0 ]]; then +elif (( INTERVAL_GFS > 0 )); then export WAVHCYC=${assim_freq:-6} export FHMAX_WAV_CUR=192 # RTOFS forecasts only out to 8 days else diff --git a/parm/config/gfs/yaml/defaults.yaml b/parm/config/gfs/yaml/defaults.yaml index 05e1b24012..dfc67d1237 100644 --- a/parm/config/gfs/yaml/defaults.yaml +++ b/parm/config/gfs/yaml/defaults.yaml @@ -52,7 +52,7 @@ snowanl: IO_LAYOUT_X: 1 IO_LAYOUT_Y: 1 -ocnanal: +marineanl: SOCA_INPUT_FIX_DIR: "${FIXgfs}/gdas/soca/72x35x25/soca" SOCA_OBS_LIST: "${PARMgfs}/gdas/soca/obs/obs_list.yaml" # TODO: This is also repeated in oceanprepobs SOCA_NINNER: 100 @@ -61,4 +61,4 @@ prepoceanobs: SOCA_INPUT_FIX_DIR: "${FIXgfs}/gdas/soca/72x35x25/soca" SOCA_OBS_LIST: "${PARMgfs}/gdas/soca/obs/obs_list.yaml" # TODO: This is also repeated in ocnanal OBSPREP_YAML: "${PARMgfs}/gdas/soca/obsprep/obsprep_config.yaml" - DMPDIR: "/scratch1/NCEPDEV/global/glopara/data/experimental_obs" + DMPDIR: "${BASE_DATA}/experimental_obs" diff --git a/parm/product/gefs.0p25.fFFF.paramlist.a.txt b/parm/product/gefs.0p25.fFFF.paramlist.a.txt index a4a3ace385..303752ac17 100644 --- a/parm/product/gefs.0p25.fFFF.paramlist.a.txt +++ b/parm/product/gefs.0p25.fFFF.paramlist.a.txt @@ -12,13 +12,14 @@ :CSNOW:surface: :CFRZR:surface: :CICEP:surface: +:FDNSSTMP:surface: :PWAT:entire atmosphere (considered as a single layer): :CAPE:180-0 mb above ground: :CAPE:surface: :CIN:180-0 mb above ground: :CIN:surface: :HLCY:3000-0 m above ground: -:TCDC:entire atmosphere: +:TCDC:entire atmosphere (considered as a single layer): :WEASD:surface: :SNOD:surface: :ULWRF:top of atmosphere: diff --git a/parm/product/gefs.0p25.fFFF.paramlist.b.txt b/parm/product/gefs.0p25.fFFF.paramlist.b.txt index f7fdb73ddf..ccad9da4d0 100644 --- a/parm/product/gefs.0p25.fFFF.paramlist.b.txt +++ b/parm/product/gefs.0p25.fFFF.paramlist.b.txt @@ -33,32 +33,32 @@ :CAPE:255-0 mb above ground: :CDUVB:surface: :CIN:255-0 mb above ground: -:CLWMR:1000 mb: -:CLWMR:100 mb: -:CLWMR:10 mb: -:CLWMR:150 mb: -:CLWMR:200 mb: -:CLWMR:20 mb: -:CLWMR:250 mb: -:CLWMR:300 mb: -:CLWMR:30 mb: -:CLWMR:350 mb: -:CLWMR:400 mb: -:CLWMR:450 mb: -:CLWMR:500 mb: -:CLWMR:50 mb: -:CLWMR:550 mb: -:CLWMR:600 mb: -:CLWMR:650 mb: -:CLWMR:700 mb: -:CLWMR:70 mb: -:CLWMR:750 mb: -:CLWMR:800 mb: -:CLWMR:850 mb: -:CLWMR:900 mb: -:CLWMR:925 mb: -:CLWMR:950 mb: -:CLWMR:975 mb: +:CLMR:1000 mb: +:CLMR:100 mb: +:CLMR:10 mb: +:CLMR:150 mb: +:CLMR:200 mb: +:CLMR:20 mb: +:CLMR:250 mb: +:CLMR:300 mb: +:CLMR:30 mb: +:CLMR:350 mb: +:CLMR:400 mb: +:CLMR:450 mb: +:CLMR:500 mb: +:CLMR:50 mb: +:CLMR:550 mb: +:CLMR:600 mb: +:CLMR:650 mb: +:CLMR:700 mb: +:CLMR:70 mb: +:CLMR:750 mb: +:CLMR:800 mb: +:CLMR:850 mb: +:CLMR:900 mb: +:CLMR:925 mb: +:CLMR:950 mb: +:CLMR:975 mb: :CNWAT:surface: :CPRAT:surface: :CWAT:entire atmosphere (considered as a single layer): @@ -276,9 +276,9 @@ :TCDC:475 mb: :TCDC:boundary layer cloud layer: :TCDC:convective cloud layer: -:TCDC:high cloud layer: -:TCDC:low cloud layer: -:TCDC:middle cloud layer: +:HCDC:high cloud layer: +:LCDC:low cloud layer: +:MCDC:middle cloud layer: :TMP:0.995 sigma level: :TMP:1000 mb: :TMP:100 m above ground: @@ -346,6 +346,7 @@ :TMP:surface: :TMP:tropopause: :TOZNE:entire atmosphere (considered as a single layer): +:TSNOWP:surface: :TSOIL:0.1-0.4 m below ground: :TSOIL:0.4-1 m below ground: :TSOIL:1-2 m below ground: diff --git a/parm/stage/analysis.yaml.j2 b/parm/stage/analysis.yaml.j2 index 9a2ec5bbdf..424bf2b5fe 100644 --- a/parm/stage/analysis.yaml.j2 +++ b/parm/stage/analysis.yaml.j2 @@ -10,17 +10,10 @@ analysis: {% for mem in range(first_mem, last_mem + 1) %} {% set imem = mem - first_mem %} {% set COMOUT_ATMOS_ANALYSIS_MEM = COMOUT_ATMOS_ANALYSIS_MEM_list[imem] %} - {% for ftype in ["abias", "abias_air", "abias_int", "abias_pc", "atminc.nc", "atmi009.nc", "atmi003.nc", "radstat", "ratminc.nc", "ratmi009.nc", "ratmi003.nc"] %} + {% for ftype in ["abias", "abias_air", "abias_int", "abias_pc", "atminc.nc", "atmi009.nc", "atmi003.nc", "radstat", "ratminc.nc", "ratmi009.nc", "ratmi003.nc", "rad_varbc_params.tar"] %} {% if path_exists(ICSDIR ~ "/" ~ COMOUT_ATMOS_ANALYSIS_MEM | relpath(ROTDIR) ~ "/" ~ RUN ~ ".t" ~ current_cycle_HH ~ "z." ~ ftype) %} - ["{{ ICSDIR }}/{{ COMOUT_ATMOS_ANALYSIS_MEM | relpath(ROTDIR) }}/{{ RUN }}.t{{ current_cycle_HH }}z.{{ ftype }}", "{{ COMOUT_ATMOS_ANALYSIS_MEM }}"] {% endif %} {% endfor %} - {% if DO_JEDIATMVAR %} - {% for ftype in ["satbias.nc", "satbias_cov.nc", "tlapse.txt"] %} - {% for file in glob(ICSDIR ~ "/" ~ COMOUT_ATMOS_ANALYSIS_MEM | relpath(ROTDIR) ~ "/" ~ RUN ~ ".t" ~ current_cycle_HH ~ "z.atms_*." ~ ftype) %} - - ["{{ file }}", "{{ COMOUT_ATMOS_ANALYSIS_MEM }}"] - {% endfor %} - {% endfor %} - {% endif %} {% endfor %} # mem loop {% endif %} diff --git a/parm/stage/atmosphere_perturbation.yaml.j2 b/parm/stage/atmosphere_ens_perturbations.yaml.j2 similarity index 79% rename from parm/stage/atmosphere_perturbation.yaml.j2 rename to parm/stage/atmosphere_ens_perturbations.yaml.j2 index 0e097b71dc..d9caf58b72 100644 --- a/parm/stage/atmosphere_perturbation.yaml.j2 +++ b/parm/stage/atmosphere_ens_perturbations.yaml.j2 @@ -1,12 +1,12 @@ -atmosphere_perturbation: +atmosphere_ens_perturbation: mkdir: - {% for mem in range(first_mem, last_mem + 1) %} + {% for mem in range(first_mem + 1, last_mem + 1) %} {% set imem = mem - first_mem %} {% set COMOUT_ATMOS_ANALYSIS_MEM = COMOUT_ATMOS_ANALYSIS_MEM_list[imem] %} - "{{ COMOUT_ATMOS_ANALYSIS_MEM }}" {% endfor %} # mem loop copy: - {% for mem in range(first_mem, last_mem + 1) %} + {% for mem in range(first_mem + 1, last_mem + 1) %} {% set imem = mem - first_mem %} {% set COMOUT_ATMOS_ANALYSIS_MEM = COMOUT_ATMOS_ANALYSIS_MEM_list[imem] %} - ["{{ ICSDIR }}/{{ COMOUT_ATMOS_ANALYSIS_MEM | relpath(ROTDIR) }}/{{ m_prefix }}.fv3_perturbation.nc", "{{ COMOUT_ATMOS_ANALYSIS_MEM }}/{{ RUN }}.t{{ current_cycle_HH }}z.atminc.nc"] diff --git a/parm/stage/master_gefs.yaml.j2 b/parm/stage/master_gefs.yaml.j2 index bdd4c8de5f..2bfe3a9d58 100644 --- a/parm/stage/master_gefs.yaml.j2 +++ b/parm/stage/master_gefs.yaml.j2 @@ -116,7 +116,7 @@ {% if REPLAY_ICS %} {% filter indent(width=4) %} -{% include "atmosphere_perturbation.yaml.j2" %} +{% include "atmosphere_ens_perturbations.yaml.j2" %} {% endfilter %} {% endif %} @@ -137,7 +137,7 @@ {% endif %} {% if REPLAY_ICS %} {% filter indent(width=4) %} -{% include "ocean_replay.yaml.j2" %} +{% include "ocean_ens_perturbations.yaml.j2" %} {% endfilter %} {% endif %} {% if EXP_WARM_START %} diff --git a/parm/stage/ocean_replay.yaml.j2 b/parm/stage/ocean_ens_perturbations.yaml.j2 similarity index 79% rename from parm/stage/ocean_replay.yaml.j2 rename to parm/stage/ocean_ens_perturbations.yaml.j2 index 8b52108bec..fede3816a7 100644 --- a/parm/stage/ocean_replay.yaml.j2 +++ b/parm/stage/ocean_ens_perturbations.yaml.j2 @@ -1,12 +1,12 @@ -ocean_replay: +ocean_ens_perturbation: mkdir: - {% for mem in range(first_mem, last_mem + 1) %} + {% for mem in range(first_mem + 1, last_mem + 1) %} {% set imem = mem - first_mem %} {% set COMOUT_OCEAN_ANALYSIS_MEM = COMOUT_OCEAN_ANALYSIS_MEM_list[imem] %} - "{{ COMOUT_OCEAN_ANALYSIS_MEM }}" {% endfor %} # mem loop copy: - {% for mem in range(first_mem, last_mem + 1) %} + {% for mem in range(first_mem + 1, last_mem + 1) %} {% set imem = mem - first_mem %} {% set COMOUT_OCEAN_ANALYSIS_MEM = COMOUT_OCEAN_ANALYSIS_MEM_list[imem] %} - ["{{ ICSDIR }}/{{ COMOUT_OCEAN_ANALYSIS_MEM | relpath(ROTDIR) }}/{{ m_prefix }}.mom6_perturbation.nc", "{{ COMOUT_OCEAN_ANALYSIS_MEM }}/mom6_increment.nc"] diff --git a/scripts/exgfs_aero_init_aerosol.py b/scripts/exgfs_aero_init_aerosol.py index aed6b88647..bc4e495e42 100755 --- a/scripts/exgfs_aero_init_aerosol.py +++ b/scripts/exgfs_aero_init_aerosol.py @@ -11,13 +11,13 @@ --------- This script requires the following environment variables be set beforehand: -CDATE: Initial time in YYYYMMDDHH format -STEP_GFS: Forecast cadence (frequency) in hours -FHMAX_GFS: Forecast length in hours -RUN: Forecast phase (gfs or gdas). Currently always expected to be gfs. -ROTDIR: Rotating (COM) directory -USHgfs: Path to global-workflow `ush` directory -PARMgfs: Path to global-workflow `parm` directory +CDATE: Initial time in YYYYMMDDHH format +INTERVAL_GFS: Forecast cadence (frequency) in hours +FHMAX_GFS: Forecast length in hours +RUN: Forecast phase (gfs or gdas). Currently always expected to be gfs. +ROTDIR: Rotating (COM) directory +USHgfs: Path to global-workflow `ush` directory +PARMgfs: Path to global-workflow `parm` directory Additionally, the following data files are used: @@ -66,7 +66,7 @@ def main() -> None: # Read in environment variables and make sure they exist cdate = get_env_var("CDATE") - incr = int(get_env_var('STEP_GFS')) + incr = int(get_env_var('INTERVAL_GFS')) fcst_length = int(get_env_var('FHMAX_GFS')) run = get_env_var("RUN") rot_dir = get_env_var("ROTDIR") diff --git a/scripts/exgfs_wave_post_pnt.sh b/scripts/exgfs_wave_post_pnt.sh index 0b8874f3fb..06769303a1 100755 --- a/scripts/exgfs_wave_post_pnt.sh +++ b/scripts/exgfs_wave_post_pnt.sh @@ -252,7 +252,9 @@ source "${USHgfs}/preamble.sh" ww3_outp_spec.inp.tmpl > ww3_outp.inp ${NLN} mod_def.$waveuoutpGRD mod_def.ww3 - HMS="${cyc}0000" + #export OFFSET_START_HOUR=$( printf "%02d" ${half_assim} ) + hh=$( printf "%02d" $(( cyc + OFFSET_START_HOUR )) ) + HMS="${hh}0000" if [[ -f "${COMIN_WAVE_HISTORY}/${WAV_MOD_TAG}.out_pnt.${waveuoutpGRD}.${PDY}.${HMS}" ]]; then ${NLN} "${COMIN_WAVE_HISTORY}/${WAV_MOD_TAG}.out_pnt.${waveuoutpGRD}.${PDY}.${HMS}" \ "./out_pnt.${waveuoutpGRD}" diff --git a/scripts/exglobal_marine_analysis_checkpoint.py b/scripts/exglobal_marine_analysis_checkpoint.py new file mode 100755 index 0000000000..84b180b287 --- /dev/null +++ b/scripts/exglobal_marine_analysis_checkpoint.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python3 +# exglobal_marine_analysis_checkpoint.py +# This script creates a MarineAnalysis object +# and runs the checkpoint methods which inserts +# the seaice analysis into the CICE6 restart or +# create a soca MOM6 IAU increment +import os + +from wxflow import Logger, cast_strdict_as_dtypedict +from pygfs.task.marine_analysis import MarineAnalysis + +# Initialize root logger +logger = Logger(level='DEBUG', colored_log=True) + + +if __name__ == '__main__': + + # Take configuration from environment and cast it as python dictionary + config = cast_strdict_as_dtypedict(os.environ) + + # Create a MarineAnalysis object + MarineAnl = MarineAnalysis(config) + + # Prepare the SOCA increment for MOM6 IAU + MarineAnl.checkpoint_mom6_iau('socaincr2mom6.yaml') + + # Insert the seaice analysis into the CICE6 restarts in 2 sequential stages + MarineAnl.checkpoint_cice6('soca_2cice_arctic.yaml') + MarineAnl.checkpoint_cice6('soca_2cice_antarctic.yaml') diff --git a/scripts/exglobal_marine_analysis_finalize.py b/scripts/exglobal_marine_analysis_finalize.py new file mode 100755 index 0000000000..daa3fbb487 --- /dev/null +++ b/scripts/exglobal_marine_analysis_finalize.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python3 +# exglobal_marine_analysis_finalize.py +# This script creates an MarineAnalysis object +# and makes copies of the variational analysis output +# to the COMROOT +import os + +from wxflow import Logger, cast_strdict_as_dtypedict +from pygfs.task.marine_analysis import MarineAnalysis + +# Initialize root logger +logger = Logger(level='DEBUG', colored_log=True) + + +if __name__ == '__main__': + + # Take configuration from environment and cast it as python dictionary + config = cast_strdict_as_dtypedict(os.environ) + + # Create a MarineAnalysis object + MarineAnl = MarineAnalysis(config) + + # Make a copy of the analysis output to the COMROOT + MarineAnl.finalize() + + # Compute the observation space statistics + MarineAnl.obs_space_stats() diff --git a/scripts/exglobal_marine_analysis_initialize.py b/scripts/exglobal_marine_analysis_initialize.py new file mode 100755 index 0000000000..37e45a5b73 --- /dev/null +++ b/scripts/exglobal_marine_analysis_initialize.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python3 +# exglobal_marine_analysis_initialize.py +# This script creates an MarineAnalysis object +# and runs the initialize method +# which create and stage the runtime directory +# and create the YAML configuration +# for a global marine variational analysis +import os + +from wxflow import Logger, cast_strdict_as_dtypedict +from pygfs.task.marine_analysis import MarineAnalysis + +# Initialize root logger +logger = Logger(level='DEBUG', colored_log=True) + + +if __name__ == '__main__': + + # Take configuration from environment and cast it as python dictionary + config = cast_strdict_as_dtypedict(os.environ) + + # Create a MarineAnalysis object + MarineAnl = MarineAnalysis(config) + MarineAnl.initialize() diff --git a/scripts/exglobal_marine_analysis_variational.py b/scripts/exglobal_marine_analysis_variational.py new file mode 100755 index 0000000000..e03c56b1e5 --- /dev/null +++ b/scripts/exglobal_marine_analysis_variational.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python3 +# exglobal_marine_analysis_variational.py +# This script creates an MarineAnalysis object +# and runs the execute method +# which executes the global marine variational analysis +import os + +from wxflow import Logger, cast_strdict_as_dtypedict +from pygfs.task.marine_analysis import MarineAnalysis + +# Initialize root logger +logger = Logger(level='DEBUG', colored_log=True) + + +if __name__ == '__main__': + + # Take configuration from environment and cast it as python dictionary + config = cast_strdict_as_dtypedict(os.environ) + + # Create a MarineAnalysis object + MarineAnl = MarineAnalysis(config) + + # Run the variational application + MarineAnl.variational() diff --git a/sorc/gdas.cd b/sorc/gdas.cd index 7c1c181359..764f58cebd 160000 --- a/sorc/gdas.cd +++ b/sorc/gdas.cd @@ -1 +1 @@ -Subproject commit 7c1c181359c2c1952bab3dc1c481bbdb361aa472 +Subproject commit 764f58cebdf64f3695d89538994a50183e5884d9 diff --git a/sorc/gfs_utils.fd b/sorc/gfs_utils.fd index bd8f13d867..a00cc0949e 160000 --- a/sorc/gfs_utils.fd +++ b/sorc/gfs_utils.fd @@ -1 +1 @@ -Subproject commit bd8f13d867721e4ee28de4af437a0de4283609c3 +Subproject commit a00cc0949e2f901e73b58d54834517743916c69a diff --git a/sorc/gsi_enkf.fd b/sorc/gsi_enkf.fd index 529bb796be..9f44c8798c 160000 --- a/sorc/gsi_enkf.fd +++ b/sorc/gsi_enkf.fd @@ -1 +1 @@ -Subproject commit 529bb796bea0e490f186729cd168a91c034bb12d +Subproject commit 9f44c8798c2087aca06df8f629699632e57df431 diff --git a/sorc/gsi_monitor.fd b/sorc/gsi_monitor.fd index e1f9f21af1..278ee629e8 160000 --- a/sorc/gsi_monitor.fd +++ b/sorc/gsi_monitor.fd @@ -1 +1 @@ -Subproject commit e1f9f21af16ce912fdc2cd75c5b27094a550a0c5 +Subproject commit 278ee629e87558822e8d13b3fb3b0e16006aa856 diff --git a/sorc/link_workflow.sh b/sorc/link_workflow.sh index 270a8bb1c9..870ddc5eba 100755 --- a/sorc/link_workflow.sh +++ b/sorc/link_workflow.sh @@ -73,7 +73,7 @@ case "${machine}" in "hera") FIX_DIR="/scratch1/NCEPDEV/global/glopara/fix" ;; "orion") FIX_DIR="/work/noaa/global/glopara/fix" ;; "hercules") FIX_DIR="/work/noaa/global/glopara/fix" ;; - "jet") FIX_DIR="/lfs4/HFIP/hfv3gfs/glopara/git/fv3gfs/fix" ;; + "jet") FIX_DIR="/lfs5/HFIP/hfv3gfs/glopara/FIX/fix" ;; "s4") FIX_DIR="/data/prod/glopara/fix" ;; "gaea") FIX_DIR="/gpfs/f5/ufs-ard/world-shared/global/glopara/data/fix" ;; "noaacloud") FIX_DIR="/contrib/global-workflow-shared-data/fix" ;; @@ -275,7 +275,6 @@ if [[ -d "${HOMEgfs}/sorc/gdas.cd/build" ]]; then done fi - #------------------------------ #--add DA Monitor file (NOTE: ensure to use correct version) #------------------------------ @@ -372,6 +371,7 @@ if [[ -d "${HOMEgfs}/sorc/gdas.cd/build" ]]; then "gdas_incr_handler.x" \ "gdas_obsprovider2ioda.x" \ "gdas_socahybridweights.x" \ + "gdassoca_obsstats.x" \ "gdasapp_land_ensrecenter.x" \ "bufr2ioda.x" \ "calcfIMS.exe" \ diff --git a/sorc/ufs_model.fd b/sorc/ufs_model.fd index fcc9f8461d..6a4e09e947 160000 --- a/sorc/ufs_model.fd +++ b/sorc/ufs_model.fd @@ -1 +1 @@ -Subproject commit fcc9f8461db5eafbfd1f080da61ea79156ca0145 +Subproject commit 6a4e09e94773ffa39ce7ab6a54a885efada91f21 diff --git a/sorc/ufs_utils.fd b/sorc/ufs_utils.fd index 3ef2e6bd72..06eec5b6f6 160000 --- a/sorc/ufs_utils.fd +++ b/sorc/ufs_utils.fd @@ -1 +1 @@ -Subproject commit 3ef2e6bd725d2662fd6ee95897cb7bac222e5144 +Subproject commit 06eec5b6f636123835e2dfd9fc5229980c006735 diff --git a/sorc/verif-global.fd b/sorc/verif-global.fd index 92904d2c43..b2ee80cac7 160000 --- a/sorc/verif-global.fd +++ b/sorc/verif-global.fd @@ -1 +1 @@ -Subproject commit 92904d2c431969345968f74e676717057ec0042a +Subproject commit b2ee80cac7921a3016fa5a857cc58acfccc4baea diff --git a/ush/detect_machine.sh b/ush/detect_machine.sh index b049a6040e..8ad217140a 100755 --- a/ush/detect_machine.sh +++ b/ush/detect_machine.sh @@ -67,7 +67,7 @@ if [[ -d /lfs/h3 ]]; then elif [[ -d /lfs/h1 && ! -d /lfs/h3 ]]; then # We are on NOAA TDS Acorn MACHINE_ID=acorn -elif [[ -d /mnt/lfs1 ]]; then +elif [[ -d /mnt/lfs5 ]]; then # We are on NOAA Jet MACHINE_ID=jet elif [[ -d /scratch1 ]]; then diff --git a/ush/extractvars_tools.sh b/ush/extractvars_tools.sh index daf61a3d2e..51c2586cb5 100644 --- a/ush/extractvars_tools.sh +++ b/ush/extractvars_tools.sh @@ -17,7 +17,8 @@ check_atmos() { done mapfile -t requestedvar_in_allgrb2file_arr < "${requestedvar_in_allgrb2file}" while read -r vari; do - if [[ ! ${requestedvar_in_allgrb2file_arr[*]} =~ ${vari} ]] ;then + # shellcheck disable=SC2076 + if [[ ! ${requestedvar_in_allgrb2file_arr[*]} =~ "${vari}" ]] ;then echo "WARNING: PARM VARIABLE (${vari}) is not available in pgrb and pgrb2b for f${fnhl}." fi done <"${varlistl}" diff --git a/ush/forecast_predet.sh b/ush/forecast_predet.sh index d7c04ea699..5aa9dc9ac7 100755 --- a/ush/forecast_predet.sh +++ b/ush/forecast_predet.sh @@ -548,7 +548,6 @@ FV3_predet(){ ${NCP} "${PARMgfs}/post/gefs/postxconfig-NT-gefs-f00.txt" "${DATA}/postxconfig-NT_FH00.txt" fi fi - } # Disable variable not used warnings diff --git a/ush/ocnice_extractvars.sh b/ush/ocnice_extractvars.sh index 51276172b9..78c4cbd91e 100755 --- a/ush/ocnice_extractvars.sh +++ b/ush/ocnice_extractvars.sh @@ -21,15 +21,23 @@ comout_rfcst_prod_ocnice=${6} [[ -d "${subdata}" ]] || mkdir -p "${subdata}" -for (( nh = FHMIN_GFS; nh <= FHMAX_GFS; nh = nh + fhout_ocnice )); do +for (( nh = FHMIN_GFS + fhout_ocnice; nh <= FHMAX_GFS; nh = nh + fhout_ocnice )); do fnh=$(printf "%3.3d" "${nh}") if [[ ${component_name} == "ocn" ]]; then - infile=${COMIN_OCEAN_NETCDF}/${datares}/${RUN}.ocean.t${cyc}z.${datares}.f${fnh}.nc + if [[ "${datares}" == "native" ]]; then + infile="${COMIN_OCEAN_HISTORY}/${RUN}.ocean.t${cyc}z.${fhout_ocnice}hr_avg.f${fnh}.nc" + else + infile="${COMIN_OCEAN_NETCDF}/${datares}/${RUN}.ocean.t${cyc}z.${datares}.f${fnh}.nc" + fi # For ocean products, add an argument to extract a subset of levels otherargs=(-d "${depthvar_name},""${zmin},""${zmax}") elif [[ ${component_name} == "ice" ]]; then - infile=${COMIN_ICE_NETCDF}/${datares}/${RUN}.ice.t${cyc}z.${datares}.f${fnh}.nc + if [[ "${datares}" == "native" ]]; then + infile="${COMIN_ICE_HISTORY}/${RUN}.ice.t${cyc}z.${fhout_ocnice}hr_avg.f${fnh}.nc" + else + infile="${COMIN_ICE_NETCDF}/${datares}/${RUN}.ice.t${cyc}z.${datares}.f${fnh}.nc" + fi otherargs=() fi outfile=${subdata}/${RUN}.${component_name}.t${cyc}z.${datares}.f${fnh}.nc diff --git a/ush/parsing_namelists_MOM6.sh b/ush/parsing_namelists_MOM6.sh index 9010851806..3ac2cb465e 100755 --- a/ush/parsing_namelists_MOM6.sh +++ b/ush/parsing_namelists_MOM6.sh @@ -41,6 +41,12 @@ cat input.nml # --------- # Prepare local variables for use in MOM_input.IN from UFSWM # The ones already defined are left commented as a reminder +# == MOM options to start from coarsed grained restarts, set to off by default +# options only available for 05 and 1 degree grids +# as restarts are coarsed grained/interpolated from the 0.25 degrees grid +local MOM6_INIT_FROM_Z=${MOM6_INIT_FROM_Z:-True} +local MOM6_WARMSTART_FILE=${MOM6_WARMSTART_FILE:-"none"} +local MOM6_INIT_UV=${MOM6_INIT_UV:-"zero"} # == MOM_domains section == # NX_GLB # NY_GLB diff --git a/ush/python/pygfs/jedi/jedi.py b/ush/python/pygfs/jedi/jedi.py index 62dcb517ca..415a0a3c08 100644 --- a/ush/python/pygfs/jedi/jedi.py +++ b/ush/python/pygfs/jedi/jedi.py @@ -1,10 +1,12 @@ #!/usr/bin/env python3 import os +import tarfile from logging import getLogger from typing import List, Dict, Any, Optional from jcb import render from wxflow import (AttrDict, + FileHandler, chdir, rm_p, parse_j2yaml, logit, @@ -188,19 +190,19 @@ def get_obs_dict(self, task_config: AttrDict) -> Dict[str, Any]: return obs_dict @logit(logger) - def get_bias_dict(self, task_config: AttrDict) -> Dict[str, Any]: + def get_bias_dict(self, task_config: AttrDict, bias_file) -> Dict[str, Any]: """Compile a dictionary of observation files to copy - This method extracts 'observers' from the JEDI yaml and from that list, extracts a list of - observation bias correction files that are to be copied to the run directory + This method extracts 'observers' from the JEDI yaml and determines from that list + if bias correction tar files are to be copied to the run directory from the component directory. - TODO: COM_ATMOS_ANALYSIS_PREV is hardwired here and this method is not appropriate in - `analysis.py` and should be implemented in the component where this is applicable. Parameters ---------- task_config: AttrDict Attribute-dictionary of all configuration variables associated with a GDAS task. + bias_file + name of bias correction tar file Returns ---------- @@ -216,18 +218,52 @@ def get_bias_dict(self, task_config: AttrDict) -> Dict[str, Any]: obfile = ob['obs bias']['input file'] obdir = os.path.dirname(obfile) basename = os.path.basename(obfile) - prefix = '.'.join(basename.split('.')[:-2]) - for file in ['satbias.nc', 'satbias_cov.nc', 'tlapse.txt']: - bfile = f"{prefix}.{file}" - copylist.append([os.path.join(task_config.COM_ATMOS_ANALYSIS_PREV, bfile), os.path.join(obdir, bfile)]) - # TODO: Why is this specific to ATMOS? + prefix = '.'.join(basename.split('.')[:-3]) + bfile = f"{prefix}.{bias_file}" + tar_file = os.path.join(obdir, bfile) + copylist.append([os.path.join(task_config.VarBcDir, bfile), tar_file]) + break bias_dict = { 'mkdir': [os.path.join(task_config.DATA, 'bc')], 'copy': copylist } + return bias_dict + @staticmethod + @logit(logger) + def extract_tar(tar_file: str) -> None: + """Extract files from a tarball + + This method extract files from a tarball + + Parameters + ---------- + tar_file + path/name of tarball + + Returns + ---------- + None + """ + + # extract files from tar file + tar_path = os.path.dirname(tar_file) + try: + with tarfile.open(tar_file, "r") as tarball: + tarball.extractall(path=tar_path) + logger.info(f"Extract {tarball.getnames()}") + except tarfile.ReadError as err: + if tarfile.is_tarfile(tar_file): + logger.error(f"FATAL ERROR: {tar_file} could not be read") + raise tarfile.ReadError(f"FATAL ERROR: unable to read {tar_file}") + else: + logger.info() + except tarfile.ExtractError as err: + logger.exception(f"FATAL ERROR: unable to extract from {tar_file}") + raise tarfile.ExtractError("FATAL ERROR: unable to extract from {tar_file}") + @logit(logger) def find_value_in_nested_dict(nested_dict: Dict, target_key: str) -> Any: diff --git a/ush/python/pygfs/task/analysis.py b/ush/python/pygfs/task/analysis.py index 6f7d3dfc68..1d8b38483b 100644 --- a/ush/python/pygfs/task/analysis.py +++ b/ush/python/pygfs/task/analysis.py @@ -41,10 +41,6 @@ def initialize(self) -> None: obs_dict = self.get_obs_dict() FileHandler(obs_dict).sync() - # some analyses need to stage bias corrections - bias_dict = self.get_bias_dict() - FileHandler(bias_dict).sync() - # link jedi executable to run directory self.link_jediexe() @@ -127,47 +123,6 @@ def get_obs_dict(self) -> Dict[str, Any]: } return obs_dict - @logit(logger) - def get_bias_dict(self) -> Dict[str, Any]: - """Compile a dictionary of observation files to copy - - This method extracts 'observers' from the JEDI yaml and from that list, extracts a list of - observation bias correction files that are to be copied to the run directory - from the component directory. - TODO: COM_ATMOS_ANALYSIS_PREV is hardwired here and this method is not appropriate in - `analysis.py` and should be implemented in the component where this is applicable. - - Parameters - ---------- - - Returns - ---------- - bias_dict: Dict - a dictionary containing the list of observation bias files to copy for FileHandler - """ - - logger.info(f"Extracting a list of bias correction files from Jedi config file") - observations = find_value_in_nested_dict(self.task_config.jedi_config, 'observations') - logger.debug(f"observations:\n{pformat(observations)}") - - copylist = [] - for ob in observations['observers']: - if 'obs bias' in ob.keys(): - obfile = ob['obs bias']['input file'] - obdir = os.path.dirname(obfile) - basename = os.path.basename(obfile) - prefix = '.'.join(basename.split('.')[:-2]) - for file in ['satbias.nc', 'satbias_cov.nc', 'tlapse.txt']: - bfile = f"{prefix}.{file}" - copylist.append([os.path.join(self.task_config.COM_ATMOS_ANALYSIS_PREV, bfile), os.path.join(obdir, bfile)]) - # TODO: Why is this specific to ATMOS? - - bias_dict = { - 'mkdir': [os.path.join(self.task_config.DATA, 'bc')], - 'copy': copylist - } - return bias_dict - @logit(logger) def add_fv3_increments(self, inc_file_tmpl: str, bkg_file_tmpl: str, incvars: List) -> None: """Add cubed-sphere increments to cubed-sphere backgrounds diff --git a/ush/python/pygfs/task/atm_analysis.py b/ush/python/pygfs/task/atm_analysis.py index 8d340a5b73..5f67ea9d72 100644 --- a/ush/python/pygfs/task/atm_analysis.py +++ b/ush/python/pygfs/task/atm_analysis.py @@ -139,10 +139,17 @@ def initialize_analysis(self) -> None: # stage bias corrections logger.info(f"Staging list of bias correction files generated from JEDI config") - bias_dict = self.jedi.get_bias_dict(self.task_config) + self.task_config.VarBcDir = f"{self.task_config.COM_ATMOS_ANALYSIS_PREV}" + bias_file = f"rad_varbc_params.tar" + bias_dict = self.jedi.get_bias_dict(self.task_config, bias_file) FileHandler(bias_dict).sync() logger.debug(f"Bias correction files:\n{pformat(bias_dict)}") + # extract bias corrections + tar_file = os.path.join(self.task_config.DATA, 'obs', f"{self.task_config.GPREFIX}{bias_file}") + logger.info(f"Extract bias correction files from {tar_file}") + self.jedi.extract_tar(tar_file) + # stage CRTM fix files logger.info(f"Staging CRTM fix files from {self.task_config.CRTM_FIX_YAML}") crtm_fix_dict = parse_j2yaml(self.task_config.CRTM_FIX_YAML, self.task_config) @@ -265,37 +272,34 @@ def finalize(self) -> None: } FileHandler(yaml_copy).sync() - # copy bias correction files to ROTDIR - logger.info("Copy bias correction files from DATA/ to COM/") - biasdir = os.path.join(self.task_config.DATA, 'bc') - biasls = os.listdir(biasdir) - biaslist = [] - for bfile in biasls: - src = os.path.join(biasdir, bfile) - dest = os.path.join(self.task_config.COM_ATMOS_ANALYSIS, bfile) - biaslist.append([src, dest]) - - gprefix = f"{self.task_config.GPREFIX}" - gsuffix = f"{to_YMDH(self.task_config.previous_cycle)}" + ".txt" - aprefix = f"{self.task_config.APREFIX}" - asuffix = f"{to_YMDH(self.task_config.current_cycle)}" + ".txt" - - logger.info(f"Copying {gprefix}*{gsuffix} from DATA/ to COM/ as {aprefix}*{asuffix}") - obsdir = os.path.join(self.task_config.DATA, 'obs') - obsls = os.listdir(obsdir) - for ofile in obsls: - if ofile.endswith(".txt"): - src = os.path.join(obsdir, ofile) - tfile = ofile.replace(gprefix, aprefix) - tfile = tfile.replace(gsuffix, asuffix) - dest = os.path.join(self.task_config.COM_ATMOS_ANALYSIS, tfile) - biaslist.append([src, dest]) - - bias_copy = { - 'mkdir': [self.task_config.COM_ATMOS_ANALYSIS], - 'copy': biaslist, + # path of output radiance bias correction tarfile + bfile = f"{self.task_config.APREFIX}rad_varbc_params.tar" + radtar = os.path.join(self.task_config.COM_ATMOS_ANALYSIS, bfile) + + # rename and copy tlapse radiance bias correction files from obs to bc + tlapobs = glob.glob(os.path.join(self.task_config.DATA, 'obs', '*tlapse.txt')) + copylist = [] + for tlapfile in tlapobs: + obsfile = os.path.basename(tlapfile).split('.', 2) + newfile = f"{self.task_config.APREFIX}{obsfile[2]}" + copylist.append([tlapfile, os.path.join(self.task_config.DATA, 'bc', newfile)]) + tlapse_dict = { + 'copy': copylist } - FileHandler(bias_copy).sync() + FileHandler(tlapse_dict).sync() + + # get lists of radiance bias correction files to add to tarball + satlist = glob.glob(os.path.join(self.task_config.DATA, 'bc', '*satbias*nc')) + tlaplist = glob.glob(os.path.join(self.task_config.DATA, 'bc', '*tlapse.txt')) + + # tar radiance bias correction files to ROTDIR + logger.info(f"Creating radiance bias correction tar file {radtar}") + with tarfile.open(radtar, 'w') as radbcor: + for satfile in satlist: + radbcor.add(satfile, arcname=os.path.basename(satfile)) + for tlapfile in tlaplist: + radbcor.add(tlapfile, arcname=os.path.basename(tlapfile)) + logger.info(f"Add {radbcor.getnames()}") # Copy FV3 atm increment to comrot directory logger.info("Copy UFS model readable atm increment file") diff --git a/ush/python/pygfs/task/atmens_analysis.py b/ush/python/pygfs/task/atmens_analysis.py index 55e72702b1..4b2f8ebbf4 100644 --- a/ush/python/pygfs/task/atmens_analysis.py +++ b/ush/python/pygfs/task/atmens_analysis.py @@ -138,10 +138,17 @@ def initialize_analysis(self) -> None: # stage bias corrections logger.info(f"Staging list of bias correction files generated from JEDI config") - bias_dict = self.jedi.get_bias_dict(self.task_config) + self.task_config.VarBcDir = f"{self.task_config.COM_ATMOS_ANALYSIS_PREV}" + bias_file = f"rad_varbc_params.tar" + bias_dict = self.jedi.get_bias_dict(self.task_config, bias_file) FileHandler(bias_dict).sync() logger.debug(f"Bias correction files:\n{pformat(bias_dict)}") + # extract bias corrections + tar_file = os.path.join(self.task_config.DATA, 'obs', f"{self.task_config.GPREFIX}{bias_file}") + logger.info(f"Extract bias correction files from {tar_file}") + self.jedi.extract_tar(tar_file) + # stage CRTM fix files logger.info(f"Staging CRTM fix files from {self.task_config.CRTM_FIX_YAML}") crtm_fix_dict = parse_j2yaml(self.task_config.CRTM_FIX_YAML, self.task_config) diff --git a/ush/python/pygfs/task/marine_analysis.py b/ush/python/pygfs/task/marine_analysis.py new file mode 100644 index 0000000000..e7b7b5e948 --- /dev/null +++ b/ush/python/pygfs/task/marine_analysis.py @@ -0,0 +1,486 @@ +#!/usr/bin/env python3 + +import copy +import os +from logging import getLogger +import pygfs.utils.marine_da_utils as mdau +import glob +import re +import netCDF4 +from multiprocessing import Process +import subprocess +import yaml +from jcb import render + +from wxflow import (AttrDict, + FileHandler, + add_to_datetime, to_timedelta, to_YMD, + parse_j2yaml, + logit, + Executable, + Task, + save_as_yaml, + Template, TemplateConstants, YAMLFile) + +logger = getLogger(__name__.split('.')[-1]) + + +def parse_obs_list_file(obs_list_yaml_path): + # Get the list of observation types from the obs_list.yaml + obs_types = [] + with open(obs_list_yaml_path, 'r') as file: + for line in file: + # Remove leading/trailing whitespace and check if the line is uncommented + line = line.strip() + if line.startswith('- !INC') and not line.startswith('#'): + # Extract the type using regex + match = re.search(r'\$\{MARINE_OBS_YAML_DIR\}/(.+)\.yaml', line) + if match: + obs_types.append(str(match.group(1))) + return obs_types + + +class MarineAnalysis(Task): + """ + Class for global marine analysis tasks + """ + @logit(logger, name="MarineAnalysis") + def __init__(self, config): + super().__init__(config) + _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( + { + 'PARMsoca': os.path.join(self.task_config.PARMgfs, 'gdas', 'soca'), + 'MARINE_WINDOW_BEGIN': _window_begin, + 'MARINE_WINDOW_BEGIN_ISO': _window_begin.strftime('%Y-%m-%dT%H:%M:%SZ'), + 'MARINE_WINDOW_END': _window_end, + 'MARINE_WINDOW_LENGTH': f"PT{self.task_config['assim_freq']}H", + 'MARINE_WINDOW_MIDDLE': self.task_config.current_cycle, + 'MARINE_WINDOW_MIDDLE_ISO': self.task_config.current_cycle.strftime('%Y-%m-%dT%H:%M:%SZ'), + 'ENSPERT_RELPATH': _enspert_relpath, + 'CALC_SCALE_EXEC': _calc_scale_exec, + 'OPREFIX': f"{self.task_config.RUN}.t{self.task_config.cyc:02d}z." + } + ) + + # Extend task_config with local_dict + self.task_config.update(local_dict) + + @logit(logger) + def initialize(self: Task) -> None: + """Initialize the marine analysis + + This method will initialize the marine analysis. + 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() + + # prepare the directory structure to run SOCA + self._prep_scratch_dir() + + # 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 + self._fetch_observations() + + # stage the ocean and ice backgrounds for FGAT + bkg_list = parse_j2yaml(self.task_config.MARINE_DET_STAGE_BKG_YAML_TMPL, self.task_config) + FileHandler(bkg_list).sync() + + # stage the soca grid + FileHandler({'copy': [[os.path.join(self.task_config.COMIN_OCEAN_BMATRIX, 'soca_gridspec.nc'), + os.path.join(self.task_config.DATA, 'soca_gridspec.nc')]]}).sync() + + # link the flow dependent static B resources from the B-matrix task of the same cycle + os.symlink('../staticb', 'staticb') + + # 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) + + # prepare the yaml configuration to run the SOCA variational application + self._prep_variational_yaml() + + # prepare the yaml configuration to run the SOCA to MOM6 IAU increment + self._prep_checkpoint() + + @logit(logger) + def _fetch_observations(self: Task) -> None: + """Fetch observations from COMIN_OBS + + This method will fetch the observations for the cycle and check the + list against what is available for the cycle. + """ + + # get the list of observations + obs_list_config = YAMLFile(self.task_config.MARINE_OBS_LIST_YAML) + obs_list_config = Template.substitute_structure(obs_list_config, TemplateConstants.DOLLAR_PARENTHESES, self.task_config) + obs_list_config = {'observations': obs_list_config} + logger.info(f"{obs_list_config}") + + obs_files = [] + for ob in obs_list_config['observations']['observers']: + logger.info(f"******** {self.task_config.OPREFIX}{ob['obs space']['name'].lower()}.{to_YMD(self.task_config.PDY)}{self.task_config.cyc}.nc4") + obs_files.append(f"{self.task_config.OPREFIX}{ob['obs space']['name'].lower()}.{to_YMD(self.task_config.PDY)}{self.task_config.cyc}.nc4") + obs_list = [] + + # copy obs from COM_OBS to DATA/obs + for obs_file in obs_files: + logger.info(f"******* {obs_file}") + obs_src = os.path.join(self.task_config.COM_OBS, obs_file) + obs_dst = os.path.join(self.task_config.DATA, 'obs', obs_file) + logger.info(f"******* {obs_src}") + if os.path.exists(obs_src): + logger.info(f"******* fetching {obs_file}") + obs_list.append([obs_src, obs_dst]) + else: + logger.info(f"******* {obs_file} is not in the database") + + FileHandler({'copy': obs_list}).sync() + + @logit(logger) + def _prep_scratch_dir(self: Task) -> None: + """Create and stage all the resources needed to run SOCA/JEDI, including the necesssary + directory structure to run the SOCA variational application + """ + logger.info(f"---------------- Setup runtime environement") + + anl_dir = self.task_config.DATA + + # create analysis directories + diags = os.path.join(anl_dir, 'diags') # output dir for soca DA obs space + obs_in = os.path.join(anl_dir, 'obs') # input " " + anl_out = os.path.join(anl_dir, 'Data') # output dir for soca DA + FileHandler({'mkdir': [diags, obs_in, anl_out]}).sync() + + # 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 the soca utility yamls (gridgen, fields and ufo mapping yamls) + logger.info(f"Staging SOCA utility yaml files from {self.task_config.PARMsoca}") + soca_utility_list = parse_j2yaml(self.task_config.MARINE_UTILITY_YAML_TMPL, self.task_config) + FileHandler(soca_utility_list).sync() + + @logit(logger) + def _prep_variational_yaml(self: Task) -> None: + """Create the yaml configuration to run the SOCA variational application + """ + + # prepare background list for the pseudo model, check bkg date for consistency + mdau.gen_bkg_list(bkg_path='./bkg', + window_begin=self.task_config.MARINE_WINDOW_BEGIN, + yaml_name='bkg_list.yaml') + + # Make a copy of the env config before modifying to avoid breaking something else + envconfig_jcb = copy.deepcopy(self.task_config) + logger.info(f"---------------- Prepare the yaml configuration") + logger.info(f"{envconfig_jcb}") # Prepare the yaml configuration + + # Add the things to the envconfig in order to template JCB files + envconfig_jcb['PARMgfs'] = self.task_config.PARMgfs + envconfig_jcb['nmem_ens'] = self.task_config.NMEM_ENS + envconfig_jcb['berror_model'] = 'marine_background_error_static_diffusion' + if self.task_config.NMEM_ENS > 3: + envconfig_jcb['berror_model'] = 'marine_background_error_hybrid_diffusion_diffusion' + envconfig_jcb['DATA'] = self.task_config.DATA + envconfig_jcb['OPREFIX'] = self.task_config.OPREFIX + envconfig_jcb['PDY'] = os.getenv('PDY') + envconfig_jcb['cyc'] = os.getenv('cyc') + envconfig_jcb['SOCA_NINNER'] = self.task_config.SOCA_NINNER + envconfig_jcb['obs_list'] = ['adt_rads_all'] + envconfig_jcb['MOM6_LEVS'] = mdau.get_mom6_levels(str(self.task_config.OCNRES)) + + # Write obs_list_short + save_as_yaml(parse_obs_list_file(self.task_config.MARINE_OBS_LIST_YAML), 'obs_list_short.yaml') + os.environ['OBS_LIST_SHORT'] = 'obs_list_short.yaml' + + # Render the JCB configuration files + jcb_base_yaml = os.path.join(self.task_config.PARMsoca, 'marine-jcb-base.yaml') + jcb_algo_yaml = os.path.join(self.task_config.PARMsoca, 'marine-jcb-3dfgat.yaml.j2') + + jcb_base_config = YAMLFile(path=jcb_base_yaml) + jcb_base_config = Template.substitute_structure(jcb_base_config, TemplateConstants.DOUBLE_CURLY_BRACES, envconfig_jcb.get) + jcb_base_config = Template.substitute_structure(jcb_base_config, TemplateConstants.DOLLAR_PARENTHESES, envconfig_jcb.get) + jcb_algo_config = YAMLFile(path=jcb_algo_yaml) + jcb_algo_config = Template.substitute_structure(jcb_algo_config, TemplateConstants.DOUBLE_CURLY_BRACES, envconfig_jcb.get) + jcb_algo_config = Template.substitute_structure(jcb_algo_config, TemplateConstants.DOLLAR_PARENTHESES, envconfig_jcb.get) + + # Override base with the application specific config + jcb_config = {**jcb_base_config, **jcb_algo_config} + + # convert datetime to string + jcb_config['window_begin'] = self.task_config.MARINE_WINDOW_BEGIN.strftime('%Y-%m-%dT%H:%M:%SZ') + jcb_config['window_middle'] = self.task_config.MARINE_WINDOW_MIDDLE.strftime('%Y-%m-%dT%H:%M:%SZ') + + # Render the full JEDI configuration file using JCB + jedi_config = render(jcb_config) + + # Save the JEDI configuration file + var_yaml_jcb = 'var.yaml' + mdau.clean_empty_obsspaces(jedi_config, target=var_yaml_jcb, app='var') + + def _prep_checkpoint(self: Task) -> None: + """Create the yaml configuration to run the SOCA to MOM6 IAU increment + """ + # prepare the socaincr2mom6.yaml + logger.info("Generate the SOCA to MOM6 IAU increment YAML file") + data = {'marine_window_begin': self.task_config.MARINE_WINDOW_BEGIN_ISO, + 'marine_window_middle': self.task_config.MARINE_WINDOW_MIDDLE_ISO} + soca2mom6inc_config = parse_j2yaml(path=os.path.join(self.task_config.MARINE_JCB_GDAS_ALGO, 'socaincr2mom6.yaml.j2'), + data=data) + soca2mom6inc_config.save(os.path.join(self.task_config.DATA, 'socaincr2mom6.yaml')) + + # prepare the SOCA to CICE YAML file + logger.info("Generate the SOCA to CICE RST YAML file") + + # set the restart date, dependent on the cycling type + if self.task_config.DOIAU: + # forecast initialized at the begining of the DA window + fcst_begin = self.task_config.MARINE_WINDOW_BEGIN_ISO + rst_date = self.task_config.MARINE_WINDOW_BEGIN.strftime('%Y%m%d.%H%M%S') + else: + # forecast initialized at the middle of the DA window + fcst_begin = self.task_config.MARINE_WINDOW_MIDDLE_ISO + rst_date = self.task_config.MARINE_WINDOW_MIDDLE.strftime('%Y%m%d.%H%M%S') + + # make a copy of the CICE6 restart + ice_rst = os.path.join(self.task_config.COMIN_ICE_RESTART_PREV, f'{rst_date}.cice_model.res.nc') + ice_rst_ana = os.path.join(self.task_config.DATA, 'Data', rst_date + '.cice_model.res.nc') + FileHandler({'copy': [[ice_rst, ice_rst_ana]]}).sync() + + # prepare the necessary configuration for the SOCA to CICE application + soca2cice_param = AttrDict({ + "ocn_ana": f"./Data/ocn.3dvarfgat_pseudo.an.{self.task_config.MARINE_WINDOW_MIDDLE_ISO}.nc", + "ice_ana": f"./Data/ice.3dvarfgat_pseudo.an.{self.task_config.MARINE_WINDOW_MIDDLE_ISO}.nc", + "ice_rst": ice_rst_ana, + "fcst_begin": fcst_begin + }) + logger.debug(f"{soca2cice_param}") + + # render the SOCA to CICE YAML file for the Arctic and Antarctic + logger.info("render the SOCA to CICE YAML file for the Arctic and Antarctic") + varchgyamls = ['soca_2cice_arctic.yaml', 'soca_2cice_antarctic.yaml'] + for varchgyaml in varchgyamls: + soca2cice_config = parse_j2yaml(path=os.path.join(self.task_config.MARINE_JCB_GDAS_ALGO, f'{varchgyaml}.j2'), + data=soca2cice_param) + soca2cice_config.save(os.path.join(self.task_config.DATA, varchgyaml)) + + @logit(logger) + def variational(self: Task) -> None: + # link gdas_soca_gridgen.x + mdau.link_executable(self.task_config, 'gdas.x') + exec_cmd = Executable(self.task_config.APRUN_MARINEANLVAR) + exec_name = os.path.join(self.task_config.DATA, 'gdas.x') + exec_cmd.add_default_arg(exec_name) + exec_cmd.add_default_arg('soca') + exec_cmd.add_default_arg('variational') + exec_cmd.add_default_arg('var.yaml') + + mdau.run(exec_cmd) + + @logit(logger) + def checkpoint_cice6(self: Task, soca2ciceyaml) -> None: + # link gdas_soca_gridgen.x + mdau.link_executable(self.task_config, 'gdas.x') + exec_cmd = Executable(self.task_config.APRUN_MARINEANLCHKPT) + exec_name = os.path.join(self.task_config.DATA, 'gdas.x') + exec_cmd.add_default_arg(exec_name) + exec_cmd.add_default_arg('soca') + exec_cmd.add_default_arg('convertstate') + exec_cmd.add_default_arg(soca2ciceyaml) + + mdau.run(exec_cmd) + + @logit(logger) + def checkpoint_mom6_iau(self: Task, socaincr2mom6yaml) -> None: + # link gdas_incr_handler.x + mdau.link_executable(self.task_config, 'gdas_incr_handler.x') + exec_cmd = Executable(self.task_config.APRUN_MARINEANLCHKPT) + exec_name = os.path.join(self.task_config.DATA, 'gdas_incr_handler.x') + exec_cmd.add_default_arg(exec_name) + exec_cmd.add_default_arg(socaincr2mom6yaml) + + mdau.run(exec_cmd) + + @logit(logger) + def finalize(self: Task) -> None: + """Finalize the marine analysis job + This method saves the results of the deterministic variational analysis to the COMROOT + """ + + def list_all_files(dir_in, dir_out, wc='*', fh_list=[]): + files = glob.glob(os.path.join(dir_in, wc)) + for file_src in files: + file_dst = os.path.join(dir_out, os.path.basename(file_src)) + fh_list.append([file_src, file_dst]) + return fh_list + + # variables of convenience + com_ocean_analysis = self.task_config.COMOUT_OCEAN_ANALYSIS + com_ice_analysis = self.task_config.COMOUT_ICE_ANALYSIS + com_ice_restart = self.task_config.COMOUT_ICE_RESTART + anl_dir = self.task_config.DATA + cdate = self.task_config.CDATE + pdy = self.task_config.PDY + staticsoca_dir = self.task_config.SOCA_INPUT_FIX_DIR + RUN = self.task_config.RUN + cyc = str(self.task_config.cyc).zfill(2) + bcyc = str(self.task_config.MARINE_WINDOW_BEGIN.hour).zfill(2) + bdate = self.task_config.MARINE_WINDOW_BEGIN_ISO + mdate = self.task_config.MARINE_WINDOW_MIDDLE_ISO + nmem_ens = int(self.task_config.NMEM_ENS) + + logger.info(f"---------------- Copy from RUNDIR to COMOUT") + + post_file_list = [] + + # Make a copy the IAU increment + post_file_list.append([os.path.join(anl_dir, 'inc.nc'), + os.path.join(com_ocean_analysis, f'{RUN}.t{cyc}z.ocninc.nc')]) + + domains = ['ocn', 'ice'] + for domain in domains: + ''' + # Copy of the diagonal of the background error for the cycle + post_file_list.append([os.path.join(anl_dir, f'{domain}.bkgerr_stddev.incr.{mdate}.nc'), + os.path.join(com_ocean_analysis, f'{RUN}.t{cyc}z.{domain}.bkgerr_stddev.nc')]) + + # Copy the recentering error + if nmem_ens > 2: + post_file_list.append([os.path.join(anl_dir, 'static_ens', f'{domain}.ssh_recentering_error.incr.{bdate}.nc'), + os.path.join(com_ocean_analysis, f'{RUN}.t{cyc}z.{domain}.recentering_error.nc')]) + ''' + + # Copy the ice and ocean increments + post_file_list.append([os.path.join(anl_dir, 'Data', f'{domain}.3dvarfgat_pseudo.incr.{mdate}.nc'), + os.path.join(com_ocean_analysis, f'{RUN}.t{cyc}z.{domain}.incr.nc')]) + + # Copy the analysis at the start of the window + post_file_list.append([os.path.join(anl_dir, 'Data', f'{domain}.3dvarfgat_pseudo.an.{mdate}.nc'), + os.path.join(com_ocean_analysis, f'{RUN}.t{cyc}z.{domain}ana.nc')]) + + # Copy of the ssh diagnostics + ''' + if nmem_ens > 2: + for string in ['ssh_steric_stddev', 'ssh_unbal_stddev', 'ssh_total_stddev', 'steric_explained_variance']: + post_file_list.append([os.path.join(anl_dir, 'static_ens', f'ocn.{string}.incr.{bdate}.nc'), + os.path.join(com_ocean_analysis, f'{RUN}.t{cyc}z.ocn.{string}.nc')]) + ''' + + # Copy DA grid (computed for the start of the window) + post_file_list.append([os.path.join(anl_dir, 'soca_gridspec.nc'), + os.path.join(com_ocean_analysis, f'{RUN}.t{bcyc}z.ocngrid.nc')]) + + # Copy the CICE analysis restart + if os.getenv('DOIAU') == "YES": + cice_rst_date = self.task_config.MARINE_WINDOW_BEGIN.strftime('%Y%m%d.%H%M%S') + else: + cice_rst_date = cdate.strftime('%Y%m%d.%H%M%S') + + post_file_list.append([os.path.join(anl_dir, 'Data', f'{cice_rst_date}.cice_model.res.nc'), + os.path.join(com_ice_analysis, f'{cice_rst_date}.cice_model_anl.res.nc')]) + + FileHandler({'copy': post_file_list}).sync() + + # create COM sub-directories + FileHandler({'mkdir': [os.path.join(com_ocean_analysis, 'diags'), + os.path.join(com_ocean_analysis, 'bump'), + os.path.join(com_ocean_analysis, 'yaml')]}).sync() + + # ioda output files + fh_list = list_all_files(os.path.join(anl_dir, 'diags'), + os.path.join(com_ocean_analysis, 'diags')) + + # yaml configurations + fh_list = list_all_files(os.path.join(anl_dir), + os.path.join(com_ocean_analysis, 'yaml'), wc='*.yaml', fh_list=fh_list) + + FileHandler({'copy': fh_list}).sync() + + @logit(logger) + def obs_space_stats(self: Task) -> None: + """Observation space statistics + This method computes a few basic statistics on the observation spaces + """ + + # obs space statistics + logger.info(f"---------------- Compute basic stats") + diags_list = glob.glob(os.path.join(os.path.join(self.task_config.COMOUT_OCEAN_ANALYSIS, 'diags', '*.nc4'))) + obsstats_j2yaml = str(os.path.join(self.task_config.PARMgfs, 'gdas', 'soca', 'obs', 'obs_stats.yaml.j2')) + + # function to create a minimalist ioda obs sapce + def create_obs_space(data): + os_dict = {"obs space": { + "name": data["obs_space"], + "obsdatain": { + "engine": {"type": "H5File", "obsfile": data["obsfile"]} + }, + "simulated variables": [data["variable"]] + }, + "variable": data["variable"], + "experiment identifier": data["pslot"], + "csv output": data["csv_output"] + } + return os_dict + + # get the experiment id + pslot = self.task_config.PSLOT + + # iterate through the obs spaces and generate the yaml for gdassoca_obsstats.x + obs_spaces = [] + for obsfile in diags_list: + + # define an obs space name + obs_space = re.sub(r'\.\d{10}\.nc4$', '', os.path.basename(obsfile)) + + # get the variable name, assume 1 variable per file + nc = netCDF4.Dataset(obsfile, 'r') + variable = next(iter(nc.groups["ObsValue"].variables)) + nc.close() + + # filling values for the templated yaml + data = {'obs_space': os.path.basename(obsfile), + 'obsfile': obsfile, + 'pslot': pslot, + 'variable': variable, + 'csv_output': os.path.join(self.task_config.COMOUT_OCEAN_ANALYSIS, + f"{self.task_config.OPREFIX}ocn.{obs_space}.stats.csv")} + obs_spaces.append(create_obs_space(data)) + + # create the yaml + data = {'obs_spaces': obs_spaces} + conf = parse_j2yaml(path=obsstats_j2yaml, data=data) + stats_yaml = 'diag_stats.yaml' + conf.save(stats_yaml) + + # run the application + mdau.link_executable(self.task_config, 'gdassoca_obsstats.x') + command = f"{os.getenv('launcher')} -n 1" + exec_cmd = Executable(command) + exec_name = os.path.join(self.task_config.DATA, 'gdassoca_obsstats.x') + exec_cmd.add_default_arg(exec_name) + exec_cmd.add_default_arg(stats_yaml) + + mdau.run(exec_cmd) diff --git a/ush/python/pygfs/task/marine_bmat.py b/ush/python/pygfs/task/marine_bmat.py index 4770583934..93329f05ac 100644 --- a/ush/python/pygfs/task/marine_bmat.py +++ b/ush/python/pygfs/task/marine_bmat.py @@ -26,29 +26,24 @@ 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) + _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 + _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( { - 'HOMEgdas': _home_gdas, + 'PARMsoca': os.path.join(self.task_config.PARMgfs, 'gdas', 'soca'), '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'), - 'UTILITY_YAML_TMPL': os.path.join(_home_gdas, 'parm', 'soca', 'soca_utils_stage.yaml.j2'), - '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.", + 'APREFIX': f"{self.task_config.RUN}.t{self.task_config.cyc:02d}z." } ) @@ -61,7 +56,7 @@ def initialize(self: Task) -> None: This method will initialize a global B-Matrix. This includes: - - staging the deterministic backgrounds (middle of window) + - staging the deterministic backgrounds - staging SOCA fix files - staging static ensemble members (optional) - staging ensemble members (optional) @@ -84,39 +79,35 @@ def initialize(self: Task) -> None: FileHandler(bkg_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.HOMEgfs}/parm/gdas/soca") - soca_utility_list = parse_j2yaml(self.task_config.UTILITY_YAML_TMPL, self.task_config) + logger.info(f"Staging SOCA utility yaml files") + soca_utility_list = parse_j2yaml(self.task_config.MARINE_UTILITY_YAML_TMPL, self.task_config) FileHandler(soca_utility_list).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) + logger.info(f"Generate variance partitioning YAML file from {self.task_config.BERROR_DIAGB_YAML}") + diagb_config = parse_j2yaml(path=self.task_config.BERROR_DIAGB_YAML, 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) + logger.info(f"Generate the vertical correlation scale YAML file from {self.task_config.BERROR_VTSCALES_YAML}") + vtscales_config = parse_j2yaml(path=self.task_config.BERROR_VTSCALES_YAML, 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) + logger.info(f"Generate vertical diffusion YAML file from {self.task_config.BERROR_DIFFV_YAML}") + diffvz_config = parse_j2yaml(path=self.task_config.BERROR_DIFFV_YAML, 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'), + logger.info(f"Generate correlation scale YAML file from {self.task_config.BERROR_HZSCALES_YAML}") + FileHandler({'copy': [[self.task_config.BERROR_HZSCALES_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) + logger.info(f"Generate horizontal diffusion scale YAML file from {self.task_config.BERROR_DIFFH_YAML}") + diffhz_config = parse_j2yaml(path=self.task_config.BERROR_DIFFH_YAML, data=self.task_config) diffhz_config.save(os.path.join(self.task_config.DATA, 'soca_parameters_diffusion_hz.yaml')) # hybrid EnVAR case @@ -127,19 +118,20 @@ def initialize(self: Task) -> None: # 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 = parse_j2yaml(path=self.task_config.BERROR_ENS_RECENTER_YAML, 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) + logger.debug("Generate hybrid-weigths YAML file") + hybridweights_config = parse_j2yaml(path=self.task_config.BERROR_HYB_WEIGHTS_YAML, 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() + # create the symbolic link to the static B-matrix directory + link_target = os.path.join(self.task_config.DATAstaticb) + link_name = os.path.join(self.task_config.DATA, 'staticb') + if os.path.exists(link_name): + os.remove(link_name) + os.symlink(link_target, link_name) @logit(logger) def gridgen(self: Task) -> None: @@ -290,12 +282,12 @@ def finalize(self: Task) -> None: 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") + src = os.path.join(self.task_config.DATAstaticb, 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") + src = os.path.join(self.task_config.DATAstaticb, 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]) @@ -308,13 +300,17 @@ def finalize(self: Task) -> None: 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") + os.rename(os.path.join(self.task_config.DATAstaticb, f"ocn.bkgerr_stddev.incr.{window_end_iso}.nc"), + os.path.join(self.task_config.DATAstaticb, f"ocn.bkgerr_stddev.nc")) + src = os.path.join(self.task_config.DATAstaticb, f"ocn.bkgerr_stddev.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") + os.rename(os.path.join(self.task_config.DATAstaticb, f"ice.bkgerr_stddev.incr.{window_end_iso}.nc"), + os.path.join(self.task_config.DATAstaticb, f"ice.bkgerr_stddev.nc")) + src = os.path.join(self.task_config.DATAstaticb, f"ice.bkgerr_stddev.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]) diff --git a/ush/python/pygfs/utils/marine_da_utils.py b/ush/python/pygfs/utils/marine_da_utils.py index 2be76ac028..50d9d84e86 100644 --- a/ush/python/pygfs/utils/marine_da_utils.py +++ b/ush/python/pygfs/utils/marine_da_utils.py @@ -1,7 +1,9 @@ -import f90nml +from datetime import datetime, timedelta +import dateutil.parser as dparser import os +from netCDF4 import Dataset from logging import getLogger -import xarray as xr +import yaml from wxflow import (FileHandler, logit, @@ -9,6 +11,7 @@ AttrDict, parse_j2yaml, Executable, + save_as_yaml, jinja) logger = getLogger(__name__.split('.')[-1]) @@ -23,9 +26,9 @@ def run(exec_cmd: Executable) -> None: logger.debug(f"Executing {exec_cmd}") exec_cmd() except OSError: - raise OSError(f"Failed to execute {exec_cmd}") + raise OSError(f"FATAL ERROR: Failed to execute {exec_cmd}") except Exception: - raise WorkflowException(f"An error occured during execution of {exec_cmd}") + raise WorkflowException(f"FATAL ERROR: Error occurred during execution of {exec_cmd}") @logit(logger) @@ -43,22 +46,19 @@ def link_executable(task_config: AttrDict, exe_name: str) -> None: @logit(logger) def prep_input_nml(task_config: AttrDict) -> None: - """Prepare the input.nml file - TODO: Use jinja2 instead of f90nml + """Prepare the mom_input.nml file """ - # stage input.nml - mom_input_nml_tmpl_src = os.path.join(task_config.HOMEgdas, 'parm', 'soca', 'fms', 'input.nml') + # stage input.nml.j2 + mom_input_nml_tmpl_src = os.path.join(task_config.PARMsoca, 'fms', 'input.nml.j2') 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') + date_init = [int(s) for s in task_config.MARINE_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} + jinja_input_nml = jinja.Jinja(mom_input_nml_tmpl, input_nml_config) + jinja_input_nml.save('mom_input.nml') @logit(logger) @@ -74,3 +74,128 @@ def stage_ens_mem(task_config: AttrDict) -> None: 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() + + +@logit(logger) +def test_hist_date(histfile: str, ref_date: datetime) -> None: + """ + Check that the date in the MOM6 history file is the expected one for the cycle. + TODO: Implement the same for seaice + """ + + ncf = Dataset(histfile, 'r') + hist_date = dparser.parse(ncf.variables['time'].units, fuzzy=True) + timedelta(hours=int(ncf.variables['time'][0])) + ncf.close() + logger.info(f"*** history file date: {hist_date} expected date: {ref_date}") + + if hist_date != ref_date: + raise ValueError(f"FATAL ERROR: Inconsistent bkg date'") + + +@logit(logger) +def gen_bkg_list(bkg_path: str, window_begin=' ', yaml_name='bkg.yaml', ice_rst=False) -> None: + """ + Generate a YAML of the list of backgrounds for the pseudo model + """ + + # Pseudo model parameters (time step, start date) + # TODO: make this a parameter + dt_pseudo = 3 + bkg_date = window_begin + + # Construct list of background file names + cyc = str(os.getenv('cyc')).zfill(2) + gcyc = str((int(cyc) - 6) % 24).zfill(2) # previous cycle + fcst_hrs = list(range(6, 10, dt_pseudo)) + files = [] + for fcst_hr in fcst_hrs: + files.append(os.path.join(bkg_path, f"ocean.bkg.f{str(fcst_hr).zfill(3)}.nc")) + + # Identify the ocean background that will be used for the vertical coordinate remapping + ocn_filename_ic = './INPUT/MOM.res.nc' + test_hist_date(ocn_filename_ic, bkg_date) # assert date of the history file is correct + + # Copy/process backgrounds and generate background yaml list + bkg_list = [] + for bkg in files: + logger.info(f"****************** bkg: {bkg}") + # assert validity of the ocean bkg date, remove basename + bkg_date = bkg_date + timedelta(hours=dt_pseudo) + test_hist_date(bkg, bkg_date) + ocn_filename = os.path.splitext(os.path.basename(bkg))[0] + '.nc' + + # prepare the seaice background, aggregate if the backgrounds are CICE restarts + ice_filename = ocn_filename.replace("ocean", "ice") + + # prepare list of ocean and ice bkg to be copied to RUNDIR + bkg_dict = {'date': bkg_date.strftime('%Y-%m-%dT%H:%M:%SZ'), + 'basename': './bkg/', + 'ocn_filename': ocn_filename, + 'ice_filename': ice_filename, + 'read_from_file': 1} + + bkg_list.append(bkg_dict) + + # save pseudo model yaml configuration + save_as_yaml(bkg_list, yaml_name) + + +@logit(logger) +def clean_empty_obsspaces(config, target, app='var'): + """ + Remove obs spaces that point to non-existent file and save + """ + + # obs space dictionary depth is dependent on the application + if app == 'var': + obs_spaces = config['cost function']['observations']['observers'] + else: + raise ValueError(f"FATAL ERROR: obs space cleaning not implemented for {app}") + + # remove obs spaces that point to a non existant file + cleaned_obs_spaces = [] + for obs_space in obs_spaces: + fname = obs_space['obs space']['obsdatain']['engine']['obsfile'] + if os.path.isfile(fname): + cleaned_obs_spaces.append(obs_space) + else: + logger.info(f"WARNING: {fname} does not exist, removing obs space") + + # update obs spaces + config['cost function']['observations']['observers'] = cleaned_obs_spaces + + # save cleaned yaml + save_as_yaml(config, target) + + +@logit(logger) +def get_mom6_levels(ocnres: str) -> int: + """ + Temporary function that returns the number of vertical levels in MOM6 given the horizontal resolution. + This is requiered by the diffusion saber block that now makes use of oops::util::FieldSetHelpers::writeFieldSet + and requires the number of levels in the configuration. I have been told this will be changed in the future. + + Parameters + ----------- + ocnres: str + Input resolution for ocean in str format. e.g. '500', '100', '050', '025' + + Returns + ------- + nlev: int + number of levels in the ocean model given an input resolution + """ + + # Currently implemented resolutions + ocnres_to_nlev = { + '500': 25, + '100': 75, + '050': 75, + '025': 75 + } + try: + nlev = ocnres_to_nlev.get(ocnres) + except KeyError: + raise KeyError("FATAL ERROR: Invalid ocnres value. Aborting.") + + return nlev diff --git a/versions/build.jet.ver b/versions/build.jet.ver index e103725d41..319a8fa0a6 100644 --- a/versions/build.jet.ver +++ b/versions/build.jet.ver @@ -1,5 +1,5 @@ export stack_intel_ver=2021.5.0 export stack_impi_ver=2021.5.1 -export spack_env=gsi-addon-dev +export spack_env=gsi-addon-intel source "${HOMEgfs:-}/versions/spack.ver" -export spack_mod_path="/lfs4/HFIP/hfv3gfs/role.epic/spack-stack/spack-stack-${spack_stack_ver}/envs/${spack_env}/install/modulefiles/Core" +export spack_mod_path="/contrib/spack-stack/spack-stack-${spack_stack_ver}/envs/${spack_env}/install/modulefiles/Core" diff --git a/versions/fix.ver b/versions/fix.ver index 7c18bea081..b175791196 100644 --- a/versions/fix.ver +++ b/versions/fix.ver @@ -8,7 +8,7 @@ export cice_ver=20240416 export cpl_ver=20230526 export datm_ver=20220805 export gdas_crtm_ver=20220805 -export gdas_fv3jedi_ver=20220805 +export gdas_fv3jedi_ver=20241022 export gdas_soca_ver=20240802 export gdas_gsibec_ver=20240416 export gdas_obs_ver=20240213 diff --git a/versions/run.jet.ver b/versions/run.jet.ver index 1e41fd0036..90553fb277 100644 --- a/versions/run.jet.ver +++ b/versions/run.jet.ver @@ -1,6 +1,6 @@ export stack_intel_ver=2021.5.0 export stack_impi_ver=2021.5.1 -export spack_env=gsi-addon-dev-rocky8 +export spack_env=gsi-addon-intel export hpss_ver= export ncl_ver=6.6.2 @@ -11,4 +11,8 @@ export gempak_ver=7.4.2 export perl_ver=5.38.0 source "${HOMEgfs:-}/versions/spack.ver" -export spack_mod_path="/lfs4/HFIP/hfv3gfs/role.epic/spack-stack/spack-stack-${spack_stack_ver}/envs/${spack_env}/install/modulefiles/Core" +export spack_mod_path="/contrib/spack-stack/spack-stack-${spack_stack_ver}/envs/${spack_env}/install/modulefiles/Core" + +# Local version of TC_tracker +export ens_tracker_ver=v1.1.15.7 + diff --git a/workflow/applications/applications.py b/workflow/applications/applications.py index a694129e38..ecd320d708 100644 --- a/workflow/applications/applications.py +++ b/workflow/applications/applications.py @@ -68,6 +68,7 @@ def __init__(self, conf: Configuration) -> None: self.nens = base.get('NMEM_ENS', 0) self.fcst_segments = base.get('FCST_SEGMENTS', None) + self.interval_gfs = to_timedelta(f"{base.get('INTERVAL_GFS')}H") if not AppConfig.is_monotonic(self.fcst_segments): raise ValueError(f'Forecast segments do not increase monotonically: {",".join(self.fcst_segments)}') @@ -109,9 +110,6 @@ def _init_finalize(self, conf: Configuration): # Save base in the internal state since it is often needed base = self.configs['_no_run']['base'] - # Get more configuration options into the class attributes - self.gfs_cyc = base.get('gfs_cyc') - # Get task names for the application self.task_names = self.get_task_names() @@ -199,19 +197,6 @@ def get_task_names(self, run="_no_run") -> Dict[str, List[str]]: ''' pass - @staticmethod - def get_gfs_interval(gfs_cyc: int) -> timedelta: - """ - return interval in hours based on gfs_cyc - """ - - gfs_internal_map = {'1': '24H', '2': '12H', '4': '6H'} - - try: - return to_timedelta(gfs_internal_map[str(gfs_cyc)]) - except KeyError: - raise KeyError(f'Invalid gfs_cyc = {gfs_cyc}') - @staticmethod def is_monotonic(test_list: List, check_decreasing: bool = False) -> bool: """ diff --git a/workflow/applications/gefs.py b/workflow/applications/gefs.py index c15786a2e8..9d1d5c3dc4 100644 --- a/workflow/applications/gefs.py +++ b/workflow/applications/gefs.py @@ -42,7 +42,6 @@ def _get_app_configs(self): def _update_base(base_in): base_out = base_in.copy() - base_out['INTERVAL_GFS'] = AppConfig.get_gfs_interval(base_in['gfs_cyc']) base_out['RUN'] = 'gefs' return base_out diff --git a/workflow/applications/gfs_cycled.py b/workflow/applications/gfs_cycled.py index 4bb473f454..da78166ede 100644 --- a/workflow/applications/gfs_cycled.py +++ b/workflow/applications/gfs_cycled.py @@ -44,10 +44,10 @@ def _get_app_configs(self): configs += ['anal', 'analdiag'] if self.do_jediocnvar: - configs += ['prepoceanobs', 'ocnanalprep', 'marinebmat', 'ocnanalrun'] + configs += ['prepoceanobs', 'marineanlinit', 'marinebmat', 'marineanlvar'] if self.do_hybvar: configs += ['ocnanalecen'] - configs += ['ocnanalchkpt', 'ocnanalpost'] + configs += ['marineanlchkpt', 'marineanlfinal'] if self.do_vrfy_oceanda: configs += ['ocnanalvrfy'] @@ -128,7 +128,7 @@ def _get_app_configs(self): @staticmethod def _update_base(base_in): - return GFSCycledAppConfig.get_gfs_cyc_dates(base_in) + return base_in def get_task_names(self): """ @@ -146,10 +146,10 @@ def get_task_names(self): gdas_gfs_common_tasks_before_fcst += ['anal'] if self.do_jediocnvar: - gdas_gfs_common_tasks_before_fcst += ['prepoceanobs', 'ocnanalprep', 'marinebmat', 'ocnanalrun'] + gdas_gfs_common_tasks_before_fcst += ['prepoceanobs', 'marineanlinit', 'marinebmat', 'marineanlvar'] if self.do_hybvar: gdas_gfs_common_tasks_before_fcst += ['ocnanalecen'] - gdas_gfs_common_tasks_before_fcst += ['ocnanalchkpt', 'ocnanalpost'] + gdas_gfs_common_tasks_before_fcst += ['marineanlchkpt', 'marineanlfinal'] if self.do_vrfy_oceanda: gdas_gfs_common_tasks_before_fcst += ['ocnanalvrfy'] @@ -297,7 +297,7 @@ def get_task_names(self): tasks['enkfgdas'] = enkfgdas_tasks # Add RUN=gfs tasks if running early cycle - if self.gfs_cyc > 0: + if self.interval_gfs > to_timedelta("0H"): tasks['gfs'] = gfs_tasks if self.do_hybvar and 'gfs' in self.eupd_runs: @@ -307,49 +307,3 @@ def get_task_names(self): tasks['enkfgfs'] = enkfgfs_tasks return tasks - - @staticmethod - def get_gfs_cyc_dates(base: Dict[str, Any]) -> Dict[str, Any]: - """ - Generate GFS dates from experiment dates and gfs_cyc choice - """ - - base_out = base.copy() - - sdate = base['SDATE'] - edate = base['EDATE'] - base_out['INTERVAL'] = to_timedelta(f"{base['assim_freq']}H") - - # Set GFS cycling dates - gfs_cyc = base['gfs_cyc'] - if gfs_cyc != 0: - interval_gfs = AppConfig.get_gfs_interval(gfs_cyc) - hrinc = 0 - hrdet = 0 - if gfs_cyc == 1: - hrinc = 24 - sdate.hour - hrdet = edate.hour - elif gfs_cyc == 2: - if sdate.hour in [0, 12]: - hrinc = 12 - elif sdate.hour in [6, 18]: - hrinc = 6 - if edate.hour in [6, 18]: - hrdet = 6 - elif gfs_cyc == 4: - hrinc = 6 - sdate_gfs = sdate + timedelta(hours=hrinc) - edate_gfs = edate - timedelta(hours=hrdet) - if sdate_gfs > edate: - print('W A R N I N G!') - print('Starting date for GFS cycles is after Ending date of experiment') - print(f'SDATE = {sdate.strftime("%Y%m%d%H")}, EDATE = {edate.strftime("%Y%m%d%H")}') - print(f'SDATE_GFS = {sdate_gfs.strftime("%Y%m%d%H")}, EDATE_GFS = {edate_gfs.strftime("%Y%m%d%H")}') - gfs_cyc = 0 - - base_out['gfs_cyc'] = gfs_cyc - base_out['SDATE_GFS'] = sdate_gfs - base_out['EDATE_GFS'] = edate_gfs - base_out['INTERVAL_GFS'] = interval_gfs - - return base_out diff --git a/workflow/applications/gfs_forecast_only.py b/workflow/applications/gfs_forecast_only.py index 93551ac0cc..fb1d2cdb8f 100644 --- a/workflow/applications/gfs_forecast_only.py +++ b/workflow/applications/gfs_forecast_only.py @@ -78,7 +78,7 @@ def _get_app_configs(self): def _update_base(base_in): base_out = base_in.copy() - base_out['INTERVAL_GFS'] = AppConfig.get_gfs_interval(base_in['gfs_cyc']) + base_out['RUN'] = 'gfs' return base_out diff --git a/workflow/generate_workflows.sh b/workflow/generate_workflows.sh new file mode 100755 index 0000000000..c2565140d3 --- /dev/null +++ b/workflow/generate_workflows.sh @@ -0,0 +1,537 @@ +#!/usr/bin/env bash + +### +function _usage() { + cat << EOF + This script automates the experiment setup process for the global workflow. + Options are also available to update submodules, build the workflow (with + specific build flags), specicy which YAMLs and YAML directory to run, and + whether to automatically update your crontab. + + Usage: generate_workflows.sh [OPTIONS] /path/to/RUNTESTS + or + RUNTESTS=/path/to/RUNTESTS generate_workflows.sh [OPTIONS] + + -H Root directory of the global workflow. + If not specified, then the directory is assumed to be one parent + directory up from this script's residing directory. + + -b Run build_all.sh with default flags + (build the UFS, UPP, UFS_Utils, and GFS-utils only + + -B "build flags" + Run build_all.sh with the build specified flags. Refer to + build_all.sh -h for a list of valid flags. + NOTE: the list of build flags MUST be in quotes. + + -u Update submodules before building and/or generating experiments. + + -y "list of YAMLs to run" + If this option is not specified, the default case (C48_ATM) will be + run. This option is overidden by -G or -E (see below). + Example: -y "C48_ATM C48_S2SW C96C48_hybatmDA" + + -Y /path/to/directory/with/YAMLs + If this option is not specified, then the \${HOMEgfs}/ci/cases/pr + directory is used. + + -G Run all valid GFS cases in the specified YAML directory. + If -b is specified, then "-g -u" (build the GSI and GDASApp) + will be passed to build_all.sh unless -B is also specified. + Note that these builds are disabled on some systems, which + will result in a warning from build_all.sh. + + -E Run all valid GEFS cases in the specified YAML directory. + If -b is specified, then "-w" will be passed to build_all.sh + unless -B is also specified. + + -S (Not yet supported!) + Run all valid SFS cases in the specified YAML directory. + + NOTES: + - Only one of -G -E or -S may be specified + - Valid cases are determined by the experiment:system key as + well as the skip_ci_on_hosts list in each YAML. + + -A "HPC account name" Set the HPC account name. + If this is not set, the default in + \$HOMEgfs/ci/platform/config.\$machine + will be used. + + -c Append the chosen set of tests to your existing crontab + If this option is not chosen, the new entries that would have been + written to your crontab will be printed to stdout. + NOTES: + - This option is not supported on Gaea. Instead, the output will + need to be written to scrontab manually. + - For Orion/Hercules, this option will not work unless run on + the [orion|hercules]-login-1 head node. + + -e "your@email.com" Email address to place in the crontab. + If this option is not specified, then the existing email address in + the crontab will be preserved. + + -t Add a 'tag' to the end of the case names in the pslots to distinguish + pslots between multiple sets of tests. + + -v Verbose mode. Prints output of all commands to stdout. + + -V Very verbose mode. Passes -v to all commands and prints to stdout. + + -d Debug mode. Same as -V but also enables logging (set -x). + + -h Display this message. +EOF +} + +set -eu + +# Set default options +HOMEgfs="" +_specified_home=false +_build=false +_build_flags="" +_explicit_build_flags=false +_update_submods=false +declare -a _yaml_list=("C48_ATM") +_specified_yaml_list=false +_yaml_dir="" # Will be set based off of HOMEgfs if not specified explicitly +_specified_yaml_dir=false +_run_all_gfs=false +_run_all_gefs=false +_run_all_sfs=false +_hpc_account="" +_set_account=false +_update_cron=false +_email="" +_tag="" +_set_email=false +_verbose=false +_very_verbose=false +_verbose_flag="--" +_debug="false" +_cwd=$(pwd) +_runtests="${RUNTESTS:-${_runtests:-}}" +_nonflag_option_count=0 + +while [[ $# -gt 0 && "$1" != "--" ]]; do + while getopts ":H:bB:uy:Y:GESA:ce:t:vVdh" option; do + case "${option}" in + H) + HOMEgfs="${OPTARG}" + _specified_home=true + if [[ ! -d "${HOMEgfs}" ]]; then + echo "Specified HOMEgfs directory (${HOMEgfs}) does not exist" + exit 1 + fi + ;; + b) _build=true ;; + B) _build_flags="${OPTARG}" && _explicit_build_flags=true ;; + u) _update_submods=true ;; + y) # Start over with an empty _yaml_list + declare -a _yaml_list=() + for _yaml in ${OPTARG}; do + # Strip .yaml from the end of each and append to _yaml_list + _yaml_list+=("${_yaml//.yaml/}") + done + _specified_yaml_list=true + ;; + Y) _yaml_dir="${OPTARG}" && _specified_yaml_dir=true ;; + G) _run_all_gfs=true ;; + E) _run_all_gefs=true ;; + S) _run_all_sfs=true ;; + c) _update_cron=true ;; + e) _email="${OPTARG}" && _set_email=true ;; + t) _tag="_${OPTARG}" ;; + v) _verbose=true ;; + V) _very_verbose=true && _verbose=true && _verbose_flag="-v" ;; + d) _debug=true && _very_verbose=true && _verbose=true && _verbose_flag="-v" && PS4='${LINENO}: ' ;; + h) _usage && exit 0 ;; + :) + echo "[${BASH_SOURCE[0]}]: ${option} requires an argument" + _usage + ;; + *) + echo "[${BASH_SOURCE[0]}]: Unrecognized option: ${option}" + _usage + ;; + esac + done + + if [[ ${OPTIND:-0} -gt 0 ]]; then + shift $((OPTIND-1)) + fi + + while [[ $# -gt 0 && ! "$1" =~ ^- ]]; do + _runtests=${1} + (( _nonflag_option_count += 1 )) + if [[ ${_nonflag_option_count} -gt 1 ]]; then + echo "Too many arguments specified." + _usage + exit 2 + fi + shift + done +done + +function send_email() { + # Send an email to $_email. + # Only use this once we get to the long steps (building, etc) and on success. + _subject="${_subject:-generate_workflows.sh failure on ${machine}}" + _body="${1}" + + echo "${_body}" | mail -s "${_subject}" "${_email}" +} + +if [[ -z "${_runtests}" ]]; then + echo "Mising run directory (RUNTESTS) argument/environment variable." + sleep 2 + _usage + exit 3 +fi + +# Turn on logging if running in debug mode +if [[ "${_debug}" == "true" ]]; then + set -x +fi + +# Create the RUNTESTS directory +[[ "${_verbose}" == "true" ]] && printf "Creating RUNTESTS in %s\n\n" "${_runtests}" +if [[ ! -d "${_runtests}" ]]; then + set +e + if ! mkdir -p "${_runtests}" "${_verbose_flag}"; then + echo "Unable to create RUNTESTS directory: ${_runtests}" + echo "Rerun with -h for usage examples." + exit 4 + fi + set -e +else + echo "The RUNTESTS directory ${_runtests} already exists." + echo "Would you like to remove it?" + _attempts=0 + while read -r _from_stdin; do + if [[ "${_from_stdin^^}" =~ Y ]]; then + rm -rf "${_runtests}" + mkdir -p "${_runtests}" + break + elif [[ "${_from_stdin^^}" =~ N ]]; then + echo "Continuing without removing the directory" + break + else + (( _attempts+=1 )) + if [[ ${_attempts} == 3 ]]; then + echo "Exiting." + exit 99 + fi + echo "'${_from_stdin}' is not a valid choice. Please type Y or N" + fi + done +fi + +# Test if multiple "run_all" options were set +_count_run_alls=0 +[[ "${_run_all_gfs}" == "true" ]] && ((_count_run_alls+=1)) +[[ "${_run_all_gefs}" == "true" ]] && ((_count_run_alls+=1)) +[[ "${_run_all_sfs}" == "true" ]] && ((_count_run_alls+=1)) + +if (( _count_run_alls > 1 )) ; then + echo "Only one run all option (-G -E -S) may be specified" + echo "Rerun with just one option and/or with -h for usage examples" + exit 5 +fi + +# If -S is specified, exit (for now). +# TODO when SFS tests come online, enable this option. +if [[ "${_run_all_sfs}" == "true" ]]; then + echo "There are no known SFS tests at this time. Aborting." + echo "If you have prepared YAMLs for SFS cases, specify their" + echo "location and names without '-S', e.g." + echo "generate_workflows.sh -y \"C48_S2S_SFS\" -Y \"/path/to/yaml/directory\"" + exit 0 +fi + +# Set HOMEgfs if it wasn't set by the user +if [[ "${_specified_home}" == "false" ]]; then + script_relpath="$(dirname "${BASH_SOURCE[0]}")" + HOMEgfs="$(cd "${script_relpath}/.." && pwd)" + [[ "${_verbose}" == "true" ]] && printf "Setting HOMEgfs to %s\n\n" "${HOMEgfs}" +fi + +# Set the _yaml_dir to HOMEgfs/ci/cases/pr if not explicitly set +[[ "${_specified_yaml_dir}" == false ]] && _yaml_dir="${HOMEgfs}/ci/cases/pr" + +function select_all_yamls() +{ + # A helper function to select all of the YAMLs for a specified system (gfs, gefs, sfs) + + # This function is called if -G, -E, or -S are specified either with or without a + # specified YAML list. If a YAML list was specified, this function will remove any + # YAMLs in that list that are not for the specified system and issue warnings when + # doing so. + + _system="${1}" + _SYSTEM="${_system^^}" + + # Bash cannot return an array from a function and any edits are descoped at + # the end of the function, so use a nameref instead. + local -n _nameref_yaml_list='_yaml_list' + + if [[ "${_specified_yaml_list}" == false ]]; then + # Start over with an empty _yaml_list + _nameref_yaml_list=() + printf "Running all %s cases in %s\n\n" "${_SYSTEM}" "${_yaml_dir}" + _yaml_count=0 + + for _full_path in "${_yaml_dir}/"*.yaml; do + # Skip any YAML that isn't supported + if ! grep -l "system: *${_system}" "${_full_path}" >& /dev/null ; then continue; fi + + # Select only cases for the specified system + _yaml=$(basename "${_full_path}") + # Strip .yaml from the filename to get the case name + _yaml="${_yaml//.yaml/}" + _nameref_yaml_list+=("${_yaml}") + [[ "${_verbose}" == true ]] && echo "Found test ${_yaml//.yaml/}" + (( _yaml_count+=1 )) + done + + if [[ ${_yaml_count} -eq 0 ]]; then + read -r -d '' _message << EOM + "No YAMLs or ${_SYSTEM} were found in the directory (${_yaml_dir})!" + "Please check the directory/YAMLs and try again" +EOM + echo "${_message}" + if [[ "${_set_email}" == true ]]; then + send_email "${_message}" + fi + exit 6 + fi + else + # Check if the specified yamls are for the specified system + for i in "${!_nameref_yaml_list}"; do + _yaml="${_nameref_yaml_list[${i}]}" + _found=$(grep -l "system: *${system}" "${_yaml_dir}/${_yaml}.yaml") + if [[ -z "${_found}" ]]; then + echo "WARNING the yaml file ${_yaml_dir}/${_yaml}.yaml is not designed for the ${_SYSTEM} system" + echo "Removing this yaml from the set of cases to run" + unset '_nameref_yaml_list[${i}]' + # Sleep 2 seconds to give the user a moment to react + sleep 2s + fi + done + fi +} + +# Check if running all GEFS cases +if [[ "${_run_all_gefs}" == "true" ]]; then + # Append -w to build_all.sh flags if -E was specified + if [[ "${_explicit_build_flags}" == "false" && "${_build}" == "true" ]]; then + _build_flags="-w" + fi + + select_all_yamls "gefs" +fi + +# Check if running all SFS cases +if [[ "${_run_all_gfs}" == "true" ]]; then + # Append -g -u to build_all.sh flags if -G was specified + if [[ "${_explicit_build_flags}" == "false" && "${_build}" == "true" ]]; then + _build_flags="-g -u" + fi + + select_all_yamls "gfs" +fi + +# Loading modules sometimes raises unassigned errors, so disable checks +set +u +[[ "${_verbose}" == "true" ]] && printf "Loading modules\n\n" +[[ "${_debug}" == "true" ]] && set +x +if ! source "${HOMEgfs}/workflow/gw_setup.sh" >& stdout; then + cat stdout + echo "Failed to source ${HOMEgfs}/workflow/gw_setup.sh!" + exit 7 +fi +[[ "${_verbose}" == "true" ]] && cat stdout +rm -f stdout +[[ "${_debug}" == "true" ]] && set -x +set -u +machine=${MACHINE_ID} +. "${HOMEgfs}/ci/platforms/config.${machine}" + +# If _yaml_dir is not set, set it to $HOMEgfs/ci/cases/pr +if [[ -z ${_yaml_dir} ]]; then + _yaml_dir="${HOMEgfs}/ci/cases/pr" +fi + +# Update submodules if requested +if [[ "${_update_submods}" == "true" ]]; then + printf "Updating submodules\n\n" + _git_cmd="git submodule update --init --recursive -j 10" + if [[ "${_verbose}" == true ]]; then + ${_git_cmd} + else + if ! ${_git_cmd} 2> stderr 1> stdout; then + cat stdout stderr + read -r -d '' _message << EOM +The git command (${_git_cmd}) failed with a non-zero status +Messages from git: +EOM + _newline=$'\n' + _message="${_message}${_newline}$(cat stdout stderr)" + if [[ "${_set_email}" == true ]]; then + send_email "${_message}" + fi + echo "${_message}" + rm -f stdout stderr + exit 8 + fi + rm -f stdout stderr + fi +fi + +# Build the system if requested +if [[ "${_build}" == "true" ]]; then + printf "Building via build_all.sh %s\n\n" "${_build_flags}" + # Let the output of build_all.sh go to stdout regardless of verbose options + #shellcheck disable=SC2086,SC2248 + ${HOMEgfs}/sorc/build_all.sh ${_build_flags} ${_verbose_flag} +fi + +# Link the workflow silently unless there's an error +[[ "${_verbose}" == true ]] && printf "Linking the workflow\n\n" +if ! "${HOMEgfs}/sorc/link_workflow.sh" >& stdout; then + cat stdout + echo "link_workflow.sh failed!" + if [[ "${_set_email}" == true ]]; then + _stdout=$(cat stdout) + send_email "link_workflow.sh failed with the message"$'\n'"${_stdout}" + fi + rm -f stdout + exit 9 +fi +rm -f stdout + +# Configure the environment for running create_experiment.py +[[ "${_verbose}" == true ]] && printf "Setting up the environment to run create_experiment.py\n\n" +for i in "${!_yaml_list[@]}"; do + _yaml_file="${_yaml_dir}/${_yaml_list[${i}]}.yaml" + # Verify that the YAMLs are where we are pointed + if [[ ! -s "${_yaml_file}" ]]; then + echo "The YAML file ${_yaml_file} does not exist!" + echo "Please check the input yaml list and directory." + if [[ "${_set_email}" == true ]]; then + read -r -d '' _message << EOM + generate_workflows.sh failed to find one of the specified YAMLs (${_yaml_file}) + in the specified YAML directory (${_yaml_dir}). +EOM + send_email "${_message}" + fi + exit 10 + fi + + # Strip any unsupported tests + _unsupported_systems=$(sed '1,/skip_ci_on_hosts/ d' "${_yaml_file}") + + for _system in ${_unsupported_systems}; do + if [[ "${_system}" =~ ${machine} ]]; then + if [[ "${_specified_yaml_list}" == true ]]; then + printf "WARNING %s is unsupported on %s, removing from case list\n\n" "${_yaml}" "${machine}" + if [[ "${_set_email}" == true ]]; then + _final_message="${_final_message:-}"$'\n'"The specified YAML case ${_yaml} is not supported on ${machine} and was skipped." + fi + # Sleep so the user has a moment to notice + sleep 2s + fi + unset '_yaml_list[${i}]' + break + fi + done +done + +# Update the account if specified +[[ "${_set_account}" == true ]] && export HPC_ACCOUNT=${_hpc_account} && \ + [[ "${_verbose}" == true ]] && printf "Setting HPC account to %s\n\n" "${HPC_ACCOUNT}" + +# Create the experiments +rm -f "tests.cron" "${_verbose_flag}" +echo "Running create_experiment.py for ${#_yaml_list[@]} cases" + +[[ "${_verbose}" == true ]] && printf "Selected cases: %s\n\n" "${_yaml_list[*]}" +for _case in "${_yaml_list[@]}"; do + [[ "${_verbose}" == false ]] && echo "${_case}" + _pslot="${_case}${_tag}" + _create_exp_cmd="./create_experiment.py -y ../ci/cases/pr/${_case}.yaml --overwrite" + if [[ "${_verbose}" == true ]]; then + pslot=${_pslot} RUNTESTS=${_runtests} ${_create_exp_cmd} + else + if ! pslot=${_pslot} RUNTESTS=${_runtests} ${_create_exp_cmd} 2> stderr 1> stdout; then + _output=$(cat stdout stderr) + _message="The create_experiment command (${_create_exp_cmd}) failed with a non-zero status. Output:" + _message="${_message}"$'\n'"${_output}" + if [[ "${_set_email}" == true ]]; then + send_email "${_message}" + fi + echo "${_message}" + rm -f stdout stderr + exit 11 + fi + rm -f stdout stderr + fi + grep "${_pslot}" "${_runtests}/EXPDIR/${_pslot}/${_pslot}.crontab" >> tests.cron +done +echo + +# Update the cron +if [[ "${_update_cron}" == "true" ]]; then + printf "Updating the existing crontab\n\n" + echo + rm -f existing.cron final.cron "${_verbose_flag}" + touch existing.cron final.cron + + # disable -e in case crontab is empty + set +e + crontab -l > existing.cron + set -e + + if [[ "${_debug}" == "true" ]]; then + echo "Existing crontab: " + echo "#######################" + cat existing.cron + echo "#######################" + fi + + if [[ "${_set_email}" == "true" ]]; then + # Replace the existing email in the crontab + [[ "${_verbose}" == "true" ]] && printf "Updating crontab email to %s\n\n" "${_email}" + sed -i "/^MAILTO/d" existing.cron + echo "MAILTO=\"${_email}\"" >> final.cron + fi + + cat existing.cron tests.cron >> final.cron + + if [[ "${_verbose}" == "true" ]]; then + echo "Setting crontab to:" + echo "#######################" + cat final.cron + echo "#######################" + fi + + crontab final.cron +else + _message="Add the following to your crontab or scrontab to start running:" + _cron_tests=$(cat tests.cron) + _message="${_message}"$'\n'"${_cron_tests}" + echo "${_message}" + if [[ "${_set_email}" == true ]]; then + final_message="${final_message:-}"$'\n'"${_message}" + fi +fi + +# Cleanup +[[ "${_debug}" == "false" ]] && rm -f final.cron existing.cron tests.cron "${_verbose_flag}" + +echo "Success!!" +if [[ "${_set_email}" == true ]]; then + final_message=$'Success!\n'"${final_message:-}" + _subject="generate_workflow.sh completed successfully" send_email "${final_message}" +fi diff --git a/workflow/hosts.py b/workflow/hosts.py index 34ea067ade..7bde58f95f 100644 --- a/workflow/hosts.py +++ b/workflow/hosts.py @@ -42,7 +42,7 @@ def detect(cls): machine = 'HERA' elif os.path.exists('/work/noaa'): machine = socket.gethostname().split("-", 1)[0].upper() - elif os.path.exists('/lfs4/HFIP'): + elif os.path.exists('/lfs5/HFIP'): machine = 'JET' elif os.path.exists('/lfs/f1'): machine = 'WCOSS2' diff --git a/workflow/hosts/awspw.yaml b/workflow/hosts/awspw.yaml index a9c708253e..ef17d8f2f4 100644 --- a/workflow/hosts/awspw.yaml +++ b/workflow/hosts/awspw.yaml @@ -18,6 +18,7 @@ CHGRP_RSTPROD: 'YES' CHGRP_CMD: 'chgrp rstprod' # TODO: This is not yet supported. HPSSARCH: 'NO' HPSS_PROJECT: emc-global #TODO: See `ATARDIR` below. +BASE_DATA: '/bucket/global-workflow-shared-data' BASE_IC: '/bucket/global-workflow-shared-data/ICSDIR' LOCALARCH: 'NO' ATARDIR: '' # TODO: This will not yet work from AWS. diff --git a/workflow/hosts/azurepw.yaml b/workflow/hosts/azurepw.yaml index d59736e653..1769f9ee19 100644 --- a/workflow/hosts/azurepw.yaml +++ b/workflow/hosts/azurepw.yaml @@ -18,6 +18,7 @@ CHGRP_RSTPROD: 'YES' CHGRP_CMD: 'chgrp rstprod' # TODO: This is not yet supported. HPSSARCH: 'NO' HPSS_PROJECT: emc-global #TODO: See `ATARDIR` below. +BASE_DATA: '/bucket/global-workflow-shared-data' BASE_IC: '/bucket/global-workflow-shared-data/ICSDIR' LOCALARCH: 'NO' ATARDIR: '' # TODO: This will not yet work from AZURE. diff --git a/workflow/hosts/gaea.yaml b/workflow/hosts/gaea.yaml index 9297fed24a..5a37b5dabf 100644 --- a/workflow/hosts/gaea.yaml +++ b/workflow/hosts/gaea.yaml @@ -1,5 +1,6 @@ BASE_GIT: '/gpfs/f5/ufs-ard/world-shared/global/glopara/data/git' DMPDIR: '/gpfs/f5/ufs-ard/world-shared/global/glopara/data/dump' +BASE_DATA: '/gpfs/f5/ufs-ard/world-shared/global/glopara/data' BASE_IC: '/gpfs/f5/ufs-ard/world-shared/global/glopara/data/ICSDIR' PACKAGEROOT: '/gpfs/f5/ufs-ard/world-shared/global/glopara/data/nwpara' COMROOT: '/gpfs/f5/ufs-ard/world-shared/global/glopara/data/com' diff --git a/workflow/hosts/googlepw.yaml b/workflow/hosts/googlepw.yaml index 2bd9439d5f..daf6cd1eb2 100644 --- a/workflow/hosts/googlepw.yaml +++ b/workflow/hosts/googlepw.yaml @@ -18,6 +18,7 @@ CHGRP_RSTPROD: 'YES' CHGRP_CMD: 'chgrp rstprod' # TODO: This is not yet supported. HPSSARCH: 'NO' HPSS_PROJECT: emc-global #TODO: See `ATARDIR` below. +BASE_DATA: '/bucket/global-workflow-shared-data' BASE_IC: '/bucket/global-workflow-shared-data/ICSDIR' LOCALARCH: 'NO' ATARDIR: '' # TODO: This will not yet work from GOOGLE. diff --git a/workflow/hosts/hera.yaml b/workflow/hosts/hera.yaml index 4ace199470..fa2c351aa1 100644 --- a/workflow/hosts/hera.yaml +++ b/workflow/hosts/hera.yaml @@ -1,5 +1,6 @@ BASE_GIT: '/scratch1/NCEPDEV/global/glopara/git' DMPDIR: '/scratch1/NCEPDEV/global/glopara/dump' +BASE_DATA: '/scratch1/NCEPDEV/global/glopara/data' BASE_IC: '/scratch1/NCEPDEV/global/glopara/data/ICSDIR' PACKAGEROOT: '/scratch1/NCEPDEV/global/glopara/nwpara' COMINsyn: '/scratch1/NCEPDEV/global/glopara/com/gfs/prod/syndat' diff --git a/workflow/hosts/hercules.yaml b/workflow/hosts/hercules.yaml index 9d6339a48e..73fde6cde6 100644 --- a/workflow/hosts/hercules.yaml +++ b/workflow/hosts/hercules.yaml @@ -1,5 +1,6 @@ BASE_GIT: '/work/noaa/global/glopara/git_rocky9' DMPDIR: '/work/noaa/rstprod/dump' +BASE_DATA: '/work/noaa/global/glopara/data' BASE_IC: '/work/noaa/global/glopara/data/ICSDIR' PACKAGEROOT: '/work/noaa/global/glopara/nwpara' COMINsyn: '/work/noaa/global/glopara/com/gfs/prod/syndat' diff --git a/workflow/hosts/jet.yaml b/workflow/hosts/jet.yaml index 21e815c9b2..a53224fe52 100644 --- a/workflow/hosts/jet.yaml +++ b/workflow/hosts/jet.yaml @@ -1,11 +1,12 @@ -BASE_GIT: '/lfs4/HFIP/hfv3gfs/glopara/git' -DMPDIR: '/lfs4/HFIP/hfv3gfs/glopara/dump' -BASE_IC: '/mnt/lfs4/HFIP/hfv3gfs/glopara/data/ICSDIR' -PACKAGEROOT: '/lfs4/HFIP/hfv3gfs/glopara/nwpara' -COMINsyn: '/lfs4/HFIP/hfv3gfs/glopara/com/gfs/prod/syndat' -HOMEDIR: '/lfs4/HFIP/hfv3gfs/${USER}' -STMP: '/lfs4/HFIP/hfv3gfs/${USER}/stmp' -PTMP: '/lfs4/HFIP/hfv3gfs/${USER}/ptmp' +BASE_GIT: '/lfs5/HFIP/hfv3gfs/glopara/git' +DMPDIR: '/lfs5/HFIP/hfv3gfs/glopara/dump' +BASE_DATA: '/lfs5/HFIP/hfv3gfs/glopara/data' +BASE_IC: '/mnt/lfs5/HFIP/hfv3gfs/glopara/data/ICSDIR' +PACKAGEROOT: '/lfs5/HFIP/hfv3gfs/glopara/nwpara' +COMINsyn: '/lfs5/HFIP/hfv3gfs/glopara/com/gfs/prod/syndat' +HOMEDIR: '/lfs5/HFIP/hfv3gfs/${USER}' +STMP: '/lfs5/HFIP/hfv3gfs/${USER}/stmp' +PTMP: '/lfs5/HFIP/hfv3gfs/${USER}/ptmp' NOSCRUB: $HOMEDIR ACCOUNT: hfv3gfs SCHEDULER: slurm @@ -24,6 +25,6 @@ ATARDIR: '/NCEPDEV/${HPSS_PROJECT}/1year/${USER}/${machine}/scratch/${PSLOT}' MAKE_NSSTBUFR: 'NO' MAKE_ACFTBUFR: 'NO' SUPPORTED_RESOLUTIONS: ['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 +COMINecmwf: /mnt/lfs5/HFIP/hfv3gfs/glopara/data/external_gempak/ecmwf +COMINnam: /mnt/lfs5/HFIP/hfv3gfs/glopara/data/external_gempak/nam +COMINukmet: /mnt/lfs5/HFIP/hfv3gfs/glopara/data/external_gempak/ukmet diff --git a/workflow/hosts/orion.yaml b/workflow/hosts/orion.yaml index 4ec78fc8cc..d47b2b2bab 100644 --- a/workflow/hosts/orion.yaml +++ b/workflow/hosts/orion.yaml @@ -1,5 +1,6 @@ BASE_GIT: '/work/noaa/global/glopara/git_rocky9' DMPDIR: '/work/noaa/rstprod/dump' +BASE_DATA: '/work/noaa/global/glopara/data' BASE_IC: '/work/noaa/global/glopara/data/ICSDIR' PACKAGEROOT: '/work/noaa/global/glopara/nwpara' COMINsyn: '/work/noaa/global/glopara/com/gfs/prod/syndat' diff --git a/workflow/hosts/s4.yaml b/workflow/hosts/s4.yaml index c2af9728f2..b93fefec39 100644 --- a/workflow/hosts/s4.yaml +++ b/workflow/hosts/s4.yaml @@ -1,5 +1,6 @@ BASE_GIT: '/data/prod/glopara/git' DMPDIR: '/data/prod/glopara/dump' +BASE_DATA: '/data/prod/glopara' BASE_IC: '/data/prod/glopara/coupled_ICs' PACKAGEROOT: '/data/prod/glopara/nwpara' COMINsyn: '/data/prod/glopara/com/gfs/prod/syndat' diff --git a/workflow/hosts/wcoss2.yaml b/workflow/hosts/wcoss2.yaml index bf2cc41c45..15f0705f91 100644 --- a/workflow/hosts/wcoss2.yaml +++ b/workflow/hosts/wcoss2.yaml @@ -1,5 +1,6 @@ BASE_GIT: '/lfs/h2/emc/global/save/emc.global/git' DMPDIR: '/lfs/h2/emc/dump/noscrub/dump' +BASE_DATA: '/lfs/h2/emc/global/noscrub/emc.global/data' BASE_IC: '/lfs/h2/emc/global/noscrub/emc.global/data/ICSDIR' PACKAGEROOT: '${PACKAGEROOT:-"/lfs/h1/ops/prod/packages"}' COMINsyn: '/lfs/h1/ops/prod/com/gfs/v16.3/syndat' diff --git a/workflow/rocoto/gefs_tasks.py b/workflow/rocoto/gefs_tasks.py index e214bb8c19..e9338c90df 100644 --- a/workflow/rocoto/gefs_tasks.py +++ b/workflow/rocoto/gefs_tasks.py @@ -12,7 +12,7 @@ def __init__(self, app_config: AppConfig, run: str) -> None: def stage_ic(self): resources = self.get_resource('stage_ic') - task_name = f'stage_ic' + task_name = f'gefs_stage_ic' task_dict = {'task_name': task_name, 'resources': resources, 'envars': self.envars, @@ -29,7 +29,7 @@ def stage_ic(self): def waveinit(self): resources = self.get_resource('waveinit') - task_name = f'wave_init' + task_name = f'gefs_wave_init' task_dict = {'task_name': task_name, 'resources': resources, 'envars': self.envars, @@ -45,12 +45,12 @@ def waveinit(self): def prep_emissions(self): deps = [] - dep_dict = {'type': 'task', 'name': f'stage_ic'} + dep_dict = {'type': 'task', 'name': f'gefs_stage_ic'} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep=deps) resources = self.get_resource('prep_emissions') - task_name = 'prep_emissions' + task_name = 'gefs_prep_emissions' task_dict = {'task_name': task_name, 'resources': resources, 'envars': self.envars, @@ -66,15 +66,15 @@ def prep_emissions(self): def fcst(self): dependencies = [] - dep_dict = {'type': 'task', 'name': f'stage_ic'} + dep_dict = {'type': 'task', 'name': f'gefs_stage_ic'} dependencies.append(rocoto.add_dependency(dep_dict)) if self.app_config.do_wave: - dep_dict = {'type': 'task', 'name': f'wave_init'} + dep_dict = {'type': 'task', 'name': f'gefs_wave_init'} dependencies.append(rocoto.add_dependency(dep_dict)) if self.app_config.do_aero: - dep_dict = {'type': 'task', 'name': f'prep_emissions'} + dep_dict = {'type': 'task', 'name': f'gefs_prep_emissions'} dependencies.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep_condition='and', dep=dependencies) @@ -87,7 +87,7 @@ def fcst(self): fcst_vars.append(rocoto.create_envar(name=key, value=str(value))) resources = self.get_resource('fcst') - task_name = f'fcst_mem000_seg#seg#' + task_name = f'gefs_fcst_mem000_seg#seg#' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -100,7 +100,7 @@ def fcst(self): } seg_var_dict = {'seg': ' '.join([f"{seg}" for seg in range(0, num_fcst_segments)])} - metatask_dict = {'task_name': f'fcst_mem000', + metatask_dict = {'task_name': f'gefs_fcst_mem000', 'is_serial': True, 'var_dict': seg_var_dict, 'task_dict': task_dict @@ -112,15 +112,15 @@ def fcst(self): def efcs(self): dependencies = [] - dep_dict = {'type': 'task', 'name': f'stage_ic'} + dep_dict = {'type': 'task', 'name': f'gefs_stage_ic'} dependencies.append(rocoto.add_dependency(dep_dict)) if self.app_config.do_wave: - dep_dict = {'type': 'task', 'name': f'wave_init'} + dep_dict = {'type': 'task', 'name': f'gefs_wave_init'} dependencies.append(rocoto.add_dependency(dep_dict)) if self.app_config.do_aero: - dep_dict = {'type': 'task', 'name': f'prep_emissions'} + dep_dict = {'type': 'task', 'name': f'gefs_prep_emissions'} dependencies.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep_condition='and', dep=dependencies) @@ -144,7 +144,7 @@ def efcs(self): for key, value in efcsenvars_dict.items(): efcsenvars.append(rocoto.create_envar(name=key, value=str(value))) - task_name = f'fcst_mem{member}_seg#seg#' + task_name = f'gefs_fcst_mem{member}_seg#seg#' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -157,7 +157,7 @@ def efcs(self): } seg_var_dict = {'seg': ' '.join([f"{seg}" for seg in range(0, num_fcst_segments)])} - seg_metatask_dict = {'task_name': f'fcst_mem{member}', + seg_metatask_dict = {'task_name': f'gefs_fcst_mem{member}', 'is_serial': True, 'var_dict': seg_var_dict, 'task_dict': task_dict @@ -170,7 +170,7 @@ def efcs(self): # Keeping this in hopes the kludge is no longer necessary at some point # # member_var_dict = {'member': ' '.join([f"{mem:03d}" for mem in range(1, self.nmem + 1)])} - # mem_metatask_dict = {'task_name': 'fcst_ens', + # mem_metatask_dict = {'task_name': 'gefs_fcst_ens', # 'is_serial': False, # 'var_dict': member_var_dict, # 'task_dict': seg_metatask_dict @@ -215,7 +215,7 @@ def _atmosoceaniceprod(self, component: str): 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': 'metatask', 'name': 'fcst_mem#member#'} + dep_dict = {'type': 'metatask', 'name': 'gefs_fcst_mem#member#'} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep=deps, dep_condition='or') @@ -227,7 +227,7 @@ def _atmosoceaniceprod(self, component: str): 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_name = f'gefs_{component}_prod_mem#member#_f#fhr#' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -254,12 +254,12 @@ def _atmosoceaniceprod(self, component: str): 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#', + fhr_metatask_dict = {'task_name': f'gefs_{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', + member_metatask_dict = {'task_name': f'gefs_{component}_prod', 'task_dict': fhr_metatask_dict, 'var_dict': member_var_dict} @@ -273,7 +273,7 @@ def atmos_ensstat(self): deps = [] for member in range(0, self.nmem + 1): - task = f'atmos_prod_mem{member:03d}_f#fhr#' + task = f'gefs_atmos_prod_mem{member:03d}_f#fhr#' dep_dict = {'type': 'task', 'name': task} deps.append(rocoto.add_dependency(dep_dict)) @@ -284,7 +284,7 @@ def atmos_ensstat(self): 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_name = f'gefs_atmos_ensstat_f#fhr#' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -304,7 +304,7 @@ def atmos_ensstat(self): fhr_var_dict = {'fhr': ' '.join([f"{fhr:03d}" for fhr in fhrs])} - fhr_metatask_dict = {'task_name': f'atmos_ensstat', + fhr_metatask_dict = {'task_name': f'gefs_atmos_ensstat', 'task_dict': task_dict, 'var_dict': fhr_var_dict} @@ -314,7 +314,7 @@ def atmos_ensstat(self): def wavepostsbs(self): deps = [] - dep_dict = {'type': 'metatask', 'name': f'fcst_mem#member#'} + dep_dict = {'type': 'metatask', 'name': f'gefs_fcst_mem#member#'} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep=deps) @@ -327,7 +327,7 @@ def wavepostsbs(self): resources = self.get_resource('wavepostsbs') - task_name = f'wave_post_grid_mem#member#' + task_name = f'gefs_wave_post_grid_mem#member#' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -340,7 +340,7 @@ def wavepostsbs(self): } 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', + member_metatask_dict = {'task_name': 'gefs_wave_post_grid', 'task_dict': task_dict, 'var_dict': member_var_dict } @@ -351,7 +351,7 @@ def wavepostsbs(self): def wavepostbndpnt(self): deps = [] - dep_dict = {'type': 'metatask', 'name': f'fcst_mem#member#'} + dep_dict = {'type': 'metatask', 'name': f'gefs_fcst_mem#member#'} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep=deps) @@ -363,7 +363,7 @@ def wavepostbndpnt(self): 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_name = f'gefs_wave_post_bndpnt_mem#member#' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -376,7 +376,7 @@ def wavepostbndpnt(self): } 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', + member_metatask_dict = {'task_name': 'gefs_wave_post_bndpnt', 'task_dict': task_dict, 'var_dict': member_var_dict } @@ -396,7 +396,7 @@ def wavepostbndpntbll(self): dep_dict = {'type': 'data', 'data': data} deps.append(rocoto.add_dependency(dep_dict)) - dep_dict = {'type': 'metatask', 'name': f'fcst_mem#member#'} + dep_dict = {'type': 'metatask', 'name': f'gefs_fcst_mem#member#'} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep_condition='or', dep=deps) @@ -408,7 +408,7 @@ def wavepostbndpntbll(self): 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_name = f'gefs_wave_post_bndpnt_bull_mem#member#' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -421,7 +421,7 @@ def wavepostbndpntbll(self): } 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', + member_metatask_dict = {'task_name': 'gefs_wave_post_bndpnt_bull', 'task_dict': task_dict, 'var_dict': member_var_dict } @@ -432,10 +432,10 @@ def wavepostbndpntbll(self): def wavepostpnt(self): deps = [] - dep_dict = {'type': 'metatask', 'name': f'fcst_mem#member#'} + dep_dict = {'type': 'metatask', 'name': f'gefs_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#'} + dep_dict = {'type': 'task', 'name': f'gefs_wave_post_bndpnt_bull_mem#member#'} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep_condition='and', dep=deps) @@ -447,7 +447,7 @@ def wavepostpnt(self): 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_name = f'gefs_wave_post_pnt_mem#member#' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -460,7 +460,7 @@ def wavepostpnt(self): } 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', + member_metatask_dict = {'task_name': 'gefs_wave_post_pnt', 'task_dict': task_dict, 'var_dict': member_var_dict } @@ -472,16 +472,16 @@ def wavepostpnt(self): def extractvars(self): deps = [] if self.app_config.do_wave: - dep_dict = {'type': 'task', 'name': 'wave_post_grid_mem#member#'} + dep_dict = {'type': 'task', 'name': 'gefs_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#'} + dep_dict = {'type': 'metatask', 'name': 'gefs_ocean_prod_#member#'} deps.append(rocoto.add_dependency(dep_dict)) if self.app_config.do_ice: - dep_dict = {'type': 'metatask', 'name': 'ice_prod_#member#'} + dep_dict = {'type': 'metatask', 'name': 'gefs_ice_prod_#member#'} deps.append(rocoto.add_dependency(dep_dict)) if self.app_config.do_atm: - dep_dict = {'type': 'metatask', 'name': 'atmos_prod_#member#'} + dep_dict = {'type': 'metatask', 'name': 'gefs_atmos_prod_#member#'} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep_condition='and', dep=deps) extractvars_envars = self.envars.copy() @@ -492,7 +492,7 @@ def extractvars(self): extractvars_envars.append(rocoto.create_envar(name=key, value=str(value))) resources = self.get_resource('extractvars') - task_name = f'extractvars_mem#member#' + task_name = f'gefs_extractvars_mem#member#' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -505,7 +505,7 @@ def extractvars(self): } member_var_dict = {'member': ' '.join([str(mem).zfill(3) for mem in range(0, self.nmem + 1)])} - member_metatask_dict = {'task_name': 'extractvars', + member_metatask_dict = {'task_name': 'gefs_extractvars', 'task_dict': task_dict, 'var_dict': member_var_dict } @@ -516,28 +516,28 @@ def extractvars(self): def arch(self): deps = [] - dep_dict = {'type': 'metatask', 'name': 'atmos_prod'} + dep_dict = {'type': 'metatask', 'name': 'gefs_atmos_prod'} deps.append(rocoto.add_dependency(dep_dict)) - dep_dict = {'type': 'metatask', 'name': 'atmos_ensstat'} + dep_dict = {'type': 'metatask', 'name': 'gefs_atmos_ensstat'} deps.append(rocoto.add_dependency(dep_dict)) if self.app_config.do_ice: - dep_dict = {'type': 'metatask', 'name': 'ice_prod'} + dep_dict = {'type': 'metatask', 'name': 'gefs_ice_prod'} deps.append(rocoto.add_dependency(dep_dict)) if self.app_config.do_ocean: - dep_dict = {'type': 'metatask', 'name': 'ocean_prod'} + dep_dict = {'type': 'metatask', 'name': 'gefs_ocean_prod'} deps.append(rocoto.add_dependency(dep_dict)) if self.app_config.do_wave: - dep_dict = {'type': 'metatask', 'name': 'wave_post_grid'} + dep_dict = {'type': 'metatask', 'name': 'gefs_wave_post_grid'} deps.append(rocoto.add_dependency(dep_dict)) - dep_dict = {'type': 'metatask', 'name': 'wave_post_pnt'} + dep_dict = {'type': 'metatask', 'name': 'gefs_wave_post_pnt'} deps.append(rocoto.add_dependency(dep_dict)) if self.app_config.do_wave_bnd: - dep_dict = {'type': 'metatask', 'name': 'wave_post_bndpnt'} + dep_dict = {'type': 'metatask', 'name': 'gefs_wave_post_bndpnt'} deps.append(rocoto.add_dependency(dep_dict)) - dep_dict = {'type': 'metatask', 'name': 'wave_post_bndpnt_bull'} + dep_dict = {'type': 'metatask', 'name': 'gefs_wave_post_bndpnt_bull'} deps.append(rocoto.add_dependency(dep_dict)) if self.app_config.do_extractvars: - dep_dict = {'type': 'metatask', 'name': 'extractvars'} + dep_dict = {'type': 'metatask', 'name': 'gefs_extractvars'} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep=deps, dep_condition='and') @@ -565,30 +565,30 @@ def cleanup(self): deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep=deps) else: - dep_dict = {'type': 'metatask', 'name': 'atmos_prod'} + dep_dict = {'type': 'metatask', 'name': 'gefs_atmos_prod'} deps.append(rocoto.add_dependency(dep_dict)) - dep_dict = {'type': 'metatask', 'name': 'atmos_ensstat'} + dep_dict = {'type': 'metatask', 'name': 'gefs_atmos_ensstat'} deps.append(rocoto.add_dependency(dep_dict)) if self.app_config.do_ice: - dep_dict = {'type': 'metatask', 'name': 'ice_prod'} + dep_dict = {'type': 'metatask', 'name': 'gefs_ice_prod'} deps.append(rocoto.add_dependency(dep_dict)) if self.app_config.do_ocean: - dep_dict = {'type': 'metatask', 'name': 'ocean_prod'} + dep_dict = {'type': 'metatask', 'name': 'gefs_ocean_prod'} deps.append(rocoto.add_dependency(dep_dict)) if self.app_config.do_wave: - dep_dict = {'type': 'metatask', 'name': 'wave_post_grid'} + dep_dict = {'type': 'metatask', 'name': 'gefs_wave_post_grid'} deps.append(rocoto.add_dependency(dep_dict)) - dep_dict = {'type': 'metatask', 'name': 'wave_post_pnt'} + dep_dict = {'type': 'metatask', 'name': 'gefs_wave_post_pnt'} deps.append(rocoto.add_dependency(dep_dict)) if self.app_config.do_wave_bnd: - dep_dict = {'type': 'metatask', 'name': 'wave_post_bndpnt'} + dep_dict = {'type': 'metatask', 'name': 'gefs_wave_post_bndpnt'} deps.append(rocoto.add_dependency(dep_dict)) - dep_dict = {'type': 'metatask', 'name': 'wave_post_bndpnt_bull'} + dep_dict = {'type': 'metatask', 'name': 'gefs_wave_post_bndpnt_bull'} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep=deps, dep_condition='and') resources = self.get_resource('cleanup') - task_name = 'cleanup' + task_name = 'gefs_cleanup' task_dict = {'task_name': task_name, 'resources': resources, 'envars': self.envars, diff --git a/workflow/rocoto/gefs_xml.py b/workflow/rocoto/gefs_xml.py index b25a73fa6c..a5dfd5140e 100644 --- a/workflow/rocoto/gefs_xml.py +++ b/workflow/rocoto/gefs_xml.py @@ -14,19 +14,19 @@ def __init__(self, app_config: AppConfig, rocoto_config: Dict) -> None: super().__init__(app_config, rocoto_config) def get_cycledefs(self): - sdate = self._base['SDATE'] + sdate = self._base['SDATE_GFS'] edate = self._base['EDATE'] - interval = self._base.get('INTERVAL_GFS', to_timedelta('24H')) + interval = self._app_config.interval_gfs sdate_str = sdate.strftime("%Y%m%d%H%M") edate_str = edate.strftime("%Y%m%d%H%M") interval_str = timedelta_to_HMS(interval) strings = [] strings.append(f'\t{sdate_str} {edate_str} {interval_str}') - sdate = sdate + interval - if sdate <= edate: - sdate_str = sdate.strftime("%Y%m%d%H%M") - strings.append(f'\t{sdate_str} {edate_str} {interval_str}') + date2 = sdate + interval + if date2 <= edate: + date2_str = date2.strftime("%Y%m%d%H%M") + strings.append(f'\t{date2_str} {edate_str} {interval_str}') strings.append('') strings.append('') diff --git a/workflow/rocoto/gfs_cycled_xml.py b/workflow/rocoto/gfs_cycled_xml.py index afd663c337..eef77ba7fc 100644 --- a/workflow/rocoto/gfs_cycled_xml.py +++ b/workflow/rocoto/gfs_cycled_xml.py @@ -24,19 +24,38 @@ def get_cycledefs(self): sdate_str = sdate.strftime("%Y%m%d%H%M") strings.append(f'\t{sdate_str} {edate_str} {interval_str}') - if self._app_config.gfs_cyc != 0: + interval_gfs = self._app_config.interval_gfs + + if interval_gfs > to_timedelta("0H"): sdate_gfs = self._base['SDATE_GFS'] - edate_gfs = self._base['EDATE_GFS'] - interval_gfs = self._base['INTERVAL_GFS'] + edate_gfs = sdate_gfs + ((edate - sdate_gfs) // interval_gfs) * interval_gfs sdate_gfs_str = sdate_gfs.strftime("%Y%m%d%H%M") edate_gfs_str = edate_gfs.strftime("%Y%m%d%H%M") interval_gfs_str = timedelta_to_HMS(interval_gfs) strings.append(f'\t{sdate_gfs_str} {edate_gfs_str} {interval_gfs_str}') - sdate_gfs = sdate_gfs + interval_gfs - sdate_gfs_str = sdate_gfs.strftime("%Y%m%d%H%M") - if sdate_gfs <= edate_gfs: - strings.append(f'\t{sdate_gfs_str} {edate_gfs_str} {interval_gfs_str}') + date2_gfs = sdate_gfs + interval_gfs + date2_gfs_str = date2_gfs.strftime("%Y%m%d%H%M") + if date2_gfs <= edate_gfs: + strings.append(f'\t{date2_gfs_str} {edate_gfs_str} {interval_gfs_str}') + + if self._base['DO_METP']: + if interval_gfs < to_timedelta('24H'): + # Run verification at 18z, no matter what if there is more than one gfs per day + sdate_metp = sdate_gfs.replace(hour=18) + edate_metp = edate_gfs.replace(hour=18) + interval_metp = to_timedelta('24H') + sdate_metp_str = sdate_metp.strftime("%Y%m%d%H%M") + edate_metp_str = edate_metp.strftime("%Y%m%d%H%M") + interval_metp_str = timedelta_to_HMS(interval_metp) + else: + # Use same cycledef as gfs if there is no more than one per day + sdate_metp_str = sdate_gfs_str + edate_metp_str = edate_gfs_str + interval_metp_str = interval_gfs_str + + strings.append(f'\t{sdate_metp_str} {edate_metp_str} {interval_metp_str}') + strings.append(f'\t{edate_gfs_str} {edate_gfs_str} 24:00:00') strings.append('') strings.append('') diff --git a/workflow/rocoto/gfs_forecast_only_xml.py b/workflow/rocoto/gfs_forecast_only_xml.py index cf53e685e9..a4d5b0878b 100644 --- a/workflow/rocoto/gfs_forecast_only_xml.py +++ b/workflow/rocoto/gfs_forecast_only_xml.py @@ -12,15 +12,36 @@ def __init__(self, app_config: AppConfig, rocoto_config: Dict) -> None: super().__init__(app_config, rocoto_config) def get_cycledefs(self): - sdate = self._base['SDATE'] - edate = self._base['EDATE'] - interval = self._base.get('INTERVAL_GFS', to_timedelta('24H')) + sdate_gfs = self._base['SDATE_GFS'] + edate_gfs = self._base['EDATE'] + interval_gfs = self._app_config.interval_gfs strings = [] - strings.append(f'\t{sdate.strftime("%Y%m%d%H%M")} {edate.strftime("%Y%m%d%H%M")} {timedelta_to_HMS(interval)}') - - sdate = sdate + interval - if sdate <= edate: - strings.append(f'\t{sdate.strftime("%Y%m%d%H%M")} {edate.strftime("%Y%m%d%H%M")} {timedelta_to_HMS(interval)}') + sdate_gfs_str = sdate_gfs.strftime("%Y%m%d%H%M") + edate_gfs_str = edate_gfs.strftime("%Y%m%d%H%M") + interval_gfs_str = timedelta_to_HMS(interval_gfs) + strings.append(f'\t{sdate_gfs_str} {edate_gfs_str} {interval_gfs_str}') + + date2 = sdate_gfs + interval_gfs + if date2 <= edate_gfs: + date2_gfs_str = date2_gfs.strftime("%Y%m%d%H%M") + strings.append(f'\t{date2_gfs_str} {edate_gfs_str} {interval_gfs_str}') + + if self._base['DO_METP']: + if interval_gfs < to_timedelta('24H'): + # Run verification at 18z, no matter what if there is more than one gfs per day + sdate_metp = sdate_gfs.replace(hour=18) + edate_metp = edate_gfs.replace(hour=18) + interval_metp = to_timedelta('24H') + sdate_metp_str = sdate_metp.strftime("%Y%m%d%H%M") + edate_metp_str = edate_metp.strftime("%Y%m%d%H%M") + interval_metp_str = timedelta_to_HMS(interval_metp) + else: + # Use same cycledef as gfs if there is no more than one per day + sdate_metp_str = sdate_gfs_str + edate_metp_str = edate_gfs_str + interval_metp_str = interval_gfs_str + + strings.append(f'\t{sdate_metp_str} {edate_metp_str} {interval_metp_str}') strings.append('') strings.append('') diff --git a/workflow/rocoto/gfs_tasks.py b/workflow/rocoto/gfs_tasks.py index 89da933d00..82dfb9f1d4 100644 --- a/workflow/rocoto/gfs_tasks.py +++ b/workflow/rocoto/gfs_tasks.py @@ -1,6 +1,6 @@ from applications.applications import AppConfig from rocoto.tasks import Tasks -from wxflow import timedelta_to_HMS +from wxflow import timedelta_to_HMS, to_timedelta import rocoto.rocoto as rocoto import numpy as np @@ -21,7 +21,7 @@ def stage_ic(self): cycledef = 'gdas_half' if self.run in ['gdas', 'enkfgdas'] else self.run resources = self.get_resource('stage_ic') - task_name = f'{self.run}stage_ic' + task_name = f'{self.run}_stage_ic' task_dict = {'task_name': task_name, 'resources': resources, 'envars': self.envars, @@ -39,7 +39,6 @@ def stage_ic(self): def prep(self): dump_suffix = self._base["DUMP_SUFFIX"] - gfs_cyc = self._base["gfs_cyc"] dmpdir = self._base["DMPDIR"] atm_hist_path = self._template_to_rocoto_cycstring(self._base["COM_ATMOS_HISTORY_TMPL"], {'RUN': 'gdas'}) dump_path = self._template_to_rocoto_cycstring(self._base["COM_OBSDMP_TMPL"], @@ -48,10 +47,10 @@ def prep(self): 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': 'gdasatmos_prod', 'offset': f"-{timedelta_to_HMS(self._base['cycle_interval'])}"} + dep_dict = {'type': 'metatask', 'name': 'gdas_atmos_prod', 'offset': f"-{timedelta_to_HMS(self._base['interval_gdas'])}"} 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'])}"} + dep_dict = {'type': 'data', 'data': data, 'offset': f"-{timedelta_to_HMS(self._base['interval_gdas'])}"} deps.append(rocoto.add_dependency(dep_dict)) data = f'{dump_path}/{self.run}.t@Hz.updated.status.tm00.bufr_d' dep_dict = {'type': 'data', 'data': data} @@ -59,11 +58,11 @@ def prep(self): dependencies = rocoto.create_dependency(dep_condition='and', dep=deps) cycledef = self.run - if self.run in ['gfs'] and gfs_enkf and gfs_cyc != 4: + if self.run in ['gfs'] and gfs_enkf and self.app_config.interval_gfs != 6: cycledef = 'gdas' resources = self.get_resource('prep') - task_name = f'{self.run}prep' + task_name = f'{self.run}_prep' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -86,14 +85,14 @@ def waveinit(self): 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.run}prep'} + dep_dict = {'type': 'task', 'name': f'{self.run}_prep'} deps.append(rocoto.add_dependency(dep_dict)) if self.run in ['gdas']: - dep_dict = {'type': 'cycleexist', 'condition': 'not', 'offset': f"-{timedelta_to_HMS(self._base['cycle_interval'])}"} + dep_dict = {'type': 'cycleexist', 'condition': 'not', 'offset': f"-{timedelta_to_HMS(self._base['interval_gdas'])}"} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep_condition='or', dep=deps) - task_name = f'{self.run}waveinit' + task_name = f'{self.run}_waveinit' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -112,12 +111,12 @@ def waveinit(self): def waveprep(self): deps = [] - dep_dict = {'type': 'task', 'name': f'{self.run}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.run in ['gdas'] else self.run resources = self.get_resource('waveprep') - task_name = f'{self.run}waveprep' + task_name = f'{self.run}_waveprep' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -168,7 +167,7 @@ def aerosol_init(self): cycledef = 'gfs_seq' resources = self.get_resource('aerosol_init') - task_name = f'{self.run}aerosol_init' + task_name = f'{self.run}_aerosol_init' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -186,17 +185,17 @@ def aerosol_init(self): def anal(self): deps = [] - dep_dict = {'type': 'task', 'name': f'{self.run}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'])}"} + dep_dict = {'type': 'metatask', 'name': 'enkfgdas_epmn', 'offset': f"-{timedelta_to_HMS(self._base['interval_gdas'])}"} 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('anal') - task_name = f'{self.run}anal' + task_name = f'{self.run}_anal' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -216,19 +215,19 @@ def sfcanl(self): deps = [] if self.app_config.do_jediatmvar: - dep_dict = {'type': 'task', 'name': f'{self.run}atmanlfinal'} + dep_dict = {'type': 'task', 'name': f'{self.run}_atmanlfinal'} else: - dep_dict = {'type': 'task', 'name': f'{self.run}anal'} + dep_dict = {'type': 'task', 'name': f'{self.run}_anal'} deps.append(rocoto.add_dependency(dep_dict)) if self.app_config.do_jedisnowda: - dep_dict = {'type': 'task', 'name': f'{self.run}snowanl'} + 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.run}sfcanl' + task_name = f'{self.run}_sfcanl' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -248,19 +247,19 @@ def analcalc(self): deps = [] if self.app_config.do_jediatmvar: - dep_dict = {'type': 'task', 'name': f'{self.run}atmanlfinal'} + dep_dict = {'type': 'task', 'name': f'{self.run}_atmanlfinal'} else: - dep_dict = {'type': 'task', 'name': f'{self.run}anal'} + dep_dict = {'type': 'task', 'name': f'{self.run}_anal'} deps.append(rocoto.add_dependency(dep_dict)) - dep_dict = {'type': 'task', 'name': f'{self.run}sfcanl'} + dep_dict = {'type': 'task', 'name': f'{self.run}_sfcanl'} deps.append(rocoto.add_dependency(dep_dict)) if self.app_config.do_hybvar and self.run in ['gdas']: - dep_dict = {'type': 'task', 'name': 'enkfgdasechgres', 'offset': f"-{timedelta_to_HMS(self._base['cycle_interval'])}"} + dep_dict = {'type': 'task', 'name': 'enkfgdas_echgres', 'offset': f"-{timedelta_to_HMS(self._base['interval_gdas'])}"} 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.run}analcalc' + task_name = f'{self.run}_analcalc' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -279,12 +278,12 @@ def analcalc(self): def analdiag(self): deps = [] - dep_dict = {'type': 'task', 'name': f'{self.run}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.run}analdiag' + task_name = f'{self.run}_analdiag' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -303,12 +302,12 @@ def analdiag(self): def prepatmiodaobs(self): deps = [] - dep_dict = {'type': 'task', 'name': f'{self.run}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.run}prepatmiodaobs' + task_name = f'{self.run}_prepatmiodaobs' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -327,24 +326,24 @@ def prepatmiodaobs(self): def atmanlinit(self): deps = [] - dep_dict = {'type': 'task', 'name': f'{self.run}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'])}"} + dep_dict = {'type': 'metatask', 'name': 'enkfgdas_epmn', 'offset': f"-{timedelta_to_HMS(self._base['interval_gdas'])}"} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep_condition='and', dep=deps) else: dependencies = rocoto.create_dependency(dep=deps) - gfs_cyc = self._base["gfs_cyc"] + interval_gfs = self._base["INTERVAL_GFS"] gfs_enkf = True if self.app_config.do_hybvar and 'gfs' in self.app_config.eupd_runs else False cycledef = self.run - if self.run in ['gfs'] and gfs_enkf and gfs_cyc != 4: + if self.run in ['gfs'] and gfs_enkf and interval_gfs != 6: cycledef = 'gdas' resources = self.get_resource('atmanlinit') - task_name = f'{self.run}atmanlinit' + task_name = f'{self.run}_atmanlinit' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -363,12 +362,12 @@ def atmanlinit(self): def atmanlvar(self): deps = [] - dep_dict = {'type': 'task', 'name': f'{self.run}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('atmanlvar') - task_name = f'{self.run}atmanlvar' + task_name = f'{self.run}_atmanlvar' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -387,12 +386,12 @@ def atmanlvar(self): def atmanlfv3inc(self): deps = [] - dep_dict = {'type': 'task', 'name': f'{self.run}atmanlvar'} + 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_name = f'{self.run}_atmanlfv3inc' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -411,12 +410,12 @@ def atmanlfv3inc(self): def atmanlfinal(self): deps = [] - dep_dict = {'type': 'task', 'name': f'{self.run}atmanlfv3inc'} + 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.run}atmanlfinal' + task_name = f'{self.run}_atmanlfinal' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -434,12 +433,12 @@ def atmanlfinal(self): def prepobsaero(self): deps = [] - dep_dict = {'type': 'task', 'name': f'{self.run}prep'} + 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_name = f'{self.run}_prepobsaero' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -458,12 +457,12 @@ def prepobsaero(self): def aeroanlgenb(self): deps = [] - dep_dict = {'type': 'metatask', 'name': f'{self.run}fcst'} + dep_dict = {'type': 'metatask', 'name': f'{self.run}_fcst'} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep=deps) resources = self.get_resource('aeroanlgenb') - task_name = f'{self.run}aeroanlgenb' + task_name = f'{self.run}_aeroanlgenb' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -482,18 +481,18 @@ def aeroanlgenb(self): def aeroanlinit(self): deps = [] - dep_dict = {'type': 'task', 'name': 'gdasaeroanlgenb', 'offset': f"-{timedelta_to_HMS(self._base['cycle_interval'])}"} + dep_dict = {'type': 'task', 'name': 'gdas_aeroanlgenb', 'offset': f"-{timedelta_to_HMS(self._base['interval_gdas'])}"} deps.append(rocoto.add_dependency(dep_dict)) - dep_dict = {'type': 'task', 'name': f'{self.run}prep'} + dep_dict = {'type': 'task', 'name': f'{self.run}_prep'} deps.append(rocoto.add_dependency(dep_dict)) if self.app_config.do_prep_obs_aero: - dep_dict = {'type': 'task', 'name': f'{self.run}prepobsaero'} + 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.run}aeroanlinit' + task_name = f'{self.run}_aeroanlinit' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -513,18 +512,18 @@ def aeroanlvar(self): deps = [] dep_dict = { - 'type': 'task', 'name': f'gdasaeroanlgenb', - 'offset': f"-{timedelta_to_HMS(self._base['cycle_interval'])}", + 'type': 'task', 'name': f'gdas_aeroanlgenb', + 'offset': f"-{timedelta_to_HMS(self._base['interval_gdas'])}", } deps.append(rocoto.add_dependency(dep_dict)) dep_dict = { - 'type': 'task', 'name': f'{self.run}aeroanlinit', + 'type': 'task', 'name': f'{self.run}_aeroanlinit', } deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep_condition='and', dep=deps) resources = self.get_resource('aeroanlvar') - task_name = f'{self.run}aeroanlvar' + task_name = f'{self.run}_aeroanlvar' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -543,12 +542,12 @@ def aeroanlvar(self): def aeroanlfinal(self): deps = [] - dep_dict = {'type': 'task', 'name': f'{self.run}aeroanlvar'} + dep_dict = {'type': 'task', 'name': f'{self.run}_aeroanlvar'} 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.run}aeroanlfinal' + task_name = f'{self.run}_aeroanlfinal' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -567,12 +566,12 @@ def aeroanlfinal(self): def prepsnowobs(self): deps = [] - dep_dict = {'type': 'task', 'name': f'{self.run}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('prepsnowobs') - task_name = f'{self.run}prepsnowobs' + task_name = f'{self.run}_prepsnowobs' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -591,12 +590,12 @@ def prepsnowobs(self): def snowanl(self): deps = [] - dep_dict = {'type': 'task', 'name': f'{self.run}prepsnowobs'} + 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('snowanl') - task_name = f'{self.run}snowanl' + task_name = f'{self.run}_snowanl' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -614,16 +613,16 @@ def snowanl(self): def esnowrecen(self): deps = [] - dep_dict = {'type': 'task', 'name': f'{self.run.replace("enkf","")}prepsnowobs'} + dep_dict = {'type': 'task', 'name': f'{self.run.replace("enkf","")}_prepsnowobs'} deps.append(rocoto.add_dependency(dep_dict)) - dep_dict = {'type': 'task', 'name': f'{self.run.replace("enkf","")}snowanl'} + dep_dict = {'type': 'task', 'name': f'{self.run.replace("enkf","")}_snowanl'} deps.append(rocoto.add_dependency(dep_dict)) - dep_dict = {'type': 'metatask', 'name': f'{self.run}epmn', 'offset': f"-{timedelta_to_HMS(self._base['cycle_interval'])}"} + dep_dict = {'type': 'metatask', 'name': f'{self.run}_epmn', 'offset': f"-{timedelta_to_HMS(self._base['interval_gdas'])}"} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep_condition='and', dep=deps) resources = self.get_resource('esnowrecen') - task_name = f'{self.run}esnowrecen' + task_name = f'{self.run}_esnowrecen' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -644,12 +643,12 @@ def prepoceanobs(self): deps = [] 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'])}"} + dep_dict = {'type': 'data', 'data': data, 'offset': f"-{timedelta_to_HMS(self._base['interval_gdas'])}"} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep=deps) resources = self.get_resource('prepoceanobs') - task_name = f'{self.run}prepoceanobs' + task_name = f'{self.run}_prepoceanobs' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -671,12 +670,12 @@ def marinebmat(self): deps = [] 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'])}"} + dep_dict = {'type': 'data', 'data': data, 'offset': f"-{timedelta_to_HMS(self._base['interval_gdas'])}"} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep=deps) resources = self.get_resource('marinebmat') - task_name = f'{self.run}marinebmat' + task_name = f'{self.run}_marinebmat' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -692,25 +691,25 @@ def marinebmat(self): return task - def ocnanalprep(self): + def marineanlinit(self): deps = [] - dep_dict = {'type': 'task', 'name': f'{self.run}prepoceanobs'} + dep_dict = {'type': 'task', 'name': f'{self.run}_prepoceanobs'} deps.append(rocoto.add_dependency(dep_dict)) - dep_dict = {'type': 'task', 'name': f'{self.run}marinebmat'} + dep_dict = {'type': 'task', 'name': f'{self.run}_marinebmat'} deps.append(rocoto.add_dependency(dep_dict)) - dep_dict = {'type': 'metatask', 'name': 'gdasfcst', 'offset': f"-{timedelta_to_HMS(self._base['cycle_interval'])}"} + dep_dict = {'type': 'metatask', 'name': 'gdas_fcst', 'offset': f"-{timedelta_to_HMS(self._base['interval_gdas'])}"} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep_condition='and', dep=deps) - resources = self.get_resource('ocnanalprep') - task_name = f'{self.run}ocnanalprep' + resources = self.get_resource('marineanlinit') + task_name = f'{self.run}_marineanlinit' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, 'envars': self.envars, 'cycledef': self.run.replace('enkf', ''), - 'command': f'{self.HOMEgfs}/jobs/rocoto/ocnanalprep.sh', + 'command': f'{self.HOMEgfs}/jobs/rocoto/marineanlinit.sh', 'job_name': f'{self.pslot}_{task_name}_@H', 'log': f'{self.rotdir}/logs/@Y@m@d@H/{task_name}.log', 'maxtries': '&MAXTRIES;' @@ -720,21 +719,21 @@ def ocnanalprep(self): return task - def ocnanalrun(self): + def marineanlvar(self): deps = [] - dep_dict = {'type': 'task', 'name': f'{self.run}ocnanalprep'} + dep_dict = {'type': 'task', 'name': f'{self.run}_marineanlinit'} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep=deps) - resources = self.get_resource('ocnanalrun') - task_name = f'{self.run}ocnanalrun' + resources = self.get_resource('marineanlvar') + task_name = f'{self.run}_marineanlvar' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, 'envars': self.envars, 'cycledef': self.run.replace('enkf', ''), - 'command': f'{self.HOMEgfs}/jobs/rocoto/ocnanalrun.sh', + 'command': f'{self.HOMEgfs}/jobs/rocoto/marineanlvar.sh', 'job_name': f'{self.pslot}_{task_name}_@H', 'log': f'{self.rotdir}/logs/@Y@m@d@H/{task_name}.log', 'maxtries': '&MAXTRIES;' @@ -747,12 +746,12 @@ def ocnanalrun(self): def ocnanalecen(self): deps = [] - dep_dict = {'type': 'task', 'name': f'{self.run}ocnanalrun'} + dep_dict = {'type': 'task', 'name': f'{self.run}_marineanlvar'} 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_name = f'{self.run}_ocnanalecen' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -768,13 +767,13 @@ def ocnanalecen(self): return task - def ocnanalchkpt(self): + def marineanlchkpt(self): deps = [] if self.app_config.do_hybvar: - dep_dict = {'type': 'task', 'name': f'{self.run}ocnanalecen'} + dep_dict = {'type': 'task', 'name': f'{self.run}_ocnanalecen'} else: - dep_dict = {'type': 'task', 'name': f'{self.run}ocnanalrun'} + dep_dict = {'type': 'task', 'name': f'{self.run}_marineanlvar'} deps.append(rocoto.add_dependency(dep_dict)) if self.app_config.do_mergensst: data = f'&ROTDIR;/{self.run}.@Y@m@d/@H/atmos/{self.run}.t@Hz.sfcanl.nc' @@ -782,14 +781,14 @@ def ocnanalchkpt(self): 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.run}ocnanalchkpt' + resources = self.get_resource('marineanlchkpt') + task_name = f'{self.run}_marineanlchkpt' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, 'envars': self.envars, 'cycledef': self.run.replace('enkf', ''), - 'command': f'{self.HOMEgfs}/jobs/rocoto/ocnanalchkpt.sh', + 'command': f'{self.HOMEgfs}/jobs/rocoto/marineanlchkpt.sh', 'job_name': f'{self.pslot}_{task_name}_@H', 'log': f'{self.rotdir}/logs/@Y@m@d@H/{task_name}.log', 'maxtries': '&MAXTRIES;' @@ -799,21 +798,21 @@ def ocnanalchkpt(self): return task - def ocnanalpost(self): + def marineanlfinal(self): deps = [] - dep_dict = {'type': 'task', 'name': f'{self.run}ocnanalchkpt'} + dep_dict = {'type': 'task', 'name': f'{self.run}_marineanlchkpt'} 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.run}ocnanalpost' + resources = self.get_resource('marineanlfinal') + task_name = f'{self.run}_marineanlfinal' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, 'envars': self.envars, 'cycledef': self.run.replace('enkf', ''), - 'command': f'{self.HOMEgfs}/jobs/rocoto/ocnanalpost.sh', + 'command': f'{self.HOMEgfs}/jobs/rocoto/marineanlfinal.sh', 'job_name': f'{self.pslot}_{task_name}_@H', 'log': f'{self.rotdir}/logs/@Y@m@d@H/{task_name}.log', 'maxtries': '&MAXTRIES;' @@ -826,12 +825,12 @@ def ocnanalpost(self): def ocnanalvrfy(self): deps = [] - dep_dict = {'type': 'task', 'name': f'{self.run}ocnanalpost'} + dep_dict = {'type': 'task', 'name': f'{self.run}_marineanlfinal'} 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.run}ocnanalvrfy' + task_name = f'{self.run}_ocnanalvrfy' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -855,8 +854,8 @@ def fcst(self): try: task = fcst_map[self.app_config.mode]() except KeyError: - raise NotImplementedError(f'{self.app_config.mode} is not a valid type.\n' + - 'Currently supported forecast types are:\n' + + raise NotImplementedError(f'{self.app_config.mode} is not a valid type.\n' + f'Currently supported forecast types are:\n' f'{" | ".join(fcst_map.keys())}') return task @@ -864,12 +863,12 @@ def fcst(self): def _fcst_forecast_only(self): dependencies = [] - dep_dict = {'type': 'task', 'name': f'{self.run}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.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.run}{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 and \ @@ -878,12 +877,12 @@ def _fcst_forecast_only(self): # Calculate offset based on RUN = gfs | gdas interval = None if self.run in ['gfs']: - interval = self._base['INTERVAL_GFS'] + interval = to_timedelta(f"{self._base['INTERVAL_GFS']}H") elif self.run in ['gdas']: - interval = self._base['INTERVAL'] + interval = self._base['assim_freq'] offset = timedelta_to_HMS(-interval) deps = [] - dep_dict = {'type': 'task', 'name': f'{self.run}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)) @@ -902,7 +901,7 @@ def _fcst_forecast_only(self): fcst_vars.append(rocoto.create_envar(name=key, value=str(value))) resources = self.get_resource('fcst') - task_name = f'{self.run}fcst_seg#seg#' + task_name = f'{self.run}_fcst_seg#seg#' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -915,7 +914,7 @@ def _fcst_forecast_only(self): } seg_var_dict = {'seg': ' '.join([f"{seg}" for seg in range(0, num_fcst_segments)])} - metatask_dict = {'task_name': f'{self.run}fcst', + metatask_dict = {'task_name': f'{self.run}_fcst', 'is_serial': True, 'var_dict': seg_var_dict, 'task_dict': task_dict @@ -927,31 +926,31 @@ def _fcst_forecast_only(self): def _fcst_cycled(self): - dep_dict = {'type': 'task', 'name': f'{self.run}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.run}ocnanalpost'} + dep_dict = {'type': 'task', 'name': f'{self.run}_marineanlfinal'} dependencies.append(rocoto.add_dependency(dep_dict)) 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'} + dep_dict = {'type': 'task', 'name': f'{self.run}_aeroanlfinal'} dependencies.append(rocoto.add_dependency(dep_dict)) if self.app_config.do_jedisnowda: - dep_dict = {'type': 'task', 'name': f'{self.run}snowanl'} + 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.run in ['gdas']: - dep_dict = {'type': 'task', 'name': f'{self.run}stage_ic'} + dep_dict = {'type': 'task', 'name': f'{self.run}_stage_ic'} dependencies.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep_condition='or', dep=dependencies) if self.app_config.do_wave and self.run in self.app_config.wave_runs: - dep_dict = {'type': 'task', 'name': f'{self.run}waveprep'} + 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) @@ -968,7 +967,7 @@ def _fcst_cycled(self): fcst_vars.append(rocoto.create_envar(name=key, value=str(value))) resources = self.get_resource('fcst') - task_name = f'{self.run}fcst_seg#seg#' + task_name = f'{self.run}_fcst_seg#seg#' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -981,7 +980,7 @@ def _fcst_cycled(self): } seg_var_dict = {'seg': ' '.join([f"{seg}" for seg in range(0, num_fcst_segments)])} - metatask_dict = {'task_name': f'{self.run}fcst', + metatask_dict = {'task_name': f'{self.run}_fcst', 'is_serial': True, 'var_dict': seg_var_dict, 'task_dict': task_dict @@ -1011,7 +1010,7 @@ def atmanlupp(self): 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.run}atmanlupp' + task_name = f'{self.run}_atmanlupp' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -1040,7 +1039,7 @@ def atmanlprod(self): deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep=deps) resources = self.get_resource('atmos_products') - task_name = f'{self.run}atmanlprod' + task_name = f'{self.run}_atmanlprod' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -1089,7 +1088,7 @@ def _upptask(self, upp_run="forecast", task_id="atmupp"): cycledef = 'gdas_half,gdas' if self.run in ['gdas'] else self.run resources = self.get_resource('upp') - task_name = f'{self.run}{task_id}_f#fhr#' + task_name = f'{self.run}_{task_id}_f#fhr#' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -1104,7 +1103,7 @@ def _upptask(self, upp_run="forecast", task_id="atmupp"): 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}', + metatask_dict = {'task_name': f'{self.run}_{task_id}', 'task_dict': task_dict, 'var_dict': fhr_var_dict } @@ -1149,14 +1148,14 @@ def _atmosoceaniceprod(self, component: str): 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': 'metatask', 'name': f'{self.run}fcst'} + dep_dict = {'type': 'metatask', 'name': f'{self.run}_fcst'} deps.append(rocoto.add_dependency(dep_dict)) 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.run}{component}_prod_f#fhr#' + task_name = f'{self.run}_{component}_prod_f#fhr#' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -1178,7 +1177,7 @@ def _atmosoceaniceprod(self, component: str): 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', + metatask_dict = {'task_name': f'{self.run}_{component}_prod', 'task_dict': task_dict, 'var_dict': fhr_var_dict} @@ -1188,12 +1187,12 @@ def _atmosoceaniceprod(self, component: str): def wavepostsbs(self): deps = [] - dep_dict = {'type': 'metatask', 'name': f'{self.run}fcst'} + dep_dict = {'type': 'metatask', 'name': f'{self.run}_fcst'} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep=deps) resources = self.get_resource('wavepostsbs') - task_name = f'{self.run}wavepostsbs' + task_name = f'{self.run}_wavepostsbs' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -1211,12 +1210,12 @@ def wavepostsbs(self): def wavepostbndpnt(self): deps = [] - dep_dict = {'type': 'metatask', 'name': f'{self.run}fcst'} + dep_dict = {'type': 'metatask', '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.run}wavepostbndpnt' + task_name = f'{self.run}_wavepostbndpnt' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -1245,7 +1244,7 @@ def wavepostbndpntbll(self): dependencies = rocoto.create_dependency(dep=deps) resources = self.get_resource('wavepostbndpntbll') - task_name = f'{self.run}wavepostbndpntbll' + task_name = f'{self.run}_wavepostbndpntbll' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -1263,15 +1262,15 @@ def wavepostbndpntbll(self): def wavepostpnt(self): deps = [] - dep_dict = {'type': 'metatask', 'name': f'{self.run}fcst'} + dep_dict = {'type': 'metatask', '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.run}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.run}wavepostpnt' + task_name = f'{self.run}_wavepostpnt' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -1289,12 +1288,12 @@ def wavepostpnt(self): def wavegempak(self): deps = [] - dep_dict = {'type': 'task', 'name': f'{self.run}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.run}wavegempak' + task_name = f'{self.run}_wavegempak' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -1312,14 +1311,14 @@ def wavegempak(self): def waveawipsbulls(self): deps = [] - dep_dict = {'type': 'task', 'name': f'{self.run}wavepostsbs'} + dep_dict = {'type': 'task', 'name': f'{self.run}_wavepostsbs'} deps.append(rocoto.add_dependency(dep_dict)) - dep_dict = {'type': 'task', 'name': f'{self.run}wavepostpnt'} + 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.run}waveawipsbulls' + task_name = f'{self.run}_waveawipsbulls' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -1337,12 +1336,12 @@ def waveawipsbulls(self): def waveawipsgridded(self): deps = [] - dep_dict = {'type': 'task', 'name': f'{self.run}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.run}waveawipsgridded' + task_name = f'{self.run}_waveawipsgridded' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -1360,12 +1359,12 @@ def waveawipsgridded(self): def postsnd(self): deps = [] - dep_dict = {'type': 'metatask', 'name': f'{self.run}fcst'} + dep_dict = {'type': 'metatask', '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.run}postsnd' + task_name = f'{self.run}_postsnd' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -1402,7 +1401,7 @@ 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.run}fbwind' + task_name = f'{self.run}_fbwind' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -1457,7 +1456,7 @@ def _get_awipsgroups(run, config): def awips_20km_1p0deg(self): deps = [] - dep_dict = {'type': 'metatask', 'name': f'{self.run}atmos_prod'} + dep_dict = {'type': 'metatask', 'name': f'{self.run}_atmos_prod'} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep=deps) @@ -1474,7 +1473,7 @@ def awips_20km_1p0deg(self): resources = self.get_resource('awips') - task_name = f'{self.run}awips_20km_1p0deg#{varname1}#' + task_name = f'{self.run}_awips_20km_1p0deg#{varname1}#' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -1486,7 +1485,7 @@ def awips_20km_1p0deg(self): 'maxtries': '&MAXTRIES;' } - metatask_dict = {'task_name': f'{self.run}awips_20km_1p0deg', + metatask_dict = {'task_name': f'{self.run}_awips_20km_1p0deg', 'task_dict': task_dict, 'var_dict': var_dict } @@ -1498,7 +1497,7 @@ def awips_20km_1p0deg(self): def gempak(self): deps = [] - dep_dict = {'type': 'task', 'name': f'{self.run}atmos_prod_f#fhr#'} + 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) @@ -1508,7 +1507,7 @@ def gempak(self): gempak_vars.append(rocoto.create_envar(name=key, value=str(value))) resources = self.get_resource('gempak') - task_name = f'{self.run}gempak_f#fhr#' + task_name = f'{self.run}_gempak_f#fhr#' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -1523,7 +1522,7 @@ def gempak(self): 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', + fhr_metatask_dict = {'task_name': f'{self.run}_gempak', 'task_dict': task_dict, 'var_dict': fhr_var_dict} @@ -1533,12 +1532,12 @@ def gempak(self): def gempakmeta(self): deps = [] - dep_dict = {'type': 'metatask', 'name': f'{self.run}gempak'} + 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.run}gempakmeta' + task_name = f'{self.run}_gempakmeta' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -1556,12 +1555,12 @@ def gempakmeta(self): def gempakmetancdc(self): deps = [] - dep_dict = {'type': 'metatask', 'name': f'{self.run}gempak'} + 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.run}gempakmetancdc' + task_name = f'{self.run}_gempakmetancdc' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -1579,12 +1578,12 @@ def gempakmetancdc(self): def gempakncdcupapgif(self): deps = [] - dep_dict = {'type': 'metatask', 'name': f'{self.run}gempak'} + 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.run}gempakncdcupapgif' + task_name = f'{self.run}_gempakncdcupapgif' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -1602,7 +1601,7 @@ def gempakncdcupapgif(self): def gempakpgrb2spec(self): deps = [] - dep_dict = {'type': 'task', 'name': f'{self.run}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) @@ -1612,7 +1611,7 @@ def gempakpgrb2spec(self): gempak_vars.append(rocoto.create_envar(name=key, value=str(value))) resources = self.get_resource('gempak') - task_name = f'{self.run}gempakgrb2spec_f#fhr#' + task_name = f'{self.run}_gempakgrb2spec_f#fhr#' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -1636,7 +1635,7 @@ def gempakpgrb2spec(self): 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', + fhr_metatask_dict = {'task_name': f'{self.run}_gempakgrb2spec', 'task_dict': task_dict, 'var_dict': fhr_var_dict} @@ -1647,14 +1646,14 @@ def gempakpgrb2spec(self): def npoess_pgrb2_0p5deg(self): deps = [] - dep_dict = {'type': 'task', 'name': f'{self.run}atmanlprod'} + dep_dict = {'type': 'task', 'name': f'{self.run}_atmanlprod'} deps.append(rocoto.add_dependency(dep_dict)) - dep_dict = {'type': 'metatask', 'name': f'{self.run}goesupp'} + 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.run}npoess_pgrb2_0p5deg' + task_name = f'{self.run}_npoess_pgrb2_0p5deg' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -1672,12 +1671,12 @@ def npoess_pgrb2_0p5deg(self): def verfozn(self): deps = [] - dep_dict = {'type': 'task', 'name': f'{self.run}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.run}verfozn' + task_name = f'{self.run}_verfozn' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -1695,12 +1694,12 @@ def verfozn(self): def verfrad(self): deps = [] - dep_dict = {'type': 'task', 'name': f'{self.run}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.run}verfrad' + task_name = f'{self.run}_verfrad' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -1718,12 +1717,12 @@ def verfrad(self): def vminmon(self): deps = [] - dep_dict = {'type': 'task', 'name': f'{self.run}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.run}vminmon' + task_name = f'{self.run}_vminmon' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -1741,12 +1740,12 @@ def vminmon(self): def tracker(self): deps = [] - dep_dict = {'type': 'metatask', 'name': f'{self.run}atmos_prod'} + 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.run}tracker' + task_name = f'{self.run}_tracker' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -1764,12 +1763,12 @@ def tracker(self): def genesis(self): deps = [] - dep_dict = {'type': 'metatask', 'name': f'{self.run}atmos_prod'} + 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.run}genesis' + task_name = f'{self.run}_genesis' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -1787,12 +1786,12 @@ def genesis(self): def genesis_fsu(self): deps = [] - dep_dict = {'type': 'metatask', 'name': f'{self.run}atmos_prod'} + 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.run}genesis_fsu' + task_name = f'{self.run}_genesis_fsu' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -1810,12 +1809,12 @@ def genesis_fsu(self): def fit2obs(self): deps = [] - dep_dict = {'type': 'metatask', 'name': f'{self.run}atmos_prod'} + 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.run}fit2obs' + task_name = f'{self.run}_fit2obs' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -1833,16 +1832,29 @@ def fit2obs(self): def metp(self): deps = [] - dep_dict = {'type': 'task', 'name': f'{self.run}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) + if self.app_config.interval_gfs < to_timedelta('24H'): + n_lookback = self.app_config.interval_gfs // to_timedelta('6H') + for lookback in range(1, n_lookback + 1): + deps2 = [] + dep_dict = {'type': 'taskvalid', 'name': f'{self.run}_arch', 'condition': 'not'} + deps2.append(rocoto.add_dependency(dep_dict)) + for lookback2 in range(1, lookback): + offset = timedelta_to_HMS(-to_timedelta(f'{6*lookback2}H')) + dep_dict = {'type': 'cycleexist', 'condition': 'not', 'offset': offset} + deps2.append(rocoto.add_dependency(dep_dict)) + + offset = timedelta_to_HMS(-to_timedelta(f'{6*lookback}H')) + dep_dict = {'type': 'task', 'name': f'{self.run}_arch', 'offset': offset} + deps2.append(rocoto.add_dependency(dep_dict)) + deps.append(rocoto.create_dependency(dep_condition='and', dep=deps2)) + + dependencies = rocoto.create_dependency(dep_condition='or', 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"), - '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 = {'SDATE_GFS': self._base.get('SDATE_GFS').strftime("%Y%m%d%H"), + 'EDATE_GFS': self._base.get('EDATE').strftime("%Y%m%d%H")} metpenvar_dict['METPCASE'] = '#metpcase#' for key, value in metpenvar_dict.items(): metpenvars.append(rocoto.create_envar(name=key, value=str(value))) @@ -1853,19 +1865,19 @@ def metp(self): resources = self.get_resource('metp') - task_name = f'{self.run}metp#{varname1}#' + task_name = f'{self.run}_metp#{varname1}#' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, 'envars': metpenvars, - 'cycledef': self.run.replace('enkf', ''), + 'cycledef': 'metp,last_gfs', '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.run}metp', + metatask_dict = {'task_name': f'{self.run}_metp', 'is_serial': True, 'task_dict': task_dict, 'var_dict': var_dict, @@ -1877,12 +1889,12 @@ def metp(self): def mos_stn_prep(self): deps = [] - dep_dict = {'type': 'metatask', 'name': f'{self.run}atmos_prod'} + 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.run}mos_stn_prep' + task_name = f'{self.run}_mos_stn_prep' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -1900,12 +1912,12 @@ def mos_stn_prep(self): def mos_grd_prep(self): deps = [] - dep_dict = {'type': 'metatask', 'name': f'{self.run}atmos_prod'} + 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.run}mos_grd_prep' + task_name = f'{self.run}_mos_grd_prep' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -1923,12 +1935,12 @@ def mos_grd_prep(self): def mos_ext_stn_prep(self): deps = [] - dep_dict = {'type': 'metatask', 'name': f'{self.run}atmos_prod'} + 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.run}mos_ext_stn_prep' + task_name = f'{self.run}_mos_ext_stn_prep' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -1946,12 +1958,12 @@ def mos_ext_stn_prep(self): def mos_ext_grd_prep(self): deps = [] - dep_dict = {'type': 'metatask', 'name': f'{self.run}atmos_prod'} + 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.run}mos_ext_grd_prep' + task_name = f'{self.run}_mos_ext_grd_prep' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -1969,12 +1981,12 @@ def mos_ext_grd_prep(self): def mos_stn_fcst(self): deps = [] - dep_dict = {'type': 'task', 'name': f'{self.run}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.run}mos_stn_fcst' + task_name = f'{self.run}_mos_stn_fcst' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -1992,15 +2004,15 @@ def mos_stn_fcst(self): def mos_grd_fcst(self): deps = [] - dep_dict = {'type': 'task', 'name': f'{self.run}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.run}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.run}mos_grd_fcst' + task_name = f'{self.run}_mos_grd_fcst' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -2018,15 +2030,15 @@ def mos_grd_fcst(self): def mos_ext_stn_fcst(self): deps = [] - dep_dict = {'type': 'task', 'name': f'{self.run}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.run}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.run}mos_ext_stn_fcst' + task_name = f'{self.run}_mos_ext_stn_fcst' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -2044,18 +2056,18 @@ def mos_ext_stn_fcst(self): def mos_ext_grd_fcst(self): deps = [] - dep_dict = {'type': 'task', 'name': f'{self.run}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.run}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.run}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.run}mos_ext_grd_fcst' + task_name = f'{self.run}_mos_ext_grd_fcst' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -2073,12 +2085,12 @@ def mos_ext_grd_fcst(self): def mos_stn_prdgen(self): deps = [] - dep_dict = {'type': 'task', 'name': f'{self.run}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.run}mos_stn_prdgen' + task_name = f'{self.run}_mos_stn_prdgen' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -2096,15 +2108,15 @@ def mos_stn_prdgen(self): def mos_grd_prdgen(self): deps = [] - dep_dict = {'type': 'task', 'name': f'{self.run}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.run}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.run}mos_grd_prdgen' + task_name = f'{self.run}_mos_grd_prdgen' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -2122,15 +2134,15 @@ def mos_grd_prdgen(self): def mos_ext_stn_prdgen(self): deps = [] - dep_dict = {'type': 'task', 'name': f'{self.run}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.run}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.run}mos_ext_stn_prdgen' + task_name = f'{self.run}_mos_ext_stn_prdgen' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -2148,18 +2160,18 @@ def mos_ext_stn_prdgen(self): def mos_ext_grd_prdgen(self): deps = [] - dep_dict = {'type': 'task', 'name': f'{self.run}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.run}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.run}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.run}mos_ext_grd_prdgen' + task_name = f'{self.run}_mos_ext_grd_prdgen' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -2177,12 +2189,12 @@ def mos_ext_grd_prdgen(self): def mos_wx_prdgen(self): deps = [] - dep_dict = {'type': 'task', 'name': f'{self.run}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.run}mos_wx_prdgen' + task_name = f'{self.run}_mos_wx_prdgen' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -2200,15 +2212,15 @@ def mos_wx_prdgen(self): def mos_wx_ext_prdgen(self): deps = [] - dep_dict = {'type': 'task', 'name': f'{self.run}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.run}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.run}mos_wx_ext_prdgen' + task_name = f'{self.run}_mos_wx_ext_prdgen' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -2228,53 +2240,53 @@ def arch(self): deps = [] if self.app_config.mode in ['cycled']: if self.run in ['gfs']: - dep_dict = {'type': 'task', 'name': f'{self.run}atmanlprod'} + 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.run}vminmon'} + dep_dict = {'type': 'task', 'name': f'{self.run}_vminmon'} deps.append(rocoto.add_dependency(dep_dict)) elif self.run in ['gdas']: - dep_dict = {'type': 'task', 'name': f'{self.run}atmanlprod'} + 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.run}fit2obs'} + 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.run}verfozn'} + 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.run}verfrad'} + 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.run}vminmon'} + dep_dict = {'type': 'task', 'name': f'{self.run}_vminmon'} deps.append(rocoto.add_dependency(dep_dict)) if self.run in ['gfs'] and self.app_config.do_tracker: - dep_dict = {'type': 'task', 'name': f'{self.run}tracker'} + dep_dict = {'type': 'task', 'name': f'{self.run}_tracker'} deps.append(rocoto.add_dependency(dep_dict)) if self.run in ['gfs'] and self.app_config.do_genesis: - dep_dict = {'type': 'task', 'name': f'{self.run}genesis'} + dep_dict = {'type': 'task', 'name': f'{self.run}_genesis'} deps.append(rocoto.add_dependency(dep_dict)) if self.run in ['gfs'] and self.app_config.do_genesis_fsu: - dep_dict = {'type': 'task', 'name': f'{self.run}genesis_fsu'} + dep_dict = {'type': 'task', 'name': f'{self.run}_genesis_fsu'} deps.append(rocoto.add_dependency(dep_dict)) # Post job dependencies - dep_dict = {'type': 'metatask', 'name': f'{self.run}atmos_prod'} + 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.run}wavepostsbs'} + dep_dict = {'type': 'task', 'name': f'{self.run}_wavepostsbs'} deps.append(rocoto.add_dependency(dep_dict)) - dep_dict = {'type': 'task', 'name': f'{self.run}wavepostpnt'} + 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.run}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.run in ['gfs']: - dep_dict = {'type': 'metatask', 'name': f'{self.run}ocean_prod'} + 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'} + dep_dict = {'type': 'metatask', 'name': f'{self.run}_ice_prod'} deps.append(rocoto.add_dependency(dep_dict)) # MOS job dependencies if self.run in ['gfs'] and self.app_config.do_mos: @@ -2283,13 +2295,13 @@ def arch(self): "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.run}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) resources = self.get_resource('arch') - task_name = f'{self.run}arch' + task_name = f'{self.run}_arch' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -2309,31 +2321,40 @@ def arch(self): def cleanup(self): deps = [] if 'enkf' in self.run: - dep_dict = {'type': 'metatask', 'name': f'{self.run}eamn'} + dep_dict = {'type': 'metatask', 'name': f'{self.run}_eamn'} deps.append(rocoto.add_dependency(dep_dict)) else: - dep_dict = {'type': 'task', 'name': f'{self.run}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'} + 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'} + 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'} + 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'} + 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'} + dep_dict = {'type': 'task', 'name': f'{self.run}_npoess_pgrb2_0p5deg'} deps.append(rocoto.add_dependency(dep_dict)) + if self.app_config.do_metp and self.run in ['gfs']: + deps2 = [] + # taskvalid only handles regular tasks, so just check the first metp job exists + dep_dict = {'type': 'taskvalid', 'name': f'{self.run}_metpg2g1', 'condition': 'not'} + deps2.append(rocoto.add_dependency(dep_dict)) + dep_dict = {'type': 'metatask', 'name': f'{self.run}_metp'} + deps2.append(rocoto.add_dependency(dep_dict)) + deps.append(rocoto.create_dependency(dep_condition='or', dep=deps2)) + dependencies = rocoto.create_dependency(dep_condition='and', dep=deps) resources = self.get_resource('cleanup') - task_name = f'{self.run}cleanup' + task_name = f'{self.run}_cleanup' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -2352,14 +2373,14 @@ def cleanup(self): # Start of ensemble tasks def eobs(self): deps = [] - dep_dict = {'type': 'task', 'name': f'{self.run.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'])}"} + dep_dict = {'type': 'metatask', 'name': 'enkfgdas_epmn', 'offset': f"-{timedelta_to_HMS(self._base['interval_gdas'])}"} 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.run}eobs' + task_name = f'{self.run}_eobs' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -2377,7 +2398,7 @@ def eobs(self): def eomg(self): deps = [] - dep_dict = {'type': 'task', 'name': f'{self.run}eobs'} + dep_dict = {'type': 'task', 'name': f'{self.run}_eobs'} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep=deps) @@ -2389,7 +2410,7 @@ def eomg(self): eomgenvars.append(rocoto.create_envar(name=key, value=str(value))) resources = self.get_resource('eomg') - task_name = f'{self.run}eomg_mem#member#' + task_name = f'{self.run}_eomg_mem#member#' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -2402,7 +2423,7 @@ def eomg(self): } 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', + metatask_dict = {'task_name': f'{self.run}_eomg', 'var_dict': member_var_dict, 'task_dict': task_dict, } @@ -2413,12 +2434,12 @@ def eomg(self): def ediag(self): deps = [] - dep_dict = {'type': 'task', 'name': f'{self.run}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.run}ediag' + task_name = f'{self.run}_ediag' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -2437,14 +2458,14 @@ def ediag(self): def eupd(self): deps = [] if self.app_config.lobsdiag_forenkf: - dep_dict = {'type': 'task', 'name': f'{self.run}ediag'} + dep_dict = {'type': 'task', 'name': f'{self.run}_ediag'} else: - dep_dict = {'type': 'metatask', 'name': f'{self.run}eomg'} + 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.run}eupd' + task_name = f'{self.run}_eupd' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -2462,15 +2483,15 @@ def eupd(self): def atmensanlinit(self): deps = [] - dep_dict = {'type': 'task', 'name': f'{self.run.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'])}"} + dep_dict = {'type': 'metatask', 'name': 'enkfgdas_epmn', 'offset': f"-{timedelta_to_HMS(self._base['interval_gdas'])}"} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep_condition='and', dep=deps) cycledef = "gdas" resources = self.get_resource('atmensanlinit') - task_name = f'{self.run}atmensanlinit' + task_name = f'{self.run}_atmensanlinit' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -2489,14 +2510,14 @@ def atmensanlinit(self): def atmensanlobs(self): deps = [] - dep_dict = {'type': 'task', 'name': f'{self.run}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'])}"} + dep_dict = {'type': 'metatask', 'name': 'enkfgdas_epmn', 'offset': f"-{timedelta_to_HMS(self._base['interval_gdas'])}"} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep_condition='and', dep=deps) resources = self.get_resource('atmensanlobs') - task_name = f'{self.run}atmensanlobs' + task_name = f'{self.run}_atmensanlobs' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -2515,14 +2536,14 @@ def atmensanlobs(self): def atmensanlsol(self): deps = [] - dep_dict = {'type': 'task', 'name': f'{self.run}atmensanlobs'} + dep_dict = {'type': 'task', 'name': f'{self.run}_atmensanlobs'} deps.append(rocoto.add_dependency(dep_dict)) - dep_dict = {'type': 'metatask', 'name': 'enkfgdasepmn', 'offset': f"-{timedelta_to_HMS(self._base['cycle_interval'])}"} + dep_dict = {'type': 'metatask', 'name': 'enkfgdas_epmn', 'offset': f"-{timedelta_to_HMS(self._base['interval_gdas'])}"} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep_condition='and', dep=deps) resources = self.get_resource('atmensanlsol') - task_name = f'{self.run}atmensanlsol' + task_name = f'{self.run}_atmensanlsol' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -2541,14 +2562,14 @@ def atmensanlsol(self): def atmensanlletkf(self): deps = [] - dep_dict = {'type': 'task', 'name': f'{self.run}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'])}"} + dep_dict = {'type': 'metatask', 'name': 'enkfgdas_epmn', 'offset': f"-{timedelta_to_HMS(self._base['interval_gdas'])}"} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep_condition='and', dep=deps) resources = self.get_resource('atmensanlletkf') - task_name = f'{self.run}atmensanlletkf' + task_name = f'{self.run}_atmensanlletkf' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -2568,16 +2589,16 @@ def atmensanlfv3inc(self): deps = [] if self.app_config.lobsdiag_forenkf: - dep_dict = {'type': 'task', 'name': f'{self.run}atmensanlsol'} + dep_dict = {'type': 'task', 'name': f'{self.run}_atmensanlsol'} else: - dep_dict = {'type': 'task', 'name': f'{self.run}atmensanlletkf'} + 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'])}"} + dep_dict = {'type': 'metatask', 'name': 'enkfgdas_epmn', 'offset': f"-{timedelta_to_HMS(self._base['interval_gdas'])}"} 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_name = f'{self.run}_atmensanlfv3inc' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -2596,12 +2617,12 @@ def atmensanlfv3inc(self): def atmensanlfinal(self): deps = [] - dep_dict = {'type': 'task', 'name': f'{self.run}atmensanlfv3inc'} + 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.run}atmensanlfinal' + task_name = f'{self.run}_atmensanlfinal' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -2643,12 +2664,12 @@ def _get_ecengroups(): return grp, dep, lst deps = [] - dep_dict = {'type': 'task', 'name': f'{self.run.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.run}atmensanlfinal'} + dep_dict = {'type': 'task', 'name': f'{self.run}_atmensanlfinal'} else: - dep_dict = {'type': 'task', 'name': f'{self.run}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) @@ -2664,7 +2685,7 @@ def _get_ecengroups(): resources = self.get_resource('ecen') - task_name = f'{self.run}ecen#{varname1}#' + task_name = f'{self.run}_ecen#{varname1}#' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -2676,7 +2697,7 @@ def _get_ecengroups(): 'maxtries': '&MAXTRIES;' } - metatask_dict = {'task_name': f'{self.run}ecmn', + metatask_dict = {'task_name': f'{self.run}_ecmn', 'var_dict': var_dict, 'task_dict': task_dict } @@ -2689,20 +2710,20 @@ def esfc(self): # eupd_run = 'gdas' if 'gdas' in self.app_config.eupd_runs else 'gfs' deps = [] - dep_dict = {'type': 'task', 'name': f'{self.run.replace("enkf","")}analcalc'} + 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.run}atmensanlfinal'} + dep_dict = {'type': 'task', 'name': f'{self.run}_atmensanlfinal'} else: - dep_dict = {'type': 'task', 'name': f'{self.run}eupd'} + dep_dict = {'type': 'task', 'name': f'{self.run}_eupd'} deps.append(rocoto.add_dependency(dep_dict)) if self.app_config.do_jedisnowda: - dep_dict = {'type': 'task', 'name': f'{self.run}esnowrecen'} + dep_dict = {'type': 'task', 'name': f'{self.run}_esnowrecen'} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep_condition='and', dep=deps) resources = self.get_resource('esfc') - task_name = f'{self.run}esfc' + task_name = f'{self.run}_esfc' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -2721,12 +2742,12 @@ def esfc(self): def efcs(self): deps = [] - dep_dict = {'type': 'metatask', 'name': f'{self.run}ecmn'} + dep_dict = {'type': 'metatask', 'name': f'{self.run}_ecmn'} deps.append(rocoto.add_dependency(dep_dict)) - dep_dict = {'type': 'task', 'name': f'{self.run}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': 'task', 'name': f'{self.run}stage_ic'} + dep_dict = {'type': 'task', 'name': f'{self.run}_stage_ic'} dependencies.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep_condition='or', dep=dependencies) @@ -2740,7 +2761,7 @@ def efcs(self): cycledef = 'gdas_half,gdas' if self.run in ['enkfgdas'] else self.run.replace('enkf', '') resources = self.get_resource('efcs') - task_name = f'{self.run}fcst_mem#member#' + task_name = f'{self.run}_fcst_mem#member#' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -2753,7 +2774,7 @@ def efcs(self): } 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', + metatask_dict = {'task_name': f'{self.run}_fcst', 'var_dict': member_var_dict, 'task_dict': task_dict } @@ -2767,16 +2788,16 @@ def echgres(self): self._is_this_a_gdas_task(self.run, 'echgres') deps = [] - dep_dict = {'type': 'metatask', 'name': f'{self.run.replace("enkf","")}fcst'} + dep_dict = {'type': 'metatask', 'name': f'{self.run.replace("enkf","")}_fcst'} deps.append(rocoto.add_dependency(dep_dict)) - dep_dict = {'type': 'task', 'name': f'{self.run}fcst_mem001'} + 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.run in ['enkfgdas'] else self.run resources = self.get_resource('echgres') - task_name = f'{self.run}echgres' + task_name = f'{self.run}_echgres' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -2817,7 +2838,7 @@ def _get_eposgroups(epos): return grp, dep, lst deps = [] - dep_dict = {'type': 'metatask', 'name': f'{self.run}fcst'} + dep_dict = {'type': 'metatask', 'name': f'{self.run}_fcst'} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep=deps) @@ -2835,7 +2856,7 @@ def _get_eposgroups(epos): resources = self.get_resource('epos') - task_name = f'{self.run}epos#{varname1}#' + task_name = f'{self.run}_epos#{varname1}#' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -2847,7 +2868,7 @@ def _get_eposgroups(epos): 'maxtries': '&MAXTRIES;' } - metatask_dict = {'task_name': f'{self.run}epmn', + metatask_dict = {'task_name': f'{self.run}_epmn', 'var_dict': var_dict, 'task_dict': task_dict } @@ -2859,7 +2880,7 @@ def _get_eposgroups(epos): def earc(self): deps = [] - dep_dict = {'type': 'metatask', 'name': f'{self.run}epmn'} + dep_dict = {'type': 'metatask', 'name': f'{self.run}_epmn'} deps.append(rocoto.add_dependency(dep_dict)) dependencies = rocoto.create_dependency(dep=deps) @@ -2874,7 +2895,7 @@ def earc(self): var_dict = {'grp': groups} - task_name = f'{self.run}earc#grp#' + task_name = f'{self.run}_earc#grp#' task_dict = {'task_name': task_name, 'resources': resources, 'dependency': dependencies, @@ -2886,7 +2907,7 @@ def earc(self): 'maxtries': '&MAXTRIES;' } - metatask_dict = {'task_name': f'{self.run}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 2a20820da8..7bacf99829 100644 --- a/workflow/rocoto/rocoto.py +++ b/workflow/rocoto/rocoto.py @@ -183,6 +183,7 @@ def add_dependency(dep_dict: Dict[str, Any]) -> str: 'metatask': _add_task_tag, 'data': _add_data_tag, 'cycleexist': _add_cycle_tag, + 'taskvalid': _add_taskvalid_tag, 'streq': _add_streq_tag, 'strneq': _add_streq_tag, 'sh': _add_sh_tag} @@ -296,6 +297,27 @@ def _add_cycle_tag(dep_dict: Dict[str, Any]) -> str: return string +def _add_taskvalid_tag(dep_dict: Dict[str, Any]) -> str: + """ + create a validtask tag + :param dep_dict: dependency key-value parameters + :type dep_dict: dict + :return: Rocoto validtask dependency + :rtype: str + """ + + dep_type = dep_dict.get('type', None) + dep_name = dep_dict.get('name', None) + + if dep_name is None: + msg = f'a {dep_type} name is necessary for {dep_type} dependency' + raise KeyError(msg) + + string = f'<{dep_type} task="{dep_name}"/>' + + return string + + def _add_streq_tag(dep_dict: Dict[str, Any]) -> str: """ create a simple string comparison tag diff --git a/workflow/rocoto/tasks.py b/workflow/rocoto/tasks.py index df2b0467db..92ceea73aa 100644 --- a/workflow/rocoto/tasks.py +++ b/workflow/rocoto/tasks.py @@ -15,7 +15,7 @@ class Tasks: 'prep', 'anal', 'sfcanl', 'analcalc', 'analdiag', 'arch', "cleanup", 'prepatmiodaobs', 'atmanlinit', 'atmanlvar', 'atmanlfv3inc', 'atmanlfinal', 'prepoceanobs', - 'ocnanalprep', 'marinebmat', 'ocnanalrun', 'ocnanalecen', 'ocnanalchkpt', 'ocnanalpost', 'ocnanalvrfy', + 'marineanlinit', 'marinebmat', 'marineanlvar', 'ocnanalecen', 'marineanlchkpt', 'marineanlfinal', 'ocnanalvrfy', 'earc', 'ecen', 'echgres', 'ediag', 'efcs', 'eobs', 'eomg', 'epos', 'esfc', 'eupd', 'atmensanlinit', 'atmensanlobs', 'atmensanlsol', 'atmensanlletkf', 'atmensanlfv3inc', 'atmensanlfinal', @@ -57,7 +57,8 @@ def __init__(self, app_config: AppConfig, run: str) -> None: 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._base['interval_gdas'] = to_timedelta(f'{self._base["assim_freq"]}H') + self._base['interval_gfs'] = to_timedelta(f'{self._base["INTERVAL_GFS"]}H') self.n_tiles = 6 # TODO - this needs to be elsewhere diff --git a/workflow/setup_expt.py b/workflow/setup_expt.py index e213394e20..f32203e600 100755 --- a/workflow/setup_expt.py +++ b/workflow/setup_expt.py @@ -8,7 +8,7 @@ import glob import shutil import warnings -from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter, SUPPRESS +from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter, SUPPRESS, ArgumentTypeError from hosts import Host @@ -116,7 +116,8 @@ def edit_baseconfig(host, inputs, yaml_dict): "@COMROOT@": inputs.comroot, "@EXP_WARM_START@": is_warm_start, "@MODE@": inputs.mode, - "@gfs_cyc@": inputs.gfs_cyc, + "@INTERVAL_GFS@": inputs.interval, + "@SDATE_GFS@": datetime_to_YMDH(inputs.sdate_gfs), "@APP@": inputs.app, "@NMEM_ENS@": getattr(inputs, 'nens', 0) } @@ -186,6 +187,19 @@ def input_args(*argv): ufs_apps = ['ATM', 'ATMA', 'ATMW', 'S2S', 'S2SA', 'S2SW', 'S2SWA'] + def _validate_interval(interval_str): + err_msg = f'must be a non-negative integer multiple of 6 ({interval_str} given)' + try: + interval = int(interval_str) + except ValueError: + raise ArgumentTypeError(err_msg) + + # This assumes the gdas frequency (assim_freq) is 6h + # If this changes, the modulus needs to as well + if interval < 0 or interval % 6 != 0: + raise ArgumentTypeError(err_msg) + return interval + def _common_args(parser): parser.add_argument('--pslot', help='parallel experiment name', type=str, required=False, default='test') @@ -199,7 +213,8 @@ def _common_args(parser): type=str, required=False, default=os.getenv('HOME')) 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('--edate', help='end date experiment', required=False, type=lambda dd: to_datetime(dd)) + parser.add_argument('--interval', help='frequency of forecast (in hours); must be a multiple of 6', type=_validate_interval, required=False, default=6) parser.add_argument('--icsdir', help='full path to user initial condition directory', type=str, required=False, default='') parser.add_argument('--overwrite', help='overwrite previously created experiment (if it exists)', action='store_true', required=False) @@ -219,8 +234,7 @@ def _gfs_args(parser): def _gfs_cycled_args(parser): parser.add_argument('--app', help='UFS application', type=str, choices=ufs_apps, required=False, default='ATM') - parser.add_argument('--gfs_cyc', help='cycles to run forecast', type=int, - choices=[0, 1, 2, 4], default=1, required=False) + parser.add_argument('--sdate_gfs', help='date to start GFS', type=lambda dd: to_datetime(dd), required=False, default=None) return parser def _gfs_or_gefs_ensemble_args(parser): @@ -233,8 +247,6 @@ 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, 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 def _gefs_args(parser): @@ -291,7 +303,28 @@ def _gefs_args(parser): for subp in [gefsforecasts]: subp = _gefs_args(subp) - return parser.parse_args(list(*argv) if len(argv) else None) + inputs = parser.parse_args(list(*argv) if len(argv) else None) + + # Validate dates + if inputs.edate is None: + inputs.edate = inputs.idate + + if inputs.edate < inputs.idate: + raise ArgumentTypeError(f'edate ({inputs.edate}) cannot be before idate ({inputs.idate})') + + # For forecast-only, GFS starts in the first cycle + if not hasattr(inputs, 'sdate_gfs'): + inputs.sdate_gfs = inputs.idate + + # For cycled, GFS starts after the half-cycle + if inputs.sdate_gfs is None: + inputs.sdate_gfs = inputs.idate + to_timedelta("6H") + + if inputs.interval > 0: + if inputs.sdate_gfs < inputs.idate or inputs.sdate_gfs > inputs.edate: + raise ArgumentTypeError(f'sdate_gfs ({inputs.sdate_gfs}) must be between idate ({inputs.idate}) and edate ({inputs.edate})') + + return inputs def query_and_clean(dirname, force_clean=False): @@ -303,7 +336,7 @@ def query_and_clean(dirname, force_clean=False): if os.path.exists(dirname): print(f'\ndirectory already exists in {dirname}') if force_clean: - overwrite = True + overwrite = "YES" print(f'removing directory ........ {dirname}\n') else: overwrite = input('Do you wish to over-write [y/N]: ')