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!).
FluxCalculator API
ClimaCoupler.FluxCalculator.turbulent_fluxes! — Functionturbulent_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.compute_surface_fluxes! — Functioncompute_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 on its own surface space rather than in coupler space, so we cannot use the generic coupler-space 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::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 — Functionget_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! — FunctionFluxCalculator.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 already area-weighted, so there's no need to weight them again.
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! — Functionupdate_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 — Functionget_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 — TypeFluxAccumulator{F}Time-accumulator for the five turbulent flux fields between a surface model and the atmosphere, used for slow surfaces (having dt > Δt_cpl) whose fluxes are computed explicitly. The fields hold the running sum of per-surface (not area-weighted) turbulent fluxes computed at each coupling step, and n_steps counts the number of contributions added since the last reset.
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! — Functionaccumulate!(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.push_and_reset! — Functionpush_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! — Functionpush_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, compute the time-averaged flux from the accumulator and write it to the surface boundary conditions 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! — Functionreset!(acc::FluxAccumulator)Zero all accumulator fields and reset the step counter. Called by push_and_reset! after pushing the averaged flux to the surface.