diff --git a/env/HERCULES.env b/env/HERCULES.env index 15e3928f08..fccc2f87a5 100755 --- a/env/HERCULES.env +++ b/env/HERCULES.env @@ -145,7 +145,7 @@ case ${step} in export NTHREADS_OCNANALECEN=${threads_per_task_ocnanalecen:-${max_threads_per_task}} [[ ${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}" -;; + ;; "marineanlchkpt") export APRUNCFP="${launcher} -n \$ncmd ${mpmd_opt}" @@ -153,6 +153,11 @@ case ${step} in export NTHREADS_OCNANAL=${NTHREADSmax} export APRUN_MARINEANLCHKPT="${APRUN_default} --cpus-per-task=${NTHREADS_OCNANAL}" ;; + "marineanlletkf") + + export NTHREADS_MARINEANLLETKF=${NTHREADSmax} + export APRUN_MARINEANLLETKF="${APRUN_default}" + ;; "anal" | "analcalc") export MKL_NUM_THREADS=4 diff --git a/env/WCOSS2.env b/env/WCOSS2.env index fff8f7b096..27001bebd7 100755 --- a/env/WCOSS2.env +++ b/env/WCOSS2.env @@ -107,17 +107,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/parm/config/gfs/config.atmanl b/parm/config/gfs/config.atmanl index 9a06088ecc..1d700a479c 100644 --- a/parm/config/gfs/config.atmanl +++ b/parm/config/gfs/config.atmanl @@ -5,8 +5,7 @@ echo "BEGIN: config.atmanl" -export JCB_BASE_YAML="${PARMgfs}/gdas/atm/jcb-base.yaml.j2" -export JCB_ALGO_YAML=@JCB_ALGO_YAML@ +export JCB_ALGO_YAML_VAR=@JCB_ALGO_YAML_VAR@ export STATICB_TYPE=@STATICB_TYPE@ export LOCALIZATION_TYPE="bump" @@ -23,6 +22,8 @@ fi export CRTM_FIX_YAML="${PARMgfs}/gdas/atm_crtm_coeff.yaml.j2" export JEDI_FIX_YAML="${PARMgfs}/gdas/atm_jedi_fix.yaml.j2" + +export JEDI_CONFIG_YAML="${PARMgfs}/gdas/atmanl_jedi_config.yaml.j2" export VAR_BKG_STAGING_YAML="${PARMgfs}/gdas/staging/atm_var_bkg.yaml.j2" export BERROR_STAGING_YAML="${PARMgfs}/gdas/staging/atm_berror_${STATICB_TYPE}.yaml.j2" export FV3ENS_STAGING_YAML="${PARMgfs}/gdas/staging/atm_var_fv3ens.yaml.j2" @@ -33,6 +34,4 @@ export layout_y_atmanl=@LAYOUT_Y_ATMANL@ export io_layout_x=@IO_LAYOUT_X@ export io_layout_y=@IO_LAYOUT_Y@ -export JEDIEXE=${EXECgfs}/gdas.x - echo "END: config.atmanl" diff --git a/parm/config/gfs/config.atmanlfv3inc b/parm/config/gfs/config.atmanlfv3inc index ab7efa3a60..4e7714628e 100644 --- a/parm/config/gfs/config.atmanlfv3inc +++ b/parm/config/gfs/config.atmanlfv3inc @@ -8,7 +8,4 @@ echo "BEGIN: config.atmanlfv3inc" # Get task specific resources . "${EXPDIR}/config.resources" atmanlfv3inc -export JCB_ALGO=fv3jedi_fv3inc_variational -export JEDIEXE=${EXECgfs}/fv3jedi_fv3inc.x - echo "END: config.atmanlfv3inc" diff --git a/parm/config/gfs/config.atmensanl b/parm/config/gfs/config.atmensanl index f5a1278248..2726f655bd 100644 --- a/parm/config/gfs/config.atmensanl +++ b/parm/config/gfs/config.atmensanl @@ -5,17 +5,16 @@ echo "BEGIN: config.atmensanl" -export JCB_BASE_YAML="${PARMgfs}/gdas/atm/jcb-base.yaml.j2" -if [[ ${lobsdiag_forenkf} = ".false." ]] ; then - export JCB_ALGO_YAML=@JCB_ALGO_YAML_LETKF@ -else - export JCB_ALGO_YAML=@JCB_ALGO_YAML_OBS@ -fi +export JCB_ALGO_YAML_LETKF=@JCB_ALGO_YAML_LETKF@ +export JCB_ALGO_YAML_OBS=@JCB_ALGO_YAML_OBS@ +export JCB_ALGO_YAML_SOL=@JCB_ALGO_YAML_SOL@ export INTERP_METHOD='barycentric' export CRTM_FIX_YAML="${PARMgfs}/gdas/atm_crtm_coeff.yaml.j2" export JEDI_FIX_YAML="${PARMgfs}/gdas/atm_jedi_fix.yaml.j2" + +export JEDI_CONFIG_YAML="${PARMgfs}/gdas/atmensanl_jedi_config.yaml.j2" export LGETKF_BKG_STAGING_YAML="${PARMgfs}/gdas/staging/atm_lgetkf_bkg.yaml.j2" export layout_x_atmensanl=@LAYOUT_X_ATMENSANL@ @@ -24,6 +23,4 @@ export layout_y_atmensanl=@LAYOUT_Y_ATMENSANL@ export io_layout_x=@IO_LAYOUT_X@ export io_layout_y=@IO_LAYOUT_Y@ -export JEDIEXE=${EXECgfs}/gdas.x - echo "END: config.atmensanl" diff --git a/parm/config/gfs/config.atmensanlfv3inc b/parm/config/gfs/config.atmensanlfv3inc index 2dc73f3f6e..fe3337e5a2 100644 --- a/parm/config/gfs/config.atmensanlfv3inc +++ b/parm/config/gfs/config.atmensanlfv3inc @@ -8,7 +8,4 @@ echo "BEGIN: config.atmensanlfv3inc" # Get task specific resources . "${EXPDIR}/config.resources" atmensanlfv3inc -export JCB_ALGO=fv3jedi_fv3inc_lgetkf -export JEDIEXE=${EXECgfs}/fv3jedi_fv3inc.x - echo "END: config.atmensanlfv3inc" diff --git a/parm/config/gfs/config.atmensanlobs b/parm/config/gfs/config.atmensanlobs index dff3fa3095..c7e050b009 100644 --- a/parm/config/gfs/config.atmensanlobs +++ b/parm/config/gfs/config.atmensanlobs @@ -8,6 +8,4 @@ echo "BEGIN: config.atmensanlobs" # Get task specific resources . "${EXPDIR}/config.resources" atmensanlobs -export JCB_ALGO_YAML=@JCB_ALGO_YAML@ - echo "END: config.atmensanlobs" diff --git a/parm/config/gfs/config.atmensanlsol b/parm/config/gfs/config.atmensanlsol index dac161373b..8ef905d1bd 100644 --- a/parm/config/gfs/config.atmensanlsol +++ b/parm/config/gfs/config.atmensanlsol @@ -8,6 +8,4 @@ echo "BEGIN: config.atmensanlsol" # Get task specific resources . "${EXPDIR}/config.resources" atmensanlsol -export JCB_ALGO_YAML=@JCB_ALGO_YAML@ - echo "END: config.atmensanlsol" diff --git a/parm/config/gfs/config.marineanl b/parm/config/gfs/config.marineanl index a19fc015e2..0b55fa447d 100644 --- a/parm/config/gfs/config.marineanl +++ b/parm/config/gfs/config.marineanl @@ -5,6 +5,8 @@ echo "BEGIN: config.marineanl" +export JEDI_CONFIG_YAML="${PARMgfs}/gdas/soca_bmat_jedi_config.yaml.j2" + 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@ diff --git a/parm/config/gfs/config.marineanlletkf b/parm/config/gfs/config.marineanlletkf index 8b84af4eaa..93f03f80ff 100644 --- a/parm/config/gfs/config.marineanlletkf +++ b/parm/config/gfs/config.marineanlletkf @@ -14,7 +14,7 @@ export MARINE_LETKF_STAGE_YAML_TMPL="${PARMgfs}/gdas/soca/letkf/letkf_stage.yaml export MARINE_LETKF_SAVE_YAML_TMPL="${PARMgfs}/gdas/soca/letkf/letkf_save.yaml.j2" export GRIDGEN_EXEC="${EXECgfs}/gdas_soca_gridgen.x" -export GRIDGEN_YAML="${PARMgfs}/gdas/soca/gridgen/gridgen.yaml" +export GRIDGEN_YAML="${HOMEgfs}/sorc/gdas.cd/parm/jcb-gdas/algorithm/marine/soca_gridgen.yaml.j2" export DIST_HALO_SIZE=500000 echo "END: config.marineanlletkf" diff --git a/parm/config/gfs/config.marinebmat b/parm/config/gfs/config.marinebmat index 00352737d0..d88739dced 100644 --- a/parm/config/gfs/config.marinebmat +++ b/parm/config/gfs/config.marinebmat @@ -8,12 +8,4 @@ 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.resources.ORION b/parm/config/gfs/config.resources.ORION index 461b6f14f7..fceb8feeb1 100644 --- a/parm/config/gfs/config.resources.ORION +++ b/parm/config/gfs/config.resources.ORION @@ -25,7 +25,8 @@ case ${step} in ;; "atmanlvar") # Run on 8 nodes for memory requirement - export tasks_per_node=8 + export tasks_per_node_gdas=8 + export tasks_per_node_gfs=8 export walltime="00:45:00" ;; "atmensanlobs") diff --git a/parm/config/gfs/yaml/defaults.yaml b/parm/config/gfs/yaml/defaults.yaml index 78caf46f5d..d8cf76a47b 100644 --- a/parm/config/gfs/yaml/defaults.yaml +++ b/parm/config/gfs/yaml/defaults.yaml @@ -23,7 +23,7 @@ base: FHMAX_ENKF_GFS: 12 atmanl: - JCB_ALGO_YAML: "${PARMgfs}/gdas/atm/jcb-prototype_3dvar.yaml.j2" + JCB_ALGO_YAML_VAR: "${PARMgfs}/gdas/atm/jcb-prototype_3dvar.yaml.j2" STATICB_TYPE: "gsibec" LAYOUT_X_ATMANL: 8 LAYOUT_Y_ATMANL: 8 @@ -33,16 +33,11 @@ atmanl: atmensanl: JCB_ALGO_YAML_LETKF: "${PARMgfs}/gdas/atm/jcb-prototype_lgetkf.yaml.j2" JCB_ALGO_YAML_OBS: "${PARMgfs}/gdas/atm/jcb-prototype_lgetkf_observer.yaml.j2" + JCB_ALGO_YAML_SOL: "${PARMgfs}/gdas/atm/jcb-prototype_lgetkf_solver.yaml.j2" LAYOUT_X_ATMENSANL: 8 LAYOUT_Y_ATMENSANL: 8 IO_LAYOUT_X: 1 IO_LAYOUT_Y: 1 - -atmensanlobs: - JCB_ALGO_YAML: "${PARMgfs}/gdas/atm/jcb-prototype_lgetkf_observer.yaml.j2" - -atmensanlsol: - JCB_ALGO_YAML: "${PARMgfs}/gdas/atm/jcb-prototype_lgetkf_solver.yaml.j2" aeroanl: IO_LAYOUT_X: 1 diff --git a/parm/gdas/atmanl_jedi_config.yaml.j2 b/parm/gdas/atmanl_jedi_config.yaml.j2 new file mode 100644 index 0000000000..4046ba0931 --- /dev/null +++ b/parm/gdas/atmanl_jedi_config.yaml.j2 @@ -0,0 +1,13 @@ +atmanlvar: + rundir: '{{ DATA }}' + exe_src: '{{ EXECgfs }}/gdas.x' + mpi_cmd: '{{ APRUN_ATMANLVAR }}' + jedi_args: ['fv3jedi', 'variational'] + jcb_base_yaml: '{{ PARMgfs }}/gdas/atm/jcb-base.yaml.j2' + jcb_algo_yaml: '{{ JCB_ALGO_YAML_VAR }}' +atmanlfv3inc: + rundir: '{{ DATA }}' + exe_src: '{{ EXECgfs }}/fv3jedi_fv3inc.x' + mpi_cmd: '{{ APRUN_ATMANLFV3INC }}' + jcb_base_yaml: '{{ PARMgfs }}/gdas/atm/jcb-base.yaml.j2' + jcb_algo: fv3jedi_fv3inc_variational diff --git a/parm/gdas/atmensanl_jedi_config.yaml.j2 b/parm/gdas/atmensanl_jedi_config.yaml.j2 new file mode 100644 index 0000000000..9ab2ec6ace --- /dev/null +++ b/parm/gdas/atmensanl_jedi_config.yaml.j2 @@ -0,0 +1,27 @@ +atmensanlobs: + rundir: '{{ DATA }}' + exe_src: '{{ EXECgfs }}/gdas.x' + mpi_cmd: '{{ APRUN_ATMENSANLOBS }}' + jedi_args: ['fv3jedi', 'localensembleda'] + jcb_base_yaml: '{{ PARMgfs }}/gdas/atm/jcb-base.yaml.j2' + jcb_algo_yaml: '{{ JCB_ALGO_YAML_OBS }}' +atmensanlsol: + rundir: '{{ DATA }}' + exe_src: '{{ EXECgfs }}/gdas.x' + mpi_cmd: '{{ APRUN_ATMENSANLSOL }}' + jedi_args: ['fv3jedi', 'localensembleda'] + jcb_base_yaml: '{{ PARMgfs }}/gdas/atm/jcb-base.yaml.j2' + jcb_algo_yaml: '{{ JCB_ALGO_YAML_SOL }}' +atmensanlfv3inc: + rundir: '{{ DATA }}' + exe_src: '{{ EXECgfs }}/fv3jedi_fv3inc.x' + mpi_cmd: '{{ APRUN_ATMENSANLFV3INC }}' + jcb_base_yaml: '{{ PARMgfs }}/gdas/atm/jcb-base.yaml.j2' + jcb_algo: fv3jedi_fv3inc_lgetkf +atmensanlletkf: + rundir: '{{ DATA }}' + exe_src: '{{ EXECgfs }}/gdas.x' + mpi_cmd: '{{ APRUN_ATMENSANLLETKF }}' + jedi_args: ['fv3jedi', 'localensembleda'] + jcb_base_yaml: '{{ PARMgfs }}/gdas/atm/jcb-base.yaml.j2' + jcb_algo_yaml: '{{ JCB_ALGO_YAML_LETKF }}' diff --git a/parm/gdas/soca_bmat_jedi_config.yaml.j2 b/parm/gdas/soca_bmat_jedi_config.yaml.j2 new file mode 100644 index 0000000000..4e476d3117 --- /dev/null +++ b/parm/gdas/soca_bmat_jedi_config.yaml.j2 @@ -0,0 +1,42 @@ +gridgen: + rundir: '{{ DATA }}' + exe_src: '{{ EXECgfs }}/gdas_soca_gridgen.x' + mpi_cmd: '{{ APRUN_MARINEBMAT }}' + jcb_base_yaml: '{{ PARMgfs }}/gdas/soca/marine-jcb-base.yaml' + jcb_algo: soca_gridgen +soca_diagb: + rundir: '{{ DATA }}' + exe_src: '{{ EXECgfs }}/gdas_soca_diagb.x' + mpi_cmd: '{{ APRUN_MARINEBMAT }}' + jcb_base_yaml: '{{ PARMgfs }}/gdas/soca/marine-jcb-base.yaml' + jcb_algo: soca_diagb +soca_parameters_diffusion_vt: + rundir: '{{ DATA }}' + exe_src: '{{ EXECgfs }}/gdas_soca_error_covariance_toolbox.x' + mpi_cmd: '{{ APRUN_MARINEBMAT }}' + jcb_base_yaml: '{{ PARMgfs }}/gdas/soca/marine-jcb-base.yaml' + jcb_algo: soca_parameters_diffusion_vt +soca_setcorscales: + rundir: '{{ DATA }}' + exe_src: '{{ EXECgfs }}/gdas_soca_setcorscales.x' + mpi_cmd: '{{ APRUN_MARINEBMAT }}' + jcb_base_yaml: '{{ PARMgfs }}/gdas/soca/marine-jcb-base.yaml' + jcb_algo: soca_setcorscales +soca_parameters_diffusion_hz: + rundir: '{{ DATA }}' + exe_src: '{{ EXECgfs }}/gdas_soca_error_covariance_toolbox.x' + mpi_cmd: '{{ APRUN_MARINEBMAT }}' + jcb_base_yaml: '{{ PARMgfs }}/gdas/soca/marine-jcb-base.yaml' + jcb_algo: soca_parameters_diffusion_hz +soca_ensb: + rundir: '{{ DATA }}' + exe_src: '{{ EXECgfs }}/gdas_ens_handler.x' + mpi_cmd: '{{ APRUN_MARINEBMAT }}' + jcb_base_yaml: '{{ PARMgfs }}/gdas/soca/marine-jcb-base.yaml' + jcb_algo: soca_ensb +soca_ensweights: + rundir: '{{ DATA }}' + exe_src: '{{ EXECgfs }}/gdas_socahybridweights.x' + mpi_cmd: '{{ APRUN_MARINEBMAT }}' + jcb_base_yaml: '{{ PARMgfs }}/gdas/soca/marine-jcb-base.yaml' + jcb_algo: soca_ensweights diff --git a/scripts/exglobal_atm_analysis_fv3_increment.py b/scripts/exglobal_atm_analysis_fv3_increment.py index 72413ddbd4..c5a3e70943 100755 --- a/scripts/exglobal_atm_analysis_fv3_increment.py +++ b/scripts/exglobal_atm_analysis_fv3_increment.py @@ -1,8 +1,8 @@ #!/usr/bin/env python3 # exglobal_atm_analysis_fv3_increment.py # This script creates an AtmAnalysis object -# and runs the initialize_fv3inc and execute methods -# which convert the JEDI increment into an FV3 increment +# and runs the execute method which runs the JEDI +# FV3 increment converter import os from wxflow import Logger, cast_strdict_as_dtypedict @@ -18,8 +18,7 @@ config = cast_strdict_as_dtypedict(os.environ) # Instantiate the atm analysis object - AtmAnl = AtmAnalysis(config, 'atmanlfv3inc') + AtmAnl = AtmAnalysis(config) # Initialize and execute FV3 increment converter - AtmAnl.initialize_jedi() - AtmAnl.execute(config.APRUN_ATMANLFV3INC) + AtmAnl.execute('atmanlfv3inc') diff --git a/scripts/exglobal_atm_analysis_initialize.py b/scripts/exglobal_atm_analysis_initialize.py index 9deae07bb3..749d320111 100755 --- a/scripts/exglobal_atm_analysis_initialize.py +++ b/scripts/exglobal_atm_analysis_initialize.py @@ -2,8 +2,8 @@ # exglobal_atm_analysis_initialize.py # This script creates an AtmAnalysis class # and runs the initialize method -# which create and stage the runtime directory -# and create the YAML configuration +# which creates and stages the runtime directory +# and creates the YAML configuration # for a global atm variational analysis import os @@ -20,8 +20,7 @@ config = cast_strdict_as_dtypedict(os.environ) # Instantiate the atm analysis task - AtmAnl = AtmAnalysis(config, 'atmanlvar') + AtmAnl = AtmAnalysis(config) # Initialize JEDI variational analysis - AtmAnl.initialize_jedi() - AtmAnl.initialize_analysis() + AtmAnl.initialize() diff --git a/scripts/exglobal_atm_analysis_variational.py b/scripts/exglobal_atm_analysis_variational.py index 8359532069..9ad121f76c 100755 --- a/scripts/exglobal_atm_analysis_variational.py +++ b/scripts/exglobal_atm_analysis_variational.py @@ -1,8 +1,8 @@ #!/usr/bin/env python3 # exglobal_atm_analysis_variational.py # This script creates an AtmAnalysis object -# and runs the execute method -# which executes the global atm variational analysis +# and runs the execute method which runs the JEDI +# variational analysis application import os from wxflow import Logger, cast_strdict_as_dtypedict @@ -18,7 +18,7 @@ config = cast_strdict_as_dtypedict(os.environ) # Instantiate the atm analysis task - AtmAnl = AtmAnalysis(config, 'atmanlvar') + AtmAnl = AtmAnalysis(config) # Execute JEDI variational analysis - AtmAnl.execute(config.APRUN_ATMANLVAR, ['fv3jedi', 'variational']) + AtmAnl.execute('atmanlvar') diff --git a/scripts/exglobal_atmens_analysis_fv3_increment.py b/scripts/exglobal_atmens_analysis_fv3_increment.py index 48eb6a6a1e..4506b28033 100755 --- a/scripts/exglobal_atmens_analysis_fv3_increment.py +++ b/scripts/exglobal_atmens_analysis_fv3_increment.py @@ -1,8 +1,8 @@ #!/usr/bin/env python3 # exglobal_atmens_analysis_fv3_increment.py # This script creates an AtmEnsAnalysis object -# and runs the initialize_fv3inc and execute methods -# which convert the JEDI increment into an FV3 increment +# and runs the execute method which runs the JEDI +# FV3 increment converter application import os from wxflow import Logger, cast_strdict_as_dtypedict @@ -18,8 +18,7 @@ config = cast_strdict_as_dtypedict(os.environ) # Instantiate the atmens analysis object - AtmEnsAnl = AtmEnsAnalysis(config, 'atmensanlfv3inc') + AtmEnsAnl = AtmEnsAnalysis(config) # Initialize and execute JEDI FV3 increment converter - AtmEnsAnl.initialize_jedi() - AtmEnsAnl.execute(config.APRUN_ATMENSANLFV3INC) + AtmEnsAnl.execute('atmensanlfv3inc') diff --git a/scripts/exglobal_atmens_analysis_initialize.py b/scripts/exglobal_atmens_analysis_initialize.py index 326fe80628..124e755594 100755 --- a/scripts/exglobal_atmens_analysis_initialize.py +++ b/scripts/exglobal_atmens_analysis_initialize.py @@ -2,8 +2,8 @@ # exglobal_atmens_analysis_initialize.py # This script creates an AtmEnsAnalysis class # and runs the initialize method -# which create and stage the runtime directory -# and create the YAML configuration +# which creates and stages the runtime directory +# and creates the YAML configuration # for a global atm local ensemble analysis import os @@ -20,11 +20,7 @@ config = cast_strdict_as_dtypedict(os.environ) # Instantiate the atmens analysis task - if not config.lobsdiag_forenkf: - AtmEnsAnl = AtmEnsAnalysis(config, 'atmensanlletkf') - else: - AtmEnsAnl = AtmEnsAnalysis(config, 'atmensanlobs') + AtmEnsAnl = AtmEnsAnalysis(config) # Initialize JEDI ensemble DA analysis - AtmEnsAnl.initialize_jedi() - AtmEnsAnl.initialize_analysis() + AtmEnsAnl.initialize() diff --git a/scripts/exglobal_atmens_analysis_letkf.py b/scripts/exglobal_atmens_analysis_letkf.py index 45b06524fe..dea9ace5b8 100755 --- a/scripts/exglobal_atmens_analysis_letkf.py +++ b/scripts/exglobal_atmens_analysis_letkf.py @@ -1,8 +1,8 @@ #!/usr/bin/env python3 # exglobal_atmens_analysis_letkf.py # This script creates an AtmEnsAnalysis object -# and runs the execute method which executes -# the global atm local ensemble analysis +# and initializes and runs the full JEDI LETKF +# application import os from wxflow import Logger, cast_strdict_as_dtypedict @@ -18,7 +18,10 @@ config = cast_strdict_as_dtypedict(os.environ) # Instantiate the atmens analysis task - AtmEnsAnl = AtmEnsAnalysis(config, 'atmensanlletkf') + AtmEnsAnl = AtmEnsAnalysis(config) + + # Initalize JEDI full ensemble DA application + AtmEnsAnl.initialize_letkf() # Execute the JEDI ensemble DA analysis - AtmEnsAnl.execute(config.APRUN_ATMENSANLLETKF, ['fv3jedi', 'localensembleda']) + AtmEnsAnl.execute('atmensanlletkf') diff --git a/scripts/exglobal_atmens_analysis_obs.py b/scripts/exglobal_atmens_analysis_obs.py index c701f8cb4e..b09c67703f 100755 --- a/scripts/exglobal_atmens_analysis_obs.py +++ b/scripts/exglobal_atmens_analysis_obs.py @@ -1,8 +1,8 @@ #!/usr/bin/env python3 # exglobal_atmens_analysis_obs.py # This script creates an AtmEnsAnalysis object -# and runs the execute method -# which executes the global atm local ensemble analysis in observer mode +# and runs the execute method which runs the JEDI LETKF +# application in observer mode import os from wxflow import Logger, cast_strdict_as_dtypedict @@ -18,7 +18,7 @@ config = cast_strdict_as_dtypedict(os.environ) # Instantiate the atmens analysis task - AtmEnsAnl = AtmEnsAnalysis(config, 'atmensanlobs') + AtmEnsAnl = AtmEnsAnalysis(config) - # Initialize and execute JEDI ensembler DA analysis in observer mode - AtmEnsAnl.execute(config.APRUN_ATMENSANLOBS, ['fv3jedi', 'localensembleda']) + # Execute JEDI ensembler DA analysis in observer mode + AtmEnsAnl.execute('atmensanlobs') diff --git a/scripts/exglobal_atmens_analysis_sol.py b/scripts/exglobal_atmens_analysis_sol.py index be78e694b1..85dc228a5a 100755 --- a/scripts/exglobal_atmens_analysis_sol.py +++ b/scripts/exglobal_atmens_analysis_sol.py @@ -1,8 +1,8 @@ #!/usr/bin/env python3 # exglobal_atmens_analysis_sol.py # This script creates an AtmEnsAnalysis object -# and runs the execute method -# which executes the global atm local ensemble analysis in solver mode +# and runs the execute method which runs the JEDI LETKF +# application in solver mode import os from wxflow import Logger, cast_strdict_as_dtypedict @@ -18,8 +18,7 @@ config = cast_strdict_as_dtypedict(os.environ) # Instantiate the atmens analysis task - AtmEnsAnl = AtmEnsAnalysis(config, 'atmensanlsol') + AtmEnsAnl = AtmEnsAnalysis(config) - # Initialize and execute JEDI ensemble DA analysis in solver mode - AtmEnsAnl.initialize_jedi() - AtmEnsAnl.execute(config.APRUN_ATMENSANLSOL, ['fv3jedi', 'localensembleda']) + # Execute JEDI ensemble DA analysis in solver mode + AtmEnsAnl.execute('atmensanlsol') diff --git a/sorc/gdas.cd b/sorc/gdas.cd index 764f58cebd..e514b92656 160000 --- a/sorc/gdas.cd +++ b/sorc/gdas.cd @@ -1 +1 @@ -Subproject commit 764f58cebdf64f3695d89538994a50183e5884d9 +Subproject commit e514b926561bfc8fa3de741876505aff74255c95 diff --git a/ush/python/pygfs/jedi/jedi.py b/ush/python/pygfs/jedi/jedi.py index 415a0a3c08..2806ba4bce 100644 --- a/ush/python/pygfs/jedi/jedi.py +++ b/ush/python/pygfs/jedi/jedi.py @@ -4,145 +4,190 @@ import tarfile from logging import getLogger from typing import List, Dict, Any, Optional +from pprint import pformat from jcb import render -from wxflow import (AttrDict, - FileHandler, +from wxflow import (AttrDict, FileHandler, Task, Executable, chdir, rm_p, - parse_j2yaml, + parse_j2yaml, save_as_yaml, logit, - Task, - Executable, WorkflowException) logger = getLogger(__name__.split('.')[-1]) +required_jedi_keys = ['rundir', 'exe_src', 'mpi_cmd'] +optional_jedi_keys = ['jedi_args', 'jcb_base_yaml', 'jcb_algo', 'jcb_algo_yaml'] + class Jedi: """ Class for initializing and executing JEDI applications """ + @logit(logger, name="Jedi") - def __init__(self, task_config: AttrDict, yaml_name: Optional[str] = None) -> None: + def __init__(self, config: Dict[str, Any]) -> None: """Constructor for JEDI objects This method will construct a Jedi object. This includes: - - save a copy of task_config for provenance - - set the default JEDI YAML and executable names - - set an empty AttrDict for the JEDI config - - set the default directory for J2-YAML templates + - create the jedi_config AttrDict and extend it with additional required entries + - save a copy of jedi_config Parameters ---------- - task_config: AttrDict - Attribute-dictionary of all configuration variables associated with a GDAS task. - yaml_name: str, optional - Name of YAML file for JEDI configuration + config: AttrDict + Attribute-dictionary of all configuration variables required for the Jedi class Returns ---------- None """ - # For provenance, save incoming task_config as a private attribute of JEDI object - self._task_config = task_config - - _exe_name = os.path.basename(task_config.JEDIEXE) - - self.exe = os.path.join(task_config.DATA, _exe_name) - if yaml_name: - self.yaml = os.path.join(task_config.DATA, yaml_name + '.yaml') - else: - self.yaml = os.path.join(task_config.DATA, os.path.splitext(_exe_name)[0] + '.yaml') - self.config = AttrDict() - self.j2tmpl_dir = os.path.join(task_config.PARMgfs, 'gdas') + # Make sure input dictionary for Jedi class constructor has the required keys + if 'yaml_name' not in config: + logger.error(f"FATAL ERROR: Key 'yaml_name' not found in config") + raise KeyError(f"FATAL ERROR: Key 'yaml_name' not found in config") + for key in required_jedi_keys: + if key not in config: + logger.error(f"FATAL ERROR: Required key '{key}' not found in config") + raise KeyError(f"FATAL ERROR: Required key '{key}' not found in config") + + # Create the configuration dictionary for JEDI object + local_dict = AttrDict( + { + 'exe': os.path.join(config.rundir, os.path.basename(config.exe_src)), + 'yaml': os.path.join(config.rundir, config.yaml_name + '.yaml'), + 'input_config': None + } + ) + self.jedi_config = AttrDict(**config, **local_dict) + + # Set optional keys in jedi_config to None if not already present + for key in optional_jedi_keys: + if key not in self.jedi_config: + self.jedi_config[key] = None + + # Save a copy of jedi_config + self._jedi_config = self.jedi_config.deepcopy() @logit(logger) - def set_config(self, task_config: AttrDict, algorithm: Optional[str] = None) -> AttrDict: - """Compile a JEDI configuration dictionary from a template file and save to a YAML file + def initialize(self, task_config: AttrDict) -> None: + """Initialize JEDI application + + This method will initialize a JEDI application. + This includes: + - generating JEDI input YAML config + - saving JEDI input YAML config to run directory + - linking the JEDI executable to run directory Parameters ---------- - task_config : AttrDict - Dictionary of all configuration variables associated with a GDAS task. - algorithm (optional) : str - Name of the algorithm used to generate the JEDI configuration dictionary. - It will override the algorithm set in the task_config.JCB_<>_YAML file. + task_config: AttrDict + Attribute-dictionary of all configuration variables associated with a GDAS task. Returns ---------- None """ - if 'JCB_BASE_YAML' in task_config.keys(): - # Step 1: Fill templates of the JCB base YAML file - jcb_config = parse_j2yaml(task_config.JCB_BASE_YAML, task_config) - - # Step 2: If algorithm is present then override the algorithm in the JEDI - # config. Otherwise, if the algorithm J2-YAML is present, fill - # its templates and merge. - if algorithm: - jcb_config['algorithm'] = algorithm - elif 'JCB_ALGO' in task_config.keys(): - jcb_config['algorithm'] = task_config.JCB_ALGO - elif 'JCB_ALGO_YAML' in task_config.keys(): - jcb_algo_config = parse_j2yaml(task_config.JCB_ALGO_YAML, task_config) - jcb_config.update(jcb_algo_config) - - # Step 3: Generate the JEDI YAML using JCB - self.config = render(jcb_config) - elif 'JEDIYAML' in task_config.keys(): - # Generate JEDI YAML without using JCB - self.config = parse_j2yaml(task_config.JEDIYAML, task_config, - searchpath=self.j2tmpl_dir) - else: - logger.exception(f"FATAL ERROR: Unable to compile JEDI configuration dictionary, ABORT!") - raise KeyError(f"FATAL ERROR: Task config must contain JCB_BASE_YAML or JEDIYAML") + # Render JEDI config dictionary + logger.info(f"Generating JEDI YAML config: {self.jedi_config.yaml}") + self.jedi_config.input_config = self.render_jcb(task_config) + logger.debug(f"JEDI config:\n{pformat(self.jedi_config.input_config)}") + + # Save JEDI config dictionary to YAML in run directory + logger.debug(f"Writing JEDI YAML config to: {self.jedi_config.yaml}") + save_as_yaml(self.jedi_config.input_config, self.jedi_config.yaml) + + # Link JEDI executable to run directory + logger.info(f"Linking JEDI executable {self.jedi_config.exe_src} to {self.jedi_config.exe}") + self.link_exe() @logit(logger) - def execute(self, task_config: AttrDict, aprun_cmd: str, jedi_args: Optional[List] = None) -> None: + def execute(self) -> None: """Execute JEDI application Parameters ---------- - task_config: AttrDict - Attribute-dictionary of all configuration variables associated with a GDAS task. - aprun_cmd: str - String comprising the run command for the JEDI executable. - jedi_args (optional): List - List of strings comprising optional input arguments for the JEDI executable. + None Returns ---------- - jedi_config: AttrDict - Attribute-dictionary of JEDI configuration rendered from a template. + None """ - chdir(task_config.DATA) + chdir(self.jedi_config.rundir) - exec_cmd = Executable(aprun_cmd) - exec_cmd.add_default_arg(self.exe) - if jedi_args: - for arg in jedi_args: + exec_cmd = Executable(self.jedi_config.mpi_cmd) + exec_cmd.add_default_arg(self.jedi_config.exe) + if self.jedi_config.jedi_args is not None: + for arg in self.jedi_config.jedi_args: exec_cmd.add_default_arg(arg) - exec_cmd.add_default_arg(self.yaml) + exec_cmd.add_default_arg(self.jedi_config.yaml) + logger.info(f"Executing {exec_cmd}") try: exec_cmd() except OSError: + logger.error(f"FATAL ERROR: Failed to execute {exec_cmd}") raise OSError(f"FATAL ERROR: Failed to execute {exec_cmd}") except Exception: + logger.error(f"FATAL ERROR: An error occurred during execution of {exec_cmd}") raise WorkflowException(f"FATAL ERROR: An error occurred during execution of {exec_cmd}") - @staticmethod @logit(logger) - def link_exe(task_config: AttrDict) -> None: + def render_jcb(self, task_config: AttrDict, algorithm: Optional[str] = None) -> AttrDict: + """Compile a JEDI configuration dictionary from a template file and save to a YAML file + + Parameters + ---------- + task_config : AttrDict + Dictionary of all configuration variables associated with a GDAS task. + algorithm (optional) : str + Name of the algorithm used to generate the JEDI configuration dictionary. + It will override the algorithm set in the jedi_config.jcb_algo_yaml file. + + Returns + ---------- + jedi_input_config: AttrDict + Attribute-dictionary of JEDI configuration rendered from a template. + """ + + # Fill JCB base YAML template and build JCB config dictionary + if self.jedi_config.jcb_base_yaml is not None: + jcb_config = parse_j2yaml(self.jedi_config.jcb_base_yaml, task_config) + else: + logger.error(f"FATAL ERROR: JCB base YAML must be specified in order to render YAML using JCB") + raise KeyError(f"FATAL ERROR: JCB base YAML must be specified in order to render YAML using JCB") + + # Add JCB algorithm YAML, if it exists, to JCB config dictionary + if self.jedi_config.jcb_algo_yaml is not None: + jcb_config.update(parse_j2yaml(self.jedi_config.jcb_algo_yaml, task_config)) + + # Set algorithm in JCB config dictionary + if algorithm is not None: + jcb_config['algorithm'] = algorithm + elif self.jedi_config.jcb_algo is not None: + jcb_config['algorithm'] = self.jedi_config.jcb_algo + elif 'algorithm' in jcb_config: + pass + else: + logger.error(f"FATAL ERROR: JCB algorithm must be specified as input to jedi.render_jcb(), " + + "in JEDI configuration dictionary as jcb_algo, or in JCB algorithm YAML") + raise Exception(f"FATAL ERROR: JCB algorithm must be specified as input to jedi.render_jcb(), " + + "in JEDI configuration dictionary as jcb_algo, or in JCB algorithm YAML") + + # Generate JEDI YAML config by rendering JCB config dictionary + jedi_input_config = render(jcb_config) + + return jedi_input_config + + @logit(logger) + def link_exe(self) -> None: """Link JEDI executable to run directory Parameters ---------- - task_config: AttrDict - Attribute-dictionary of all configuration variables associated with a GDAS task. + None Returns ---------- @@ -152,185 +197,156 @@ def link_exe(task_config: AttrDict) -> None: # TODO: linking is not permitted per EE2. # Needs work in JEDI to be able to copy the exec. [NOAA-EMC/GDASApp#1254] logger.warn("Linking is not permitted per EE2.") - exe_dest = os.path.join(task_config.DATA, os.path.basename(task_config.JEDIEXE)) - if os.path.exists(exe_dest): - rm_p(exe_dest) - os.symlink(task_config.JEDIEXE, exe_dest) + if not os.path.exists(self.jedi_config.exe): + os.symlink(self.jedi_config.exe_src, self.jedi_config.exe) + @staticmethod @logit(logger) - def get_obs_dict(self, task_config: AttrDict) -> 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 files that are to be copied to the run directory - from the observation input directory + def get_jedi_dict(jedi_config_yaml: str, task_config: AttrDict, expected_block_names: Optional[list] = None): + """Get dictionary of Jedi objects from YAML specifying their configuration dictionaries Parameters ---------- - task_config: AttrDict - Attribute-dictionary of all configuration variables associated with a GDAS task. + jedi_config_yaml : str + path to YAML specifying configuration dictionaries for Jedi objects + task_config : str + attribute-dictionary of all configuration variables associated with a GDAS task + expected_block_names (optional) : str + list of names of blocks expected to be in jedi_config_yaml YAML file Returns ---------- - obs_dict: Dict - a dictionary containing the list of observation files to copy for FileHandler + None """ - observations = find_value_in_nested_dict(self.config, 'observations') + # Initialize dictionary of Jedi objects + jedi_dict = AttrDict() - copylist = [] - for ob in observations['observers']: - obfile = ob['obs space']['obsdatain']['engine']['obsfile'] - basename = os.path.basename(obfile) - copylist.append([os.path.join(task_config.COM_OBS, basename), obfile]) - obs_dict = { - 'mkdir': [os.path.join(task_config.DATA, 'obs')], - 'copy': copylist - } - return obs_dict + # Parse J2-YAML file for dictionary of JEDI configuration dictionaries + jedi_config_dict = parse_j2yaml(jedi_config_yaml, task_config) - @logit(logger) - def get_bias_dict(self, task_config: AttrDict, bias_file) -> Dict[str, Any]: - """Compile a dictionary of observation files to copy + # Loop through dictionary of Jedi configuration dictionaries + for block_name in jedi_config_dict: + # yaml_name key is set to name for this block + jedi_config_dict[block_name]['yaml_name'] = block_name + + # Make sure all required keys present + for key in required_jedi_keys: + if key not in jedi_config_dict[block_name]: + logger.error(f"FATAL ERROR: Required key {key} not found in {jedi_config_yaml} for block {block_name}.") + raise KeyError(f"FATAL ERROR: Required key {key} not found in {jedi_config_yaml} for block {block_name}.") + + # Set optional keys to None + for key in optional_jedi_keys: + if key not in jedi_config_dict[block_name]: + jedi_config_dict[block_name][key] = None + + # Construct JEDI object + jedi_dict[block_name] = Jedi(jedi_config_dict[block_name]) - 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. + # Make sure jedi_dict has the blocks we expect + if expected_block_names: + for block_name in expected_block_names: + if block_name not in jedi_dict: + logger.error(f"FATAL ERROR: Expected block {block_name} not present {jedi_config_yaml}") + raise Exception(f"FATAL ERROR: Expected block {block_name} not present {jedi_config_yaml}") + if len(jedi_dict) > len(expected_block_names): + logger.error(f"FATAL ERROR: {jedi_config_yaml} specifies more Jedi objects than expected.") + raise Exception(f"FATAL ERROR: {jedi_config_yaml} specifies more Jedi objects than expected.") + + # Return dictionary of JEDI objects + return jedi_dict + + @staticmethod + @logit(logger) + def remove_redundant(input_list: List) -> List: + """Remove reduncancies from list with possible redundant, non-mutable elements Parameters ---------- - task_config: AttrDict - Attribute-dictionary of all configuration variables associated with a GDAS task. - bias_file - name of bias correction tar file + input_list : List + List with possible redundant, non-mutable elements Returns ---------- - bias_dict: Dict - a dictionary containing the list of observation bias files to copy for FileHandler + output_list : List + Input list but with redundancies removed """ - observations = find_value_in_nested_dict(self.config, '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('.')[:-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 + output_list = [] + for item in input_list: + if item not in output_list: + output_list.append(item) - bias_dict = { - 'mkdir': [os.path.join(task_config.DATA, 'bc')], - 'copy': copylist - } - - return bias_dict + return output_list @staticmethod @logit(logger) - def extract_tar(tar_file: str) -> None: - """Extract files from a tarball + def extract_tar_from_filehandler_dict(filehandler_dict) -> None: + """Extract tarballs from FileHandler input dictionary - This method extract files from a tarball + This method extracts files from tarballs specified in a FileHander + input dictionary for the 'copy' action. Parameters ---------- - tar_file - path/name of tarball + filehandler_dict + Input dictionary for FileHandler 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}") + for item in filehandler_dict['copy']: + # Use the filename from the destination entry if it's a file path + # Otherwise, it's a directory, so use the source entry filename + if os.path.isfile(item[1]): + filename = os.path.basename(item[1]) 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}") + filename = os.path.basename(item[0]) + + # Check if file is a tar ball + if os.path.splitext(filename)[1] == '.tar': + tar_file = f"{os.path.dirname(item[1])}/{filename}" + + # Extract tarball + logger.info(f"Extract files from {tar_file}") + extract_tar(tar_file) @logit(logger) -def find_value_in_nested_dict(nested_dict: Dict, target_key: str) -> Any: - """ - Recursively search through a nested dictionary and return the value for the target key. - This returns the first target key it finds. So if a key exists in a subsequent - nested dictionary, it will not be found. +def extract_tar(tar_file: str) -> None: + """Extract files from a tarball + + This method extract files from a tarball Parameters ---------- - nested_dict : Dict - Dictionary to search - target_key : str - Key to search for + tar_file + path/name of tarball Returns - ------- - Any - Value of the target key - - Raises - ------ - KeyError - If key is not found in dictionary - - TODO: if this gives issues due to landing on an incorrect key in the nested - dictionary, we will have to implement a more concrete method to search for a key - given a more complete address. See resolved conversations in PR 2387 - - # Example usage: - nested_dict = { - 'a': { - 'b': { - 'c': 1, - 'd': { - 'e': 2, - 'f': 3 - } - }, - 'g': 4 - }, - 'h': { - 'i': 5 - }, - 'j': { - 'k': 6 - } - } - - user_key = input("Enter the key to search for: ") - result = find_value_in_nested_dict(nested_dict, user_key) + ---------- + None """ - if not isinstance(nested_dict, dict): - raise TypeError(f"Input is not of type(dict)") - - result = nested_dict.get(target_key) - if result is not None: - return result - - for value in nested_dict.values(): - if isinstance(value, dict): - try: - result = find_value_in_nested_dict(value, target_key) - if result is not None: - return result - except KeyError: - pass - - raise KeyError(f"Key '{target_key}' not found in the nested dictionary") + # 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.FileExistsError as err: + logger.exception(f"FATAL ERROR: {tar_file} does not exist") + raise tarfile.FileExistsError(f"FATAL ERROR: {tar_file} does not exist") + except tarfile.ReadError as err: + if tarfile.is_tarfile(tar_file): + logger.error(f"FATAL ERROR: tar archive {tar_file} could not be read") + raise tarfile.ReadError(f"FATAL ERROR: tar archive {tar_file} could not be read") + else: + logger.error(f"FATAL ERROR: {tar_file} is not a tar archive") + raise tarfile.ReadError(f"FATAL ERROR: {tar_file} is not a tar archive") + 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}") diff --git a/ush/python/pygfs/task/atm_analysis.py b/ush/python/pygfs/task/atm_analysis.py index 5f67ea9d72..5c3aa0f764 100644 --- a/ush/python/pygfs/task/atm_analysis.py +++ b/ush/python/pygfs/task/atm_analysis.py @@ -6,8 +6,7 @@ import tarfile from logging import getLogger from pprint import pformat -from typing import Optional, Dict, Any - +from typing import Any, Dict, List, Optional from wxflow import (AttrDict, FileHandler, add_to_datetime, to_fv3time, to_timedelta, to_YMDH, @@ -24,20 +23,18 @@ class AtmAnalysis(Task): Class for JEDI-based global atm analysis tasks """ @logit(logger, name="AtmAnalysis") - def __init__(self, config: Dict[str, Any], yaml_name: Optional[str] = None): + def __init__(self, config: Dict[str, Any]): """Constructor global atm analysis task This method will construct a global atm analysis task. This includes: - extending the task_config attribute AttrDict to include parameters required for this task - - instantiate the Jedi attribute object + - instantiate the Jedi attribute objects Parameters ---------- config: Dict dictionary object containing task configuration - yaml_name: str, optional - name of YAML file for JEDI configuration Returns ---------- @@ -73,46 +70,17 @@ def __init__(self, config: Dict[str, Any], yaml_name: Optional[str] = None): # Extend task_config with local_dict self.task_config = AttrDict(**self.task_config, **local_dict) - # Create JEDI object - self.jedi = Jedi(self.task_config, yaml_name) - - @logit(logger) - def initialize_jedi(self): - """Initialize JEDI application - - This method will initialize a JEDI application used in the global atm analysis. - This includes: - - generating and saving JEDI YAML config - - linking the JEDI executable - - Parameters - ---------- - None - - Returns - ---------- - None - """ - - # get JEDI-to-FV3 increment converter config and save to YAML file - logger.info(f"Generating JEDI YAML config: {self.jedi.yaml}") - self.jedi.set_config(self.task_config) - logger.debug(f"JEDI config:\n{pformat(self.jedi.config)}") - - # save JEDI config to YAML file - logger.debug(f"Writing JEDI YAML config to: {self.jedi.yaml}") - save_as_yaml(self.jedi.config, self.jedi.yaml) - - # link JEDI executable - logger.info(f"Linking JEDI executable {self.task_config.JEDIEXE} to {self.jedi.exe}") - self.jedi.link_exe(self.task_config) + # Create dictionary of Jedi objects + expected_keys = ['atmanlvar', 'atmanlfv3inc'] + self.jedi_dict = Jedi.get_jedi_dict(self.task_config.JEDI_CONFIG_YAML, self.task_config, expected_keys) @logit(logger) - def initialize_analysis(self) -> None: + def initialize(self) -> None: """Initialize a global atm analysis This method will initialize a global atm analysis. This includes: + - initialize JEDI applications - staging observation files - staging bias correction files - staging CRTM fix files @@ -129,26 +97,33 @@ def initialize_analysis(self) -> None: ---------- None """ - super().initialize() + + # initialize JEDI variational application + logger.info(f"Initializing JEDI variational DA application") + self.jedi_dict['atmanlvar'].initialize(self.task_config) + + # initialize JEDI FV3 increment conversion application + logger.info(f"Initializing JEDI FV3 increment conversion application") + self.jedi_dict['atmanlfv3inc'].initialize(self.task_config) # stage observations - logger.info(f"Staging list of observation files generated from JEDI config") - obs_dict = self.jedi.get_obs_dict(self.task_config) + logger.info(f"Staging list of observation files") + obs_dict = self.jedi_dict['atmanlvar'].render_jcb(self.task_config, 'atm_obs_staging') FileHandler(obs_dict).sync() logger.debug(f"Observation files:\n{pformat(obs_dict)}") # stage bias corrections - logger.info(f"Staging list of bias correction files generated from JEDI 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) + logger.info(f"Staging list of bias correction files") + bias_dict = self.jedi_dict['atmanlvar'].render_jcb(self.task_config, 'atm_bias_staging') + if bias_dict['copy'] is None: + logger.info(f"No bias correction files to stage") + else: + bias_dict['copy'] = Jedi.remove_redundant(bias_dict['copy']) + FileHandler(bias_dict).sync() + logger.debug(f"Bias correction files:\n{pformat(bias_dict)}") + + # extract bias corrections + Jedi.extract_tar_from_filehandler_dict(bias_dict) # stage CRTM fix files logger.info(f"Staging CRTM fix files from {self.task_config.CRTM_FIX_YAML}") @@ -193,29 +168,20 @@ def initialize_analysis(self) -> None: FileHandler({'mkdir': newdirs}).sync() @logit(logger) - def execute(self, aprun_cmd: str, jedi_args: Optional[str] = None) -> None: - """Run JEDI executable - - This method will run JEDI executables for the global atm analysis + def execute(self, jedi_dict_key: str) -> None: + """Execute JEDI application of atm analysis Parameters ---------- - aprun_cmd : str - Run command for JEDI application on HPC system - jedi_args : List - List of additional optional arguments for JEDI application + jedi_dict_key + key specifying particular Jedi object in self.jedi_dict Returns ---------- None """ - if jedi_args: - logger.info(f"Executing {self.jedi.exe} {' '.join(jedi_args)} {self.jedi.yaml}") - else: - logger.info(f"Executing {self.jedi.exe} {self.jedi.yaml}") - - self.jedi.execute(self.task_config, aprun_cmd, jedi_args) + self.jedi_dict[jedi_dict_key].execute() @logit(logger) def finalize(self) -> None: diff --git a/ush/python/pygfs/task/atmens_analysis.py b/ush/python/pygfs/task/atmens_analysis.py index 4b2f8ebbf4..81cae238bb 100644 --- a/ush/python/pygfs/task/atmens_analysis.py +++ b/ush/python/pygfs/task/atmens_analysis.py @@ -28,20 +28,18 @@ class AtmEnsAnalysis(Task): Class for JEDI-based global atmens analysis tasks """ @logit(logger, name="AtmEnsAnalysis") - def __init__(self, config: Dict[str, Any], yaml_name: Optional[str] = None): + def __init__(self, config: Dict[str, Any]): """Constructor global atmens analysis task This method will construct a global atmens analysis task. This includes: - extending the task_config attribute AttrDict to include parameters required for this task - - instantiate the Jedi attribute object + - instantiate the Jedi attribute objects Parameters ---------- config: Dict dictionary object containing task configuration - yaml_name: str, optional - name of YAML file for JEDI configuration Returns ---------- @@ -73,46 +71,17 @@ def __init__(self, config: Dict[str, Any], yaml_name: Optional[str] = None): # Extend task_config with local_dict self.task_config = AttrDict(**self.task_config, **local_dict) - # Create JEDI object - self.jedi = Jedi(self.task_config, yaml_name) + # Create dictionary of JEDI objects + expected_keys = ['atmensanlobs', 'atmensanlsol', 'atmensanlfv3inc', 'atmensanlletkf'] + self.jedi_dict = Jedi.get_jedi_dict(self.task_config.JEDI_CONFIG_YAML, self.task_config, expected_keys) @logit(logger) - def initialize_jedi(self): - """Initialize JEDI application - - This method will initialize a JEDI application used in the global atmens analysis. - This includes: - - generating and saving JEDI YAML config - - linking the JEDI executable - - Parameters - ---------- - None - - Returns - ---------- - None - """ - - # get JEDI config and save to YAML file - logger.info(f"Generating JEDI config: {self.jedi.yaml}") - self.jedi.set_config(self.task_config) - logger.debug(f"JEDI config:\n{pformat(self.jedi.config)}") - - # save JEDI config to YAML file - logger.info(f"Writing JEDI config to YAML file: {self.jedi.yaml}") - save_as_yaml(self.jedi.config, self.jedi.yaml) - - # link JEDI-to-FV3 increment converter executable - logger.info(f"Linking JEDI executable {self.task_config.JEDIEXE} to {self.jedi.exe}") - self.jedi.link_exe(self.task_config) - - @logit(logger) - def initialize_analysis(self) -> None: + def initialize(self) -> None: """Initialize a global atmens analysis This method will initialize a global atmens analysis. This includes: + - initialize JEDI applications - staging observation files - staging bias correction files - staging CRTM fix files @@ -128,26 +97,34 @@ def initialize_analysis(self) -> None: ---------- None """ - super().initialize() + + # initialize JEDI LETKF observer application + logger.info(f"Initializing JEDI LETKF observer application") + self.jedi_dict['atmensanlobs'].initialize(self.task_config) + + # initialize JEDI LETKF solver application + logger.info(f"Initializing JEDI LETKF solver application") + self.jedi_dict['atmensanlsol'].initialize(self.task_config) + + # initialize JEDI FV3 increment conversion application + logger.info(f"Initializing JEDI FV3 increment conversion application") + self.jedi_dict['atmensanlfv3inc'].initialize(self.task_config) # stage observations - logger.info(f"Staging list of observation files generated from JEDI config") - obs_dict = self.jedi.get_obs_dict(self.task_config) + logger.info(f"Staging list of observation files") + obs_dict = self.jedi_dict['atmensanlobs'].render_jcb(self.task_config, 'atm_obs_staging') FileHandler(obs_dict).sync() logger.debug(f"Observation files:\n{pformat(obs_dict)}") # stage bias corrections - logger.info(f"Staging list of bias correction files generated from JEDI 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) + logger.info(f"Staging list of bias correction files") + bias_dict = self.jedi_dict['atmensanlobs'].render_jcb(self.task_config, 'atm_bias_staging') + bias_dict['copy'] = Jedi.remove_redundant(bias_dict['copy']) 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) + Jedi.extract_tar_from_filehandler_dict(bias_dict) # stage CRTM fix files logger.info(f"Staging CRTM fix files from {self.task_config.CRTM_FIX_YAML}") @@ -176,28 +153,38 @@ def initialize_analysis(self) -> None: FileHandler({'mkdir': newdirs}).sync() @logit(logger) - def execute(self, aprun_cmd: str, jedi_args: Optional[str] = None) -> None: - """Run JEDI executable + def initialize_letkf(self) -> None: + """Initialize a global atmens analysis - This method will run JEDI executables for the global atmens analysis + Note: This would normally be done in AtmEnsAnalysis.initialize(), but that method + now initializes the split observer-solver. This method is just for testing. Parameters ---------- - aprun_cmd : str - Run command for JEDI application on HPC system - jedi_args : List - List of additional optional arguments for JEDI application + None + Returns ---------- None """ - if jedi_args: - logger.info(f"Executing {self.jedi.exe} {' '.join(jedi_args)} {self.jedi.yaml}") - else: - logger.info(f"Executing {self.jedi.exe} {self.jedi.yaml}") + self.jedi_dict['atmensanlletkf'].initialize(self.task_config) + + @logit(logger) + def execute(self, jedi_dict_key: str) -> None: + """Execute JEDI application of atmens analysis + + Parameters + ---------- + jedi_dict_key + key specifying a particular Jedi object in self.jedi_dict + + Returns + ---------- + None + """ - self.jedi.execute(self.task_config, aprun_cmd, jedi_args) + self.jedi_dict[jedi_dict_key].execute() @logit(logger) def finalize(self) -> None: diff --git a/ush/python/pygfs/task/marine_analysis.py b/ush/python/pygfs/task/marine_analysis.py index e7b7b5e948..75cc28c7b3 100644 --- a/ush/python/pygfs/task/marine_analysis.py +++ b/ush/python/pygfs/task/marine_analysis.py @@ -15,7 +15,7 @@ from wxflow import (AttrDict, FileHandler, add_to_datetime, to_timedelta, to_YMD, - parse_j2yaml, + parse_j2yaml, parse_yaml, logit, Executable, Task, @@ -200,7 +200,7 @@ def _prep_variational_yaml(self: Task) -> None: # 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['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' @@ -234,6 +234,9 @@ def _prep_variational_yaml(self: Task) -> None: 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') + # Current hack so that this is not done directly in the JCB base yaml + jcb_config['marine_pseudo_model_states'] = parse_yaml('bkg_list.yaml') + # Render the full JEDI configuration file using JCB jedi_config = render(jcb_config) diff --git a/ush/python/pygfs/task/marine_bmat.py b/ush/python/pygfs/task/marine_bmat.py index a4a5b4f144..a21699227b 100644 --- a/ush/python/pygfs/task/marine_bmat.py +++ b/ush/python/pygfs/task/marine_bmat.py @@ -9,21 +9,40 @@ FileHandler, add_to_datetime, to_timedelta, chdir, - parse_j2yaml, + parse_j2yaml, save_as_yaml, logit, Executable, Task) +from pygfs.jedi import Jedi + logger = getLogger(__name__.split('.')[-1]) class MarineBMat(Task): """ - Class for global marine B-matrix tasks + Class for global marine B-matrix tasks. """ @logit(logger, name="MarineBMat") def __init__(self, config): + """Constructor for marine B-matrix task + + This method will construct the marine B-matrix task object + This includes: + - extending the task_config AttrDict to include parameters required for this task + - instantiate the Jedi attribute objects + + Parameters + ---------- + config: Dict + dictionary object containing task configuration + + Returns + ---------- + None + """ 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, @@ -38,18 +57,26 @@ def __init__(self, config): local_dict = AttrDict( { 'PARMsoca': os.path.join(self.task_config.PARMgfs, 'gdas', 'soca'), + 'CALC_SCALE_EXEC': _calc_scale_exec, 'MARINE_WINDOW_BEGIN': _window_begin, - 'MARINE_WINDOW_END': _window_end, 'MARINE_WINDOW_MIDDLE': self.task_config.current_cycle, + 'MARINE_WINDOW_END': _window_end, + 'MARINE_WINDOW_LENGTH': f"PT{self.task_config['assim_freq']}H", 'ENSPERT_RELPATH': _enspert_relpath, - 'CALC_SCALE_EXEC': _calc_scale_exec, - 'APREFIX': f"{self.task_config.RUN}.t{self.task_config.cyc:02d}z." + 'MOM6_LEVS': mdau.get_mom6_levels(str(self.task_config.OCNRES)), + 'APREFIX': f"{self.task_config.RUN}.t{self.task_config.cyc:02d}z.", + 'OPREFIX': f"{self.task_config.RUN}.t{self.task_config.cyc:02d}z." } ) # Extend task_config with local_dict self.task_config = AttrDict(**self.task_config, **local_dict) + # Create dictionary of Jedi objects + expected_keys = ['gridgen', 'soca_diagb', 'soca_parameters_diffusion_vt', 'soca_setcorscales', + 'soca_parameters_diffusion_hz', 'soca_ensb', 'soca_ensweights'] + self.jedi_dict = Jedi.get_jedi_dict(self.task_config.JEDI_CONFIG_YAML, self.task_config, expected_keys) + @logit(logger) def initialize(self: Task) -> None: """Initialize a global B-matrix @@ -60,10 +87,18 @@ def initialize(self: Task) -> None: - staging SOCA fix files - staging static ensemble members (optional) - staging ensemble members (optional) - - generating the YAML files for the JEDI and GDASApp executables + - initializing the soca_vtscales Python script + - initializing the JEDI applications - creating output directories + + Parameters + ---------- + None + + Returns + ---------- + None """ - super().initialize() # stage fix files logger.info(f"Staging SOCA fix files from {self.task_config.SOCA_INPUT_FIX_DIR}") @@ -78,54 +113,32 @@ def initialize(self: Task) -> None: bkg_list = parse_j2yaml(self.task_config.MARINE_DET_STAGE_BKG_YAML_TMPL, self.task_config) FileHandler(bkg_list).sync() - # stage the soca utility yamls (gridgen, fields and ufo mapping yamls) + # stage the soca utility yamls (fields and ufo mapping yamls) logger.info(f"Staging SOCA utility yaml files") soca_utility_list = parse_j2yaml(self.task_config.MARINE_UTILITY_YAML_TMPL, self.task_config) FileHandler(soca_utility_list).sync() - # generate the variance partitioning YAML file - 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.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.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.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.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')) + # initialize vtscales python script + vtscales_config = self.jedi_dict['soca_parameters_diffusion_vt'].render_jcb(self.task_config, 'soca_vtscales') + save_as_yaml(vtscales_config, os.path.join(self.task_config.DATA, 'soca_vtscales.yaml')) + FileHandler({'copy': [[os.path.join(self.task_config.CALC_SCALE_EXEC), + os.path.join(self.task_config.DATA, 'calc_scales.x')]]}).sync() + + # initialize JEDI applications + self.jedi_dict['gridgen'].initialize(self.task_config) + self.jedi_dict['soca_diagb'].initialize(self.task_config) + self.jedi_dict['soca_parameters_diffusion_vt'].initialize(self.task_config) + self.jedi_dict['soca_setcorscales'].initialize(self.task_config) + self.jedi_dict['soca_parameters_diffusion_hz'].initialize(self.task_config) + if self.task_config.DOHYBVAR == "YES" or self.task_config.NMEM_ENS > 2: + self.jedi_dict['soca_ensb'].initialize(self.task_config) + self.jedi_dict['soca_ensweights'].initialize(self.task_config) - # hybrid EnVAR case + # stage ensemble members for the hybrid background error if self.task_config.DOHYBVAR == "YES" or self.task_config.NMEM_ENS > 2: - # stage ensemble membersfiles for use in hybrid background error logger.debug(f"Stage ensemble members for the hybrid background error") mdau.stage_ens_mem(self.task_config) - # generate ensemble recentering/rebalancing YAML file - logger.debug("Generate ensemble recentering YAML file") - ensrecenter_config = parse_j2yaml(path=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 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')) - # 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') @@ -134,130 +147,44 @@ def initialize(self: Task) -> None: os.symlink(link_target, link_name) @logit(logger) - def gridgen(self: Task) -> None: - # link gdas_soca_gridgen.x - mdau.link_executable(self.task_config, 'gdas_soca_gridgen.x') - exec_cmd = Executable(self.task_config.APRUN_MARINEBMAT) - exec_name = os.path.join(self.task_config.DATA, 'gdas_soca_gridgen.x') - exec_cmd.add_default_arg(exec_name) - exec_cmd.add_default_arg('gridgen.yaml') - - mdau.run(exec_cmd) + def execute(self) -> None: + """Generate the full B-matrix - @logit(logger) - def variance_partitioning(self: Task) -> None: - # link the variance partitioning executable, gdas_soca_diagb.x - mdau.link_executable(self.task_config, 'gdas_soca_diagb.x') - exec_cmd = Executable(self.task_config.APRUN_MARINEBMAT) - exec_name = os.path.join(self.task_config.DATA, 'gdas_soca_diagb.x') - exec_cmd.add_default_arg(exec_name) - exec_cmd.add_default_arg('soca_diagb.yaml') + This method will generate the full B-matrix according to the configuration. + This includes: + - running all JEDI application and Python scripts required to generate the B-matrix - mdau.run(exec_cmd) + Parameters + ---------- + None - @logit(logger) - def horizontal_diffusion(self: Task) -> None: - """Generate the horizontal diffusion coefficients + Returns + ---------- + None """ - # link the executable that computes the correlation scales, gdas_soca_setcorscales.x, - # and prepare the command to run it - mdau.link_executable(self.task_config, 'gdas_soca_setcorscales.x') - exec_cmd = Executable(self.task_config.APRUN_MARINEBMAT) - exec_name = os.path.join(self.task_config.DATA, 'gdas_soca_setcorscales.x') - exec_cmd.add_default_arg(exec_name) - exec_cmd.add_default_arg('soca_setcorscales.yaml') - # create a files containing the correlation scales - mdau.run(exec_cmd) + self.jedi_dict['gridgen'].execute() - # link the executable that computes the correlation scales, gdas_soca_error_covariance_toolbox.x, - # and prepare the command to run it - mdau.link_executable(self.task_config, 'gdas_soca_error_covariance_toolbox.x') - exec_cmd = Executable(self.task_config.APRUN_MARINEBMAT) - exec_name = os.path.join(self.task_config.DATA, 'gdas_soca_error_covariance_toolbox.x') - exec_cmd.add_default_arg(exec_name) - exec_cmd.add_default_arg('soca_parameters_diffusion_hz.yaml') + # variance partitioning + self.jedi_dict['soca_diagb'].execute() - # compute the coefficients of the diffusion operator - mdau.run(exec_cmd) + # horizontal diffusion + self.jedi_dict['soca_setcorscales'].execute() + self.jedi_dict['soca_parameters_diffusion_hz'].execute() - @logit(logger) - def vertical_diffusion(self: Task) -> None: - """Generate the vertical diffusion coefficients - """ - # compute the vertical correlation scales based on the MLD - FileHandler({'copy': [[os.path.join(self.task_config.CALC_SCALE_EXEC), - os.path.join(self.task_config.DATA, 'calc_scales.x')]]}).sync() + # vertical diffusion exec_cmd = Executable("python") exec_name = os.path.join(self.task_config.DATA, 'calc_scales.x') exec_cmd.add_default_arg(exec_name) exec_cmd.add_default_arg('soca_vtscales.yaml') mdau.run(exec_cmd) - # link the executable that computes the correlation scales, gdas_soca_error_covariance_toolbox.x, - # and prepare the command to run it - mdau.link_executable(self.task_config, 'gdas_soca_error_covariance_toolbox.x') - exec_cmd = Executable(self.task_config.APRUN_MARINEBMAT) - exec_name = os.path.join(self.task_config.DATA, 'gdas_soca_error_covariance_toolbox.x') - exec_cmd.add_default_arg(exec_name) - exec_cmd.add_default_arg('soca_parameters_diffusion_vt.yaml') - - # compute the coefficients of the diffusion operator - mdau.run(exec_cmd) - - @logit(logger) - def ensemble_perturbations(self: Task) -> None: - """Generate the 3D ensemble of perturbation for the 3DEnVAR - - This method will generate ensemble perturbations re-balanced w.r.t the - deterministic background. - This includes: - - computing a storing the unbalanced ensemble perturbations' statistics - - recentering the ensemble members around the deterministic background and - accounting for the nonlinear steric recentering - - saving the recentered ensemble statistics - """ - mdau.link_executable(self.task_config, 'gdas_ens_handler.x') - exec_cmd = Executable(self.task_config.APRUN_MARINEBMAT) - exec_name = os.path.join(self.task_config.DATA, 'gdas_ens_handler.x') - exec_cmd.add_default_arg(exec_name) - exec_cmd.add_default_arg('soca_ensb.yaml') + self.jedi_dict['soca_parameters_diffusion_vt'].execute() - # generate the ensemble perturbations - mdau.run(exec_cmd) - - @logit(logger) - def hybrid_weight(self: Task) -> None: - """Generate the hybrid weights for the 3DEnVAR - - This method will generate the 3D fields hybrid weights for the 3DEnVAR for each - variables. - TODO(G): Currently implemented for the specific case of the static ensemble members only - """ - mdau.link_executable(self.task_config, 'gdas_socahybridweights.x') - exec_cmd = Executable(self.task_config.APRUN_MARINEBMAT) - exec_name = os.path.join(self.task_config.DATA, 'gdas_socahybridweights.x') - exec_cmd.add_default_arg(exec_name) - exec_cmd.add_default_arg('soca_ensweights.yaml') - - # compute the ensemble weights - mdau.run(exec_cmd) - - @logit(logger) - def execute(self: Task) -> None: - """Generate the full B-matrix - - This method will generate the full B-matrix according to the configuration. - """ - chdir(self.task_config.DATA) - self.gridgen() # TODO: This should be optional in case the geometry file was staged - self.variance_partitioning() - self.horizontal_diffusion() # TODO: Make this optional once we've converged on an acceptable set of scales - self.vertical_diffusion() # hybrid EnVAR case if self.task_config.DOHYBVAR == "YES" or self.task_config.NMEM_ENS > 2: - self.ensemble_perturbations() # TODO: refactor this from the old scripts - self.hybrid_weight() # TODO: refactor this from the old scripts + self.jedi_dict['soca_ensb'].execute() + self.jedi_dict['soca_ensweights'].execute() @logit(logger) def finalize(self: Task) -> None: @@ -270,6 +197,13 @@ def finalize(self: Task) -> None: - keep the re-balanced ensemble perturbation files in DATAenspert - ... + Parameters + ---------- + None + + Returns + ---------- + None """ # Copy the soca grid if it was created grid_file = os.path.join(self.task_config.DATA, 'soca_gridspec.nc') diff --git a/versions/fix.ver b/versions/fix.ver index b175791196..4739ce778a 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=20241022 +export gdas_fv3jedi_ver=20241115 export gdas_soca_ver=20240802 export gdas_gsibec_ver=20240416 export gdas_obs_ver=20240213