Ocean
The ocean in SpeedyWeather.jl is defined with two horizontal fields in the prognostic variables which has a field ocean
, i.e. simulation.prognostic_variables.ocean
.
ocean.sea_surface_temperature
with units of Kelvin [K].ocean.sea_ice_concentration
with units of area fraction [1].
Both are two-dimensional grids using the same grid type and resolution as the dynamical core. So both sea surface temperature and sea ice concentration are globally defined but their mask is defined with The land-sea mask. However, one should still set grid cells where the sea surface temperature is not defined to NaN
in which case any fluxes are zero. This is important when a fractional land-sea mask does not align with the sea surface temperatures to not produce unphysical fluxes. The sea ice concentration is simply set to zero everywhere where there is no sea ice.
Note that neither sea surface temperature, land-sea mask or orography have to agree. It is possible to have an ocean on top of a mountain. For an ocean grid-cell that is (partially) masked by the land-sea mask, its value will be (fractionally) ignored in the calculation of surface fluxes (potentially leading to a zero flux depending on land surface temperatures). For an ocean grid cell that is NaN
but not masked by the land-sea mask, its value is always ignored.
Custom ocean model
Now the ocean model is expected to change ocean.sea_surface_temperature
and/or ocean.sea_ice_concentration
on a given time step. A new ocean model has to be defined as
struct CustomOceanModel <: AbstractOcean
# fields, coefficients, whatever is constant,
end
and can have parameters like CustomOceanModel{T}
and any fields. CustomOceanModel
then needs to extend the following functions
function initialize!(
ocean_model::CustomOceanModel,
model::PrimitiveEquation)
# your code here to initialize the ocean model itself
# you can use other fields from model, e.g. model.geometry
end
function initialize!(
ocean::PrognosticVariablesOcean,
time::DateTime,
ocean_model::CustomOceanModel,
model::PrimitiveEquation)
# your code here to initialize the prognostic variables for the ocean
# namely, ocean.sea_surface_temperature, ocean.sea_ice_concentration, e.g.
# ocean.sea_surface_temperature .= 300 # 300K everywhere
# ocean also has its own time, set initial time
ocean.time = time
end
Note that the first is only to initialize the CustomOceanModel
not the prognostic variables. For example SeasonalOceanClimatology <: AbstractOcean
loads in climatological sea surface temperatures for every time month in the first initialize!
but only writes them (given time
) into the prognostic variables in the second initialize!
. They are internally therefore also called in that order. Note that the function signatures should not be changed except to define a new method for CustomOceanModel
or whichever name you chose.
Then you have to extend the ocean_timestep!
function which has a signature like
function ocean_timestep!(
ocean::PrognosticVariablesOcean,
time::DateTime,
ocean_model::CustomOceanModel,
)
# your code here to change the ocean.sea_surface_temperature and/or
# ocean.sea_ice_concentration on any timestep
# you should also synchronize the clocks when executed like
# ocean.time = time
end
which is called on every time step before the land and before the parameterization and therefore also before the dynamics. You can schedule the execution with Schedules or you can use the ocean.time
time to determine when last the ocean time step was executed and whether it should be executed now, e.g. (time - ocean.time) < ocean_model.Δt && return nothing
would not execute unless the period of the ocean_model.Δt
time step has passed. Note that the ocean.sea_surface_temperature
or .sea_ice_concentration
are unchanged if the ocean time step is not executed, meaning that the sea surface temperatures for example can lag behind the dynamical core for some days essentially assuming constant temperatures throughout that period. Any ocean model with constant temperatures and sea ice should just return nothing
.