From 8cae291b9ddf978986d2a8f3081e5ee54c8eee85 Mon Sep 17 00:00:00 2001 From: mvertens Date: Tue, 1 Sep 2026 14:06:16 +0200 Subject: [PATCH 1/3] Add optional stream_scale_factor for unit conversion on read and fix ndep unit conversion and cplhist field mapping --- datm/cime_config/stream_definition_datm.xml | 38 +++++----- datm/datm_pres_ndep_mod.F90 | 31 ++++---- streams/dshr_strdata_mod.F90 | 25 +++++++ streams/dshr_stream_mod.F90 | 79 ++++++++++++++++++++- 4 files changed, 139 insertions(+), 34 deletions(-) diff --git a/datm/cime_config/stream_definition_datm.xml b/datm/cime_config/stream_definition_datm.xml index ced657e8e..702acd166 100644 --- a/datm/cime_config/stream_definition_datm.xml +++ b/datm/cime_config/stream_definition_datm.xml @@ -4933,8 +4933,8 @@ - NDEP_NHx_month Faxa_ndep_nhx - NDEP_NOy_month Faxa_ndep_noy + NDEP_NHx_month Faxa_ndep_nhx 1.0e-3 + NDEP_NOy_month Faxa_ndep_noy 1.0e-3 null @@ -4966,8 +4966,8 @@ - NDEP_NHx_month Faxa_ndep_nhx - NDEP_NOy_month Faxa_ndep_noy + NDEP_NHx_month Faxa_ndep_nhx 1.0e-3 + NDEP_NOy_month Faxa_ndep_noy 1.0e-3 null @@ -4998,8 +4998,8 @@ $DIN_LOC_ROOT/lnd/clm2/ndepdata/fndep_clm_hist_b.e21.BWHIST.f09_g17.CMIP6-historical-WACCM.ensmean_1849-2015_monthly_0.9x1.25_c180926.nc - NDEP_NHx_month Faxa_ndep_nhx - NDEP_NOy_month Faxa_ndep_noy + NDEP_NHx_month Faxa_ndep_nhx 1.0e-3 + NDEP_NOy_month Faxa_ndep_noy 1.0e-3 null @@ -5031,8 +5031,8 @@ - NDEP_NHx_month Faxa_ndep_nhx - NDEP_NOy_month Faxa_ndep_noy + NDEP_NHx_month Faxa_ndep_nhx 1.0e-3 + NDEP_NOy_month Faxa_ndep_noy 1.0e-3 null @@ -5064,8 +5064,8 @@ - NDEP_NHx_month Faxa_ndep_nhx - NDEP_NOy_month Faxa_ndep_noy + NDEP_NHx_month Faxa_ndep_nhx 1.0e-3 + NDEP_NOy_month Faxa_ndep_noy 1.0e-3 null @@ -5097,8 +5097,8 @@ - NDEP_NHx_month Faxa_ndep_nhx - NDEP_NOy_month Faxa_ndep_noy + NDEP_NHx_month Faxa_ndep_nhx 1.0e-3 + NDEP_NOy_month Faxa_ndep_noy 1.0e-3 null @@ -5130,8 +5130,8 @@ - NDEP_NHx_month Faxa_ndep_nhx - NDEP_NOy_month Faxa_ndep_noy + NDEP_NHx_month Faxa_ndep_nhx 1.0e-3 + NDEP_NOy_month Faxa_ndep_noy 1.0e-3 null @@ -5164,8 +5164,8 @@ - NDEP_NHx_month Faxa_ndep_nhx - NDEP_NOy_month Faxa_ndep_noy + NDEP_NHx_month Faxa_ndep_nhx 1.0e-3 + NDEP_NOy_month Faxa_ndep_noy 1.0e-3 null @@ -5274,7 +5274,11 @@ $DATM_CPLHIST_DIR/$DATM_CPLHIST_CASE.cpl.ha2x1d.%ym.nc - a2x1d_Faxa_ndep Faxa_ndep + + atmImp_Faxa_ndep1 Faxa_ndep_nhx + atmImp_Faxa_ndep2 Faxa_ndep_noy null diff --git a/datm/datm_pres_ndep_mod.F90 b/datm/datm_pres_ndep_mod.F90 index 2b1548e12..7fb118d57 100644 --- a/datm/datm_pres_ndep_mod.F90 +++ b/datm/datm_pres_ndep_mod.F90 @@ -26,7 +26,6 @@ module datm_pres_ndep_mod real(r8), pointer :: strm_Faxa_ndep_nhx(:) => null() ! pre-cmip7 ndep data real(r8), pointer :: strm_Faxa_ndep_noy(:) => null() ! pre-cmip7 ndep data - logical :: use_cmip7_ndep character(len=*), parameter :: u_FILE_u = & __FILE__ @@ -82,14 +81,11 @@ subroutine datm_pres_ndep_init_pointers(exportState, sdat, rc) call shr_strdata_get_stream_pointer( sdat, 'Faxa_ndep_noy', strm_Faxa_ndep_noy, rc) if (ChkErr(rc,__LINE__,u_FILE_u)) return - ! determine use_cmip_ndep module variable - if (associated(strm_Faxa_ndep_nhx_dry) .and. associated(strm_Faxa_ndep_nhx_wet) .and. & - associated(strm_Faxa_ndep_noy_dry) .and. associated(strm_Faxa_ndep_noy_wet)) then - use_cmip7_ndep = .true. - else if (associated(strm_Faxa_ndep_nhx) .and. associated(strm_Faxa_ndep_noy)) then - use_cmip7_ndep = .false. - else - call shr_log_error('datm_ndep_advance: ERROR: no associated stream pointers for ndep forcing', rc=rc) + ! Check that one of the two supported field sets was found. + if (.not. ((associated(strm_Faxa_ndep_nhx_dry) .and. associated(strm_Faxa_ndep_nhx_wet) .and. & + associated(strm_Faxa_ndep_noy_dry) .and. associated(strm_Faxa_ndep_noy_wet)) .or. & + (associated(strm_Faxa_ndep_nhx) .and. associated(strm_Faxa_ndep_noy)))) then + call shr_log_error('datm_pres_ndep: ERROR: no associated stream pointers for ndep forcing', rc=rc) return end if @@ -98,14 +94,21 @@ end subroutine datm_pres_ndep_init_pointers !=============================================================================== subroutine datm_pres_ndep_advance() - if (use_cmip7_ndep) then - ! assume data is in kgN/m2/s + ! Separate dry and wet deposition fields are summed where the stream provides + ! them; otherwise the combined fields are used. + ! + ! No unit conversion happens here. Streams whose data is not already in + ! kgN/m2/s declare a stream_scale_factor in the stream definition xml, and + ! the conversion is applied as the data is read. Doing it here was wrong: + ! the same two field names are supplied in gN/m2/s by the CMIP6 forcing + ! datasets and in kgN/m2/s by cplhist output, so the field names cannot say + ! which conversion, if any, is needed. + if (associated(strm_Faxa_ndep_nhx_dry)) then Faxa_ndep(1,:) = strm_Faxa_ndep_nhx_dry(:) + strm_Faxa_ndep_nhx_wet(:) Faxa_ndep(2,:) = strm_Faxa_ndep_noy_dry(:) + strm_Faxa_ndep_noy_wet(:) else - ! convert ndep flux to units of kgN/m2/s (input is in gN/m2/s) - Faxa_ndep(1,:) = strm_Faxa_ndep_nhx(:) / 1000._r8 - Faxa_ndep(2,:) = strm_Faxa_ndep_noy(:) / 1000._r8 + Faxa_ndep(1,:) = strm_Faxa_ndep_nhx(:) + Faxa_ndep(2,:) = strm_Faxa_ndep_noy(:) end if end subroutine datm_pres_ndep_advance diff --git a/streams/dshr_strdata_mod.F90 b/streams/dshr_strdata_mod.F90 index 1489ba918..64ccdbbfa 100644 --- a/streams/dshr_strdata_mod.F90 +++ b/streams/dshr_strdata_mod.F90 @@ -34,6 +34,7 @@ module dshr_strdata_mod use shr_string_mod , only : shr_string_listgetname, shr_string_listisvalid, shr_string_listgetnum use dshr_stream_mod , only : shr_stream_streamtype, shr_stream_getModelFieldList, shr_stream_getStreamFieldList + use dshr_stream_mod , only : shr_stream_getFieldScaleFactors use dshr_stream_mod , only : shr_stream_taxis_cycle, shr_stream_taxis_extend, shr_stream_findBounds use dshr_stream_mod , only : shr_stream_getCurrFile, shr_stream_setCurrFile, shr_stream_getMeshFilename use dshr_stream_mod , only : shr_stream_init_from_inline, shr_stream_init_from_esmfconfig @@ -98,6 +99,7 @@ module dshr_strdata_mod type(ESMF_RouteHandle) :: routehandle ! stream n -> model mesh mapping character(len=CL), allocatable :: fldlist_stream(:) ! names of stream file fields character(len=CL), allocatable :: fldlist_model(:) ! names of stream model fields + real(r8), allocatable :: fldlist_scale(:) ! per-field unit conversion applied on read integer :: stream_nlev ! number of vertical levels in stream real(r8), allocatable :: stream_vlevs(:) ! values of vertical levels in stream integer :: stream_lb ! index of the Lowerbound (LB) in fldlist_stream @@ -554,6 +556,13 @@ subroutine shr_strdata_init(sdat, model_clock, stream_name, rc) return end if call shr_stream_getStreamFieldList(sdat%stream(ns), sdat%pstrm(ns)%fldlist_stream) + allocate(sdat%pstrm(ns)%fldlist_scale(nvars), stat=istat) + if ( istat /= 0 ) then + call shr_log_error(subName//& + ': allocation error for sdat%pstrm('//toString(ns)//')%fldlist_scale with nvars '//toString(nvars), rc=rc) + return + end if + call shr_stream_getFieldScaleFactors(sdat%stream(ns), sdat%pstrm(ns)%fldlist_scale) ! Create field bundles on model mesh if (sdat%stream(ns)%readmode=='single') then @@ -2132,6 +2141,22 @@ subroutine shr_strdata_readstrm(sdat, per_stream, stream, fldbun_data, & end if + ! Apply this field's unit conversion, if the stream definition gave one as + ! a third token on its line. Units belong to the field rather than + ! the stream, so each field carries its own factor and a stream may mix + ! converted and unconverted fields. + ! + ! Fill values are left alone: they are markers, not measurements. This is + ! separate from the CF scale_factor/add_offset unpacking above, which + ! undoes on-disk packing and has already been applied by this point. + if (per_stream%fldlist_scale(nf) /= 1.0_r8) then + if (stream_nlev > 1) then + where (dataptr2d /= r8fill) dataptr2d = dataptr2d * per_stream%fldlist_scale(nf) + else + where (dataptr1d /= r8fill) dataptr1d = dataptr1d * per_stream%fldlist_scale(nf) + end if + end if + if(associated(dataptr2d_src) .and. trim(per_stream%fldlist_model(nf)) .eq. uname) then ! save in dataptr2d_src dataptr2d_src(1,:) = dataptr1d(:) diff --git a/streams/dshr_stream_mod.F90 b/streams/dshr_stream_mod.F90 index e46e33caf..1648b9fed 100644 --- a/streams/dshr_stream_mod.F90 +++ b/streams/dshr_stream_mod.F90 @@ -52,7 +52,8 @@ module dshr_stream_mod public :: shr_stream_findBounds ! return lower/upper bounding date info public :: shr_stream_getMeshFileName ! return stream filename public :: shr_stream_getModelFieldList ! return model field name list - public :: shr_stream_getStreamFieldList! return stream file field name list + public :: shr_stream_getStreamFieldList ! return stream file field name list + public :: shr_stream_getFieldScaleFactors ! return per-field unit conversion factors public :: shr_stream_getPrevFileName ! return previous file in sequence public :: shr_stream_getNextFileName ! return next file in sequence public :: shr_stream_getNFiles ! get the number of files in a stream @@ -99,6 +100,11 @@ module dshr_stream_mod type shr_stream_data_variable character(len=CS) :: nameinfile character(len=CS) :: nameinmodel + ! Optional unit conversion, applied to this field as it is read. Given as + ! a third token on the line in the stream definition xml; absent + ! means 1.0. Units are a property of the field, not of the stream, so a + ! stream may mix converted and unconverted fields. + real(r8) :: scale_factor = 1.0_r8 end type shr_stream_data_variable type shr_stream_streamType @@ -350,8 +356,7 @@ subroutine shr_stream_init_from_xml(streamfilename, streamdat, isroot_task, logu do n = 1, streamdat(i)%nvars p => item(varlist, n-1) call extractDataContent(p, tmpstr) - streamdat(i)%varlist(n)%nameinfile = tmpstr(1:index(tmpstr, " ")) - streamdat(i)%varlist(n)%nameinmodel = tmpstr(index(trim(tmpstr), " ", .true.)+1:) + call parse_var_entry(tmpstr, streamdat(i)%varlist(n)) enddo enddo @@ -413,6 +418,10 @@ subroutine shr_stream_init_from_xml(streamfilename, streamdat, isroot_task, logu if (ChkErr(rc,__LINE__,u_FILE_u)) return call ESMF_VMBroadCast(vm, streamdat(i)%varlist(n)%nameinmodel, CS, 0, rc=rc) if (ChkErr(rc,__LINE__,u_FILE_u)) return + rtmp(1) = streamdat(i)%varlist(n)%scale_factor + call ESMF_VMBroadCast(vm, rtmp, 1, 0, rc=rc) + if (ChkErr(rc,__LINE__,u_FILE_u)) return + streamdat(i)%varlist(n)%scale_factor = rtmp(1) enddo call ESMF_VMBroadCast(vm, streamdat(i)%meshfile, CL, 0, rc=rc) if (ChkErr(rc,__LINE__,u_FILE_u)) return @@ -1613,6 +1622,70 @@ subroutine shr_stream_getModelFieldList(stream, list) end subroutine shr_stream_getModelFieldList + !=============================================================================== + subroutine parse_var_entry(entry, var) + + ! Parse one line into its two or three whitespace-separated tokens: + ! + ! drynhx Faxa_ndep_nhx_dry + ! NDEP_NHx_month Faxa_ndep_nhx 1.0e-3 + ! + ! The optional third token is a unit-conversion factor applied to this field + ! as it is read. Absent leaves the default of 1.0. + + ! input/output parameters: + character(len=*) ,intent(in) :: entry + type(shr_stream_data_variable) ,intent(inout) :: var + + integer :: pos, next, ios + character(len=CX) :: rest, third + character(len=*),parameter :: subName = '(parse_var_entry) ' + !------------------------------------------------------------------------------- + + rest = adjustl(entry) + + pos = index(trim(rest), " ") + if (pos == 0) then + call shr_sys_abort(subName//" stream var entry needs at least two fields: "//trim(entry)) + end if + var%nameinfile = rest(1:pos-1) + + rest = adjustl(rest(pos+1:)) + next = index(trim(rest), " ") + if (next == 0) then + ! two tokens: no conversion + var%nameinmodel = trim(rest) + var%scale_factor = 1.0_r8 + return + end if + var%nameinmodel = rest(1:next-1) + + third = adjustl(rest(next+1:)) + read(third, *, iostat=ios) var%scale_factor + if (ios /= 0) then + call shr_sys_abort(subName//" could not read scale factor from stream var entry: "//trim(entry)) + end if + + end subroutine parse_var_entry + + !=============================================================================== + subroutine shr_stream_getFieldScaleFactors(stream, factors) + + ! Get the per-field unit-conversion factors, in the same order as + ! shr_stream_getStreamFieldList and shr_stream_getModelFieldList. + + !input/output parameters: + type(shr_stream_streamType) ,intent(in) :: stream + real(r8) ,intent(out) :: factors(:) + !------------------------------------------------------------------------------- + integer :: i + + do i=1,stream%nvars + factors(i) = stream%varlist(i)%scale_factor + enddo + + end subroutine shr_stream_getFieldScaleFactors + !=============================================================================== subroutine shr_stream_getStreamFieldList(stream, list) From afc7bb744ba3db6433db58fcfb66d4045dc98e17 Mon Sep 17 00:00:00 2001 From: mvertens Date: Tue, 1 Sep 2026 14:11:17 +0200 Subject: [PATCH 2/3] fixed specification of forcing data for presndep.cplhist --- datm/cime_config/stream_definition_datm.xml | 2 +- datm/datm_pres_ndep_mod.F90 | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/datm/cime_config/stream_definition_datm.xml b/datm/cime_config/stream_definition_datm.xml index 702acd166..0cb5f5839 100644 --- a/datm/cime_config/stream_definition_datm.xml +++ b/datm/cime_config/stream_definition_datm.xml @@ -5271,7 +5271,7 @@ $ATM_DOMAIN_MESH - $DATM_CPLHIST_DIR/$DATM_CPLHIST_CASE.cpl.ha2x1d.%ym.nc + $DATM_CPLHIST_DIR/$DATM_CPLHIST_CASE.cpl.hx.atm.24h.avrg.%ymd.nc - NDEP_NHx_month Faxa_ndep_nhx 1.0e-3 - NDEP_NOy_month Faxa_ndep_noy 1.0e-3 + NDEP_NHx_month Faxa_ndep_nhx 1.0d-3 + NDEP_NOy_month Faxa_ndep_noy 1.0d-3 null @@ -4966,8 +4966,8 @@ - NDEP_NHx_month Faxa_ndep_nhx 1.0e-3 - NDEP_NOy_month Faxa_ndep_noy 1.0e-3 + NDEP_NHx_month Faxa_ndep_nhx 1.0d-3 + NDEP_NOy_month Faxa_ndep_noy 1.0d-3 null @@ -4998,8 +4998,9 @@ $DIN_LOC_ROOT/lnd/clm2/ndepdata/fndep_clm_hist_b.e21.BWHIST.f09_g17.CMIP6-historical-WACCM.ensmean_1849-2015_monthly_0.9x1.25_c180926.nc - NDEP_NHx_month Faxa_ndep_nhx 1.0e-3 - NDEP_NOy_month Faxa_ndep_noy 1.0e-3 + + NDEP_NHx_month Faxa_ndep_nhx 1.0d-3 + NDEP_NOy_month Faxa_ndep_noy 1.0d-3 null @@ -5031,8 +5032,8 @@ - NDEP_NHx_month Faxa_ndep_nhx 1.0e-3 - NDEP_NOy_month Faxa_ndep_noy 1.0e-3 + NDEP_NHx_month Faxa_ndep_nhx 1.0d-3 + NDEP_NOy_month Faxa_ndep_noy 1.0d-3 null @@ -5064,8 +5065,8 @@ - NDEP_NHx_month Faxa_ndep_nhx 1.0e-3 - NDEP_NOy_month Faxa_ndep_noy 1.0e-3 + NDEP_NHx_month Faxa_ndep_nhx 1.0d-3 + NDEP_NOy_month Faxa_ndep_noy 1.0d-3 null @@ -5097,8 +5098,8 @@ - NDEP_NHx_month Faxa_ndep_nhx 1.0e-3 - NDEP_NOy_month Faxa_ndep_noy 1.0e-3 + NDEP_NHx_month Faxa_ndep_nhx 1.0d-3 + NDEP_NOy_month Faxa_ndep_noy 1.0d-3 null @@ -5130,8 +5131,8 @@ - NDEP_NHx_month Faxa_ndep_nhx 1.0e-3 - NDEP_NOy_month Faxa_ndep_noy 1.0e-3 + NDEP_NHx_month Faxa_ndep_nhx 1.0d-3 + NDEP_NOy_month Faxa_ndep_noy 1.0d-3 null @@ -5164,8 +5165,8 @@ - NDEP_NHx_month Faxa_ndep_nhx 1.0e-3 - NDEP_NOy_month Faxa_ndep_noy 1.0e-3 + NDEP_NHx_month Faxa_ndep_nhx 1.0d-3 + NDEP_NOy_month Faxa_ndep_noy 1.0d-3 null diff --git a/datm/datm_pres_ndep_mod.F90 b/datm/datm_pres_ndep_mod.F90 index 009395c5b..be81395b3 100644 --- a/datm/datm_pres_ndep_mod.F90 +++ b/datm/datm_pres_ndep_mod.F90 @@ -96,13 +96,6 @@ subroutine datm_pres_ndep_advance() ! Separate dry and wet deposition fields are summed where the stream provides ! them; otherwise the combined fields are used. - ! - ! No unit conversion happens here. Streams whose data is not already in - ! kgN/m2/s declare a stream_scale_factor in the stream definition xml, and - ! the conversion is applied as the data is read. Doing it here was wrong: - ! the same two field names are supplied in gN/m2/s by the CMIP6 forcing - ! datasets and in kgN/m2/s by cplhist output, so the field names cannot say - ! which conversion, if any, is needed. if (associated(strm_Faxa_ndep_nhx_dry)) then Faxa_ndep(1,:) = strm_Faxa_ndep_nhx_dry(:) + strm_Faxa_ndep_nhx_wet(:) Faxa_ndep(2,:) = strm_Faxa_ndep_noy_dry(:) + strm_Faxa_ndep_noy_wet(:) diff --git a/streams/dshr_stream_mod.F90 b/streams/dshr_stream_mod.F90 index 1648b9fed..0a860dc4f 100644 --- a/streams/dshr_stream_mod.F90 +++ b/streams/dshr_stream_mod.F90 @@ -1646,7 +1646,8 @@ subroutine parse_var_entry(entry, var) pos = index(trim(rest), " ") if (pos == 0) then - call shr_sys_abort(subName//" stream var entry needs at least two fields: "//trim(entry)) + call shr_sys_abort(subName//" stream var entry needs at least two fields: "//trim(entry), & + file=u_FILE_u, line=__LINE__) end if var%nameinfile = rest(1:pos-1) @@ -1663,7 +1664,8 @@ subroutine parse_var_entry(entry, var) third = adjustl(rest(next+1:)) read(third, *, iostat=ios) var%scale_factor if (ios /= 0) then - call shr_sys_abort(subName//" could not read scale factor from stream var entry: "//trim(entry)) + call shr_sys_abort(subName//" could not read scale factor from stream var entry: "//trim(entry), & + file=u_FILE_u, line=__LINE__) end if end subroutine parse_var_entry