Parameterizations
The following is an overview of how our parameterizations from a software engineering perspective are internally defined and how a new parameterization can be accordingly implemented. For the mathematical formulation and the physics they represent see
We generally recommend reading Extending SpeedyWeather first, which explains the logic of how to extend many of the components in SpeedyWeather. The same logic applies here and we will not iterate on many of the details, but want to highlight which abstract supertype new parameterizations have to subtype respectively and which functions and signatures they have to extend.
In general, every parameterization "class" (e.g. convection) is just a conceptual class for clarity. You can define a custom convection parameterization that acts as a longwave radiation and vice versa. This also means that if you want to implement a parameterization that does not fit into any of the "classes" described here you can still implement it under any name and any class. From a software engineering perspective they are all the same except that they are executed in the order as outlined in Pass on to model construction. That's also why below we write for every parameterization "expected to write into some.array_name
" as this would correspond conceptually to this class, but no hard requirement exists that a parameterization actually does that.
We start by highlighting some general do's and don'ts for parameterization before listing specifics for individual parameterizations.
The parameterizations described here can only be used for the primitive equation models PrimitiveDryModel
and PrimitiveWetModel
as the parameterizations are defined to act on a vertical column. For the 2D models BarotropicModel
and ShallowWaterModel
additional terms have to be defined as a custom forcing or drag, see Extending SpeedyWeather.
Use ColumnVariables
work arrays
When defining a new (mutable) parameterization with (mutable) fields do make sure that is constant during the model integration. While you can and are encouraged to use the initialize!
function to precompute arrays (e.g. something that depends on latitude using model.geometry.latd
) these should not be used as work arrays on every time step of the model integration. The reason is that the parameterization are executed in a parallel loop over all grid points and a mutating parameterization object would create a race condition with undefined behaviour.
Instead, column::ColumnVariables
has several work arrays that you can reuse column.a
and .b
, .c
, .d
. Depending on the number of threads there will be several column
objects to avoid the race condition if several threads would compute the parameterizations for several columns in parallel. An example is the Simplified Betts-Miller convection scheme which needs to compute reference profiles which should not live inside the model.convection
object (as there's always only one of those). Instead this parameterization does the following inside convection!(column::ColumnVariables, ...)
# use work arrays for temp_ref_profile, humid_ref_profile
temp_ref_profile = column.a
humid_ref_profile = column.b
These work arrays have an unknown state so you should overwrite every entry and you also should not use them to retain information after that parameterization has been executed.
Accumulate do not overwrite
Every parameterization either computes tendencies directly or indirectly via fluxes (upward or downward, see Fluxes to tendencies). Both of these are arrays in which every parameterization writes into, meaning they should be accumulated not overwritten. Otherwise any parameterization that executed beforehand is effectively disabled. Hence, do
column.temp_tend[k] += something_you_calculated
not column.temp_tend[k] = something_you_calculated
which would overwrite any previous tendency. The tendencies are reset to zero for every grid point at the beginning of the evaluation of the parameterization for that grid point, meaning you can do tend += a
even for the first parameterization that writes into a given tendency as this translates to tend = 0 + a
.
Pass on to model construction
After defining a (custom) parameterization it is recommended to also define a generator function that takes in the SpectralGrid
object (see How to run SpeedyWeather.jl) as first (positional) argument, all other arguments can then be passed on as keyword arguments with defaults defined. Creating the default convection parameterization for example would be
using SpeedyWeather
spectral_grid = SpectralGrid(trunc=31, nlayers=8)
convection = SimplifiedBettsMiller(spectral_grid, time_scale=Hour(4))
SimplifiedBettsMiller{Float32} <: SpeedyWeather.AbstractConvection
├ nlayers::Int64 = 8
├ time_scale::Second = 14400 seconds
└ relative_humidity::Float32 = 0.7
Further keyword arguments can be added or omitted all together (using the default setup), only the spectral_grid
is required. We have chosen here the name of that parameterization to be convection
but you could choose any other name too. However, with this choice one can conveniently pass on via matching the convection
field inside PrimitiveWetModel
, see Keyword Arguments.
model = PrimitiveWetModel(;spectral_grid, convection)
otherwise we would need to write
my_convection = SimplifiedBettsMiller(spectral_grid)
model = PrimitiveWetModel(;spectral_grid, convection=my_convection)
The following is an overview of what the parameterization fields inside the model are called. See also Tree structure, and therein PrimitiveDryModel and PrimitiveWetModel
model.boundary_layer_drag
model.temperature_relaxation
model.vertical_diffusion
model.convection
model.large_scale_condensation
(PrimitiveWetModel
only)model.shortwave_radiation
model.longwave_radiation
model.surface_thermodynamics
model.surface_wind
model.surface_heat_flux
model.surface_evaporation
(PrimitiveWetModel
only)
Note that the parameterizations are executed in the order of the list above. That way, for example, radiation can depend on calculations in large-scale condensation but not vice versa (only at the next time step).
Custom boundary layer drag
A boundary layer drag can serve two purposes: (1) Actually define a tendency to the momentum equations that acts as a drag term, or (2) calculate the drag coefficient $C$ in column.boundary_layer_drag
that is used in the Surface fluxes.
- subtype
CustomDrag <: AbstractBoundaryLayer
- define
initialize!(::CustomDrag, ::PrimitiveEquation)
- define
boundary_layer_drag!(::ColumnVariables, ::CustomDrag, ::PrimitiveEquation)
- expected to accumulate (
+=
) intocolumn.u_tend
andcolumn.v_tend
- or calculate
column.boundary_layer_drag
to be used in surface fluxes
Custom temperature relaxation
By default, there is no temperature relaxation in the primitive equation models (i.e. temperature_relaxation = NoTemperatureRelaxation()
). This parameterization exists for the Held-Suarez forcing.
- subtype
CustomTemperatureRelaxation <: AbstractTemperatureRelaxation
- define
initialize!(::CustomTemperatureRelaxation, ::PrimitiveEquation)
- define
temperature_relaxation!(::ColumnVariables, ::CustomTemperatureRelaxation, ::PrimitiveEquation)
- expected to accumulate (
+=
) intocolumn.temp_tend
Custom vertical diffusion
While vertical diffusion may be applied to temperature (usually via some form of static energy to account for adiabatic diffusion), humidity and/or momentum, they are grouped together. You can define a vertical diffusion for only one or several of these variables where you then can internally call functions like diffuse_temperature!(...)
for each variable. For vertical diffusion
- subtype
CustomVerticalDiffusion <: AbstractVerticalDiffusion
- define
initialize!(::CustomVerticalDiffusion, ::PrimitiveEquation)
- define
vertical_diffusion!(::ColumnVariables, ::CustomVerticalDiffusion, ::PrimitiveEquation)
- expected to accumulate (
+=
) intocolumn.temp_tend
, and similarly forhumid
,u
, and/orv
- or using fluxes like
column.flux_temp_upward
Custom convection
- subtype
CustomConvection <: AbstractConvection
- define
initialize!(::CustomConvection, ::PrimitiveEquation)
- define
convection!(::ColumnVariables, ::CustomConvection, ::PrimitiveEquation)
- expected to accumulate (
+=
) intocolumn.temp_tend
andcolumn.humid_tend
- or using fluxes,
.flux_temp_upward
or similarly forhumid
ordownward
Note that we define convection here for a model of type PrimitiveEquation
, i.e. both dry and moist convection. If your CustomConvection
only makes sense for one of them use ::PrimitiveDry
or ::PrimitiveWet
instead.
Custom large-scale condensation
- subtype
CustomCondensation <: AbstractCondensation
- define
initialize!(::CustomCondensation, ::PrimitiveWet)
- define
condensation!(::ColumnVariables, ::CustomCondensation, ::PrimitiveWet)
- expected to accumulate (
+=
) intocolumn.humid_tend
andcolumn.temp_tend
- or using fluxes,
.flux_humid_downward
or similarly fortemp
orupward
Custom radiation
AbstractRadiation
has two subtypes, AbstractShortwave
and AbstractLongwave
representing two (from a software engineering perspective) independent parameterizations that are called one after another (short then long). For shortwave
- subtype
CustomShortwave <: AbstractShortwave
- define
initialize!(::CustomShortwave, ::PrimitiveEquation)
- define
shortwave_radiation!(::ColumnVariables, ::CustomShortwave, ::PrimitiveEquation)
- expected to accumulate (
+=
) intocolumn.flux_temp_upward
,.flux_temp_downward
- or directly into the tendency
.temp_tend
For longwave this is similar but using <: AbstractLongwave
and longwave_radiation!
.
Custom surface fluxes
Surface fluxes are the most complicated to customize as they depend on the Ocean and Land model, The land-sea mask, and by default the Bulk Richardson-based drag coefficient, see Custom boundary layer drag. The computation of the surface fluxes is split into four (five if you include the boundary layer drag coefficient in Custom boundary layer drag) components that are called one after another
- Surface thermodynamics to calculate the surface values of lowermost layer variables
- subtype
CustomSurfaceThermodynamics <: AbstractSurfaceThermodynamics
- define
initialize!(::CustomSurfaceThermodynamics, ::PrimitiveEquation)
- define
surface_thermodynamics!(::ColumnVariables, ::CustomSurfaceThermodynamics, ::PrimitiveEquation)
- expected to set
column.surface_temp
,.surface_humid
,.surface_air_density
- Surface wind to calculate wind stress (momentum flux) as well as surface wind used
- subtype
CustomSurfaceWind <: AbstractSurfaceWind
- define
initialize!(::CustomSurfaceWind, ::PrimitiveEquation)
- define
surface_wind_stress!(::ColumnVariables, ::CustomSurfaceWind, ::PrimitiveEquation)
- expected to set
column.surface_wind_speed
for other fluxes - and accumulate (
+=
) intocolumn.flux_u_upward
and.flux_v_upward
- Surface (sensible) heat flux
- subtype
CustomSurfaceHeatFlux <: AbstractSurfaceHeatFlux
- define
initialize!(::CustomSurfaceHeatFlux, ::PrimitiveEquation)
- define
surface_heat_flux!(::ColumnVariables, ::CustomSurfaceHeatFlux, ::PrimitiveEquation)
- expected to accumulate (
+=
) intocolumn.flux_temp_upward
- Surface evaporation
- subtype
CustomSurfaceEvaporation <: AbstractSurfaceEvaporation
- define
initialize!(::CustomSurfaceEvaporation, ::PrimitiveEquation)
- define
surface_evaporation!(::ColumnVariables, ::CustomSurfaceEvaporation, ::PrimitiveEquation)
- expected to accumulate (
+=
) intocolumn.flux_humid_upward
You can customize individual components and leave the other ones as default or by setting them to NoSurfaceWind
, NoSurfaceHeatFlux
, NoSurfaceEvaporation
, but note that without the surface wind the heat and evaporative fluxes are also effectively disabled as they scale with the column.surface_wind_speed
set by default with the surface_wind_stress!
in (2.) above.