diff --git a/ext/ClimaCouplerCMIPExt/clima_seaice.jl b/ext/ClimaCouplerCMIPExt/clima_seaice.jl index a311632645..f6c2c621d6 100644 --- a/ext/ClimaCouplerCMIPExt/clima_seaice.jl +++ b/ext/ClimaCouplerCMIPExt/clima_seaice.jl @@ -429,6 +429,21 @@ function FluxCalculator.update_turbulent_fluxes!(sim::ClimaSeaIceSimulation, fie return nothing end +function FluxCalculator.reset_fluxes!(sim::ClimaSeaIceSimulation) + si_flux_heat = sim.ice.model.external_heat_fluxes.top + if si_flux_heat isa OC.Field + OC.interior(si_flux_heat, :, :, 1) .= 0 + end + + if !isnothing(sim.ice.model.dynamics) + si_flux_u = sim.ice.model.dynamics.external_momentum_stresses.top.u + si_flux_v = sim.ice.model.dynamics.external_momentum_stresses.top.v + si_flux_u isa OC.Field && (OC.interior(si_flux_u, :, :, 1) .= 0) + si_flux_v isa OC.Field && (OC.interior(si_flux_v, :, :, 1) .= 0) + end + return nothing +end + function Interfacer.update_field!(sim::ClimaSeaIceSimulation, ::Val{:area_fraction}, field) sim.area_fraction .= field return nothing diff --git a/ext/ClimaCouplerCMIPExt/oceananigans.jl b/ext/ClimaCouplerCMIPExt/oceananigans.jl index 76903510ef..1ae8a3fb6d 100644 --- a/ext/ClimaCouplerCMIPExt/oceananigans.jl +++ b/ext/ClimaCouplerCMIPExt/oceananigans.jl @@ -576,6 +576,14 @@ function Interfacer.update_field!(sim::OceananigansSimulation, ::Val{:area_fract return nothing end +function FluxCalculator.reset_fluxes!(sim::OceananigansSimulation) + oc_flux_T = surface_flux(sim.ocean.model.tracers.T) + OC.interior(oc_flux_T, :, :, 1) .= 0 + oc_flux_S = surface_flux(sim.ocean.model.tracers.S) + OC.interior(oc_flux_S, :, :, 1) .= 0 + return nothing +end + """ FieldExchanger.update_sim!(sim::OceananigansSimulation, csf) @@ -585,8 +593,9 @@ by the coupler. Update the portion of the surface_fluxes for T and S that is due to radiation and precipitation. The rest will be updated in `update_turbulent_fluxes!`. -This function sets the surface fluxes directly, overwriting any previous values. -Additional contributions will be made in `update_turbulent_fluxes!` and `ocean_seaice_fluxes!`. +Flux tendencies are expected to be reset at the start of the coupler timestep via +[`FluxCalculator.reset_fluxes!`](@ref). Additional contributions are made in `update_turbulent_fluxes!` +and `ocean_seaice_fluxes!`. A note on sign conventions: ClimaAtmos and Oceananigans both use the convention that a positive flux is an upward flux. @@ -600,12 +609,8 @@ function FieldExchanger.update_sim!(sim::OceananigansSimulation, csf) grid = sim.ocean.model.grid Nz = grid.Nz ice_concentration = OC.interior(sim.ice_concentration, :, :, 1) - - # Reset fluxes to 0 at the start of the step oc_flux_T = surface_flux(sim.ocean.model.tracers.T) - OC.interior(oc_flux_T, :, :, 1) .= 0 oc_flux_S = surface_flux(sim.ocean.model.tracers.S) - OC.interior(oc_flux_S, :, :, 1) .= 0 # for masking out the poles polar_mask = sim.remapping.polar_mask diff --git a/ext/ClimaCouplerClimaAtmosExt.jl b/ext/ClimaCouplerClimaAtmosExt.jl index f120fceca4..54d9b4546a 100644 --- a/ext/ClimaCouplerClimaAtmosExt.jl +++ b/ext/ClimaCouplerClimaAtmosExt.jl @@ -539,6 +539,26 @@ function FluxCalculator.update_turbulent_fluxes!(sim::ClimaAtmosSimulation, fiel return nothing end +function FluxCalculator.reset_fluxes!(sim::ClimaAtmosSimulation) + integrator = sim.integrator + sfc = integrator.p.precomputed.sfc_conditions + + fill!(sfc.ρ_flux_h_tot, zero(eltype(sfc.ρ_flux_h_tot))) + fill!(sfc.ρ_flux_uₕ, zero(eltype(sfc.ρ_flux_uₕ))) + + if hasmoisture(integrator) + fill!(sfc.ρ_flux_q_tot, zero(eltype(sfc.ρ_flux_q_tot))) + fill!(integrator.p.precomputed.surface_rain_flux, zero(eltype(integrator.p.precomputed.surface_rain_flux))) + fill!(integrator.p.precomputed.surface_snow_flux, zero(eltype(integrator.p.precomputed.surface_snow_flux))) + end + + fill!(sfc.obukhov_length, zero(eltype(sfc.obukhov_length))) + fill!(sfc.ustar, zero(eltype(sfc.ustar))) + fill!(sfc.buoyancy_flux, zero(eltype(sfc.buoyancy_flux))) + + return nothing +end + """ Extend Interfacer.add_coupler_fields! to add the fields required for ClimaAtmosSimulation. diff --git a/src/FieldExchanger.jl b/src/FieldExchanger.jl index 3d055b0370..9d189f0c11 100644 --- a/src/FieldExchanger.jl +++ b/src/FieldExchanger.jl @@ -405,6 +405,10 @@ function exchange!(cs::Interfacer.CoupledSimulation) import_atmos_fields!(cs.fields, cs.model_sims) import_combined_surface_fields!(cs.fields, cs.model_sims) + # NOTE: Component-model flux accumulators are expected to have been zeroed by + # `FluxCalculator.reset_fluxes!(cs)` in the top-level coupler step. We do not + # re-zero here so the ordering remains explicit in `SimCoordinator.step!`. + # Update the component model simulations with the coupler fields update_model_sims!(cs.model_sims, cs.fields) return nothing diff --git a/src/FluxCalculator.jl b/src/FluxCalculator.jl index 8a275aad80..fc3960c76d 100644 --- a/src/FluxCalculator.jl +++ b/src/FluxCalculator.jl @@ -17,10 +17,37 @@ import ..Interfacer, ..Utilities export turbulent_fluxes!, get_surface_params, + reset_fluxes!, update_turbulent_fluxes!, compute_surface_fluxes!, ocean_seaice_fluxes! +""" + reset_fluxes!(cs::Interfacer.CoupledSimulation) + reset_fluxes!(sim::Interfacer.AbstractComponentSimulation) + +Reset any internally accumulated surface flux tendencies on each component model in `cs` +(or on a single `sim`) at the start of a coupler timestep, before flux contributions are +added in `FieldExchanger.exchange!`/`FieldExchanger.update_sim!`, +[`FluxCalculator.update_turbulent_fluxes!`](@ref), and/or +[`FluxCalculator.ocean_seaice_fluxes!`](@ref). + +The `CoupledSimulation` method is intended to be called explicitly from the top-level +coupler loop (`SimCoordinator.step!`) between `update_surface_fractions!` and `exchange!`, +so the reset ordering is visible at the call site rather than hidden inside the exchange. + +By default, the per-`sim` method is a no-op. Component models that accumulate fluxes across +multiple coupler updates should extend that method. +""" +function reset_fluxes!(cs::Interfacer.CoupledSimulation) + for sim in cs.model_sims + reset_fluxes!(sim) + end + return nothing +end + +reset_fluxes!(sim::Interfacer.AbstractComponentSimulation) = nothing + function turbulent_fluxes!(cs::Interfacer.CoupledSimulation) return turbulent_fluxes!(cs.fields, cs.model_sims, cs.thermo_params) end diff --git a/src/SimCoordinator.jl b/src/SimCoordinator.jl index b97e84e48a..4877bf440b 100644 --- a/src/SimCoordinator.jl +++ b/src/SimCoordinator.jl @@ -114,6 +114,11 @@ function step!(cs::Interfacer.CoupledSimulation) # Update the surface fractions for surface models FieldExchanger.update_surface_fractions!(cs) + # Zero any internally-accumulated surface flux tendencies on each component model + # before this coupler step starts adding new contributions in `exchange!`, + # `turbulent_fluxes!`, and `ocean_seaice_fluxes!`. + FluxCalculator.reset_fluxes!(cs) + # Exchange all non-turbulent flux fields between models, including radiative and precipitation fluxes FieldExchanger.exchange!(cs)