From 5544903f9e29d241e9371580a9bfc8c14596b200 Mon Sep 17 00:00:00 2001 From: Michael Levy Date: Fri, 10 Apr 2026 07:58:28 -0600 Subject: [PATCH 1/3] Make sure MARBL fluxes get averaged correctly When DT_THERMO is larger than the coupling interval, MOM6 stores a weighted average of fluxes used by the thermodynamics over the multiple intervals. Several forcings used for MARBL were not included in this process. Also, dust_flux and iron_flux are computed from five constituent classes (atmospheric fine / coarse dust, sea ice dust, atmospheric black carbon, and sea ice black carbon); these five fields were not being accumulated correctly in cases where DT_THERMO is larger than the coupling interval. This commit contains a first attempt at fixing that -- moving the enable_averages() call into convert_driver_fields_to_forcings() and only calling it / post_data() when flux_used = true. These diagnostics are still wrong in this commit, though. --- .../nuopc_cap/mom_ocean_model_nuopc.F90 | 8 +- .../nuopc_cap/mom_surface_forcing_nuopc.F90 | 14 ++-- src/core/MOM_forcing_type.F90 | 80 ++++++++++++++++--- src/tracer/MARBL_forcing_mod.F90 | 44 +++++----- 4 files changed, 109 insertions(+), 37 deletions(-) diff --git a/config_src/drivers/nuopc_cap/mom_ocean_model_nuopc.F90 b/config_src/drivers/nuopc_cap/mom_ocean_model_nuopc.F90 index 87a16300ee..f4207f1d96 100644 --- a/config_src/drivers/nuopc_cap/mom_ocean_model_nuopc.F90 +++ b/config_src/drivers/nuopc_cap/mom_ocean_model_nuopc.F90 @@ -564,13 +564,13 @@ subroutine update_ocean_model(Ice_ocean_boundary, OS, Ocean_sfc, & if (OS%fluxes%fluxes_used) then - ! enable_averages() is necessary to post forcing fields to diagnostics - call enable_averages(dt_coupling, OS%Time + Ocean_coupling_time_step, OS%diag) - if (do_thermo) & call convert_IOB_to_fluxes(Ice_ocean_boundary, OS%fluxes, index_bnds, OS%Time, dt_coupling, & OS%grid, OS%US, OS%forcing_CSp, OS%sfc_state, & - OS%restore_salinity, OS%restore_temp) + OS%restore_salinity, OS%restore_temp, Ocean_coupling_time_step) + + ! enable_averages() is necessary to post forcing fields to diagnostics + call enable_averages(dt_coupling, OS%Time + Ocean_coupling_time_step, OS%diag) ! Add ice shelf fluxes if (OS%use_ice_shelf) then diff --git a/config_src/drivers/nuopc_cap/mom_surface_forcing_nuopc.F90 b/config_src/drivers/nuopc_cap/mom_surface_forcing_nuopc.F90 index a32987e6a2..9af2c7255e 100644 --- a/config_src/drivers/nuopc_cap/mom_surface_forcing_nuopc.F90 +++ b/config_src/drivers/nuopc_cap/mom_surface_forcing_nuopc.F90 @@ -247,7 +247,7 @@ module MOM_surface_forcing_nuopc !! thermodynamic forcing type, including changes of units, sign conventions, !! and putting the fields into arrays with MOM-standard halos. subroutine convert_IOB_to_fluxes(IOB, fluxes, index_bounds, Time, valid_time, G, US, CS, & - sfc_state, restore_salt, restore_temp) + sfc_state, restore_salt, restore_temp, Ocean_coupling_time_step) type(ice_ocean_boundary_type), & target, intent(in) :: IOB !< An ice-ocean boundary type with fluxes to drive !! the ocean in a coupled model @@ -265,8 +265,10 @@ subroutine convert_IOB_to_fluxes(IOB, fluxes, index_bounds, Time, valid_time, G, !! previous call to surface_forcing_init. type(surface), intent(in) :: sfc_state !< A structure containing fields that describe the !! surface state of the ocean. - logical, optional, intent(in) :: restore_salt !< If true, salinity is restored to a target value. - logical, optional, intent(in) :: restore_temp !< If true, temperature is restored to a target value. + logical, optional, intent(in) :: restore_salt !< If true, salinity is restored to a target value. + logical, optional, intent(in) :: restore_temp !< If true, temperature is restored to a target value. + type(time_type), optional, intent(in) :: Ocean_coupling_time_step !< The amount of time over + !! which to advance the ocean. ! local variables real, dimension(SZI_(G),SZJ_(G)) :: & @@ -620,7 +622,8 @@ subroutine convert_IOB_to_fluxes(IOB, fluxes, index_bounds, Time, valid_time, G, IOB%seaice_dust_flux, IOB%atm_bc_flux, IOB%seaice_bc_flux, & IOB%nhx_dep, IOB%noy_dep, IOB%atm_co2_prog, IOB%atm_co2_diag, & IOB%afracr, IOB%swnet_afracr, IOB%ifrac_n, IOB%swpen_ifrac_n, & - Time, G, US, i0, j0, fluxes, CS%marbl_forcing_CSp) + Time, G, US, i0, j0, fluxes, CS%marbl_forcing_CSp, & + Ocean_coupling_time_step) ! wave to ocean coupling if ( associated(IOB%lamult)) then @@ -1468,7 +1471,8 @@ subroutine surface_forcing_init(Time, G, US, param_file, diag, CS, restore_salt, call register_forcing_type_diags(Time, diag, US, CS%use_temperature, CS%handles, & use_berg_fluxes=iceberg_flux_diags, use_waves=use_waves, & - use_cfcs=CS%use_CFC, use_glc_runoff=glc_runoff_diags) + use_cfcs=CS%use_CFC, use_MARBL_tracers=CS%use_MARBL_tracers, & + use_glc_runoff=glc_runoff_diags) call get_param(param_file, mdl, "ALLOW_FLUX_ADJUSTMENTS", CS%allow_flux_adjustments, & "If true, allows flux adjustments to specified via the "//& diff --git a/src/core/MOM_forcing_type.F90 b/src/core/MOM_forcing_type.F90 index 1ceaa3988f..60160db3c6 100644 --- a/src/core/MOM_forcing_type.F90 +++ b/src/core/MOM_forcing_type.F90 @@ -1540,7 +1540,7 @@ end subroutine forcing_SinglePointPrint !> Register members of the forcing type for diagnostics subroutine register_forcing_type_diags(Time, diag, US, use_temperature, handles, use_berg_fluxes, use_waves, & - use_cfcs, use_glc_runoff) + use_cfcs, use_MARBL_tracers, use_glc_runoff) type(time_type), intent(in) :: Time !< time type type(diag_ctrl), intent(inout) :: diag !< diagnostic control type type(unit_scale_type), intent(in) :: US !< A dimensional unit scaling type @@ -1549,8 +1549,18 @@ subroutine register_forcing_type_diags(Time, diag, US, use_temperature, handles, logical, optional, intent(in) :: use_berg_fluxes !< If true, allow iceberg flux diagnostics logical, optional, intent(in) :: use_waves !< If true, allow wave forcing diagnostics logical, optional, intent(in) :: use_cfcs !< If true, allow cfc related diagnostics + logical, optional, intent(in) :: use_MARBL_tracers !< If true, allow MARBL related diagnostics logical, optional, intent(in) :: use_glc_runoff !< If true, allow separate glacial runoff diagnostics + logical :: use_cfcs_or_MARBL_tracers + + ! some diagnostics should be registered if either cfc or MARBL tracers are enabled + use_cfcs_or_MARBL_tracers = .false. + if (present(use_cfcs)) & + use_cfcs_or_MARBL_tracers = use_cfcs_or_MARBL_tracers .or. use_cfcs + if (present(use_MARBL_tracers)) & + use_cfcs_or_MARBL_tracers = use_cfcs_or_MARBL_tracers .or. use_MARBL_tracers + ! Clock for forcing diagnostics handles%id_clock_forcing=cpu_clock_id('(Ocean forcing diagnostics)', grain=CLOCK_ROUTINE) @@ -1599,15 +1609,12 @@ subroutine register_forcing_type_diags(Time, diag, US, use_temperature, handles, endif endif - ! See: - if (present(use_cfcs)) then - if (use_cfcs) then - handles%id_ice_fraction = register_diag_field('ocean_model', 'ice_fraction', diag%axesT1, Time, & - 'Fraction of cell area covered by sea ice', 'm2 m-2', conversion=1.0) + if (use_cfcs_or_MARBL_tracers) then + handles%id_ice_fraction = register_diag_field('ocean_model', 'ice_fraction', diag%axesT1, Time, & + 'Fraction of cell area covered by sea ice', 'm2 m-2', conversion=1.0) - handles%id_u10_sqr = register_diag_field('ocean_model', 'u10_sqr', diag%axesT1, Time, & - 'Wind magnitude at 10m, squared', 'm2 s-2', conversion=US%L_to_m**2*US%s_to_T**2) - endif + handles%id_u10_sqr = register_diag_field('ocean_model', 'u10_sqr', diag%axesT1, Time, & + 'Wind magnitude at 10m, squared', 'm2 s-2', conversion=US%L_to_m**2*US%s_to_T**2) endif handles%id_psurf = register_diag_field('ocean_model', 'p_surf', diag%axesT1, Time, & @@ -2338,7 +2345,7 @@ subroutine fluxes_accumulate(flux_tmp, fluxes, G, wt2, forces) ! applied based on the time interval stored in flux_tmp. real :: wt1 ! The relative weight of the previous fluxes [nondim] - integer :: i, j, is, ie, js, je, Isq, Ieq, Jsq, Jeq + integer :: i, j, is, ie, js, je, Isq, Ieq, Jsq, Jeq, n integer :: isd, ied, jsd, jed, IsdB, IedB, JsdB, JedB is = G%isc ; ie = G%iec ; js = G%jsc ; je = G%jec Isq = G%IscB ; Ieq = G%IecB ; Jsq = G%JscB ; Jeq = G%JecB @@ -2499,6 +2506,59 @@ subroutine fluxes_accumulate(flux_tmp, fluxes, G, wt2, forces) enddo ; enddo endif + ! Forcings introduced for MARBL + ! NOTE: fluxes%salt_flux, %sw, and %p_surf_full are handled above + if (associated(fluxes%nhx_dep) .and. associated(flux_tmp%nhx_dep)) then + do j=jsd,jed ; do i=isd,ied + fluxes%nhx_dep(i,j) = wt1*fluxes%nhx_dep(i,j) + wt2*flux_tmp%nhx_dep(i,j) + enddo ; enddo + endif + if (associated(fluxes%noy_dep) .and. associated(flux_tmp%noy_dep)) then + do j=jsd,jed ; do i=isd,ied + fluxes%noy_dep(i,j) = wt1*fluxes%noy_dep(i,j) + wt2*flux_tmp%noy_dep(i,j) + enddo ; enddo + endif + if (associated(fluxes%atm_co2) .and. associated(flux_tmp%atm_co2)) then + do j=jsd,jed ; do i=isd,ied + fluxes%atm_co2(i,j) = wt1*fluxes%atm_co2(i,j) + wt2*flux_tmp%atm_co2(i,j) + enddo ; enddo + endif + if (associated(fluxes%atm_alt_co2) .and. associated(flux_tmp%atm_alt_co2)) then + do j=jsd,jed ; do i=isd,ied + fluxes%atm_alt_co2(i,j) = wt1*fluxes%atm_alt_co2(i,j) + wt2*flux_tmp%atm_alt_co2(i,j) + enddo ; enddo + endif + if (associated(fluxes%dust_flux) .and. associated(flux_tmp%dust_flux)) then + do j=jsd,jed ; do i=isd,ied + fluxes%dust_flux(i,j) = wt1*fluxes%dust_flux(i,j) + wt2*flux_tmp%dust_flux(i,j) + enddo ; enddo + endif + if (associated(fluxes%iron_flux) .and. associated(flux_tmp%iron_flux)) then + do j=jsd,jed ; do i=isd,ied + fluxes%iron_flux(i,j) = wt1*fluxes%iron_flux(i,j) + wt2*flux_tmp%iron_flux(i,j) + enddo ; enddo + endif + if (associated(fluxes%fracr_cat) .and. associated(flux_tmp%fracr_cat)) then + do n=1,size(fluxes%fracr_cat,dim=3) ; do j=jsd,jed ; do i=isd,ied + fluxes%fracr_cat(i,j,n) = wt1*fluxes%fracr_cat(i,j,n) + wt2*flux_tmp%fracr_cat(i,j,n) + enddo ; enddo ; enddo + endif + if (associated(fluxes%qsw_cat) .and. associated(flux_tmp%qsw_cat)) then + do n=1,size(fluxes%qsw_cat,dim=3) ; do j=jsd,jed ; do i=isd,ied + fluxes%qsw_cat(i,j,n) = wt1*fluxes%qsw_cat(i,j,n) + wt2*flux_tmp%qsw_cat(i,j,n) + enddo ; enddo ; enddo + endif + if (associated(fluxes%ice_fraction) .and. associated(flux_tmp%ice_fraction)) then + do j=jsd,jed ; do i=isd,ied + fluxes%ice_fraction(i,j) = wt1*fluxes%ice_fraction(i,j) + wt2*flux_tmp%ice_fraction(i,j) + enddo ; enddo + endif + if (associated(fluxes%u10_sqr) .and. associated(flux_tmp%u10_sqr)) then + do j=jsd,jed ; do i=isd,ied + fluxes%u10_sqr(i,j) = wt1*fluxes%u10_sqr(i,j) + wt2*flux_tmp%u10_sqr(i,j) + enddo ; enddo + endif + if (coupler_type_initialized(fluxes%tr_fluxes) .and. & coupler_type_initialized(flux_tmp%tr_fluxes)) & call coupler_type_increment_data(flux_tmp%tr_fluxes, fluxes%tr_fluxes, & diff --git a/src/tracer/MARBL_forcing_mod.F90 b/src/tracer/MARBL_forcing_mod.F90 index 7769175e83..a6f5d08577 100644 --- a/src/tracer/MARBL_forcing_mod.F90 +++ b/src/tracer/MARBL_forcing_mod.F90 @@ -7,7 +7,8 @@ module MARBL_forcing_mod !! (This comment can go in the wiki on the NCAR fork?) use MOM_diag_mediator, only : safe_alloc_ptr, diag_ctrl, register_diag_field, post_data -use MOM_time_manager, only : time_type +use MOM_diag_mediator, only : enable_averages, disable_averaging +use MOM_time_manager, only : time_type, operator(+) use MOM_error_handler, only : MOM_error, WARNING, FATAL use MOM_file_parser, only : get_param, log_param, param_file_type use MOM_grid, only : ocean_grid_type @@ -173,7 +174,8 @@ subroutine convert_driver_fields_to_forcings(atm_fine_dust_flux, atm_coarse_dust seaice_dust_flux, atm_bc_flux, seaice_bc_flux, & nhx_dep, noy_dep, atm_co2_prog, atm_co2_diag, & afracr, swnet_afracr, ifrac_n, & - swpen_ifrac_n, Time, G, US, i0, j0, fluxes, CS) + swpen_ifrac_n, Time, G, US, i0, j0, fluxes, CS, & + Ocean_coupling_time_step) real, dimension(:,:), pointer, intent(in) :: atm_fine_dust_flux !< atmosphere fine dust flux from IOB !! [kg m-2 s-1] @@ -184,13 +186,13 @@ subroutine convert_driver_fields_to_forcings(atm_fine_dust_flux, atm_coarse_dust !! [kg m-2 s-1] real, dimension(:,:), pointer, intent(in) :: seaice_bc_flux !< sea ice black carbon flux from IOB !! [kg m-2 s-1] - real, dimension(:,:), pointer, intent(in) :: afracr !< open ocean fraction [1] real, dimension(:,:), pointer, intent(in) :: nhx_dep !< NHx flux from atmosphere [kg m-2 s-1] real, dimension(:,:), pointer, intent(in) :: noy_dep !< NOy flux from atmosphere [kg m-2 s-1] real, dimension(:,:), pointer, intent(in) :: atm_co2_prog !< Prognostic atmospheric CO2 concentration !! [ppm] real, dimension(:,:), pointer, intent(in) :: atm_co2_diag !< Diagnostic atmospheric CO2 concentration !! [ppm] + real, dimension(:,:), pointer, intent(in) :: afracr !< open ocean fraction [1] real, dimension(:,:), pointer, intent(in) :: swnet_afracr !< shortwave flux * open ocean fraction !! [W m-2] real, dimension(:,:,:), pointer, intent(in) :: ifrac_n !< per-category ice fraction [1] @@ -207,6 +209,8 @@ subroutine convert_driver_fields_to_forcings(atm_fine_dust_flux, atm_coarse_dust type(forcing), intent(inout) :: fluxes !< MARBL-specific forcing fields type(marbl_forcing_CS), pointer, intent(inout) :: CS !< A pointer that is set to point to !! control structure for MARBL forcing + type(time_type), optional, intent(in) :: Ocean_coupling_time_step !< The amount of time over + !! which to advance the ocean. integer :: i, j, is, ie, js, je, m real :: atm_fe_bioavail_frac !< Fraction of iron from the atmosphere available for biological uptake [1] @@ -227,21 +231,25 @@ subroutine convert_driver_fields_to_forcings(atm_fine_dust_flux, atm_coarse_dust ! Post fields from coupler to diagnostics ! TODO: units from diag register are incorrect; we should be converting these in the cap, I think - if (CS%diag_ids%atm_fine_dust > 0) & - call post_data(CS%diag_ids%atm_fine_dust, atm_fine_dust_flux(is-i0:ie-i0,js-j0:je-j0), & - CS%diag, mask=G%mask2dT(is:ie,js:je)) - if (CS%diag_ids%atm_coarse_dust > 0) & - call post_data(CS%diag_ids%atm_coarse_dust, atm_coarse_dust_flux(is-i0:ie-i0,js-j0:je-j0), & - CS%diag, mask=G%mask2dT(is:ie,js:je)) - if (CS%diag_ids%atm_bc > 0) & - call post_data(CS%diag_ids%atm_bc, atm_bc_flux(is-i0:ie-i0,js-j0:je-j0), CS%diag, & - mask=G%mask2dT(is:ie,js:je)) - if (CS%diag_ids%ice_dust > 0) & - call post_data(CS%diag_ids%ice_dust, seaice_dust_flux(is-i0:ie-i0,js-j0:je-j0), CS%diag, & - mask=G%mask2dT(is:ie,js:je)) - if (CS%diag_ids%ice_bc > 0) & - call post_data(CS%diag_ids%ice_bc, seaice_bc_flux(is-i0:ie-i0,js-j0:je-j0), CS%diag, & - mask=G%mask2dT(is:ie,js:je)) + if (present(Ocean_coupling_time_step)) then + call enable_averages(fluxes%dt_buoy_accum, Time + Ocean_coupling_time_step, CS%diag) + if (CS%diag_ids%atm_fine_dust > 0) & + call post_data(CS%diag_ids%atm_fine_dust, atm_fine_dust_flux(is-i0:ie-i0,js-j0:je-j0), & + CS%diag, mask=G%mask2dT(is:ie,js:je)) + if (CS%diag_ids%atm_coarse_dust > 0) & + call post_data(CS%diag_ids%atm_coarse_dust, atm_coarse_dust_flux(is-i0:ie-i0,js-j0:je-j0), & + CS%diag, mask=G%mask2dT(is:ie,js:je)) + if (CS%diag_ids%atm_bc > 0) & + call post_data(CS%diag_ids%atm_bc, atm_bc_flux(is-i0:ie-i0,js-j0:je-j0), CS%diag, & + mask=G%mask2dT(is:ie,js:je)) + if (CS%diag_ids%ice_dust > 0) & + call post_data(CS%diag_ids%ice_dust, seaice_dust_flux(is-i0:ie-i0,js-j0:je-j0), CS%diag, & + mask=G%mask2dT(is:ie,js:je)) + if (CS%diag_ids%ice_bc > 0) & + call post_data(CS%diag_ids%ice_bc, seaice_bc_flux(is-i0:ie-i0,js-j0:je-j0), CS%diag, & + mask=G%mask2dT(is:ie,js:je)) + call disable_averaging(CS%diag) + endif do j=js,je ; do i=is,ie ! Nitrogen Deposition From 3c15cad5ab1fef97f116b2570e2e0d82b46b6d13 Mon Sep 17 00:00:00 2001 From: Michael Levy Date: Fri, 10 Apr 2026 08:04:27 -0600 Subject: [PATCH 2/3] Move dust / iron flux constituents to forcing_type Added five new fields to forcing_type so they can be added to history files from forcing_diagnostics() with the rest of the flux fields. This fixes the issue where these fields were not being accumulated correctly when DT_THERMO was greater than the coupling interval. --- .../nuopc_cap/mom_ocean_model_nuopc.F90 | 2 +- .../nuopc_cap/mom_surface_forcing_nuopc.F90 | 11 +-- src/core/MOM_forcing_type.F90 | 82 ++++++++++++++++++- src/tracer/MARBL_forcing_mod.F90 | 73 +++-------------- 4 files changed, 98 insertions(+), 70 deletions(-) diff --git a/config_src/drivers/nuopc_cap/mom_ocean_model_nuopc.F90 b/config_src/drivers/nuopc_cap/mom_ocean_model_nuopc.F90 index f4207f1d96..cfdfee4f96 100644 --- a/config_src/drivers/nuopc_cap/mom_ocean_model_nuopc.F90 +++ b/config_src/drivers/nuopc_cap/mom_ocean_model_nuopc.F90 @@ -567,7 +567,7 @@ subroutine update_ocean_model(Ice_ocean_boundary, OS, Ocean_sfc, & if (do_thermo) & call convert_IOB_to_fluxes(Ice_ocean_boundary, OS%fluxes, index_bnds, OS%Time, dt_coupling, & OS%grid, OS%US, OS%forcing_CSp, OS%sfc_state, & - OS%restore_salinity, OS%restore_temp, Ocean_coupling_time_step) + OS%restore_salinity, OS%restore_temp) ! enable_averages() is necessary to post forcing fields to diagnostics call enable_averages(dt_coupling, OS%Time + Ocean_coupling_time_step, OS%diag) diff --git a/config_src/drivers/nuopc_cap/mom_surface_forcing_nuopc.F90 b/config_src/drivers/nuopc_cap/mom_surface_forcing_nuopc.F90 index 9af2c7255e..aac1ecbaad 100644 --- a/config_src/drivers/nuopc_cap/mom_surface_forcing_nuopc.F90 +++ b/config_src/drivers/nuopc_cap/mom_surface_forcing_nuopc.F90 @@ -247,7 +247,7 @@ module MOM_surface_forcing_nuopc !! thermodynamic forcing type, including changes of units, sign conventions, !! and putting the fields into arrays with MOM-standard halos. subroutine convert_IOB_to_fluxes(IOB, fluxes, index_bounds, Time, valid_time, G, US, CS, & - sfc_state, restore_salt, restore_temp, Ocean_coupling_time_step) + sfc_state, restore_salt, restore_temp) type(ice_ocean_boundary_type), & target, intent(in) :: IOB !< An ice-ocean boundary type with fluxes to drive !! the ocean in a coupled model @@ -265,10 +265,8 @@ subroutine convert_IOB_to_fluxes(IOB, fluxes, index_bounds, Time, valid_time, G, !! previous call to surface_forcing_init. type(surface), intent(in) :: sfc_state !< A structure containing fields that describe the !! surface state of the ocean. - logical, optional, intent(in) :: restore_salt !< If true, salinity is restored to a target value. - logical, optional, intent(in) :: restore_temp !< If true, temperature is restored to a target value. - type(time_type), optional, intent(in) :: Ocean_coupling_time_step !< The amount of time over - !! which to advance the ocean. + logical, optional, intent(in) :: restore_salt !< If true, salinity is restored to a target value. + logical, optional, intent(in) :: restore_temp !< If true, temperature is restored to a target value. ! local variables real, dimension(SZI_(G),SZJ_(G)) :: & @@ -622,8 +620,7 @@ subroutine convert_IOB_to_fluxes(IOB, fluxes, index_bounds, Time, valid_time, G, IOB%seaice_dust_flux, IOB%atm_bc_flux, IOB%seaice_bc_flux, & IOB%nhx_dep, IOB%noy_dep, IOB%atm_co2_prog, IOB%atm_co2_diag, & IOB%afracr, IOB%swnet_afracr, IOB%ifrac_n, IOB%swpen_ifrac_n, & - Time, G, US, i0, j0, fluxes, CS%marbl_forcing_CSp, & - Ocean_coupling_time_step) + Time, G, US, i0, j0, fluxes, CS%marbl_forcing_CSp) ! wave to ocean coupling if ( associated(IOB%lamult)) then diff --git a/src/core/MOM_forcing_type.F90 b/src/core/MOM_forcing_type.F90 index 60160db3c6..9da9f1b049 100644 --- a/src/core/MOM_forcing_type.F90 +++ b/src/core/MOM_forcing_type.F90 @@ -234,7 +234,12 @@ module MOM_forcing_type atm_co2 => NULL(), & !< Atmospheric CO2 Concentration [ppm] atm_alt_co2 => NULL(), & !< Alternate atmospheric CO2 Concentration [ppm] dust_flux => NULL(), & !< Flux of dust into the ocean [R Z T-1 ~> kgN m-2 s-1] - iron_flux => NULL() !< Flux of dust into the ocean [conc Z T-1 ~> conc m s-1] + iron_flux => NULL(), & !< Flux of dust into the ocean [conc Z T-1 ~> conc m s-1] + atm_fine_dust_flux => NULL(), & !< Fine dust flux from atmosphere [R Z T-1 ~> kg m-2 s-1] + atm_coarse_dust_flux => NULL(), & !< Coarse dust flux from atmosphere [R Z T-1 ~> kg m-2 s-1] + seaice_dust_flux => NULL(), & !< Dust flux from seaice [R Z T-1 ~> kg m-2 s-1] + atm_bc_flux => NULL(), & !< Black carbon flux from atmosphere [R Z T-1 ~> kg m-2 s-1] + seaice_bc_flux => NULL() !< Black carbon flux from seaice [R Z T-1 ~> kg m-2 s-1] real, pointer, dimension(:,:,:) :: & fracr_cat => NULL(), & !< per-category ice fraction [nondim] @@ -421,6 +426,11 @@ module MOM_forcing_type ! tracer surface flux related diagnostics handles integer :: id_ice_fraction = -1 integer :: id_u10_sqr = -1 + integer :: id_atm_fine_dust_flux = -1 + integer :: id_atm_coarse_dust_flux = -1 + integer :: id_atm_bc_flux = -1 + integer :: id_seaice_dust_flux = -1 + integer :: id_seaice_bc_flux = -1 ! iceberg diagnostic handles integer :: id_ustar_berg = -1 @@ -1617,6 +1627,26 @@ subroutine register_forcing_type_diags(Time, diag, US, use_temperature, handles, 'Wind magnitude at 10m, squared', 'm2 s-2', conversion=US%L_to_m**2*US%s_to_T**2) endif + if (present(use_MARBL_tracers)) then + if (use_MARBL_tracers) then + handles%id_atm_fine_dust_flux = register_diag_field('ocean_model', 'ATM_FINE_DUST_FLUX_CPL', & + diag%axesT1, Time, 'ATM_FINE_DUST_FLUX from cpl', 'kg m-2 s', & + conversion=US%RZ_T_to_kg_m2s) + handles%id_atm_coarse_dust_flux = register_diag_field('ocean_model', 'ATM_COARSE_DUST_FLUX_CPL', & + diag%axesT1, Time, 'ATM_COARSE_DUST_FLUX from cpl', 'kg m-2 s', & + conversion=US%RZ_T_to_kg_m2s) + handles%id_atm_bc_flux = register_diag_field('ocean_model', 'ATM_BLACK_CARBON_FLUX_CPL', & + diag%axesT1, Time, 'ATM_BLACK_CARBON_FLUX from cpl', 'kg m-2 s', & + conversion=US%RZ_T_to_kg_m2s) + + handles%id_seaice_dust_flux = register_diag_field('ocean_model', 'SEAICE_DUST_FLUX_CPL', & + diag%axesT1, Time, 'SEAICE_DUST_FLUX from cpl', 'kg m-2 s', & + conversion=US%RZ_T_to_kg_m2s) + handles%id_seaice_bc_flux = register_diag_field('ocean_model', 'SEAICE_BLACK_CARBON_FLUX_CPL', & + diag%axesT1, Time, 'SEAICE_BLACK_CARBON_FLUX from cpl', 'kg m-2 s', & + conversion=US%RZ_T_to_kg_m2s) + end if + end if handles%id_psurf = register_diag_field('ocean_model', 'p_surf', diag%axesT1, Time, & 'Pressure at ice-ocean or atmosphere-ocean interface', & 'Pa', conversion=US%RL2_T2_to_Pa, cmor_field_name='pso', & @@ -2538,6 +2568,31 @@ subroutine fluxes_accumulate(flux_tmp, fluxes, G, wt2, forces) fluxes%iron_flux(i,j) = wt1*fluxes%iron_flux(i,j) + wt2*flux_tmp%iron_flux(i,j) enddo ; enddo endif + if (associated(fluxes%atm_fine_dust_flux) .and. associated(flux_tmp%atm_fine_dust_flux)) then + do j=jsd,jed ; do i=isd,ied + fluxes%atm_fine_dust_flux(i,j) = wt1*fluxes%atm_fine_dust_flux(i,j) + wt2*flux_tmp%atm_fine_dust_flux(i,j) + enddo ; enddo + endif + if (associated(fluxes%atm_coarse_dust_flux) .and. associated(flux_tmp%atm_coarse_dust_flux)) then + do j=jsd,jed ; do i=isd,ied + fluxes%atm_coarse_dust_flux(i,j) = wt1*fluxes%atm_coarse_dust_flux(i,j) + wt2*flux_tmp%atm_coarse_dust_flux(i,j) + enddo ; enddo + endif + if (associated(fluxes%atm_bc_flux) .and. associated(flux_tmp%atm_bc_flux)) then + do j=jsd,jed ; do i=isd,ied + fluxes%atm_bc_flux(i,j) = wt1*fluxes%atm_bc_flux(i,j) + wt2*flux_tmp%atm_bc_flux(i,j) + enddo ; enddo + endif + if (associated(fluxes%seaice_dust_flux) .and. associated(flux_tmp%seaice_dust_flux)) then + do j=jsd,jed ; do i=isd,ied + fluxes%seaice_dust_flux(i,j) = wt1*fluxes%seaice_dust_flux(i,j) + wt2*flux_tmp%seaice_dust_flux(i,j) + enddo ; enddo + endif + if (associated(fluxes%seaice_bc_flux) .and. associated(flux_tmp%seaice_bc_flux)) then + do j=jsd,jed ; do i=isd,ied + fluxes%seaice_bc_flux(i,j) = wt1*fluxes%seaice_bc_flux(i,j) + wt2*flux_tmp%seaice_bc_flux(i,j) + enddo ; enddo + endif if (associated(fluxes%fracr_cat) .and. associated(flux_tmp%fracr_cat)) then do n=1,size(fluxes%fracr_cat,dim=3) ; do j=jsd,jed ; do i=isd,ied fluxes%fracr_cat(i,j,n) = wt1*fluxes%fracr_cat(i,j,n) + wt2*flux_tmp%fracr_cat(i,j,n) @@ -3426,6 +3481,21 @@ subroutine forcing_diagnostics(fluxes_in, sfc_state, G_in, US, time_end, diag, h if ((handles%id_u10_sqr > 0) .and. associated(fluxes%u10_sqr)) & call post_data(handles%id_u10_sqr, fluxes%u10_sqr, diag) + if ((handles%id_atm_fine_dust_flux > 0) .and. associated(fluxes%atm_fine_dust_flux)) & + call post_data(handles%id_atm_fine_dust_flux, fluxes%atm_fine_dust_flux, diag) + + if ((handles%id_atm_coarse_dust_flux > 0) .and. associated(fluxes%atm_coarse_dust_flux)) & + call post_data(handles%id_atm_coarse_dust_flux, fluxes%atm_coarse_dust_flux, diag) + + if ((handles%id_atm_bc_flux > 0) .and. associated(fluxes%atm_bc_flux)) & + call post_data(handles%id_atm_bc_flux, fluxes%atm_bc_flux, diag) + + if ((handles%id_seaice_dust_flux > 0) .and. associated(fluxes%seaice_dust_flux)) & + call post_data(handles%id_seaice_dust_flux, fluxes%seaice_dust_flux, diag) + + if ((handles%id_seaice_bc_flux > 0) .and. associated(fluxes%seaice_bc_flux)) & + call post_data(handles%id_seaice_bc_flux, fluxes%seaice_bc_flux, diag) + ! remaining boundary terms ================================================== if ((handles%id_psurf > 0) .and. associated(fluxes%p_surf)) & @@ -3595,6 +3665,11 @@ subroutine allocate_forcing_by_group(G, fluxes, water, heat, ustar, press, & call myAlloc(fluxes%atm_alt_co2,isd,ied,jsd,jed, marbl) call myAlloc(fluxes%dust_flux,isd,ied,jsd,jed, marbl) call myAlloc(fluxes%iron_flux,isd,ied,jsd,jed, marbl) + call myAlloc(fluxes%atm_fine_dust_flux,isd,ied,jsd,jed, marbl) + call myAlloc(fluxes%atm_coarse_dust_flux,isd,ied,jsd,jed, marbl) + call myAlloc(fluxes%atm_bc_flux,isd,ied,jsd,jed, marbl) + call myAlloc(fluxes%seaice_dust_flux,isd,ied,jsd,jed, marbl) + call myAlloc(fluxes%seaice_bc_flux,isd,ied,jsd,jed, marbl) ! These fields should only be allocated when receiving multiple ice categories if (present(ice_ncat)) then @@ -3901,6 +3976,11 @@ subroutine deallocate_forcing_type(fluxes) if (associated(fluxes%atm_alt_co2)) deallocate(fluxes%atm_alt_co2) if (associated(fluxes%dust_flux)) deallocate(fluxes%dust_flux) if (associated(fluxes%iron_flux)) deallocate(fluxes%iron_flux) + if (associated(fluxes%atm_fine_dust_flux)) deallocate(fluxes%atm_fine_dust_flux) + if (associated(fluxes%atm_coarse_dust_flux)) deallocate(fluxes%atm_coarse_dust_flux) + if (associated(fluxes%atm_bc_flux)) deallocate(fluxes%atm_bc_flux) + if (associated(fluxes%seaice_dust_flux)) deallocate(fluxes%seaice_dust_flux) + if (associated(fluxes%seaice_bc_flux)) deallocate(fluxes%seaice_bc_flux) if (associated(fluxes%fracr_cat)) deallocate(fluxes%fracr_cat) if (associated(fluxes%qsw_cat)) deallocate(fluxes%qsw_cat) diff --git a/src/tracer/MARBL_forcing_mod.F90 b/src/tracer/MARBL_forcing_mod.F90 index a6f5d08577..0ed00b8774 100644 --- a/src/tracer/MARBL_forcing_mod.F90 +++ b/src/tracer/MARBL_forcing_mod.F90 @@ -6,9 +6,8 @@ module MARBL_forcing_mod !! for passing forcing fields to MARBL !! (This comment can go in the wiki on the NCAR fork?) -use MOM_diag_mediator, only : safe_alloc_ptr, diag_ctrl, register_diag_field, post_data -use MOM_diag_mediator, only : enable_averages, disable_averaging -use MOM_time_manager, only : time_type, operator(+) +use MOM_diag_mediator, only : safe_alloc_ptr, diag_ctrl +use MOM_time_manager, only : time_type use MOM_error_handler, only : MOM_error, WARNING, FATAL use MOM_file_parser, only : get_param, log_param, param_file_type use MOM_grid, only : ocean_grid_type @@ -25,16 +24,6 @@ module MARBL_forcing_mod public :: MARBL_forcing_init public :: convert_driver_fields_to_forcings -!> Data type used to store diagnostic index returned from register_diag_field() -!! For the forcing fields that can be written via post_data() -type, private :: marbl_forcing_diag_ids - integer :: atm_fine_dust !< Atmospheric fine dust component of dust_flux - integer :: atm_coarse_dust !< Atmospheric coarse dust component of dust_flux - integer :: atm_bc !< Atmospheric black carbon component of iron_flux - integer :: ice_dust !< Sea-ice dust component of dust_flux - integer :: ice_bc !< Sea-ice black carbon component of iron_flux -end type marbl_forcing_diag_ids - !> Control structure for this module type, public :: marbl_forcing_CS ; private type(diag_ctrl), pointer :: diag => NULL() !< A structure that is used to @@ -51,8 +40,6 @@ module MARBL_forcing_mod real :: atm_alt_co2_const !< alternate atmospheric CO2 for _ALT_CO2 tracers !! (if specifying a constant value) [ppm] - type(marbl_forcing_diag_ids) :: diag_ids !< used for registering and posting some MARBL forcing fields as diagnostics - logical :: use_MARBL_tracers !< most functions can return immediately !! MARBL tracers are turned off integer :: atm_co2_iopt !< Integer version of atm_co2_opt, which determines source of atm_co2 @@ -147,26 +134,6 @@ subroutine MARBL_forcing_init(G, US, param_file, diag, day, inputdir, use_MARBL_ default=284.317, units="ppm") endif - ! Register diagnostic fields for outputing forcing values - ! These fields are posted from convert_driver_fields_to_forcings(), and they are received - ! in physical units so no conversion is necessary here. - CS%diag_ids%atm_fine_dust = register_diag_field("ocean_model", "ATM_FINE_DUST_FLUX_CPL", & - CS%diag%axesT1, & ! T=> tracer grid? 1 => no vertical grid - day, "ATM_FINE_DUST_FLUX from cpl", "kg/m^2/s") - CS%diag_ids%atm_coarse_dust = register_diag_field("ocean_model", "ATM_COARSE_DUST_FLUX_CPL", & - CS%diag%axesT1, & ! T=> tracer grid? 1 => no vertical grid - day, "ATM_COARSE_DUST_FLUX from cpl", "kg/m^2/s") - CS%diag_ids%atm_bc = register_diag_field("ocean_model", "ATM_BLACK_CARBON_FLUX_CPL", & - CS%diag%axesT1, & ! T=> tracer grid? 1 => no vertical grid - day, "ATM_BLACK_CARBON_FLUX from cpl", "kg/m^2/s") - - CS%diag_ids%ice_dust = register_diag_field("ocean_model", "SEAICE_DUST_FLUX_CPL", & - CS%diag%axesT1, & ! T=> tracer grid? 1 => no vertical grid - day, "SEAICE_DUST_FLUX from cpl", "kg/m^2/s") - CS%diag_ids%ice_bc = register_diag_field("ocean_model", "SEAICE_BLACK_CARBON_FLUX_CPL", & - CS%diag%axesT1, & ! T=> tracer grid? 1 => no vertical grid - day, "SEAICE_BLACK_CARBON_FLUX from cpl", "kg/m^2/s") - end subroutine MARBL_forcing_init ! Note: ice fraction and u10_sqr are handled in mom_surface_forcing because of CFCs @@ -174,8 +141,7 @@ subroutine convert_driver_fields_to_forcings(atm_fine_dust_flux, atm_coarse_dust seaice_dust_flux, atm_bc_flux, seaice_bc_flux, & nhx_dep, noy_dep, atm_co2_prog, atm_co2_diag, & afracr, swnet_afracr, ifrac_n, & - swpen_ifrac_n, Time, G, US, i0, j0, fluxes, CS, & - Ocean_coupling_time_step) + swpen_ifrac_n, Time, G, US, i0, j0, fluxes, CS) real, dimension(:,:), pointer, intent(in) :: atm_fine_dust_flux !< atmosphere fine dust flux from IOB !! [kg m-2 s-1] @@ -209,8 +175,6 @@ subroutine convert_driver_fields_to_forcings(atm_fine_dust_flux, atm_coarse_dust type(forcing), intent(inout) :: fluxes !< MARBL-specific forcing fields type(marbl_forcing_CS), pointer, intent(inout) :: CS !< A pointer that is set to point to !! control structure for MARBL forcing - type(time_type), optional, intent(in) :: Ocean_coupling_time_step !< The amount of time over - !! which to advance the ocean. integer :: i, j, is, ie, js, je, m real :: atm_fe_bioavail_frac !< Fraction of iron from the atmosphere available for biological uptake [1] @@ -229,29 +193,16 @@ subroutine convert_driver_fields_to_forcings(atm_fine_dust_flux, atm_coarse_dust ndep_conversion = (1.e6/14.) * (US%m_to_Z * US%T_to_s) iron_flux_conversion = (1.e6 / molw_Fe) * (US%m_to_Z * US%T_to_s) - ! Post fields from coupler to diagnostics - ! TODO: units from diag register are incorrect; we should be converting these in the cap, I think - if (present(Ocean_coupling_time_step)) then - call enable_averages(fluxes%dt_buoy_accum, Time + Ocean_coupling_time_step, CS%diag) - if (CS%diag_ids%atm_fine_dust > 0) & - call post_data(CS%diag_ids%atm_fine_dust, atm_fine_dust_flux(is-i0:ie-i0,js-j0:je-j0), & - CS%diag, mask=G%mask2dT(is:ie,js:je)) - if (CS%diag_ids%atm_coarse_dust > 0) & - call post_data(CS%diag_ids%atm_coarse_dust, atm_coarse_dust_flux(is-i0:ie-i0,js-j0:je-j0), & - CS%diag, mask=G%mask2dT(is:ie,js:je)) - if (CS%diag_ids%atm_bc > 0) & - call post_data(CS%diag_ids%atm_bc, atm_bc_flux(is-i0:ie-i0,js-j0:je-j0), CS%diag, & - mask=G%mask2dT(is:ie,js:je)) - if (CS%diag_ids%ice_dust > 0) & - call post_data(CS%diag_ids%ice_dust, seaice_dust_flux(is-i0:ie-i0,js-j0:je-j0), CS%diag, & - mask=G%mask2dT(is:ie,js:je)) - if (CS%diag_ids%ice_bc > 0) & - call post_data(CS%diag_ids%ice_bc, seaice_bc_flux(is-i0:ie-i0,js-j0:je-j0), CS%diag, & - mask=G%mask2dT(is:ie,js:je)) - call disable_averaging(CS%diag) - endif - do j=js,je ; do i=is,ie + ! Components of dust flux + fluxes%atm_fine_dust_flux(i,j) = (G%mask2dT(i,j) * US%kg_m2s_to_RZ_T) * atm_fine_dust_flux(i-i0,j-j0) + fluxes%atm_coarse_dust_flux(i,j) = (G%mask2dT(i,j) * US%kg_m2s_to_RZ_T) * atm_coarse_dust_flux(i-i0,j-j0) + fluxes%seaice_dust_flux(i,j) = (G%mask2dT(i,j) * US%kg_m2s_to_RZ_T) * seaice_dust_flux(i-i0,j-j0) + + ! Components of black carbon flux + fluxes%atm_bc_flux(i,j) = (G%mask2dT(i,j) * US%kg_m2s_to_RZ_T) * atm_bc_flux(i-i0,j-j0) + fluxes%seaice_bc_flux(i,j) = (G%mask2dT(i,j) * US%kg_m2s_to_RZ_T) * seaice_bc_flux(i-i0,j-j0) + ! Nitrogen Deposition fluxes%nhx_dep(i,j) = (G%mask2dT(i,j) * ndep_conversion) * nhx_dep(i-i0,j-j0) fluxes%noy_dep(i,j) = (G%mask2dT(i,j) * ndep_conversion) * noy_dep(i-i0,j-j0) From da74cad0b1363068154631ba20c9ad3c29b3795f Mon Sep 17 00:00:00 2001 From: Michael Levy Date: Mon, 13 Apr 2026 09:40:37 -0600 Subject: [PATCH 3/3] Remove enable_averages() in update_ocean_model() I had added enable_averages() when I put some post_data() calls in a routine called from convert_IOB_to_fluxes(), but I moved those calls to forcing_diagnostics() so we are back to not calling post_data() from this part of the code. --- config_src/drivers/nuopc_cap/mom_ocean_model_nuopc.F90 | 3 --- 1 file changed, 3 deletions(-) diff --git a/config_src/drivers/nuopc_cap/mom_ocean_model_nuopc.F90 b/config_src/drivers/nuopc_cap/mom_ocean_model_nuopc.F90 index cfdfee4f96..342abae563 100644 --- a/config_src/drivers/nuopc_cap/mom_ocean_model_nuopc.F90 +++ b/config_src/drivers/nuopc_cap/mom_ocean_model_nuopc.F90 @@ -569,9 +569,6 @@ subroutine update_ocean_model(Ice_ocean_boundary, OS, Ocean_sfc, & OS%grid, OS%US, OS%forcing_CSp, OS%sfc_state, & OS%restore_salinity, OS%restore_temp) - ! enable_averages() is necessary to post forcing fields to diagnostics - call enable_averages(dt_coupling, OS%Time + Ocean_coupling_time_step, OS%diag) - ! Add ice shelf fluxes if (OS%use_ice_shelf) then if (do_thermo) &