Surface fluxes
The surfaces fluxes in SpeedyWeather represent the exchange of momentum, heat, and humidity/moisture between ocean and land as surface into or out of the lowermost atmospheric layer (see Vertical coordinates for how vertical layers are indexed). Surface fluxes of momentum represent a drag that the boundary layer wind experiences due to friction over more or less rough ground on land or over sea. Surface fluxes of heat represent a sensible heat flux from a warmer or colder ocean or land into or out of the surface layer of the atmosphere. Surface fluxes of humidity represent evaporation of sea water or condensation of humidity onto the ocean surface. Similarly, humidity fluxes over land with a given soil moisture and vegetation's evapotranspiration.
Surface flux implementations
Currently implemented surface fluxes of momentum are
using SpeedyWeather
subtypes(SpeedyWeather.AbstractSurfaceMomentumFlux)1-element Vector{Any}:
SurfaceMomentumFluxInterdependence of surface flux computations
model.boundary_layer.surface_condition extrapolates atmospheric variables (wind, temperature, humidity) to the surface. model.boundary_layer computes centrally a drag coefficient for surface momentum, heat and humidity fluxes. Setting model.boundary_layer.surface_condition = nothing or model.boundary_layer = nothing will therefore disable all other surface fluxes unless they have use_boundary_layer_drag = false which lets them use independently their own drag coefficient.
with more explanation below. The surface heat fluxes currently implemented are
subtypes(SpeedyWeather.AbstractSurfaceHeatFlux)5-element Vector{Any}:
PrescribedLandHeatFlux
PrescribedOceanHeatFlux
SurfaceHeatFlux
SurfaceLandHeatFlux
SurfaceOceanHeatFluxand the surface humidity fluxes (this does not include Convection or Large-scale condensation those are precipitation) implemented are
subtypes(SpeedyWeather.AbstractSurfaceHumidityFlux)5-element Vector{Any}:
PrescribedLandHumidityFlux
PrescribedOceanHumidityFlux
SurfaceHumidityFlux
SurfaceLandHumidityFlux
SurfaceOceanHumidityFluxThe calculation of thermodynamic quantities at the surface (air density, temperature, humidity) and the computation of drag coefficients (which is used by default for the surface fluxes above) are handled through the model.boundary_layer where currently implemented are
subtypes(SpeedyWeather.AbstractBoundaryLayer)1-element Vector{Any}:
BoundaryLayerSee Parameterizations for the general parameterization interface these implement.
Boundary layer
The model.boundary_layer is a composite of four components that run one after another for every grid point, each writing variables the next one (and the surface fluxes) read
BoundaryLayer
├── surface_condition # 1. surface air temperature, density, wind speed
│ ├── wind_slowdown # ratio of near-surface to lowest-layer wind
│ └── wind_gust # sub-grid scale gusts added to the surface wind speed
├── neutral_wind_speed # 2. stability-corrected (neutral) surface wind speed
├── surface_roughness # 3. roughness lengths for momentum, heat, humidity over land and ocean
│ ├── land_surface_roughness
│ └── ocean_surface_roughness
│ ├── charnock
│ ├── sea_ice_momentum_roughness
│ └── max_ocean_momentum_roughness
└── drag # 4. drag coefficients for momentum, heat, humidity over land and oceanCircularity of wind slowdown and ocean roughness
The LearnedWindSlowdown (default) reduces the lowermost-layer wind towards the surface depending on the momentum roughness length LearnedOceanRoughness (default) computes surface_condition → neutral_wind_speed → surface_roughness → surface_condition). Over land there is no such circularity, as the land roughness depends on vegetation, snow and soil only for now.
There are several ways to resolve this circularity, each with a downside:
Lag: use the roughness from the previous time step. This introduces a hidden state that is not part of the prognostic variables (it is not, e.g., written to restart files), needs an ad-hoc roughness on the first time step, and can oscillate in time at low wind speeds, where the learned ocean roughness drops by many orders of magnitude.
Iterate: repeat surface condition → neutral wind speed → surface roughness within a time step until converged. Stateless and consistent, but this multiplies the cost of these parameterizations and converges slowly at the same low-wind points.
Decouple: use fixed roughness lengths in the wind slowdown only. Stateless and cheap, but the wind slowdown does not see the actual roughness.
The default is to lag. We set LearnedWindSlowdown with use_computed_roughness = true which uses the roughness from the previous timestep, and we fall back to the fixed roughness length on the first time step.
To decouple, one sets use_computed_roughness = false, and the LearnedWindSlowdown uses fixed roughness lengths over land and ocean (land_roughness = 0.1 m, ocean_roughness = 1e-4 m, weighted by the land fraction) so that the surface condition depends only on the current state. The computed roughness is still used for the drag coefficients.
The iterative solution is currently not implemented.
The subcomponents of SurfaceCondition are
subtypes(SpeedyWeather.AbstractWindSlowdown)2-element Vector{Any}:
ConstantWindSlowdown
LearnedWindSlowdownsubtypes(SpeedyWeather.AbstractWindGust)2-element Vector{Any}:
BeljaarsWindGust
ConstantWindGustthe neutral wind speed
subtypes(SpeedyWeather.AbstractNeutralWindSpeed)1-element Vector{Any}:
NeutralWindSpeedwhich can also be set to nothing (BoundaryLayer(spectral_grid, neutral_wind_speed = nothing)). Parameterizations that require a neutral wind speed (e.g. LearnedOceanRoughness) then use the (non-neutral) surface wind speed instead, i.e. without stability correction.
the surface roughness, with its land and ocean subcomponents (used by LearnedSurfaceRoughness)
subtypes(SpeedyWeather.AbstractSurfaceRoughness)4-element Vector{Any}:
ConstantSurfaceRoughness
LearnedSurfaceRoughness
SpeedyWeather.AbstractLandRoughness
SpeedyWeather.AbstractOceanRoughnesssubtypes(SpeedyWeather.AbstractLandRoughness)1-element Vector{Any}:
LearnedLandRoughnesssubtypes(SpeedyWeather.AbstractOceanRoughness)1-element Vector{Any}:
LearnedOceanRoughnessand the functors used within the learned ocean roughness
subtypes(SpeedyWeather.AbstractRoughnessFunctor)3-element Vector{Any}:
Charnock
MaxOceanMomentumRoughness
SeaIceMomentumRoughnessand finally the drag coefficients
subtypes(SpeedyWeather.AbstractBoundaryLayerDrag)2-element Vector{Any}:
BulkRichardsonDrag
ConstantDragAll of these take the spectral_grid as first argument and their parameters (or subcomponents) as keyword arguments, so that they can be composed together and passed on to the model constructor, e.g.
spectral_grid = SpectralGrid(truncation = 32, nlayers = 8)
# constant wind slowdown and gusts instead of the defaults
surface_condition = SurfaceCondition(
spectral_grid,
wind_slowdown = ConstantWindSlowdown(spectral_grid, wind_slowdown = 0.9),
wind_gust = ConstantWindGust(spectral_grid, wind_gust = 1),
)
# learned roughness but with a different Charnock parameterization
ocean_surface_roughness = LearnedOceanRoughness(
spectral_grid,
charnock = Charnock(spectral_grid, charnock_min = 1.0e-4),
)
surface_roughness = LearnedSurfaceRoughness(spectral_grid; ocean_surface_roughness)
# or constant roughness lengths instead
# surface_roughness = ConstantSurfaceRoughness(spectral_grid, momentum_roughness_ocean = 2e-4)
boundary_layer = BoundaryLayer(
spectral_grid;
surface_condition,
surface_roughness,
drag = BulkRichardsonDrag(spectral_grid, critical_Richardson = 5),
)
model = PrimitiveWetModel(spectral_grid; boundary_layer)
model.boundary_layerBoundaryLayer{SurfaceCondition{ConstantWind...} <: SpeedyWeather.AbstractBoundaryLayer
├ surface_condition::SurfaceCondition{ConstantWindSlowdown{Float32}, Constant...
├ neutral_wind_speed::NeutralWindSpeed{Float32}
├ surface_roughness::LearnedSurfaceRoughness{LearnedLandRoughness{Float32}, L...
└ drag::BulkRichardsonDrag{Float32}Fluxes to tendencies
In SpeedyWeather.jl, parameterizations can be defined either in terms of tendencies for a given layer or as fluxes between two layers including the surface flux and a top-of-the-atmosphere flux. The upward flux
A quick overview of the units
| Quantity | Variable | Unit | Flux unit |
|---|---|---|---|
| Momentum | Velocity | ||
| Heat | Temperature | ||
| Moisture | Specific humidity |
The time-stepping in SpeedyWeather.jl (see Time integration) eventually requires tendencies which are calculated from the absorbed fluxes of momentum
with gravity
The temperature tendency is calculated from the absorbed heat flux as
with heat capacity of air at constant pressure
Time integration of surface fluxes
Surface fluxes as a relaxation
The bulk surface fluxes of momentum, heat and humidity below all relax a variable
with the conductance
| Flux | ||||
|---|---|---|---|---|
| Momentum | ||||
| Heat | ||||
| Humidity |
with
A flux
The relaxation time scale FriersonSigmaCoordinates for example, the lowermost layer is about 3 times thinner than with equi-distant
Heat fluxes and the heat capacity
The sensible heat flux is defined in
and
Exact solution and flux damping
Holding
How does this compare to what the model actually does? The time stepping in SpeedyWeather is explicit: all tendencies, including those from the surface fluxes, are calculated from the state at the beginning of the time step and then used to step forward. For the surface flux alone this is a forward Euler step
with
So within one time step the explicit step multiplies the deviation
Can we get the exact solution but keep the explicit time stepping? Compare the change in
With
which only depends on
There is also a physical interpretation of
So
For small
However, evaluating ExponentialFluxes for all surface fluxes increases the time per time step by about 5%, compared to about 1% for ImplicitFluxes. We therefore look for cheaper approximations of
Explicit,
, only approximates the exact solution to first order in. replaces, which overshoots the surface value for and is unstable ( ) for .Implicit (backward Euler) evaluates the flux with
at the end of the time step, , so that . This is unconditionally stable and monotone, and has the correctbehaviour for large , but is only first-order accurate, , so it overdamps for small. Rational,
, matches the expansion of the exactto second order, , and has the samebehaviour for large , at the cost of the implicit scheme (one division) instead of an exp. becomes negative for with a minimum of aboutnear , meaning thatovershoots the surface value by at most 10% of the initial deviation, then tends to zero for large .
| Scheme | Single step | Cost | ||
|---|---|---|---|---|
ExplicitFluxes() | overshoots for | none | ||
ImplicitFluxes() | monotone, stable, first order | 1 division | ||
ExponentialFluxes() | monotone, stable, exact for fixed | exp, ~5% of a time step at T32L8 | ||
RationalFluxes() (default) | stable, ≤10% overshoot for | 1 division |
Because PrescribedOceanHeatFlux) are applied as given.
Choosing the time integration
The scheme is chosen per flux, for example
spectral_grid = SpectralGrid(truncation = 31, nlayers = 8)
surface_momentum_flux = SurfaceMomentumFlux(spectral_grid, time_integration = ExponentialFluxes())
surface_heat_flux = SurfaceHeatFlux(
spectral_grid,
ocean = SurfaceOceanHeatFlux(spectral_grid, time_integration = ImplicitFluxes()),
land = SurfaceLandHeatFlux(spectral_grid, time_integration = ExplicitFluxes()),
)
model = PrimitiveWetModel(spectral_grid; surface_momentum_flux, surface_heat_flux)and similarly for SurfaceOceanHumidityFlux and SurfaceLandHumidityFlux.
Bulk Richardson-based drag coefficient
All surface fluxes depend on a dimensionless drag coefficient
with
The virtual dry static energy at the surface
computed separately over land and ocean. Over ocean,
Then the drag coefficient
with the neutral drag coefficient boundary_layer.surface_roughness, per surface and per flux type. There is a maximum drag
which depends on the temperature
with
with drag_min) a minimum drag that avoids vanishing surface fluxes in strongly stable conditions.
Surface momentum fluxes
The surface momentum flux is calculated from the surface wind velocities
meaning it is scaled down by
such that for low wind speeds the fluxes are somewhat higher because of unresolved winds on smaller time and length scales. The momentum flux is then
with
Surface heat fluxes
The surface heat flux is proportional to the difference of the surface air temperature
in
Surface humidity fluxes
The surface humidity flux, i.e. evaporation/condensation of soil moisture over land and evaporation of sea water over the ocean or condensation of humidity on the ocean surface is proportional to the difference of the surface specific humidity
The surface humidity flux in
with
following the Fortran SPEEDY documentation (), which follows . The variables (or spatially prescribed arrays) are water content in the top soil layer model.land.geometry), soil wetness at field capacity (volume fraction)
Land-sea mask
SpeedyWeather uses a fractional land-sea mask, i.e. for every grid-point
1 indicates land
0 indicates ocean
a value in between indicates a grid-cell partially covered by ocean and land
The land-sea mask determines solely how to weight the surface fluxes coming from land or from the ocean. For the sensible heat fluxes this uses land and sea surface temperatures and weights the respective fluxes proportional to the fractional mask. Similar for humidity fluxes. You can therefore define an ocean on top of a mountain, or vice versa. Let
Note that this weighting is purely multiplicative: it does not protect against NaN propagating into NaN. Land and sea surface temperatures (and other quantities entering the flux calculation, such as soil moisture) must therefore be finite everywhere, even where the land-sea mask is 0 or 1, i.e. a NaN land or sea surface temperature is not supported anymore. Use a fallback temperature for grid cells without "real" land or ocean instead (e.g. SlabOcean's land_temperature, or SeasonalLandTemperature's ocean_temperature).
Setting the land-sea mask to ocean therefore will disable any fluxes that may come from land, and vice versa. However, with an ocean-everywhere land-sea mask you must still also define sea surface temperatures everywhere, otherwise the fluxes in those regions will be incorrect rather than simply zero.
For more details see The land-sea mask implementation section.