FluxCalculator
This module contains the infrastructure to compute turbulent fluxes.
How are fluxes computed?
The key function that computes surface fluxes is FluxCalculator.turbulent_fluxes!. This function computes turbulent fluxes and ancillary quantities, such as the Obukhov length, using SurfaceFluxes.jl. Generally, this function is called at the end of each coupling step.
All the quantities computed in turbulent_fluxes! are calculated separately for each surface model using the FluxCalculator.compute_surface_fluxes! function. This function can be extended by component models if they need specific type of flux calculation, and a default is provided for models that can use the standard flux calculation.
The default method of FluxCalculator.compute_surface_fluxes!, in turn, calls FluxCalculator.get_surface_fluxes. This function uses a thermal state obtained by using the model surface temperature, extrapolates atmospheric density adiabatically to the surface, and with the surface humidity (if available, if not, assuming a saturation specific humidity for liquid phase). compute_surface_fluxes! also updates the component internal fluxes fields via FluxCalculator.update_turbulent_fluxes!, and adds the area-weighted contribution from this component model to the CoupledSimulation fluxes fields.
Any extension of FluxCalculator.compute_surface_fluxes! for a particular surface model is also expected to update both the component models' internal fluxes and the CoupledSimulation object's fluxes fields.
FluxCalculator.compute_surface_fluxes! sets:
- the flux of momenta,
F_turb_ρτxz,F_turb_ρτyz; - the flux of energy due to latent heat,
F_lh; - the flux of energy due to sensible heat,
F_sh; - the flux of moisture,
F_turb_moisture;
FluxCalculator.compute_surface_fluxes! always returns the area weighted sum, even if this is not necessarily the most meaningful operation for a given quantity (e.g., for the Obukhov length). This can be improved in the future, if you know how, please open an issue.
Note also that FluxCalculator.turbulent_fluxes! only computes turbulent fluxes, not radiative fluxes. Currently, these are computed within the atmospheric model.
Turbulent flux accumulation for slow surfaces
When a surface model's own timestep is larger than the coupling timestep (e.g. dt_ocean > Δt_cpl), pushing a fresh flux to it every coupling step is wasteful: the surface only consumes its boundary fluxes when it steps, and the intermediate writes are typically overwritten by the next coupling step exchange. For correctness and conservation, it is also better to feed the surface a time-average of the per-coupling-step fluxes rather than a single instantaneous snapshot.
To support this, the coupler allocates a FluxCalculator.FluxAccumulator for each slow explicit surface (see Interfacer.sim_dt, Interfacer.will_step), and:
- Each coupling step,
FluxCalculator.turbulent_fluxes!callsFluxCalculator.accumulate!on the accumulator instead of writing the flux directly to the surface viaupdate_turbulent_fluxes!. The area-weighted combined fields in the CoupledSimulationfieldsNamedTuple, which get sent to the atmosphere, are still updated every call. FluxCalculator.turbulent_fluxes!also callsFluxCalculator.push_ready_accumulators!, which checks if the surface is about to step and if so, divides the accumulator byn_steps, writes the time-averaged flux to the surface boundary conditions, and zeros the accumulator.
Accumulators are not allocated for fast surfaces (sim_dt ≤ Δt_cpl), AbstractSurfaceStubs, or AbstractImplicitFluxSimulations (which compute their own fluxes inside step!).
Coupler flux accumulation for slow surfaces
The same argument applies to the fluxes a surface reads in FieldExchanger.update_sim! — radiation and precipitation, and for the bucket land model the near-surface atmospheric state. A slow surface takes one step covering many coupling steps, so an instantaneous sample of SW_d scaled up to the surface's timestep does not carry the energy the atmosphere actually lost over that interval.
The same accumulator carries them, under the same step count and the same push:
- Each coupling step,
FieldExchanger.update_model_sims!callsupdate_sim!as usual and additionally callsFluxCalculator.accumulate_coupler_fields!for the fields the surface declared inFieldExchanger.accumulated_coupler_fields. FluxCalculator.push_ready_accumulators!then pushes the averaged coupler fields throughupdate_sim!before the turbulent push, overwriting the instantaneous values written that step. Both sums are divided by the samen_steps, which onlyFluxCalculator.accumulate!increments.
update_sim! keeps running every coupling step because a surface that computes its own turbulent fluxes reads the atmospheric state it writes.
Ice-ocean fluxes
FluxCalculator.ocean_seaice_fluxes! runs on the ocean/sea-ice cadence rather than the coupling cadence. compute_sea_ice_ocean_fluxes! takes a single Δt and its result is consumed by one ocean step and one sea-ice step; its frazil half also clamps supercooled ocean temperature in place, so recomputing it every coupling step would leave the ice reading a value computed against an ocean that had already been clamped. The CoupledSimulation method therefore assembles the interface fluxes on the coupling step that precedes the shared step, and dt_ocean == dt_seaice is required when coupling Oceananigans to ClimaSeaIce so that shared cadence exists.
FluxCalculator API
ClimaCoupler.FluxCalculator.turbulent_fluxes! — Function
turbulent_fluxes!(cs::CoupledSimulation)
turbulent_fluxes!(fields, model_sims, thermo_params, flux_accumulators = (;))Compute turbulent fluxes and associated quantities. Store the results in fields as area-weighted sums.
This function uses SurfaceFluxes.jl under the hood.
For any surface present in flux_accumulators, the per-surface flux is added to that surface's FluxAccumulator instead of being pushed directly to the surface via update_turbulent_fluxes!. The area-weighted combined cs.fields.F_* fields (which the atmosphere reads) are updated every call regardless.
Args:
csf: [Field of NamedTuple] containing coupler fields.model_sims: [NamedTuple] containingAbstractComponentSimulations.thermo_params: [TD.Parameters.ThermodynamicsParameters] the thermodynamic parameters.flux_accumulators: [NamedTuple] ofFluxAccumulators keyed by surface simulation name (the same keys asmodel_sims). Entries are only present for slow explicit surfaces; the default empty NamedTuple disables accumulation entirely.
(NB: Radiation surface fluxes are calculated by the atmosphere.)
ClimaCoupler.FluxCalculator.ocean_seaice_fluxes! — Function
ocean_seaice_fluxes!(ocean_sim::OceananigansSimulation, ice_sim::ClimaSeaIceSimulation)Compute the fluxes between the ocean and sea ice, storing them in the ocean_ice_fluxes fields of the ocean and sea ice simulations.
This function assumes both simulations share the same grid, so no remapping is done.
Both simulations have had their atmospheric fluxes updated already in this timestep (see update_sim! and update_turbulent_fluxes!), so we add the contributions from the ocean-sea ice interactions to the existing fluxes, rather than overwriting all fluxes.
ocean_seaice_fluxes!(cs::CoupledSimulation)
ocean_seaice_fluxes!(ocean_sim, ice_sim)Compute the fluxes between the ocean and sea ice simulations. This function does nothing by default - it should be extended for any ocean and sea ice models that support flux calculations.
The CoupledSimulation method runs on the ocean/sea-ice cadence, not the coupling one.
ClimaCoupler.FluxCalculator.compute_surface_fluxes! — Function
compute_surface_fluxes!(csf, sim::BucketSimulation, atmos_sim, thermo_params, accumulator = nothing)This function computes surface fluxes between the bucket simulation and the atmosphere.
The bucket computes its turbulent fluxes into its own cache fields rather than into the coupler fields, so we cannot use the generic FluxCalculator.update_flux_fields!. Instead:
- Compute the turbulent fluxes at each coupler step via
turbulent_fluxes_at_a_pointdirectly intobucket_dest. For fast buckets (dt_land <= dt_cpl),bucket_dest = p.bucket.turbulent_fluxes. For slow buckets,bucket_dest = sim.flux_bufferso we don't overwrite the time-averaged fluxes already inp.bucket.turbulent_fluxes. - Remap each component to the coupler boundary-space
sim.remapped_fluxes, including thevapor_flux(m/s) →F_turb_moisture(kg/m²/s) unit conversion. For slow buckets, also add the fluxes to the accumulator, which will be sent to the bucket just before it steps.
Arguments
csf: [CC.Fields.Field] containing a NamedTuple of turbulent flux fields:F_turb_ρτxz,F_turb_ρτyz,F_lh,F_sh,F_turb_moisture.sim: [BucketSimulation] the bucket simulation to compute fluxes for.atmos_sim: [Interfacer.AbstractAtmosSimulation] the atmosphere simulation to compute fluxes with.thermo_params: [ClimaParams.ThermodynamicParameters] the thermodynamic parameters for the simulation.accumulator: optionalFluxAccumulatorfor slow buckets;nothing(default) for fast buckets.
compute_surface_fluxes!(csf, sim::ClimaLandSimulation, atmos_sim, thermo_params, accumulator = nothing)This function computes surface fluxes between the integrated land model simulation and the atmosphere.
Update the input coupler surface fields csf in-place with the computed fluxes for this model. These are then summed using area-weighting across all surface models to get the total fluxes. Fluxes where the area fraction is zero are set to zero.
The integrated land model requires fluxes to be computed implicitly, so they are computed in the land model's internal step! function, where they can be solved for at the same time as canopy temperature. As a result, this function does not actually compute the fluxes, and some inputs are unused. However, it does access them from the land cache, combine them to get the total fluxes for the integrated land model, and update the coupler fields in-place.
Because the integrated land model is composed of multiple sub-components, the fluxes are computed for each sub-component and then combined here to get the total for this model. The land model cache contains the computed fluxes for each sub-component.
Arguments
csf: [CC.Fields.Field] containing a NamedTuple of turbulent flux fields:F_turb_ρτxz,F_turb_ρτyz,F_lh,F_sh,F_turb_moisture.sim: [ClimaLandSimulation] the integrated land simulation to compute fluxes for.atmos_sim: [Interfacer.AbstractAtmosSimulation] the atmosphere simulation to compute fluxes with.thermo_params: [ClimaParams.ThermodynamicParameters] the thermodynamic parameters for the simulation.
FluxCalculator.compute_surface_fluxes!(csf, sim::OceananigansSimulation,
atmos_sim, thermo_params,
accumulator = nothing)Compute atmosphere-ocean turbulent fluxes on the exchange (intersection) grid: one SurfaceFluxes evaluation per polygon, with the atmospheric state gathered from the SE nodes and the SST read directly from the owning ocean cell. The per-polygon fluxes are aggregated to the boundary space (see scatter_poly_fluxes_to_boundary!) and handed to FluxCalculator.update_flux_fields!, which applies the (exchange-grid derived) area-fraction weighting for the coupler sums and triggers the ocean-side push or the accumulator.
Falls back to the generic boundary-space computation when the exchange grid is not active.
FluxCalculator.compute_surface_fluxes!(csf, sim::ClimaSeaIceSimulation, atmos_sim, thermo_params)Compute surface fluxes for ClimaSeaIceSimulation, iteratively diagnosing Tsfc via the per-column `updateTsfccbclosure (fromClimaCouplerCMIPExt.updateTsfc`) to satisfy the skin-temperature flux balance.
The diagnosed Tsfc is written back to ClimaSeaIce's `topsurface_temperature(used byPrescribedTemperature`) so the ice thermodynamics stays consistent.
compute_surface_fluxes!(csf, sim, atmos_sim, thermo_params)This function computes surface fluxes between the input component model simulation and the atmosphere.
Update the input coupler surface fields csf in-place with the computed fluxes for this model. These are then summed using area-weighting across all surface models to get the total fluxes.
Since the fluxes are computed between the input model and the atmosphere, this function does nothing if called on an atmosphere model simulation.
The function for AbstractImplicitFluxSimulation is a placeholder that does nothing. Currently, the only AbstractImplicitFluxSimulation is ClimaLandSimulation, for which computesurfacefluxes! is defined in the component model. We can extend this function for other AbstractImplicitFluxSimulation in the future.
Arguments
csf: [CC.Fields.Field] containing a NamedTuple of turbulent flux fields:F_turb_ρτxz,F_turb_ρτyz,F_lh,F_sh,F_turb_moisture.sim: [Interfacer.AbstractComponentSimulation] the surface simulation to compute fluxes for.atmos_sim: [Interfacer.AbstractAtmosSimulation] the atmosphere simulation to compute fluxes with.thermo_params: [TD.Parameters.ThermodynamicsParameters] the thermodynamic parameters.
The roughness model is obtained from the simulation via get_field(sim, Val(:roughness_model)). Ocean simulations return :coare3, while land and ice simulations return :constant (the default).
ClimaCoupler.FluxCalculator.get_surface_fluxes — Function
get_surface_fluxes(surface_fluxes_params, u_int, T_int, ..., config,
update_T_sfc_cb, update_q_vap_sfc;
roughness_inputs, scheme, solver_opts, flux_specs)Uses SurfaceFluxes.jl to calculate turbulent surface fluxes. Fluxes are computed over the entire surface, even where the relevant surface model is not present.
update_T_sfc_cb and update_q_vap_sfc are positional so they participate in broadcast (they may vary per element). The remaining extended-API arguments (roughness_inputs, scheme, solver_opts, flux_specs) are keyword arguments.
ClimaCoupler.FluxCalculator.update_turbulent_fluxes! — Function
FluxCalculator.update_turbulent_fluxes!(sim::BucketSimulation, fields)Push the time-averaged coupler-boundary-space turbulent fluxes from a slow bucket's FluxAccumulator back into the bucket's BC fields in p.bucket.turbulent_fluxes. Called by push_and_reset! immediately before each bucket step. (Fast buckets skip this entirely: compute_surface_fluxes! writes directly to p.bucket.turbulent_fluxes.)
Remaps each component from boundary space to bucket surface space, and applies the Fturbmoisture (kg/m²/s) → vapor_flux (m/s) unit conversion.
FluxCalculator.update_turbulent_fluxes!(sim::OceananigansSimulation, fields)Update the turbulent fluxes in the simulation using the values computed at this time step. These include latent heat flux, sensible heat flux, momentum fluxes, and moisture flux.
Rather than setting the surface fluxes and overwriting previous values, this function adds only the contributions from the turbulent fluxes. update_sim! sets the surface fluxes due to radiation and precipitation. Additional contributions may be made in ocean_seaice_fluxes!. An exception is the momentum fluxes, which are set directly here since they are not updated in update_sim!.
A note on sign conventions: SurfaceFluxes and Oceananigans both use the convention that a positive flux is an upward flux. No sign change is needed during the exchange, except for moisture/salinity fluxes: SurfaceFluxes provides moisture moving from atmosphere to ocean as a negative flux at the surface, and Oceananigans represents moisture moving from atmosphere to ocean as a positive salinity flux, so a sign change is needed when we convert from moisture to salinity flux.
FluxCalculator.update_turbulent_fluxes!(sim::ClimaSeaIceSimulation, fields)Update the turbulent fluxes in the simulation using the values stored in the coupler fields. These include latent heat flux, sensible heat flux, momentum fluxes, and moisture flux.
The input fields are per unit surface area of this model.
Note that currently the moisture flux has no effect on the sea ice model, which has constant salinity.
A note on sign conventions: SurfaceFluxes and ClimaSeaIce both use the convention that a positive flux is an upward flux. No sign change is needed during the exchange, except for moisture/salinity fluxes: SurfaceFluxes provides moisture moving from atmosphere to ocean as a negative flux at the surface, and ClimaSeaIce represents moisture moving from atmosphere to ocean as a positive salinity flux, so a sign change is needed when we convert from moisture to salinity flux.
update_turbulent_fluxes!(sim::Interfacer.AbstractComponentSimulation, fields::NamedTuple)Updates the fluxes in the simulation sim with the fluxes in fields.
For surface models, this should be the fluxes computed between the surface model and the atmosphere. For atmosphere models, this should be the area-weighted sum of fluxes across all surface models.
ClimaCoupler.FluxCalculator.update_flux_fields! — Function
update_flux_fields!(csf, sim::Interfacer.AbstractSurfaceSimulation, fluxes, accumulator = nothing)Update the surface simulation sim with the values stored in fluxes, without any area-weighting. Then, update the coupler fields csf with the area-weighted fluxes.
If accumulator is a FluxAccumulator (i.e. sim is a slow surface), the per-surface fluxes are added to the accumulator instead of being pushed directly to the surface's boundary conditions. The area-weighted contribution to csf.F_* (which the atmosphere reads each coupling step) is updated unconditionally.
Arguments
csf: [CC.Fields.Field] containing a NamedTuple of turbulent flux fields:F_turb_ρτxz,F_turb_ρτyz,F_lh,F_sh,F_turb_moisture.sim: [Interfacer.AbstractComponentSimulation] the surface simulation to update.fluxes: [NamedTuple] containing the fluxes to update the surface simulation with.accumulator: optionalFluxAccumulatorfor slow surfaces;nothing(default) updates the surface immediately.
ClimaCoupler.FluxCalculator.get_roughness_params — Function
get_roughness_params(csf, sim)Return the roughness parameters for the simulation, based on the roughness model.
get_roughness_params(csf, sim, ::Val{:coare3})Return COARE3 roughness parameters from the simulation.
get_roughness_params(csf, sim, ::Val{:constant})Load momentum and buoyancy roughness from the simulation into csf scratch fields and return element-wise ConstantRoughnessParams.
get_roughness_params(csf, sim, roughness_model)Fallback for unknown roughness model; errors.
ClimaCoupler.FluxCalculator.FluxAccumulator — Type
FluxAccumulator{F, D}Time-accumulator for the fluxes between a surface model and the atmosphere, used for slow surfaces (having dt > Δt_cpl) whose fluxes are computed explicitly. The F_* fields hold the running sum of per-surface (not area-weighted) turbulent fluxes computed at each coupling step, and coupler_fields holds the running sum of the coupler fields the surface declares in FieldExchanger.accumulated_coupler_fields (radiative and precipitation fluxes, and for some surfaces the near-surface atmospheric state).
n_steps counts the coupling steps accumulated since the last reset. It is incremented by accumulate! only, not by accumulate_coupler_fields!, and is the divisor for both sums.
Used by turbulent_fluxes! to push the time-averaged flux to a slow surface just before it steps. Allocated only for slow explicit surfaces; fast surfaces and implicit-flux surfaces do not have an accumulator.
ClimaCoupler.FluxCalculator.accumulate! — Function
accumulate!(acc::FluxAccumulator, fields)Add the per-surface turbulent fluxes in the NamedTuple fields (F_lh, F_sh, F_turb_moisture, F_turb_ρτxz, F_turb_ρτyz) into the accumulator and increment n_steps. Called once per coupling step from update_flux_fields! for each slow surface, in place of the direct update_turbulent_fluxes! push.
ClimaCoupler.FluxCalculator.accumulate_coupler_fields! — Function
accumulate_coupler_fields!(acc::FluxAccumulator, csf)Add each declared coupler field in csf into the accumulator. Called from FieldExchanger.update_model_sims!, once per coupling step alongside accumulate!. n_steps is not incremented here: accumulate! counts the coupling step for both sums.
ClimaCoupler.FluxCalculator.push_and_reset! — Function
FluxCalculator.push_and_reset!(sim::OceananigansSimulation, acc)Slow-surface push for the exchange-grid path: time-average the per-polygon flux accumulators into the flux-state outputs, push them to the ocean boundary conditions, and reset both the per-polygon and the boundary-space accumulators. Falls back to the generic boundary-space behavior when the exchange grid is not active.
FluxCalculator.push_and_reset!(sim::ClimaSeaIceSimulation, acc)Slow-surface push for the exchange-grid path: time-average the per-polygon ice flux accumulators, push them to the ClimaSeaIce boundary conditions, and reset both the per-polygon and the boundary-space accumulators. Falls back to the generic boundary-space behavior when the exchange grid is not active.
push_and_reset!(sim, acc::FluxAccumulator)Compute the time-averaged flux (dividing the accumulator fields in-place by n_steps), push it to the surface via update_turbulent_fluxes!(sim, ...), then zero the accumulator. A no-op if n_steps is zero.
ClimaCoupler.FluxCalculator.push_ready_accumulators! — Function
push_ready_accumulators!(model_sims, flux_accumulators, t_next; force = false)For each surface model present in flux_accumulators, check whether the surface will step at time t_next. If so, push the time-averaged coupler fluxes via update_sim! and the time-averaged turbulent fluxes via push_and_reset!.
Called by turbulent_fluxes!(cs) with t_next = cs.t[] + cs.Δt_cpl immediately after accumulation.
Pass force = true to skip the will_step check and unconditionally push every non-empty accumulator to its surface. This is used during initialization to populate slow-surface BCs before the simulation starts.
ClimaCoupler.FluxCalculator.reset! — Function
reset!(acc::FluxAccumulator)Zero all accumulator fields and the step counter. Called by push_and_reset! after pushing the averaged fluxes to the surface.