From b33d942cfb9072f7eb600c2e1f7b8e8c49e41bcd Mon Sep 17 00:00:00 2001 From: amfox37 Date: Fri, 20 Feb 2026 11:15:10 -0700 Subject: [PATCH 01/40] Add optional ObsFcstAna NetCDF output mode and wire through landassim --- .../GEOS_LandAssimGridComp.F90 | 4 + .../clsm_ensupd_enkf_update.F90 | 180 +++++++++++++++--- .../clsm_ensupd_upd_routines.F90 | 26 +++ GEOSldas_App/LDASsa_DEFAULT_inputs_ensupd.nml | 2 + .../LDASsa_SPECIAL_inputs_ensupd.nml | 2 + GEOSldas_App/lenkf_j_template.py | 10 + 6 files changed, 193 insertions(+), 31 deletions(-) diff --git a/GEOSlandassim_GridComp/GEOS_LandAssimGridComp.F90 b/GEOSlandassim_GridComp/GEOS_LandAssimGridComp.F90 index c7904fca..5e520a2b 100644 --- a/GEOSlandassim_GridComp/GEOS_LandAssimGridComp.F90 +++ b/GEOSlandassim_GridComp/GEOS_LandAssimGridComp.F90 @@ -99,6 +99,7 @@ module GEOS_LandAssimGridCompMod integer :: N_obs_param logical :: out_obslog logical :: out_ObsFcstAna + integer :: out_ObsFcstAna_mode logical :: out_smapL4SMaup integer :: N_obsbias_max @@ -1385,6 +1386,7 @@ subroutine Initialize(gc, import, export, clock, rc) obs_param, & out_obslog, & out_ObsFcstAna, & + out_ObsFcstAna_mode, & out_smapL4SMaup, & N_obsbias_max & ) @@ -1408,6 +1410,7 @@ subroutine Initialize(gc, import, export, clock, rc) call MPI_BCAST(N_obs_param, 1, MPI_INTEGER, 0,MPICOMM,mpierr) call MPI_BCAST(out_obslog, 1, MPI_LOGICAL, 0,MPICOMM,mpierr) call MPI_BCAST(out_ObsFcstAna, 1, MPI_LOGICAL, 0,MPICOMM,mpierr) + call MPI_BCAST(out_ObsFcstAna_mode, 1, MPI_INTEGER, 0,MPICOMM,mpierr) call MPI_BCAST(out_smapL4SMaup, 1, MPI_LOGICAL, 0,MPICOMM,mpierr) call MPI_BCAST(N_obsbias_max, 1, MPI_INTEGER, 0,MPICOMM,mpierr) @@ -1948,6 +1951,7 @@ subroutine RUN ( GC, IMPORT, EXPORT, CLOCK, RC ) if (.true.) then ! replace obsolete check for analysis time with "if true" to keep indents call output_ObsFcstAna_wrapper( out_ObsFcstAna, & + out_ObsFcstAna_mode, & date_time_new, trim(exp_id), & N_obsl, N_obs_param, NUM_ENSEMBLE, & N_catl, tile_coord_l, & diff --git a/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 b/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 index b304ac01..fa6af5fe 100644 --- a/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 +++ b/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 @@ -97,7 +97,10 @@ module clsm_ensupd_enkf_update halo_type, & FOV_threshold, & get_halo_around_tile, & - TileNnzObs + TileNnzObs, & + OBSFCSTANA_FMT_BIN, & + OBSFCSTANA_FMT_NC4, & + OBSFCSTANA_FMT_BOTH use clsm_ensupd_read_obs, ONLY: & collect_obs @@ -1512,7 +1515,7 @@ end subroutine apply_enkf_increments ! ******************************************************************** subroutine output_ObsFcstAna(date_time, exp_id, & - N_obsl, Observations_l, N_obs_param, rf2f) + N_obsl, Observations_l, N_obs_param, out_ObsFcstAna_mode, rf2f) ! obs space output: observations, obs space forecast, obs space analysis, and ! associated error variances @@ -1526,6 +1529,7 @@ subroutine output_ObsFcstAna(date_time, exp_id, & character(*), intent(in) :: exp_id integer, intent(in) :: N_obsl, N_obs_param + integer, intent(in) :: out_ObsFcstAna_mode type(obs_type), dimension(N_obsl), intent(in) :: Observations_l @@ -1547,6 +1551,9 @@ subroutine output_ObsFcstAna(date_time, exp_id, & integer, dimension(numprocs) :: N_obsl_vec, tmp_low_ind character(300) :: fname + character( 40) :: file_ext + character(len=*), parameter :: Iam = 'output_ObsFcstAna' + character(len=400) :: err_msg #ifdef LDAS_MPI @@ -1689,55 +1696,165 @@ subroutine output_ObsFcstAna(date_time, exp_id, & ! write to file - fname = get_io_filename( './', exp_id, file_tag, date_time=date_time, & - dir_name=dir_name, ens_id=-1, no_subdirs=.true. ) + if ( (out_ObsFcstAna_mode /= OBSFCSTANA_FMT_BIN ) .and. & + (out_ObsFcstAna_mode /= OBSFCSTANA_FMT_NC4 ) .and. & + (out_ObsFcstAna_mode /= OBSFCSTANA_FMT_BOTH) ) then + err_msg = 'unknown out_ObsFcstAna_mode' + call ldas_abort(LDAS_GENERIC_ERROR, Iam, err_msg) + end if + + if (out_ObsFcstAna_mode == OBSFCSTANA_FMT_BIN .or. & + out_ObsFcstAna_mode == OBSFCSTANA_FMT_BOTH) then + + fname = get_io_filename( './', exp_id, file_tag, date_time=date_time, & + dir_name=dir_name, ens_id=-1, no_subdirs=.true. ) - open( 10, file=fname, form='unformatted', action='write') + open( 10, file=fname, form='unformatted', action='write') - ! write header + ! write header - write (10) N_obsf, date_time%year, date_time%month, & - date_time%day, date_time%hour, date_time%min, date_time%sec, & - date_time%dofyr, date_time%pentad + write (10) N_obsf, date_time%year, date_time%month, & + date_time%day, date_time%hour, date_time%min, date_time%sec, & + date_time%dofyr, date_time%pentad - ! write data + ! write data - ! Assuming a linear model and uncorrelated obs/model errors, - ! - ! the expected var of OminusF is HPHt + R, and - ! the expected var of OminusA is R - HAHt, where - ! - ! P = prior state error covariance - ! A = posterior state error covariance - ! H = observation operator + ! Assuming a linear model and uncorrelated obs/model errors, + ! + ! the expected var of OminusF is HPHt + R, and + ! the expected var of OminusA is R - HAHt, where + ! + ! P = prior state error covariance + ! A = posterior state error covariance + ! H = observation operator - write (10) (Observations_f(n)%assim, n=1,N_obsf) - write (10) (Observations_f(n)%species, n=1,N_obsf) + write (10) (Observations_f(n)%assim, n=1,N_obsf) + write (10) (Observations_f(n)%species, n=1,N_obsf) - write (10) (Observations_f(n)%tilenum, n=1,N_obsf) + write (10) (Observations_f(n)%tilenum, n=1,N_obsf) - write (10) (Observations_f(n)%lon, n=1,N_obsf) - write (10) (Observations_f(n)%lat, n=1,N_obsf) + write (10) (Observations_f(n)%lon, n=1,N_obsf) + write (10) (Observations_f(n)%lat, n=1,N_obsf) + + write (10) (Observations_f(n)%obs, n=1,N_obsf) + write (10) (Observations_f(n)%obsvar, n=1,N_obsf) ! R + + write (10) (Observations_f(n)%fcst, n=1,N_obsf) + write (10) (Observations_f(n)%fcstvar, n=1,N_obsf) ! HPHt + + write (10) (Observations_f(n)%ana, n=1,N_obsf) + write (10) (Observations_f(n)%anavar, n=1,N_obsf) ! HAHt + + close(10,status='keep') + + end if - write (10) (Observations_f(n)%obs, n=1,N_obsf) - write (10) (Observations_f(n)%obsvar, n=1,N_obsf) ! R + if (out_ObsFcstAna_mode == OBSFCSTANA_FMT_NC4 .or. & + out_ObsFcstAna_mode == OBSFCSTANA_FMT_BOTH) then - write (10) (Observations_f(n)%fcst, n=1,N_obsf) - write (10) (Observations_f(n)%fcstvar, n=1,N_obsf) ! HPHt + file_ext = '.nc4' + fname = get_io_filename( './', exp_id, file_tag, date_time=date_time, & + dir_name=dir_name, ens_id=-1, file_ext=file_ext, no_subdirs=.true. ) - write (10) (Observations_f(n)%ana, n=1,N_obsf) - write (10) (Observations_f(n)%anavar, n=1,N_obsf) ! HAHt + call write_ObsFcstAna_nc4(fname, date_time, N_obsf, Observations_f) - close(10,status='keep') + end if end if if (allocated(Observations_f)) deallocate(Observations_f) end subroutine output_ObsFcstAna + ! ******************************************************************** + + subroutine write_ObsFcstAna_nc4(fname, date_time, N_obsf, Observations_f) + use netcdf + + implicit none + + character(*), intent(in) :: fname + type(date_time_type), intent(in) :: date_time + integer, intent(in) :: N_obsf + type(obs_type), dimension(N_obsf), intent(in) :: Observations_f + + integer :: ncid + integer :: nobs_dimid + integer :: assim_varid, species_varid, tilenum_varid + integer :: lon_varid, lat_varid + integer :: obs_varid, obsvar_varid + integer :: fcst_varid, fcstvar_varid + integer :: ana_varid, anavar_varid + + integer, dimension(:), allocatable :: assim_int + + call check( nf90_create(trim(fname), nf90_clobber + NF90_NETCDF4, ncid) ) + + call check( nf90_def_dim(ncid, 'nobs', NF90_UNLIMITED, nobs_dimid) ) + + call check( nf90_def_var(ncid, 'assim', NF90_INT, [nobs_dimid], assim_varid) ) + call check( nf90_def_var(ncid, 'species', NF90_INT, [nobs_dimid], species_varid) ) + call check( nf90_def_var(ncid, 'tilenum', NF90_INT, [nobs_dimid], tilenum_varid) ) + call check( nf90_def_var(ncid, 'lon', NF90_FLOAT, [nobs_dimid], lon_varid) ) + call check( nf90_def_var(ncid, 'lat', NF90_FLOAT, [nobs_dimid], lat_varid) ) + call check( nf90_def_var(ncid, 'obs', NF90_FLOAT, [nobs_dimid], obs_varid) ) + call check( nf90_def_var(ncid, 'obsvar', NF90_FLOAT, [nobs_dimid], obsvar_varid) ) + call check( nf90_def_var(ncid, 'fcst', NF90_FLOAT, [nobs_dimid], fcst_varid) ) + call check( nf90_def_var(ncid, 'fcstvar', NF90_FLOAT, [nobs_dimid], fcstvar_varid) ) + call check( nf90_def_var(ncid, 'ana', NF90_FLOAT, [nobs_dimid], ana_varid) ) + call check( nf90_def_var(ncid, 'anavar', NF90_FLOAT, [nobs_dimid], anavar_varid) ) + + call check( nf90_put_att(ncid, NF90_GLOBAL, 'N_obsf', N_obsf) ) + call check( nf90_put_att(ncid, NF90_GLOBAL, 'year', date_time%year) ) + call check( nf90_put_att(ncid, NF90_GLOBAL, 'month', date_time%month) ) + call check( nf90_put_att(ncid, NF90_GLOBAL, 'day', date_time%day) ) + call check( nf90_put_att(ncid, NF90_GLOBAL, 'hour', date_time%hour) ) + call check( nf90_put_att(ncid, NF90_GLOBAL, 'minute', date_time%min) ) + call check( nf90_put_att(ncid, NF90_GLOBAL, 'second', date_time%sec) ) + call check( nf90_put_att(ncid, NF90_GLOBAL, 'dofyr', date_time%dofyr) ) + call check( nf90_put_att(ncid, NF90_GLOBAL, 'pentad', date_time%pentad) ) + + call check( nf90_enddef(ncid) ) + + if (N_obsf > 0) then + + allocate(assim_int(N_obsf)) + assim_int = 0 + where (Observations_f(1:N_obsf)%assim) assim_int = 1 + + call check( nf90_put_var(ncid, assim_varid, assim_int) ) + call check( nf90_put_var(ncid, species_varid, Observations_f(1:N_obsf)%species) ) + call check( nf90_put_var(ncid, tilenum_varid, Observations_f(1:N_obsf)%tilenum) ) + call check( nf90_put_var(ncid, lon_varid, Observations_f(1:N_obsf)%lon) ) + call check( nf90_put_var(ncid, lat_varid, Observations_f(1:N_obsf)%lat) ) + call check( nf90_put_var(ncid, obs_varid, Observations_f(1:N_obsf)%obs) ) + call check( nf90_put_var(ncid, obsvar_varid, Observations_f(1:N_obsf)%obsvar) ) + call check( nf90_put_var(ncid, fcst_varid, Observations_f(1:N_obsf)%fcst) ) + call check( nf90_put_var(ncid, fcstvar_varid, Observations_f(1:N_obsf)%fcstvar) ) + call check( nf90_put_var(ncid, ana_varid, Observations_f(1:N_obsf)%ana) ) + call check( nf90_put_var(ncid, anavar_varid, Observations_f(1:N_obsf)%anavar) ) + + deallocate(assim_int) + + end if + + call check( nf90_close(ncid) ) + + contains + subroutine check(status) + integer, intent ( in) :: status + + if(status /= nf90_noerr) then + print *, trim(nf90_strerror(status)) + stop 1 + end if + end subroutine check + + end subroutine write_ObsFcstAna_nc4 + ! ********************************************************************** subroutine output_ObsFcstAna_wrapper( out_ObsFcstAna, & + out_ObsFcstAna_mode, & date_time, exp_id, & N_obsl, N_obs_param, N_ens, & N_catl, tile_coord_l, & @@ -1757,6 +1874,7 @@ subroutine output_ObsFcstAna_wrapper( out_ObsFcstAna, & ! major revisions for new obs handling and MPI logical, intent(in) :: out_ObsFcstAna + integer, intent(in) :: out_ObsFcstAna_mode type(date_time_type), intent(in) :: date_time @@ -1829,7 +1947,7 @@ subroutine output_ObsFcstAna_wrapper( out_ObsFcstAna, & ! write out model, observations, and "OminusA" information call output_ObsFcstAna( date_time, exp_id, N_obsl, & - Observations_l(1:N_obsl), N_obs_param, rf2f=rf2f ) + Observations_l(1:N_obsl), N_obs_param, out_ObsFcstAna_mode, rf2f=rf2f ) end if diff --git a/GEOSlandassim_GridComp/clsm_ensupd_upd_routines.F90 b/GEOSlandassim_GridComp/clsm_ensupd_upd_routines.F90 index 795346f9..70db22e2 100644 --- a/GEOSlandassim_GridComp/clsm_ensupd_upd_routines.F90 +++ b/GEOSlandassim_GridComp/clsm_ensupd_upd_routines.F90 @@ -162,6 +162,10 @@ module clsm_ensupd_upd_routines public :: TileNnzObs public :: dist_km2deg + integer, parameter, public :: OBSFCSTANA_FMT_BIN = 1 + integer, parameter, public :: OBSFCSTANA_FMT_NC4 = 2 + integer, parameter, public :: OBSFCSTANA_FMT_BOTH = 3 + ! threshold below which FOV is considered zero (regardless of units) real, parameter, public :: FOV_threshold = 1e-4 @@ -189,6 +193,7 @@ subroutine read_ens_upd_inputs( & obs_param, & out_obslog, & out_ObsFcstAna, & + out_ObsFcstAna_mode, & out_smapL4SMaup, & N_obsbias_max & ) @@ -236,6 +241,7 @@ subroutine read_ens_upd_inputs( & logical, intent(out) :: out_obslog logical, intent(out) :: out_ObsFcstAna + integer, intent(out) :: out_ObsFcstAna_mode logical, intent(out) :: out_smapL4SMaup integer, intent(out) :: N_obsbias_max @@ -257,6 +263,7 @@ subroutine read_ens_upd_inputs( & character(200) :: ens_upd_inputs_path character( 40) :: ens_upd_inputs_file, dir_name, file_tag, file_ext + character( 40) :: out_ObsFcstAna_format integer :: i, j, k, N_tmp, k_hD, k_hA, k_vD, k_vA @@ -279,6 +286,7 @@ subroutine read_ens_upd_inputs( & update_type, & out_obslog, & out_ObsFcstAna, & + out_ObsFcstAna_format, & out_smapL4SMaup, & xcompact, ycompact, & fcsterr_inflation_fac, & @@ -290,6 +298,7 @@ subroutine read_ens_upd_inputs( & ens_upd_inputs_path = '.' ! set default ens_upd_inputs_file = 'LDASsa_DEFAULT_inputs_ensupd.nml' + out_ObsFcstAna_format = 'BIN' ! Read data from default ens_upd_inputs namelist file @@ -340,6 +349,23 @@ subroutine read_ens_upd_inputs( & ! ----------------------------------------------------------------- ! ! consistency checks etc + + select case (trim(out_ObsFcstAna_format)) + + case ('BIN') + out_ObsFcstAna_mode = OBSFCSTANA_FMT_BIN + + case ('NC4') + out_ObsFcstAna_mode = OBSFCSTANA_FMT_NC4 + + case ('BOTH') + out_ObsFcstAna_mode = OBSFCSTANA_FMT_BOTH + + case default + err_msg = 'unknown value of "out_ObsFcstAna_format": "' // & + trim(out_ObsFcstAna_format) // '"' + call ldas_abort(LDAS_GENERIC_ERROR, Iam, err_msg) + end select if (update_type==0) then err_msg = 'executable was built for assimilation but update_type=0' diff --git a/GEOSldas_App/LDASsa_DEFAULT_inputs_ensupd.nml b/GEOSldas_App/LDASsa_DEFAULT_inputs_ensupd.nml index 0a31e143..6f9f2a22 100644 --- a/GEOSldas_App/LDASsa_DEFAULT_inputs_ensupd.nml +++ b/GEOSldas_App/LDASsa_DEFAULT_inputs_ensupd.nml @@ -44,6 +44,8 @@ update_type = 0 out_obslog = .true. out_ObsFcstAna = .false. +! out_ObsFcstAna_format: 'BIN' (legacy unformatted), 'NC4', or 'BOTH' +out_ObsFcstAna_format = 'BIN' out_smapL4SMaup = .false. ! --------------------------------------------------------------------- diff --git a/GEOSldas_App/LandAtmDAS_nml/LDASsa_SPECIAL_inputs_ensupd.nml b/GEOSldas_App/LandAtmDAS_nml/LDASsa_SPECIAL_inputs_ensupd.nml index e8ecb12e..15c79b32 100644 --- a/GEOSldas_App/LandAtmDAS_nml/LDASsa_SPECIAL_inputs_ensupd.nml +++ b/GEOSldas_App/LandAtmDAS_nml/LDASsa_SPECIAL_inputs_ensupd.nml @@ -38,6 +38,8 @@ update_type = 10 out_obslog = .true. out_ObsFcstAna = .true. +! out_ObsFcstAna_format: 'BIN' (legacy unformatted), 'NC4', or 'BOTH' +out_ObsFcstAna_format = 'BIN' out_smapL4SMaup = .false. ! --------------------------------------------------------------------- diff --git a/GEOSldas_App/lenkf_j_template.py b/GEOSldas_App/lenkf_j_template.py index 60443ff0..3348fb06 100644 --- a/GEOSldas_App/lenkf_j_template.py +++ b/GEOSldas_App/lenkf_j_template.py @@ -481,6 +481,16 @@ /bin/mv $obsfcs ${{THISDIR}}$obsfcs end + set ObsFcses = `ls *.ldas_ObsFcstAna.*.nc4` + foreach obsfcs ( $ObsFcses ) + set ThisTime = `echo $obsfcs | rev | cut -d'.' -f2 | rev` + set TY = `echo $ThisTime | cut -c1-4` + set TM = `echo $ThisTime | cut -c5-6` + set THISDIR = $EXPDIR/output/$EXPDOMAIN/ana/ens_avg/Y${{TY}}/M${{TM}}/ + if (! -e $THISDIR ) mkdir -p $THISDIR + /bin/mv $obsfcs ${{THISDIR}}$obsfcs + end + set smapL4s = `ls *.ldas_tile_inst_smapL4SMaup.*.bin` foreach smapl4 ( $smapL4s ) set ThisTime = `echo $smapl4 | rev | cut -d'.' -f2 | rev` From 1e6fc8108074bb8b7bd15398065eb4ca9cf720ec Mon Sep 17 00:00:00 2001 From: amfox37 Date: Fri, 20 Feb 2026 11:51:29 -0700 Subject: [PATCH 02/40] Add ObsFcstAna NetCDF metadata and thread runtime context --- .../GEOS_LandAssimGridComp.F90 | 2 + .../clsm_ensupd_enkf_update.F90 | 140 +++++++++++++++++- 2 files changed, 138 insertions(+), 4 deletions(-) diff --git a/GEOSlandassim_GridComp/GEOS_LandAssimGridComp.F90 b/GEOSlandassim_GridComp/GEOS_LandAssimGridComp.F90 index 5e520a2b..6447610a 100644 --- a/GEOSlandassim_GridComp/GEOS_LandAssimGridComp.F90 +++ b/GEOSlandassim_GridComp/GEOS_LandAssimGridComp.F90 @@ -1957,6 +1957,8 @@ subroutine RUN ( GC, IMPORT, EXPORT, CLOCK, RC ) N_catl, tile_coord_l, & N_catf, tile_coord_rf, tcinternal%pgrid_g, & N_catl_vec, low_ind, rf2l, & + update_type, xcompact, ycompact, fcsterr_inflation_fac, & + LandAssimDTstep, & obs_param, & met_force, lai, & cat_param, cat_progn, mwRTM_param, & diff --git a/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 b/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 index fa6af5fe..8eaa405a 100644 --- a/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 +++ b/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 @@ -1515,7 +1515,9 @@ end subroutine apply_enkf_increments ! ******************************************************************** subroutine output_ObsFcstAna(date_time, exp_id, & - N_obsl, Observations_l, N_obs_param, out_ObsFcstAna_mode, rf2f) + N_obsl, Observations_l, N_obs_param, obs_param, out_ObsFcstAna_mode, & + update_type, xcompact, ycompact, fcsterr_inflation_fac, dtstep_assim, & + grid_name, rf2f) ! obs space output: observations, obs space forecast, obs space analysis, and ! associated error variances @@ -1529,7 +1531,13 @@ subroutine output_ObsFcstAna(date_time, exp_id, & character(*), intent(in) :: exp_id integer, intent(in) :: N_obsl, N_obs_param + type(obs_param_type), dimension(N_obs_param), intent(in) :: obs_param integer, intent(in) :: out_ObsFcstAna_mode + integer, intent(in) :: update_type + real, intent(in) :: xcompact, ycompact + real, intent(in) :: fcsterr_inflation_fac + integer, intent(in) :: dtstep_assim + character(*), intent(in) :: grid_name type(obs_type), dimension(N_obsl), intent(in) :: Observations_l @@ -1552,6 +1560,7 @@ subroutine output_ObsFcstAna(date_time, exp_id, & character(300) :: fname character( 40) :: file_ext + character( 8) :: out_ObsFcstAna_format character(len=*), parameter :: Iam = 'output_ObsFcstAna' character(len=400) :: err_msg @@ -1703,6 +1712,15 @@ subroutine output_ObsFcstAna(date_time, exp_id, & call ldas_abort(LDAS_GENERIC_ERROR, Iam, err_msg) end if + select case (out_ObsFcstAna_mode) + case (OBSFCSTANA_FMT_BIN) + out_ObsFcstAna_format = 'BIN' + case (OBSFCSTANA_FMT_NC4) + out_ObsFcstAna_format = 'NC4' + case (OBSFCSTANA_FMT_BOTH) + out_ObsFcstAna_format = 'BOTH' + end select + if (out_ObsFcstAna_mode == OBSFCSTANA_FMT_BIN .or. & out_ObsFcstAna_mode == OBSFCSTANA_FMT_BOTH) then @@ -1756,7 +1774,10 @@ subroutine output_ObsFcstAna(date_time, exp_id, & fname = get_io_filename( './', exp_id, file_tag, date_time=date_time, & dir_name=dir_name, ens_id=-1, file_ext=file_ext, no_subdirs=.true. ) - call write_ObsFcstAna_nc4(fname, date_time, N_obsf, Observations_f) + call write_ObsFcstAna_nc4(fname, date_time, exp_id, file_tag, N_obsf, & + Observations_f, N_obs_param, obs_param, out_ObsFcstAna_format, & + update_type, xcompact, ycompact, fcsterr_inflation_fac, & + dtstep_assim, grid_name) end if @@ -1767,29 +1788,50 @@ end subroutine output_ObsFcstAna ! ******************************************************************** - subroutine write_ObsFcstAna_nc4(fname, date_time, N_obsf, Observations_f) + subroutine write_ObsFcstAna_nc4(fname, date_time, exp_id, file_tag, N_obsf, & + Observations_f, N_obs_param, obs_param, out_ObsFcstAna_format, & + update_type, xcompact, ycompact, fcsterr_inflation_fac, & + dtstep_assim, grid_name) use netcdf implicit none character(*), intent(in) :: fname type(date_time_type), intent(in) :: date_time + character(*), intent(in) :: exp_id + character(*), intent(in) :: file_tag integer, intent(in) :: N_obsf + integer, intent(in) :: N_obs_param type(obs_type), dimension(N_obsf), intent(in) :: Observations_f + type(obs_param_type), dimension(N_obs_param), intent(in) :: obs_param + character(*), intent(in) :: out_ObsFcstAna_format + integer, intent(in) :: update_type + real, intent(in) :: xcompact + real, intent(in) :: ycompact + real, intent(in) :: fcsterr_inflation_fac + integer, intent(in) :: dtstep_assim + character(*), intent(in) :: grid_name integer :: ncid integer :: nobs_dimid + integer :: nspecies_dimid integer :: assim_varid, species_varid, tilenum_varid integer :: lon_varid, lat_varid integer :: obs_varid, obsvar_varid integer :: fcst_varid, fcstvar_varid integer :: ana_varid, anavar_varid + integer :: species_id_varid, species_orbit_varid, species_pol_varid + integer :: species_freq_varid, species_assim_varid integer, dimension(:), allocatable :: assim_int + integer, dimension(:), allocatable :: species_assim_int + character(len=40) :: attr_name + integer :: i call check( nf90_create(trim(fname), nf90_clobber + NF90_NETCDF4, ncid) ) call check( nf90_def_dim(ncid, 'nobs', NF90_UNLIMITED, nobs_dimid) ) + call check( nf90_def_dim(ncid, 'n_species', N_obs_param, nspecies_dimid) ) call check( nf90_def_var(ncid, 'assim', NF90_INT, [nobs_dimid], assim_varid) ) call check( nf90_def_var(ncid, 'species', NF90_INT, [nobs_dimid], species_varid) ) @@ -1803,7 +1845,15 @@ subroutine write_ObsFcstAna_nc4(fname, date_time, N_obsf, Observations_f) call check( nf90_def_var(ncid, 'ana', NF90_FLOAT, [nobs_dimid], ana_varid) ) call check( nf90_def_var(ncid, 'anavar', NF90_FLOAT, [nobs_dimid], anavar_varid) ) + call check( nf90_def_var(ncid, 'species_id', NF90_INT, [nspecies_dimid], species_id_varid) ) + call check( nf90_def_var(ncid, 'species_orbit', NF90_INT, [nspecies_dimid], species_orbit_varid) ) + call check( nf90_def_var(ncid, 'species_pol', NF90_INT, [nspecies_dimid], species_pol_varid) ) + call check( nf90_def_var(ncid, 'species_freq', NF90_FLOAT, [nspecies_dimid], species_freq_varid) ) + call check( nf90_def_var(ncid, 'species_assim', NF90_INT, [nspecies_dimid], species_assim_varid) ) + + call check( nf90_put_att(ncid, NF90_GLOBAL, 'schema_version', 'ObsFcstAna_nc4_v1') ) call check( nf90_put_att(ncid, NF90_GLOBAL, 'N_obsf', N_obsf) ) + call check( nf90_put_att(ncid, NF90_GLOBAL, 'N_obs_param', N_obs_param) ) call check( nf90_put_att(ncid, NF90_GLOBAL, 'year', date_time%year) ) call check( nf90_put_att(ncid, NF90_GLOBAL, 'month', date_time%month) ) call check( nf90_put_att(ncid, NF90_GLOBAL, 'day', date_time%day) ) @@ -1812,6 +1862,66 @@ subroutine write_ObsFcstAna_nc4(fname, date_time, N_obsf, Observations_f) call check( nf90_put_att(ncid, NF90_GLOBAL, 'second', date_time%sec) ) call check( nf90_put_att(ncid, NF90_GLOBAL, 'dofyr', date_time%dofyr) ) call check( nf90_put_att(ncid, NF90_GLOBAL, 'pentad', date_time%pentad) ) + call check( nf90_put_att(ncid, NF90_GLOBAL, 'exp_id', trim(exp_id)) ) + call check( nf90_put_att(ncid, NF90_GLOBAL, 'file_tag', trim(file_tag)) ) + call check( nf90_put_att(ncid, NF90_GLOBAL, 'out_ObsFcstAna_format', trim(out_ObsFcstAna_format)) ) + call check( nf90_put_att(ncid, NF90_GLOBAL, 'update_type', update_type) ) + call check( nf90_put_att(ncid, NF90_GLOBAL, 'xcompact', xcompact) ) + call check( nf90_put_att(ncid, NF90_GLOBAL, 'ycompact', ycompact) ) + call check( nf90_put_att(ncid, NF90_GLOBAL, 'fcsterr_inflation_fac', fcsterr_inflation_fac) ) + call check( nf90_put_att(ncid, NF90_GLOBAL, 'dtstep_assim', dtstep_assim) ) + call check( nf90_put_att(ncid, NF90_GLOBAL, 'grid_name', trim(grid_name)) ) + + do i=1,N_obs_param + write(attr_name, '(A,I4.4,A)') 'species_', obs_param(i)%species, '_descr' + call check( nf90_put_att(ncid, NF90_GLOBAL, trim(attr_name), trim(obs_param(i)%descr)) ) + write(attr_name, '(A,I4.4,A)') 'species_', obs_param(i)%species, '_varname' + call check( nf90_put_att(ncid, NF90_GLOBAL, trim(attr_name), trim(obs_param(i)%varname)) ) + write(attr_name, '(A,I4.4,A)') 'species_', obs_param(i)%species, '_units' + call check( nf90_put_att(ncid, NF90_GLOBAL, trim(attr_name), trim(obs_param(i)%units)) ) + end do + + call check( nf90_put_att(ncid, assim_varid, 'long_name', 'assimilation flag') ) + call check( nf90_put_att(ncid, assim_varid, 'units', '1') ) + call check( nf90_put_att(ncid, species_varid, 'long_name', 'observation species identifier') ) + call check( nf90_put_att(ncid, species_varid, 'units', '1') ) + call check( nf90_put_att(ncid, tilenum_varid, 'long_name', 'tile number (full domain)') ) + call check( nf90_put_att(ncid, tilenum_varid, 'units', '1') ) + call check( nf90_put_att(ncid, lon_varid, 'long_name', 'observation longitude') ) + call check( nf90_put_att(ncid, lon_varid, 'units', 'degrees_east') ) + call check( nf90_put_att(ncid, lon_varid, 'missing_value', nodata_generic) ) + call check( nf90_put_att(ncid, lat_varid, 'long_name', 'observation latitude') ) + call check( nf90_put_att(ncid, lat_varid, 'units', 'degrees_north') ) + call check( nf90_put_att(ncid, lat_varid, 'missing_value', nodata_generic) ) + call check( nf90_put_att(ncid, obs_varid, 'long_name', 'observation value') ) + call check( nf90_put_att(ncid, obs_varid, 'units', 'species-dependent') ) + call check( nf90_put_att(ncid, obs_varid, 'missing_value', nodata_generic) ) + call check( nf90_put_att(ncid, obsvar_varid, 'long_name', 'observation error variance') ) + call check( nf90_put_att(ncid, obsvar_varid, 'units', 'species-dependent') ) + call check( nf90_put_att(ncid, obsvar_varid, 'missing_value', nodata_generic) ) + call check( nf90_put_att(ncid, fcst_varid, 'long_name', 'forecast observation equivalent') ) + call check( nf90_put_att(ncid, fcst_varid, 'units', 'species-dependent') ) + call check( nf90_put_att(ncid, fcst_varid, 'missing_value', nodata_generic) ) + call check( nf90_put_att(ncid, fcstvar_varid, 'long_name', 'forecast error variance in obs space') ) + call check( nf90_put_att(ncid, fcstvar_varid, 'units', 'species-dependent') ) + call check( nf90_put_att(ncid, fcstvar_varid, 'missing_value', nodata_generic) ) + call check( nf90_put_att(ncid, ana_varid, 'long_name', 'analysis observation equivalent') ) + call check( nf90_put_att(ncid, ana_varid, 'units', 'species-dependent') ) + call check( nf90_put_att(ncid, ana_varid, 'missing_value', nodata_generic) ) + call check( nf90_put_att(ncid, anavar_varid, 'long_name', 'analysis error variance in obs space') ) + call check( nf90_put_att(ncid, anavar_varid, 'units', 'species-dependent') ) + call check( nf90_put_att(ncid, anavar_varid, 'missing_value', nodata_generic) ) + + call check( nf90_put_att(ncid, species_id_varid, 'long_name', 'observation species identifier') ) + call check( nf90_put_att(ncid, species_id_varid, 'units', '1') ) + call check( nf90_put_att(ncid, species_orbit_varid, 'long_name', 'observation orbit type') ) + call check( nf90_put_att(ncid, species_orbit_varid, 'units', '1') ) + call check( nf90_put_att(ncid, species_pol_varid, 'long_name', 'observation polarization') ) + call check( nf90_put_att(ncid, species_pol_varid, 'units', '1') ) + call check( nf90_put_att(ncid, species_freq_varid, 'long_name', 'observation frequency') ) + call check( nf90_put_att(ncid, species_freq_varid, 'units', 'Hz') ) + call check( nf90_put_att(ncid, species_assim_varid, 'long_name', 'species assimilation flag') ) + call check( nf90_put_att(ncid, species_assim_varid, 'units', '1') ) call check( nf90_enddef(ncid) ) @@ -1837,6 +1947,22 @@ subroutine write_ObsFcstAna_nc4(fname, date_time, N_obsf, Observations_f) end if + if (N_obs_param > 0) then + + allocate(species_assim_int(N_obs_param)) + species_assim_int = 0 + where (obs_param(1:N_obs_param)%assim) species_assim_int = 1 + + call check( nf90_put_var(ncid, species_id_varid, obs_param(1:N_obs_param)%species) ) + call check( nf90_put_var(ncid, species_orbit_varid, obs_param(1:N_obs_param)%orbit) ) + call check( nf90_put_var(ncid, species_pol_varid, obs_param(1:N_obs_param)%pol) ) + call check( nf90_put_var(ncid, species_freq_varid, obs_param(1:N_obs_param)%freq) ) + call check( nf90_put_var(ncid, species_assim_varid, species_assim_int) ) + + deallocate(species_assim_int) + + end if + call check( nf90_close(ncid) ) contains @@ -1860,6 +1986,7 @@ subroutine output_ObsFcstAna_wrapper( out_ObsFcstAna, & N_catl, tile_coord_l, & N_catf, tile_coord_f, pert_grid_g, & N_catl_vec, low_ind, f2l, & + update_type, xcompact, ycompact, fcsterr_inflation_fac, dtstep_assim, & obs_param, & met_force, lai, cat_param, cat_progn, mwRTM_param, & Observations_l, rf2f ) @@ -1883,6 +2010,9 @@ subroutine output_ObsFcstAna_wrapper( out_ObsFcstAna, & integer, intent(in) :: N_obsl, N_obs_param, N_ens, N_catl, N_catf + integer, intent(in) :: update_type, dtstep_assim + real, intent(in) :: xcompact, ycompact, fcsterr_inflation_fac + type(tile_coord_type), dimension(:), pointer :: tile_coord_l ! input type(tile_coord_type), dimension(:), pointer :: tile_coord_f ! input @@ -1947,7 +2077,9 @@ subroutine output_ObsFcstAna_wrapper( out_ObsFcstAna, & ! write out model, observations, and "OminusA" information call output_ObsFcstAna( date_time, exp_id, N_obsl, & - Observations_l(1:N_obsl), N_obs_param, out_ObsFcstAna_mode, rf2f=rf2f ) + Observations_l(1:N_obsl), N_obs_param, obs_param, out_ObsFcstAna_mode, & + update_type, xcompact, ycompact, fcsterr_inflation_fac, dtstep_assim, & + trim(pert_grid_g%gridtype), rf2f=rf2f ) end if From b5a4a3b1c97998bd00c942fd52ea0a65d60b9ce5 Mon Sep 17 00:00:00 2001 From: amfox37 Date: Fri, 20 Feb 2026 18:20:15 -0700 Subject: [PATCH 03/40] Add ObsFcstAna bin-vs-nc4 comparison utility --- .../Compare_ObsFcstAna_bin_nc4.py | 213 ++++++++++++++++++ 1 file changed, 213 insertions(+) create mode 100755 GEOSldas_App/util/postproc/ObsFcstAna_stats/Compare_ObsFcstAna_bin_nc4.py diff --git a/GEOSldas_App/util/postproc/ObsFcstAna_stats/Compare_ObsFcstAna_bin_nc4.py b/GEOSldas_App/util/postproc/ObsFcstAna_stats/Compare_ObsFcstAna_bin_nc4.py new file mode 100755 index 00000000..1ce43ccc --- /dev/null +++ b/GEOSldas_App/util/postproc/ObsFcstAna_stats/Compare_ObsFcstAna_bin_nc4.py @@ -0,0 +1,213 @@ +#!/usr/bin/env python3 + +""" +Find and compare matching ObsFcstAna .bin and .nc4 files. + +Usage examples: + ./Compare_ObsFcstAna_bin_nc4.py /path/to/ana/ens_avg + ./Compare_ObsFcstAna_bin_nc4.py /path/to/ana/ens_avg --max-pairs 5 +""" + +import argparse +import sys +from pathlib import Path + +import numpy as np +from netCDF4 import Dataset + +THIS_DIR = Path(__file__).resolve().parent +sys.path.append(str((THIS_DIR / '../../shared/python').resolve())) +from read_GEOSldas import read_ObsFcstAna # noqa: E402 + + +INT_MAP = [ + ('obs_assim', 'assim'), + ('obs_species', 'species'), + ('obs_tilenum', 'tilenum'), +] + +FLOAT_MAP = [ + ('obs_lon', 'lon'), + ('obs_lat', 'lat'), + ('obs_obs', 'obs'), + ('obs_obsvar', 'obsvar'), + ('obs_fcst', 'fcst'), + ('obs_fcstvar', 'fcstvar'), + ('obs_ana', 'ana'), + ('obs_anavar', 'anavar'), +] + +TIME_KEYS = ['year', 'month', 'day', 'hour', 'minute', 'second', 'dofyr', 'pentad'] + + +def _as_array(x): + return np.asarray(x) + + +def _normalize_missing(a, fill_value=-9999.0): + out = np.asarray(a, dtype=np.float64).copy() + out[np.isclose(out, fill_value)] = np.nan + return out + + +def read_obsfcstana_nc4(fname): + out = {} + date_time = {} + + with Dataset(fname, mode='r') as nc: + for bkey, nkey in INT_MAP: + out[bkey] = _as_array(nc.variables[nkey][:]).astype(np.int32) + + for bkey, nkey in FLOAT_MAP: + v = _as_array(nc.variables[nkey][:]).astype(np.float64) + fillv = getattr(nc.variables[nkey], 'missing_value', -9999.0) + out[bkey] = _normalize_missing(v, fill_value=float(fillv)) + + for key in TIME_KEYS: + date_time[key] = int(getattr(nc, key, -9999)) + + out['N_obsf_attr'] = int(getattr(nc, 'N_obsf', out['obs_species'].size)) + + return date_time, out + + +def compare_arrays_int(a, b): + if a.shape != b.shape: + return {'shape_match': False, 'n_bad': max(a.size, b.size), 'max_abs': np.nan} + bad = (a != b) + return { + 'shape_match': True, + 'n_bad': int(np.count_nonzero(bad)), + 'max_abs': int(np.max(np.abs(a.astype(np.int64) - b.astype(np.int64)))) if a.size > 0 else 0, + } + + +def compare_arrays_float(a, b, rtol, atol): + if a.shape != b.shape: + return {'shape_match': False, 'n_bad': max(a.size, b.size), 'max_abs': np.nan} + + bad = ~np.isclose(a, b, rtol=rtol, atol=atol, equal_nan=True) + n_bad = int(np.count_nonzero(bad)) + + diff = np.abs(a - b) + max_abs = float(np.nanmax(diff)) if diff.size > 0 else 0.0 + + return {'shape_match': True, 'n_bad': n_bad, 'max_abs': max_abs} + + +def find_pairs(root): + bin_files = sorted(root.rglob('*.ldas_ObsFcstAna.*z.bin')) + nc4_files = sorted(root.rglob('*.ldas_ObsFcstAna.*z.nc4')) + + pairs = [] + missing_nc4 = [] + for b in bin_files: + n = b.with_suffix('.nc4') + if n.exists(): + pairs.append((b, n)) + else: + missing_nc4.append(b) + + missing_bin = [] + for n in nc4_files: + b = n.with_suffix('.bin') + if not b.exists(): + missing_bin.append(n) + + return pairs, missing_nc4, missing_bin + + +def compare_one(bin_file, nc4_file, rtol, atol): + b = read_ObsFcstAna(str(bin_file)) + t_nc4, n = read_obsfcstana_nc4(str(nc4_file)) + + issues = [] + n_bad_total = 0 + + # Time/header checks + t_bin = b['date_time'] + for key in TIME_KEYS: + key_bin = 'min' if key == 'minute' else ('sec' if key == 'second' else key) + vb = int(t_bin.get(key_bin, -9999)) + vn = int(t_nc4.get(key, -9999)) + if vb != vn: + issues.append(f'time mismatch: {key} bin={vb} nc4={vn}') + + n_obs_bin = int(_as_array(b['obs_species']).size) + n_obs_nc4 = int(_as_array(n['obs_species']).size) + if n_obs_bin != n_obs_nc4: + issues.append(f'N_obs mismatch: bin={n_obs_bin} nc4={n_obs_nc4}') + if int(n.get('N_obsf_attr', n_obs_nc4)) != n_obs_nc4: + issues.append(f'N_obsf attr mismatch: attr={n.get("N_obsf_attr")} actual={n_obs_nc4}') + + # Integer fields + for bkey, _ in INT_MAP: + cb = compare_arrays_int(_as_array(b[bkey]), _as_array(n[bkey])) + if not cb['shape_match'] or cb['n_bad'] > 0: + issues.append(f'{bkey}: n_bad={cb["n_bad"]}, max_abs={cb["max_abs"]}') + n_bad_total += cb['n_bad'] + + # Float fields + for bkey, _ in FLOAT_MAP: + a_bin = _normalize_missing(_as_array(b[bkey])) + a_nc4 = _normalize_missing(_as_array(n[bkey])) + cb = compare_arrays_float(a_bin, a_nc4, rtol=rtol, atol=atol) + if not cb['shape_match'] or cb['n_bad'] > 0: + issues.append(f'{bkey}: n_bad={cb["n_bad"]}, max_abs={cb["max_abs"]:.6g}') + n_bad_total += cb['n_bad'] + + return issues, n_bad_total, n_obs_bin + + +def main(): + parser = argparse.ArgumentParser(description='Compare GEOSldas ObsFcstAna .bin and .nc4 files.') + parser.add_argument('root', nargs='?', default='.', help='Root directory to scan recursively.') + parser.add_argument('--rtol', type=float, default=0.0, help='Relative tolerance for float compare.') + parser.add_argument('--atol', type=float, default=1e-6, help='Absolute tolerance for float compare.') + parser.add_argument('--max-pairs', type=int, default=0, help='Max number of pairs to compare (0=all).') + args = parser.parse_args() + + root = Path(args.root).expanduser().resolve() + pairs, missing_nc4, missing_bin = find_pairs(root) + + print(f'scan_root: {root}') + print(f'found pairs: {len(pairs)}') + print(f'missing nc4 for bin: {len(missing_nc4)}') + print(f'missing bin for nc4: {len(missing_bin)}') + + if len(missing_nc4) > 0: + print('example missing nc4:') + for p in missing_nc4[:5]: + print(f' {p}') + + if len(missing_bin) > 0: + print('example missing bin:') + for p in missing_bin[:5]: + print(f' {p}') + + if not pairs: + return 1 + + n_compare = len(pairs) if args.max_pairs <= 0 else min(args.max_pairs, len(pairs)) + + n_pass = 0 + n_fail = 0 + + for i, (fbin, fnc4) in enumerate(pairs[:n_compare], start=1): + issues, n_bad, n_obs = compare_one(fbin, fnc4, rtol=args.rtol, atol=args.atol) + rel = fbin.relative_to(root) + if len(issues) == 0: + print(f'[{i}/{n_compare}] PASS {rel} (N_obs={n_obs})') + n_pass += 1 + else: + print(f'[{i}/{n_compare}] FAIL {rel} (N_obs={n_obs}, n_bad={n_bad})') + for msg in issues: + print(f' - {msg}') + n_fail += 1 + + print(f'summary: pass={n_pass}, fail={n_fail}, compared={n_compare}') + return 0 if n_fail == 0 else 2 + + +if __name__ == '__main__': + raise SystemExit(main()) From 09afdf4bdd6d0f7569590d6e5effa463a922e598 Mon Sep 17 00:00:00 2001 From: amfox37 Date: Fri, 20 Feb 2026 19:07:23 -0700 Subject: [PATCH 04/40] Remove species orbit/pol/freq from ObsFcstAna nc4 output --- .../clsm_ensupd_enkf_update.F90 | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 b/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 index 8eaa405a..c5fb769a 100644 --- a/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 +++ b/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 @@ -1820,8 +1820,7 @@ subroutine write_ObsFcstAna_nc4(fname, date_time, exp_id, file_tag, N_obsf, & integer :: obs_varid, obsvar_varid integer :: fcst_varid, fcstvar_varid integer :: ana_varid, anavar_varid - integer :: species_id_varid, species_orbit_varid, species_pol_varid - integer :: species_freq_varid, species_assim_varid + integer :: species_id_varid, species_assim_varid integer, dimension(:), allocatable :: assim_int integer, dimension(:), allocatable :: species_assim_int @@ -1846,9 +1845,6 @@ subroutine write_ObsFcstAna_nc4(fname, date_time, exp_id, file_tag, N_obsf, & call check( nf90_def_var(ncid, 'anavar', NF90_FLOAT, [nobs_dimid], anavar_varid) ) call check( nf90_def_var(ncid, 'species_id', NF90_INT, [nspecies_dimid], species_id_varid) ) - call check( nf90_def_var(ncid, 'species_orbit', NF90_INT, [nspecies_dimid], species_orbit_varid) ) - call check( nf90_def_var(ncid, 'species_pol', NF90_INT, [nspecies_dimid], species_pol_varid) ) - call check( nf90_def_var(ncid, 'species_freq', NF90_FLOAT, [nspecies_dimid], species_freq_varid) ) call check( nf90_def_var(ncid, 'species_assim', NF90_INT, [nspecies_dimid], species_assim_varid) ) call check( nf90_put_att(ncid, NF90_GLOBAL, 'schema_version', 'ObsFcstAna_nc4_v1') ) @@ -1914,12 +1910,6 @@ subroutine write_ObsFcstAna_nc4(fname, date_time, exp_id, file_tag, N_obsf, & call check( nf90_put_att(ncid, species_id_varid, 'long_name', 'observation species identifier') ) call check( nf90_put_att(ncid, species_id_varid, 'units', '1') ) - call check( nf90_put_att(ncid, species_orbit_varid, 'long_name', 'observation orbit type') ) - call check( nf90_put_att(ncid, species_orbit_varid, 'units', '1') ) - call check( nf90_put_att(ncid, species_pol_varid, 'long_name', 'observation polarization') ) - call check( nf90_put_att(ncid, species_pol_varid, 'units', '1') ) - call check( nf90_put_att(ncid, species_freq_varid, 'long_name', 'observation frequency') ) - call check( nf90_put_att(ncid, species_freq_varid, 'units', 'Hz') ) call check( nf90_put_att(ncid, species_assim_varid, 'long_name', 'species assimilation flag') ) call check( nf90_put_att(ncid, species_assim_varid, 'units', '1') ) @@ -1954,9 +1944,6 @@ subroutine write_ObsFcstAna_nc4(fname, date_time, exp_id, file_tag, N_obsf, & where (obs_param(1:N_obs_param)%assim) species_assim_int = 1 call check( nf90_put_var(ncid, species_id_varid, obs_param(1:N_obs_param)%species) ) - call check( nf90_put_var(ncid, species_orbit_varid, obs_param(1:N_obs_param)%orbit) ) - call check( nf90_put_var(ncid, species_pol_varid, obs_param(1:N_obs_param)%pol) ) - call check( nf90_put_var(ncid, species_freq_varid, obs_param(1:N_obs_param)%freq) ) call check( nf90_put_var(ncid, species_assim_varid, species_assim_int) ) deallocate(species_assim_int) From e3eb583b8e2e6cd2f132ca31462843866bb13d1f Mon Sep 17 00:00:00 2001 From: amfox37 Date: Sun, 22 Feb 2026 11:08:37 -0700 Subject: [PATCH 05/40] Harden ObsFcstAna nc4 write error handling --- .../clsm_ensupd_enkf_update.F90 | 204 +++++++++--------- .../Compare_ObsFcstAna_bin_nc4.py | 8 + 2 files changed, 111 insertions(+), 101 deletions(-) diff --git a/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 b/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 index c5fb769a..c9ffece9 100644 --- a/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 +++ b/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 @@ -1826,94 +1826,96 @@ subroutine write_ObsFcstAna_nc4(fname, date_time, exp_id, file_tag, N_obsf, & integer, dimension(:), allocatable :: species_assim_int character(len=40) :: attr_name integer :: i - - call check( nf90_create(trim(fname), nf90_clobber + NF90_NETCDF4, ncid) ) - - call check( nf90_def_dim(ncid, 'nobs', NF90_UNLIMITED, nobs_dimid) ) - call check( nf90_def_dim(ncid, 'n_species', N_obs_param, nspecies_dimid) ) - - call check( nf90_def_var(ncid, 'assim', NF90_INT, [nobs_dimid], assim_varid) ) - call check( nf90_def_var(ncid, 'species', NF90_INT, [nobs_dimid], species_varid) ) - call check( nf90_def_var(ncid, 'tilenum', NF90_INT, [nobs_dimid], tilenum_varid) ) - call check( nf90_def_var(ncid, 'lon', NF90_FLOAT, [nobs_dimid], lon_varid) ) - call check( nf90_def_var(ncid, 'lat', NF90_FLOAT, [nobs_dimid], lat_varid) ) - call check( nf90_def_var(ncid, 'obs', NF90_FLOAT, [nobs_dimid], obs_varid) ) - call check( nf90_def_var(ncid, 'obsvar', NF90_FLOAT, [nobs_dimid], obsvar_varid) ) - call check( nf90_def_var(ncid, 'fcst', NF90_FLOAT, [nobs_dimid], fcst_varid) ) - call check( nf90_def_var(ncid, 'fcstvar', NF90_FLOAT, [nobs_dimid], fcstvar_varid) ) - call check( nf90_def_var(ncid, 'ana', NF90_FLOAT, [nobs_dimid], ana_varid) ) - call check( nf90_def_var(ncid, 'anavar', NF90_FLOAT, [nobs_dimid], anavar_varid) ) - - call check( nf90_def_var(ncid, 'species_id', NF90_INT, [nspecies_dimid], species_id_varid) ) - call check( nf90_def_var(ncid, 'species_assim', NF90_INT, [nspecies_dimid], species_assim_varid) ) - - call check( nf90_put_att(ncid, NF90_GLOBAL, 'schema_version', 'ObsFcstAna_nc4_v1') ) - call check( nf90_put_att(ncid, NF90_GLOBAL, 'N_obsf', N_obsf) ) - call check( nf90_put_att(ncid, NF90_GLOBAL, 'N_obs_param', N_obs_param) ) - call check( nf90_put_att(ncid, NF90_GLOBAL, 'year', date_time%year) ) - call check( nf90_put_att(ncid, NF90_GLOBAL, 'month', date_time%month) ) - call check( nf90_put_att(ncid, NF90_GLOBAL, 'day', date_time%day) ) - call check( nf90_put_att(ncid, NF90_GLOBAL, 'hour', date_time%hour) ) - call check( nf90_put_att(ncid, NF90_GLOBAL, 'minute', date_time%min) ) - call check( nf90_put_att(ncid, NF90_GLOBAL, 'second', date_time%sec) ) - call check( nf90_put_att(ncid, NF90_GLOBAL, 'dofyr', date_time%dofyr) ) - call check( nf90_put_att(ncid, NF90_GLOBAL, 'pentad', date_time%pentad) ) - call check( nf90_put_att(ncid, NF90_GLOBAL, 'exp_id', trim(exp_id)) ) - call check( nf90_put_att(ncid, NF90_GLOBAL, 'file_tag', trim(file_tag)) ) - call check( nf90_put_att(ncid, NF90_GLOBAL, 'out_ObsFcstAna_format', trim(out_ObsFcstAna_format)) ) - call check( nf90_put_att(ncid, NF90_GLOBAL, 'update_type', update_type) ) - call check( nf90_put_att(ncid, NF90_GLOBAL, 'xcompact', xcompact) ) - call check( nf90_put_att(ncid, NF90_GLOBAL, 'ycompact', ycompact) ) - call check( nf90_put_att(ncid, NF90_GLOBAL, 'fcsterr_inflation_fac', fcsterr_inflation_fac) ) - call check( nf90_put_att(ncid, NF90_GLOBAL, 'dtstep_assim', dtstep_assim) ) - call check( nf90_put_att(ncid, NF90_GLOBAL, 'grid_name', trim(grid_name)) ) + character(len=*), parameter :: Iam = 'write_ObsFcstAna_nc4' + character(len=400) :: err_msg + + call nc4_check( nf90_create(trim(fname), nf90_clobber + NF90_NETCDF4, ncid) ) + + call nc4_check( nf90_def_dim(ncid, 'nobs', NF90_UNLIMITED, nobs_dimid) ) + call nc4_check( nf90_def_dim(ncid, 'n_species', N_obs_param, nspecies_dimid) ) + + call nc4_check( nf90_def_var(ncid, 'assim', NF90_INT, [nobs_dimid], assim_varid) ) + call nc4_check( nf90_def_var(ncid, 'species', NF90_INT, [nobs_dimid], species_varid) ) + call nc4_check( nf90_def_var(ncid, 'tilenum', NF90_INT, [nobs_dimid], tilenum_varid) ) + call nc4_check( nf90_def_var(ncid, 'lon', NF90_FLOAT, [nobs_dimid], lon_varid) ) + call nc4_check( nf90_def_var(ncid, 'lat', NF90_FLOAT, [nobs_dimid], lat_varid) ) + call nc4_check( nf90_def_var(ncid, 'obs', NF90_FLOAT, [nobs_dimid], obs_varid) ) + call nc4_check( nf90_def_var(ncid, 'obsvar', NF90_FLOAT, [nobs_dimid], obsvar_varid) ) + call nc4_check( nf90_def_var(ncid, 'fcst', NF90_FLOAT, [nobs_dimid], fcst_varid) ) + call nc4_check( nf90_def_var(ncid, 'fcstvar', NF90_FLOAT, [nobs_dimid], fcstvar_varid) ) + call nc4_check( nf90_def_var(ncid, 'ana', NF90_FLOAT, [nobs_dimid], ana_varid) ) + call nc4_check( nf90_def_var(ncid, 'anavar', NF90_FLOAT, [nobs_dimid], anavar_varid) ) + + call nc4_check( nf90_def_var(ncid, 'species_id', NF90_INT, [nspecies_dimid], species_id_varid) ) + call nc4_check( nf90_def_var(ncid, 'species_assim', NF90_INT, [nspecies_dimid], species_assim_varid) ) + + call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, 'schema_version', 'ObsFcstAna_nc4_v1') ) + call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, 'N_obsf', N_obsf) ) + call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, 'N_obs_param', N_obs_param) ) + call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, 'year', date_time%year) ) + call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, 'month', date_time%month) ) + call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, 'day', date_time%day) ) + call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, 'hour', date_time%hour) ) + call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, 'minute', date_time%min) ) + call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, 'second', date_time%sec) ) + call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, 'dofyr', date_time%dofyr) ) + call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, 'pentad', date_time%pentad) ) + call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, 'exp_id', trim(exp_id)) ) + call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, 'file_tag', trim(file_tag)) ) + call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, 'out_ObsFcstAna_format', trim(out_ObsFcstAna_format)) ) + call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, 'update_type', update_type) ) + call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, 'xcompact', xcompact) ) + call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, 'ycompact', ycompact) ) + call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, 'fcsterr_inflation_fac', fcsterr_inflation_fac) ) + call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, 'dtstep_assim', dtstep_assim) ) + call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, 'grid_name', trim(grid_name)) ) do i=1,N_obs_param write(attr_name, '(A,I4.4,A)') 'species_', obs_param(i)%species, '_descr' - call check( nf90_put_att(ncid, NF90_GLOBAL, trim(attr_name), trim(obs_param(i)%descr)) ) + call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, trim(attr_name), trim(obs_param(i)%descr)) ) write(attr_name, '(A,I4.4,A)') 'species_', obs_param(i)%species, '_varname' - call check( nf90_put_att(ncid, NF90_GLOBAL, trim(attr_name), trim(obs_param(i)%varname)) ) + call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, trim(attr_name), trim(obs_param(i)%varname)) ) write(attr_name, '(A,I4.4,A)') 'species_', obs_param(i)%species, '_units' - call check( nf90_put_att(ncid, NF90_GLOBAL, trim(attr_name), trim(obs_param(i)%units)) ) + call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, trim(attr_name), trim(obs_param(i)%units)) ) end do - call check( nf90_put_att(ncid, assim_varid, 'long_name', 'assimilation flag') ) - call check( nf90_put_att(ncid, assim_varid, 'units', '1') ) - call check( nf90_put_att(ncid, species_varid, 'long_name', 'observation species identifier') ) - call check( nf90_put_att(ncid, species_varid, 'units', '1') ) - call check( nf90_put_att(ncid, tilenum_varid, 'long_name', 'tile number (full domain)') ) - call check( nf90_put_att(ncid, tilenum_varid, 'units', '1') ) - call check( nf90_put_att(ncid, lon_varid, 'long_name', 'observation longitude') ) - call check( nf90_put_att(ncid, lon_varid, 'units', 'degrees_east') ) - call check( nf90_put_att(ncid, lon_varid, 'missing_value', nodata_generic) ) - call check( nf90_put_att(ncid, lat_varid, 'long_name', 'observation latitude') ) - call check( nf90_put_att(ncid, lat_varid, 'units', 'degrees_north') ) - call check( nf90_put_att(ncid, lat_varid, 'missing_value', nodata_generic) ) - call check( nf90_put_att(ncid, obs_varid, 'long_name', 'observation value') ) - call check( nf90_put_att(ncid, obs_varid, 'units', 'species-dependent') ) - call check( nf90_put_att(ncid, obs_varid, 'missing_value', nodata_generic) ) - call check( nf90_put_att(ncid, obsvar_varid, 'long_name', 'observation error variance') ) - call check( nf90_put_att(ncid, obsvar_varid, 'units', 'species-dependent') ) - call check( nf90_put_att(ncid, obsvar_varid, 'missing_value', nodata_generic) ) - call check( nf90_put_att(ncid, fcst_varid, 'long_name', 'forecast observation equivalent') ) - call check( nf90_put_att(ncid, fcst_varid, 'units', 'species-dependent') ) - call check( nf90_put_att(ncid, fcst_varid, 'missing_value', nodata_generic) ) - call check( nf90_put_att(ncid, fcstvar_varid, 'long_name', 'forecast error variance in obs space') ) - call check( nf90_put_att(ncid, fcstvar_varid, 'units', 'species-dependent') ) - call check( nf90_put_att(ncid, fcstvar_varid, 'missing_value', nodata_generic) ) - call check( nf90_put_att(ncid, ana_varid, 'long_name', 'analysis observation equivalent') ) - call check( nf90_put_att(ncid, ana_varid, 'units', 'species-dependent') ) - call check( nf90_put_att(ncid, ana_varid, 'missing_value', nodata_generic) ) - call check( nf90_put_att(ncid, anavar_varid, 'long_name', 'analysis error variance in obs space') ) - call check( nf90_put_att(ncid, anavar_varid, 'units', 'species-dependent') ) - call check( nf90_put_att(ncid, anavar_varid, 'missing_value', nodata_generic) ) - - call check( nf90_put_att(ncid, species_id_varid, 'long_name', 'observation species identifier') ) - call check( nf90_put_att(ncid, species_id_varid, 'units', '1') ) - call check( nf90_put_att(ncid, species_assim_varid, 'long_name', 'species assimilation flag') ) - call check( nf90_put_att(ncid, species_assim_varid, 'units', '1') ) - - call check( nf90_enddef(ncid) ) + call nc4_check( nf90_put_att(ncid, assim_varid, 'long_name', 'assimilation flag') ) + call nc4_check( nf90_put_att(ncid, assim_varid, 'units', '1') ) + call nc4_check( nf90_put_att(ncid, species_varid, 'long_name', 'observation species identifier') ) + call nc4_check( nf90_put_att(ncid, species_varid, 'units', '1') ) + call nc4_check( nf90_put_att(ncid, tilenum_varid, 'long_name', 'tile number (full domain)') ) + call nc4_check( nf90_put_att(ncid, tilenum_varid, 'units', '1') ) + call nc4_check( nf90_put_att(ncid, lon_varid, 'long_name', 'observation longitude') ) + call nc4_check( nf90_put_att(ncid, lon_varid, 'units', 'degrees_east') ) + call nc4_check( nf90_put_att(ncid, lon_varid, 'missing_value', nodata_generic) ) + call nc4_check( nf90_put_att(ncid, lat_varid, 'long_name', 'observation latitude') ) + call nc4_check( nf90_put_att(ncid, lat_varid, 'units', 'degrees_north') ) + call nc4_check( nf90_put_att(ncid, lat_varid, 'missing_value', nodata_generic) ) + call nc4_check( nf90_put_att(ncid, obs_varid, 'long_name', 'observation value') ) + call nc4_check( nf90_put_att(ncid, obs_varid, 'units', 'species-dependent') ) + call nc4_check( nf90_put_att(ncid, obs_varid, 'missing_value', nodata_generic) ) + call nc4_check( nf90_put_att(ncid, obsvar_varid, 'long_name', 'observation error variance') ) + call nc4_check( nf90_put_att(ncid, obsvar_varid, 'units', 'species-dependent') ) + call nc4_check( nf90_put_att(ncid, obsvar_varid, 'missing_value', nodata_generic) ) + call nc4_check( nf90_put_att(ncid, fcst_varid, 'long_name', 'forecast observation equivalent') ) + call nc4_check( nf90_put_att(ncid, fcst_varid, 'units', 'species-dependent') ) + call nc4_check( nf90_put_att(ncid, fcst_varid, 'missing_value', nodata_generic) ) + call nc4_check( nf90_put_att(ncid, fcstvar_varid, 'long_name', 'forecast error variance in obs space') ) + call nc4_check( nf90_put_att(ncid, fcstvar_varid, 'units', 'species-dependent') ) + call nc4_check( nf90_put_att(ncid, fcstvar_varid, 'missing_value', nodata_generic) ) + call nc4_check( nf90_put_att(ncid, ana_varid, 'long_name', 'analysis observation equivalent') ) + call nc4_check( nf90_put_att(ncid, ana_varid, 'units', 'species-dependent') ) + call nc4_check( nf90_put_att(ncid, ana_varid, 'missing_value', nodata_generic) ) + call nc4_check( nf90_put_att(ncid, anavar_varid, 'long_name', 'analysis error variance in obs space') ) + call nc4_check( nf90_put_att(ncid, anavar_varid, 'units', 'species-dependent') ) + call nc4_check( nf90_put_att(ncid, anavar_varid, 'missing_value', nodata_generic) ) + + call nc4_check( nf90_put_att(ncid, species_id_varid, 'long_name', 'observation species identifier') ) + call nc4_check( nf90_put_att(ncid, species_id_varid, 'units', '1') ) + call nc4_check( nf90_put_att(ncid, species_assim_varid, 'long_name', 'species assimilation flag') ) + call nc4_check( nf90_put_att(ncid, species_assim_varid, 'units', '1') ) + + call nc4_check( nf90_enddef(ncid) ) if (N_obsf > 0) then @@ -1921,17 +1923,17 @@ subroutine write_ObsFcstAna_nc4(fname, date_time, exp_id, file_tag, N_obsf, & assim_int = 0 where (Observations_f(1:N_obsf)%assim) assim_int = 1 - call check( nf90_put_var(ncid, assim_varid, assim_int) ) - call check( nf90_put_var(ncid, species_varid, Observations_f(1:N_obsf)%species) ) - call check( nf90_put_var(ncid, tilenum_varid, Observations_f(1:N_obsf)%tilenum) ) - call check( nf90_put_var(ncid, lon_varid, Observations_f(1:N_obsf)%lon) ) - call check( nf90_put_var(ncid, lat_varid, Observations_f(1:N_obsf)%lat) ) - call check( nf90_put_var(ncid, obs_varid, Observations_f(1:N_obsf)%obs) ) - call check( nf90_put_var(ncid, obsvar_varid, Observations_f(1:N_obsf)%obsvar) ) - call check( nf90_put_var(ncid, fcst_varid, Observations_f(1:N_obsf)%fcst) ) - call check( nf90_put_var(ncid, fcstvar_varid, Observations_f(1:N_obsf)%fcstvar) ) - call check( nf90_put_var(ncid, ana_varid, Observations_f(1:N_obsf)%ana) ) - call check( nf90_put_var(ncid, anavar_varid, Observations_f(1:N_obsf)%anavar) ) + call nc4_check( nf90_put_var(ncid, assim_varid, assim_int) ) + call nc4_check( nf90_put_var(ncid, species_varid, Observations_f(1:N_obsf)%species) ) + call nc4_check( nf90_put_var(ncid, tilenum_varid, Observations_f(1:N_obsf)%tilenum) ) + call nc4_check( nf90_put_var(ncid, lon_varid, Observations_f(1:N_obsf)%lon) ) + call nc4_check( nf90_put_var(ncid, lat_varid, Observations_f(1:N_obsf)%lat) ) + call nc4_check( nf90_put_var(ncid, obs_varid, Observations_f(1:N_obsf)%obs) ) + call nc4_check( nf90_put_var(ncid, obsvar_varid, Observations_f(1:N_obsf)%obsvar) ) + call nc4_check( nf90_put_var(ncid, fcst_varid, Observations_f(1:N_obsf)%fcst) ) + call nc4_check( nf90_put_var(ncid, fcstvar_varid, Observations_f(1:N_obsf)%fcstvar) ) + call nc4_check( nf90_put_var(ncid, ana_varid, Observations_f(1:N_obsf)%ana) ) + call nc4_check( nf90_put_var(ncid, anavar_varid, Observations_f(1:N_obsf)%anavar) ) deallocate(assim_int) @@ -1943,24 +1945,24 @@ subroutine write_ObsFcstAna_nc4(fname, date_time, exp_id, file_tag, N_obsf, & species_assim_int = 0 where (obs_param(1:N_obs_param)%assim) species_assim_int = 1 - call check( nf90_put_var(ncid, species_id_varid, obs_param(1:N_obs_param)%species) ) - call check( nf90_put_var(ncid, species_assim_varid, species_assim_int) ) + call nc4_check( nf90_put_var(ncid, species_id_varid, obs_param(1:N_obs_param)%species) ) + call nc4_check( nf90_put_var(ncid, species_assim_varid, species_assim_int) ) deallocate(species_assim_int) end if - call check( nf90_close(ncid) ) + call nc4_check( nf90_close(ncid) ) contains - subroutine check(status) - integer, intent ( in) :: status + subroutine nc4_check(status) + integer, intent(in) :: status - if(status /= nf90_noerr) then - print *, trim(nf90_strerror(status)) - stop 1 + if (status /= nf90_noerr) then + err_msg = 'NetCDF error in ' // Iam // ': ' // trim(nf90_strerror(status)) + call ldas_abort(LDAS_GENERIC_ERROR, Iam, err_msg) end if - end subroutine check + end subroutine nc4_check end subroutine write_ObsFcstAna_nc4 diff --git a/GEOSldas_App/util/postproc/ObsFcstAna_stats/Compare_ObsFcstAna_bin_nc4.py b/GEOSldas_App/util/postproc/ObsFcstAna_stats/Compare_ObsFcstAna_bin_nc4.py index 1ce43ccc..4f04889c 100755 --- a/GEOSldas_App/util/postproc/ObsFcstAna_stats/Compare_ObsFcstAna_bin_nc4.py +++ b/GEOSldas_App/util/postproc/ObsFcstAna_stats/Compare_ObsFcstAna_bin_nc4.py @@ -45,6 +45,14 @@ def _as_array(x): def _normalize_missing(a, fill_value=-9999.0): + """Replace fill_value sentinels with NaN for consistent comparison. + + The binary reader (read_ObsFcstAna) converts obs_obsvar/fcst/fcstvar/ana/ + anavar to NaN but leaves obs_obs, obs_lon, and obs_lat as raw -9999. + The NC4 reader uses the per-variable missing_value attribute (also -9999). + Calling this function on both sides guarantees a uniform NaN-based + comparison regardless of which fields each reader normalizes internally. + """ out = np.asarray(a, dtype=np.float64).copy() out[np.isclose(out, fill_value)] = np.nan return out From 66f0f77cc0c0fd172a3dc9e624c5c159c10cd786 Mon Sep 17 00:00:00 2001 From: amfox37 Date: Thu, 12 Mar 2026 14:07:54 -0600 Subject: [PATCH 06/40] Refine ObsFcstAna nc4 metadata and species fields --- CHANGELOG.md | 2 + .../GEOS_LandAssimGridComp.F90 | 3 +- .../clsm_ensupd_enkf_update.F90 | 48 +++++++++++-------- 3 files changed, 32 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a6f7f0d8..67eebd72 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- Added optional NetCDF4 output mode for ObsFcstAna, including NetCDF metadata and runtime context. + ### Changed ### Fixed diff --git a/GEOSlandassim_GridComp/GEOS_LandAssimGridComp.F90 b/GEOSlandassim_GridComp/GEOS_LandAssimGridComp.F90 index 6447610a..1196e2b4 100644 --- a/GEOSlandassim_GridComp/GEOS_LandAssimGridComp.F90 +++ b/GEOSlandassim_GridComp/GEOS_LandAssimGridComp.F90 @@ -1957,8 +1957,7 @@ subroutine RUN ( GC, IMPORT, EXPORT, CLOCK, RC ) N_catl, tile_coord_l, & N_catf, tile_coord_rf, tcinternal%pgrid_g, & N_catl_vec, low_ind, rf2l, & - update_type, xcompact, ycompact, fcsterr_inflation_fac, & - LandAssimDTstep, & + update_type, LandAssimDTstep, & obs_param, & met_force, lai, & cat_param, cat_progn, mwRTM_param, & diff --git a/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 b/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 index c9ffece9..d6fe8ccc 100644 --- a/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 +++ b/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 @@ -1516,8 +1516,7 @@ end subroutine apply_enkf_increments subroutine output_ObsFcstAna(date_time, exp_id, & N_obsl, Observations_l, N_obs_param, obs_param, out_ObsFcstAna_mode, & - update_type, xcompact, ycompact, fcsterr_inflation_fac, dtstep_assim, & - grid_name, rf2f) + update_type, dtstep_assim, grid_name, rf2f) ! obs space output: observations, obs space forecast, obs space analysis, and ! associated error variances @@ -1534,8 +1533,6 @@ subroutine output_ObsFcstAna(date_time, exp_id, & type(obs_param_type), dimension(N_obs_param), intent(in) :: obs_param integer, intent(in) :: out_ObsFcstAna_mode integer, intent(in) :: update_type - real, intent(in) :: xcompact, ycompact - real, intent(in) :: fcsterr_inflation_fac integer, intent(in) :: dtstep_assim character(*), intent(in) :: grid_name @@ -1776,8 +1773,7 @@ subroutine output_ObsFcstAna(date_time, exp_id, & call write_ObsFcstAna_nc4(fname, date_time, exp_id, file_tag, N_obsf, & Observations_f, N_obs_param, obs_param, out_ObsFcstAna_format, & - update_type, xcompact, ycompact, fcsterr_inflation_fac, & - dtstep_assim, grid_name) + update_type, dtstep_assim, grid_name) end if @@ -1790,8 +1786,7 @@ end subroutine output_ObsFcstAna subroutine write_ObsFcstAna_nc4(fname, date_time, exp_id, file_tag, N_obsf, & Observations_f, N_obs_param, obs_param, out_ObsFcstAna_format, & - update_type, xcompact, ycompact, fcsterr_inflation_fac, & - dtstep_assim, grid_name) + update_type, dtstep_assim, grid_name) use netcdf implicit none @@ -1806,9 +1801,6 @@ subroutine write_ObsFcstAna_nc4(fname, date_time, exp_id, file_tag, N_obsf, & type(obs_param_type), dimension(N_obs_param), intent(in) :: obs_param character(*), intent(in) :: out_ObsFcstAna_format integer, intent(in) :: update_type - real, intent(in) :: xcompact - real, intent(in) :: ycompact - real, intent(in) :: fcsterr_inflation_fac integer, intent(in) :: dtstep_assim character(*), intent(in) :: grid_name @@ -1821,9 +1813,11 @@ subroutine write_ObsFcstAna_nc4(fname, date_time, exp_id, file_tag, N_obsf, & integer :: fcst_varid, fcstvar_varid integer :: ana_varid, anavar_varid integer :: species_id_varid, species_assim_varid + integer :: species_scale_varid, species_getinnov_varid, species_errstd_varid integer, dimension(:), allocatable :: assim_int integer, dimension(:), allocatable :: species_assim_int + integer, dimension(:), allocatable :: species_scale_int, species_getinnov_int character(len=40) :: attr_name integer :: i character(len=*), parameter :: Iam = 'write_ObsFcstAna_nc4' @@ -1848,6 +1842,9 @@ subroutine write_ObsFcstAna_nc4(fname, date_time, exp_id, file_tag, N_obsf, & call nc4_check( nf90_def_var(ncid, 'species_id', NF90_INT, [nspecies_dimid], species_id_varid) ) call nc4_check( nf90_def_var(ncid, 'species_assim', NF90_INT, [nspecies_dimid], species_assim_varid) ) + call nc4_check( nf90_def_var(ncid, 'species_scale', NF90_INT, [nspecies_dimid], species_scale_varid) ) + call nc4_check( nf90_def_var(ncid, 'species_getinnov', NF90_INT, [nspecies_dimid], species_getinnov_varid) ) + call nc4_check( nf90_def_var(ncid, 'species_errstd', NF90_FLOAT, [nspecies_dimid], species_errstd_varid) ) call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, 'schema_version', 'ObsFcstAna_nc4_v1') ) call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, 'N_obsf', N_obsf) ) @@ -1864,9 +1861,6 @@ subroutine write_ObsFcstAna_nc4(fname, date_time, exp_id, file_tag, N_obsf, & call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, 'file_tag', trim(file_tag)) ) call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, 'out_ObsFcstAna_format', trim(out_ObsFcstAna_format)) ) call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, 'update_type', update_type) ) - call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, 'xcompact', xcompact) ) - call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, 'ycompact', ycompact) ) - call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, 'fcsterr_inflation_fac', fcsterr_inflation_fac) ) call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, 'dtstep_assim', dtstep_assim) ) call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, 'grid_name', trim(grid_name)) ) @@ -1914,6 +1908,12 @@ subroutine write_ObsFcstAna_nc4(fname, date_time, exp_id, file_tag, N_obsf, & call nc4_check( nf90_put_att(ncid, species_id_varid, 'units', '1') ) call nc4_check( nf90_put_att(ncid, species_assim_varid, 'long_name', 'species assimilation flag') ) call nc4_check( nf90_put_att(ncid, species_assim_varid, 'units', '1') ) + call nc4_check( nf90_put_att(ncid, species_scale_varid, 'long_name', 'species scaling flag') ) + call nc4_check( nf90_put_att(ncid, species_scale_varid, 'units', '1') ) + call nc4_check( nf90_put_att(ncid, species_getinnov_varid, 'long_name', 'species innovation-output flag') ) + call nc4_check( nf90_put_att(ncid, species_getinnov_varid, 'units', '1') ) + call nc4_check( nf90_put_att(ncid, species_errstd_varid, 'long_name', 'species default observation error standard deviation') ) + call nc4_check( nf90_put_att(ncid, species_errstd_varid, 'units', 'species-dependent') ) call nc4_check( nf90_enddef(ncid) ) @@ -1942,13 +1942,24 @@ subroutine write_ObsFcstAna_nc4(fname, date_time, exp_id, file_tag, N_obsf, & if (N_obs_param > 0) then allocate(species_assim_int(N_obs_param)) + allocate(species_scale_int(N_obs_param)) + allocate(species_getinnov_int(N_obs_param)) species_assim_int = 0 + species_scale_int = 0 + species_getinnov_int = 0 where (obs_param(1:N_obs_param)%assim) species_assim_int = 1 + where (obs_param(1:N_obs_param)%scale) species_scale_int = 1 + where (obs_param(1:N_obs_param)%getinnov) species_getinnov_int = 1 - call nc4_check( nf90_put_var(ncid, species_id_varid, obs_param(1:N_obs_param)%species) ) - call nc4_check( nf90_put_var(ncid, species_assim_varid, species_assim_int) ) + call nc4_check( nf90_put_var(ncid, species_id_varid, obs_param(1:N_obs_param)%species) ) + call nc4_check( nf90_put_var(ncid, species_assim_varid, species_assim_int) ) + call nc4_check( nf90_put_var(ncid, species_scale_varid, species_scale_int) ) + call nc4_check( nf90_put_var(ncid, species_getinnov_varid, species_getinnov_int) ) + call nc4_check( nf90_put_var(ncid, species_errstd_varid, obs_param(1:N_obs_param)%errstd) ) deallocate(species_assim_int) + deallocate(species_scale_int) + deallocate(species_getinnov_int) end if @@ -1975,7 +1986,7 @@ subroutine output_ObsFcstAna_wrapper( out_ObsFcstAna, & N_catl, tile_coord_l, & N_catf, tile_coord_f, pert_grid_g, & N_catl_vec, low_ind, f2l, & - update_type, xcompact, ycompact, fcsterr_inflation_fac, dtstep_assim, & + update_type, dtstep_assim, & obs_param, & met_force, lai, cat_param, cat_progn, mwRTM_param, & Observations_l, rf2f ) @@ -2000,7 +2011,6 @@ subroutine output_ObsFcstAna_wrapper( out_ObsFcstAna, & integer, intent(in) :: N_obsl, N_obs_param, N_ens, N_catl, N_catf integer, intent(in) :: update_type, dtstep_assim - real, intent(in) :: xcompact, ycompact, fcsterr_inflation_fac type(tile_coord_type), dimension(:), pointer :: tile_coord_l ! input type(tile_coord_type), dimension(:), pointer :: tile_coord_f ! input @@ -2067,7 +2077,7 @@ subroutine output_ObsFcstAna_wrapper( out_ObsFcstAna, & call output_ObsFcstAna( date_time, exp_id, N_obsl, & Observations_l(1:N_obsl), N_obs_param, obs_param, out_ObsFcstAna_mode, & - update_type, xcompact, ycompact, fcsterr_inflation_fac, dtstep_assim, & + update_type, dtstep_assim, & trim(pert_grid_g%gridtype), rf2f=rf2f ) end if From fc2b58d73f602707171e8889d6a90406aa998752 Mon Sep 17 00:00:00 2001 From: amfox37 Date: Thu, 12 Mar 2026 14:42:45 -0600 Subject: [PATCH 07/40] ObsFcstAna nc4: keep assim per-observation; move species flags to global attrs --- .../clsm_ensupd_enkf_update.F90 | 47 +++++-------------- 1 file changed, 11 insertions(+), 36 deletions(-) diff --git a/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 b/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 index d6fe8ccc..3caf3512 100644 --- a/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 +++ b/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 @@ -1812,12 +1812,9 @@ subroutine write_ObsFcstAna_nc4(fname, date_time, exp_id, file_tag, N_obsf, & integer :: obs_varid, obsvar_varid integer :: fcst_varid, fcstvar_varid integer :: ana_varid, anavar_varid - integer :: species_id_varid, species_assim_varid - integer :: species_scale_varid, species_getinnov_varid, species_errstd_varid + integer :: species_id_varid integer, dimension(:), allocatable :: assim_int - integer, dimension(:), allocatable :: species_assim_int - integer, dimension(:), allocatable :: species_scale_int, species_getinnov_int character(len=40) :: attr_name integer :: i character(len=*), parameter :: Iam = 'write_ObsFcstAna_nc4' @@ -1840,11 +1837,7 @@ subroutine write_ObsFcstAna_nc4(fname, date_time, exp_id, file_tag, N_obsf, & call nc4_check( nf90_def_var(ncid, 'ana', NF90_FLOAT, [nobs_dimid], ana_varid) ) call nc4_check( nf90_def_var(ncid, 'anavar', NF90_FLOAT, [nobs_dimid], anavar_varid) ) - call nc4_check( nf90_def_var(ncid, 'species_id', NF90_INT, [nspecies_dimid], species_id_varid) ) - call nc4_check( nf90_def_var(ncid, 'species_assim', NF90_INT, [nspecies_dimid], species_assim_varid) ) - call nc4_check( nf90_def_var(ncid, 'species_scale', NF90_INT, [nspecies_dimid], species_scale_varid) ) - call nc4_check( nf90_def_var(ncid, 'species_getinnov', NF90_INT, [nspecies_dimid], species_getinnov_varid) ) - call nc4_check( nf90_def_var(ncid, 'species_errstd', NF90_FLOAT, [nspecies_dimid], species_errstd_varid) ) + call nc4_check( nf90_def_var(ncid, 'species_id', NF90_INT, [nspecies_dimid], species_id_varid) ) call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, 'schema_version', 'ObsFcstAna_nc4_v1') ) call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, 'N_obsf', N_obsf) ) @@ -1871,6 +1864,14 @@ subroutine write_ObsFcstAna_nc4(fname, date_time, exp_id, file_tag, N_obsf, & call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, trim(attr_name), trim(obs_param(i)%varname)) ) write(attr_name, '(A,I4.4,A)') 'species_', obs_param(i)%species, '_units' call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, trim(attr_name), trim(obs_param(i)%units)) ) + write(attr_name, '(A,I4.4,A)') 'species_', obs_param(i)%species, '_assim' + call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, trim(attr_name), merge(1, 0, obs_param(i)%assim)) ) + write(attr_name, '(A,I4.4,A)') 'species_', obs_param(i)%species, '_scale' + call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, trim(attr_name), merge(1, 0, obs_param(i)%scale)) ) + write(attr_name, '(A,I4.4,A)') 'species_', obs_param(i)%species, '_getinnov' + call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, trim(attr_name), merge(1, 0, obs_param(i)%getinnov)) ) + write(attr_name, '(A,I4.4,A)') 'species_', obs_param(i)%species, '_errstd' + call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, trim(attr_name), obs_param(i)%errstd) ) end do call nc4_check( nf90_put_att(ncid, assim_varid, 'long_name', 'assimilation flag') ) @@ -1906,14 +1907,6 @@ subroutine write_ObsFcstAna_nc4(fname, date_time, exp_id, file_tag, N_obsf, & call nc4_check( nf90_put_att(ncid, species_id_varid, 'long_name', 'observation species identifier') ) call nc4_check( nf90_put_att(ncid, species_id_varid, 'units', '1') ) - call nc4_check( nf90_put_att(ncid, species_assim_varid, 'long_name', 'species assimilation flag') ) - call nc4_check( nf90_put_att(ncid, species_assim_varid, 'units', '1') ) - call nc4_check( nf90_put_att(ncid, species_scale_varid, 'long_name', 'species scaling flag') ) - call nc4_check( nf90_put_att(ncid, species_scale_varid, 'units', '1') ) - call nc4_check( nf90_put_att(ncid, species_getinnov_varid, 'long_name', 'species innovation-output flag') ) - call nc4_check( nf90_put_att(ncid, species_getinnov_varid, 'units', '1') ) - call nc4_check( nf90_put_att(ncid, species_errstd_varid, 'long_name', 'species default observation error standard deviation') ) - call nc4_check( nf90_put_att(ncid, species_errstd_varid, 'units', 'species-dependent') ) call nc4_check( nf90_enddef(ncid) ) @@ -1941,25 +1934,7 @@ subroutine write_ObsFcstAna_nc4(fname, date_time, exp_id, file_tag, N_obsf, & if (N_obs_param > 0) then - allocate(species_assim_int(N_obs_param)) - allocate(species_scale_int(N_obs_param)) - allocate(species_getinnov_int(N_obs_param)) - species_assim_int = 0 - species_scale_int = 0 - species_getinnov_int = 0 - where (obs_param(1:N_obs_param)%assim) species_assim_int = 1 - where (obs_param(1:N_obs_param)%scale) species_scale_int = 1 - where (obs_param(1:N_obs_param)%getinnov) species_getinnov_int = 1 - - call nc4_check( nf90_put_var(ncid, species_id_varid, obs_param(1:N_obs_param)%species) ) - call nc4_check( nf90_put_var(ncid, species_assim_varid, species_assim_int) ) - call nc4_check( nf90_put_var(ncid, species_scale_varid, species_scale_int) ) - call nc4_check( nf90_put_var(ncid, species_getinnov_varid, species_getinnov_int) ) - call nc4_check( nf90_put_var(ncid, species_errstd_varid, obs_param(1:N_obs_param)%errstd) ) - - deallocate(species_assim_int) - deallocate(species_scale_int) - deallocate(species_getinnov_int) + call nc4_check( nf90_put_var(ncid, species_id_varid, obs_param(1:N_obs_param)%species) ) end if From e584f2cd6ff44d3eb5ee6d4192741cb4f28a30f5 Mon Sep 17 00:00:00 2001 From: amfox37 Date: Thu, 12 Mar 2026 15:24:28 -0600 Subject: [PATCH 08/40] Expand ObsFcstAna nc4 species table and simplify global species attrs --- .../clsm_ensupd_enkf_update.F90 | 83 +++++++++++++++---- 1 file changed, 68 insertions(+), 15 deletions(-) diff --git a/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 b/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 index 3caf3512..96f1202d 100644 --- a/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 +++ b/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 @@ -1807,14 +1807,22 @@ subroutine write_ObsFcstAna_nc4(fname, date_time, exp_id, file_tag, N_obsf, & integer :: ncid integer :: nobs_dimid integer :: nspecies_dimid + integer :: species_strlen_dimid integer :: assim_varid, species_varid, tilenum_varid integer :: lon_varid, lat_varid integer :: obs_varid, obsvar_varid integer :: fcst_varid, fcstvar_varid integer :: ana_varid, anavar_varid - integer :: species_id_varid + integer :: species_id_varid, species_assim_varid, species_scale_varid + integer :: species_getinnov_varid, species_errstd_varid + integer :: species_varname_varid, species_units_varid, species_descr_varid integer, dimension(:), allocatable :: assim_int + integer, dimension(:), allocatable :: species_assim_int, species_scale_int + integer, dimension(:), allocatable :: species_getinnov_int + real, dimension(:), allocatable :: species_errstd_r4 + character(len=40), dimension(:), allocatable :: species_varname_c40, species_units_c40 + character(len=40), dimension(:), allocatable :: species_descr_c40 character(len=40) :: attr_name integer :: i character(len=*), parameter :: Iam = 'write_ObsFcstAna_nc4' @@ -1824,6 +1832,7 @@ subroutine write_ObsFcstAna_nc4(fname, date_time, exp_id, file_tag, N_obsf, & call nc4_check( nf90_def_dim(ncid, 'nobs', NF90_UNLIMITED, nobs_dimid) ) call nc4_check( nf90_def_dim(ncid, 'n_species', N_obs_param, nspecies_dimid) ) + call nc4_check( nf90_def_dim(ncid, 'species_strlen', 40, species_strlen_dimid) ) call nc4_check( nf90_def_var(ncid, 'assim', NF90_INT, [nobs_dimid], assim_varid) ) call nc4_check( nf90_def_var(ncid, 'species', NF90_INT, [nobs_dimid], species_varid) ) @@ -1837,7 +1846,14 @@ subroutine write_ObsFcstAna_nc4(fname, date_time, exp_id, file_tag, N_obsf, & call nc4_check( nf90_def_var(ncid, 'ana', NF90_FLOAT, [nobs_dimid], ana_varid) ) call nc4_check( nf90_def_var(ncid, 'anavar', NF90_FLOAT, [nobs_dimid], anavar_varid) ) - call nc4_check( nf90_def_var(ncid, 'species_id', NF90_INT, [nspecies_dimid], species_id_varid) ) + call nc4_check( nf90_def_var(ncid, 'species_id', NF90_INT, [nspecies_dimid], species_id_varid) ) + call nc4_check( nf90_def_var(ncid, 'species_assim', NF90_INT, [nspecies_dimid], species_assim_varid) ) + call nc4_check( nf90_def_var(ncid, 'species_scale', NF90_INT, [nspecies_dimid], species_scale_varid) ) + call nc4_check( nf90_def_var(ncid, 'species_getinnov', NF90_INT, [nspecies_dimid], species_getinnov_varid) ) + call nc4_check( nf90_def_var(ncid, 'species_errstd', NF90_FLOAT, [nspecies_dimid], species_errstd_varid) ) + call nc4_check( nf90_def_var(ncid, 'species_varname', NF90_CHAR, [species_strlen_dimid, nspecies_dimid], species_varname_varid) ) + call nc4_check( nf90_def_var(ncid, 'species_units', NF90_CHAR, [species_strlen_dimid, nspecies_dimid], species_units_varid) ) + call nc4_check( nf90_def_var(ncid, 'species_descr', NF90_CHAR, [species_strlen_dimid, nspecies_dimid], species_descr_varid) ) call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, 'schema_version', 'ObsFcstAna_nc4_v1') ) call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, 'N_obsf', N_obsf) ) @@ -1860,18 +1876,6 @@ subroutine write_ObsFcstAna_nc4(fname, date_time, exp_id, file_tag, N_obsf, & do i=1,N_obs_param write(attr_name, '(A,I4.4,A)') 'species_', obs_param(i)%species, '_descr' call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, trim(attr_name), trim(obs_param(i)%descr)) ) - write(attr_name, '(A,I4.4,A)') 'species_', obs_param(i)%species, '_varname' - call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, trim(attr_name), trim(obs_param(i)%varname)) ) - write(attr_name, '(A,I4.4,A)') 'species_', obs_param(i)%species, '_units' - call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, trim(attr_name), trim(obs_param(i)%units)) ) - write(attr_name, '(A,I4.4,A)') 'species_', obs_param(i)%species, '_assim' - call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, trim(attr_name), merge(1, 0, obs_param(i)%assim)) ) - write(attr_name, '(A,I4.4,A)') 'species_', obs_param(i)%species, '_scale' - call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, trim(attr_name), merge(1, 0, obs_param(i)%scale)) ) - write(attr_name, '(A,I4.4,A)') 'species_', obs_param(i)%species, '_getinnov' - call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, trim(attr_name), merge(1, 0, obs_param(i)%getinnov)) ) - write(attr_name, '(A,I4.4,A)') 'species_', obs_param(i)%species, '_errstd' - call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, trim(attr_name), obs_param(i)%errstd) ) end do call nc4_check( nf90_put_att(ncid, assim_varid, 'long_name', 'assimilation flag') ) @@ -1907,6 +1911,17 @@ subroutine write_ObsFcstAna_nc4(fname, date_time, exp_id, file_tag, N_obsf, & call nc4_check( nf90_put_att(ncid, species_id_varid, 'long_name', 'observation species identifier') ) call nc4_check( nf90_put_att(ncid, species_id_varid, 'units', '1') ) + call nc4_check( nf90_put_att(ncid, species_assim_varid, 'long_name', 'species assimilation flag') ) + call nc4_check( nf90_put_att(ncid, species_assim_varid, 'units', '1') ) + call nc4_check( nf90_put_att(ncid, species_scale_varid, 'long_name', 'species scaling flag') ) + call nc4_check( nf90_put_att(ncid, species_scale_varid, 'units', '1') ) + call nc4_check( nf90_put_att(ncid, species_getinnov_varid, 'long_name', 'species innovation-output flag') ) + call nc4_check( nf90_put_att(ncid, species_getinnov_varid, 'units', '1') ) + call nc4_check( nf90_put_att(ncid, species_errstd_varid, 'long_name', 'species default observation error standard deviation') ) + call nc4_check( nf90_put_att(ncid, species_errstd_varid, 'units', 'species-dependent') ) + call nc4_check( nf90_put_att(ncid, species_varname_varid, 'long_name', 'species model variable name') ) + call nc4_check( nf90_put_att(ncid, species_units_varid, 'long_name', 'species observation units') ) + call nc4_check( nf90_put_att(ncid, species_descr_varid, 'long_name', 'species description') ) call nc4_check( nf90_enddef(ncid) ) @@ -1934,7 +1949,45 @@ subroutine write_ObsFcstAna_nc4(fname, date_time, exp_id, file_tag, N_obsf, & if (N_obs_param > 0) then - call nc4_check( nf90_put_var(ncid, species_id_varid, obs_param(1:N_obs_param)%species) ) + allocate(species_assim_int(N_obs_param)) + allocate(species_scale_int(N_obs_param)) + allocate(species_getinnov_int(N_obs_param)) + allocate(species_errstd_r4(N_obs_param)) + allocate(species_varname_c40(N_obs_param)) + allocate(species_units_c40(N_obs_param)) + allocate(species_descr_c40(N_obs_param)) + + species_assim_int = 0 + species_scale_int = 0 + species_getinnov_int = 0 + + where (obs_param(1:N_obs_param)%assim) species_assim_int = 1 + where (obs_param(1:N_obs_param)%scale) species_scale_int = 1 + where (obs_param(1:N_obs_param)%getinnov) species_getinnov_int = 1 + + do i=1,N_obs_param + species_errstd_r4(i) = obs_param(i)%errstd + species_varname_c40(i) = obs_param(i)%varname + species_units_c40(i) = obs_param(i)%units + species_descr_c40(i) = obs_param(i)%descr + end do + + call nc4_check( nf90_put_var(ncid, species_id_varid, obs_param(1:N_obs_param)%species) ) + call nc4_check( nf90_put_var(ncid, species_assim_varid, species_assim_int) ) + call nc4_check( nf90_put_var(ncid, species_scale_varid, species_scale_int) ) + call nc4_check( nf90_put_var(ncid, species_getinnov_varid, species_getinnov_int) ) + call nc4_check( nf90_put_var(ncid, species_errstd_varid, species_errstd_r4) ) + call nc4_check( nf90_put_var(ncid, species_varname_varid, species_varname_c40) ) + call nc4_check( nf90_put_var(ncid, species_units_varid, species_units_c40) ) + call nc4_check( nf90_put_var(ncid, species_descr_varid, species_descr_c40) ) + + deallocate(species_assim_int) + deallocate(species_scale_int) + deallocate(species_getinnov_int) + deallocate(species_errstd_r4) + deallocate(species_varname_c40) + deallocate(species_units_c40) + deallocate(species_descr_c40) end if From 5f75581ad6ecea0a5ba784feee15cd436d028a77 Mon Sep 17 00:00:00 2001 From: amfox37 Date: Thu, 12 Mar 2026 17:39:54 -0600 Subject: [PATCH 09/40] Remove ObsFcstAna bin-vs-nc4 utility from GridComp repo --- .../Compare_ObsFcstAna_bin_nc4.py | 221 ------------------ 1 file changed, 221 deletions(-) delete mode 100755 GEOSldas_App/util/postproc/ObsFcstAna_stats/Compare_ObsFcstAna_bin_nc4.py diff --git a/GEOSldas_App/util/postproc/ObsFcstAna_stats/Compare_ObsFcstAna_bin_nc4.py b/GEOSldas_App/util/postproc/ObsFcstAna_stats/Compare_ObsFcstAna_bin_nc4.py deleted file mode 100755 index 4f04889c..00000000 --- a/GEOSldas_App/util/postproc/ObsFcstAna_stats/Compare_ObsFcstAna_bin_nc4.py +++ /dev/null @@ -1,221 +0,0 @@ -#!/usr/bin/env python3 - -""" -Find and compare matching ObsFcstAna .bin and .nc4 files. - -Usage examples: - ./Compare_ObsFcstAna_bin_nc4.py /path/to/ana/ens_avg - ./Compare_ObsFcstAna_bin_nc4.py /path/to/ana/ens_avg --max-pairs 5 -""" - -import argparse -import sys -from pathlib import Path - -import numpy as np -from netCDF4 import Dataset - -THIS_DIR = Path(__file__).resolve().parent -sys.path.append(str((THIS_DIR / '../../shared/python').resolve())) -from read_GEOSldas import read_ObsFcstAna # noqa: E402 - - -INT_MAP = [ - ('obs_assim', 'assim'), - ('obs_species', 'species'), - ('obs_tilenum', 'tilenum'), -] - -FLOAT_MAP = [ - ('obs_lon', 'lon'), - ('obs_lat', 'lat'), - ('obs_obs', 'obs'), - ('obs_obsvar', 'obsvar'), - ('obs_fcst', 'fcst'), - ('obs_fcstvar', 'fcstvar'), - ('obs_ana', 'ana'), - ('obs_anavar', 'anavar'), -] - -TIME_KEYS = ['year', 'month', 'day', 'hour', 'minute', 'second', 'dofyr', 'pentad'] - - -def _as_array(x): - return np.asarray(x) - - -def _normalize_missing(a, fill_value=-9999.0): - """Replace fill_value sentinels with NaN for consistent comparison. - - The binary reader (read_ObsFcstAna) converts obs_obsvar/fcst/fcstvar/ana/ - anavar to NaN but leaves obs_obs, obs_lon, and obs_lat as raw -9999. - The NC4 reader uses the per-variable missing_value attribute (also -9999). - Calling this function on both sides guarantees a uniform NaN-based - comparison regardless of which fields each reader normalizes internally. - """ - out = np.asarray(a, dtype=np.float64).copy() - out[np.isclose(out, fill_value)] = np.nan - return out - - -def read_obsfcstana_nc4(fname): - out = {} - date_time = {} - - with Dataset(fname, mode='r') as nc: - for bkey, nkey in INT_MAP: - out[bkey] = _as_array(nc.variables[nkey][:]).astype(np.int32) - - for bkey, nkey in FLOAT_MAP: - v = _as_array(nc.variables[nkey][:]).astype(np.float64) - fillv = getattr(nc.variables[nkey], 'missing_value', -9999.0) - out[bkey] = _normalize_missing(v, fill_value=float(fillv)) - - for key in TIME_KEYS: - date_time[key] = int(getattr(nc, key, -9999)) - - out['N_obsf_attr'] = int(getattr(nc, 'N_obsf', out['obs_species'].size)) - - return date_time, out - - -def compare_arrays_int(a, b): - if a.shape != b.shape: - return {'shape_match': False, 'n_bad': max(a.size, b.size), 'max_abs': np.nan} - bad = (a != b) - return { - 'shape_match': True, - 'n_bad': int(np.count_nonzero(bad)), - 'max_abs': int(np.max(np.abs(a.astype(np.int64) - b.astype(np.int64)))) if a.size > 0 else 0, - } - - -def compare_arrays_float(a, b, rtol, atol): - if a.shape != b.shape: - return {'shape_match': False, 'n_bad': max(a.size, b.size), 'max_abs': np.nan} - - bad = ~np.isclose(a, b, rtol=rtol, atol=atol, equal_nan=True) - n_bad = int(np.count_nonzero(bad)) - - diff = np.abs(a - b) - max_abs = float(np.nanmax(diff)) if diff.size > 0 else 0.0 - - return {'shape_match': True, 'n_bad': n_bad, 'max_abs': max_abs} - - -def find_pairs(root): - bin_files = sorted(root.rglob('*.ldas_ObsFcstAna.*z.bin')) - nc4_files = sorted(root.rglob('*.ldas_ObsFcstAna.*z.nc4')) - - pairs = [] - missing_nc4 = [] - for b in bin_files: - n = b.with_suffix('.nc4') - if n.exists(): - pairs.append((b, n)) - else: - missing_nc4.append(b) - - missing_bin = [] - for n in nc4_files: - b = n.with_suffix('.bin') - if not b.exists(): - missing_bin.append(n) - - return pairs, missing_nc4, missing_bin - - -def compare_one(bin_file, nc4_file, rtol, atol): - b = read_ObsFcstAna(str(bin_file)) - t_nc4, n = read_obsfcstana_nc4(str(nc4_file)) - - issues = [] - n_bad_total = 0 - - # Time/header checks - t_bin = b['date_time'] - for key in TIME_KEYS: - key_bin = 'min' if key == 'minute' else ('sec' if key == 'second' else key) - vb = int(t_bin.get(key_bin, -9999)) - vn = int(t_nc4.get(key, -9999)) - if vb != vn: - issues.append(f'time mismatch: {key} bin={vb} nc4={vn}') - - n_obs_bin = int(_as_array(b['obs_species']).size) - n_obs_nc4 = int(_as_array(n['obs_species']).size) - if n_obs_bin != n_obs_nc4: - issues.append(f'N_obs mismatch: bin={n_obs_bin} nc4={n_obs_nc4}') - if int(n.get('N_obsf_attr', n_obs_nc4)) != n_obs_nc4: - issues.append(f'N_obsf attr mismatch: attr={n.get("N_obsf_attr")} actual={n_obs_nc4}') - - # Integer fields - for bkey, _ in INT_MAP: - cb = compare_arrays_int(_as_array(b[bkey]), _as_array(n[bkey])) - if not cb['shape_match'] or cb['n_bad'] > 0: - issues.append(f'{bkey}: n_bad={cb["n_bad"]}, max_abs={cb["max_abs"]}') - n_bad_total += cb['n_bad'] - - # Float fields - for bkey, _ in FLOAT_MAP: - a_bin = _normalize_missing(_as_array(b[bkey])) - a_nc4 = _normalize_missing(_as_array(n[bkey])) - cb = compare_arrays_float(a_bin, a_nc4, rtol=rtol, atol=atol) - if not cb['shape_match'] or cb['n_bad'] > 0: - issues.append(f'{bkey}: n_bad={cb["n_bad"]}, max_abs={cb["max_abs"]:.6g}') - n_bad_total += cb['n_bad'] - - return issues, n_bad_total, n_obs_bin - - -def main(): - parser = argparse.ArgumentParser(description='Compare GEOSldas ObsFcstAna .bin and .nc4 files.') - parser.add_argument('root', nargs='?', default='.', help='Root directory to scan recursively.') - parser.add_argument('--rtol', type=float, default=0.0, help='Relative tolerance for float compare.') - parser.add_argument('--atol', type=float, default=1e-6, help='Absolute tolerance for float compare.') - parser.add_argument('--max-pairs', type=int, default=0, help='Max number of pairs to compare (0=all).') - args = parser.parse_args() - - root = Path(args.root).expanduser().resolve() - pairs, missing_nc4, missing_bin = find_pairs(root) - - print(f'scan_root: {root}') - print(f'found pairs: {len(pairs)}') - print(f'missing nc4 for bin: {len(missing_nc4)}') - print(f'missing bin for nc4: {len(missing_bin)}') - - if len(missing_nc4) > 0: - print('example missing nc4:') - for p in missing_nc4[:5]: - print(f' {p}') - - if len(missing_bin) > 0: - print('example missing bin:') - for p in missing_bin[:5]: - print(f' {p}') - - if not pairs: - return 1 - - n_compare = len(pairs) if args.max_pairs <= 0 else min(args.max_pairs, len(pairs)) - - n_pass = 0 - n_fail = 0 - - for i, (fbin, fnc4) in enumerate(pairs[:n_compare], start=1): - issues, n_bad, n_obs = compare_one(fbin, fnc4, rtol=args.rtol, atol=args.atol) - rel = fbin.relative_to(root) - if len(issues) == 0: - print(f'[{i}/{n_compare}] PASS {rel} (N_obs={n_obs})') - n_pass += 1 - else: - print(f'[{i}/{n_compare}] FAIL {rel} (N_obs={n_obs}, n_bad={n_bad})') - for msg in issues: - print(f' - {msg}') - n_fail += 1 - - print(f'summary: pass={n_pass}, fail={n_fail}, compared={n_compare}') - return 0 if n_fail == 0 else 2 - - -if __name__ == '__main__': - raise SystemExit(main()) From 0782a9a1d04b8455449c8f776ef2e6cb08ca4a8d Mon Sep 17 00:00:00 2001 From: amfox37 Date: Thu, 12 Mar 2026 17:44:49 -0600 Subject: [PATCH 10/40] Remove MKL build-system changes from netcdf_obsfcstana PR --- GEOSlandpert_GridComp/CMakeLists.txt | 5 ++--- GEOSlandpert_GridComp/random_fields.F90 | 7 +------ 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/GEOSlandpert_GridComp/CMakeLists.txt b/GEOSlandpert_GridComp/CMakeLists.txt index affe50a3..9930cab5 100644 --- a/GEOSlandpert_GridComp/CMakeLists.txt +++ b/GEOSlandpert_GridComp/CMakeLists.txt @@ -11,7 +11,6 @@ esma_add_library (${this} DEPENDENCIES GEOS_LdasShared GEOSens_GridComp GEOSland_GridComp MAPL ${MKL_LIBRARIES} INCLUDES ${INC_ESMF} ${MKL_INCLUDE_DIRS}) -# Enable MKL paths only when MKL was detected and the compiler supports it. -if (MKL_FOUND AND NOT CMAKE_Fortran_COMPILER_ID MATCHES "NAG") - target_compile_definitions(${this} PRIVATE MKL_AVAILABLE) +if (NOT CMAKE_Fortran_COMPILER_ID MATCHES "NAG") + target_compile_definitions(${this} PRIVATE MKL_AVAILABLE) endif () diff --git a/GEOSlandpert_GridComp/random_fields.F90 b/GEOSlandpert_GridComp/random_fields.F90 index 0b37d6a7..3ed12e69 100644 --- a/GEOSlandpert_GridComp/random_fields.F90 +++ b/GEOSlandpert_GridComp/random_fields.F90 @@ -311,13 +311,11 @@ subroutine sqrt_gauss_spectrum_2d(this, lx, ly, dx, dy) ! assemble spectrum in "wrap-around" order i1 = 1 i2 = this%N_x_fft -#ifdef MKL_AVAILABLE if (this%comm /= MPI_COMM_NULL) then call MPI_COMM_Rank(this%node_comm, rank, ierror) i1 = sum(this%dim1_counts(1:rank)) + 1 i2 = sum(this%dim1_counts(1:rank+1)) endif -#endif do j=1,this%N_y_fft this%field1_fft(i1:i2,j) = fac*exp(-.25*(lx2kx2(i1:i2)+ly2ky2(j))) @@ -397,14 +395,13 @@ subroutine rfg2d_fft(this, rseed, rfield, rfield2, lx, ly, dx, dy) integer :: i, j integer :: N_x_fft, N_y_fft real :: N_xy_fft_real - integer :: n1, n2 #ifdef MKL_AVAILABLE integer :: status complex, allocatable :: z_inout(:) complex, pointer :: tmp_field(:,:) complex, pointer :: tmp_field_dim1(:,:) complex, pointer :: tmp_field_dim2(:,:) - integer :: npes, rank, ldim1, ldim2, ierror + integer :: n1, n2, npes, rank, ldim1, ldim2, ierror complex, pointer :: X(:) type (c_ptr) :: cptr #else @@ -627,7 +624,6 @@ subroutine quit(message) end subroutine quit -#ifdef MKL_AVAILABLE subroutine win_allocate(this, nx, ny, rc) class(random_fields), intent(inout) :: this integer, intent(in) :: nx, ny @@ -666,7 +662,6 @@ subroutine win_deallocate(this, rc) _VERIFY(status) end subroutine win_deallocate -#endif end module Random_fieldsMod From 068d9843dfadefa6395f4b327ba6cc6091fddd1f Mon Sep 17 00:00:00 2001 From: amfox37 Date: Mon, 16 Mar 2026 10:26:26 -0600 Subject: [PATCH 11/40] Consolidate ObsFcstAna file move loop for bin/nc4 outputs --- GEOSldas_App/lenkf_j_template.py | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/GEOSldas_App/lenkf_j_template.py b/GEOSldas_App/lenkf_j_template.py index 3348fb06..53bfe5fb 100644 --- a/GEOSldas_App/lenkf_j_template.py +++ b/GEOSldas_App/lenkf_j_template.py @@ -471,17 +471,7 @@ # must be done before moving HISTORY files - set ObsFcses = `ls *.ldas_ObsFcstAna.*.bin` - foreach obsfcs ( $ObsFcses ) - set ThisTime = `echo $obsfcs | rev | cut -d'.' -f2 | rev` - set TY = `echo $ThisTime | cut -c1-4` - set TM = `echo $ThisTime | cut -c5-6` - set THISDIR = $EXPDIR/output/$EXPDOMAIN/ana/ens_avg/Y${{TY}}/M${{TM}}/ - if (! -e $THISDIR ) mkdir -p $THISDIR - /bin/mv $obsfcs ${{THISDIR}}$obsfcs - end - - set ObsFcses = `ls *.ldas_ObsFcstAna.*.nc4` + set ObsFcses = `ls *.ldas_ObsFcstAna.*` foreach obsfcs ( $ObsFcses ) set ThisTime = `echo $obsfcs | rev | cut -d'.' -f2 | rev` set TY = `echo $ThisTime | cut -c1-4` From 3b1acb3a556f1b1a7107d1835143b43bb609e4fd Mon Sep 17 00:00:00 2001 From: amfox37 Date: Mon, 16 Mar 2026 11:33:55 -0600 Subject: [PATCH 12/40] Write ObsFcstAna species text metadata as netCDF4 string vars --- GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 b/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 index 96f1202d..de46aba9 100644 --- a/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 +++ b/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 @@ -1807,7 +1807,6 @@ subroutine write_ObsFcstAna_nc4(fname, date_time, exp_id, file_tag, N_obsf, & integer :: ncid integer :: nobs_dimid integer :: nspecies_dimid - integer :: species_strlen_dimid integer :: assim_varid, species_varid, tilenum_varid integer :: lon_varid, lat_varid integer :: obs_varid, obsvar_varid @@ -1832,7 +1831,6 @@ subroutine write_ObsFcstAna_nc4(fname, date_time, exp_id, file_tag, N_obsf, & call nc4_check( nf90_def_dim(ncid, 'nobs', NF90_UNLIMITED, nobs_dimid) ) call nc4_check( nf90_def_dim(ncid, 'n_species', N_obs_param, nspecies_dimid) ) - call nc4_check( nf90_def_dim(ncid, 'species_strlen', 40, species_strlen_dimid) ) call nc4_check( nf90_def_var(ncid, 'assim', NF90_INT, [nobs_dimid], assim_varid) ) call nc4_check( nf90_def_var(ncid, 'species', NF90_INT, [nobs_dimid], species_varid) ) @@ -1851,9 +1849,9 @@ subroutine write_ObsFcstAna_nc4(fname, date_time, exp_id, file_tag, N_obsf, & call nc4_check( nf90_def_var(ncid, 'species_scale', NF90_INT, [nspecies_dimid], species_scale_varid) ) call nc4_check( nf90_def_var(ncid, 'species_getinnov', NF90_INT, [nspecies_dimid], species_getinnov_varid) ) call nc4_check( nf90_def_var(ncid, 'species_errstd', NF90_FLOAT, [nspecies_dimid], species_errstd_varid) ) - call nc4_check( nf90_def_var(ncid, 'species_varname', NF90_CHAR, [species_strlen_dimid, nspecies_dimid], species_varname_varid) ) - call nc4_check( nf90_def_var(ncid, 'species_units', NF90_CHAR, [species_strlen_dimid, nspecies_dimid], species_units_varid) ) - call nc4_check( nf90_def_var(ncid, 'species_descr', NF90_CHAR, [species_strlen_dimid, nspecies_dimid], species_descr_varid) ) + call nc4_check( nf90_def_var(ncid, 'species_varname', NF90_STRING, [nspecies_dimid], species_varname_varid) ) + call nc4_check( nf90_def_var(ncid, 'species_units', NF90_STRING, [nspecies_dimid], species_units_varid) ) + call nc4_check( nf90_def_var(ncid, 'species_descr', NF90_STRING, [nspecies_dimid], species_descr_varid) ) call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, 'schema_version', 'ObsFcstAna_nc4_v1') ) call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, 'N_obsf', N_obsf) ) From 4cc2f33de1ebc25b2d65c235c88e6fc265adf9f1 Mon Sep 17 00:00:00 2001 From: amfox37 Date: Mon, 16 Mar 2026 13:02:02 -0600 Subject: [PATCH 13/40] Use 3-digit species descriptor attribute formatting --- GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 b/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 index de46aba9..0064cdef 100644 --- a/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 +++ b/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 @@ -1872,7 +1872,7 @@ subroutine write_ObsFcstAna_nc4(fname, date_time, exp_id, file_tag, N_obsf, & call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, 'grid_name', trim(grid_name)) ) do i=1,N_obs_param - write(attr_name, '(A,I4.4,A)') 'species_', obs_param(i)%species, '_descr' + write(attr_name, '(A,I0.3,A)') 'species_', obs_param(i)%species, '_descr' call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, trim(attr_name), trim(obs_param(i)%descr)) ) end do From 222549d4aef878870a4fefd2e8e0f88155eaf8b5 Mon Sep 17 00:00:00 2001 From: amfox37 Date: Mon, 16 Mar 2026 13:53:51 -0600 Subject: [PATCH 14/40] Fix ObsFcstAna netCDF string writes for species metadata --- .../clsm_ensupd_enkf_update.F90 | 22 +++++-------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 b/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 index 0064cdef..a8b04582 100644 --- a/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 +++ b/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 @@ -1820,8 +1820,6 @@ subroutine write_ObsFcstAna_nc4(fname, date_time, exp_id, file_tag, N_obsf, & integer, dimension(:), allocatable :: species_assim_int, species_scale_int integer, dimension(:), allocatable :: species_getinnov_int real, dimension(:), allocatable :: species_errstd_r4 - character(len=40), dimension(:), allocatable :: species_varname_c40, species_units_c40 - character(len=40), dimension(:), allocatable :: species_descr_c40 character(len=40) :: attr_name integer :: i character(len=*), parameter :: Iam = 'write_ObsFcstAna_nc4' @@ -1951,10 +1949,6 @@ subroutine write_ObsFcstAna_nc4(fname, date_time, exp_id, file_tag, N_obsf, & allocate(species_scale_int(N_obs_param)) allocate(species_getinnov_int(N_obs_param)) allocate(species_errstd_r4(N_obs_param)) - allocate(species_varname_c40(N_obs_param)) - allocate(species_units_c40(N_obs_param)) - allocate(species_descr_c40(N_obs_param)) - species_assim_int = 0 species_scale_int = 0 species_getinnov_int = 0 @@ -1964,10 +1958,7 @@ subroutine write_ObsFcstAna_nc4(fname, date_time, exp_id, file_tag, N_obsf, & where (obs_param(1:N_obs_param)%getinnov) species_getinnov_int = 1 do i=1,N_obs_param - species_errstd_r4(i) = obs_param(i)%errstd - species_varname_c40(i) = obs_param(i)%varname - species_units_c40(i) = obs_param(i)%units - species_descr_c40(i) = obs_param(i)%descr + species_errstd_r4(i) = obs_param(i)%errstd end do call nc4_check( nf90_put_var(ncid, species_id_varid, obs_param(1:N_obs_param)%species) ) @@ -1975,17 +1966,16 @@ subroutine write_ObsFcstAna_nc4(fname, date_time, exp_id, file_tag, N_obsf, & call nc4_check( nf90_put_var(ncid, species_scale_varid, species_scale_int) ) call nc4_check( nf90_put_var(ncid, species_getinnov_varid, species_getinnov_int) ) call nc4_check( nf90_put_var(ncid, species_errstd_varid, species_errstd_r4) ) - call nc4_check( nf90_put_var(ncid, species_varname_varid, species_varname_c40) ) - call nc4_check( nf90_put_var(ncid, species_units_varid, species_units_c40) ) - call nc4_check( nf90_put_var(ncid, species_descr_varid, species_descr_c40) ) + do i=1,N_obs_param + call nc4_check( nf90_put_var(ncid, species_varname_varid, trim(obs_param(i)%varname), start=[i], count=[1]) ) + call nc4_check( nf90_put_var(ncid, species_units_varid, trim(obs_param(i)%units), start=[i], count=[1]) ) + call nc4_check( nf90_put_var(ncid, species_descr_varid, trim(obs_param(i)%descr), start=[i], count=[1]) ) + end do deallocate(species_assim_int) deallocate(species_scale_int) deallocate(species_getinnov_int) deallocate(species_errstd_r4) - deallocate(species_varname_c40) - deallocate(species_units_c40) - deallocate(species_descr_c40) end if From 5732eb1f7b03f57fbd4cfa145ae9d294401d62cc Mon Sep 17 00:00:00 2001 From: amfox37 Date: Mon, 16 Mar 2026 15:23:06 -0600 Subject: [PATCH 15/40] Fix ObsFcstAna NF90_STRING species metadata writes --- .../clsm_ensupd_enkf_update.F90 | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 b/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 index a8b04582..e8060fa0 100644 --- a/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 +++ b/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 @@ -1820,6 +1820,8 @@ subroutine write_ObsFcstAna_nc4(fname, date_time, exp_id, file_tag, N_obsf, & integer, dimension(:), allocatable :: species_assim_int, species_scale_int integer, dimension(:), allocatable :: species_getinnov_int real, dimension(:), allocatable :: species_errstd_r4 + character(len=40), dimension(:), allocatable :: species_varname_s, species_units_s + character(len=40), dimension(:), allocatable :: species_descr_s character(len=40) :: attr_name integer :: i character(len=*), parameter :: Iam = 'write_ObsFcstAna_nc4' @@ -1949,6 +1951,9 @@ subroutine write_ObsFcstAna_nc4(fname, date_time, exp_id, file_tag, N_obsf, & allocate(species_scale_int(N_obs_param)) allocate(species_getinnov_int(N_obs_param)) allocate(species_errstd_r4(N_obs_param)) + allocate(species_varname_s(N_obs_param)) + allocate(species_units_s(N_obs_param)) + allocate(species_descr_s(N_obs_param)) species_assim_int = 0 species_scale_int = 0 species_getinnov_int = 0 @@ -1959,6 +1964,9 @@ subroutine write_ObsFcstAna_nc4(fname, date_time, exp_id, file_tag, N_obsf, & do i=1,N_obs_param species_errstd_r4(i) = obs_param(i)%errstd + species_varname_s(i) = trim(obs_param(i)%varname) + species_units_s(i) = trim(obs_param(i)%units) + species_descr_s(i) = trim(obs_param(i)%descr) end do call nc4_check( nf90_put_var(ncid, species_id_varid, obs_param(1:N_obs_param)%species) ) @@ -1966,16 +1974,17 @@ subroutine write_ObsFcstAna_nc4(fname, date_time, exp_id, file_tag, N_obsf, & call nc4_check( nf90_put_var(ncid, species_scale_varid, species_scale_int) ) call nc4_check( nf90_put_var(ncid, species_getinnov_varid, species_getinnov_int) ) call nc4_check( nf90_put_var(ncid, species_errstd_varid, species_errstd_r4) ) - do i=1,N_obs_param - call nc4_check( nf90_put_var(ncid, species_varname_varid, trim(obs_param(i)%varname), start=[i], count=[1]) ) - call nc4_check( nf90_put_var(ncid, species_units_varid, trim(obs_param(i)%units), start=[i], count=[1]) ) - call nc4_check( nf90_put_var(ncid, species_descr_varid, trim(obs_param(i)%descr), start=[i], count=[1]) ) - end do + call nc4_check( nf90_put_var(ncid, species_varname_varid, species_varname_s) ) + call nc4_check( nf90_put_var(ncid, species_units_varid, species_units_s) ) + call nc4_check( nf90_put_var(ncid, species_descr_varid, species_descr_s) ) deallocate(species_assim_int) deallocate(species_scale_int) deallocate(species_getinnov_int) deallocate(species_errstd_r4) + deallocate(species_varname_s) + deallocate(species_units_s) + deallocate(species_descr_s) end if From 499346135b78c2360dae365c7a621a9edda03711 Mon Sep 17 00:00:00 2001 From: amfox37 Date: Mon, 16 Mar 2026 15:39:59 -0600 Subject: [PATCH 16/40] Use PFIO helper for ObsFcstAna NF90_STRING writes --- GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 b/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 index e8060fa0..ffb8aa43 100644 --- a/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 +++ b/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 @@ -1788,6 +1788,7 @@ subroutine write_ObsFcstAna_nc4(fname, date_time, exp_id, file_tag, N_obsf, & Observations_f, N_obs_param, obs_param, out_ObsFcstAna_format, & update_type, dtstep_assim, grid_name) use netcdf + use pfio_NetCDF_Supplement, only: pfio_nf90_put_var_string implicit none @@ -1974,9 +1975,9 @@ subroutine write_ObsFcstAna_nc4(fname, date_time, exp_id, file_tag, N_obsf, & call nc4_check( nf90_put_var(ncid, species_scale_varid, species_scale_int) ) call nc4_check( nf90_put_var(ncid, species_getinnov_varid, species_getinnov_int) ) call nc4_check( nf90_put_var(ncid, species_errstd_varid, species_errstd_r4) ) - call nc4_check( nf90_put_var(ncid, species_varname_varid, species_varname_s) ) - call nc4_check( nf90_put_var(ncid, species_units_varid, species_units_s) ) - call nc4_check( nf90_put_var(ncid, species_descr_varid, species_descr_s) ) + call nc4_check( pfio_nf90_put_var_string(ncid, species_varname_varid, species_varname_s) ) + call nc4_check( pfio_nf90_put_var_string(ncid, species_units_varid, species_units_s) ) + call nc4_check( pfio_nf90_put_var_string(ncid, species_descr_varid, species_descr_s) ) deallocate(species_assim_int) deallocate(species_scale_int) From 5b0c475d12e2bca8acc1ef754af95de1edcb3b6d Mon Sep 17 00:00:00 2001 From: amfox37 Date: Tue, 17 Mar 2026 15:28:17 -0600 Subject: [PATCH 17/40] Use integer out_ObsFcstAna mode and centralize format mapping --- .../GEOS_LandAssimGridComp.F90 | 4 +- .../clsm_ensupd_enkf_update.F90 | 20 ++--- .../clsm_ensupd_upd_routines.F90 | 75 +++++++++++++++---- GEOSldas_App/LDASsa_DEFAULT_inputs_ensupd.nml | 5 +- .../LDASsa_SPECIAL_inputs_ensupd.nml | 5 +- 5 files changed, 71 insertions(+), 38 deletions(-) diff --git a/GEOSlandassim_GridComp/GEOS_LandAssimGridComp.F90 b/GEOSlandassim_GridComp/GEOS_LandAssimGridComp.F90 index 1196e2b4..d90041f8 100644 --- a/GEOSlandassim_GridComp/GEOS_LandAssimGridComp.F90 +++ b/GEOSlandassim_GridComp/GEOS_LandAssimGridComp.F90 @@ -98,7 +98,7 @@ module GEOS_LandAssimGridCompMod real :: fcsterr_inflation_fac integer :: N_obs_param logical :: out_obslog - logical :: out_ObsFcstAna + integer :: out_ObsFcstAna integer :: out_ObsFcstAna_mode logical :: out_smapL4SMaup integer :: N_obsbias_max @@ -1409,7 +1409,7 @@ subroutine Initialize(gc, import, export, clock, rc) call MPI_BCAST(fcsterr_inflation_fac, 1, MPI_REAL, 0,MPICOMM,mpierr) call MPI_BCAST(N_obs_param, 1, MPI_INTEGER, 0,MPICOMM,mpierr) call MPI_BCAST(out_obslog, 1, MPI_LOGICAL, 0,MPICOMM,mpierr) - call MPI_BCAST(out_ObsFcstAna, 1, MPI_LOGICAL, 0,MPICOMM,mpierr) + call MPI_BCAST(out_ObsFcstAna, 1, MPI_INTEGER, 0,MPICOMM,mpierr) call MPI_BCAST(out_ObsFcstAna_mode, 1, MPI_INTEGER, 0,MPICOMM,mpierr) call MPI_BCAST(out_smapL4SMaup, 1, MPI_LOGICAL, 0,MPICOMM,mpierr) call MPI_BCAST(N_obsbias_max, 1, MPI_INTEGER, 0,MPICOMM,mpierr) diff --git a/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 b/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 index ffb8aa43..2f24097c 100644 --- a/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 +++ b/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 @@ -98,6 +98,7 @@ module clsm_ensupd_enkf_update FOV_threshold, & get_halo_around_tile, & TileNnzObs, & + obsfcstana_mode_to_format, & OBSFCSTANA_FMT_BIN, & OBSFCSTANA_FMT_NC4, & OBSFCSTANA_FMT_BOTH @@ -1558,6 +1559,7 @@ subroutine output_ObsFcstAna(date_time, exp_id, & character(300) :: fname character( 40) :: file_ext character( 8) :: out_ObsFcstAna_format + logical :: mode_ok character(len=*), parameter :: Iam = 'output_ObsFcstAna' character(len=400) :: err_msg @@ -1702,22 +1704,12 @@ subroutine output_ObsFcstAna(date_time, exp_id, & ! write to file - if ( (out_ObsFcstAna_mode /= OBSFCSTANA_FMT_BIN ) .and. & - (out_ObsFcstAna_mode /= OBSFCSTANA_FMT_NC4 ) .and. & - (out_ObsFcstAna_mode /= OBSFCSTANA_FMT_BOTH) ) then + call obsfcstana_mode_to_format(out_ObsFcstAna_mode, out_ObsFcstAna_format, mode_ok) + if (.not. mode_ok) then err_msg = 'unknown out_ObsFcstAna_mode' call ldas_abort(LDAS_GENERIC_ERROR, Iam, err_msg) end if - select case (out_ObsFcstAna_mode) - case (OBSFCSTANA_FMT_BIN) - out_ObsFcstAna_format = 'BIN' - case (OBSFCSTANA_FMT_NC4) - out_ObsFcstAna_format = 'NC4' - case (OBSFCSTANA_FMT_BOTH) - out_ObsFcstAna_format = 'BOTH' - end select - if (out_ObsFcstAna_mode == OBSFCSTANA_FMT_BIN .or. & out_ObsFcstAna_mode == OBSFCSTANA_FMT_BOTH) then @@ -2026,7 +2018,7 @@ subroutine output_ObsFcstAna_wrapper( out_ObsFcstAna, & ! major revisions for new obs handling and MPI - logical, intent(in) :: out_ObsFcstAna + integer, intent(in) :: out_ObsFcstAna integer, intent(in) :: out_ObsFcstAna_mode @@ -2079,7 +2071,7 @@ subroutine output_ObsFcstAna_wrapper( out_ObsFcstAna, & ! output "O-A" (obs - analysis) whenever innovations are output - if (out_ObsFcstAna) then + if (out_ObsFcstAna > 0) then ! compute model forecast of observations diff --git a/GEOSlandassim_GridComp/clsm_ensupd_upd_routines.F90 b/GEOSlandassim_GridComp/clsm_ensupd_upd_routines.F90 index 70db22e2..f2adbfe8 100644 --- a/GEOSlandassim_GridComp/clsm_ensupd_upd_routines.F90 +++ b/GEOSlandassim_GridComp/clsm_ensupd_upd_routines.F90 @@ -161,7 +161,9 @@ module clsm_ensupd_upd_routines public :: get_halo_around_tile public :: TileNnzObs public :: dist_km2deg + public :: obsfcstana_mode_to_format + integer, parameter, public :: OBSFCSTANA_FMT_OFF = 0 integer, parameter, public :: OBSFCSTANA_FMT_BIN = 1 integer, parameter, public :: OBSFCSTANA_FMT_NC4 = 2 integer, parameter, public :: OBSFCSTANA_FMT_BOTH = 3 @@ -176,6 +178,38 @@ module clsm_ensupd_upd_routines contains + ! ******************************************************************** + + subroutine obsfcstana_mode_to_format(mode, format, is_valid) + + implicit none + + integer, intent(in) :: mode + character(*), intent(out) :: format + logical, optional, intent(out) :: is_valid + + logical :: valid_mode + + valid_mode = .true. + + select case (mode) + case (OBSFCSTANA_FMT_OFF) + format = 'OFF' + case (OBSFCSTANA_FMT_BIN) + format = 'BIN' + case (OBSFCSTANA_FMT_NC4) + format = 'NC4' + case (OBSFCSTANA_FMT_BOTH) + format = 'BOTH' + case default + format = 'UNKNOWN' + valid_mode = .false. + end select + + if (present(is_valid)) is_valid = valid_mode + + end subroutine obsfcstana_mode_to_format + ! ******************************************************************** subroutine read_ens_upd_inputs( & @@ -240,7 +274,7 @@ subroutine read_ens_upd_inputs( & type(obs_param_type), dimension(:), pointer :: obs_param ! output logical, intent(out) :: out_obslog - logical, intent(out) :: out_ObsFcstAna + integer, intent(out) :: out_ObsFcstAna integer, intent(out) :: out_ObsFcstAna_mode logical, intent(out) :: out_smapL4SMaup @@ -263,7 +297,6 @@ subroutine read_ens_upd_inputs( & character(200) :: ens_upd_inputs_path character( 40) :: ens_upd_inputs_file, dir_name, file_tag, file_ext - character( 40) :: out_ObsFcstAna_format integer :: i, j, k, N_tmp, k_hD, k_hA, k_vD, k_vA @@ -279,6 +312,8 @@ subroutine read_ens_upd_inputs( & character(len=400) :: err_msg character(len= 6) :: tmpstring6 logical :: file_exists + integer :: ios + character(len=400) :: iomsg ! ----------------------------------------------------------------- @@ -286,7 +321,6 @@ subroutine read_ens_upd_inputs( & update_type, & out_obslog, & out_ObsFcstAna, & - out_ObsFcstAna_format, & out_smapL4SMaup, & xcompact, ycompact, & fcsterr_inflation_fac, & @@ -298,7 +332,7 @@ subroutine read_ens_upd_inputs( & ens_upd_inputs_path = '.' ! set default ens_upd_inputs_file = 'LDASsa_DEFAULT_inputs_ensupd.nml' - out_ObsFcstAna_format = 'BIN' + out_ObsFcstAna = OBSFCSTANA_FMT_OFF ! Read data from default ens_upd_inputs namelist file @@ -310,7 +344,12 @@ subroutine read_ens_upd_inputs( & if (logit) write (logunit,'(400A)') 'reading *default* EnKF inputs from ' // trim(fname) if (logit) write (logunit,*) - read (10, nml=ens_upd_inputs) + read (10, nml=ens_upd_inputs, iostat=ios, iomsg=iomsg) + if (ios /= 0) then + err_msg = 'error reading ens_upd_inputs from ' // trim(fname) // & + '; out_ObsFcstAna must be integer 0/1/2/3 (OFF/BIN/NC4/BOTH). ' // trim(iomsg) + call ldas_abort(LDAS_GENERIC_ERROR, Iam, err_msg) + end if close(10,status='keep') @@ -334,7 +373,12 @@ subroutine read_ens_upd_inputs( & if (logit) write (logunit,'(400A)') 'reading *special* EnKF inputs from ' // trim(fname) if (logit) write (logunit,*) - read (10, nml=ens_upd_inputs) + read (10, nml=ens_upd_inputs, iostat=ios, iomsg=iomsg) + if (ios /= 0) then + err_msg = 'error reading ens_upd_inputs from ' // trim(fname) // & + '; out_ObsFcstAna must be integer 0/1/2/3 (OFF/BIN/NC4/BOTH). ' // trim(iomsg) + call ldas_abort(LDAS_GENERIC_ERROR, Iam, err_msg) + end if close(10,status='keep') @@ -350,20 +394,19 @@ subroutine read_ens_upd_inputs( & ! ! consistency checks etc - select case (trim(out_ObsFcstAna_format)) - - case ('BIN') + select case (out_ObsFcstAna) + case (0) + out_ObsFcstAna_mode = OBSFCSTANA_FMT_OFF + case (1) out_ObsFcstAna_mode = OBSFCSTANA_FMT_BIN - - case ('NC4') + case (2) out_ObsFcstAna_mode = OBSFCSTANA_FMT_NC4 - - case ('BOTH') + case (3) out_ObsFcstAna_mode = OBSFCSTANA_FMT_BOTH - case default - err_msg = 'unknown value of "out_ObsFcstAna_format": "' // & - trim(out_ObsFcstAna_format) // '"' + write(tmpstring6,'(I6)') out_ObsFcstAna + err_msg = 'unknown integer value of "out_ObsFcstAna": "' // & + trim(tmpstring6) // '". Use 0=OFF, 1=BIN, 2=NC4, 3=BOTH.' call ldas_abort(LDAS_GENERIC_ERROR, Iam, err_msg) end select diff --git a/GEOSldas_App/LDASsa_DEFAULT_inputs_ensupd.nml b/GEOSldas_App/LDASsa_DEFAULT_inputs_ensupd.nml index 6f9f2a22..1b1da3ee 100644 --- a/GEOSldas_App/LDASsa_DEFAULT_inputs_ensupd.nml +++ b/GEOSldas_App/LDASsa_DEFAULT_inputs_ensupd.nml @@ -43,9 +43,8 @@ update_type = 0 out_obslog = .true. -out_ObsFcstAna = .false. -! out_ObsFcstAna_format: 'BIN' (legacy unformatted), 'NC4', or 'BOTH' -out_ObsFcstAna_format = 'BIN' +! out_ObsFcstAna: 0=OFF, 1=BIN (legacy unformatted), 2=NC4, 3=BOTH +out_ObsFcstAna = 0 out_smapL4SMaup = .false. ! --------------------------------------------------------------------- diff --git a/GEOSldas_App/LandAtmDAS_nml/LDASsa_SPECIAL_inputs_ensupd.nml b/GEOSldas_App/LandAtmDAS_nml/LDASsa_SPECIAL_inputs_ensupd.nml index 15c79b32..c0eaff90 100644 --- a/GEOSldas_App/LandAtmDAS_nml/LDASsa_SPECIAL_inputs_ensupd.nml +++ b/GEOSldas_App/LandAtmDAS_nml/LDASsa_SPECIAL_inputs_ensupd.nml @@ -37,9 +37,8 @@ update_type = 10 out_obslog = .true. -out_ObsFcstAna = .true. -! out_ObsFcstAna_format: 'BIN' (legacy unformatted), 'NC4', or 'BOTH' -out_ObsFcstAna_format = 'BIN' +! out_ObsFcstAna: 0=OFF, 1=BIN (legacy unformatted), 2=NC4, 3=BOTH +out_ObsFcstAna = 1 out_smapL4SMaup = .false. ! --------------------------------------------------------------------- From 4e288093ef75beb4021951c728253dc4e3c02381 Mon Sep 17 00:00:00 2001 From: amfox37 Date: Tue, 17 Mar 2026 15:38:43 -0600 Subject: [PATCH 18/40] Drop redundant ObsFcstAna count attributes from nc4 header --- GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 | 2 -- 1 file changed, 2 deletions(-) diff --git a/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 b/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 index 2f24097c..afb28f8c 100644 --- a/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 +++ b/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 @@ -1847,8 +1847,6 @@ subroutine write_ObsFcstAna_nc4(fname, date_time, exp_id, file_tag, N_obsf, & call nc4_check( nf90_def_var(ncid, 'species_descr', NF90_STRING, [nspecies_dimid], species_descr_varid) ) call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, 'schema_version', 'ObsFcstAna_nc4_v1') ) - call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, 'N_obsf', N_obsf) ) - call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, 'N_obs_param', N_obs_param) ) call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, 'year', date_time%year) ) call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, 'month', date_time%month) ) call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, 'day', date_time%day) ) From 8ae73191b2fce7c3e3b60d9103e3159b4e803160 Mon Sep 17 00:00:00 2001 From: amfox37 Date: Tue, 17 Mar 2026 15:42:07 -0600 Subject: [PATCH 19/40] Rename obs_param metadata variables to obsparam_* in ObsFcstAna nc4 --- .../clsm_ensupd_enkf_update.F90 | 64 +++++++++---------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 b/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 index afb28f8c..d35ac54c 100644 --- a/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 +++ b/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 @@ -1805,9 +1805,9 @@ subroutine write_ObsFcstAna_nc4(fname, date_time, exp_id, file_tag, N_obsf, & integer :: obs_varid, obsvar_varid integer :: fcst_varid, fcstvar_varid integer :: ana_varid, anavar_varid - integer :: species_id_varid, species_assim_varid, species_scale_varid - integer :: species_getinnov_varid, species_errstd_varid - integer :: species_varname_varid, species_units_varid, species_descr_varid + integer :: obsparam_species_varid, obsparam_assim_varid, obsparam_scale_varid + integer :: obsparam_getinnov_varid, obsparam_errstd_varid + integer :: obsparam_varname_varid, obsparam_units_varid, obsparam_descr_varid integer, dimension(:), allocatable :: assim_int integer, dimension(:), allocatable :: species_assim_int, species_scale_int @@ -1837,14 +1837,14 @@ subroutine write_ObsFcstAna_nc4(fname, date_time, exp_id, file_tag, N_obsf, & call nc4_check( nf90_def_var(ncid, 'ana', NF90_FLOAT, [nobs_dimid], ana_varid) ) call nc4_check( nf90_def_var(ncid, 'anavar', NF90_FLOAT, [nobs_dimid], anavar_varid) ) - call nc4_check( nf90_def_var(ncid, 'species_id', NF90_INT, [nspecies_dimid], species_id_varid) ) - call nc4_check( nf90_def_var(ncid, 'species_assim', NF90_INT, [nspecies_dimid], species_assim_varid) ) - call nc4_check( nf90_def_var(ncid, 'species_scale', NF90_INT, [nspecies_dimid], species_scale_varid) ) - call nc4_check( nf90_def_var(ncid, 'species_getinnov', NF90_INT, [nspecies_dimid], species_getinnov_varid) ) - call nc4_check( nf90_def_var(ncid, 'species_errstd', NF90_FLOAT, [nspecies_dimid], species_errstd_varid) ) - call nc4_check( nf90_def_var(ncid, 'species_varname', NF90_STRING, [nspecies_dimid], species_varname_varid) ) - call nc4_check( nf90_def_var(ncid, 'species_units', NF90_STRING, [nspecies_dimid], species_units_varid) ) - call nc4_check( nf90_def_var(ncid, 'species_descr', NF90_STRING, [nspecies_dimid], species_descr_varid) ) + call nc4_check( nf90_def_var(ncid, 'obsparam_species', NF90_INT, [nspecies_dimid], obsparam_species_varid) ) + call nc4_check( nf90_def_var(ncid, 'obsparam_assim', NF90_INT, [nspecies_dimid], obsparam_assim_varid) ) + call nc4_check( nf90_def_var(ncid, 'obsparam_scale', NF90_INT, [nspecies_dimid], obsparam_scale_varid) ) + call nc4_check( nf90_def_var(ncid, 'obsparam_getinnov', NF90_INT, [nspecies_dimid], obsparam_getinnov_varid) ) + call nc4_check( nf90_def_var(ncid, 'obsparam_errstd', NF90_FLOAT, [nspecies_dimid], obsparam_errstd_varid) ) + call nc4_check( nf90_def_var(ncid, 'obsparam_varname', NF90_STRING, [nspecies_dimid], obsparam_varname_varid) ) + call nc4_check( nf90_def_var(ncid, 'obsparam_units', NF90_STRING, [nspecies_dimid], obsparam_units_varid) ) + call nc4_check( nf90_def_var(ncid, 'obsparam_descr', NF90_STRING, [nspecies_dimid], obsparam_descr_varid) ) call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, 'schema_version', 'ObsFcstAna_nc4_v1') ) call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, 'year', date_time%year) ) @@ -1898,19 +1898,19 @@ subroutine write_ObsFcstAna_nc4(fname, date_time, exp_id, file_tag, N_obsf, & call nc4_check( nf90_put_att(ncid, anavar_varid, 'units', 'species-dependent') ) call nc4_check( nf90_put_att(ncid, anavar_varid, 'missing_value', nodata_generic) ) - call nc4_check( nf90_put_att(ncid, species_id_varid, 'long_name', 'observation species identifier') ) - call nc4_check( nf90_put_att(ncid, species_id_varid, 'units', '1') ) - call nc4_check( nf90_put_att(ncid, species_assim_varid, 'long_name', 'species assimilation flag') ) - call nc4_check( nf90_put_att(ncid, species_assim_varid, 'units', '1') ) - call nc4_check( nf90_put_att(ncid, species_scale_varid, 'long_name', 'species scaling flag') ) - call nc4_check( nf90_put_att(ncid, species_scale_varid, 'units', '1') ) - call nc4_check( nf90_put_att(ncid, species_getinnov_varid, 'long_name', 'species innovation-output flag') ) - call nc4_check( nf90_put_att(ncid, species_getinnov_varid, 'units', '1') ) - call nc4_check( nf90_put_att(ncid, species_errstd_varid, 'long_name', 'species default observation error standard deviation') ) - call nc4_check( nf90_put_att(ncid, species_errstd_varid, 'units', 'species-dependent') ) - call nc4_check( nf90_put_att(ncid, species_varname_varid, 'long_name', 'species model variable name') ) - call nc4_check( nf90_put_att(ncid, species_units_varid, 'long_name', 'species observation units') ) - call nc4_check( nf90_put_att(ncid, species_descr_varid, 'long_name', 'species description') ) + call nc4_check( nf90_put_att(ncid, obsparam_species_varid, 'long_name', 'obs parameter species identifier') ) + call nc4_check( nf90_put_att(ncid, obsparam_species_varid, 'units', '1') ) + call nc4_check( nf90_put_att(ncid, obsparam_assim_varid, 'long_name', 'obs parameter assimilation flag') ) + call nc4_check( nf90_put_att(ncid, obsparam_assim_varid, 'units', '1') ) + call nc4_check( nf90_put_att(ncid, obsparam_scale_varid, 'long_name', 'obs parameter scaling flag') ) + call nc4_check( nf90_put_att(ncid, obsparam_scale_varid, 'units', '1') ) + call nc4_check( nf90_put_att(ncid, obsparam_getinnov_varid, 'long_name', 'obs parameter innovation-output flag') ) + call nc4_check( nf90_put_att(ncid, obsparam_getinnov_varid, 'units', '1') ) + call nc4_check( nf90_put_att(ncid, obsparam_errstd_varid, 'long_name', 'obs parameter default observation error standard deviation') ) + call nc4_check( nf90_put_att(ncid, obsparam_errstd_varid, 'units', 'species-dependent') ) + call nc4_check( nf90_put_att(ncid, obsparam_varname_varid, 'long_name', 'obs parameter model variable name') ) + call nc4_check( nf90_put_att(ncid, obsparam_units_varid, 'long_name', 'obs parameter observation units') ) + call nc4_check( nf90_put_att(ncid, obsparam_descr_varid, 'long_name', 'obs parameter description') ) call nc4_check( nf90_enddef(ncid) ) @@ -1960,14 +1960,14 @@ subroutine write_ObsFcstAna_nc4(fname, date_time, exp_id, file_tag, N_obsf, & species_descr_s(i) = trim(obs_param(i)%descr) end do - call nc4_check( nf90_put_var(ncid, species_id_varid, obs_param(1:N_obs_param)%species) ) - call nc4_check( nf90_put_var(ncid, species_assim_varid, species_assim_int) ) - call nc4_check( nf90_put_var(ncid, species_scale_varid, species_scale_int) ) - call nc4_check( nf90_put_var(ncid, species_getinnov_varid, species_getinnov_int) ) - call nc4_check( nf90_put_var(ncid, species_errstd_varid, species_errstd_r4) ) - call nc4_check( pfio_nf90_put_var_string(ncid, species_varname_varid, species_varname_s) ) - call nc4_check( pfio_nf90_put_var_string(ncid, species_units_varid, species_units_s) ) - call nc4_check( pfio_nf90_put_var_string(ncid, species_descr_varid, species_descr_s) ) + call nc4_check( nf90_put_var(ncid, obsparam_species_varid, obs_param(1:N_obs_param)%species) ) + call nc4_check( nf90_put_var(ncid, obsparam_assim_varid, species_assim_int) ) + call nc4_check( nf90_put_var(ncid, obsparam_scale_varid, species_scale_int) ) + call nc4_check( nf90_put_var(ncid, obsparam_getinnov_varid, species_getinnov_int) ) + call nc4_check( nf90_put_var(ncid, obsparam_errstd_varid, species_errstd_r4) ) + call nc4_check( pfio_nf90_put_var_string(ncid, obsparam_varname_varid, species_varname_s) ) + call nc4_check( pfio_nf90_put_var_string(ncid, obsparam_units_varid, species_units_s) ) + call nc4_check( pfio_nf90_put_var_string(ncid, obsparam_descr_varid, species_descr_s) ) deallocate(species_assim_int) deallocate(species_scale_int) From 9587a8fcb29f32c3d350ed35cbe4a8b91f619983 Mon Sep 17 00:00:00 2001 From: amfox37 Date: Tue, 17 Mar 2026 15:42:58 -0600 Subject: [PATCH 20/40] Rename ObsFcstAna nc4 assim variable to assim_flag --- GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 b/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 index d35ac54c..fb3d7b8f 100644 --- a/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 +++ b/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 @@ -1800,7 +1800,7 @@ subroutine write_ObsFcstAna_nc4(fname, date_time, exp_id, file_tag, N_obsf, & integer :: ncid integer :: nobs_dimid integer :: nspecies_dimid - integer :: assim_varid, species_varid, tilenum_varid + integer :: assim_flag_varid, species_varid, tilenum_varid integer :: lon_varid, lat_varid integer :: obs_varid, obsvar_varid integer :: fcst_varid, fcstvar_varid @@ -1825,7 +1825,7 @@ subroutine write_ObsFcstAna_nc4(fname, date_time, exp_id, file_tag, N_obsf, & call nc4_check( nf90_def_dim(ncid, 'nobs', NF90_UNLIMITED, nobs_dimid) ) call nc4_check( nf90_def_dim(ncid, 'n_species', N_obs_param, nspecies_dimid) ) - call nc4_check( nf90_def_var(ncid, 'assim', NF90_INT, [nobs_dimid], assim_varid) ) + call nc4_check( nf90_def_var(ncid, 'assim_flag', NF90_INT, [nobs_dimid], assim_flag_varid) ) call nc4_check( nf90_def_var(ncid, 'species', NF90_INT, [nobs_dimid], species_varid) ) call nc4_check( nf90_def_var(ncid, 'tilenum', NF90_INT, [nobs_dimid], tilenum_varid) ) call nc4_check( nf90_def_var(ncid, 'lon', NF90_FLOAT, [nobs_dimid], lon_varid) ) @@ -1867,8 +1867,8 @@ subroutine write_ObsFcstAna_nc4(fname, date_time, exp_id, file_tag, N_obsf, & call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, trim(attr_name), trim(obs_param(i)%descr)) ) end do - call nc4_check( nf90_put_att(ncid, assim_varid, 'long_name', 'assimilation flag') ) - call nc4_check( nf90_put_att(ncid, assim_varid, 'units', '1') ) + call nc4_check( nf90_put_att(ncid, assim_flag_varid, 'long_name', 'assimilation flag') ) + call nc4_check( nf90_put_att(ncid, assim_flag_varid, 'units', '1') ) call nc4_check( nf90_put_att(ncid, species_varid, 'long_name', 'observation species identifier') ) call nc4_check( nf90_put_att(ncid, species_varid, 'units', '1') ) call nc4_check( nf90_put_att(ncid, tilenum_varid, 'long_name', 'tile number (full domain)') ) @@ -1920,7 +1920,7 @@ subroutine write_ObsFcstAna_nc4(fname, date_time, exp_id, file_tag, N_obsf, & assim_int = 0 where (Observations_f(1:N_obsf)%assim) assim_int = 1 - call nc4_check( nf90_put_var(ncid, assim_varid, assim_int) ) + call nc4_check( nf90_put_var(ncid, assim_flag_varid, assim_int) ) call nc4_check( nf90_put_var(ncid, species_varid, Observations_f(1:N_obsf)%species) ) call nc4_check( nf90_put_var(ncid, tilenum_varid, Observations_f(1:N_obsf)%tilenum) ) call nc4_check( nf90_put_var(ncid, lon_varid, Observations_f(1:N_obsf)%lon) ) From 046924b90eb4beb19650c8f8587121f0565d37ee Mon Sep 17 00:00:00 2001 From: amfox37 Date: Fri, 20 Feb 2026 08:24:15 -0700 Subject: [PATCH 21/40] Reapply non-MKL GEOSlandpert build fix Restore the MKL guards in GEOSlandpert_GridComp/CMakeLists.txt and random_fields.F90 so builds succeed when MKL is not detected. This is intentionally re-applied here because a prior branch commit reverted the fix; merging develop was a no-op in history and did not restore the reverted content. --- GEOSlandpert_GridComp/CMakeLists.txt | 5 +++-- GEOSlandpert_GridComp/random_fields.F90 | 7 ++++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/GEOSlandpert_GridComp/CMakeLists.txt b/GEOSlandpert_GridComp/CMakeLists.txt index 9930cab5..affe50a3 100644 --- a/GEOSlandpert_GridComp/CMakeLists.txt +++ b/GEOSlandpert_GridComp/CMakeLists.txt @@ -11,6 +11,7 @@ esma_add_library (${this} DEPENDENCIES GEOS_LdasShared GEOSens_GridComp GEOSland_GridComp MAPL ${MKL_LIBRARIES} INCLUDES ${INC_ESMF} ${MKL_INCLUDE_DIRS}) -if (NOT CMAKE_Fortran_COMPILER_ID MATCHES "NAG") - target_compile_definitions(${this} PRIVATE MKL_AVAILABLE) +# Enable MKL paths only when MKL was detected and the compiler supports it. +if (MKL_FOUND AND NOT CMAKE_Fortran_COMPILER_ID MATCHES "NAG") + target_compile_definitions(${this} PRIVATE MKL_AVAILABLE) endif () diff --git a/GEOSlandpert_GridComp/random_fields.F90 b/GEOSlandpert_GridComp/random_fields.F90 index 3ed12e69..0b37d6a7 100644 --- a/GEOSlandpert_GridComp/random_fields.F90 +++ b/GEOSlandpert_GridComp/random_fields.F90 @@ -311,11 +311,13 @@ subroutine sqrt_gauss_spectrum_2d(this, lx, ly, dx, dy) ! assemble spectrum in "wrap-around" order i1 = 1 i2 = this%N_x_fft +#ifdef MKL_AVAILABLE if (this%comm /= MPI_COMM_NULL) then call MPI_COMM_Rank(this%node_comm, rank, ierror) i1 = sum(this%dim1_counts(1:rank)) + 1 i2 = sum(this%dim1_counts(1:rank+1)) endif +#endif do j=1,this%N_y_fft this%field1_fft(i1:i2,j) = fac*exp(-.25*(lx2kx2(i1:i2)+ly2ky2(j))) @@ -395,13 +397,14 @@ subroutine rfg2d_fft(this, rseed, rfield, rfield2, lx, ly, dx, dy) integer :: i, j integer :: N_x_fft, N_y_fft real :: N_xy_fft_real + integer :: n1, n2 #ifdef MKL_AVAILABLE integer :: status complex, allocatable :: z_inout(:) complex, pointer :: tmp_field(:,:) complex, pointer :: tmp_field_dim1(:,:) complex, pointer :: tmp_field_dim2(:,:) - integer :: n1, n2, npes, rank, ldim1, ldim2, ierror + integer :: npes, rank, ldim1, ldim2, ierror complex, pointer :: X(:) type (c_ptr) :: cptr #else @@ -624,6 +627,7 @@ subroutine quit(message) end subroutine quit +#ifdef MKL_AVAILABLE subroutine win_allocate(this, nx, ny, rc) class(random_fields), intent(inout) :: this integer, intent(in) :: nx, ny @@ -662,6 +666,7 @@ subroutine win_deallocate(this, rc) _VERIFY(status) end subroutine win_deallocate +#endif end module Random_fieldsMod From e1bc6f3588a4ed812562ec19f5422c148de33de8 Mon Sep 17 00:00:00 2001 From: amfox37 Date: Wed, 18 Mar 2026 13:39:39 -0600 Subject: [PATCH 22/40] Refine ObsFcstAna nc4 metadata attributes Rename obsparam_species to obsparam_species_id. Replace granular date/time attrs with Date timestamp and add CreatedBy. Remove file_tag and out_ObsFcstAna_format from global attributes. --- .../clsm_ensupd_enkf_update.F90 | 41 ++++++++++++------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 b/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 index fb3d7b8f..29a27b69 100644 --- a/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 +++ b/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 @@ -1805,7 +1805,7 @@ subroutine write_ObsFcstAna_nc4(fname, date_time, exp_id, file_tag, N_obsf, & integer :: obs_varid, obsvar_varid integer :: fcst_varid, fcstvar_varid integer :: ana_varid, anavar_varid - integer :: obsparam_species_varid, obsparam_assim_varid, obsparam_scale_varid + integer :: obsparam_species_id_varid, obsparam_assim_varid, obsparam_scale_varid integer :: obsparam_getinnov_varid, obsparam_errstd_varid integer :: obsparam_varname_varid, obsparam_units_varid, obsparam_descr_varid @@ -1816,6 +1816,13 @@ subroutine write_ObsFcstAna_nc4(fname, date_time, exp_id, file_tag, N_obsf, & character(len=40), dimension(:), allocatable :: species_varname_s, species_units_s character(len=40), dimension(:), allocatable :: species_descr_s character(len=40) :: attr_name + character(len=8) :: write_date_yyyymmdd + character(len=10) :: write_time_hhmmss + character(len=5) :: write_zone + character(len=24) :: write_datetime_iso + character(len=64) :: user_name + character(len=128) :: created_by + integer :: user_len, user_status integer :: i character(len=*), parameter :: Iam = 'write_ObsFcstAna_nc4' character(len=400) :: err_msg @@ -1837,7 +1844,7 @@ subroutine write_ObsFcstAna_nc4(fname, date_time, exp_id, file_tag, N_obsf, & call nc4_check( nf90_def_var(ncid, 'ana', NF90_FLOAT, [nobs_dimid], ana_varid) ) call nc4_check( nf90_def_var(ncid, 'anavar', NF90_FLOAT, [nobs_dimid], anavar_varid) ) - call nc4_check( nf90_def_var(ncid, 'obsparam_species', NF90_INT, [nspecies_dimid], obsparam_species_varid) ) + call nc4_check( nf90_def_var(ncid, 'obsparam_species_id', NF90_INT, [nspecies_dimid], obsparam_species_id_varid) ) call nc4_check( nf90_def_var(ncid, 'obsparam_assim', NF90_INT, [nspecies_dimid], obsparam_assim_varid) ) call nc4_check( nf90_def_var(ncid, 'obsparam_scale', NF90_INT, [nspecies_dimid], obsparam_scale_varid) ) call nc4_check( nf90_def_var(ncid, 'obsparam_getinnov', NF90_INT, [nspecies_dimid], obsparam_getinnov_varid) ) @@ -1846,18 +1853,22 @@ subroutine write_ObsFcstAna_nc4(fname, date_time, exp_id, file_tag, N_obsf, & call nc4_check( nf90_def_var(ncid, 'obsparam_units', NF90_STRING, [nspecies_dimid], obsparam_units_varid) ) call nc4_check( nf90_def_var(ncid, 'obsparam_descr', NF90_STRING, [nspecies_dimid], obsparam_descr_varid) ) + call date_and_time(write_date_yyyymmdd, write_time_hhmmss, write_zone) + write(write_datetime_iso, '(A4,A1,A2,A1,A2,A1,A2,A1,A2,A1,A2,A5)') & + write_date_yyyymmdd(1:4), '-', write_date_yyyymmdd(5:6), '-', & + write_date_yyyymmdd(7:8), 'T', write_time_hhmmss(1:2), ':', & + write_time_hhmmss(3:4), ':', write_time_hhmmss(5:6), write_zone + call get_environment_variable('USER', user_name, length=user_len, status=user_status) + if (user_status == 0 .and. user_len > 0) then + created_by = trim(user_name(1:user_len)) // ' via write_ObsFcstAna_nc4' + else + created_by = 'GEOSldas write_ObsFcstAna_nc4' + end if + call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, 'schema_version', 'ObsFcstAna_nc4_v1') ) - call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, 'year', date_time%year) ) - call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, 'month', date_time%month) ) - call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, 'day', date_time%day) ) - call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, 'hour', date_time%hour) ) - call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, 'minute', date_time%min) ) - call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, 'second', date_time%sec) ) - call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, 'dofyr', date_time%dofyr) ) - call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, 'pentad', date_time%pentad) ) + call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, 'Date', trim(write_datetime_iso)) ) + call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, 'CreatedBy', trim(created_by)) ) call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, 'exp_id', trim(exp_id)) ) - call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, 'file_tag', trim(file_tag)) ) - call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, 'out_ObsFcstAna_format', trim(out_ObsFcstAna_format)) ) call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, 'update_type', update_type) ) call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, 'dtstep_assim', dtstep_assim) ) call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, 'grid_name', trim(grid_name)) ) @@ -1898,8 +1909,8 @@ subroutine write_ObsFcstAna_nc4(fname, date_time, exp_id, file_tag, N_obsf, & call nc4_check( nf90_put_att(ncid, anavar_varid, 'units', 'species-dependent') ) call nc4_check( nf90_put_att(ncid, anavar_varid, 'missing_value', nodata_generic) ) - call nc4_check( nf90_put_att(ncid, obsparam_species_varid, 'long_name', 'obs parameter species identifier') ) - call nc4_check( nf90_put_att(ncid, obsparam_species_varid, 'units', '1') ) + call nc4_check( nf90_put_att(ncid, obsparam_species_id_varid, 'long_name', 'obs parameter species identifier') ) + call nc4_check( nf90_put_att(ncid, obsparam_species_id_varid, 'units', '1') ) call nc4_check( nf90_put_att(ncid, obsparam_assim_varid, 'long_name', 'obs parameter assimilation flag') ) call nc4_check( nf90_put_att(ncid, obsparam_assim_varid, 'units', '1') ) call nc4_check( nf90_put_att(ncid, obsparam_scale_varid, 'long_name', 'obs parameter scaling flag') ) @@ -1960,7 +1971,7 @@ subroutine write_ObsFcstAna_nc4(fname, date_time, exp_id, file_tag, N_obsf, & species_descr_s(i) = trim(obs_param(i)%descr) end do - call nc4_check( nf90_put_var(ncid, obsparam_species_varid, obs_param(1:N_obs_param)%species) ) + call nc4_check( nf90_put_var(ncid, obsparam_species_id_varid, obs_param(1:N_obs_param)%species) ) call nc4_check( nf90_put_var(ncid, obsparam_assim_varid, species_assim_int) ) call nc4_check( nf90_put_var(ncid, obsparam_scale_varid, species_scale_int) ) call nc4_check( nf90_put_var(ncid, obsparam_getinnov_varid, species_getinnov_int) ) From 9c35f48e985736a4ccc6adf38b1ce4f36ca0de70 Mon Sep 17 00:00:00 2001 From: amfox37 Date: Wed, 18 Mar 2026 15:00:09 -0600 Subject: [PATCH 23/40] Adjust ObsFcstAna nc4 globals and special output mode Remove grid_name from ObsFcstAna nc4 global attributes. Write schema_version/Date/CreatedBy last in the global attribute list. Set LDASsa_SPECIAL_inputs_ensupd out_ObsFcstAna default to 2 (NC4). --- GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 | 7 +++---- .../LandAtmDAS_nml/LDASsa_SPECIAL_inputs_ensupd.nml | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 b/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 index 29a27b69..74a18dff 100644 --- a/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 +++ b/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 @@ -1865,18 +1865,17 @@ subroutine write_ObsFcstAna_nc4(fname, date_time, exp_id, file_tag, N_obsf, & created_by = 'GEOSldas write_ObsFcstAna_nc4' end if - call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, 'schema_version', 'ObsFcstAna_nc4_v1') ) - call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, 'Date', trim(write_datetime_iso)) ) - call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, 'CreatedBy', trim(created_by)) ) call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, 'exp_id', trim(exp_id)) ) call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, 'update_type', update_type) ) call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, 'dtstep_assim', dtstep_assim) ) - call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, 'grid_name', trim(grid_name)) ) do i=1,N_obs_param write(attr_name, '(A,I0.3,A)') 'species_', obs_param(i)%species, '_descr' call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, trim(attr_name), trim(obs_param(i)%descr)) ) end do + call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, 'schema_version', 'ObsFcstAna_nc4_v1') ) + call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, 'Date', trim(write_datetime_iso)) ) + call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, 'CreatedBy', trim(created_by)) ) call nc4_check( nf90_put_att(ncid, assim_flag_varid, 'long_name', 'assimilation flag') ) call nc4_check( nf90_put_att(ncid, assim_flag_varid, 'units', '1') ) diff --git a/GEOSldas_App/LandAtmDAS_nml/LDASsa_SPECIAL_inputs_ensupd.nml b/GEOSldas_App/LandAtmDAS_nml/LDASsa_SPECIAL_inputs_ensupd.nml index c0eaff90..b3550508 100644 --- a/GEOSldas_App/LandAtmDAS_nml/LDASsa_SPECIAL_inputs_ensupd.nml +++ b/GEOSldas_App/LandAtmDAS_nml/LDASsa_SPECIAL_inputs_ensupd.nml @@ -38,7 +38,7 @@ update_type = 10 out_obslog = .true. ! out_ObsFcstAna: 0=OFF, 1=BIN (legacy unformatted), 2=NC4, 3=BOTH -out_ObsFcstAna = 1 +out_ObsFcstAna = 2 out_smapL4SMaup = .false. ! --------------------------------------------------------------------- From d374f6f7a13a0032e16fa7ae1f7a1df98671a383 Mon Sep 17 00:00:00 2001 From: amfox37 Date: Wed, 18 Mar 2026 16:39:18 -0600 Subject: [PATCH 24/40] Set special ensupd ObsFcstAna mode back to BIN --- GEOSldas_App/LandAtmDAS_nml/LDASsa_SPECIAL_inputs_ensupd.nml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GEOSldas_App/LandAtmDAS_nml/LDASsa_SPECIAL_inputs_ensupd.nml b/GEOSldas_App/LandAtmDAS_nml/LDASsa_SPECIAL_inputs_ensupd.nml index b3550508..c0eaff90 100644 --- a/GEOSldas_App/LandAtmDAS_nml/LDASsa_SPECIAL_inputs_ensupd.nml +++ b/GEOSldas_App/LandAtmDAS_nml/LDASsa_SPECIAL_inputs_ensupd.nml @@ -38,7 +38,7 @@ update_type = 10 out_obslog = .true. ! out_ObsFcstAna: 0=OFF, 1=BIN (legacy unformatted), 2=NC4, 3=BOTH -out_ObsFcstAna = 2 +out_ObsFcstAna = 1 out_smapL4SMaup = .false. ! --------------------------------------------------------------------- From c8304a96fe43509b6355401ea04ba1d23aa61f4a Mon Sep 17 00:00:00 2001 From: Rolf Reichle Date: Mon, 13 Apr 2026 11:41:48 -0400 Subject: [PATCH 25/40] clarified description of out_ObsFcstAna change in CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cc336acc..703f7313 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- Added optional NetCDF4 output mode for ObsFcstAna, including NetCDF metadata and runtime context. +- Added optional NetCDF4 output mode for ObsFcstAna, including NetCDF metadata and runtime context. Changed namelist variable "out_ObsFcstAna" from logical to integer. ### Changed From 263a4ee6de127c263a97ebf855e5781d48152f03 Mon Sep 17 00:00:00 2001 From: Rolf Reichle Date: Mon, 13 Apr 2026 11:49:26 -0400 Subject: [PATCH 26/40] edited comment re. out_ObsFcstAna (LDASsa_DEFAULT_inputs_ensupd.nml; LandAtmDAS_nml/LDASsa_SPECIAL_inputs_ensupd.nml) --- GEOSldas_App/LDASsa_DEFAULT_inputs_ensupd.nml | 3 +-- GEOSldas_App/LandAtmDAS_nml/LDASsa_SPECIAL_inputs_ensupd.nml | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/GEOSldas_App/LDASsa_DEFAULT_inputs_ensupd.nml b/GEOSldas_App/LDASsa_DEFAULT_inputs_ensupd.nml index 1b1da3ee..0368701d 100644 --- a/GEOSldas_App/LDASsa_DEFAULT_inputs_ensupd.nml +++ b/GEOSldas_App/LDASsa_DEFAULT_inputs_ensupd.nml @@ -43,8 +43,7 @@ update_type = 0 out_obslog = .true. -! out_ObsFcstAna: 0=OFF, 1=BIN (legacy unformatted), 2=NC4, 3=BOTH -out_ObsFcstAna = 0 +out_ObsFcstAna = 0 ! 0=OFF, 1=BIN (legacy unformatted), 2=NC4, 3=BIN and NC4 out_smapL4SMaup = .false. ! --------------------------------------------------------------------- diff --git a/GEOSldas_App/LandAtmDAS_nml/LDASsa_SPECIAL_inputs_ensupd.nml b/GEOSldas_App/LandAtmDAS_nml/LDASsa_SPECIAL_inputs_ensupd.nml index c0eaff90..e8bc5978 100644 --- a/GEOSldas_App/LandAtmDAS_nml/LDASsa_SPECIAL_inputs_ensupd.nml +++ b/GEOSldas_App/LandAtmDAS_nml/LDASsa_SPECIAL_inputs_ensupd.nml @@ -37,8 +37,7 @@ update_type = 10 out_obslog = .true. -! out_ObsFcstAna: 0=OFF, 1=BIN (legacy unformatted), 2=NC4, 3=BOTH -out_ObsFcstAna = 1 +out_ObsFcstAna = 1 ! 0=OFF, 1=BIN (legacy unformatted), 2=NC4, 3=BIN and NC4 out_smapL4SMaup = .false. ! --------------------------------------------------------------------- From b26a34f34cd63f737e6e7206b799dfdbb540faf0 Mon Sep 17 00:00:00 2001 From: Rolf Reichle Date: Mon, 13 Apr 2026 13:15:03 -0400 Subject: [PATCH 27/40] removed redundant out_ObsFcstAna_mode/FMT (GEOS_LandAssimGridComp.F90, clsm_ensupd_enkf_update.F90, clsm_ensupd_upd_routines.F90) --- .../GEOS_LandAssimGridComp.F90 | 4 -- .../clsm_ensupd_enkf_update.F90 | 54 +++++---------- .../clsm_ensupd_upd_routines.F90 | 65 ++----------------- 3 files changed, 24 insertions(+), 99 deletions(-) diff --git a/GEOSlandassim_GridComp/GEOS_LandAssimGridComp.F90 b/GEOSlandassim_GridComp/GEOS_LandAssimGridComp.F90 index d90041f8..a8177203 100644 --- a/GEOSlandassim_GridComp/GEOS_LandAssimGridComp.F90 +++ b/GEOSlandassim_GridComp/GEOS_LandAssimGridComp.F90 @@ -99,7 +99,6 @@ module GEOS_LandAssimGridCompMod integer :: N_obs_param logical :: out_obslog integer :: out_ObsFcstAna - integer :: out_ObsFcstAna_mode logical :: out_smapL4SMaup integer :: N_obsbias_max @@ -1386,7 +1385,6 @@ subroutine Initialize(gc, import, export, clock, rc) obs_param, & out_obslog, & out_ObsFcstAna, & - out_ObsFcstAna_mode, & out_smapL4SMaup, & N_obsbias_max & ) @@ -1410,7 +1408,6 @@ subroutine Initialize(gc, import, export, clock, rc) call MPI_BCAST(N_obs_param, 1, MPI_INTEGER, 0,MPICOMM,mpierr) call MPI_BCAST(out_obslog, 1, MPI_LOGICAL, 0,MPICOMM,mpierr) call MPI_BCAST(out_ObsFcstAna, 1, MPI_INTEGER, 0,MPICOMM,mpierr) - call MPI_BCAST(out_ObsFcstAna_mode, 1, MPI_INTEGER, 0,MPICOMM,mpierr) call MPI_BCAST(out_smapL4SMaup, 1, MPI_LOGICAL, 0,MPICOMM,mpierr) call MPI_BCAST(N_obsbias_max, 1, MPI_INTEGER, 0,MPICOMM,mpierr) @@ -1951,7 +1948,6 @@ subroutine RUN ( GC, IMPORT, EXPORT, CLOCK, RC ) if (.true.) then ! replace obsolete check for analysis time with "if true" to keep indents call output_ObsFcstAna_wrapper( out_ObsFcstAna, & - out_ObsFcstAna_mode, & date_time_new, trim(exp_id), & N_obsl, N_obs_param, NUM_ENSEMBLE, & N_catl, tile_coord_l, & diff --git a/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 b/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 index 74a18dff..95b86fdf 100644 --- a/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 +++ b/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 @@ -97,11 +97,7 @@ module clsm_ensupd_enkf_update halo_type, & FOV_threshold, & get_halo_around_tile, & - TileNnzObs, & - obsfcstana_mode_to_format, & - OBSFCSTANA_FMT_BIN, & - OBSFCSTANA_FMT_NC4, & - OBSFCSTANA_FMT_BOTH + TileNnzObs use clsm_ensupd_read_obs, ONLY: & collect_obs @@ -1516,7 +1512,7 @@ end subroutine apply_enkf_increments ! ******************************************************************** subroutine output_ObsFcstAna(date_time, exp_id, & - N_obsl, Observations_l, N_obs_param, obs_param, out_ObsFcstAna_mode, & + N_obsl, Observations_l, N_obs_param, obs_param, out_ObsFcstAna, & update_type, dtstep_assim, grid_name, rf2f) ! obs space output: observations, obs space forecast, obs space analysis, and @@ -1532,7 +1528,7 @@ subroutine output_ObsFcstAna(date_time, exp_id, & integer, intent(in) :: N_obsl, N_obs_param type(obs_param_type), dimension(N_obs_param), intent(in) :: obs_param - integer, intent(in) :: out_ObsFcstAna_mode + integer, intent(in) :: out_ObsFcstAna integer, intent(in) :: update_type integer, intent(in) :: dtstep_assim character(*), intent(in) :: grid_name @@ -1558,8 +1554,6 @@ subroutine output_ObsFcstAna(date_time, exp_id, & character(300) :: fname character( 40) :: file_ext - character( 8) :: out_ObsFcstAna_format - logical :: mode_ok character(len=*), parameter :: Iam = 'output_ObsFcstAna' character(len=400) :: err_msg @@ -1704,14 +1698,7 @@ subroutine output_ObsFcstAna(date_time, exp_id, & ! write to file - call obsfcstana_mode_to_format(out_ObsFcstAna_mode, out_ObsFcstAna_format, mode_ok) - if (.not. mode_ok) then - err_msg = 'unknown out_ObsFcstAna_mode' - call ldas_abort(LDAS_GENERIC_ERROR, Iam, err_msg) - end if - - if (out_ObsFcstAna_mode == OBSFCSTANA_FMT_BIN .or. & - out_ObsFcstAna_mode == OBSFCSTANA_FMT_BOTH) then + if (out_ObsFcstAna == 1 .or. out_ObsFcstAna == 3) then fname = get_io_filename( './', exp_id, file_tag, date_time=date_time, & dir_name=dir_name, ens_id=-1, no_subdirs=.true. ) @@ -1756,16 +1743,14 @@ subroutine output_ObsFcstAna(date_time, exp_id, & end if - if (out_ObsFcstAna_mode == OBSFCSTANA_FMT_NC4 .or. & - out_ObsFcstAna_mode == OBSFCSTANA_FMT_BOTH) then + if (out_ObsFcstAna == 2 .or. out_ObsFcstAna == 3) then file_ext = '.nc4' fname = get_io_filename( './', exp_id, file_tag, date_time=date_time, & dir_name=dir_name, ens_id=-1, file_ext=file_ext, no_subdirs=.true. ) - - call write_ObsFcstAna_nc4(fname, date_time, exp_id, file_tag, N_obsf, & - Observations_f, N_obs_param, obs_param, out_ObsFcstAna_format, & - update_type, dtstep_assim, grid_name) + + call write_ObsFcstAna_nc4(fname, date_time, exp_id, file_tag, N_obsf, & + Observations_f, N_obs_param, obs_param, update_type, dtstep_assim, grid_name) end if @@ -1775,10 +1760,10 @@ subroutine output_ObsFcstAna(date_time, exp_id, & end subroutine output_ObsFcstAna ! ******************************************************************** - - subroutine write_ObsFcstAna_nc4(fname, date_time, exp_id, file_tag, N_obsf, & - Observations_f, N_obs_param, obs_param, out_ObsFcstAna_format, & - update_type, dtstep_assim, grid_name) + + subroutine write_ObsFcstAna_nc4(fname, date_time, exp_id, file_tag, N_obsf, & + Observations_f, N_obs_param, obs_param, update_type, dtstep_assim, grid_name) + use netcdf use pfio_NetCDF_Supplement, only: pfio_nf90_put_var_string @@ -1792,7 +1777,6 @@ subroutine write_ObsFcstAna_nc4(fname, date_time, exp_id, file_tag, N_obsf, & integer, intent(in) :: N_obs_param type(obs_type), dimension(N_obsf), intent(in) :: Observations_f type(obs_param_type), dimension(N_obs_param), intent(in) :: obs_param - character(*), intent(in) :: out_ObsFcstAna_format integer, intent(in) :: update_type integer, intent(in) :: dtstep_assim character(*), intent(in) :: grid_name @@ -1990,23 +1974,23 @@ subroutine write_ObsFcstAna_nc4(fname, date_time, exp_id, file_tag, N_obsf, & end if call nc4_check( nf90_close(ncid) ) - + contains + subroutine nc4_check(status) integer, intent(in) :: status - + if (status /= nf90_noerr) then err_msg = 'NetCDF error in ' // Iam // ': ' // trim(nf90_strerror(status)) call ldas_abort(LDAS_GENERIC_ERROR, Iam, err_msg) end if end subroutine nc4_check - + end subroutine write_ObsFcstAna_nc4 ! ********************************************************************** - + subroutine output_ObsFcstAna_wrapper( out_ObsFcstAna, & - out_ObsFcstAna_mode, & date_time, exp_id, & N_obsl, N_obs_param, N_ens, & N_catl, tile_coord_l, & @@ -2027,8 +2011,6 @@ subroutine output_ObsFcstAna_wrapper( out_ObsFcstAna, & ! major revisions for new obs handling and MPI integer, intent(in) :: out_ObsFcstAna - integer, intent(in) :: out_ObsFcstAna_mode - type(date_time_type), intent(in) :: date_time @@ -2102,7 +2084,7 @@ subroutine output_ObsFcstAna_wrapper( out_ObsFcstAna, & ! write out model, observations, and "OminusA" information call output_ObsFcstAna( date_time, exp_id, N_obsl, & - Observations_l(1:N_obsl), N_obs_param, obs_param, out_ObsFcstAna_mode, & + Observations_l(1:N_obsl), N_obs_param, obs_param, out_ObsFcstAna, & update_type, dtstep_assim, & trim(pert_grid_g%gridtype), rf2f=rf2f ) diff --git a/GEOSlandassim_GridComp/clsm_ensupd_upd_routines.F90 b/GEOSlandassim_GridComp/clsm_ensupd_upd_routines.F90 index f2adbfe8..9c0a2db5 100644 --- a/GEOSlandassim_GridComp/clsm_ensupd_upd_routines.F90 +++ b/GEOSlandassim_GridComp/clsm_ensupd_upd_routines.F90 @@ -161,12 +161,6 @@ module clsm_ensupd_upd_routines public :: get_halo_around_tile public :: TileNnzObs public :: dist_km2deg - public :: obsfcstana_mode_to_format - - integer, parameter, public :: OBSFCSTANA_FMT_OFF = 0 - integer, parameter, public :: OBSFCSTANA_FMT_BIN = 1 - integer, parameter, public :: OBSFCSTANA_FMT_NC4 = 2 - integer, parameter, public :: OBSFCSTANA_FMT_BOTH = 3 ! threshold below which FOV is considered zero (regardless of units) @@ -178,38 +172,6 @@ module clsm_ensupd_upd_routines contains - ! ******************************************************************** - - subroutine obsfcstana_mode_to_format(mode, format, is_valid) - - implicit none - - integer, intent(in) :: mode - character(*), intent(out) :: format - logical, optional, intent(out) :: is_valid - - logical :: valid_mode - - valid_mode = .true. - - select case (mode) - case (OBSFCSTANA_FMT_OFF) - format = 'OFF' - case (OBSFCSTANA_FMT_BIN) - format = 'BIN' - case (OBSFCSTANA_FMT_NC4) - format = 'NC4' - case (OBSFCSTANA_FMT_BOTH) - format = 'BOTH' - case default - format = 'UNKNOWN' - valid_mode = .false. - end select - - if (present(is_valid)) is_valid = valid_mode - - end subroutine obsfcstana_mode_to_format - ! ******************************************************************** subroutine read_ens_upd_inputs( & @@ -227,7 +189,6 @@ subroutine read_ens_upd_inputs( & obs_param, & out_obslog, & out_ObsFcstAna, & - out_ObsFcstAna_mode, & out_smapL4SMaup, & N_obsbias_max & ) @@ -275,7 +236,6 @@ subroutine read_ens_upd_inputs( & logical, intent(out) :: out_obslog integer, intent(out) :: out_ObsFcstAna - integer, intent(out) :: out_ObsFcstAna_mode logical, intent(out) :: out_smapL4SMaup integer, intent(out) :: N_obsbias_max @@ -332,7 +292,6 @@ subroutine read_ens_upd_inputs( & ens_upd_inputs_path = '.' ! set default ens_upd_inputs_file = 'LDASsa_DEFAULT_inputs_ensupd.nml' - out_ObsFcstAna = OBSFCSTANA_FMT_OFF ! Read data from default ens_upd_inputs namelist file @@ -343,11 +302,10 @@ subroutine read_ens_upd_inputs( & if (logit) write (logunit,*) if (logit) write (logunit,'(400A)') 'reading *default* EnKF inputs from ' // trim(fname) if (logit) write (logunit,*) - + read (10, nml=ens_upd_inputs, iostat=ios, iomsg=iomsg) if (ios /= 0) then - err_msg = 'error reading ens_upd_inputs from ' // trim(fname) // & - '; out_ObsFcstAna must be integer 0/1/2/3 (OFF/BIN/NC4/BOTH). ' // trim(iomsg) + err_msg = 'Error reading ens_upd_inputs. NOTE: "out_ObsFcstAna" must be integer. ' // trim(iomsg) call ldas_abort(LDAS_GENERIC_ERROR, Iam, err_msg) end if @@ -375,8 +333,7 @@ subroutine read_ens_upd_inputs( & read (10, nml=ens_upd_inputs, iostat=ios, iomsg=iomsg) if (ios /= 0) then - err_msg = 'error reading ens_upd_inputs from ' // trim(fname) // & - '; out_ObsFcstAna must be integer 0/1/2/3 (OFF/BIN/NC4/BOTH). ' // trim(iomsg) + err_msg = 'Error reading ens_upd_inputs. NOTE: "out_ObsFcstAna" must be integer. ' // trim(iomsg) call ldas_abort(LDAS_GENERIC_ERROR, Iam, err_msg) end if @@ -394,21 +351,11 @@ subroutine read_ens_upd_inputs( & ! ! consistency checks etc - select case (out_ObsFcstAna) - case (0) - out_ObsFcstAna_mode = OBSFCSTANA_FMT_OFF - case (1) - out_ObsFcstAna_mode = OBSFCSTANA_FMT_BIN - case (2) - out_ObsFcstAna_mode = OBSFCSTANA_FMT_NC4 - case (3) - out_ObsFcstAna_mode = OBSFCSTANA_FMT_BOTH - case default + if (out_ObsFcstAna<0 .or. out_ObsFcstAna>3) then write(tmpstring6,'(I6)') out_ObsFcstAna - err_msg = 'unknown integer value of "out_ObsFcstAna": "' // & - trim(tmpstring6) // '". Use 0=OFF, 1=BIN, 2=NC4, 3=BOTH.' + err_msg = 'Unknown integer value of "out_ObsFcstAna": "' // trim(tmpstring6) // '". Use 0=OFF, 1=BIN, 2=NC4, 3=BIN and NC4.' call ldas_abort(LDAS_GENERIC_ERROR, Iam, err_msg) - end select + end if if (update_type==0) then err_msg = 'executable was built for assimilation but update_type=0' From f46a93e74e306c517e45563db4f10d23db9f5514 Mon Sep 17 00:00:00 2001 From: Rolf Reichle Date: Mon, 13 Apr 2026 13:47:33 -0400 Subject: [PATCH 28/40] removed unused variables from argument list of write_ObsFcstAna_nc4(); edited comments; improved vertical alignment (clsm_ensupd_enkf_update.F90) --- .../clsm_ensupd_enkf_update.F90 | 264 +++++++++--------- 1 file changed, 136 insertions(+), 128 deletions(-) diff --git a/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 b/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 index 95b86fdf..c0263436 100644 --- a/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 +++ b/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 @@ -1746,41 +1746,38 @@ subroutine output_ObsFcstAna(date_time, exp_id, & if (out_ObsFcstAna == 2 .or. out_ObsFcstAna == 3) then file_ext = '.nc4' - fname = get_io_filename( './', exp_id, file_tag, date_time=date_time, & + + fname = get_io_filename( './', exp_id, file_tag, date_time=date_time, & dir_name=dir_name, ens_id=-1, file_ext=file_ext, no_subdirs=.true. ) - call write_ObsFcstAna_nc4(fname, date_time, exp_id, file_tag, N_obsf, & - Observations_f, N_obs_param, obs_param, update_type, dtstep_assim, grid_name) - + call write_ObsFcstAna_nc4( fname, exp_id, N_obsf, Observations_f, & + N_obs_param, obs_param, update_type, dtstep_assim, grid_name) + end if end if + if (allocated(Observations_f)) deallocate(Observations_f) - + end subroutine output_ObsFcstAna ! ******************************************************************** - subroutine write_ObsFcstAna_nc4(fname, date_time, exp_id, file_tag, N_obsf, & - Observations_f, N_obs_param, obs_param, update_type, dtstep_assim, grid_name) + subroutine write_ObsFcstAna_nc4(fname, exp_id, N_obsf, Observations_f, & + N_obs_param, obs_param, update_type, dtstep_assim, grid_name) use netcdf use pfio_NetCDF_Supplement, only: pfio_nf90_put_var_string implicit none - character(*), intent(in) :: fname - type(date_time_type), intent(in) :: date_time - character(*), intent(in) :: exp_id - character(*), intent(in) :: file_tag - integer, intent(in) :: N_obsf - integer, intent(in) :: N_obs_param - type(obs_type), dimension(N_obsf), intent(in) :: Observations_f + character(*), intent(in) :: fname, exp_id, grid_name + integer, intent(in) :: N_obsf, N_obs_param, update_type, dtstep_assim + type(obs_type), dimension(N_obsf), intent(in) :: Observations_f type(obs_param_type), dimension(N_obs_param), intent(in) :: obs_param - integer, intent(in) :: update_type - integer, intent(in) :: dtstep_assim - character(*), intent(in) :: grid_name + ! ---------------- + integer :: ncid integer :: nobs_dimid integer :: nspecies_dimid @@ -1793,55 +1790,59 @@ subroutine write_ObsFcstAna_nc4(fname, date_time, exp_id, file_tag, N_obsf, integer :: obsparam_getinnov_varid, obsparam_errstd_varid integer :: obsparam_varname_varid, obsparam_units_varid, obsparam_descr_varid - integer, dimension(:), allocatable :: assim_int - integer, dimension(:), allocatable :: species_assim_int, species_scale_int - integer, dimension(:), allocatable :: species_getinnov_int - real, dimension(:), allocatable :: species_errstd_r4 + integer, dimension(:), allocatable :: assim_int + integer, dimension(:), allocatable :: species_assim_int, species_scale_int + integer, dimension(:), allocatable :: species_getinnov_int + real, dimension(:), allocatable :: species_errstd_r4 character(len=40), dimension(:), allocatable :: species_varname_s, species_units_s character(len=40), dimension(:), allocatable :: species_descr_s - character(len=40) :: attr_name - character(len=8) :: write_date_yyyymmdd - character(len=10) :: write_time_hhmmss - character(len=5) :: write_zone - character(len=24) :: write_datetime_iso - character(len=64) :: user_name - character(len=128) :: created_by - integer :: user_len, user_status - integer :: i + character(len=40) :: attr_name + character(len=8) :: write_date_yyyymmdd + character(len=10) :: write_time_hhmmss + character(len=5) :: write_zone + character(len=24) :: write_datetime_iso + character(len=64) :: user_name + character(len=128) :: created_by + integer :: user_len, user_status + integer :: i + character(len=*), parameter :: Iam = 'write_ObsFcstAna_nc4' character(len=400) :: err_msg - call nc4_check( nf90_create(trim(fname), nf90_clobber + NF90_NETCDF4, ncid) ) + call nc4_check( nf90_create( trim(fname), nf90_clobber + NF90_NETCDF4, ncid ) ) - call nc4_check( nf90_def_dim(ncid, 'nobs', NF90_UNLIMITED, nobs_dimid) ) - call nc4_check( nf90_def_dim(ncid, 'n_species', N_obs_param, nspecies_dimid) ) + call nc4_check( nf90_def_dim(ncid, 'nobs', NF90_UNLIMITED, nobs_dimid) ) + call nc4_check( nf90_def_dim(ncid, 'n_species', N_obs_param, nspecies_dimid) ) - call nc4_check( nf90_def_var(ncid, 'assim_flag', NF90_INT, [nobs_dimid], assim_flag_varid) ) - call nc4_check( nf90_def_var(ncid, 'species', NF90_INT, [nobs_dimid], species_varid) ) - call nc4_check( nf90_def_var(ncid, 'tilenum', NF90_INT, [nobs_dimid], tilenum_varid) ) - call nc4_check( nf90_def_var(ncid, 'lon', NF90_FLOAT, [nobs_dimid], lon_varid) ) - call nc4_check( nf90_def_var(ncid, 'lat', NF90_FLOAT, [nobs_dimid], lat_varid) ) - call nc4_check( nf90_def_var(ncid, 'obs', NF90_FLOAT, [nobs_dimid], obs_varid) ) - call nc4_check( nf90_def_var(ncid, 'obsvar', NF90_FLOAT, [nobs_dimid], obsvar_varid) ) - call nc4_check( nf90_def_var(ncid, 'fcst', NF90_FLOAT, [nobs_dimid], fcst_varid) ) - call nc4_check( nf90_def_var(ncid, 'fcstvar', NF90_FLOAT, [nobs_dimid], fcstvar_varid) ) - call nc4_check( nf90_def_var(ncid, 'ana', NF90_FLOAT, [nobs_dimid], ana_varid) ) - call nc4_check( nf90_def_var(ncid, 'anavar', NF90_FLOAT, [nobs_dimid], anavar_varid) ) + call nc4_check( nf90_def_var(ncid, 'assim_flag', NF90_INT, [nobs_dimid], assim_flag_varid) ) + call nc4_check( nf90_def_var(ncid, 'species', NF90_INT, [nobs_dimid], species_varid) ) + call nc4_check( nf90_def_var(ncid, 'tilenum', NF90_INT, [nobs_dimid], tilenum_varid) ) + call nc4_check( nf90_def_var(ncid, 'lon', NF90_FLOAT, [nobs_dimid], lon_varid) ) + call nc4_check( nf90_def_var(ncid, 'lat', NF90_FLOAT, [nobs_dimid], lat_varid) ) + call nc4_check( nf90_def_var(ncid, 'obs', NF90_FLOAT, [nobs_dimid], obs_varid) ) + call nc4_check( nf90_def_var(ncid, 'obsvar', NF90_FLOAT, [nobs_dimid], obsvar_varid) ) + call nc4_check( nf90_def_var(ncid, 'fcst', NF90_FLOAT, [nobs_dimid], fcst_varid) ) + call nc4_check( nf90_def_var(ncid, 'fcstvar', NF90_FLOAT, [nobs_dimid], fcstvar_varid) ) + call nc4_check( nf90_def_var(ncid, 'ana', NF90_FLOAT, [nobs_dimid], ana_varid) ) + call nc4_check( nf90_def_var(ncid, 'anavar', NF90_FLOAT, [nobs_dimid], anavar_varid) ) call nc4_check( nf90_def_var(ncid, 'obsparam_species_id', NF90_INT, [nspecies_dimid], obsparam_species_id_varid) ) - call nc4_check( nf90_def_var(ncid, 'obsparam_assim', NF90_INT, [nspecies_dimid], obsparam_assim_varid) ) - call nc4_check( nf90_def_var(ncid, 'obsparam_scale', NF90_INT, [nspecies_dimid], obsparam_scale_varid) ) - call nc4_check( nf90_def_var(ncid, 'obsparam_getinnov', NF90_INT, [nspecies_dimid], obsparam_getinnov_varid) ) - call nc4_check( nf90_def_var(ncid, 'obsparam_errstd', NF90_FLOAT, [nspecies_dimid], obsparam_errstd_varid) ) - call nc4_check( nf90_def_var(ncid, 'obsparam_varname', NF90_STRING, [nspecies_dimid], obsparam_varname_varid) ) - call nc4_check( nf90_def_var(ncid, 'obsparam_units', NF90_STRING, [nspecies_dimid], obsparam_units_varid) ) - call nc4_check( nf90_def_var(ncid, 'obsparam_descr', NF90_STRING, [nspecies_dimid], obsparam_descr_varid) ) - + call nc4_check( nf90_def_var(ncid, 'obsparam_assim', NF90_INT, [nspecies_dimid], obsparam_assim_varid) ) + call nc4_check( nf90_def_var(ncid, 'obsparam_scale', NF90_INT, [nspecies_dimid], obsparam_scale_varid) ) + call nc4_check( nf90_def_var(ncid, 'obsparam_getinnov', NF90_INT, [nspecies_dimid], obsparam_getinnov_varid) ) + call nc4_check( nf90_def_var(ncid, 'obsparam_errstd', NF90_FLOAT, [nspecies_dimid], obsparam_errstd_varid) ) + call nc4_check( nf90_def_var(ncid, 'obsparam_varname', NF90_STRING, [nspecies_dimid], obsparam_varname_varid) ) + call nc4_check( nf90_def_var(ncid, 'obsparam_units', NF90_STRING, [nspecies_dimid], obsparam_units_varid) ) + call nc4_check( nf90_def_var(ncid, 'obsparam_descr', NF90_STRING, [nspecies_dimid], obsparam_descr_varid) ) + + ! get date and time when file is written call date_and_time(write_date_yyyymmdd, write_time_hhmmss, write_zone) write(write_datetime_iso, '(A4,A1,A2,A1,A2,A1,A2,A1,A2,A1,A2,A5)') & write_date_yyyymmdd(1:4), '-', write_date_yyyymmdd(5:6), '-', & write_date_yyyymmdd(7:8), 'T', write_time_hhmmss(1:2), ':', & write_time_hhmmss(3:4), ':', write_time_hhmmss(5:6), write_zone + + ! get user info (if available) call get_environment_variable('USER', user_name, length=user_len, status=user_status) if (user_status == 0 .and. user_len > 0) then created_by = trim(user_name(1:user_len)) // ' via write_ObsFcstAna_nc4' @@ -1849,10 +1850,12 @@ subroutine write_ObsFcstAna_nc4(fname, date_time, exp_id, file_tag, N_obsf, created_by = 'GEOSldas write_ObsFcstAna_nc4' end if + ! write attributes + call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, 'exp_id', trim(exp_id)) ) call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, 'update_type', update_type) ) call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, 'dtstep_assim', dtstep_assim) ) - + do i=1,N_obs_param write(attr_name, '(A,I0.3,A)') 'species_', obs_param(i)%species, '_descr' call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, trim(attr_name), trim(obs_param(i)%descr)) ) @@ -1861,70 +1864,73 @@ subroutine write_ObsFcstAna_nc4(fname, date_time, exp_id, file_tag, N_obsf, call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, 'Date', trim(write_datetime_iso)) ) call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, 'CreatedBy', trim(created_by)) ) - call nc4_check( nf90_put_att(ncid, assim_flag_varid, 'long_name', 'assimilation flag') ) - call nc4_check( nf90_put_att(ncid, assim_flag_varid, 'units', '1') ) - call nc4_check( nf90_put_att(ncid, species_varid, 'long_name', 'observation species identifier') ) - call nc4_check( nf90_put_att(ncid, species_varid, 'units', '1') ) - call nc4_check( nf90_put_att(ncid, tilenum_varid, 'long_name', 'tile number (full domain)') ) - call nc4_check( nf90_put_att(ncid, tilenum_varid, 'units', '1') ) - call nc4_check( nf90_put_att(ncid, lon_varid, 'long_name', 'observation longitude') ) - call nc4_check( nf90_put_att(ncid, lon_varid, 'units', 'degrees_east') ) - call nc4_check( nf90_put_att(ncid, lon_varid, 'missing_value', nodata_generic) ) - call nc4_check( nf90_put_att(ncid, lat_varid, 'long_name', 'observation latitude') ) - call nc4_check( nf90_put_att(ncid, lat_varid, 'units', 'degrees_north') ) - call nc4_check( nf90_put_att(ncid, lat_varid, 'missing_value', nodata_generic) ) - call nc4_check( nf90_put_att(ncid, obs_varid, 'long_name', 'observation value') ) - call nc4_check( nf90_put_att(ncid, obs_varid, 'units', 'species-dependent') ) - call nc4_check( nf90_put_att(ncid, obs_varid, 'missing_value', nodata_generic) ) - call nc4_check( nf90_put_att(ncid, obsvar_varid, 'long_name', 'observation error variance') ) - call nc4_check( nf90_put_att(ncid, obsvar_varid, 'units', 'species-dependent') ) - call nc4_check( nf90_put_att(ncid, obsvar_varid, 'missing_value', nodata_generic) ) - call nc4_check( nf90_put_att(ncid, fcst_varid, 'long_name', 'forecast observation equivalent') ) - call nc4_check( nf90_put_att(ncid, fcst_varid, 'units', 'species-dependent') ) - call nc4_check( nf90_put_att(ncid, fcst_varid, 'missing_value', nodata_generic) ) - call nc4_check( nf90_put_att(ncid, fcstvar_varid, 'long_name', 'forecast error variance in obs space') ) - call nc4_check( nf90_put_att(ncid, fcstvar_varid, 'units', 'species-dependent') ) - call nc4_check( nf90_put_att(ncid, fcstvar_varid, 'missing_value', nodata_generic) ) - call nc4_check( nf90_put_att(ncid, ana_varid, 'long_name', 'analysis observation equivalent') ) - call nc4_check( nf90_put_att(ncid, ana_varid, 'units', 'species-dependent') ) - call nc4_check( nf90_put_att(ncid, ana_varid, 'missing_value', nodata_generic) ) - call nc4_check( nf90_put_att(ncid, anavar_varid, 'long_name', 'analysis error variance in obs space') ) - call nc4_check( nf90_put_att(ncid, anavar_varid, 'units', 'species-dependent') ) - call nc4_check( nf90_put_att(ncid, anavar_varid, 'missing_value', nodata_generic) ) - + call nc4_check( nf90_put_att(ncid, assim_flag_varid, 'long_name', 'assimilation flag') ) + call nc4_check( nf90_put_att(ncid, assim_flag_varid, 'units', '1') ) + call nc4_check( nf90_put_att(ncid, species_varid, 'long_name', 'observation species identifier') ) + call nc4_check( nf90_put_att(ncid, species_varid, 'units', '1') ) + call nc4_check( nf90_put_att(ncid, tilenum_varid, 'long_name', 'tile number (full domain)') ) + call nc4_check( nf90_put_att(ncid, tilenum_varid, 'units', '1') ) + call nc4_check( nf90_put_att(ncid, lon_varid, 'long_name', 'observation longitude') ) + call nc4_check( nf90_put_att(ncid, lon_varid, 'units', 'degrees_east') ) + call nc4_check( nf90_put_att(ncid, lon_varid, 'missing_value', nodata_generic) ) + call nc4_check( nf90_put_att(ncid, lat_varid, 'long_name', 'observation latitude') ) + call nc4_check( nf90_put_att(ncid, lat_varid, 'units', 'degrees_north') ) + call nc4_check( nf90_put_att(ncid, lat_varid, 'missing_value', nodata_generic) ) + call nc4_check( nf90_put_att(ncid, obs_varid, 'long_name', 'observation value') ) + call nc4_check( nf90_put_att(ncid, obs_varid, 'units', 'species-dependent') ) + call nc4_check( nf90_put_att(ncid, obs_varid, 'missing_value', nodata_generic) ) + call nc4_check( nf90_put_att(ncid, obsvar_varid, 'long_name', 'observation error variance') ) + call nc4_check( nf90_put_att(ncid, obsvar_varid, 'units', 'species-dependent') ) + call nc4_check( nf90_put_att(ncid, obsvar_varid, 'missing_value', nodata_generic) ) + call nc4_check( nf90_put_att(ncid, fcst_varid, 'long_name', 'forecast observation equivalent') ) + call nc4_check( nf90_put_att(ncid, fcst_varid, 'units', 'species-dependent') ) + call nc4_check( nf90_put_att(ncid, fcst_varid, 'missing_value', nodata_generic) ) + call nc4_check( nf90_put_att(ncid, fcstvar_varid, 'long_name', 'forecast error variance in obs space') ) + call nc4_check( nf90_put_att(ncid, fcstvar_varid, 'units', 'species-dependent') ) + call nc4_check( nf90_put_att(ncid, fcstvar_varid, 'missing_value', nodata_generic) ) + call nc4_check( nf90_put_att(ncid, ana_varid, 'long_name', 'analysis observation equivalent') ) + call nc4_check( nf90_put_att(ncid, ana_varid, 'units', 'species-dependent') ) + call nc4_check( nf90_put_att(ncid, ana_varid, 'missing_value', nodata_generic) ) + call nc4_check( nf90_put_att(ncid, anavar_varid, 'long_name', 'analysis error variance in obs space') ) + call nc4_check( nf90_put_att(ncid, anavar_varid, 'units', 'species-dependent') ) + call nc4_check( nf90_put_att(ncid, anavar_varid, 'missing_value', nodata_generic) ) + call nc4_check( nf90_put_att(ncid, obsparam_species_id_varid, 'long_name', 'obs parameter species identifier') ) call nc4_check( nf90_put_att(ncid, obsparam_species_id_varid, 'units', '1') ) - call nc4_check( nf90_put_att(ncid, obsparam_assim_varid, 'long_name', 'obs parameter assimilation flag') ) - call nc4_check( nf90_put_att(ncid, obsparam_assim_varid, 'units', '1') ) - call nc4_check( nf90_put_att(ncid, obsparam_scale_varid, 'long_name', 'obs parameter scaling flag') ) - call nc4_check( nf90_put_att(ncid, obsparam_scale_varid, 'units', '1') ) - call nc4_check( nf90_put_att(ncid, obsparam_getinnov_varid, 'long_name', 'obs parameter innovation-output flag') ) - call nc4_check( nf90_put_att(ncid, obsparam_getinnov_varid, 'units', '1') ) - call nc4_check( nf90_put_att(ncid, obsparam_errstd_varid, 'long_name', 'obs parameter default observation error standard deviation') ) - call nc4_check( nf90_put_att(ncid, obsparam_errstd_varid, 'units', 'species-dependent') ) - call nc4_check( nf90_put_att(ncid, obsparam_varname_varid, 'long_name', 'obs parameter model variable name') ) - call nc4_check( nf90_put_att(ncid, obsparam_units_varid, 'long_name', 'obs parameter observation units') ) - call nc4_check( nf90_put_att(ncid, obsparam_descr_varid, 'long_name', 'obs parameter description') ) + call nc4_check( nf90_put_att(ncid, obsparam_assim_varid, 'long_name', 'obs parameter assimilation flag') ) + call nc4_check( nf90_put_att(ncid, obsparam_assim_varid, 'units', '1') ) + call nc4_check( nf90_put_att(ncid, obsparam_scale_varid, 'long_name', 'obs parameter scaling flag') ) + call nc4_check( nf90_put_att(ncid, obsparam_scale_varid, 'units', '1') ) + call nc4_check( nf90_put_att(ncid, obsparam_getinnov_varid, 'long_name', 'obs parameter innovation-output flag') ) + call nc4_check( nf90_put_att(ncid, obsparam_getinnov_varid, 'units', '1') ) + call nc4_check( nf90_put_att(ncid, obsparam_errstd_varid, 'long_name', 'obs parameter default observation error standard deviation') ) + call nc4_check( nf90_put_att(ncid, obsparam_errstd_varid, 'units', 'species-dependent') ) + call nc4_check( nf90_put_att(ncid, obsparam_varname_varid, 'long_name', 'obs parameter model variable name') ) + call nc4_check( nf90_put_att(ncid, obsparam_units_varid, 'long_name', 'obs parameter observation units') ) + call nc4_check( nf90_put_att(ncid, obsparam_descr_varid, 'long_name', 'obs parameter description') ) call nc4_check( nf90_enddef(ncid) ) + ! write variables + if (N_obsf > 0) then + ! convert logical to integer allocate(assim_int(N_obsf)) assim_int = 0 where (Observations_f(1:N_obsf)%assim) assim_int = 1 - + call nc4_check( nf90_put_var(ncid, assim_flag_varid, assim_int) ) - call nc4_check( nf90_put_var(ncid, species_varid, Observations_f(1:N_obsf)%species) ) - call nc4_check( nf90_put_var(ncid, tilenum_varid, Observations_f(1:N_obsf)%tilenum) ) - call nc4_check( nf90_put_var(ncid, lon_varid, Observations_f(1:N_obsf)%lon) ) - call nc4_check( nf90_put_var(ncid, lat_varid, Observations_f(1:N_obsf)%lat) ) - call nc4_check( nf90_put_var(ncid, obs_varid, Observations_f(1:N_obsf)%obs) ) - call nc4_check( nf90_put_var(ncid, obsvar_varid, Observations_f(1:N_obsf)%obsvar) ) - call nc4_check( nf90_put_var(ncid, fcst_varid, Observations_f(1:N_obsf)%fcst) ) - call nc4_check( nf90_put_var(ncid, fcstvar_varid, Observations_f(1:N_obsf)%fcstvar) ) - call nc4_check( nf90_put_var(ncid, ana_varid, Observations_f(1:N_obsf)%ana) ) - call nc4_check( nf90_put_var(ncid, anavar_varid, Observations_f(1:N_obsf)%anavar) ) + call nc4_check( nf90_put_var(ncid, species_varid, Observations_f(1:N_obsf)%species) ) + call nc4_check( nf90_put_var(ncid, tilenum_varid, Observations_f(1:N_obsf)%tilenum) ) + call nc4_check( nf90_put_var(ncid, lon_varid, Observations_f(1:N_obsf)%lon) ) + call nc4_check( nf90_put_var(ncid, lat_varid, Observations_f(1:N_obsf)%lat) ) + call nc4_check( nf90_put_var(ncid, obs_varid, Observations_f(1:N_obsf)%obs) ) + call nc4_check( nf90_put_var(ncid, obsvar_varid, Observations_f(1:N_obsf)%obsvar) ) + call nc4_check( nf90_put_var(ncid, fcst_varid, Observations_f(1:N_obsf)%fcst) ) + call nc4_check( nf90_put_var(ncid, fcstvar_varid, Observations_f(1:N_obsf)%fcstvar) ) + call nc4_check( nf90_put_var(ncid, ana_varid, Observations_f(1:N_obsf)%ana) ) + call nc4_check( nf90_put_var(ncid, anavar_varid, Observations_f(1:N_obsf)%anavar) ) deallocate(assim_int) @@ -1932,36 +1938,38 @@ subroutine write_ObsFcstAna_nc4(fname, date_time, exp_id, file_tag, N_obsf, if (N_obs_param > 0) then - allocate(species_assim_int(N_obs_param)) - allocate(species_scale_int(N_obs_param)) + allocate(species_assim_int( N_obs_param)) + allocate(species_scale_int( N_obs_param)) allocate(species_getinnov_int(N_obs_param)) - allocate(species_errstd_r4(N_obs_param)) - allocate(species_varname_s(N_obs_param)) - allocate(species_units_s(N_obs_param)) - allocate(species_descr_s(N_obs_param)) - species_assim_int = 0 - species_scale_int = 0 + allocate(species_errstd_r4( N_obs_param)) + allocate(species_varname_s( N_obs_param)) + allocate(species_units_s( N_obs_param)) + allocate(species_descr_s( N_obs_param)) + + species_assim_int = 0 + species_scale_int = 0 species_getinnov_int = 0 - where (obs_param(1:N_obs_param)%assim) species_assim_int = 1 - where (obs_param(1:N_obs_param)%scale) species_scale_int = 1 + ! convert logicals to integer + where (obs_param(1:N_obs_param)%assim) species_assim_int = 1 + where (obs_param(1:N_obs_param)%scale) species_scale_int = 1 where (obs_param(1:N_obs_param)%getinnov) species_getinnov_int = 1 do i=1,N_obs_param - species_errstd_r4(i) = obs_param(i)%errstd + species_errstd_r4(i) = obs_param(i)%errstd species_varname_s(i) = trim(obs_param(i)%varname) - species_units_s(i) = trim(obs_param(i)%units) - species_descr_s(i) = trim(obs_param(i)%descr) + species_units_s(i) = trim(obs_param(i)%units ) + species_descr_s(i) = trim(obs_param(i)%descr ) end do - - call nc4_check( nf90_put_var(ncid, obsparam_species_id_varid, obs_param(1:N_obs_param)%species) ) - call nc4_check( nf90_put_var(ncid, obsparam_assim_varid, species_assim_int) ) - call nc4_check( nf90_put_var(ncid, obsparam_scale_varid, species_scale_int) ) - call nc4_check( nf90_put_var(ncid, obsparam_getinnov_varid, species_getinnov_int) ) - call nc4_check( nf90_put_var(ncid, obsparam_errstd_varid, species_errstd_r4) ) - call nc4_check( pfio_nf90_put_var_string(ncid, obsparam_varname_varid, species_varname_s) ) - call nc4_check( pfio_nf90_put_var_string(ncid, obsparam_units_varid, species_units_s) ) - call nc4_check( pfio_nf90_put_var_string(ncid, obsparam_descr_varid, species_descr_s) ) + + call nc4_check( nf90_put_var( ncid, obsparam_species_id_varid, obs_param(1:N_obs_param)%species) ) + call nc4_check( nf90_put_var( ncid, obsparam_assim_varid, species_assim_int) ) + call nc4_check( nf90_put_var( ncid, obsparam_scale_varid, species_scale_int) ) + call nc4_check( nf90_put_var( ncid, obsparam_getinnov_varid, species_getinnov_int) ) + call nc4_check( nf90_put_var( ncid, obsparam_errstd_varid, species_errstd_r4) ) + call nc4_check( pfio_nf90_put_var_string(ncid, obsparam_varname_varid, species_varname_s) ) + call nc4_check( pfio_nf90_put_var_string(ncid, obsparam_units_varid, species_units_s) ) + call nc4_check( pfio_nf90_put_var_string(ncid, obsparam_descr_varid, species_descr_s) ) deallocate(species_assim_int) deallocate(species_scale_int) From 92c9308c1a25075872506b38a4f66fad50bd13eb Mon Sep 17 00:00:00 2001 From: Rolf Reichle Date: Mon, 13 Apr 2026 13:57:35 -0400 Subject: [PATCH 29/40] edited long_name attributes of ObsFcstAna variables (clsm_ensupd_enkf_update.F90) --- GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 b/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 index c0263436..7c0b5df7 100644 --- a/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 +++ b/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 @@ -1863,13 +1863,14 @@ subroutine write_ObsFcstAna_nc4(fname, exp_id, N_obsf, Observations_f, & call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, 'schema_version', 'ObsFcstAna_nc4_v1') ) call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, 'Date', trim(write_datetime_iso)) ) call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, 'CreatedBy', trim(created_by)) ) - + call nc4_check( nf90_put_att(ncid, assim_flag_varid, 'long_name', 'assimilation flag') ) call nc4_check( nf90_put_att(ncid, assim_flag_varid, 'units', '1') ) call nc4_check( nf90_put_att(ncid, species_varid, 'long_name', 'observation species identifier') ) call nc4_check( nf90_put_att(ncid, species_varid, 'units', '1') ) call nc4_check( nf90_put_att(ncid, tilenum_varid, 'long_name', 'tile number (full domain)') ) call nc4_check( nf90_put_att(ncid, tilenum_varid, 'units', '1') ) + call nc4_check( nf90_put_att(ncid, lon_varid, 'long_name', 'observation longitude') ) call nc4_check( nf90_put_att(ncid, lon_varid, 'units', 'degrees_east') ) call nc4_check( nf90_put_att(ncid, lon_varid, 'missing_value', nodata_generic) ) @@ -1882,16 +1883,16 @@ subroutine write_ObsFcstAna_nc4(fname, exp_id, N_obsf, Observations_f, & call nc4_check( nf90_put_att(ncid, obsvar_varid, 'long_name', 'observation error variance') ) call nc4_check( nf90_put_att(ncid, obsvar_varid, 'units', 'species-dependent') ) call nc4_check( nf90_put_att(ncid, obsvar_varid, 'missing_value', nodata_generic) ) - call nc4_check( nf90_put_att(ncid, fcst_varid, 'long_name', 'forecast observation equivalent') ) + call nc4_check( nf90_put_att(ncid, fcst_varid, 'long_name', 'forecast value (observation equivalent)') ) call nc4_check( nf90_put_att(ncid, fcst_varid, 'units', 'species-dependent') ) call nc4_check( nf90_put_att(ncid, fcst_varid, 'missing_value', nodata_generic) ) - call nc4_check( nf90_put_att(ncid, fcstvar_varid, 'long_name', 'forecast error variance in obs space') ) + call nc4_check( nf90_put_att(ncid, fcstvar_varid, 'long_name', 'forecast error variance in observation space') ) call nc4_check( nf90_put_att(ncid, fcstvar_varid, 'units', 'species-dependent') ) call nc4_check( nf90_put_att(ncid, fcstvar_varid, 'missing_value', nodata_generic) ) - call nc4_check( nf90_put_att(ncid, ana_varid, 'long_name', 'analysis observation equivalent') ) + call nc4_check( nf90_put_att(ncid, ana_varid, 'long_name', 'analysis value (observation equivalent)') ) call nc4_check( nf90_put_att(ncid, ana_varid, 'units', 'species-dependent') ) call nc4_check( nf90_put_att(ncid, ana_varid, 'missing_value', nodata_generic) ) - call nc4_check( nf90_put_att(ncid, anavar_varid, 'long_name', 'analysis error variance in obs space') ) + call nc4_check( nf90_put_att(ncid, anavar_varid, 'long_name', 'analysis error variance in observation space') ) call nc4_check( nf90_put_att(ncid, anavar_varid, 'units', 'species-dependent') ) call nc4_check( nf90_put_att(ncid, anavar_varid, 'missing_value', nodata_generic) ) @@ -1905,6 +1906,7 @@ subroutine write_ObsFcstAna_nc4(fname, exp_id, N_obsf, Observations_f, & call nc4_check( nf90_put_att(ncid, obsparam_getinnov_varid, 'units', '1') ) call nc4_check( nf90_put_att(ncid, obsparam_errstd_varid, 'long_name', 'obs parameter default observation error standard deviation') ) call nc4_check( nf90_put_att(ncid, obsparam_errstd_varid, 'units', 'species-dependent') ) + call nc4_check( nf90_put_att(ncid, obsparam_varname_varid, 'long_name', 'obs parameter model variable name') ) call nc4_check( nf90_put_att(ncid, obsparam_units_varid, 'long_name', 'obs parameter observation units') ) call nc4_check( nf90_put_att(ncid, obsparam_descr_varid, 'long_name', 'obs parameter description') ) From 6d172cb98604b0cb080738fa6f025fcd05e9b91d Mon Sep 17 00:00:00 2001 From: Rolf Reichle Date: Mon, 13 Apr 2026 14:05:30 -0400 Subject: [PATCH 30/40] removed unused "grid_name" from argument list of ObsFcstAna output subroutines (clsm_ensupd_enkf_update.F90) --- .../clsm_ensupd_enkf_update.F90 | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 b/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 index 7c0b5df7..9544ee95 100644 --- a/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 +++ b/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 @@ -1513,7 +1513,7 @@ end subroutine apply_enkf_increments subroutine output_ObsFcstAna(date_time, exp_id, & N_obsl, Observations_l, N_obs_param, obs_param, out_ObsFcstAna, & - update_type, dtstep_assim, grid_name, rf2f) + update_type, dtstep_assim, rf2f) ! obs space output: observations, obs space forecast, obs space analysis, and ! associated error variances @@ -1531,8 +1531,6 @@ subroutine output_ObsFcstAna(date_time, exp_id, & integer, intent(in) :: out_ObsFcstAna integer, intent(in) :: update_type integer, intent(in) :: dtstep_assim - character(*), intent(in) :: grid_name - type(obs_type), dimension(N_obsl), intent(in) :: Observations_l @@ -1751,7 +1749,7 @@ subroutine output_ObsFcstAna(date_time, exp_id, & dir_name=dir_name, ens_id=-1, file_ext=file_ext, no_subdirs=.true. ) call write_ObsFcstAna_nc4( fname, exp_id, N_obsf, Observations_f, & - N_obs_param, obs_param, update_type, dtstep_assim, grid_name) + N_obs_param, obs_param, update_type, dtstep_assim ) end if @@ -1764,14 +1762,14 @@ end subroutine output_ObsFcstAna ! ******************************************************************** subroutine write_ObsFcstAna_nc4(fname, exp_id, N_obsf, Observations_f, & - N_obs_param, obs_param, update_type, dtstep_assim, grid_name) + N_obs_param, obs_param, update_type, dtstep_assim ) use netcdf use pfio_NetCDF_Supplement, only: pfio_nf90_put_var_string implicit none - character(*), intent(in) :: fname, exp_id, grid_name + character(*), intent(in) :: fname, exp_id integer, intent(in) :: N_obsf, N_obs_param, update_type, dtstep_assim type(obs_type), dimension(N_obsf), intent(in) :: Observations_f type(obs_param_type), dimension(N_obs_param), intent(in) :: obs_param @@ -2093,10 +2091,9 @@ subroutine output_ObsFcstAna_wrapper( out_ObsFcstAna, & ! write out model, observations, and "OminusA" information - call output_ObsFcstAna( date_time, exp_id, N_obsl, & - Observations_l(1:N_obsl), N_obs_param, obs_param, out_ObsFcstAna, & - update_type, dtstep_assim, & - trim(pert_grid_g%gridtype), rf2f=rf2f ) + call output_ObsFcstAna( date_time, exp_id, N_obsl, & + Observations_l(1:N_obsl), N_obs_param, obs_param, out_ObsFcstAna, & + update_type, dtstep_assim, rf2f=rf2f ) end if From 3f1ed0fbb0f69b10e6322fa19ef840cc9c681ee1 Mon Sep 17 00:00:00 2001 From: Rolf Reichle Date: Wed, 15 Apr 2026 16:23:49 -0400 Subject: [PATCH 31/40] edited meta-data written into ObsFcstAna nc4 output files (clsm_ensupd_enkf_update.F90) --- .../clsm_ensupd_enkf_update.F90 | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 b/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 index 9544ee95..949a60a6 100644 --- a/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 +++ b/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 @@ -1843,24 +1843,31 @@ subroutine write_ObsFcstAna_nc4(fname, exp_id, N_obsf, Observations_f, & ! get user info (if available) call get_environment_variable('USER', user_name, length=user_len, status=user_status) if (user_status == 0 .and. user_len > 0) then - created_by = trim(user_name(1:user_len)) // ' via write_ObsFcstAna_nc4' + created_by = trim(user_name(1:user_len)) // ' via write_ObsFcstAna_nc4()' else created_by = 'GEOSldas write_ObsFcstAna_nc4' end if ! write attributes + + call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, 'Comment', 'NetCDF-4') ) + call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, 'Contact', '') ) + call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, 'Conventions', 'CF') ) + call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, 'Filename', trim(fname)) ) + call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, 'Institution', 'NASA Global Modeling and Assimilation Office') ) + call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, 'Source', 'Experiment_ID: ' // trim(exp_id)) ) + call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, 'Title' , 'Observation-space,Single-Level,Assimilation,Land Surface Diagnostics') ) - call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, 'exp_id', trim(exp_id)) ) - call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, 'update_type', update_type) ) - call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, 'dtstep_assim', dtstep_assim) ) + call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, 'GEOSldas_update_type', update_type) ) + call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, 'GEOSldas_assimilation_time_step_in_seconds', dtstep_assim) ) do i=1,N_obs_param - write(attr_name, '(A,I0.3,A)') 'species_', obs_param(i)%species, '_descr' + write(attr_name, '(A,I0.3,A)') 'GEOSldas_observation_species_', obs_param(i)%species, '_descr' call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, trim(attr_name), trim(obs_param(i)%descr)) ) end do call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, 'schema_version', 'ObsFcstAna_nc4_v1') ) - call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, 'Date', trim(write_datetime_iso)) ) - call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, 'CreatedBy', trim(created_by)) ) + call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, 'DateCreated', trim(write_datetime_iso)) ) + call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, 'CreatedBy', trim(created_by)) ) call nc4_check( nf90_put_att(ncid, assim_flag_varid, 'long_name', 'assimilation flag') ) call nc4_check( nf90_put_att(ncid, assim_flag_varid, 'units', '1') ) @@ -1870,9 +1877,11 @@ subroutine write_ObsFcstAna_nc4(fname, exp_id, N_obsf, Observations_f, & call nc4_check( nf90_put_att(ncid, tilenum_varid, 'units', '1') ) call nc4_check( nf90_put_att(ncid, lon_varid, 'long_name', 'observation longitude') ) + call nc4_check( nf90_put_att(ncid, lon_varid, 'standard_name', 'longitude') ) call nc4_check( nf90_put_att(ncid, lon_varid, 'units', 'degrees_east') ) call nc4_check( nf90_put_att(ncid, lon_varid, 'missing_value', nodata_generic) ) call nc4_check( nf90_put_att(ncid, lat_varid, 'long_name', 'observation latitude') ) + call nc4_check( nf90_put_att(ncid, lat_varid, 'standard_name', 'latitude') ) call nc4_check( nf90_put_att(ncid, lat_varid, 'units', 'degrees_north') ) call nc4_check( nf90_put_att(ncid, lat_varid, 'missing_value', nodata_generic) ) call nc4_check( nf90_put_att(ncid, obs_varid, 'long_name', 'observation value') ) From 73baa9064456bb77140275437d9454627d7420ac Mon Sep 17 00:00:00 2001 From: Rolf Reichle Date: Fri, 17 Apr 2026 16:45:28 -0400 Subject: [PATCH 32/40] updated obs_param structure and ObsFcstAna nc4 output (clsm_ensupd_enkf_update.F90, clsm_ensupd_upd_routines.F90, LDASsa_DEFAULT_inputs_ensupd.nml, read_obsparam.m, LDAS_ensdrv_mpi.F90, enkf_types.F90) - added %fcstvarname and %fcstunits fields to obs_param structure - improved obs_param documentation - edited long_name attributes in ObsFcstAna nc4 output - removed obs_param%get_innov from ObsFcstAna nc4 output --- .../clsm_ensupd_enkf_update.F90 | 137 +++++++-------- .../clsm_ensupd_upd_routines.F90 | 47 ++++++ GEOSldas_App/LDASsa_DEFAULT_inputs_ensupd.nml | 156 +++++++++++++++--- .../util/shared/matlab/read_obsparam.m | 32 ++-- LDAS_Shared/LDAS_ensdrv_mpi.F90 | 4 +- LDAS_Shared/enkf_types.F90 | 143 ++++++++-------- 6 files changed, 351 insertions(+), 168 deletions(-) diff --git a/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 b/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 index 949a60a6..d91a151c 100644 --- a/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 +++ b/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 @@ -1785,14 +1785,15 @@ subroutine write_ObsFcstAna_nc4(fname, exp_id, N_obsf, Observations_f, & integer :: fcst_varid, fcstvar_varid integer :: ana_varid, anavar_varid integer :: obsparam_species_id_varid, obsparam_assim_varid, obsparam_scale_varid - integer :: obsparam_getinnov_varid, obsparam_errstd_varid - integer :: obsparam_varname_varid, obsparam_units_varid, obsparam_descr_varid + integer :: obsparam_errstd_varid + integer :: obsparam_varname_varid, obsparam_units_varid, obsparam_descr_varid + integer :: obsparam_fcstvarname_varid, obsparam_fcstunits_varid integer, dimension(:), allocatable :: assim_int integer, dimension(:), allocatable :: species_assim_int, species_scale_int - integer, dimension(:), allocatable :: species_getinnov_int real, dimension(:), allocatable :: species_errstd_r4 - character(len=40), dimension(:), allocatable :: species_varname_s, species_units_s + character(len=40), dimension(:), allocatable :: species_varname_s, species_units_s + character(len=40), dimension(:), allocatable :: species_fcstvarname_s, species_fcstunits_s character(len=40), dimension(:), allocatable :: species_descr_s character(len=40) :: attr_name character(len=8) :: write_date_yyyymmdd @@ -1827,10 +1828,11 @@ subroutine write_ObsFcstAna_nc4(fname, exp_id, N_obsf, Observations_f, & call nc4_check( nf90_def_var(ncid, 'obsparam_species_id', NF90_INT, [nspecies_dimid], obsparam_species_id_varid) ) call nc4_check( nf90_def_var(ncid, 'obsparam_assim', NF90_INT, [nspecies_dimid], obsparam_assim_varid) ) call nc4_check( nf90_def_var(ncid, 'obsparam_scale', NF90_INT, [nspecies_dimid], obsparam_scale_varid) ) - call nc4_check( nf90_def_var(ncid, 'obsparam_getinnov', NF90_INT, [nspecies_dimid], obsparam_getinnov_varid) ) call nc4_check( nf90_def_var(ncid, 'obsparam_errstd', NF90_FLOAT, [nspecies_dimid], obsparam_errstd_varid) ) call nc4_check( nf90_def_var(ncid, 'obsparam_varname', NF90_STRING, [nspecies_dimid], obsparam_varname_varid) ) call nc4_check( nf90_def_var(ncid, 'obsparam_units', NF90_STRING, [nspecies_dimid], obsparam_units_varid) ) + call nc4_check( nf90_def_var(ncid, 'obsparam_fcstvarname', NF90_STRING, [nspecies_dimid], obsparam_fcstvarname_varid) ) + call nc4_check( nf90_def_var(ncid, 'obsparam_fcstunits', NF90_STRING, [nspecies_dimid], obsparam_fcstunits_varid) ) call nc4_check( nf90_def_var(ncid, 'obsparam_descr', NF90_STRING, [nspecies_dimid], obsparam_descr_varid) ) ! get date and time when file is written @@ -1869,54 +1871,54 @@ subroutine write_ObsFcstAna_nc4(fname, exp_id, N_obsf, Observations_f, & call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, 'DateCreated', trim(write_datetime_iso)) ) call nc4_check( nf90_put_att(ncid, NF90_GLOBAL, 'CreatedBy', trim(created_by)) ) - call nc4_check( nf90_put_att(ncid, assim_flag_varid, 'long_name', 'assimilation flag') ) - call nc4_check( nf90_put_att(ncid, assim_flag_varid, 'units', '1') ) - call nc4_check( nf90_put_att(ncid, species_varid, 'long_name', 'observation species identifier') ) - call nc4_check( nf90_put_att(ncid, species_varid, 'units', '1') ) - call nc4_check( nf90_put_att(ncid, tilenum_varid, 'long_name', 'tile number (full domain)') ) - call nc4_check( nf90_put_att(ncid, tilenum_varid, 'units', '1') ) + call nc4_check( nf90_put_att(ncid, assim_flag_varid, 'long_name', 'observation assimilation flag (0=not assimilated, 1=assimilated)') ) + call nc4_check( nf90_put_att(ncid, assim_flag_varid, 'units', '1') ) + call nc4_check( nf90_put_att(ncid, species_varid, 'long_name', 'observation species identifier') ) + call nc4_check( nf90_put_att(ncid, species_varid, 'units', '1') ) + call nc4_check( nf90_put_att(ncid, tilenum_varid, 'long_name', 'model tile number associated with observation location') ) + call nc4_check( nf90_put_att(ncid, tilenum_varid, 'units', '1') ) + + call nc4_check( nf90_put_att(ncid, lon_varid, 'long_name', 'observation longitude') ) + call nc4_check( nf90_put_att(ncid, lon_varid, 'standard_name', 'longitude') ) + call nc4_check( nf90_put_att(ncid, lon_varid, 'units', 'degrees_east') ) + call nc4_check( nf90_put_att(ncid, lon_varid, 'missing_value', nodata_generic) ) + call nc4_check( nf90_put_att(ncid, lat_varid, 'long_name', 'observation latitude') ) + call nc4_check( nf90_put_att(ncid, lat_varid, 'standard_name', 'latitude') ) + call nc4_check( nf90_put_att(ncid, lat_varid, 'units', 'degrees_north') ) + call nc4_check( nf90_put_att(ncid, lat_varid, 'missing_value', nodata_generic) ) + call nc4_check( nf90_put_att(ncid, obs_varid, 'long_name', 'observation value (after scaling)') ) + call nc4_check( nf90_put_att(ncid, obs_varid, 'units', 'species-dependent') ) + call nc4_check( nf90_put_att(ncid, obs_varid, 'missing_value', nodata_generic) ) + call nc4_check( nf90_put_att(ncid, obsvar_varid, 'long_name', 'observation error variance (after scaling)') ) + call nc4_check( nf90_put_att(ncid, obsvar_varid, 'units', 'species-dependent') ) + call nc4_check( nf90_put_att(ncid, obsvar_varid, 'missing_value', nodata_generic) ) + call nc4_check( nf90_put_att(ncid, fcst_varid, 'long_name', 'observation-equivalent model forecast value') ) + call nc4_check( nf90_put_att(ncid, fcst_varid, 'units', 'species-dependent') ) + call nc4_check( nf90_put_att(ncid, fcst_varid, 'missing_value', nodata_generic) ) + call nc4_check( nf90_put_att(ncid, fcstvar_varid, 'long_name', 'observation-equivalent model forecast error variance') ) + call nc4_check( nf90_put_att(ncid, fcstvar_varid, 'units', 'species-dependent') ) + call nc4_check( nf90_put_att(ncid, fcstvar_varid, 'missing_value', nodata_generic) ) + call nc4_check( nf90_put_att(ncid, ana_varid, 'long_name', 'observation-equivalent model analysis value') ) + call nc4_check( nf90_put_att(ncid, ana_varid, 'units', 'species-dependent') ) + call nc4_check( nf90_put_att(ncid, ana_varid, 'missing_value', nodata_generic) ) + call nc4_check( nf90_put_att(ncid, anavar_varid, 'long_name', 'observation-equivalent model analysis error variance') ) + call nc4_check( nf90_put_att(ncid, anavar_varid, 'units', 'species-dependent') ) + call nc4_check( nf90_put_att(ncid, anavar_varid, 'missing_value', nodata_generic) ) - call nc4_check( nf90_put_att(ncid, lon_varid, 'long_name', 'observation longitude') ) - call nc4_check( nf90_put_att(ncid, lon_varid, 'standard_name', 'longitude') ) - call nc4_check( nf90_put_att(ncid, lon_varid, 'units', 'degrees_east') ) - call nc4_check( nf90_put_att(ncid, lon_varid, 'missing_value', nodata_generic) ) - call nc4_check( nf90_put_att(ncid, lat_varid, 'long_name', 'observation latitude') ) - call nc4_check( nf90_put_att(ncid, lat_varid, 'standard_name', 'latitude') ) - call nc4_check( nf90_put_att(ncid, lat_varid, 'units', 'degrees_north') ) - call nc4_check( nf90_put_att(ncid, lat_varid, 'missing_value', nodata_generic) ) - call nc4_check( nf90_put_att(ncid, obs_varid, 'long_name', 'observation value') ) - call nc4_check( nf90_put_att(ncid, obs_varid, 'units', 'species-dependent') ) - call nc4_check( nf90_put_att(ncid, obs_varid, 'missing_value', nodata_generic) ) - call nc4_check( nf90_put_att(ncid, obsvar_varid, 'long_name', 'observation error variance') ) - call nc4_check( nf90_put_att(ncid, obsvar_varid, 'units', 'species-dependent') ) - call nc4_check( nf90_put_att(ncid, obsvar_varid, 'missing_value', nodata_generic) ) - call nc4_check( nf90_put_att(ncid, fcst_varid, 'long_name', 'forecast value (observation equivalent)') ) - call nc4_check( nf90_put_att(ncid, fcst_varid, 'units', 'species-dependent') ) - call nc4_check( nf90_put_att(ncid, fcst_varid, 'missing_value', nodata_generic) ) - call nc4_check( nf90_put_att(ncid, fcstvar_varid, 'long_name', 'forecast error variance in observation space') ) - call nc4_check( nf90_put_att(ncid, fcstvar_varid, 'units', 'species-dependent') ) - call nc4_check( nf90_put_att(ncid, fcstvar_varid, 'missing_value', nodata_generic) ) - call nc4_check( nf90_put_att(ncid, ana_varid, 'long_name', 'analysis value (observation equivalent)') ) - call nc4_check( nf90_put_att(ncid, ana_varid, 'units', 'species-dependent') ) - call nc4_check( nf90_put_att(ncid, ana_varid, 'missing_value', nodata_generic) ) - call nc4_check( nf90_put_att(ncid, anavar_varid, 'long_name', 'analysis error variance in observation space') ) - call nc4_check( nf90_put_att(ncid, anavar_varid, 'units', 'species-dependent') ) - call nc4_check( nf90_put_att(ncid, anavar_varid, 'missing_value', nodata_generic) ) + call nc4_check( nf90_put_att(ncid, obsparam_species_id_varid, 'long_name', 'species-level observation parameter: observation species identifier') ) + call nc4_check( nf90_put_att(ncid, obsparam_species_id_varid, 'units', '1') ) + call nc4_check( nf90_put_att(ncid, obsparam_assim_varid, 'long_name', 'species-level observation parameter: observation assimilation flag') ) + call nc4_check( nf90_put_att(ncid, obsparam_assim_varid, 'units', '1') ) + call nc4_check( nf90_put_att(ncid, obsparam_scale_varid, 'long_name', 'species-level observation parameter: observation scaling flag') ) + call nc4_check( nf90_put_att(ncid, obsparam_scale_varid, 'units', '1') ) + call nc4_check( nf90_put_att(ncid, obsparam_errstd_varid, 'long_name', 'species-level observation parameter: default observation error standard deviation (before scaling)') ) + call nc4_check( nf90_put_att(ncid, obsparam_errstd_varid, 'units', 'species-dependent') ) - call nc4_check( nf90_put_att(ncid, obsparam_species_id_varid, 'long_name', 'obs parameter species identifier') ) - call nc4_check( nf90_put_att(ncid, obsparam_species_id_varid, 'units', '1') ) - call nc4_check( nf90_put_att(ncid, obsparam_assim_varid, 'long_name', 'obs parameter assimilation flag') ) - call nc4_check( nf90_put_att(ncid, obsparam_assim_varid, 'units', '1') ) - call nc4_check( nf90_put_att(ncid, obsparam_scale_varid, 'long_name', 'obs parameter scaling flag') ) - call nc4_check( nf90_put_att(ncid, obsparam_scale_varid, 'units', '1') ) - call nc4_check( nf90_put_att(ncid, obsparam_getinnov_varid, 'long_name', 'obs parameter innovation-output flag') ) - call nc4_check( nf90_put_att(ncid, obsparam_getinnov_varid, 'units', '1') ) - call nc4_check( nf90_put_att(ncid, obsparam_errstd_varid, 'long_name', 'obs parameter default observation error standard deviation') ) - call nc4_check( nf90_put_att(ncid, obsparam_errstd_varid, 'units', 'species-dependent') ) - - call nc4_check( nf90_put_att(ncid, obsparam_varname_varid, 'long_name', 'obs parameter model variable name') ) - call nc4_check( nf90_put_att(ncid, obsparam_units_varid, 'long_name', 'obs parameter observation units') ) - call nc4_check( nf90_put_att(ncid, obsparam_descr_varid, 'long_name', 'obs parameter description') ) + call nc4_check( nf90_put_att(ncid, obsparam_varname_varid, 'long_name', 'species-level observation parameter: observation native variable name (before scaling)') ) + call nc4_check( nf90_put_att(ncid, obsparam_units_varid, 'long_name', 'species-level observation parameter: observation native units (before scaling)') ) + call nc4_check( nf90_put_att(ncid, obsparam_fcstvarname_varid, 'long_name', 'species-level observation parameter: observation-equivalent model variable name') ) + call nc4_check( nf90_put_att(ncid, obsparam_fcstunits_varid, 'long_name', 'species-level observation parameter: observation-equivalent model units') ) + call nc4_check( nf90_put_att(ncid, obsparam_descr_varid, 'long_name', 'species-level observation parameter: observation description') ) call nc4_check( nf90_enddef(ncid) ) @@ -1947,45 +1949,48 @@ subroutine write_ObsFcstAna_nc4(fname, exp_id, N_obsf, Observations_f, & if (N_obs_param > 0) then - allocate(species_assim_int( N_obs_param)) - allocate(species_scale_int( N_obs_param)) - allocate(species_getinnov_int(N_obs_param)) - allocate(species_errstd_r4( N_obs_param)) - allocate(species_varname_s( N_obs_param)) - allocate(species_units_s( N_obs_param)) - allocate(species_descr_s( N_obs_param)) + allocate(species_assim_int( N_obs_param)) + allocate(species_scale_int( N_obs_param)) + allocate(species_errstd_r4( N_obs_param)) + allocate(species_varname_s( N_obs_param)) + allocate(species_units_s( N_obs_param)) + allocate(species_fcstvarname_s(N_obs_param)) + allocate(species_fcstunits_s( N_obs_param)) + allocate(species_descr_s( N_obs_param)) species_assim_int = 0 species_scale_int = 0 - species_getinnov_int = 0 ! convert logicals to integer where (obs_param(1:N_obs_param)%assim) species_assim_int = 1 where (obs_param(1:N_obs_param)%scale) species_scale_int = 1 - where (obs_param(1:N_obs_param)%getinnov) species_getinnov_int = 1 do i=1,N_obs_param - species_errstd_r4(i) = obs_param(i)%errstd - species_varname_s(i) = trim(obs_param(i)%varname) - species_units_s(i) = trim(obs_param(i)%units ) - species_descr_s(i) = trim(obs_param(i)%descr ) + species_errstd_r4(i) = obs_param(i)%errstd + species_varname_s(i) = trim(obs_param(i)%varname) + species_units_s(i) = trim(obs_param(i)%units ) + species_fcstvarname_s(i) = trim(obs_param(i)%fcstvarname) + species_fcstunits_s(i) = trim(obs_param(i)%fcstunits ) + species_descr_s(i) = trim(obs_param(i)%descr ) end do call nc4_check( nf90_put_var( ncid, obsparam_species_id_varid, obs_param(1:N_obs_param)%species) ) call nc4_check( nf90_put_var( ncid, obsparam_assim_varid, species_assim_int) ) call nc4_check( nf90_put_var( ncid, obsparam_scale_varid, species_scale_int) ) - call nc4_check( nf90_put_var( ncid, obsparam_getinnov_varid, species_getinnov_int) ) call nc4_check( nf90_put_var( ncid, obsparam_errstd_varid, species_errstd_r4) ) call nc4_check( pfio_nf90_put_var_string(ncid, obsparam_varname_varid, species_varname_s) ) call nc4_check( pfio_nf90_put_var_string(ncid, obsparam_units_varid, species_units_s) ) + call nc4_check( pfio_nf90_put_var_string(ncid, obsparam_fcstvarname_varid, species_fcstvarname_s) ) + call nc4_check( pfio_nf90_put_var_string(ncid, obsparam_fcstunits_varid, species_fcstunits_s) ) call nc4_check( pfio_nf90_put_var_string(ncid, obsparam_descr_varid, species_descr_s) ) deallocate(species_assim_int) deallocate(species_scale_int) - deallocate(species_getinnov_int) deallocate(species_errstd_r4) deallocate(species_varname_s) deallocate(species_units_s) + deallocate(species_fcstvarname_s) + deallocate(species_fcstunits_s) deallocate(species_descr_s) end if diff --git a/GEOSlandassim_GridComp/clsm_ensupd_upd_routines.F90 b/GEOSlandassim_GridComp/clsm_ensupd_upd_routines.F90 index 9c0a2db5..fff6bb51 100644 --- a/GEOSlandassim_GridComp/clsm_ensupd_upd_routines.F90 +++ b/GEOSlandassim_GridComp/clsm_ensupd_upd_routines.F90 @@ -332,6 +332,8 @@ subroutine read_ens_upd_inputs( & if (logit) write (logunit,*) read (10, nml=ens_upd_inputs, iostat=ios, iomsg=iomsg) + + ! if read error, exit gracefully; hint at possible cause (out_ObsFcstAna changed from logical to integer, Apr 2026) if (ios /= 0) then err_msg = 'Error reading ens_upd_inputs. NOTE: "out_ObsFcstAna" must be integer. ' // trim(iomsg) call ldas_abort(LDAS_GENERIC_ERROR, Iam, err_msg) @@ -347,6 +349,46 @@ subroutine read_ens_upd_inputs( & ! ! none implemented so far (reichle, 19 Jul 2005) + + ! ----------------------------------------------------------------- + + ! fill obs_param%fcstvarname and obs_param%fcstunits + + do i=1,N_obs_species_nml + + ! expect "fcstvarname" and "fcstunits" to be 'NULL'; they are not meant to be filled by the user in the config nml file + + if ( .not. ( (trim(obs_param_nml(i)%fcstvarname) /= 'NULL') .or. (trim(obs_param_nml(i)%fcstvarname) /= 'null') ) ) & + call ldas_abort(LDAS_GENERIC_ERROR, Iam, 'obs_param_nml%fcstvarname must be NULL on input') + + if ( .not. ( (trim(obs_param_nml(i)%fcstunits ) /= 'NULL') .or. (trim(obs_param_nml(i)%fcstunits ) /= 'null') ) ) & + call ldas_abort(LDAS_GENERIC_ERROR, Iam, 'obs_param_nml%fcstunits must be NULL on input') + + ! IMPORTANT: Must maintain consistency in the mapping between obs and model variables + ! that is encoded here with that in get_obs_pred(). + + select case (trim(obs_param_nml(i)%varname)) + + case ('sfmc', 'sfds') + + ! NOTE: 'sfds' is scaled into volumetric units of sfmc + + obs_param_nml(i)%fcstvarname = 'sfmc' + obs_param_nml(i)%fcstunits = 'm3 m-3' + + case ('rzmc', 'tsurf', 'FT', 'Tb', 'asnow') + + obs_param_nml(i)%fcstvarname = obs_param_nml(i)%varname + obs_param_nml(i)%fcstunits = obs_param_nml(i)%units + + case default + + call ldas_abort(LDAS_GENERIC_ERROR, Iam, 'unknown obs_param_nml%varname') + + end select + + end do + ! ----------------------------------------------------------------- ! ! consistency checks etc @@ -362,6 +404,8 @@ subroutine read_ens_upd_inputs( & call ldas_abort(LDAS_GENERIC_ERROR, Iam, err_msg) end if + ! obs bias init and checks + N_obsbias_max = 0 ! initialize do i=1,N_obs_species_nml @@ -1182,6 +1226,9 @@ subroutine get_obs_pred( & do i=1,N_obs_param + ! IMPORTANT: Must maintain consistency in the mapping between obs and model ("lH") variables + ! that is encoded here with that in read_ens_upd_inputs(). + select case (trim(obs_param(i)%varname)) case ('sfmc', 'sfds') diff --git a/GEOSldas_App/LDASsa_DEFAULT_inputs_ensupd.nml b/GEOSldas_App/LDASsa_DEFAULT_inputs_ensupd.nml index 0368701d..13ac0538 100644 --- a/GEOSldas_App/LDASsa_DEFAULT_inputs_ensupd.nml +++ b/GEOSldas_App/LDASsa_DEFAULT_inputs_ensupd.nml @@ -85,55 +85,59 @@ fcsterr_inflation_fac = -9999. ! Definition of obs_param_nml fields (see also enkf_types.F90): ! ! %descr = description -! %species = identifier for type of measurement +! %species = identifier for type of observation ! %orbit = type of (half-)orbit ! 0 = n/a [eg., in situ obs] ! 1 = ascending ! 2 = descending ! 3 = ascending or descending ! 4 = geostationary +! ! %pol = polarization ! 0 = n/a [eg., multi-pol. retrieval] ! 1 = horizontal ! 2 = vertical -! 3 = ... +! 3 = ... [could add 3rd/4th Stokes, HH, HV, VH, VV] +! ! %N_ang = # satellite viewing angles in species (radiance obs only) ! %ang = vector of satellite viewing angles ! %freq = frequency [Hz] ! %FOV = field-of-view *radius*, see NOTES below ! (if FOV==0. equate obs footprint w/ tile) -! %FOV_units = field-of-view units ('km' or 'deg'), see NOTES below -! %assim = Should this obs type be assimilated (state update)? (logical) -! %scale = Should this obs be scaled? (logical) -! %getinnov = Should innov be computed for this obs type (logical) -! (innovations are always computed if assim==.true.) -! %RTM_ID = ID of radiative transfer model to use for Tb forward modeling -! (subroutine get_obs_pred()) +! %FOV_units = FOV units ('km' or 'deg'), see NOTES below +! %assim = assimilate obs species: true/false (see also "obs_type") +! %scale = scale obs species: true/false +! %getinnov = compute innovs? (set to .T. if assim==.T.) +! %RTM_ID = ID of radiative transfer model to use for Tb forward modeling (see get_obs_pred()) ! 0 = none ! 1 = L-band tau-omega model as in De Lannoy et al. 2013 (doi:10.1175/JHM-D-12-092.1) (old SMOS preproc) ! 2 = same as 1 but without Pellarin atm corr (SMAP) ! 3 = same as 1 but with Mironov and SMAP L2_SM pol mixing -! 4 = same as 3 but without Pellarin atm corr (targeted for SMAP L4_SM Version 8) -! %bias_Npar = number of obs bias states tracked per day (integer) +! 4 = same as 3 but without Pellarin atm corr (SMAP L4_SM Version 8) +! +! %bias_Npar = number of obs bias states tracked per day ! %bias_trel = e-folding time scale of obs bias memory [s] ! %bias_tcut = cutoff time for confident obs bias estimate [s] ! %nodata = no-data-value -! %varname = equivalent model variable name (for "Obs_pred") -! %units = units (eg., 'K' or 'm3/m3') -! %path = path to measurement files -! %name = name identifier for file containing measurements +! %varname = observation native variable name (before scaling) +! %units = observation native units (before scaling; eg., 'K' or 'm3/m3') +! %fcstvarname = observation-equivalent model variable name (Obs_pred) [do not edit, filled by read_ens_upd_inputs()] +! %fcstunits = observation-equivalent model units (eg., 'K' or 'm3/m3') [do not edit, filled by read_ens_upd_inputs()] +! %path = path to observations file +! %name = name identifier for file containing observations ! %maskpath = path to obs mask file ! %maskname = filename for obs mask ! %scalepath = path to file(s) with scaling parameters ! %scalename = filename for scaling parameters ! %flistpath = path to file with list of obs file names ! %flistname = name of file with list of obs file names -! %errstd = default obs error std -! %std_normal_max = maximum allowed perturbation (relative to N(0,1)) -! %zeromean = enforce zero mean across ensemble -! %coarsen_pert = generate obs perturbations on coarser grid (see pert_param_type%coarsen) -! %xcorr = correlation length (deg) in longitude direction -! %ycorr = correlation length (deg) in latitude direction +! %errstd = default obs error std (before scaling) +! %std_normal_max = maximum allowed obs perturbation (relative to N(0,1)) (see pert_param_type) +! %zeromean = enforce zero mean across ensemble (see pert_param_type) +! %coarsen_pert = generate obs perturbations on coarser grid (see pert_param_type) +! %xcorr = obs error correlation length (deg) in longitude direction (see pert_param_type) +! %ycorr = obs error correlation length (deg) in latitude direction (see pert_param_type) +! %adapt = identifier for adaptive filtering ! ! For observation perturbations, always use: ! @@ -257,6 +261,8 @@ obs_param_nml( 1)%bias_tcut = 432000 obs_param_nml( 1)%nodata = -9999. obs_param_nml( 1)%varname = 'sfmc' obs_param_nml( 1)%units = 'm3/m3' +obs_param_nml( 1)%fcstvarname = 'NULL' ! do not edit! +obs_param_nml( 1)%fcstunits = 'NULL' ! do not edit! obs_param_nml( 1)%path = '/land/l_data/AMSR/data/AMSR_E_L2_Land_V001/' obs_param_nml( 1)%name = 'AMSR_E_L2_Land_' obs_param_nml( 1)%maskpath = '' @@ -294,6 +300,8 @@ obs_param_nml( 2)%bias_tcut = 432000 obs_param_nml( 2)%nodata = -9999. obs_param_nml( 2)%varname = 'sfmc' obs_param_nml( 2)%units = 'm3/m3' +obs_param_nml( 2)%fcstvarname = 'NULL' ! do not edit! +obs_param_nml( 2)%fcstunits = 'NULL' ! do not edit! obs_param_nml( 2)%path = '/land/l_data/AMSR/data/AMSR_E_L2_Land_V001/' obs_param_nml( 2)%name = 'AMSR_E_L2_Land_' obs_param_nml( 2)%maskpath = '' @@ -331,6 +339,8 @@ obs_param_nml( 3)%bias_tcut = 432000 obs_param_nml( 3)%nodata = -9999. obs_param_nml( 3)%varname = 'tsurf' obs_param_nml( 3)%units = 'K' +obs_param_nml( 3)%fcstvarname = 'NULL' ! do not edit! +obs_param_nml( 3)%fcstunits = 'NULL' ! do not edit! obs_param_nml( 3)%path = '/land/l_data/ISCCP/GSWP2_1by1_V1/' obs_param_nml( 3)%name = 'isccpdx_tskin.' obs_param_nml( 3)%maskpath = '' @@ -368,6 +378,8 @@ obs_param_nml( 4)%bias_tcut = 432000 obs_param_nml( 4)%nodata = -999. obs_param_nml( 4)%varname = 'sfmc' obs_param_nml( 4)%units = 'm3/m3' +obs_param_nml( 4)%fcstvarname = 'NULL' ! do not edit! +obs_param_nml( 4)%fcstunits = 'NULL' ! do not edit! obs_param_nml( 4)%path = '/land/l_data/RedArk/Retrievals_36km/retrievals_20060508/' obs_param_nml( 4)%name = 'SM_retrieval.' obs_param_nml( 4)%maskpath = '' @@ -405,6 +417,8 @@ obs_param_nml( 5)%bias_tcut = 432000 obs_param_nml( 5)%nodata = -999. obs_param_nml( 5)%varname = 'sfmc' obs_param_nml( 5)%units = 'm3/m3' +obs_param_nml( 5)%fcstvarname = 'NULL' ! do not edit! +obs_param_nml( 5)%fcstunits = 'NULL' ! do not edit! obs_param_nml( 5)%path = '/land/l_data/RedArk/Truth/50mm_Soil_Moisture_Truth/' obs_param_nml( 5)%name = 'red_ark_50mm.sm.' obs_param_nml( 5)%maskpath = '' @@ -442,6 +456,8 @@ obs_param_nml( 6)%bias_tcut = 432000 obs_param_nml( 6)%nodata = -999. obs_param_nml( 6)%varname = 'rzmc' obs_param_nml( 6)%units = 'm3/m3' +obs_param_nml( 6)%fcstvarname = 'NULL' ! do not edit! +obs_param_nml( 6)%fcstunits = 'NULL' ! do not edit! obs_param_nml( 6)%path = '/land/l_data/RedArk/Truth/400mm_Soil_Moisture_Truth/' obs_param_nml( 6)%name = 'red_ark_400mm.sm.' obs_param_nml( 6)%maskpath = '' @@ -479,6 +495,8 @@ obs_param_nml( 7)%bias_tcut = 432000 obs_param_nml( 7)%nodata = -9999. obs_param_nml( 7)%varname = 'sfmc' obs_param_nml( 7)%units = 'm3/m3' +obs_param_nml( 7)%fcstvarname = 'NULL' ! do not edit! +obs_param_nml( 7)%fcstunits = 'NULL' ! do not edit! obs_param_nml( 7)%path = '/land/l_data/RedArk_OSSE/data/Retrievals_CLSM_synth/M0001_P0001_R0001_URI/std_synth_obs_0.020/' obs_param_nml( 7)%name = 'CLSM_synth_sm.' obs_param_nml( 7)%maskpath = '' @@ -516,6 +534,8 @@ obs_param_nml( 8)%bias_tcut = 432000 obs_param_nml( 8)%nodata = -9999. obs_param_nml( 8)%varname = 'sfmc' obs_param_nml( 8)%units = 'm3/m3' +obs_param_nml( 8)%fcstvarname = 'NULL' ! do not edit! +obs_param_nml( 8)%fcstunits = 'NULL' ! do not edit! obs_param_nml( 8)%path = '/discover/nobackup/vmaggion/Synth_sfmc/radar_sim/std_synth_sfmc_0.040/' obs_param_nml( 8)%name = 'synth_sfmc_VivianaOK_' obs_param_nml( 8)%maskpath = '' @@ -553,6 +573,8 @@ obs_param_nml( 9)%bias_tcut = 432000 obs_param_nml( 9)%nodata = -9999. obs_param_nml( 9)%varname = 'sfmc' obs_param_nml( 9)%units = 'm3/m3' +obs_param_nml( 9)%fcstvarname = 'NULL' ! do not edit! +obs_param_nml( 9)%fcstunits = 'NULL' ! do not edit! obs_param_nml( 9)%path = '/land/l_data/AMSR/data/AMSR_E_sm_LPRM/L2_EASE/bin/' obs_param_nml( 9)%name = 'AMSRsmUVA.EASE.v03.' obs_param_nml( 9)%maskpath = '' @@ -590,6 +612,8 @@ obs_param_nml(10)%bias_tcut = 432000 obs_param_nml(10)%nodata = -9999. obs_param_nml(10)%varname = 'sfmc' obs_param_nml(10)%units = 'm3/m3' +obs_param_nml(10)%fcstvarname = 'NULL' ! do not edit! +obs_param_nml(10)%fcstunits = 'NULL' ! do not edit! obs_param_nml(10)%path = '/land/l_data/AMSR/data/AMSR_E_sm_LPRM/L2_EASE/bin/' obs_param_nml(10)%name = 'AMSRsmUVA.EASE.v03.' obs_param_nml(10)%maskpath = '' @@ -627,6 +651,8 @@ obs_param_nml(11)%bias_tcut = 432000 obs_param_nml(11)%nodata = -9999. obs_param_nml(11)%varname = 'sfmc' obs_param_nml(11)%units = 'm3/m3' +obs_param_nml(11)%fcstvarname = 'NULL' ! do not edit! +obs_param_nml(11)%fcstunits = 'NULL' ! do not edit! obs_param_nml(11)%path = '/land/l_data/AMSR/data/AMSR_E_sm_LPRM/L2_EASE/bin/' obs_param_nml(11)%name = 'AMSRsmUVA.EASE.v03.' obs_param_nml(11)%maskpath = '' @@ -664,6 +690,8 @@ obs_param_nml(12)%bias_tcut = 432000 obs_param_nml(12)%nodata = -9999. obs_param_nml(12)%varname = 'sfmc' obs_param_nml(12)%units = 'm3/m3' +obs_param_nml(12)%fcstvarname = 'NULL' ! do not edit! +obs_param_nml(12)%fcstunits = 'NULL' ! do not edit! obs_param_nml(12)%path = '/land/l_data/AMSR/data/AMSR_E_sm_LPRM/L2_EASE/bin/' obs_param_nml(12)%name = 'AMSRsmUVA.EASE.v03.' obs_param_nml(12)%maskpath = '' @@ -705,6 +733,8 @@ obs_param_nml(13)%bias_tcut = 432000 obs_param_nml(13)%nodata = -9999. obs_param_nml(13)%varname = 'sfmc' obs_param_nml(13)%units = 'm3/m3' +obs_param_nml(13)%fcstvarname = 'NULL' ! do not edit! +obs_param_nml(13)%fcstunits = 'NULL' ! do not edit! obs_param_nml(13)%path = '/discover/nobackup/rreichle/l_data/ASCAT/TUW_W5.4/EASE/CONUS/bin/' obs_param_nml(13)%name = 'SDS_' obs_param_nml(13)%maskpath = '' @@ -746,6 +776,8 @@ obs_param_nml(14)%bias_tcut = 432000 obs_param_nml(14)%nodata = -9999. obs_param_nml(14)%varname = 'sfmc' obs_param_nml(14)%units = 'm3/m3' +obs_param_nml(14)%fcstvarname = 'NULL' ! do not edit! +obs_param_nml(14)%fcstunits = 'NULL' ! do not edit! obs_param_nml(14)%path = '/discover/nobackup/rreichle/l_data/ASCAT/TUW_W5.4/EASE/CONUS/bin/' obs_param_nml(14)%name = 'SDS_' obs_param_nml(14)%maskpath = '' @@ -783,6 +815,8 @@ obs_param_nml(15)%bias_tcut = 432000 obs_param_nml(15)%nodata = -9999. obs_param_nml(15)%varname = 'sfmc' obs_param_nml(15)%units = 'm3/m3' +obs_param_nml(15)%fcstvarname = 'NULL' ! do not edit! +obs_param_nml(15)%fcstunits = 'NULL' ! do not edit! obs_param_nml(15)%path = '/discover/nobackup/projects/gmao/ssd/land/l_data/SMOS/EASEv2/ESA_REPR/SMOS_M36_SMUDP2/' obs_param_nml(15)%name = '' obs_param_nml(15)%maskpath = '' @@ -820,6 +854,8 @@ obs_param_nml(16)%bias_tcut = 432000 obs_param_nml(16)%nodata = -9999. obs_param_nml(16)%varname = 'sfmc' obs_param_nml(16)%units = 'm3/m3' +obs_param_nml(16)%fcstvarname = 'NULL' ! do not edit! +obs_param_nml(16)%fcstunits = 'NULL' ! do not edit! obs_param_nml(16)%path = '/discover/nobackup/projects/gmao/ssd/land/l_data/SMOS/EASEv2/ESA_REPR/SMOS_M36_SMUDP2/' obs_param_nml(16)%name = '' obs_param_nml(16)%maskpath = '' @@ -905,6 +941,8 @@ obs_param_nml(17)%bias_tcut = 432000 obs_param_nml(17)%nodata = -9999. obs_param_nml(17)%varname = 'Tb' obs_param_nml(17)%units = 'K' +obs_param_nml(17)%fcstvarname = 'NULL' ! do not edit! +obs_param_nml(17)%fcstunits = 'NULL' ! do not edit! obs_param_nml(17)%path = '/discover/nobackup/projects/gmao/ssd/land/l_data/SMOS/EASEv2/ESA_REPR/SMOS_M36_SCLF1C_reg_nosky_noatm_v620_ESA_v102/' obs_param_nml(17)%name = '' obs_param_nml(17)%maskpath = '' @@ -949,6 +987,8 @@ obs_param_nml(18)%bias_tcut = 432000 obs_param_nml(18)%nodata = -9999. obs_param_nml(18)%varname = 'Tb' obs_param_nml(18)%units = 'K' +obs_param_nml(18)%fcstvarname = 'NULL' ! do not edit! +obs_param_nml(18)%fcstunits = 'NULL' ! do not edit! obs_param_nml(18)%path = '/discover/nobackup/projects/gmao/ssd/land/l_data/SMOS/EASEv2/ESA_REPR/SMOS_M36_SCLF1C_reg_nosky_noatm_v620_ESA_v102/' obs_param_nml(18)%name = '' obs_param_nml(18)%maskpath = '' @@ -993,6 +1033,8 @@ obs_param_nml(19)%bias_tcut = 432000 obs_param_nml(19)%nodata = -9999. obs_param_nml(19)%varname = 'Tb' obs_param_nml(19)%units = 'K' +obs_param_nml(19)%fcstvarname = 'NULL' ! do not edit! +obs_param_nml(19)%fcstunits = 'NULL' ! do not edit! obs_param_nml(19)%path = '/discover/nobackup/projects/gmao/ssd/land/l_data/SMOS/EASEv2/ESA_REPR/SMOS_M36_SCLF1C_reg_nosky_noatm_v620_ESA_v102/' obs_param_nml(19)%name = '' obs_param_nml(19)%maskpath = '' @@ -1037,6 +1079,8 @@ obs_param_nml(20)%bias_tcut = 432000 obs_param_nml(20)%nodata = -9999. obs_param_nml(20)%varname = 'Tb' obs_param_nml(20)%units = 'K' +obs_param_nml(20)%fcstvarname = 'NULL' ! do not edit! +obs_param_nml(20)%fcstunits = 'NULL' ! do not edit! obs_param_nml(20)%path = '/discover/nobackup/projects/gmao/ssd/land/l_data/SMOS/EASEv2/ESA_REPR/SMOS_M36_SCLF1C_reg_nosky_noatm_v620_ESA_v102/' obs_param_nml(20)%name = '' obs_param_nml(20)%maskpath = '' @@ -1075,6 +1119,8 @@ obs_param_nml(21)%bias_tcut = 432000 obs_param_nml(21)%nodata = -9999. obs_param_nml(21)%varname = 'Tb' obs_param_nml(21)%units = 'K' +obs_param_nml(21)%fcstvarname = 'NULL' ! do not edit! +obs_param_nml(21)%fcstunits = 'NULL' ! do not edit! obs_param_nml(21)%path = '/discover/nobackup/projects/gmao/smap/SMAP_Nature/SMOS/EASEv2/ESA/SMOS_M36_SCLF1C_fit_nosky_noatm_v724_ESA_v102/SMOS_fit_poly2/' obs_param_nml(21)%name = '' obs_param_nml(21)%maskpath = '' @@ -1113,6 +1159,8 @@ obs_param_nml(22)%bias_tcut = 432000 obs_param_nml(22)%nodata = -9999. obs_param_nml(22)%varname = 'Tb' obs_param_nml(22)%units = 'K' +obs_param_nml(22)%fcstvarname = 'NULL' ! do not edit! +obs_param_nml(22)%fcstunits = 'NULL' ! do not edit! obs_param_nml(22)%path = '/discover/nobackup/projects/gmao/smap/SMAP_Nature/SMOS/EASEv2/ESA/SMOS_M36_SCLF1C_fit_nosky_noatm_v724_ESA_v102/SMOS_fit_poly2/' obs_param_nml(22)%name = '' obs_param_nml(22)%maskpath = '' @@ -1151,6 +1199,8 @@ obs_param_nml(23)%bias_tcut = 432000 obs_param_nml(23)%nodata = -9999. obs_param_nml(23)%varname = 'Tb' obs_param_nml(23)%units = 'K' +obs_param_nml(23)%fcstvarname = 'NULL' ! do not edit! +obs_param_nml(23)%fcstunits = 'NULL' ! do not edit! obs_param_nml(23)%path = '/discover/nobackup/projects/gmao/smap/SMAP_Nature/SMOS/EASEv2/ESA/SMOS_M36_SCLF1C_fit_nosky_noatm_v724_ESA_v102/SMOS_fit_poly2/' obs_param_nml(23)%name = '' obs_param_nml(23)%maskpath = '' @@ -1189,6 +1239,8 @@ obs_param_nml(24)%bias_tcut = 432000 obs_param_nml(24)%nodata = -9999. obs_param_nml(24)%varname = 'Tb' obs_param_nml(24)%units = 'K' +obs_param_nml(24)%fcstvarname = 'NULL' ! do not edit! +obs_param_nml(24)%fcstunits = 'NULL' ! do not edit! obs_param_nml(24)%path = '/discover/nobackup/projects/gmao/smap/SMAP_Nature/SMOS/EASEv2/ESA/SMOS_M36_SCLF1C_fit_nosky_noatm_v724_ESA_v102/SMOS_fit_poly2/' obs_param_nml(24)%name = '' obs_param_nml(24)%maskpath = '' @@ -1227,6 +1279,8 @@ obs_param_nml(25)%bias_tcut = -9999 obs_param_nml(25)%nodata = -9999. obs_param_nml(25)%varname = 'NULL' obs_param_nml(25)%units = 'NULL' +obs_param_nml(25)%fcstvarname = 'NULL' ! do not edit! +obs_param_nml(25)%fcstunits = 'NULL' ! do not edit! obs_param_nml(25)%path = 'NULL' obs_param_nml(25)%name = 'NULL' obs_param_nml(25)%maskpath = 'NULL' @@ -1265,6 +1319,8 @@ obs_param_nml(26)%bias_tcut = 432000 obs_param_nml(26)%nodata = -9999. obs_param_nml(26)%varname = 'tsurf' obs_param_nml(26)%units = 'K' +obs_param_nml(26)%fcstvarname = 'NULL' ! do not edit! +obs_param_nml(26)%fcstunits = 'NULL' ! do not edit! obs_param_nml(26)%path = '/discover/nobackup/csdraper/LaRC_float/GOES-WEST/' obs_param_nml(26)%name = 'larc-v3.inst3_g15_Nch.' obs_param_nml(26)%maskpath = '' @@ -1303,6 +1359,8 @@ obs_param_nml(27)%bias_tcut = 432000 obs_param_nml(27)%nodata = -9999. obs_param_nml(27)%varname = 'tsurf' obs_param_nml(27)%units = 'K' +obs_param_nml(27)%fcstvarname = 'NULL' ! do not edit! +obs_param_nml(27)%fcstunits = 'NULL' ! do not edit! obs_param_nml(27)%path = '/discover/nobackup/csdraper/LaRC_float/v4/GOES-EAST/' obs_param_nml(27)%name = 'larc-v3.inst3_g13_Nch.' obs_param_nml(27)%maskpath = '' @@ -1341,6 +1399,8 @@ obs_param_nml(28)%bias_tcut = 432000 obs_param_nml(28)%nodata = -9999. obs_param_nml(28)%varname = 'tsurf' obs_param_nml(28)%units = 'K' +obs_param_nml(28)%fcstvarname = 'NULL' ! do not edit! +obs_param_nml(28)%fcstunits = 'NULL' ! do not edit! obs_param_nml(28)%path = '/discover/nobackup/csdraper/LaRC_float/MET09/' obs_param_nml(28)%name = 'larc-v3.inst3_mt9_Nch.' obs_param_nml(28)%maskpath = '' @@ -1379,6 +1439,8 @@ obs_param_nml(29)%bias_tcut = 432000 obs_param_nml(29)%nodata = -9999. obs_param_nml(29)%varname = 'tsurf' obs_param_nml(29)%units = 'K' +obs_param_nml(29)%fcstvarname = 'NULL' ! do not edit! +obs_param_nml(29)%fcstunits = 'NULL' ! do not edit! obs_param_nml(29)%path = '/discover/nobackup/csdraper/LaRC_float/FY2E/' obs_param_nml(29)%name = 'larc-v3.inst3_fye_Nch.' obs_param_nml(29)%maskpath = '' @@ -1417,6 +1479,8 @@ obs_param_nml(30)%bias_tcut = 432000 obs_param_nml(30)%nodata = -9999. obs_param_nml(30)%varname = 'tsurf' obs_param_nml(30)%units = 'K' +obs_param_nml(30)%fcstvarname = 'NULL' ! do not edit! +obs_param_nml(30)%fcstunits = 'NULL' ! do not edit! obs_param_nml(30)%path = '/discover/nobackup/csdraper/LaRC_float/MTSAT-2/' obs_param_nml(30)%name = 'larc-v3.inst3_mt2_Nch.' obs_param_nml(30)%maskpath = '' @@ -1465,6 +1529,8 @@ obs_param_nml(31)%bias_tcut = 432000 obs_param_nml(31)%nodata = -9999. obs_param_nml(31)%varname = 'Tb' obs_param_nml(31)%units = 'K' +obs_param_nml(31)%fcstvarname = 'NULL' ! do not edit! +obs_param_nml(31)%fcstunits = 'NULL' ! do not edit! obs_param_nml(31)%path = '/discover/nobackup/projects/gmao/smap/SMAP_L4/SMAP/OPS/L1C_TB/' obs_param_nml(31)%name = '' obs_param_nml(31)%maskpath = '' @@ -1503,6 +1569,8 @@ obs_param_nml(32)%bias_tcut = 432000 obs_param_nml(32)%nodata = -9999. obs_param_nml(32)%varname = 'Tb' obs_param_nml(32)%units = 'K' +obs_param_nml(32)%fcstvarname = 'NULL' ! do not edit! +obs_param_nml(32)%fcstunits = 'NULL' ! do not edit! obs_param_nml(32)%path = '/discover/nobackup/projects/gmao/smap/SMAP_L4/SMAP/OPS/L1C_TB/' obs_param_nml(32)%name = '' obs_param_nml(32)%maskpath = '' @@ -1541,6 +1609,8 @@ obs_param_nml(33)%bias_tcut = 432000 obs_param_nml(33)%nodata = -9999. obs_param_nml(33)%varname = 'Tb' obs_param_nml(33)%units = 'K' +obs_param_nml(33)%fcstvarname = 'NULL' ! do not edit! +obs_param_nml(33)%fcstunits = 'NULL' ! do not edit! obs_param_nml(33)%path = '/discover/nobackup/projects/gmao/smap/SMAP_L4/SMAP/OPS/L1C_TB/' obs_param_nml(33)%name = '' obs_param_nml(33)%maskpath = '' @@ -1579,6 +1649,8 @@ obs_param_nml(34)%bias_tcut = 432000 obs_param_nml(34)%nodata = -9999. obs_param_nml(34)%varname = 'Tb' obs_param_nml(34)%units = 'K' +obs_param_nml(34)%fcstvarname = 'NULL' ! do not edit! +obs_param_nml(34)%fcstunits = 'NULL' ! do not edit! obs_param_nml(34)%path = '/discover/nobackup/projects/gmao/smap/SMAP_L4/SMAP/OPS/L1C_TB/' obs_param_nml(34)%name = '' obs_param_nml(34)%maskpath = '' @@ -1628,6 +1700,8 @@ obs_param_nml(35)%bias_tcut = 432000 obs_param_nml(35)%nodata = -9999. obs_param_nml(35)%varname = 'Tb' obs_param_nml(35)%units = 'K' +obs_param_nml(35)%fcstvarname = 'NULL' ! do not edit! +obs_param_nml(35)%fcstunits = 'NULL' ! do not edit! obs_param_nml(35)%path = '/discover/nobackup/projects/gmao/smap/SMAP_L4/SMAP/OPS/L2_SM_AP/' obs_param_nml(35)%name = '' obs_param_nml(35)%maskpath = '' @@ -1666,6 +1740,8 @@ obs_param_nml(36)%bias_tcut = 432000 obs_param_nml(36)%nodata = -9999. obs_param_nml(36)%varname = 'Tb' obs_param_nml(36)%units = 'K' +obs_param_nml(36)%fcstvarname = 'NULL' ! do not edit! +obs_param_nml(36)%fcstunits = 'NULL' ! do not edit! obs_param_nml(36)%path = '/discover/nobackup/projects/gmao/smap/SMAP_L4/SMAP/OPS/L2_SM_AP/' obs_param_nml(36)%name = '' obs_param_nml(36)%maskpath = '' @@ -1704,6 +1780,8 @@ obs_param_nml(37)%bias_tcut = 432000 obs_param_nml(37)%nodata = -9999. obs_param_nml(37)%varname = 'Tb' obs_param_nml(37)%units = 'K' +obs_param_nml(37)%fcstvarname = 'NULL' ! do not edit! +obs_param_nml(37)%fcstunits = 'NULL' ! do not edit! obs_param_nml(37)%path = '/discover/nobackup/projects/gmao/smap/SMAP_L4/SMAP/OPS/L2_SM_AP/' obs_param_nml(37)%name = '' obs_param_nml(37)%maskpath = '' @@ -1742,6 +1820,8 @@ obs_param_nml(38)%bias_tcut = 432000 obs_param_nml(38)%nodata = -9999. obs_param_nml(38)%varname = 'Tb' obs_param_nml(38)%units = 'K' +obs_param_nml(38)%fcstvarname = 'NULL' ! do not edit! +obs_param_nml(38)%fcstunits = 'NULL' ! do not edit! obs_param_nml(38)%path = '/discover/nobackup/projects/gmao/smap/SMAP_L4/SMAP/OPS/L2_SM_AP/' obs_param_nml(38)%name = '' obs_param_nml(38)%maskpath = '' @@ -1779,6 +1859,8 @@ obs_param_nml(39)%bias_tcut = 432000 obs_param_nml(39)%nodata = -9999. obs_param_nml(39)%varname = 'FT' obs_param_nml(39)%units = '-' +obs_param_nml(39)%fcstvarname = 'NULL' ! do not edit! +obs_param_nml(39)%fcstunits = 'NULL' ! do not edit! obs_param_nml(39)%path = '/discover/nobackup/projects/gmao/smap/SMAP_L4/SMAP/OPS/L2_SM_AP/' obs_param_nml(39)%name = '' obs_param_nml(39)%maskpath = '' @@ -1816,6 +1898,8 @@ obs_param_nml(40)%bias_tcut = 432000 obs_param_nml(40)%nodata = -9999. obs_param_nml(40)%varname = 'FT' obs_param_nml(40)%units = '-' +obs_param_nml(40)%fcstvarname = 'NULL' ! do not edit! +obs_param_nml(40)%fcstunits = 'NULL' ! do not edit! obs_param_nml(40)%path = '/discover/nobackup/projects/gmao/smap/SMAP_L4/SMAP/OPS/L2_SM_AP/' obs_param_nml(40)%name = '' obs_param_nml(40)%maskpath = '' @@ -1876,6 +1960,8 @@ obs_param_nml(41)%bias_tcut = 432000 obs_param_nml(41)%nodata = -9999. obs_param_nml(41)%varname = 'Tb' obs_param_nml(41)%units = 'K' +obs_param_nml(41)%fcstvarname = 'NULL' ! do not edit! +obs_param_nml(41)%fcstunits = 'NULL' ! do not edit! obs_param_nml(41)%path = '/discover/nobackup/projects/gmao/smap/SMAP_L4/SMAP/OPS/L1C_TB_E/' obs_param_nml(41)%name = '' obs_param_nml(41)%maskpath = '' @@ -1914,6 +2000,8 @@ obs_param_nml(42)%bias_tcut = 432000 obs_param_nml(42)%nodata = -9999. obs_param_nml(42)%varname = 'Tb' obs_param_nml(42)%units = 'K' +obs_param_nml(42)%fcstvarname = 'NULL' ! do not edit! +obs_param_nml(42)%fcstunits = 'NULL' ! do not edit! obs_param_nml(42)%path = '/discover/nobackup/projects/gmao/smap/SMAP_L4/SMAP/OPS/L1C_TB_E/' obs_param_nml(42)%name = '' obs_param_nml(42)%maskpath = '' @@ -1952,6 +2040,8 @@ obs_param_nml(43)%bias_tcut = 432000 obs_param_nml(43)%nodata = -9999. obs_param_nml(43)%varname = 'Tb' obs_param_nml(43)%units = 'K' +obs_param_nml(43)%fcstvarname = 'NULL' ! do not edit! +obs_param_nml(43)%fcstunits = 'NULL' ! do not edit! obs_param_nml(43)%path = '/discover/nobackup/projects/gmao/smap/SMAP_L4/SMAP/OPS/L1C_TB_E/' obs_param_nml(43)%name = '' obs_param_nml(43)%maskpath = '' @@ -1990,6 +2080,8 @@ obs_param_nml(44)%bias_tcut = 432000 obs_param_nml(44)%nodata = -9999. obs_param_nml(44)%varname = 'Tb' obs_param_nml(44)%units = 'K' +obs_param_nml(44)%fcstvarname = 'NULL' ! do not edit! +obs_param_nml(44)%fcstunits = 'NULL' ! do not edit! obs_param_nml(44)%path = '/discover/nobackup/projects/gmao/smap/SMAP_L4/SMAP/OPS/L1C_TB_E/' obs_param_nml(44)%name = '' obs_param_nml(44)%maskpath = '' @@ -2028,6 +2120,8 @@ obs_param_nml(45)%bias_tcut = 432000 obs_param_nml(45)%nodata = -9999. obs_param_nml(45)%varname = 'Tb' obs_param_nml(45)%units = 'K' +obs_param_nml(45)%fcstvarname = 'NULL' ! do not edit! +obs_param_nml(45)%fcstunits = 'NULL' ! do not edit! obs_param_nml(45)%path = '/discover/nobackup/projects/gmao/smap/SMAP_L4/SMAP/OPS/L1C_TB_E/' obs_param_nml(45)%name = '' obs_param_nml(45)%maskpath = '' @@ -2066,6 +2160,8 @@ obs_param_nml(46)%bias_tcut = 432000 obs_param_nml(46)%nodata = -9999. obs_param_nml(46)%varname = 'Tb' obs_param_nml(46)%units = 'K' +obs_param_nml(46)%fcstvarname = 'NULL' ! do not edit! +obs_param_nml(46)%fcstunits = 'NULL' ! do not edit! obs_param_nml(46)%path = '/discover/nobackup/projects/gmao/smap/SMAP_L4/SMAP/OPS/L1C_TB_E/' obs_param_nml(46)%name = '' obs_param_nml(46)%maskpath = '' @@ -2104,6 +2200,8 @@ obs_param_nml(47)%bias_tcut = 432000 obs_param_nml(47)%nodata = -9999. obs_param_nml(47)%varname = 'Tb' obs_param_nml(47)%units = 'K' +obs_param_nml(47)%fcstvarname = 'NULL' ! do not edit! +obs_param_nml(47)%fcstunits = 'NULL' ! do not edit! obs_param_nml(47)%path = '/discover/nobackup/projects/gmao/smap/SMAP_L4/SMAP/OPS/L1C_TB_E/' obs_param_nml(47)%name = '' obs_param_nml(47)%maskpath = '' @@ -2142,6 +2240,8 @@ obs_param_nml(48)%bias_tcut = 432000 obs_param_nml(48)%nodata = -9999. obs_param_nml(48)%varname = 'Tb' obs_param_nml(48)%units = 'K' +obs_param_nml(48)%fcstvarname = 'NULL' ! do not edit! +obs_param_nml(48)%fcstunits = 'NULL' ! do not edit! obs_param_nml(48)%path = '/discover/nobackup/projects/gmao/smap/SMAP_L4/SMAP/OPS/L1C_TB_E/' obs_param_nml(48)%name = '' obs_param_nml(48)%maskpath = '' @@ -2187,6 +2287,8 @@ obs_param_nml(49)%bias_tcut = 432000 obs_param_nml(49)%nodata = -9999. obs_param_nml(49)%varname = 'sfds' obs_param_nml(49)%units = '%' +obs_param_nml(49)%fcstvarname = 'NULL' ! do not edit! +obs_param_nml(49)%fcstunits = 'NULL' ! do not edit! obs_param_nml(49)%path = '/discover/nobackup/projects/gmao/smap/SMAP_Nature/ASCAT_EUMETSAT/Metop_A/' obs_param_nml(49)%name = '' obs_param_nml(49)%maskpath = '' @@ -2226,6 +2328,8 @@ obs_param_nml(50)%bias_tcut = 432000 obs_param_nml(50)%nodata = -9999. obs_param_nml(50)%varname = 'sfds' obs_param_nml(50)%units = '%' +obs_param_nml(50)%fcstvarname = 'NULL' ! do not edit! +obs_param_nml(50)%fcstunits = 'NULL' ! do not edit! obs_param_nml(50)%path = '/discover/nobackup/projects/gmao/smap/SMAP_Nature/ASCAT_EUMETSAT/Metop_B/' obs_param_nml(50)%name = '' obs_param_nml(50)%maskpath = '' @@ -2265,6 +2369,8 @@ obs_param_nml(51)%bias_tcut = 432000 obs_param_nml(51)%nodata = -9999. obs_param_nml(51)%varname = 'sfds' obs_param_nml(51)%units = '%' +obs_param_nml(51)%fcstvarname = 'NULL' ! do not edit! +obs_param_nml(51)%fcstunits = 'NULL' ! do not edit! obs_param_nml(51)%path = '/discover/nobackup/projects/gmao/smap/SMAP_Nature/ASCAT_EUMETSAT/Metop_C/' obs_param_nml(51)%name = '' obs_param_nml(51)%maskpath = '' @@ -2306,6 +2412,8 @@ obs_param_nml(52)%bias_tcut = 432000 obs_param_nml(52)%nodata = -9999. obs_param_nml(52)%varname = 'asnow' obs_param_nml(52)%units = 'm2/m2' +obs_param_nml(52)%fcstvarname = 'NULL' ! do not edit! +obs_param_nml(52)%fcstunits = 'NULL' ! do not edit! obs_param_nml(52)%path = '/discover/nobackup/projects/S2SHMA/MODIS/MYD10C1_V61/' obs_param_nml(52)%name = 'MYD10C1.Ayyyyddd.061.hdf' obs_param_nml(52)%maskpath = '' @@ -2347,6 +2455,8 @@ obs_param_nml(53)%bias_tcut = 432000 obs_param_nml(53)%nodata = -9999. obs_param_nml(53)%varname = 'asnow' obs_param_nml(53)%units = 'm2/m2' +obs_param_nml(53)%fcstvarname = 'NULL' ! do not edit! +obs_param_nml(53)%fcstunits = 'NULL' ! do not edit! obs_param_nml(53)%path = '/discover/nobackup/projects/S2SHMA/MODIS/MOD10C1_V61/' obs_param_nml(53)%name = 'MOD10C1.Ayyyyddd.061.hdf' obs_param_nml(53)%maskpath = '' @@ -2386,6 +2496,8 @@ obs_param_nml(54)%bias_tcut = 432000 obs_param_nml(54)%nodata = -9999. obs_param_nml(54)%varname = 'sfmc' obs_param_nml(54)%units = 'm3 m-3' +obs_param_nml(54)%fcstvarname = 'NULL' ! do not edit! +obs_param_nml(54)%fcstunits = 'NULL' ! do not edit! obs_param_nml(54)%path = '/discover/nobackup/projects/gmao/smap/SMAP_Nature/CYGNSS/' obs_param_nml(54)%name = '' obs_param_nml(54)%maskpath = '' @@ -2425,6 +2537,8 @@ obs_param_nml(55)%bias_tcut = 432000 obs_param_nml(55)%nodata = -9999. obs_param_nml(55)%varname = 'sfmc' obs_param_nml(55)%units = 'm3 m-3' +obs_param_nml(55)%fcstvarname = 'NULL' ! do not edit! +obs_param_nml(55)%fcstunits = 'NULL' ! do not edit! obs_param_nml(55)%path = '/discover/nobackup/projects/gmao/smap/SMAP_Nature/CYGNSS/' obs_param_nml(55)%name = '' obs_param_nml(55)%maskpath = '' diff --git a/GEOSldas_App/util/shared/matlab/read_obsparam.m b/GEOSldas_App/util/shared/matlab/read_obsparam.m index 19e66056..433a1f07 100644 --- a/GEOSldas_App/util/shared/matlab/read_obsparam.m +++ b/GEOSldas_App/util/shared/matlab/read_obsparam.m @@ -7,7 +7,9 @@ % % 1 Dec 2011 - reichle: minor modifications and check-in to CVS % -% 8 Jun 2017 - reichle: added "flistpath" and "flistname" +% 8 Jun 2017 - reichle: added "flistpath" and "flistname" +% +% 17 Apr 2026 - reichle: added "fcstvarname" and "fcstunits" % % ------------------------------------------------------------------ @@ -40,6 +42,8 @@ obs_param(i).nodata = fscanf(fid, '%f ', 1); obs_param(i).varname = fscanf(fid, '%s ', 1); obs_param(i).units = fscanf(fid, '%s ', 1); + obs_param(i).fcstvarname = fscanf(fid, '%s ', 1); + obs_param(i).fcstunits = fscanf(fid, '%s ', 1); obs_param(i).path = fscanf(fid, '%s ', 1); obs_param(i).name = fscanf(fid, '%s ', 1); obs_param(i).maskpath = fscanf(fid, '%s ', 1); @@ -58,18 +62,20 @@ % remove leading and trailing quotes from strings - obs_param(i).descr = obs_param(i).descr( 2:end-1); - obs_param(i).FOV_units = obs_param(i).FOV_units(2:end-1); - obs_param(i).varname = obs_param(i).varname( 2:end-1); - obs_param(i).units = obs_param(i).units( 2:end-1); - obs_param(i).path = obs_param(i).path( 2:end-1); - obs_param(i).name = obs_param(i).name( 2:end-1); - obs_param(i).maskpath = obs_param(i).maskpath( 2:end-1); - obs_param(i).maskname = obs_param(i).maskname( 2:end-1); - obs_param(i).scalepath = obs_param(i).scalepath(2:end-1); - obs_param(i).scalename = obs_param(i).scalename(2:end-1); - obs_param(i).flistpath = obs_param(i).flistpath(2:end-1); - obs_param(i).flistname = obs_param(i).flistname(2:end-1); + obs_param(i).descr = obs_param(i).descr( 2:end-1); + obs_param(i).FOV_units = obs_param(i).FOV_units( 2:end-1); + obs_param(i).varname = obs_param(i).varname( 2:end-1); + obs_param(i).units = obs_param(i).units( 2:end-1); + obs_param(i).fcstvarname = obs_param(i).fcstvarname(2:end-1); + obs_param(i).fcstunits = obs_param(i).fcstunits( 2:end-1); + obs_param(i).path = obs_param(i).path( 2:end-1); + obs_param(i).name = obs_param(i).name( 2:end-1); + obs_param(i).maskpath = obs_param(i).maskpath( 2:end-1); + obs_param(i).maskname = obs_param(i).maskname( 2:end-1); + obs_param(i).scalepath = obs_param(i).scalepath( 2:end-1); + obs_param(i).scalename = obs_param(i).scalename( 2:end-1); + obs_param(i).flistpath = obs_param(i).flistpath( 2:end-1); + obs_param(i).flistname = obs_param(i).flistname( 2:end-1); end diff --git a/LDAS_Shared/LDAS_ensdrv_mpi.F90 b/LDAS_Shared/LDAS_ensdrv_mpi.F90 index 1da40cdf..6b36a77a 100644 --- a/LDAS_Shared/LDAS_ensdrv_mpi.F90 +++ b/LDAS_Shared/LDAS_ensdrv_mpi.F90 @@ -739,6 +739,8 @@ subroutine init_MPI_types() ! real :: nodata ! block #7 (real) ! character(40) :: varname ! block #8 (character) ! character(40) :: units + ! character(40) :: fcstvarname + ! character(40) :: fcstunits ! character(200) :: path ! character(80) :: name ! character(200) :: maskpath @@ -781,7 +783,7 @@ subroutine init_MPI_types() iblock( 5) = 3 iblock( 6) = 4 iblock( 7) = 1 - iblock( 8) = 40+40+200+80+200+80+200+80+200+80 + iblock( 8) = 40+40+40+40+200+80+200+80+200+80+200+80 iblock( 9) = 2 iblock(10) = 2 iblock(11) = 2 diff --git a/LDAS_Shared/enkf_types.F90 b/LDAS_Shared/enkf_types.F90 index 566c75ef..cb1daf06 100644 --- a/LDAS_Shared/enkf_types.F90 +++ b/LDAS_Shared/enkf_types.F90 @@ -56,7 +56,8 @@ module enkf_types ! added "varname" field to "obs_param_type" - reichle 14 Jun 2011 ! major revisions to "obs_type" fields - reichle 16 Jun 2011 ! added "units" field to "obs_param_type" - reichle 22 Nov 2011 - + ! added "fcstvarname" and "fcstunits" to "obs_param_type" - reichle 17 Apr 2026 + type :: obs_type ! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! @@ -73,8 +74,8 @@ module enkf_types real*8 :: time ! time of obs (J2000 seconds w/ 'TT12' epoch; see date_time_util.F90) real :: lon ! longitude of obs real :: lat ! latitude of obs - real :: obs ! observed value - real :: obsvar ! obs error var + real :: obs ! observed value (after scaling) + real :: obsvar ! obs error var (after scaling) real :: fcst ! "forecast": value of obs pred before EnKF update (ens mean) real :: fcstvar ! forecast error var (in obs space), a.k.a. HPHt real :: ana ! "analysis": value of obs pred after EnKF update (ens mean) @@ -94,68 +95,74 @@ module enkf_types ! any subroutines or operators defined herein ! !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - character(40) :: descr ! description - integer :: species ! identifier for type of measurement + character(40) :: descr ! description + integer :: species ! identifier for type of observation - integer :: orbit ! type of (half-)orbit - ! 0 = n/a [eg., in situ obs] - ! 1 = ascending - ! 2 = descending - ! 3 = ascending or descending - ! 4 = geostationary + integer :: orbit ! type of (half-)orbit + ! 0 = n/a [eg., in situ obs] + ! 1 = ascending + ! 2 = descending + ! 3 = ascending or descending + ! 4 = geostationary - integer :: pol ! polarization - ! 0 = n/a [eg., multi-pol. retrieval] - ! 1 = horizontal - ! 2 = vertical - ! 3 = ... - ! [add 3rd/4th Stokes, HH, HV, VH, VV] + integer :: pol ! polarization + ! 0 = n/a [eg., multi-pol. retrieval] + ! 1 = horizontal + ! 2 = vertical + ! 3 = ... [cpuld add 3rd/4th Stokes, HH, HV, VH, VV] - integer :: N_ang ! # satellite viewing angles in species (radiance obs only) + integer :: N_ang ! # satellite viewing angles in species (radiance obs only) real, & - dimension(N_obs_ang_max) :: ang ! vector of satellite viewing angles + dimension(N_obs_ang_max) :: ang ! vector of satellite viewing angles - real :: freq ! frequency [Hz] - - real :: FOV ! field-of-view *radius* - ! if FOV==0. equate obs footprint w/ tile - ! for details see LDASsa_DEFAULT_inputs ensupd.nml - character(40) :: FOV_units ! FOV units ('km' or 'deg') - - logical :: assim ! assimilate yes/no? (see also "obs_type") - logical :: scale ! scale yes/no? - logical :: getinnov ! compute innovs? (.T. if assim==.T.) - - integer :: RTM_ID ! ID of radiative transfer model - - integer :: bias_Npar ! number of bias states tracked per day - integer :: bias_trel ! e-folding time scale of obs bias memory [s] - integer :: bias_tcut ! cutoff time for confident obs bias est [s] - - real :: nodata ! no-data-value - - character(40) :: varname ! equivalent model variable name (Obs_pred) - character(40) :: units ! units (eg., 'K' or 'm3/m3') - - character(200) :: path ! path to measurements file - character(80) :: name ! name identifier for measurements - character(200) :: maskpath ! path to obs mask file - character(80) :: maskname ! filename for obs mask - character(200) :: scalepath ! path to file with scaling parameters - character(80) :: scalename ! filename for scaling parameters - character(200) :: flistpath ! path to file with list of obs file names - character(80) :: flistname ! name of file with list of obs file names - - real :: errstd ! default obs error std + real :: freq ! frequency [Hz] + + real :: FOV ! field-of-view *radius* + ! if FOV==0. equate obs footprint w/ tile + ! for details see LDASsa_DEFAULT_inputs ensupd.nml + character(40) :: FOV_units ! FOV units ('km' or 'deg') + + logical :: assim ! assimilate obs species: yes/no? (see also "obs_type") + logical :: scale ! scale obs species: yes/no? + logical :: getinnov ! compute innovs? (set .T. if assim==.T.) + + integer :: RTM_ID ! ID of radiative transfer model to use for Tb forward modeling (see get_obs_pred()) + ! 0 = none + ! 1 = L-band tau-omega model as in De Lannoy et al. 2013 (doi:10.1175/JHM-D-12-092.1) (old SMOS preproc) + ! 2 = same as 1 but without Pellarin atm corr (SMAP) + ! 3 = same as 1 but with Mironov and SMAP L2_SM pol mixing + ! 4 = same as 3 but without Pellarin atm corr (SMAP L4_SM Version 8) + + integer :: bias_Npar ! number of obs bias states tracked per day + integer :: bias_trel ! e-folding time scale of obs bias memory [s] + integer :: bias_tcut ! cutoff time for confident obs bias estimate [s] + + real :: nodata ! no-data-value + + character(40) :: varname ! observation native variable name (before scaling) + character(40) :: units ! observation native units (before scaling; eg., 'K' or 'm3/m3') + character(40) :: fcstvarname ! observation-equivalent model variable name (Obs_pred) [do not edit, filled by read_ens_upd_inputs()] + character(40) :: fcstunits ! observation-equivalent model units (eg., 'K' or 'm3/m3') [do not edit, filled by read_ens_upd_inputs()] + + character(200) :: path ! path to observations file + character(80) :: name ! name identifier for file containing observations + character(200) :: maskpath ! path to obs mask file + character(80) :: maskname ! filename for obs mask + character(200) :: scalepath ! path to file(s) with scaling parameters + character(80) :: scalename ! filename for scaling parameters + character(200) :: flistpath ! path to file with list of obs file names + character(80) :: flistname ! name of file with list of obs file names + + real :: errstd ! default obs error std (before scaling) - real :: std_normal_max ! see pert_param_type - logical :: zeromean ! see pert_param_type - logical :: coarsen_pert ! see pert_param_type ("%coarsen") - real :: xcorr ! see pert_param_type - real :: ycorr ! see pert_param_type + real :: std_normal_max ! maximum allowed obs perturbation (relative to N(0,1)) (see pert_param_type) + logical :: zeromean ! enforce zero mean across ensemble (see pert_param_type) + logical :: coarsen_pert ! generate obs perturbations on coarser grid (see pert_param_type) + real :: xcorr ! obs error correlation length (deg) in longitude direction (see pert_param_type) + real :: ycorr ! obs error correlation length (deg) in latitude direction (see pert_param_type) - integer :: adapt ! identifier for adaptive filtering + integer :: adapt ! identifier for adaptive filtering end type obs_param_type @@ -201,16 +208,18 @@ subroutine write_obs_param(unitnumber, N_obs_param, obs_param) write (unitnumber, *) obs_param(i)%bias_trel write (unitnumber, *) obs_param(i)%bias_tcut write (unitnumber, *) obs_param(i)%nodata - write (unitnumber, '(42A)') "'" // trim(obs_param(i)%varname) // "'" - write (unitnumber, '(42A)') "'" // trim(obs_param(i)%units) // "'" - write (unitnumber,'(202A)') "'" // trim(obs_param(i)%path) // "'" - write (unitnumber, '(82A)') "'" // trim(obs_param(i)%name) // "'" - write (unitnumber,'(202A)') "'" // trim(obs_param(i)%maskpath) // "'" - write (unitnumber, '(82A)') "'" // trim(obs_param(i)%maskname) // "'" - write (unitnumber,'(202A)') "'" // trim(obs_param(i)%scalepath) // "'" - write (unitnumber, '(82A)') "'" // trim(obs_param(i)%scalename) // "'" - write (unitnumber,'(202A)') "'" // trim(obs_param(i)%flistpath) // "'" - write (unitnumber, '(82A)') "'" // trim(obs_param(i)%flistname) // "'" + write (unitnumber, '(42A)') "'" // trim(obs_param(i)%varname) // "'" + write (unitnumber, '(42A)') "'" // trim(obs_param(i)%units) // "'" + write (unitnumber, '(42A)') "'" // trim(obs_param(i)%fcstvarname) // "'" + write (unitnumber, '(42A)') "'" // trim(obs_param(i)%fcstunits) // "'" + write (unitnumber,'(202A)') "'" // trim(obs_param(i)%path) // "'" + write (unitnumber, '(82A)') "'" // trim(obs_param(i)%name) // "'" + write (unitnumber,'(202A)') "'" // trim(obs_param(i)%maskpath) // "'" + write (unitnumber, '(82A)') "'" // trim(obs_param(i)%maskname) // "'" + write (unitnumber,'(202A)') "'" // trim(obs_param(i)%scalepath) // "'" + write (unitnumber, '(82A)') "'" // trim(obs_param(i)%scalename) // "'" + write (unitnumber,'(202A)') "'" // trim(obs_param(i)%flistpath) // "'" + write (unitnumber, '(82A)') "'" // trim(obs_param(i)%flistname) // "'" write (unitnumber, *) obs_param(i)%errstd write (unitnumber, *) obs_param(i)%std_normal_max write (unitnumber, *) obs_param(i)%zeromean From 2bacee3b3d024a095061a07a0ff7215878ea3572 Mon Sep 17 00:00:00 2001 From: amfox37 Date: Thu, 23 Apr 2026 15:45:27 -0400 Subject: [PATCH 33/40] needed to deal with obs_param_nml(25)%varname = 'NULL' in DEFAULT --- GEOSlandassim_GridComp/clsm_ensupd_upd_routines.F90 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GEOSlandassim_GridComp/clsm_ensupd_upd_routines.F90 b/GEOSlandassim_GridComp/clsm_ensupd_upd_routines.F90 index fff6bb51..6c80bfcd 100644 --- a/GEOSlandassim_GridComp/clsm_ensupd_upd_routines.F90 +++ b/GEOSlandassim_GridComp/clsm_ensupd_upd_routines.F90 @@ -376,7 +376,7 @@ subroutine read_ens_upd_inputs( & obs_param_nml(i)%fcstvarname = 'sfmc' obs_param_nml(i)%fcstunits = 'm3 m-3' - case ('rzmc', 'tsurf', 'FT', 'Tb', 'asnow') + case ('rzmc', 'tsurf', 'FT', 'Tb', 'asnow', 'NULL') obs_param_nml(i)%fcstvarname = obs_param_nml(i)%varname obs_param_nml(i)%fcstunits = obs_param_nml(i)%units From e9ef89b36899272342e0df0bf818e07afd1be743 Mon Sep 17 00:00:00 2001 From: amfox37 Date: Thu, 23 Apr 2026 14:04:44 -0600 Subject: [PATCH 34/40] Fix logic for intended behaviour --- GEOSlandassim_GridComp/clsm_ensupd_upd_routines.F90 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/GEOSlandassim_GridComp/clsm_ensupd_upd_routines.F90 b/GEOSlandassim_GridComp/clsm_ensupd_upd_routines.F90 index 6c80bfcd..e674813d 100644 --- a/GEOSlandassim_GridComp/clsm_ensupd_upd_routines.F90 +++ b/GEOSlandassim_GridComp/clsm_ensupd_upd_routines.F90 @@ -358,11 +358,11 @@ subroutine read_ens_upd_inputs( & ! expect "fcstvarname" and "fcstunits" to be 'NULL'; they are not meant to be filled by the user in the config nml file - if ( .not. ( (trim(obs_param_nml(i)%fcstvarname) /= 'NULL') .or. (trim(obs_param_nml(i)%fcstvarname) /= 'null') ) ) & + if ( .not. ( (trim(obs_param_nml(i)%fcstvarname) /= 'NULL') .and. (trim(obs_param_nml(i)%fcstvarname) /= 'null') ) ) & call ldas_abort(LDAS_GENERIC_ERROR, Iam, 'obs_param_nml%fcstvarname must be NULL on input') - if ( .not. ( (trim(obs_param_nml(i)%fcstunits ) /= 'NULL') .or. (trim(obs_param_nml(i)%fcstunits ) /= 'null') ) ) & - call ldas_abort(LDAS_GENERIC_ERROR, Iam, 'obs_param_nml%fcstunits must be NULL on input') + if ( .not. ( (trim(obs_param_nml(i)%fcstunits ) /= 'NULL') .and. (trim(obs_param_nml(i)%fcstunits ) /= 'null') ) ) & + call ldas_abort(LDAS_GENERIC_ERROR, Iam, 'obs_param_nml%fcstunits must be NULL on input') ! IMPORTANT: Must maintain consistency in the mapping between obs and model variables ! that is encoded here with that in get_obs_pred(). From be6577848051abecb4f8b0119a2cec9f9daa1f3a Mon Sep 17 00:00:00 2001 From: amfox37 Date: Thu, 23 Apr 2026 15:15:01 -0600 Subject: [PATCH 35/40] Refactor input validation for obs_param_nml for NULL checks for fcstvarname and fcstunits --- GEOSlandassim_GridComp/clsm_ensupd_upd_routines.F90 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/GEOSlandassim_GridComp/clsm_ensupd_upd_routines.F90 b/GEOSlandassim_GridComp/clsm_ensupd_upd_routines.F90 index e674813d..15223c2f 100644 --- a/GEOSlandassim_GridComp/clsm_ensupd_upd_routines.F90 +++ b/GEOSlandassim_GridComp/clsm_ensupd_upd_routines.F90 @@ -358,10 +358,10 @@ subroutine read_ens_upd_inputs( & ! expect "fcstvarname" and "fcstunits" to be 'NULL'; they are not meant to be filled by the user in the config nml file - if ( .not. ( (trim(obs_param_nml(i)%fcstvarname) /= 'NULL') .and. (trim(obs_param_nml(i)%fcstvarname) /= 'null') ) ) & + if (trim(obs_param_nml(i)%fcstvarname) /= 'NULL' .and. trim(obs_param_nml(i)%fcstvarname) /= 'null') & call ldas_abort(LDAS_GENERIC_ERROR, Iam, 'obs_param_nml%fcstvarname must be NULL on input') - if ( .not. ( (trim(obs_param_nml(i)%fcstunits ) /= 'NULL') .and. (trim(obs_param_nml(i)%fcstunits ) /= 'null') ) ) & + if (trim(obs_param_nml(i)%fcstunits ) /= 'NULL' .and. trim(obs_param_nml(i)%fcstunits ) /= 'null') & call ldas_abort(LDAS_GENERIC_ERROR, Iam, 'obs_param_nml%fcstunits must be NULL on input') ! IMPORTANT: Must maintain consistency in the mapping between obs and model variables From 4aecf203067758cf27067d69b8d2d991a429ef8d Mon Sep 17 00:00:00 2001 From: Rolf Reichle Date: Tue, 28 Apr 2026 12:13:35 -0400 Subject: [PATCH 36/40] in ObsFcstAna nc4 output, replace LDAS no_data_generic with MAPL_UNDEF; assume no-data-values do not occur for lat/lon; use automatic arrays for temporary integer and float (clsm_ensupd_enkf_update.F90) --- .../clsm_ensupd_enkf_update.F90 | 60 ++++++++++--------- 1 file changed, 31 insertions(+), 29 deletions(-) diff --git a/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 b/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 index d91a151c..5a7246f1 100644 --- a/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 +++ b/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 @@ -9,6 +9,9 @@ module clsm_ensupd_enkf_update + use MAPL_BaseMod, ONLY: & + MAPL_UNDEF + use MAPL_SortMod, ONLY: & MAPL_Sort @@ -30,7 +33,8 @@ module clsm_ensupd_enkf_update logit, & logunit, & nodata_generic, & - nodata_tolfrac_generic + nodata_tolfrac_generic, & + LDAS_is_nodata use LDAS_DateTimeMod, ONLY: & date_time_type, & @@ -1789,7 +1793,6 @@ subroutine write_ObsFcstAna_nc4(fname, exp_id, N_obsf, Observations_f, & integer :: obsparam_varname_varid, obsparam_units_varid, obsparam_descr_varid integer :: obsparam_fcstvarname_varid, obsparam_fcstunits_varid - integer, dimension(:), allocatable :: assim_int integer, dimension(:), allocatable :: species_assim_int, species_scale_int real, dimension(:), allocatable :: species_errstd_r4 character(len=40), dimension(:), allocatable :: species_varname_s, species_units_s @@ -1804,6 +1807,9 @@ subroutine write_ObsFcstAna_nc4(fname, exp_id, N_obsf, Observations_f, & character(len=128) :: created_by integer :: user_len, user_status integer :: i + + integer, dimension(N_obsf) :: tmpvecint + real, dimension(N_obsf) :: tmpvecreal character(len=*), parameter :: Iam = 'write_ObsFcstAna_nc4' character(len=400) :: err_msg @@ -1881,29 +1887,28 @@ subroutine write_ObsFcstAna_nc4(fname, exp_id, N_obsf, Observations_f, & call nc4_check( nf90_put_att(ncid, lon_varid, 'long_name', 'observation longitude') ) call nc4_check( nf90_put_att(ncid, lon_varid, 'standard_name', 'longitude') ) call nc4_check( nf90_put_att(ncid, lon_varid, 'units', 'degrees_east') ) - call nc4_check( nf90_put_att(ncid, lon_varid, 'missing_value', nodata_generic) ) call nc4_check( nf90_put_att(ncid, lat_varid, 'long_name', 'observation latitude') ) call nc4_check( nf90_put_att(ncid, lat_varid, 'standard_name', 'latitude') ) call nc4_check( nf90_put_att(ncid, lat_varid, 'units', 'degrees_north') ) - call nc4_check( nf90_put_att(ncid, lat_varid, 'missing_value', nodata_generic) ) + call nc4_check( nf90_put_att(ncid, obs_varid, 'long_name', 'observation value (after scaling)') ) call nc4_check( nf90_put_att(ncid, obs_varid, 'units', 'species-dependent') ) - call nc4_check( nf90_put_att(ncid, obs_varid, 'missing_value', nodata_generic) ) + call nc4_check( nf90_put_att(ncid, obs_varid, 'missing_value', MAPL_UNDEF) ) call nc4_check( nf90_put_att(ncid, obsvar_varid, 'long_name', 'observation error variance (after scaling)') ) call nc4_check( nf90_put_att(ncid, obsvar_varid, 'units', 'species-dependent') ) - call nc4_check( nf90_put_att(ncid, obsvar_varid, 'missing_value', nodata_generic) ) + call nc4_check( nf90_put_att(ncid, obsvar_varid, 'missing_value', MAPL_UNDEF) ) call nc4_check( nf90_put_att(ncid, fcst_varid, 'long_name', 'observation-equivalent model forecast value') ) call nc4_check( nf90_put_att(ncid, fcst_varid, 'units', 'species-dependent') ) - call nc4_check( nf90_put_att(ncid, fcst_varid, 'missing_value', nodata_generic) ) + call nc4_check( nf90_put_att(ncid, fcst_varid, 'missing_value', MAPL_UNDEF) ) call nc4_check( nf90_put_att(ncid, fcstvar_varid, 'long_name', 'observation-equivalent model forecast error variance') ) call nc4_check( nf90_put_att(ncid, fcstvar_varid, 'units', 'species-dependent') ) - call nc4_check( nf90_put_att(ncid, fcstvar_varid, 'missing_value', nodata_generic) ) + call nc4_check( nf90_put_att(ncid, fcstvar_varid, 'missing_value', MAPL_UNDEF) ) call nc4_check( nf90_put_att(ncid, ana_varid, 'long_name', 'observation-equivalent model analysis value') ) call nc4_check( nf90_put_att(ncid, ana_varid, 'units', 'species-dependent') ) - call nc4_check( nf90_put_att(ncid, ana_varid, 'missing_value', nodata_generic) ) + call nc4_check( nf90_put_att(ncid, ana_varid, 'missing_value', MAPL_UNDEF) ) call nc4_check( nf90_put_att(ncid, anavar_varid, 'long_name', 'observation-equivalent model analysis error variance') ) call nc4_check( nf90_put_att(ncid, anavar_varid, 'units', 'species-dependent') ) - call nc4_check( nf90_put_att(ncid, anavar_varid, 'missing_value', nodata_generic) ) + call nc4_check( nf90_put_att(ncid, anavar_varid, 'missing_value', MAPL_UNDEF) ) call nc4_check( nf90_put_att(ncid, obsparam_species_id_varid, 'long_name', 'species-level observation parameter: observation species identifier') ) call nc4_check( nf90_put_att(ncid, obsparam_species_id_varid, 'units', '1') ) @@ -1925,26 +1930,23 @@ subroutine write_ObsFcstAna_nc4(fname, exp_id, N_obsf, Observations_f, & ! write variables if (N_obsf > 0) then - - ! convert logical to integer - allocate(assim_int(N_obsf)) - assim_int = 0 - where (Observations_f(1:N_obsf)%assim) assim_int = 1 - call nc4_check( nf90_put_var(ncid, assim_flag_varid, assim_int) ) - call nc4_check( nf90_put_var(ncid, species_varid, Observations_f(1:N_obsf)%species) ) - call nc4_check( nf90_put_var(ncid, tilenum_varid, Observations_f(1:N_obsf)%tilenum) ) - call nc4_check( nf90_put_var(ncid, lon_varid, Observations_f(1:N_obsf)%lon) ) - call nc4_check( nf90_put_var(ncid, lat_varid, Observations_f(1:N_obsf)%lat) ) - call nc4_check( nf90_put_var(ncid, obs_varid, Observations_f(1:N_obsf)%obs) ) - call nc4_check( nf90_put_var(ncid, obsvar_varid, Observations_f(1:N_obsf)%obsvar) ) - call nc4_check( nf90_put_var(ncid, fcst_varid, Observations_f(1:N_obsf)%fcst) ) - call nc4_check( nf90_put_var(ncid, fcstvar_varid, Observations_f(1:N_obsf)%fcstvar) ) - call nc4_check( nf90_put_var(ncid, ana_varid, Observations_f(1:N_obsf)%ana) ) - call nc4_check( nf90_put_var(ncid, anavar_varid, Observations_f(1:N_obsf)%anavar) ) - - deallocate(assim_int) - + call nc4_check( nf90_put_var(ncid, species_varid, Observations_f(1:N_obsf)%species) ) + call nc4_check( nf90_put_var(ncid, tilenum_varid, Observations_f(1:N_obsf)%tilenum) ) + call nc4_check( nf90_put_var(ncid, lon_varid, Observations_f(1:N_obsf)%lon) ) + call nc4_check( nf90_put_var(ncid, lat_varid, Observations_f(1:N_obsf)%lat) ) + + ! for assim flag, convert logical to integer + tmpvecint = 0; where (Observations_f(1:N_obsf)%assim) tmpvecint = 1; call nc4_check( nf90_put_var(ncid, assim_flag_varid, tmpvecint)) + + ! for data fields, replace LDAS no-data-value with MAPL_UNDEF for consistency with MAPL HISTORY output + tmpvecreal = Observations_f(1:N_obsf)%obs ; where (LDAS_is_nodata(tmpvecreal)) tmpvecreal=MAPL_UNDEF; call nc4_check( nf90_put_var(ncid, obs_varid, tmpvecreal)) + tmpvecreal = Observations_f(1:N_obsf)%obsvar ; where (LDAS_is_nodata(tmpvecreal)) tmpvecreal=MAPL_UNDEF; call nc4_check( nf90_put_var(ncid, obsvar_varid, tmpvecreal)) + tmpvecreal = Observations_f(1:N_obsf)%fcst ; where (LDAS_is_nodata(tmpvecreal)) tmpvecreal=MAPL_UNDEF; call nc4_check( nf90_put_var(ncid, fcst_varid, tmpvecreal)) + tmpvecreal = Observations_f(1:N_obsf)%fcstvar; where (LDAS_is_nodata(tmpvecreal)) tmpvecreal=MAPL_UNDEF; call nc4_check( nf90_put_var(ncid, fcstvar_varid, tmpvecreal)) + tmpvecreal = Observations_f(1:N_obsf)%ana ; where (LDAS_is_nodata(tmpvecreal)) tmpvecreal=MAPL_UNDEF; call nc4_check( nf90_put_var(ncid, ana_varid, tmpvecreal)) + tmpvecreal = Observations_f(1:N_obsf)%anavar ; where (LDAS_is_nodata(tmpvecreal)) tmpvecreal=MAPL_UNDEF; call nc4_check( nf90_put_var(ncid, anavar_varid, tmpvecreal)) + end if if (N_obs_param > 0) then From 138c6b29bb44b5287af50af7f9509ad6ca648279 Mon Sep 17 00:00:00 2001 From: Rolf Reichle Date: Tue, 28 Apr 2026 12:24:52 -0400 Subject: [PATCH 37/40] in ObsFcstAna nc4 output, add "_FillValue" attribute for CF-compliance (clsm_ensupd_enkf_update.F90) --- GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 b/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 index 5a7246f1..022dbfc4 100644 --- a/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 +++ b/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 @@ -1893,21 +1893,27 @@ subroutine write_ObsFcstAna_nc4(fname, exp_id, N_obsf, Observations_f, & call nc4_check( nf90_put_att(ncid, obs_varid, 'long_name', 'observation value (after scaling)') ) call nc4_check( nf90_put_att(ncid, obs_varid, 'units', 'species-dependent') ) + call nc4_check( nf90_put_att(ncid, obs_varid, '_FillValue', MAPL_UNDEF) ) call nc4_check( nf90_put_att(ncid, obs_varid, 'missing_value', MAPL_UNDEF) ) call nc4_check( nf90_put_att(ncid, obsvar_varid, 'long_name', 'observation error variance (after scaling)') ) call nc4_check( nf90_put_att(ncid, obsvar_varid, 'units', 'species-dependent') ) + call nc4_check( nf90_put_att(ncid, obsvar_varid, '_FillValue', MAPL_UNDEF) ) call nc4_check( nf90_put_att(ncid, obsvar_varid, 'missing_value', MAPL_UNDEF) ) call nc4_check( nf90_put_att(ncid, fcst_varid, 'long_name', 'observation-equivalent model forecast value') ) call nc4_check( nf90_put_att(ncid, fcst_varid, 'units', 'species-dependent') ) + call nc4_check( nf90_put_att(ncid, fcst_varid, '_FillValue', MAPL_UNDEF) ) call nc4_check( nf90_put_att(ncid, fcst_varid, 'missing_value', MAPL_UNDEF) ) call nc4_check( nf90_put_att(ncid, fcstvar_varid, 'long_name', 'observation-equivalent model forecast error variance') ) call nc4_check( nf90_put_att(ncid, fcstvar_varid, 'units', 'species-dependent') ) + call nc4_check( nf90_put_att(ncid, fcstvar_varid, '_FillValue', MAPL_UNDEF) ) call nc4_check( nf90_put_att(ncid, fcstvar_varid, 'missing_value', MAPL_UNDEF) ) call nc4_check( nf90_put_att(ncid, ana_varid, 'long_name', 'observation-equivalent model analysis value') ) call nc4_check( nf90_put_att(ncid, ana_varid, 'units', 'species-dependent') ) + call nc4_check( nf90_put_att(ncid, ana_varid, '_FillValue', MAPL_UNDEF) ) call nc4_check( nf90_put_att(ncid, ana_varid, 'missing_value', MAPL_UNDEF) ) call nc4_check( nf90_put_att(ncid, anavar_varid, 'long_name', 'observation-equivalent model analysis error variance') ) call nc4_check( nf90_put_att(ncid, anavar_varid, 'units', 'species-dependent') ) + call nc4_check( nf90_put_att(ncid, anavar_varid, '_FillValue', MAPL_UNDEF) ) call nc4_check( nf90_put_att(ncid, anavar_varid, 'missing_value', MAPL_UNDEF) ) call nc4_check( nf90_put_att(ncid, obsparam_species_id_varid, 'long_name', 'species-level observation parameter: observation species identifier') ) From 58d3484a6e97550133bdf389badad029d1d8d2fa Mon Sep 17 00:00:00 2001 From: Rolf Reichle Date: Tue, 28 Apr 2026 12:36:53 -0400 Subject: [PATCH 38/40] in ObsFcstAna nc4 output, changed name of "nobs" dimension to "n_obs" and use fixed dimension length (not NF90_UNLIMITED) for consistency with "n_species" (clsm_ensupd_enkf_update.F90) --- GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 b/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 index 022dbfc4..061d19a2 100644 --- a/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 +++ b/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 @@ -1816,8 +1816,8 @@ subroutine write_ObsFcstAna_nc4(fname, exp_id, N_obsf, Observations_f, & call nc4_check( nf90_create( trim(fname), nf90_clobber + NF90_NETCDF4, ncid ) ) - call nc4_check( nf90_def_dim(ncid, 'nobs', NF90_UNLIMITED, nobs_dimid) ) - call nc4_check( nf90_def_dim(ncid, 'n_species', N_obs_param, nspecies_dimid) ) + call nc4_check( nf90_def_dim(ncid, 'n_obs', N_obsf, nobs_dimid) ) + call nc4_check( nf90_def_dim(ncid, 'n_species', N_obs_param, nspecies_dimid) ) call nc4_check( nf90_def_var(ncid, 'assim_flag', NF90_INT, [nobs_dimid], assim_flag_varid) ) call nc4_check( nf90_def_var(ncid, 'species', NF90_INT, [nobs_dimid], species_varid) ) From 52ef3bb9e3ced0eecb90eb6b78c7410d059c98ba Mon Sep 17 00:00:00 2001 From: Rolf Reichle Date: Tue, 28 Apr 2026 16:07:21 -0400 Subject: [PATCH 39/40] added comment/documentation re. "rf2f"; minimal white-space changes (clsm_ensupd_enkf_update.F90, GEOS_LandAssimGridComp.F90) --- GEOSlandassim_GridComp/GEOS_LandAssimGridComp.F90 | 2 +- GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/GEOSlandassim_GridComp/GEOS_LandAssimGridComp.F90 b/GEOSlandassim_GridComp/GEOS_LandAssimGridComp.F90 index a8177203..5fe2d892 100644 --- a/GEOSlandassim_GridComp/GEOS_LandAssimGridComp.F90 +++ b/GEOSlandassim_GridComp/GEOS_LandAssimGridComp.F90 @@ -105,7 +105,7 @@ module GEOS_LandAssimGridCompMod integer, dimension(:), pointer :: N_catl_vec,low_ind integer :: N_catf - !reordered tile_coord_rf and mapping l2rf + ! reordered tile_coord_rf and mapping l2rf integer, dimension(:), pointer :: l2rf, rf2l,rf2g, rf2f type(tile_coord_type), dimension(:), pointer :: tile_coord_rf => null() diff --git a/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 b/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 index 061d19a2..c8570b57 100644 --- a/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 +++ b/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 @@ -1538,7 +1538,7 @@ subroutine output_ObsFcstAna(date_time, exp_id, & type(obs_type), dimension(N_obsl), intent(in) :: Observations_l - integer, dimension(:), optional, intent(in) :: rf2f + integer, dimension(:), optional, intent(in) :: rf2f ! re-ordered to LDASsa ! --------------------- @@ -2072,9 +2072,9 @@ subroutine output_ObsFcstAna_wrapper( out_ObsFcstAna, & type(mwRTM_param_type), dimension(N_catl), intent(in) :: mwRTM_param - type(obs_type), dimension(:), pointer :: Observations_l ! inout + type(obs_type), dimension(:), pointer :: Observations_l ! inout - integer, dimension(N_catf), optional, intent(in) :: rf2f ! re-ordered to LDASsa + integer, dimension(N_catf), optional, intent(in) :: rf2f ! re-ordered to LDASsa ! local variables From 41ab7d83139aa7a71070cbd7a35cc10ffbfdeb73 Mon Sep 17 00:00:00 2001 From: amfox37 Date: Thu, 30 Apr 2026 13:39:44 -0600 Subject: [PATCH 40/40] Use scalar no-data replacement when writing ObsFcstAna nc4 fields --- .../clsm_ensupd_enkf_update.F90 | 47 ++++++++++++++++--- 1 file changed, 40 insertions(+), 7 deletions(-) diff --git a/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 b/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 index c8570b57..d9cb8278 100644 --- a/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 +++ b/GEOSlandassim_GridComp/clsm_ensupd_enkf_update.F90 @@ -1943,15 +1943,48 @@ subroutine write_ObsFcstAna_nc4(fname, exp_id, N_obsf, Observations_f, & call nc4_check( nf90_put_var(ncid, lat_varid, Observations_f(1:N_obsf)%lat) ) ! for assim flag, convert logical to integer - tmpvecint = 0; where (Observations_f(1:N_obsf)%assim) tmpvecint = 1; call nc4_check( nf90_put_var(ncid, assim_flag_varid, tmpvecint)) + tmpvecint = 0 + where (Observations_f(1:N_obsf)%assim) + tmpvecint = 1 + end where + call nc4_check( nf90_put_var(ncid, assim_flag_varid, tmpvecint) ) ! for data fields, replace LDAS no-data-value with MAPL_UNDEF for consistency with MAPL HISTORY output - tmpvecreal = Observations_f(1:N_obsf)%obs ; where (LDAS_is_nodata(tmpvecreal)) tmpvecreal=MAPL_UNDEF; call nc4_check( nf90_put_var(ncid, obs_varid, tmpvecreal)) - tmpvecreal = Observations_f(1:N_obsf)%obsvar ; where (LDAS_is_nodata(tmpvecreal)) tmpvecreal=MAPL_UNDEF; call nc4_check( nf90_put_var(ncid, obsvar_varid, tmpvecreal)) - tmpvecreal = Observations_f(1:N_obsf)%fcst ; where (LDAS_is_nodata(tmpvecreal)) tmpvecreal=MAPL_UNDEF; call nc4_check( nf90_put_var(ncid, fcst_varid, tmpvecreal)) - tmpvecreal = Observations_f(1:N_obsf)%fcstvar; where (LDAS_is_nodata(tmpvecreal)) tmpvecreal=MAPL_UNDEF; call nc4_check( nf90_put_var(ncid, fcstvar_varid, tmpvecreal)) - tmpvecreal = Observations_f(1:N_obsf)%ana ; where (LDAS_is_nodata(tmpvecreal)) tmpvecreal=MAPL_UNDEF; call nc4_check( nf90_put_var(ncid, ana_varid, tmpvecreal)) - tmpvecreal = Observations_f(1:N_obsf)%anavar ; where (LDAS_is_nodata(tmpvecreal)) tmpvecreal=MAPL_UNDEF; call nc4_check( nf90_put_var(ncid, anavar_varid, tmpvecreal)) + tmpvecreal = Observations_f(1:N_obsf)%obs + do i=1,N_obsf + if (LDAS_is_nodata(tmpvecreal(i))) tmpvecreal(i) = MAPL_UNDEF + end do + call nc4_check( nf90_put_var(ncid, obs_varid, tmpvecreal) ) + + tmpvecreal = Observations_f(1:N_obsf)%obsvar + do i=1,N_obsf + if (LDAS_is_nodata(tmpvecreal(i))) tmpvecreal(i) = MAPL_UNDEF + end do + call nc4_check( nf90_put_var(ncid, obsvar_varid, tmpvecreal) ) + + tmpvecreal = Observations_f(1:N_obsf)%fcst + do i=1,N_obsf + if (LDAS_is_nodata(tmpvecreal(i))) tmpvecreal(i) = MAPL_UNDEF + end do + call nc4_check( nf90_put_var(ncid, fcst_varid, tmpvecreal) ) + + tmpvecreal = Observations_f(1:N_obsf)%fcstvar + do i=1,N_obsf + if (LDAS_is_nodata(tmpvecreal(i))) tmpvecreal(i) = MAPL_UNDEF + end do + call nc4_check( nf90_put_var(ncid, fcstvar_varid, tmpvecreal) ) + + tmpvecreal = Observations_f(1:N_obsf)%ana + do i=1,N_obsf + if (LDAS_is_nodata(tmpvecreal(i))) tmpvecreal(i) = MAPL_UNDEF + end do + call nc4_check( nf90_put_var(ncid, ana_varid, tmpvecreal) ) + + tmpvecreal = Observations_f(1:N_obsf)%anavar + do i=1,N_obsf + if (LDAS_is_nodata(tmpvecreal(i))) tmpvecreal(i) = MAPL_UNDEF + end do + call nc4_check( nf90_put_var(ncid, anavar_varid, tmpvecreal) ) end if